diff --git a/.rubocop_todo/rspec/feature_category.yml b/.rubocop_todo/rspec/feature_category.yml index a3811dfb2dd..c7c5e3545e6 100644 --- a/.rubocop_todo/rspec/feature_category.yml +++ b/.rubocop_todo/rspec/feature_category.yml @@ -3980,7 +3980,6 @@ RSpec/FeatureCategory: - 'spec/lib/gitlab/usage_data_counters/merge_request_widget_extension_counter_spec.rb' - 'spec/lib/gitlab/usage_data_counters/note_counter_spec.rb' - 'spec/lib/gitlab/usage_data_counters/package_event_counter_spec.rb' - - 'spec/lib/gitlab/usage_data_counters/productivity_analytics_counter_spec.rb' - 'spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb' - 'spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb' - 'spec/lib/gitlab/usage_data_counters/search_counter_spec.rb' diff --git a/.rubocop_todo/style/class_and_module_children.yml b/.rubocop_todo/style/class_and_module_children.yml index e6a3076b089..c389a3966d2 100644 --- a/.rubocop_todo/style/class_and_module_children.yml +++ b/.rubocop_todo/style/class_and_module_children.yml @@ -510,7 +510,6 @@ Style/ClassAndModuleChildren: - 'lib/gitlab/usage_data_counters/ci_template_unique_counter.rb' - 'lib/gitlab/usage_data_counters/designs_counter.rb' - 'lib/gitlab/usage_data_counters/note_counter.rb' - - 'lib/gitlab/usage_data_counters/productivity_analytics_counter.rb' - 'lib/gitlab/usage_data_counters/snippet_counter.rb' - 'lib/gitlab/usage_data_counters/source_code_counter.rb' - 'lib/gitlab/usage_data_counters/wiki_page_counter.rb' diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ae50ea0307..c082652e7cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 16.11.1 (2024-04-24) + +### Security (5 changes) + +- [Validation for encoded formatting characters](gitlab-org/security/gitlab@fc42e4b96ae1ac3cd766569d62d025cbf23ef16c) ([merge request](gitlab-org/security/gitlab!3979)) +- [Forbid untrusted sign-ins to GitLab with Bitbucket and fix related uid](gitlab-org/security/gitlab@ef083c319e67072029787cd5c6a588562984a58c) ([merge request](gitlab-org/security/gitlab!3983)) +- [Ensure PAT scope is validated everywhere for GraphQL/ActionCable](gitlab-org/security/gitlab@1847435210161d95b9c5fcd079380e7f2892195f) ([merge request](gitlab-org/security/gitlab!3975)) +- [Protect against ReDoS in FileFinder with wildcard filters](gitlab-org/security/gitlab@dc16f3baa640ca8d5b223782ef3d58369423a1dd) ([merge request](gitlab-org/security/gitlab!3969)) +- [fix: Validate security report version against schema during parsing](gitlab-org/security/gitlab@55e58d49051aa42938ec1d159b5e7eb3c47d2eb1) ([merge request](gitlab-org/security/gitlab!3967)) + ## 16.11.0 (2024-04-17) ### Added (121 changes) @@ -605,6 +615,20 @@ entry. - [Finalize the backfill migration for onboarding status step url](gitlab-org/gitlab@f986c1b1cf00968ff106136893bfe68d47895c69) ([merge request](gitlab-org/gitlab!147278)) - [Remove ClusterRepositoryCache migration helper class](gitlab-org/gitlab@f71a7a94ce8d70d9d378ebc225b802b58f0ae006) ([merge request](gitlab-org/gitlab!147244)) +## 16.10.4 (2024-04-24) + +### Fixed (1 change) + +- [Update vulnerability_reads scanner in the ingestion pipeline](gitlab-org/security/gitlab@14b8876233e5dd29149426fd88bab0fc4f014d46) **GitLab Enterprise Edition** + +### Security (5 changes) + +- [Validation for encoded formatting characters](gitlab-org/security/gitlab@4cd13c705ce1a94152fb2fd6fcaa77e90e6441e5) ([merge request](gitlab-org/security/gitlab!3950)) +- [Forbid untrusted sign-ins to GitLab with Bitbucket and fix related uid](gitlab-org/security/gitlab@5d3c3a599cc5560dea2236474309537536428cdc) ([merge request](gitlab-org/security/gitlab!3984)) +- [Ensure PAT scope is validated everywhere for GraphQL/ActionCable](gitlab-org/security/gitlab@079dfee8cff9da9075eec7c03ce002e87eeebfff) ([merge request](gitlab-org/security/gitlab!3976)) +- [Protect against ReDoS in FileFinder with wildcard filters](gitlab-org/security/gitlab@0e7e54050f1c4829b1d55aac85bd4e9cd96f1580) ([merge request](gitlab-org/security/gitlab!3960)) +- [fix: Validate security report version against schema during parsing](gitlab-org/security/gitlab@217040b1062caad501d60af387c47cff758788a1) ([merge request](gitlab-org/security/gitlab!3956)) + ## 16.10.3 (2024-04-12) No changes. @@ -1319,6 +1343,16 @@ No changes. - [Add sharding keys for system_access](gitlab-org/gitlab@62c2fd4788e62e46f1469e2f18d178840e8e3df2) ([merge request](gitlab-org/gitlab!142501)) - [Add sharding keys for purchase](gitlab-org/gitlab@9c3843da74714c72483c17489d5d3d68ceffd2c8) ([merge request](gitlab-org/gitlab!142505)) +## 16.9.6 (2024-04-24) + +### Security (5 changes) + +- [Validation for encoded formatting characters](gitlab-org/security/gitlab@de8dc151e5ef3f07cf50839e50645df6ec12f5a5) ([merge request](gitlab-org/security/gitlab!3951)) +- [Forbid untrusted sign-ins to GitLab with Bitbucket and fix related uid](gitlab-org/security/gitlab@94496a91c17a0f73202cd5c55abc93395825c68c) ([merge request](gitlab-org/security/gitlab!3985)) +- [Ensure PAT scope is validated everywhere for GraphQL/ActionCable](gitlab-org/security/gitlab@0dccf32b71614584e05a8590b21a902220e8c701) ([merge request](gitlab-org/security/gitlab!3977)) +- [Protect against ReDoS in FileFinder with wildcard filters](gitlab-org/security/gitlab@60a7418ec10f7c6f4ef9bcc75b2fec71255ddcc3) ([merge request](gitlab-org/security/gitlab!3961)) +- [fix: Validate security report version against schema during parsing](gitlab-org/security/gitlab@ce709ff78fd8f18024383085d6ac0bf43fa2efbb) ([merge request](gitlab-org/security/gitlab!3957)) + ## 16.9.5 (2024-04-12) No changes. diff --git a/Gemfile b/Gemfile index f6ecd52da52..13ff99f7b68 100644 --- a/Gemfile +++ b/Gemfile @@ -358,7 +358,6 @@ gem 'gitlab-license', '~> 2.4', feature_category: :shared gem 'rack-attack', '~> 6.7.0' # rubocop:todo Gemfile/MissingFeatureCategory # Sentry integration -gem 'sentry-raven', '~> 3.1', feature_category: :error_tracking gem 'sentry-ruby', '~> 5.10.0', feature_category: :error_tracking gem 'sentry-rails', '~> 5.10.0', feature_category: :error_tracking gem 'sentry-sidekiq', '~> 5.10.0', feature_category: :error_tracking @@ -411,7 +410,6 @@ group :opentelemetry do gem 'opentelemetry-instrumentation-action_view', feature_category: :tooling gem 'opentelemetry-instrumentation-aws_sdk', feature_category: :tooling gem 'opentelemetry-instrumentation-http', feature_category: :tooling - gem 'opentelemetry-instrumentation-active_model_serializers', feature_category: :tooling gem 'opentelemetry-instrumentation-concurrent_ruby', feature_category: :tooling gem 'opentelemetry-instrumentation-ethon', feature_category: :tooling gem 'opentelemetry-instrumentation-excon', feature_category: :tooling @@ -592,7 +590,7 @@ gem 'ssh_data', '~> 1.3' # rubocop:todo Gemfile/MissingFeatureCategory gem 'spamcheck', '~> 1.3.0' # rubocop:todo Gemfile/MissingFeatureCategory # Gitaly GRPC protocol definitions -gem 'gitaly', '~> 16.11.0.pre.rc1', feature_category: :gitaly +gem 'gitaly', '~> 17.0.0.pre.rc2', feature_category: :gitaly # KAS GRPC protocol definitions gem 'kas-grpc', '~> 0.4.0', feature_category: :deployment_management diff --git a/Gemfile.checksum b/Gemfile.checksum index 476c0f8f31e..3dbd7f5e4d9 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -206,7 +206,7 @@ {"name":"gettext","version":"3.4.9","platform":"ruby","checksum":"292864fe6a15c224cee4125a4a72fab426fdbb280e4cff3cfe44935f549b009a"}, {"name":"gettext_i18n_rails","version":"1.12.0","platform":"ruby","checksum":"6ac4817731a9e2ce47e1e83381ac34f9142263bc2911aaaafb2526d2f1afc1be"}, {"name":"git","version":"1.18.0","platform":"ruby","checksum":"c9b80462e4565cd3d7a9ba8440c41d2c52244b17b0dad0bfddb46de70630c465"}, -{"name":"gitaly","version":"16.11.0.pre.rc1","platform":"ruby","checksum":"24334f5f3fd5b6c3d278eea9fe2b6732dd08e87a4146cd4374615506b1a6e7ae"}, +{"name":"gitaly","version":"17.0.0.pre.rc2","platform":"ruby","checksum":"2fa5998d3cbc37ba66bef428fb7150ab3f8b4105a4bd29ea48d965fae98ead43"}, {"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"}, {"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"}, {"name":"gitlab-dangerfiles","version":"4.7.0","platform":"ruby","checksum":"2576876a8dcb7290853fc3aef8048001cfe593b87318dd0016959d42e0e145ca"}, @@ -453,7 +453,6 @@ {"name":"opentelemetry-instrumentation-action_pack","version":"0.9.0","platform":"ruby","checksum":"c5df8472afc9cdbfc1425d9af7816b9cfc1a1a69b86621f1fc624974bd9acb9a"}, {"name":"opentelemetry-instrumentation-action_view","version":"0.7.0","platform":"ruby","checksum":"bc7c714be0b4bb76843085c29ecc9465e65cb7fe6722e34c71629e44f8c3cb75"}, {"name":"opentelemetry-instrumentation-active_job","version":"0.7.1","platform":"ruby","checksum":"da24806c9d92fe580db42638f6c763fe1324ff90aa147d45d4247f8052c68089"}, -{"name":"opentelemetry-instrumentation-active_model_serializers","version":"0.20.1","platform":"ruby","checksum":"8c47f859fc925c4c078d37f5a13c55f4ba9751f880aa64d0c9568f3f59a3efaa"}, {"name":"opentelemetry-instrumentation-active_record","version":"0.7.0","platform":"ruby","checksum":"327ca53ebb74187b463ab05c1d89508552e9cd9122db0843ad1f27930ee91797"}, {"name":"opentelemetry-instrumentation-active_support","version":"0.5.1","platform":"ruby","checksum":"03898327e8284410b8935a3d3b980bda56e2063eb5a7d30acf75487dd6934a66"}, {"name":"opentelemetry-instrumentation-aws_sdk","version":"0.5.1","platform":"ruby","checksum":"496a8d13c59ff4d08dcd69b16db97c013398173295058593aa0c2f3ef3090cce"}, @@ -623,7 +622,6 @@ {"name":"selenium-webdriver","version":"4.19.0","platform":"ruby","checksum":"4c8bd1d6016a456154b4ba71a3bb4d532a0ae185a38acf9cec0acbd38b4e5066"}, {"name":"semver_dialects","version":"2.0.2","platform":"ruby","checksum":"60059c9f416f931b5212d862fad2879d6b9affb8e0b9afb0d91b793639c116fe"}, {"name":"sentry-rails","version":"5.10.0","platform":"ruby","checksum":"99aa2fac136c26942eb1897c65de65dac88ad43ac5eb183ff20711287a137ebd"}, -{"name":"sentry-raven","version":"3.1.2","platform":"ruby","checksum":"103d3b122958810d34898ce2e705bcf549ddb9d855a70ce9a3970ee2484f364a"}, {"name":"sentry-ruby","version":"5.10.0","platform":"ruby","checksum":"115c24c0aee1309210f3a2988fb118e2bec1f11609feeda90e694388b1183619"}, {"name":"sentry-sidekiq","version":"5.10.0","platform":"ruby","checksum":"cc81018d0733fb1be3fb5641c9e0b61030bbeaa1d0b23ca64797d70def7aea1a"}, {"name":"sexp_processor","version":"4.17.1","platform":"ruby","checksum":"91110946720307f30bf1d549e90d9a529fef40d1fc471c069c8cca7667015da0"}, diff --git a/Gemfile.lock b/Gemfile.lock index da5d5dcac70..d6740b03c1d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -680,7 +680,7 @@ GEM git (1.18.0) addressable (~> 2.8) rchardet (~> 1.8) - gitaly (16.11.0.pre.rc1) + gitaly (17.0.0.pre.rc2) grpc (~> 1.0) gitlab (4.19.0) httparty (~> 0.20) @@ -1253,9 +1253,6 @@ GEM opentelemetry-instrumentation-active_job (0.7.1) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-active_model_serializers (0.20.1) - opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-active_record (0.7.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) @@ -1669,8 +1666,6 @@ GEM sentry-rails (5.10.0) railties (>= 5.0) sentry-ruby (~> 5.10.0) - sentry-raven (3.1.2) - faraday (>= 1.0) sentry-ruby (5.10.0) concurrent-ruby (~> 1.0, >= 1.0.2) sentry-sidekiq (5.10.0) @@ -2008,7 +2003,7 @@ DEPENDENCIES gdk-toogle (~> 0.9) gettext (~> 3.4, >= 3.4.9) gettext_i18n_rails (~> 1.12.0) - gitaly (~> 16.11.0.pre.rc1) + gitaly (~> 17.0.0.pre.rc2) gitlab-backup-cli! gitlab-chronic (~> 0.10.5) gitlab-dangerfiles (~> 4.7.0) @@ -2142,7 +2137,6 @@ DEPENDENCIES opentelemetry-instrumentation-action_pack opentelemetry-instrumentation-action_view opentelemetry-instrumentation-active_job - opentelemetry-instrumentation-active_model_serializers opentelemetry-instrumentation-active_record opentelemetry-instrumentation-active_support opentelemetry-instrumentation-aws_sdk @@ -2224,7 +2218,6 @@ DEPENDENCIES selenium-webdriver (~> 4.19) semver_dialects (~> 2.0, >= 2.0.2) sentry-rails (~> 5.10.0) - sentry-raven (~> 3.1) sentry-ruby (~> 5.10.0) sentry-sidekiq (~> 5.10.0) shoulda-matchers (~> 5.1.0) diff --git a/app/assets/javascripts/ci/catalog/components/details/ci_resource_header.vue b/app/assets/javascripts/ci/catalog/components/details/ci_resource_header.vue index a027363a0c2..3001bb2e1ae 100644 --- a/app/assets/javascripts/ci/catalog/components/details/ci_resource_header.vue +++ b/app/assets/javascripts/ci/catalog/components/details/ci_resource_header.vue @@ -13,6 +13,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { cleanLeadingSeparator } from '~/lib/utils/url_utility'; import { formatDate } from '~/lib/utils/datetime_utility'; import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; +import Markdown from '~/vue_shared/components/markdown/non_gfm_markdown.vue'; import CiVerificationBadge from '../shared/ci_verification_badge.vue'; import CiResourceAbout from './ci_resource_about.vue'; import CiResourceHeaderSkeletonLoader from './ci_resource_header_skeleton_loader.vue'; @@ -35,6 +36,7 @@ export default { GlDisclosureDropdown, GlDisclosureDropdownItem, GlLink, + Markdown, }, directives: { GlTooltip: GlTooltipDirective, @@ -198,9 +200,7 @@ export default { v-if="isLoadingSharedData" class="gl-animate-skeleton-loader gl-h-4 gl-rounded-base gl-my-3 gl-max-w-20!" > -

- {{ resource.description }} -

+
- {{ resource.description }} +
': 'gt', + '<': 'lt', + '!~': 'not_like', + '=~': 'like', +}; + +const SEARCH_FILTER_NAME = 'search'; + +/** + * Return the query parameter name, given an operator and param key + * + * e.g + * if paramKey is 'foo' and operator is "=", param name is 'foo' + * if paramKey is 'foo' and operator is "!=", param name is 'not[foo]' + * + * @param {String} paramKey - The parameter name + * @param {String} operator - The operator + * @returns String | undefined - Query param name + */ +function getFilterParamName(filterName, operator, filterToQueryMapping) { + const paramKey = filterToQueryMapping[filterName]; + if (!paramKey) return undefined; + + if (operator === '=' || filterName === SEARCH_FILTER_NAME) { + return paramKey; + } + + const prefix = FILTER_OPERATORS_PREFIX[operator]; + if (prefix) { + return `${prefix}[${paramKey}]`; + } + + return undefined; +} + +/** + * Process `filterValue` and append the proper query params to the `searchParams` arg, using `nameParam` and `valueParam` + * + * It mutates `searchParams` + * + * @param {String} filterValue The filter value, in the format `attribute_name=attribute_value` + * @param {String} filterOperator The filter operator + * @param {URLSearchParams} searchParams The URLSearchParams object where to append the proper query params + * @param {String} nameParam The query param name for the attribute name + * @param {String} nameParam The query param name for the attribute value + * + * e.g. + * + * handleAttributeFilter('foo=bar', '=', searchParams, 'attr_name', 'attr_value') + * + * it adds { attr_name: 'foo', attr_value: 'bar'} to `searchParams` + * + */ +function handleAttributeFilter(filterValue, filterOperator, searchParams, nameParam, valueParam) { + const [attrName, attrValue] = filterValue.split('='); + if (attrName && attrValue) { + if (filterOperator === '=') { + searchParams.append(nameParam, attrName); + searchParams.append(valueParam, attrValue); + } + } +} + +function addDateRangeFilterToQueryParams(dateRangeFilter, params) { + if (!dateRangeFilter || !params) return; + + const { value, endDate, startDate } = dateRangeFilter; + if (value === CUSTOM_DATE_RANGE_OPTION) { + if (isValidDate(startDate) && isValidDate(endDate)) { + params.append('start_time', startDate.toISOString()); + params.append('end_time', endDate.toISOString()); + } + } else if (typeof value === 'string') { + params.append('period', value); + } +} + +/** **** + * + * Tracing API + * + * ***** */ + async function fetchTrace(tracingUrl, traceId) { try { if (!traceId) { @@ -67,88 +165,23 @@ const SUPPORTED_TRACING_FILTERS = { traceId: ['=', '!='], attribute: ['='], status: ['=', '!='], - // free-text 'search' temporarily ignored https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/2309 + // 'search' temporarily ignored https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/2309 }; /** - * Mapping of filter name to query param + * Mapping of filter name to tracing query param */ const TRACING_FILTER_TO_QUERY_PARAM = { durationMs: 'duration_nano', operation: 'operation', service: 'service_name', - period: 'period', traceId: 'trace_id', - attribute: 'attribute', status: 'status', + // `attribute` is handled separately, see `handleAttributeFilter` method + // `period` is handled separately, see `handleTracingPeriodFilter` method }; -const FILTER_OPERATORS_PREFIX = { - '!=': 'not', - '>': 'gt', - '<': 'lt', - '!~': 'not_like', - '=~': 'like', -}; - -/** - * Return the query parameter name, given an operator and param key - * - * e.g - * if paramKey is 'foo' and operator is "=", param name is 'foo' - * if paramKey is 'foo' and operator is "!=", param name is 'not[foo]' - * - * @param {String} paramKey - The parameter name - * @param {String} operator - The operator - * @returns String | undefined - Query param name - */ -function getFilterParamName(paramKey, operator) { - if (operator === '=') { - return paramKey; - } - - const prefix = FILTER_OPERATORS_PREFIX[operator]; - if (prefix) { - return `${prefix}[${paramKey}]`; - } - - return undefined; -} - -/** - * Builds the tracing query param name for the given filter and operator - * - * @param {String} filterName - The filter name - * @param {String} operator - The operator - * @returns String | undefined - Query param name - */ -function getTracingFilterParamName(filterName, operator) { - const paramKey = TRACING_FILTER_TO_QUERY_PARAM[filterName]; - if (!paramKey) return undefined; - - return getFilterParamName(paramKey, operator); -} - -/** - * Process `filterValue` and append the proper query params to the `searchParams` arg - * - * It mutates `searchParams` - * - * @param {String} filterValue The filter value, in the format `attribute_name=attribute_value` - * @param {String} filterOperator The filter operator - * @param {URLSearchParams} searchParams The URLSearchParams object where to append the proper query params - */ -function handleAttributeFilter(filterValue, filterOperator, searchParams) { - const [attrName, attrValue] = filterValue.split('='); - if (attrName && attrValue) { - if (filterOperator === '=') { - searchParams.append('attr_name', attrName); - searchParams.append('attr_value', attrValue); - } - } -} - -function handlePeriodFilter(rawValue, filterName, filterParams) { +function handleTracingPeriodFilter(rawValue, filterName, filterParams) { if (rawValue.trim().indexOf(' ') < 0) { filterParams.append(filterName, rawValue.trim()); return; @@ -189,13 +222,14 @@ function tracingFilterObjToQueryParams(filterObj) { const validFilters = filterValues.filter((f) => SUPPORTED_TRACING_FILTERS[filterName].includes(f.operator), ); + validFilters.forEach(({ operator, value: rawValue }) => { if (filterName === 'attribute') { - handleAttributeFilter(rawValue, operator, filterParams); + handleAttributeFilter(rawValue, operator, filterParams, 'attr_name', 'attr_value'); } else if (filterName === 'period') { - handlePeriodFilter(rawValue, filterName, filterParams); + handleTracingPeriodFilter(rawValue, filterName, filterParams); } else { - const paramName = getTracingFilterParamName(filterName, operator); + const paramName = getFilterParamName(filterName, operator, TRACING_FILTER_TO_QUERY_PARAM); let value = rawValue; if (filterName === 'durationMs') { // converting durationMs to duration_nano @@ -318,6 +352,12 @@ function handleMetricsAttributeFilters(attributeFilters, params) { } } +/** **** + * + * Metrics API + * + * ***** */ + async function fetchMetrics(metricsUrl, { filters = {}, limit } = {}) { try { const params = new URLSearchParams(); @@ -359,6 +399,7 @@ const SUPPORTED_METRICS_DIMENSIONS_OPERATORS = { '=~': 're', '!~': 'nre', }; + function addMetricsAttributeFilterToQueryParams(dimensionFilter, params) { if (!dimensionFilter || !params) return; @@ -374,21 +415,7 @@ function addMetricsAttributeFilterToQueryParams(dimensionFilter, params) { }); } -function addDateRangeFilterToQueryParams(dateRangeFilter, params) { - if (!dateRangeFilter || !params) return; - - const { value, endDate, startDate } = dateRangeFilter; - if (value === CUSTOM_DATE_RANGE_OPTION) { - if (isValidDate(startDate) && isValidDate(endDate)) { - params.append('start_time', startDate.toISOString()); - params.append('end_time', endDate.toISOString()); - } - } else if (typeof value === 'string') { - params.append('period', value); - } -} - -function addGroupByFilterToQueryParams(groupByFilter, params) { +function addMetricsGroupByFilterToQueryParams(groupByFilter, params) { if (!groupByFilter || !params) return; const { func, attributes } = groupByFilter; @@ -429,7 +456,7 @@ async function fetchMetric(searchUrl, name, type, options = {}) { } if (groupBy) { - addGroupByFilterToQueryParams(groupBy, params); + addMetricsGroupByFilterToQueryParams(groupBy, params); } const { data } = await axios.get(searchUrl, { @@ -470,15 +497,95 @@ async function fetchMetricSearchMetadata(searchMetadataUrl, name, type) { } } +/** **** + * + * Logs API + * + * ***** */ + +/** + * Filters (and operators) allowed by logs query API + */ +const SUPPORTED_LOGS_FILTERS = { + service: ['=', '!='], + severityName: ['=', '!='], + traceId: ['='], + spanId: ['='], + fingerprint: ['='], + traceFlags: ['=', '!='], + attribute: ['='], + resourceAttribute: ['='], + search: [], // 'search' filter does not have any operator +}; + +/** + * Mapping of filter name to query param + */ +const LOGS_FILTER_TO_QUERY_PARAM = { + service: 'service_name', + severityName: 'severity_name', + traceId: 'trace_id', + spanId: 'span_id', + fingerprint: 'fingerprint', + traceFlags: 'trace_flags', + search: 'body', + // `attribute` and `resource_attribute` are handled separately +}; + +/** + * Builds URLSearchParams from a filter object of type { [filterName]: undefined | null | Array<{operator: String, value: any} } + * e.g: + * + * filterObj = { + * severityName: [{operator: '=', value: 'info' }], + * service: [{operator: '!=', value: 'foo' }] + * } + * + * It handles converting the filter to the proper supported query params + * + * @param {Object} filterObj : An Object representing handleAttributeFilter + * @returns URLSearchParams + */ +function addLogsAttributesFiltersToQueryParams(filterObj, filterParams) { + Object.keys(SUPPORTED_LOGS_FILTERS).forEach((filterName) => { + const filterValues = Array.isArray(filterObj[filterName]) + ? filterObj[filterName].filter(({ value }) => Boolean(value)) // ignore empty strings + : []; + const validFilters = filterValues.filter( + (f) => + (filterName === SEARCH_FILTER_NAME && SUPPORTED_LOGS_FILTERS[filterName]) || + SUPPORTED_LOGS_FILTERS[filterName].includes(f.operator), + ); + validFilters.forEach(({ operator, value: rawValue }) => { + if (filterName === 'attribute') { + handleAttributeFilter(rawValue, operator, filterParams, 'log_attr_name', 'log_attr_value'); + } else if (filterName === 'resourceAttribute') { + handleAttributeFilter(rawValue, operator, filterParams, 'res_attr_name', 'res_attr_value'); + } else { + const paramName = getFilterParamName(filterName, operator, LOGS_FILTER_TO_QUERY_PARAM); + const value = rawValue; + if (paramName && value) { + filterParams.append(paramName, value); + } + } + }); + }); + return filterParams; +} + export async function fetchLogs(logsSearchUrl, { pageToken, pageSize, filters = {} } = {}) { try { const params = new URLSearchParams(); - const { dateRange } = filters; + const { dateRange, attributes } = filters; if (dateRange) { addDateRangeFilterToQueryParams(dateRange, params); } + if (attributes) { + addLogsAttributesFiltersToQueryParams(attributes, params); + } + if (pageToken) { params.append('page_token', pageToken); } @@ -501,6 +608,12 @@ export async function fetchLogs(logsSearchUrl, { pageToken, pageSize, filters = } } +/** **** + * + * ObservabilityClient + * + * ***** */ + export function buildClient(config) { if (!config) { throw new Error('No options object provided'); // eslint-disable-line @gitlab/require-i18n-strings diff --git a/app/assets/javascripts/profile/edit/constants.js b/app/assets/javascripts/profile/edit/constants.js index 2a4f690c3be..a6e7a5eb4ef 100644 --- a/app/assets/javascripts/profile/edit/constants.js +++ b/app/assets/javascripts/profile/edit/constants.js @@ -13,7 +13,7 @@ export const avatarI18n = { uploadNewAvatar: s__('Profiles|Upload new avatar'), chooseFile: s__('Profiles|Choose file...'), noFileChosen: s__('Profiles|No file chosen.'), - maximumFileSize: s__('Profiles|The maximum file size allowed is 200KB.'), + maximumFileSize: s__('Profiles|The maximum file size allowed is 200 KiB.'), imageDimensions: s__('Profiles|The ideal image size is 192 x 192 pixels.'), removeAvatar: s__('Profiles|Remove avatar'), removeAvatarConfirmation: s__('Profiles|Avatar will be removed. Are you sure?'), diff --git a/app/assets/javascripts/render_vue_component_for_legacy_js.js b/app/assets/javascripts/render_vue_component_for_legacy_js.js index 7ef1943f7ac..47706411ebb 100644 --- a/app/assets/javascripts/render_vue_component_for_legacy_js.js +++ b/app/assets/javascripts/render_vue_component_for_legacy_js.js @@ -14,15 +14,14 @@ import Vue from 'vue'; * @returns {HTMLElement} */ export const renderVueComponentForLegacyJS = (Component, data, children) => { - const mountEl = document.createElement('div'); - const vm = new Vue({ - el: mountEl, render(h) { return h(Component, data, children); }, }); + vm.$mount(); + // Ensure it's rendered vm.$forceUpdate(); diff --git a/app/assets/javascripts/search/sidebar/components/group_filter.vue b/app/assets/javascripts/search/sidebar/components/group_filter.vue index 20231cdda6a..d649c789ed5 100644 --- a/app/assets/javascripts/search/sidebar/components/group_filter.vue +++ b/app/assets/javascripts/search/sidebar/components/group_filter.vue @@ -67,7 +67,7 @@ export default { diff --git a/app/assets/javascripts/search/topbar/components/search_type_indicator.vue b/app/assets/javascripts/search/topbar/components/search_type_indicator.vue index b57290172a5..d33e974edca 100644 --- a/app/assets/javascripts/search/topbar/components/search_type_indicator.vue +++ b/app/assets/javascripts/search/topbar/components/search_type_indicator.vue @@ -28,7 +28,7 @@ export default { }, i18n: { zoekt_enabled: s__( - 'GlobalSearch|%{linkStart}Exact code search (powered by Zoekt)%{linkEnd} is enabled', + 'GlobalSearch|%{linkStart}Exact code search (powered by Zoekt)%{linkEnd} is enabled.', ), zoekt_disabled: s__( 'GlobalSearch|%{linkStart}Exact code search (powered by Zoekt)%{linkEnd} is disabled since %{ref_elem} is not the default branch. %{docs_link}', @@ -119,19 +119,19 @@ export default {