From c47ade2adb94e4c33f87b4b825c92c7fe61ef044 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 6 Jul 2021 21:07:50 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/ci/reports.gitlab-ci.yml | 24 +++++- .gitlab/ci/rules.gitlab-ci.yml | 10 ++- .../Security Release.md | 4 +- Gemfile | 2 +- Gemfile.lock | 6 +- .../vue_shared/components/web_ide_link.vue | 10 +-- app/helpers/blob_helper.rb | 4 +- app/helpers/integrations_helper.rb | 81 +++++++++++++------ app/models/integration.rb | 4 - app/models/integrations/jira.rb | 9 --- app/models/integrations/teamcity.rb | 9 --- app/serializers/service_event_entity.rb | 2 +- .../service_ping/build_payload_service.rb | 27 +++++++ ...balancing_refine_load_balancer_methods.yml | 8 ++ config/initializers/asciidoctor_patch.rb | 20 ----- doc/administration/packages/index.md | 14 ++++ doc/development/i18n/proofreader.md | 1 + doc/user/project/import/github.md | 3 + lib/api/services.rb | 8 +- .../background_migration/batched_job.rb | 46 +++++++++++ .../database/load_balancing/load_balancer.rb | 2 + .../database/load_balancing/sticking.rb | 10 ++- .../instrumentations/database_metric.rb | 34 +++++++- locale/gitlab.pot | 6 ++ package.json | 2 +- .../remove_cluster_confirmation_spec.js.snap | 1 - .../jira_import_form_spec.js.snap | 2 - .../components/web_ide_link_spec.js | 8 +- spec/helpers/blob_helper_spec.rb | 16 ++-- spec/helpers/integrations_helper_spec.rb | 16 ++++ .../background_migration/batched_job_spec.rb | 69 ++++++++++++++++ .../database/load_balancing/sticking_spec.rb | 14 +++- spec/lib/gitlab/kroki_spec.rb | 2 +- .../instrumentations/database_metric_spec.rb | 75 +++++++++++++++++ .../build_payload_service_spec.rb | 47 +++++++++++ .../support/matchers/usage_metric_matchers.rb | 21 +++++ ...ping_metrics_definitions_shared_context.rb | 43 ++++++++++ ...te_service_ping_payload_shared_examples.rb | 9 +++ ...th_all_expected_metrics_shared_examples.rb | 11 +++ ...hout_restricted_metrics_shared_examples.rb | 11 +++ yarn.lock | 8 +- 41 files changed, 580 insertions(+), 119 deletions(-) create mode 100644 app/services/service_ping/build_payload_service.rb create mode 100644 config/feature_flags/development/load_balancing_refine_load_balancer_methods.yml delete mode 100644 config/initializers/asciidoctor_patch.rb create mode 100644 spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb create mode 100644 spec/services/service_ping/build_payload_service_spec.rb create mode 100644 spec/support/matchers/usage_metric_matchers.rb create mode 100644 spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb create mode 100644 spec/support/shared_examples/services/service_ping/complete_service_ping_payload_shared_examples.rb create mode 100644 spec/support/shared_examples/services/service_ping/service_ping_payload_with_all_expected_metrics_shared_examples.rb create mode 100644 spec/support/shared_examples/services/service_ping/service_ping_payload_without_restricted_metrics_shared_examples.rb diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml index 7920d835a29..7671a1a8650 100644 --- a/.gitlab/ci/reports.gitlab-ci.yml +++ b/.gitlab/ci/reports.gitlab-ci.yml @@ -87,20 +87,22 @@ gemnasium-python-dependency_scanning: # Analyze dependencies for malicious behavior # See https://gitlab.com/gitlab-com/gl-security/security-research/package-hunter -package_hunter: +.package_hunter-base: extends: - .default-retry - - .reports:rules:package_hunter stage: test image: name: registry.gitlab.com/gitlab-com/gl-security/security-research/package-hunter-cli:latest entrypoint: [""] + variables: + DEBUG: '*' + HTR_user: '$PACKAGE_HUNTER_USER' + HTR_pass: '$PACKAGE_HUNTER_PASS' needs: [] allow_failure: true - script: + before_script: - rm -r spec locale .git app/assets/images doc/ - cd .. && tar -I "gzip --best" -cf gitlab.tgz gitlab/ - - DEBUG=* HTR_user=$PACKAGE_HUNTER_USER HTR_pass=$PACKAGE_HUNTER_PASS node /usr/src/app/cli.js analyze --format gitlab gitlab.tgz | tee $CI_PROJECT_DIR/gl-dependency-scanning-report.json artifacts: paths: - gl-dependency-scanning-report.json @@ -108,6 +110,20 @@ package_hunter: dependency_scanning: gl-dependency-scanning-report.json expire_in: 1 week +package_hunter-yarn: + extends: + - .package_hunter-base + - .reports:rules:package_hunter-yarn + script: + - node /usr/src/app/cli.js analyze --format gitlab --manager yarn gitlab.tgz | tee $CI_PROJECT_DIR/gl-dependency-scanning-report.json + +package_hunter-bundler: + extends: + - .package_hunter-base + - .reports:rules:package_hunter-bundler + script: + - node /usr/src/app/cli.js analyze --format gitlab --manager bundler gitlab.tgz | tee $CI_PROJECT_DIR/gl-dependency-scanning-report.json + license_scanning: extends: .default-retry needs: [] diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index bdeda8c5642..5910fb86356 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -1099,7 +1099,7 @@ - <<: *if-default-branch-schedule-nightly allow_failure: true -.reports:rules:package_hunter: +.reports:rules:package_hunter-yarn: rules: - if: "$PACKAGE_HUNTER_USER == null || $PACKAGE_HUNTER_USER == ''" when: never @@ -1107,6 +1107,14 @@ - <<: *if-merge-request changes: ["yarn.lock"] +.reports:rules:package_hunter-bundler: + rules: + - if: "$PACKAGE_HUNTER_USER == null || $PACKAGE_HUNTER_USER == ''" + when: never + - <<: *if-default-branch-schedule-2-hourly + - <<: *if-merge-request + changes: ["Gemfile.lock"] + .reports:rules:license_scanning: rules: - if: '$LICENSE_SCANNING_DISABLED || $GITLAB_FEATURES !~ /\blicense_scanning\b/' diff --git a/.gitlab/merge_request_templates/Security Release.md b/.gitlab/merge_request_templates/Security Release.md index 77e8718c34f..33c0a5b98a8 100644 --- a/.gitlab/merge_request_templates/Security Release.md +++ b/.gitlab/merge_request_templates/Security Release.md @@ -30,8 +30,8 @@ See [the general developer security release guidelines](https://gitlab.com/gitla ## Maintainer checklist -- [ ] Correct milestone is applied and the title is matching across all backports -- [ ] Assigned to `@gitlab-release-tools-bot` with passing CI pipelines and **when all backports including the MR targeting master are ready.** +- [ ] Correct milestone is applied and the title is matching across all backports. +- [ ] Assigned to `@gitlab-release-tools-bot` with passing CI pipelines. /label ~security diff --git a/Gemfile b/Gemfile index 85b4fde8342..da03752f287 100644 --- a/Gemfile +++ b/Gemfile @@ -164,7 +164,7 @@ gem 'wikicloth', '0.8.1' gem 'asciidoctor', '~> 2.0.10' gem 'asciidoctor-include-ext', '~> 0.3.1', require: false gem 'asciidoctor-plantuml', '~> 0.0.12' -gem 'asciidoctor-kroki', '~> 0.4.0', require: false +gem 'asciidoctor-kroki', '~> 0.5.0', require: false gem 'rouge', '~> 3.26.0' gem 'truncato', '~> 0.7.11' gem 'bootstrap_form', '~> 4.2.0' diff --git a/Gemfile.lock b/Gemfile.lock index ca412317641..3972ab5f6bc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -92,10 +92,10 @@ GEM faraday_middleware (~> 1.0) faraday_middleware-multi_json (~> 0.0) oauth2 (~> 1.4) - asciidoctor (2.0.12) + asciidoctor (2.0.15) asciidoctor-include-ext (0.3.1) asciidoctor (>= 1.5.6, < 3.0.0) - asciidoctor-kroki (0.4.0) + asciidoctor-kroki (0.5.0) asciidoctor (~> 2.0) asciidoctor-plantuml (0.0.12) asciidoctor (>= 1.5.6, < 3.0.0) @@ -1409,7 +1409,7 @@ DEPENDENCIES asana (~> 0.10.3) asciidoctor (~> 2.0.10) asciidoctor-include-ext (~> 0.3.1) - asciidoctor-kroki (~> 0.4.0) + asciidoctor-kroki (~> 0.5.0) asciidoctor-plantuml (~> 0.0.12) atlassian-jwt (~> 0.2.0) attr_encrypted (~> 3.1.0) diff --git a/app/assets/javascripts/vue_shared/components/web_ide_link.vue b/app/assets/javascripts/vue_shared/components/web_ide_link.vue index 4bd3e352fd2..5ba7c107c12 100644 --- a/app/assets/javascripts/vue_shared/components/web_ide_link.vue +++ b/app/assets/javascripts/vue_shared/components/web_ide_link.vue @@ -93,9 +93,8 @@ export default { tooltip: '', attrs: { 'data-qa-selector': 'edit_button', - 'data-track-event': 'click_edit', - // eslint-disable-next-line @gitlab/require-i18n-strings - 'data-track-label': 'Edit', + 'data-track-action': 'click_consolidated_edit', + 'data-track-label': 'edit', }, ...handleOptions, }; @@ -127,9 +126,8 @@ export default { tooltip: '', attrs: { 'data-qa-selector': 'web_ide_button', - 'data-track-event': 'click_edit_ide', - // eslint-disable-next-line @gitlab/require-i18n-strings - 'data-track-label': 'Web IDE', + 'data-track-action': 'click_consolidated_edit_ide', + 'data-track-label': 'web_ide', }, ...handleOptions, }; diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index dfd6de3f1d5..eccd0e7a34c 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -65,7 +65,7 @@ module BlobHelper return unless blob = readable_blob(options, path, project, ref) common_classes = "btn gl-button btn-confirm js-edit-blob gl-ml-3 #{options[:extra_class]}" - data = { track_event: 'click_edit', track_label: 'Edit' } + data = { track_action: 'click_edit', track_label: 'edit' } if Feature.enabled?(:web_ide_primary_edit, project.group) common_classes += " btn-inverted" @@ -85,7 +85,7 @@ module BlobHelper return unless blob common_classes = 'btn gl-button btn-confirm ide-edit-button gl-ml-3' - data = { track_event: 'click_edit_ide', track_label: 'Web IDE' } + data = { track_action: 'click_edit_ide', track_label: 'web_ide' } unless Feature.enabled?(:web_ide_primary_edit, project.group) common_classes += " btn-inverted" diff --git a/app/helpers/integrations_helper.rb b/app/helpers/integrations_helper.rb index f37f7518dec..b8678763f65 100644 --- a/app/helpers/integrations_helper.rb +++ b/app/helpers/integrations_helper.rb @@ -1,32 +1,14 @@ # frozen_string_literal: true module IntegrationsHelper - def integration_event_description(event) - case event - when "push", "push_events" - s_("ProjectService|Trigger event for pushes to the repository.") - when "tag_push", "tag_push_events" - s_("ProjectService|Trigger event for new tags pushed to the repository.") - when "note", "note_events" - s_("ProjectService|Trigger event for new comments.") - when "confidential_note", "confidential_note_events" - s_("ProjectService|Trigger event for new comments on confidential issues.") - when "issue", "issue_events" - s_("ProjectService|Trigger event when an issue is created, updated, or closed.") - when "confidential_issue", "confidential_issue_events" - s_("ProjectService|Trigger event when a confidential issue is created, updated, or closed.") - when "merge_request", "merge_request_events" - s_("ProjectService|Trigger event when a merge request is created, updated, or merged.") - when "pipeline", "pipeline_events" - s_("ProjectService|Trigger event when a pipeline status changes.") - when "wiki_page", "wiki_page_events" - s_("ProjectService|Trigger event when a wiki page is created or updated.") - when "commit", "commit_events" - s_("ProjectService|Trigger event when a commit is created or updated.") - when "deployment" - s_("ProjectService|Trigger event when a deployment starts or finishes.") - when "alert" - s_("ProjectService|Trigger event when a new, unique alert is recorded.") + def integration_event_description(integration, event) + case integration + when Integrations::Jira + jira_integration_event_description(event) + when Integrations::Teamcity + teamcity_integration_event_description(event) + else + default_integration_event_description(event) end end @@ -144,6 +126,53 @@ module IntegrationsHelper private + def jira_integration_event_description(event) + case event + when "merge_request", "merge_request_events" + s_("JiraService|Jira comments are created when an issue is referenced in a merge request.") + when "commit", "commit_events" + s_("JiraService|Jira comments are created when an issue is referenced in a commit.") + end + end + + def teamcity_integration_event_description(event) + case event + when 'push', 'push_events' + s_('TeamcityIntegration|Trigger TeamCity CI after every push to the repository, except branch delete') + when 'merge_request', 'merge_request_events' + s_('TeamcityIntegration|Trigger TeamCity CI after a merge request has been created or updated') + end + end + + def default_integration_event_description(event) + case event + when "push", "push_events" + s_("ProjectService|Trigger event for pushes to the repository.") + when "tag_push", "tag_push_events" + s_("ProjectService|Trigger event for new tags pushed to the repository.") + when "note", "note_events" + s_("ProjectService|Trigger event for new comments.") + when "confidential_note", "confidential_note_events" + s_("ProjectService|Trigger event for new comments on confidential issues.") + when "issue", "issue_events" + s_("ProjectService|Trigger event when an issue is created, updated, or closed.") + when "confidential_issue", "confidential_issue_events" + s_("ProjectService|Trigger event when a confidential issue is created, updated, or closed.") + when "merge_request", "merge_request_events" + s_("ProjectService|Trigger event when a merge request is created, updated, or merged.") + when "pipeline", "pipeline_events" + s_("ProjectService|Trigger event when a pipeline status changes.") + when "wiki_page", "wiki_page_events" + s_("ProjectService|Trigger event when a wiki page is created or updated.") + when "commit", "commit_events" + s_("ProjectService|Trigger event when a commit is created or updated.") + when "deployment" + s_("ProjectService|Trigger event when a deployment starts or finishes.") + when "alert" + s_("ProjectService|Trigger event when a new, unique alert is recorded.") + end + end + def trigger_events_for_integration(integration) ServiceEventSerializer.new(service: integration).represent(integration.configurable_events).to_json end diff --git a/app/models/integration.rb b/app/models/integration.rb index 20de51ebcd1..9d5d2a2d921 100644 --- a/app/models/integration.rb +++ b/app/models/integration.rb @@ -172,10 +172,6 @@ class Integration < ApplicationRecord 'push' end - def self.event_description(event) - IntegrationsHelper.integration_event_description(event) - end - def self.find_or_create_templates create_nonexistent_templates for_template diff --git a/app/models/integrations/jira.rb b/app/models/integrations/jira.rb index 40734a29724..7b878903bac 100644 --- a/app/models/integrations/jira.rb +++ b/app/models/integrations/jira.rb @@ -577,15 +577,6 @@ module Integrations data_fields.deployment_server! end end - - def self.event_description(event) - case event - when "merge_request", "merge_request_events" - s_("JiraService|Jira comments are created when an issue is referenced in a merge request.") - when "commit", "commit_events" - s_("JiraService|Jira comments are created when an issue is referenced in a commit.") - end - end end end diff --git a/app/models/integrations/teamcity.rb b/app/models/integrations/teamcity.rb index 3f14c5d82b3..403271aa6a9 100644 --- a/app/models/integrations/teamcity.rb +++ b/app/models/integrations/teamcity.rb @@ -29,15 +29,6 @@ module Integrations def supported_events %w(push merge_request) end - - def event_description(event) - case event - when 'push', 'push_events' - 'TeamCity CI will be triggered after every push to the repository except branch delete' - when 'merge_request', 'merge_request_events' - 'TeamCity CI will be triggered after a merge request has been created or updated' - end - end end def compose_service_hook diff --git a/app/serializers/service_event_entity.rb b/app/serializers/service_event_entity.rb index 56dfdc794eb..a1fbfa1d4c4 100644 --- a/app/serializers/service_event_entity.rb +++ b/app/serializers/service_event_entity.rb @@ -14,7 +14,7 @@ class ServiceEventEntity < Grape::Entity end expose :description do |event| - IntegrationsHelper.integration_event_description(event) + IntegrationsHelper.integration_event_description(integration, event) end expose :field, if: -> (_, _) { event_field } do diff --git a/app/services/service_ping/build_payload_service.rb b/app/services/service_ping/build_payload_service.rb new file mode 100644 index 00000000000..2bef3d32103 --- /dev/null +++ b/app/services/service_ping/build_payload_service.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module ServicePing + class BuildPayloadService + def execute + return {} unless allowed_to_report? + + raw_payload + end + + private + + def allowed_to_report? + product_intelligence_enabled? && !User.single_user&.requires_usage_stats_consent? + end + + def product_intelligence_enabled? + ::Gitlab::CurrentSettings.usage_ping_enabled? + end + + def raw_payload + @raw_payload ||= ::Gitlab::UsageData.data(force_refresh: true) + end + end +end + +ServicePing::BuildPayloadService.prepend_mod_with('ServicePing::BuildPayloadService') diff --git a/config/feature_flags/development/load_balancing_refine_load_balancer_methods.yml b/config/feature_flags/development/load_balancing_refine_load_balancer_methods.yml new file mode 100644 index 00000000000..bef59588f7a --- /dev/null +++ b/config/feature_flags/development/load_balancing_refine_load_balancer_methods.yml @@ -0,0 +1,8 @@ +--- +name: load_balancing_refine_load_balancer_methods +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65356 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/335109 +milestone: '14.1' +type: development +group: group::memory +default_enabled: false diff --git a/config/initializers/asciidoctor_patch.rb b/config/initializers/asciidoctor_patch.rb deleted file mode 100644 index b7da50db77c..00000000000 --- a/config/initializers/asciidoctor_patch.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -# Ensure that locked attributes can not be changed using a counter. -# TODO: this can be removed once `asciidoctor` gem is > 2.0.12 -# and https://github.com/asciidoctor/asciidoctor/issues/3939 is merged -module Asciidoctor - module DocumentPatch - def counter(name, seed = nil) - return @parent_document.counter(name, seed) if @parent_document # rubocop: disable Gitlab/ModuleWithInstanceVariables - - unless attribute_locked? name - super - end - end - end -end - -class Asciidoctor::Document - prepend Asciidoctor::DocumentPatch -end diff --git a/doc/administration/packages/index.md b/doc/administration/packages/index.md index 6440fb16fc6..85c7a96ef55 100644 --- a/doc/administration/packages/index.md +++ b/doc/administration/packages/index.md @@ -237,3 +237,17 @@ For installations from source: ```shell RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:packages:migrate ``` + +You can optionally track progress and verify that all packages migrated successfully. + +From the [PostgreSQL console](https://docs.gitlab.com/omnibus/settings/database.html#connecting-to-the-bundled-postgresql-database) +(`sudo gitlab-psql -d gitlabhq_production` for Omnibus GitLab), verify that `objectstg` below (where +`file_store=2`) has the count of all packages: + +```shell +gitlabhq_production=# SELECT count(*) AS total, sum(case when file_store = '1' then 1 else 0 end) AS filesystem, sum(case when file_store = '2' then 1 else 0 end) AS objectstg FROM packages_package_files; + +total | filesystem | objectstg +------+------------+----------- + 34 | 0 | 34 +``` diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md index fc19ab93ecd..6e3b32e18df 100644 --- a/doc/development/i18n/proofreader.md +++ b/doc/development/i18n/proofreader.md @@ -93,6 +93,7 @@ are very appreciative of the work done by translators and proofreaders! - Portuguese, Brazilian - Paulo George Gomes Bezerra - [GitLab](https://gitlab.com/paulobezerra), [CrowdIn](https://crowdin.com/profile/paulogomes.rep) - André Gama - [GitLab](https://gitlab.com/andregamma), [CrowdIn](https://crowdin.com/profile/ToeOficial) + - Eduardo Addad de Oliveira - [GitLab](https://gitlab.com/eduardoaddad), [CrowdIn](https://crowdin.com/profile/eduardoaddad) - Romanian - Proofreaders needed. - Russian diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md index ec56ea851d7..33bdc2bc7d8 100644 --- a/doc/user/project/import/github.md +++ b/doc/user/project/import/github.md @@ -134,6 +134,9 @@ If you are not using the GitHub integration, you can still perform an authorizat 1. Hit the **List Your GitHub Repositories** button and wait while GitLab reads your repositories' information. Once done, you'll be taken to the importer page to select the repositories to import. +To use a newer personal access token in imports after previously performing these steps, sign out of +your GitLab account and sign in again, or revoke the older personal access token in GitHub. + ### Select which repositories to import After you have authorized access to your GitHub repositories, you are redirected to the GitHub importer page and diff --git a/lib/api/services.rb b/lib/api/services.rb index c81af1791a5..6c21a80509d 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -23,14 +23,14 @@ module API INTEGRATIONS = integrations.freeze - integration_classes.each do |service| - event_names = service.try(:event_names) || next + integration_classes.each do |integration| + event_names = integration.try(:event_names) || next event_names.each do |event_name| - INTEGRATIONS[service.to_param.tr("_", "-")] << { + INTEGRATIONS[integration.to_param.tr("_", "-")] << { required: false, name: event_name.to_sym, type: String, - desc: service.event_description(event_name) + desc: IntegrationsHelper.integration_event_description(integration, event_name) } end end diff --git a/lib/gitlab/database/background_migration/batched_job.rb b/lib/gitlab/database/background_migration/batched_job.rb index 9a1dc4ee17d..b238d0dd4b8 100644 --- a/lib/gitlab/database/background_migration/batched_job.rb +++ b/lib/gitlab/database/background_migration/batched_job.rb @@ -44,6 +44,52 @@ module Gitlab # TODO: Switch to individual job interval (prereq: https://gitlab.com/gitlab-org/gitlab/-/issues/328801) duration.to_f / batched_migration.interval end + + def split_and_retry! + with_lock do + raise 'Only failed jobs can be split' unless failed? + + new_batch_size = batch_size / 2 + + raise 'Job cannot be split further' if new_batch_size < 1 + + batching_strategy = batched_migration.batch_class.new + next_batch_bounds = batching_strategy.next_batch( + batched_migration.table_name, + batched_migration.column_name, + batch_min_value: min_value, + batch_size: new_batch_size + ) + midpoint = next_batch_bounds.last + + # We don't want the midpoint to go over the existing max_value because + # those IDs would already be in the next batched migration job. + # This could happen when a lot of records in the current batch are deleted. + # + # In this case, we just lower the batch size so that future calls to this + # method could eventually split the job if it continues to fail. + if midpoint >= max_value + update!(batch_size: new_batch_size, status: :pending) + else + old_max_value = max_value + + update!( + batch_size: new_batch_size, + max_value: midpoint, + attempts: 0, + status: :pending, + started_at: nil, + finished_at: nil, + metrics: {} + ) + + new_record = dup + new_record.min_value = midpoint.next + new_record.max_value = old_max_value + new_record.save! + end + end + end end end end diff --git a/lib/gitlab/database/load_balancing/load_balancer.rb b/lib/gitlab/database/load_balancing/load_balancer.rb index d25c274edd4..b5960476759 100644 --- a/lib/gitlab/database/load_balancing/load_balancer.rb +++ b/lib/gitlab/database/load_balancing/load_balancer.rb @@ -165,6 +165,7 @@ module Gitlab # while we only need a single host: https://gitlab.com/gitlab-org/gitlab/-/issues/326125#note_615271604 # Also, shuffling the list afterwards doesn't seem to be necessary. # This may be improved by merging this method with `select_up_to_date_host`. + # Could be removed when `:load_balancing_refine_load_balancer_methods` FF is rolled out def select_caught_up_hosts(location) all_hosts = @host_list.hosts valid_hosts = all_hosts.select { |host| host.caught_up?(location) } @@ -201,6 +202,7 @@ module Gitlab true end + # Could be removed when `:load_balancing_refine_load_balancer_methods` FF is rolled out def set_consistent_hosts_for_request(hosts) RequestStore[VALID_HOSTS_CACHE_KEY] = hosts end diff --git a/lib/gitlab/database/load_balancing/sticking.rb b/lib/gitlab/database/load_balancing/sticking.rb index d3b5a28074e..1223225ef5b 100644 --- a/lib/gitlab/database/load_balancing/sticking.rb +++ b/lib/gitlab/database/load_balancing/sticking.rb @@ -53,8 +53,14 @@ module Gitlab # write location. If no such location exists, err on the side of caution. return false unless location - load_balancer.select_caught_up_hosts(location).tap do |selected| - unstick(namespace, id) if selected + if ::Feature.enabled?(:load_balancing_refine_load_balancer_methods) + load_balancer.select_up_to_date_host(location).tap do |selected| + unstick(namespace, id) if selected + end + else + load_balancer.select_caught_up_hosts(location).tap do |selected| + unstick(namespace, id) if selected + end end end diff --git a/lib/gitlab/usage/metrics/instrumentations/database_metric.rb b/lib/gitlab/usage/metrics/instrumentations/database_metric.rb index 69a288e5b6e..7b3a545185b 100644 --- a/lib/gitlab/usage/metrics/instrumentations/database_metric.rb +++ b/lib/gitlab/usage/metrics/instrumentations/database_metric.rb @@ -16,14 +16,20 @@ module Gitlab # end class << self def start(&block) + return @metric_start&.call unless block_given? + @metric_start = block end def finish(&block) + return @metric_finish&.call unless block_given? + @metric_finish = block end def relation(&block) + return @metric_relation&.call unless block_given? + @metric_relation = block end @@ -32,15 +38,21 @@ module Gitlab @column = column end - attr_reader :metric_operation, :metric_relation, :metric_start, :metric_finish, :column + def cache_start_and_finish_as(cache_key) + @cache_key = cache_key + end + + attr_reader :metric_operation, :metric_relation, :metric_start, :metric_finish, :column, :cache_key end def value + start, finish = get_or_cache_batch_ids + method(self.class.metric_operation) .call(relation, self.class.column, - start: self.class.metric_start&.call, - finish: self.class.metric_finish&.call) + start: start, + finish: finish) end def to_sql @@ -73,6 +85,22 @@ module Gitlab raise "Unknown time frame: #{time_frame} for DatabaseMetric" end end + + def get_or_cache_batch_ids + return [self.class.start, self.class.finish] unless self.class.cache_key.present? + + key_name = "metric_instrumentation/#{self.class.cache_key}" + + start = Gitlab::Cache.fetch_once("#{key_name}_minimum_id", expires_in: 1.day) do + self.class.start + end + + finish = Gitlab::Cache.fetch_once("#{key_name}_maximum_id", expires_in: 1.day) do + self.class.finish + end + + [start, finish] + end end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 71a62f55aba..4ee339377bf 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -31905,6 +31905,12 @@ msgstr "" msgid "Team domain" msgstr "" +msgid "TeamcityIntegration|Trigger TeamCity CI after a merge request has been created or updated" +msgstr "" + +msgid "TeamcityIntegration|Trigger TeamCity CI after every push to the repository, except branch delete" +msgstr "" + msgid "Telephone number" msgstr "" diff --git a/package.json b/package.json index 9a8180b92ec..3a88f17584d 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "@gitlab/favicon-overlay": "2.0.0", "@gitlab/svgs": "1.202.0", "@gitlab/tributejs": "1.0.0", - "@gitlab/ui": "30.2.0", + "@gitlab/ui": "30.2.1", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "6.1.3-2", "@rails/ujs": "6.1.3-2", diff --git a/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap b/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap index e5e336eb3d5..0e1fe790771 100644 --- a/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap +++ b/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap @@ -156,7 +156,6 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ - diff --git a/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap index 172b6e4831c..f2142ce1fcf 100644 --- a/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap +++ b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap @@ -176,7 +176,6 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] = - @@ -304,7 +303,6 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] = - diff --git a/spec/frontend/vue_shared/components/web_ide_link_spec.js b/spec/frontend/vue_shared/components/web_ide_link_spec.js index 5a6c91bda9f..0fd4d0dab87 100644 --- a/spec/frontend/vue_shared/components/web_ide_link_spec.js +++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js @@ -15,8 +15,8 @@ const ACTION_EDIT = { tooltip: '', attrs: { 'data-qa-selector': 'edit_button', - 'data-track-event': 'click_edit', - 'data-track-label': 'Edit', + 'data-track-action': 'click_consolidated_edit', + 'data-track-label': 'edit', }, }; const ACTION_EDIT_CONFIRM_FORK = { @@ -32,8 +32,8 @@ const ACTION_WEB_IDE = { text: 'Web IDE', attrs: { 'data-qa-selector': 'web_ide_button', - 'data-track-event': 'click_edit_ide', - 'data-track-label': 'Web IDE', + 'data-track-action': 'click_consolidated_edit_ide', + 'data-track-label': 'web_ide', }, }; const ACTION_WEB_IDE_CONFIRM_FORK = { diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index 885569574a4..c48d609836d 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -67,8 +67,8 @@ RSpec.describe BlobHelper do it 'passes on primary tracking attributes' do parsed_link = Capybara.string(link).find_link('Edit') - expect(parsed_link[:'data-track-event']).to eq("click_edit") - expect(parsed_link[:'data-track-label']).to eq("Edit") + expect(parsed_link[:'data-track-action']).to eq("click_edit") + expect(parsed_link[:'data-track-label']).to eq("edit") expect(parsed_link[:'data-track-property']).to eq(nil) end end @@ -85,8 +85,8 @@ RSpec.describe BlobHelper do it 'passes on secondary tracking attributes' do parsed_link = Capybara.string(link).find_link('Edit') - expect(parsed_link[:'data-track-event']).to eq("click_edit") - expect(parsed_link[:'data-track-label']).to eq("Edit") + expect(parsed_link[:'data-track-action']).to eq("click_edit") + expect(parsed_link[:'data-track-label']).to eq("edit") expect(parsed_link[:'data-track-property']).to eq("secondary") end end @@ -332,8 +332,8 @@ RSpec.describe BlobHelper do it 'passes on secondary tracking attributes' do parsed_link = Capybara.string(link).find_link('Web IDE') - expect(parsed_link[:'data-track-event']).to eq("click_edit_ide") - expect(parsed_link[:'data-track-label']).to eq("Web IDE") + expect(parsed_link[:'data-track-action']).to eq("click_edit_ide") + expect(parsed_link[:'data-track-label']).to eq("web_ide") expect(parsed_link[:'data-track-property']).to eq("secondary") end end @@ -350,8 +350,8 @@ RSpec.describe BlobHelper do it 'passes on primary tracking attributes' do parsed_link = Capybara.string(link).find_link('Web IDE') - expect(parsed_link[:'data-track-event']).to eq("click_edit_ide") - expect(parsed_link[:'data-track-label']).to eq("Web IDE") + expect(parsed_link[:'data-track-action']).to eq("click_edit_ide") + expect(parsed_link[:'data-track-label']).to eq("web_ide") expect(parsed_link[:'data-track-property']).to eq(nil) end end diff --git a/spec/helpers/integrations_helper_spec.rb b/spec/helpers/integrations_helper_spec.rb index 8cecf2296e8..8e652d2f150 100644 --- a/spec/helpers/integrations_helper_spec.rb +++ b/spec/helpers/integrations_helper_spec.rb @@ -3,6 +3,22 @@ require 'spec_helper' RSpec.describe IntegrationsHelper do + describe '#integration_event_description' do + subject(:description) { helper.integration_event_description(integration, 'merge_request_events') } + + context 'when integration is Jira' do + let(:integration) { Integrations::Jira.new } + + it { is_expected.to include('Jira') } + end + + context 'when integration is Team City' do + let(:integration) { Integrations::Teamcity.new } + + it { is_expected.to include('TeamCity') } + end + end + describe '#integration_form_data' do let(:fields) do [ diff --git a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb index 2de784d3e16..60971a99e34 100644 --- a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb @@ -124,4 +124,73 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d end end end + + describe '#split_and_retry!' do + let!(:job) { create(:batched_background_migration_job, batch_size: 10, min_value: 6, max_value: 15, status: :failed) } + + it 'splits the job into two and marks them as pending' do + allow_next_instance_of(Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchingStrategy) do |batch_class| + allow(batch_class).to receive(:next_batch).with(anything, anything, batch_min_value: 6, batch_size: 5).and_return([6, 10]) + end + + expect { job.split_and_retry! }.to change { described_class.count }.by(1) + + expect(job).to have_attributes( + min_value: 6, + max_value: 10, + batch_size: 5, + status: 'pending', + attempts: 0, + started_at: nil, + finished_at: nil, + metrics: {} + ) + + new_job = described_class.last + + expect(new_job).to have_attributes( + batched_background_migration_id: job.batched_background_migration_id, + min_value: 11, + max_value: 15, + batch_size: 5, + status: 'pending', + attempts: 0, + started_at: nil, + finished_at: nil, + metrics: {} + ) + expect(new_job.created_at).not_to eq(job.created_at) + end + + context 'when job is not failed' do + let!(:job) { create(:batched_background_migration_job, status: :succeeded) } + + it 'raises an exception' do + expect { job.split_and_retry! }.to raise_error 'Only failed jobs can be split' + end + end + + context 'when batch size is already 1' do + let!(:job) { create(:batched_background_migration_job, batch_size: 1, status: :failed) } + + it 'raises an exception' do + expect { job.split_and_retry! }.to raise_error 'Job cannot be split further' + end + end + + context 'when computed midpoint is larger than the max value of the batch' do + before do + allow_next_instance_of(Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchingStrategy) do |batch_class| + allow(batch_class).to receive(:next_batch).with(anything, anything, batch_min_value: 6, batch_size: 5).and_return([6, 16]) + end + end + + it 'lowers the batch size and marks the job as pending' do + expect { job.split_and_retry! }.not_to change { described_class.count } + + expect(job.batch_size).to eq(5) + expect(job.status).to eq('pending') + end + end + end end diff --git a/spec/lib/gitlab/database/load_balancing/sticking_spec.rb b/spec/lib/gitlab/database/load_balancing/sticking_spec.rb index 29591f2f851..f1ab923a67e 100644 --- a/spec/lib/gitlab/database/load_balancing/sticking_spec.rb +++ b/spec/lib/gitlab/database/load_balancing/sticking_spec.rb @@ -325,10 +325,22 @@ RSpec.describe Gitlab::Database::LoadBalancing::Sticking, :redis do end it 'returns true, selects hosts, and unsticks if any secondary has caught up' do - expect(lb).to receive(:select_caught_up_hosts).and_return(true) + expect(lb).to receive(:select_up_to_date_host).and_return(true) expect(described_class).to receive(:unstick).with(:project, 42) expect(described_class.select_caught_up_replicas(:project, 42)).to be true end + + context 'when :load_balancing_refine_load_balancer_methods FF is disabled' do + before do + stub_feature_flags(load_balancing_refine_load_balancer_methods: false) + end + + it 'returns true, selects hosts, and unsticks if any secondary has caught up' do + expect(lb).to receive(:select_caught_up_hosts).and_return(true) + expect(described_class).to receive(:unstick).with(:project, 42) + expect(described_class.select_caught_up_replicas(:project, 42)).to be true + end + end end end end diff --git a/spec/lib/gitlab/kroki_spec.rb b/spec/lib/gitlab/kroki_spec.rb index 31d3edd158b..7d29d018ff1 100644 --- a/spec/lib/gitlab/kroki_spec.rb +++ b/spec/lib/gitlab/kroki_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Gitlab::Kroki do describe '.formats' do def default_formats - %w[bytefield c4plantuml ditaa erd graphviz nomnoml plantuml svgbob umlet vega vegalite wavedrom].freeze + %w[bytefield c4plantuml ditaa erd graphviz nomnoml pikchr plantuml svgbob umlet vega vegalite wavedrom].freeze end subject { described_class.formats(Gitlab::CurrentSettings) } diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb new file mode 100644 index 00000000000..5e36820df5e --- /dev/null +++ b/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::DatabaseMetric do + subject do + described_class.tap do |m| + m.relation { Issue } + m.operation :count + m.start { m.relation.minimum(:id) } + m.finish { m.relation.maximum(:id) } + end.new(time_frame: 'all') + end + + describe '#value' do + let_it_be(:issue_1) { create(:issue) } + let_it_be(:issue_2) { create(:issue) } + let_it_be(:issue_3) { create(:issue) } + let_it_be(:issues) { Issue.all } + + before do + allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false) + end + + it 'calculates a correct result' do + expect(subject.value).to eq(3) + end + + it 'does not cache the result of start and finish', :request_store, :use_clean_rails_redis_caching do + expect(Gitlab::Cache).not_to receive(:fetch_once) + expect(subject).to receive(:count).with(any_args, hash_including(start: issues.min_by(&:id).id, finish: issues.max_by(&:id).id)).and_call_original + + subject.value + + expect(Rails.cache.read('metric_instrumentation/special_issue_count_minimum_id')).to eq(nil) + expect(Rails.cache.read('metric_instrumentation/special_issue_count_maximum_id')).to eq(nil) + end + + context 'with start and finish not called' do + subject do + described_class.tap do |m| + m.relation { Issue } + m.operation :count + end.new(time_frame: 'all') + end + + it 'calculates a correct result' do + expect(subject.value).to eq(3) + end + end + + context 'with cache_start_and_finish_as called' do + subject do + described_class.tap do |m| + m.relation { Issue } + m.operation :count + m.start { m.relation.minimum(:id) } + m.finish { m.relation.maximum(:id) } + m.cache_start_and_finish_as :special_issue_count + end.new(time_frame: 'all') + end + + it 'caches using the key name passed', :request_store, :use_clean_rails_redis_caching do + expect(Gitlab::Cache).to receive(:fetch_once).with('metric_instrumentation/special_issue_count_minimum_id', any_args).and_call_original + expect(Gitlab::Cache).to receive(:fetch_once).with('metric_instrumentation/special_issue_count_maximum_id', any_args).and_call_original + expect(subject).to receive(:count).with(any_args, hash_including(start: issues.min_by(&:id).id, finish: issues.max_by(&:id).id)).and_call_original + + subject.value + + expect(Rails.cache.read('metric_instrumentation/special_issue_count_minimum_id')).to eq(issues.min_by(&:id).id) + expect(Rails.cache.read('metric_instrumentation/special_issue_count_maximum_id')).to eq(issues.max_by(&:id).id) + end + end + end +end diff --git a/spec/services/service_ping/build_payload_service_spec.rb b/spec/services/service_ping/build_payload_service_spec.rb new file mode 100644 index 00000000000..cd2685069c9 --- /dev/null +++ b/spec/services/service_ping/build_payload_service_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ServicePing::BuildPayloadService do + describe '#execute', :without_license do + subject(:service_ping_payload) { described_class.new.execute } + + include_context 'stubbed service ping metrics definitions' do + let(:subscription_metrics) do + [ + metric_attributes('active_user_count', "Subscription") + ] + end + end + + context 'when usage_ping_enabled setting is false' do + before do + # Gitlab::CurrentSettings.usage_ping_enabled? == false + stub_config_setting(usage_ping_enabled: false) + end + + it 'returns empty service ping payload' do + expect(service_ping_payload).to eq({}) + end + end + + context 'when usage_ping_enabled setting is true' do + before do + # Gitlab::CurrentSettings.usage_ping_enabled? == true + stub_config_setting(usage_ping_enabled: true) + end + + it_behaves_like 'complete service ping payload' + + context 'with require stats consent enabled' do + before do + allow(User).to receive(:single_user).and_return(double(:user, requires_usage_stats_consent?: true)) + end + + it 'returns empty service ping payload' do + expect(service_ping_payload).to eq({}) + end + end + end + end +end diff --git a/spec/support/matchers/usage_metric_matchers.rb b/spec/support/matchers/usage_metric_matchers.rb new file mode 100644 index 00000000000..83433334e8b --- /dev/null +++ b/spec/support/matchers/usage_metric_matchers.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec::Matchers.define :have_usage_metric do |key_path| + match do |payload| + payload = payload.deep_stringify_keys + + key_path.split('.').each do |part| + break false unless payload&.has_key?(part) + + payload = payload[part] + end + end + + failure_message do + "Payload does not contain metric with key path: '#{key_path}'" + end + + failure_message_when_negated do + "Payload contains restricted metric with key path: '#{key_path}'" + end +end diff --git a/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb b/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb new file mode 100644 index 00000000000..ea72398010c --- /dev/null +++ b/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +RSpec.shared_context 'stubbed service ping metrics definitions' do + include UsageDataHelpers + + let(:metrics_definitions) { standard_metrics + subscription_metrics + operational_metrics + optional_metrics } + let(:standard_metrics) do + [ + metric_attributes('uuid', "Standard") + ] + end + + let(:operational_metrics) do + [ + metric_attributes('counts.merge_requests', "Operational"), + metric_attributes('counts.todos', "Operational") + ] + end + + let(:optional_metrics) do + [ + metric_attributes('counts.boards', "Optional"), + metric_attributes('gitaly.filesystems', '').except('data_category') + ] + end + + before do + stub_usage_data_connections + stub_object_store_settings + + allow(Gitlab::Usage::MetricDefinition).to( + receive(:definitions) + .and_return(metrics_definitions.to_h { |definition| [definition['key_path'], Gitlab::Usage::MetricDefinition.new('', definition.symbolize_keys)] }) + ) + end + + def metric_attributes(key_path, category) + { + 'key_path' => key_path, + 'data_category' => category + } + end +end diff --git a/spec/support/shared_examples/services/service_ping/complete_service_ping_payload_shared_examples.rb b/spec/support/shared_examples/services/service_ping/complete_service_ping_payload_shared_examples.rb new file mode 100644 index 00000000000..8dcff99fb6f --- /dev/null +++ b/spec/support/shared_examples/services/service_ping/complete_service_ping_payload_shared_examples.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'complete service ping payload' do + it_behaves_like 'service ping payload with all expected metrics' do + let(:expected_metrics) do + standard_metrics + subscription_metrics + operational_metrics + optional_metrics + end + end +end diff --git a/spec/support/shared_examples/services/service_ping/service_ping_payload_with_all_expected_metrics_shared_examples.rb b/spec/support/shared_examples/services/service_ping/service_ping_payload_with_all_expected_metrics_shared_examples.rb new file mode 100644 index 00000000000..535e7291b7e --- /dev/null +++ b/spec/support/shared_examples/services/service_ping/service_ping_payload_with_all_expected_metrics_shared_examples.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'service ping payload with all expected metrics' do + specify do + aggregate_failures do + expected_metrics.each do |metric| + is_expected.to have_usage_metric metric['key_path'] + end + end + end +end diff --git a/spec/support/shared_examples/services/service_ping/service_ping_payload_without_restricted_metrics_shared_examples.rb b/spec/support/shared_examples/services/service_ping/service_ping_payload_without_restricted_metrics_shared_examples.rb new file mode 100644 index 00000000000..9f18174cbc7 --- /dev/null +++ b/spec/support/shared_examples/services/service_ping/service_ping_payload_without_restricted_metrics_shared_examples.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'service ping payload without restricted metrics' do + specify do + aggregate_failures do + restricted_metrics.each do |metric| + is_expected.not_to have_usage_metric metric['key_path'] + end + end + end +end diff --git a/yarn.lock b/yarn.lock index d5af76e8829..be0d9afd4f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -908,10 +908,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8" integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw== -"@gitlab/ui@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-30.2.0.tgz#eceec947f901cca9507a1ac8b3bd0031a5dcacf9" - integrity sha512-rYG3HyUHZQyum9+6OKvp45r9b9E/wzAl8rpFyIIZMg6a14JPfsGhdjXqycWlLxf3TAsbTD6MtjQm/z/I8J6V8g== +"@gitlab/ui@30.2.1": + version "30.2.1" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-30.2.1.tgz#be582413712cd2372ff01279e47579f5785591d4" + integrity sha512-Pv2w5ZSR7+G5zcaRjvf8KPs7wQRBqAXXNvKOw2pg/aZsRoK+JfN1neNmfeaqO6K/k1TJyiP6inDXAvhU8SqmOg== dependencies: "@babel/standalone" "^7.0.0" bootstrap-vue "2.18.1"