diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 48f85219ff4..b8a0b894d5b 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -128,11 +128,13 @@ update-storybook-yarn-cache: - tmp/tests/frontend/ - knapsack/ -rspec frontend_fixture: +# In gitlab-foss, generates FOSS fixtures. In gitlab, generates FOSS & EE fixtures. +# That way, we don't need to have two separate jobs. +rspec-all frontend_fixture: extends: - .frontend-fixtures-base - .frontend:rules:default-frontend-jobs - parallel: 2 + parallel: 5 rspec frontend_fixture as-if-foss: extends: @@ -140,12 +142,6 @@ rspec frontend_fixture as-if-foss: - .frontend:rules:default-frontend-jobs-as-if-foss - .as-if-foss -rspec-ee frontend_fixture: - extends: - - .frontend-fixtures-base - - .frontend:rules:default-frontend-jobs-ee - parallel: 3 - graphql-schema-dump: variables: SETUP_DB: "false" @@ -196,9 +192,7 @@ jest: - .frontend:rules:jest needs: - job: "detect-tests" - - job: "rspec frontend_fixture" - - job: "rspec-ee frontend_fixture" - optional: true + - job: "rspec-all frontend_fixture" artifacts: name: coverage-frontend expire_in: 31d @@ -225,9 +219,7 @@ jest-integration: script: - run_timed_command "yarn jest:integration --ci" needs: - - job: "rspec frontend_fixture" - - job: "rspec-ee frontend_fixture" - optional: true + - job: "rspec-all frontend_fixture" - job: "graphql-schema-dump" jest-as-if-foss: @@ -341,9 +333,7 @@ startup-css-check: - .frontend:rules:default-frontend-jobs needs: - job: "compile-test-assets" - - job: "rspec frontend_fixture" - - job: "rspec-ee frontend_fixture" - optional: true + - job: "rspec-all frontend_fixture" startup-css-check as-if-foss: extends: diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 62fb7c75cca..d69988a83ed 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -495,13 +495,6 @@ - <<: *if-default-refs changes: *code-backstage-patterns -.frontend:rules:default-frontend-jobs-ee: - rules: - - <<: *if-not-ee - when: never - - <<: *if-default-refs - changes: *code-backstage-patterns - .frontend:rules:default-frontend-jobs-as-if-foss: rules: - <<: *if-not-ee diff --git a/.gitlab/ci/test-metadata.gitlab-ci.yml b/.gitlab/ci/test-metadata.gitlab-ci.yml index ac719977975..2d96fb6d4b0 100644 --- a/.gitlab/ci/test-metadata.gitlab-ci.yml +++ b/.gitlab/ci/test-metadata.gitlab-ci.yml @@ -29,8 +29,7 @@ update-tests-metadata: - retrieve-tests-metadata - setup-test-env - rspec migration pg12 - - rspec frontend_fixture - - rspec-ee frontend_fixture + - rspec-all frontend_fixture - rspec unit pg12 - rspec integration pg12 - rspec system pg12 diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue index db80d48239b..b6ccc6a00fe 100644 --- a/app/assets/javascripts/boards/components/board_card_inner.vue +++ b/app/assets/javascripts/boards/components/board_card_inner.vue @@ -316,7 +316,7 @@ export default {

- + {{ totalEpicsCount }} @@ -334,7 +334,7 @@ export default { diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue index dc5313b1bf6..a8d71ab7a35 100644 --- a/app/assets/javascripts/boards/components/board_list_header.vue +++ b/app/assets/javascripts/boards/components/board_list_header.vue @@ -365,7 +365,7 @@ export default { > - + @@ -388,7 +388,7 @@ export default { v-gl-tooltip.hover :aria-label="$options.i18n.newIssue" :title="$options.i18n.newIssue" - class="issue-count-badge-add-button no-drag" + class="no-drag" icon="plus" @click="showNewIssueForm" /> diff --git a/app/assets/javascripts/related_issues/components/related_issues_block.vue b/app/assets/javascripts/related_issues/components/related_issues_block.vue index 7d23c7033f3..94535e1b8c9 100644 --- a/app/assets/javascripts/related_issues/components/related_issues_block.vue +++ b/app/assets/javascripts/related_issues/components/related_issues_block.vue @@ -162,7 +162,6 @@ export default { icon="plus" :aria-label="__('Add a related issue')" :class="qaClass" - class="js-issue-count-badge-add-button" @click="$emit('toggleAddRelatedIssuesForm', $event)" /> diff --git a/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue index 6fb1d1ed365..05858c7469d 100644 --- a/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue +++ b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue @@ -81,13 +81,13 @@ export default { {{ __('Related merge requests') }}
-
-
- - - - {{ totalCount }} -
+
+ + + + {{ totalCount }}
diff --git a/app/assets/javascripts/token_access/index.js b/app/assets/javascripts/token_access/index.js index 6a29883290a..8d29a65d705 100644 --- a/app/assets/javascripts/token_access/index.js +++ b/app/assets/javascripts/token_access/index.js @@ -6,7 +6,7 @@ import TokenAccess from './components/token_access.vue'; Vue.use(VueApollo); const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(), + defaultClient: createDefaultClient({}, { assumeImmutableResults: true }), }); export const initTokenAccess = (containerId = 'js-ci-token-access-app') => { diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss index fa5ab590232..dfbdee8e1c8 100644 --- a/app/assets/stylesheets/_page_specific_files.scss +++ b/app/assets/stylesheets/_page_specific_files.scss @@ -10,7 +10,6 @@ @import './pages/groups'; @import './pages/help'; @import './pages/issuable'; -@import './pages/issues/issue_count_badge'; @import './pages/issues'; @import './pages/labels'; @import './pages/login'; diff --git a/app/assets/stylesheets/pages/issues/issue_count_badge.scss b/app/assets/stylesheets/pages/issues/issue_count_badge.scss deleted file mode 100644 index f2283e02ad2..00000000000 --- a/app/assets/stylesheets/pages/issues/issue_count_badge.scss +++ /dev/null @@ -1,10 +0,0 @@ -.issue-count-badge, -.mr-count-badge { - padding: 5px $gl-padding-8; -} - -.issue-count-badge-count, -.mr-count-badge-count { - display: inline-flex; - align-items: center; -} diff --git a/app/services/ci/stuck_builds/drop_running_service.rb b/app/services/ci/stuck_builds/drop_running_service.rb new file mode 100644 index 00000000000..ef23bd7e7bd --- /dev/null +++ b/app/services/ci/stuck_builds/drop_running_service.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Ci + module StuckBuilds + class DropRunningService + include DropHelpers + + BUILD_RUNNING_OUTDATED_TIMEOUT = 1.hour + + def execute + Gitlab::AppLogger.info "#{self.class}: Cleaning running, timed-out builds" + + drop(running_timed_out_builds, failure_reason: :stuck_or_timeout_failure) + end + + private + + def running_timed_out_builds + Ci::Build.running.updated_at_before(BUILD_RUNNING_OUTDATED_TIMEOUT.ago) + end + end + end +end diff --git a/app/services/ci/stuck_builds/drop_service.rb b/app/services/ci/stuck_builds/drop_service.rb index 3fee9a94381..dbc1860eb2b 100644 --- a/app/services/ci/stuck_builds/drop_service.rb +++ b/app/services/ci/stuck_builds/drop_service.rb @@ -5,7 +5,6 @@ module Ci class DropService include DropHelpers - BUILD_RUNNING_OUTDATED_TIMEOUT = 1.hour BUILD_PENDING_OUTDATED_TIMEOUT = 1.day BUILD_SCHEDULED_OUTDATED_TIMEOUT = 1.hour BUILD_PENDING_STUCK_TIMEOUT = 1.hour @@ -14,8 +13,6 @@ module Ci def execute Gitlab::AppLogger.info "#{self.class}: Cleaning stuck builds" - drop(running_timed_out_builds, failure_reason: :stuck_or_timeout_failure) - drop( pending_builds(BUILD_PENDING_OUTDATED_TIMEOUT.ago), failure_reason: :stuck_or_timeout_failure @@ -50,13 +47,6 @@ module Ci BUILD_SCHEDULED_OUTDATED_TIMEOUT.ago ) end - - def running_timed_out_builds - Ci::Build.running.where( # rubocop: disable CodeReuse/ActiveRecord - 'ci_builds.updated_at < ?', - BUILD_RUNNING_OUTDATED_TIMEOUT.ago - ) - end end end end diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index ed9b5ef3a5e..1c294ea9204 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -228,6 +228,15 @@ :weight: 1 :idempotent: true :tags: [] +- :name: cronjob:ci_stuck_builds_drop_running + :worker_name: Ci::StuckBuilds::DropRunningWorker + :feature_category: :continuous_integration + :has_external_dependencies: + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: cronjob:container_expiration_policy :worker_name: ContainerExpirationPolicyWorker :feature_category: :container_registry diff --git a/app/workers/ci/stuck_builds/drop_running_worker.rb b/app/workers/ci/stuck_builds/drop_running_worker.rb new file mode 100644 index 00000000000..35326bf74c9 --- /dev/null +++ b/app/workers/ci/stuck_builds/drop_running_worker.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Ci + module StuckBuilds + class DropRunningWorker + include ApplicationWorker + + idempotent! + + # rubocop:disable Scalability/CronWorkerContext + # This is an instance-wide cleanup query, so there's no meaningful + # scope to consider this in the context of. + include CronjobQueue + # rubocop:enable Scalability/CronWorkerContext + + data_consistency :always + + feature_category :continuous_integration + + EXCLUSIVE_LEASE_KEY = 'ci_stuck_builds_drop_running_worker_lease' + + def perform + return unless try_obtain_lease + + begin + Ci::StuckBuilds::DropRunningService.new.execute + ensure + remove_lease + end + end + + private + + def try_obtain_lease + @uuid = Gitlab::ExclusiveLease.new(EXCLUSIVE_LEASE_KEY, timeout: 30.minutes).try_obtain + end + + def remove_lease + Gitlab::ExclusiveLease.cancel(EXCLUSIVE_LEASE_KEY, @uuid) + end + end + end +end diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb index a2b2686c8d5..20bc201f5f0 100644 --- a/app/workers/stuck_ci_jobs_worker.rb +++ b/app/workers/stuck_ci_jobs_worker.rb @@ -17,6 +17,8 @@ class StuckCiJobsWorker # rubocop:disable Scalability/IdempotentWorker EXCLUSIVE_LEASE_KEY = 'stuck_ci_builds_worker_lease' def perform + Ci::StuckBuilds::DropRunningWorker.perform_in(20.minutes) + return unless try_obtain_lease Ci::StuckBuilds::DropService.new.execute diff --git a/config/initializers/database_config.rb b/config/initializers/database_config.rb index e9f10abd0b9..7aedf9013ae 100644 --- a/config/initializers/database_config.rb +++ b/config/initializers/database_config.rb @@ -1,15 +1,5 @@ # frozen_string_literal: true -def log_pool_size(db, previous_pool_size, current_pool_size) - log_message = ["#{db} connection pool size: #{current_pool_size}"] - - if previous_pool_size && current_pool_size > previous_pool_size - log_message << "(increased from #{previous_pool_size} to match thread count)" - end - - Gitlab::AppLogger.debug(log_message.join(' ')) -end - Gitlab.ee do # We need to initialize the Geo database before # setting the Geo DB connection pool size. diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md index f3946131914..8a7add3e9e6 100644 --- a/doc/development/pipelines.md +++ b/doc/development/pipelines.md @@ -252,7 +252,7 @@ graph RL; 2_1-1 & 2_1-2 & 2_1-3 & 2_1-4 --> 1-6; end - 2_2-2["rspec frontend_fixture/rspec-ee frontend_fixture (7 minutes)"]; + 2_2-2["rspec-all frontend_fixture (7 minutes)"]; class 2_2-2 criticalPath; click 2_2-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7910143&udv=0" 2_2-4["memory-on-boot (3.5 minutes)"]; @@ -284,7 +284,7 @@ graph RL; 3_1-1["jest (14.5 minutes)"]; class 3_1-1 criticalPath; click 3_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914204&udv=0" - subgraph "Needs `rspec frontend_fixture/rspec-ee frontend_fixture`"; + subgraph "Needs `rspec-all frontend_fixture`"; 3_1-1 --> 2_2-2; end @@ -355,7 +355,7 @@ graph RL; 2_1-1 & 2_1-2 & 2_1-3 & 2_1-4 --> 1-6; end - 2_2-2["rspec frontend_fixture/rspec-ee frontend_fixture (7 minutes)"]; + 2_2-2["rspec-all frontend_fixture (7 minutes)"]; class 2_2-2 criticalPath; click 2_2-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7910143&udv=0" 2_2-4["memory-on-boot (3.5 minutes)"]; @@ -395,7 +395,7 @@ graph RL; 3_1-1["jest (14.5 minutes)"]; class 3_1-1 criticalPath; click 3_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914204&udv=0" - subgraph "Needs `rspec frontend_fixture/rspec-ee frontend_fixture`"; + subgraph "Needs `rspec-all frontend_fixture`"; 3_1-1 --> 2_2-2; end diff --git a/doc/operations/incident_management/alerts.md b/doc/operations/incident_management/alerts.md index c6ff70103d9..682a9a24712 100644 --- a/doc/operations/incident_management/alerts.md +++ b/doc/operations/incident_management/alerts.md @@ -53,6 +53,8 @@ immediately identify which alerts you should prioritize investigating: Alerts contain one of the following icons: + + | Severity | Icon | Color (hexadecimal) | |----------|-------------------------|---------------------| | Critical | **{severity-critical}** | `#8b2615` | @@ -62,6 +64,8 @@ Alerts contain one of the following icons: | Info | **{severity-info}** | `#418cd8` | | Unknown | **{severity-unknown}** | `#bababa` | + + ## Alert details page Navigate to the Alert details view by visiting the [Alert list](alerts.md) diff --git a/doc/user/application_security/coverage_fuzzing/index.md b/doc/user/application_security/coverage_fuzzing/index.md index 8e03369a702..303427d31e9 100644 --- a/doc/user/application_security/coverage_fuzzing/index.md +++ b/doc/user/application_security/coverage_fuzzing/index.md @@ -10,7 +10,7 @@ type: reference, howto Coverage-guided fuzzing sends random inputs to an instrumented version of your application in an effort to cause unexpected behavior. Such behavior indicates a bug that you should address. GitLab allows you to add coverage-guided fuzz testing to your pipelines. This helps you discover -bugs and potential security issues that other QA processes may miss. +bugs and potential security issues that other QA processes may miss. We recommend that you use fuzz testing in addition to the other security scanners in [GitLab Secure](../index.md) and your own test processes. If you're using [GitLab CI/CD](../../../ci/index.md), @@ -248,6 +248,8 @@ which shows an overview of all the security vulnerabilities in your groups, proj Clicking the vulnerability opens a modal that provides additional information about the vulnerability: + + - Status: The vulnerability's status. As with any type of vulnerability, a coverage fuzzing vulnerability can be Detected, Confirmed, Dismissed, or Resolved. - Project: The project in which the vulnerability exists. @@ -261,3 +263,5 @@ vulnerability: - Scanner: The scanner that detected the vulnerability (for example, Coverage Fuzzing). - Scanner Provider: The engine that did the scan. For Coverage Fuzzing, this can be any of the engines listed in [Supported fuzzing engines and languages](#supported-fuzzing-engines-and-languages). + + diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md index 3caa1771a5b..d713ee0d5bc 100644 --- a/doc/user/application_security/sast/index.md +++ b/doc/user/application_security/sast/index.md @@ -27,6 +27,8 @@ analysis are available in the [security dashboards](../security_dashboard/index. The results are sorted by the priority of the vulnerability: + + 1. Critical 1. High 1. Medium @@ -34,6 +36,8 @@ The results are sorted by the priority of the vulnerability: 1. Info 1. Unknown + + A pipeline consists of multiple jobs, including SAST and DAST scanning. If any job fails to finish for any reason, the security dashboard does not show SAST scanner output. For example, if the SAST job finishes but the DAST job fails, the security dashboard does not show SAST results. On failure, diff --git a/doc/user/application_security/vulnerability_report/index.md b/doc/user/application_security/vulnerability_report/index.md index 8b811c62ec3..f5b0194c320 100644 --- a/doc/user/application_security/vulnerability_report/index.md +++ b/doc/user/application_security/vulnerability_report/index.md @@ -45,6 +45,8 @@ From the Vulnerability Report you can: You can filter the vulnerabilities table by: + + | Filter | Available options | |:---------|:------------------| | Status | Detected, Confirmed, Dismissed, Resolved. | @@ -53,6 +55,8 @@ You can filter the vulnerabilities table by: | Project | For more details, see [Project filter](#project-filter). | | Activity | For more details, see [Activity filter](#activity-filter). | + + ### Filter the list of vulnerabilities To filter the list of vulnerabilities: diff --git a/doc/user/project/import/gitea.md b/doc/user/project/import/gitea.md index 9364ac4f954..3bbc70b4337 100644 --- a/doc/user/project/import/gitea.md +++ b/doc/user/project/import/gitea.md @@ -1,5 +1,4 @@ --- -type: reference, howto stage: Manage group: Import info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments @@ -10,30 +9,30 @@ info: To determine the technical writer assigned to the Stage/Group associated w Import your projects from Gitea to GitLab with minimal effort. NOTE: -This requires Gitea `v1.0.0` or newer. +This requires Gitea `v1.0.0` or later. The Gitea importer can import: -- Repository description (GitLab 8.15+) -- Git repository data (GitLab 8.15+) -- Issues (GitLab 8.15+) -- Pull requests (GitLab 8.15+) -- Milestones (GitLab 8.15+) -- Labels (GitLab 8.15+) +- Repository description +- Git repository data +- Issues +- Pull requests +- Milestones +- Labels When importing, repository public access is retained. If a repository is private in Gitea, it's created as private in GitLab as well. ## How it works -Since Gitea is currently not an OAuth provider, author/assignee cannot be mapped -to users in your GitLab instance. This means that the project creator (most of -the times the current user that started the import process) is set as the author, -but a reference on the issue about the original Gitea author is kept. +Because Gitea isn't an OAuth provider, author/assignee can't be mapped to users +in your GitLab instance. This means the project creator (usually the user that +started the import process) is set as the author. A reference, however, is kept +on the issue about the original Gitea author. -The importer creates any new namespaces (groups) if they don't exist or in -the case the namespace is taken, the repository is imported under the user's -namespace that started the import process. +The importer creates any new namespaces (groups) if they don't exist. If the +namespace is taken, the repository is imported under the user's namespace +that started the import process. ## Import your Gitea repositories @@ -41,7 +40,7 @@ The importer page is visible when you create a new project. ![New project page on GitLab](img/import_projects_from_new_project_page.png) -Click the **Gitea** link and the import authorization process starts. +Select the **Gitea** link to start the import authorization process. ![New Gitea project import](img/import_projects_from_gitea_new_import.png) @@ -52,13 +51,13 @@ GitLab access your repositories: 1. Go to `https://your-gitea-instance/user/settings/applications` (replace `your-gitea-instance` with the host of your Gitea instance). -1. Click **Generate New Token**. +1. Select **Generate New Token**. 1. Enter a token description. -1. Click **Generate Token**. +1. Select **Generate Token**. 1. Copy the token hash. 1. Go back to GitLab and provide the token to the Gitea importer. -1. Hit the **List Your Gitea Repositories** button and wait while GitLab reads - your repositories' information. Once done, you are taken to the importer +1. Select **List Your Gitea Repositories** and wait while GitLab reads + your repositories' information. After it's done, GitLab displays the importer page to select the repositories to import. ### Select which repositories to import @@ -66,19 +65,19 @@ GitLab access your repositories: After you've authorized access to your Gitea repositories, you are redirected to the Gitea importer page. -From there, you can see the import statuses of your Gitea repositories. +From there, you can view the import statuses of your Gitea repositories: -- Those that are being imported show a _started_ status, -- those already successfully imported are green with a _done_ status, -- whereas those that are not yet imported have an **Import** button on the +- Those that are being imported show a _started_ status. +- Those already successfully imported are green with a _done_ status. +- Those that aren't yet imported have an **Import** button on the right side of the table. You also can: -- Import all your Gitea projects in one go by hitting **Import all projects** in - the upper left corner. -- Filter projects by name. If filter is applied, hitting **Import all projects** - only imports matched projects. +- Import all of your Gitea projects in one go by selecting **Import all projects** + in the upper left corner. +- Filter projects by name. If filter is applied, selecting **Import all projects** + imports only matched projects. ![Gitea importer page](img/import_projects_from_gitea_importer_v12_3.png) diff --git a/package.json b/package.json index ae33b5ef76a..5f2c72beea2 100644 --- a/package.json +++ b/package.json @@ -55,9 +55,9 @@ "@babel/preset-env": "^7.10.1", "@gitlab/at.js": "1.5.7", "@gitlab/favicon-overlay": "2.0.0", - "@gitlab/svgs": "1.212.0", + "@gitlab/svgs": "1.213.0", "@gitlab/tributejs": "1.0.0", - "@gitlab/ui": "32.11.4", + "@gitlab/ui": "32.12.0", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "6.1.3-2", "@rails/ujs": "6.1.3-2", diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb index c3be58fda74..c136d14c1e5 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb @@ -58,8 +58,8 @@ module QA testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1806', issue_1: 'https://gitlab.com/gitlab-org/gitlab/-/issues/331252', issue_2: 'https://gitlab.com/gitlab-org/gitlab/-/issues/333678', - # mostly impacts testing as it makes small groups import slower - issue_3: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332351' + issue_3: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332351', + except: { job: 'instance-image-slow-network' } ) do Page::Group::BulkImport.perform do |import_page| import_page.import_group(imported_group.path, imported_group.sandbox.path) diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh index 797d9188f81..280a1586de3 100644 --- a/scripts/rspec_helpers.sh +++ b/scripts/rspec_helpers.sh @@ -109,14 +109,18 @@ function rspec_paralellized_job() { local test_level="${job_name[1]}" local report_name=$(echo "${CI_JOB_NAME}" | sed -E 's|[/ ]|_|g') # e.g. 'rspec unit pg12 1/24' would become 'rspec_unit_pg12_1_24' local rspec_opts="${1}" - local spec_folder_prefix="" + local spec_folder_prefixes="" if [[ "${test_tool}" =~ "-ee" ]]; then - spec_folder_prefix="ee/" + spec_folder_prefixes="'ee/'" fi if [[ "${test_tool}" =~ "-jh" ]]; then - spec_folder_prefix="jh/" + spec_folder_prefixes="'jh/'" + fi + + if [[ "${test_tool}" =~ "-all" ]]; then + spec_folder_prefixes="['', 'ee/']" fi export KNAPSACK_LOG_LEVEL="debug" @@ -131,7 +135,7 @@ function rspec_paralellized_job() { cp "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" "${KNAPSACK_REPORT_PATH}" if [[ -z "${KNAPSACK_TEST_FILE_PATTERN}" ]]; then - pattern=$(ruby -r./tooling/quality/test_level.rb -e "puts Quality::TestLevel.new(%(${spec_folder_prefix})).pattern(:${test_level})") + pattern=$(ruby -r./tooling/quality/test_level.rb -e "puts Quality::TestLevel.new(${spec_folder_prefixes}).pattern(:${test_level})") export KNAPSACK_TEST_FILE_PATTERN="${pattern}" fi diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 020d80775d2..f88d31bda88 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -68,7 +68,7 @@ RSpec.describe 'Issue Boards new issue', :js do wait_for_requests - page.within(first('.board .issue-count-badge-count')) do + page.within(first('.board [data-testid="issue-count-badge"]')) do expect(page).to have_content('1') end diff --git a/spec/features/boards/reload_boards_on_browser_back_spec.rb b/spec/features/boards/reload_boards_on_browser_back_spec.rb index 36682036d48..6a09e3c9506 100644 --- a/spec/features/boards/reload_boards_on_browser_back_spec.rb +++ b/spec/features/boards/reload_boards_on_browser_back_spec.rb @@ -16,7 +16,7 @@ RSpec.describe 'Ensure Boards do not show stale data on browser back', :js do visit project_board_path(project, board) wait_for_requests - page.within(first('.board .issue-count-badge-count')) do + page.within(first('.board [data-testid="issue-count-badge"]')) do expect(page).to have_content('0') end end @@ -35,7 +35,7 @@ RSpec.describe 'Ensure Boards do not show stale data on browser back', :js do page.go_back wait_for_requests - page.within(first('.board .issue-count-badge-count')) do + page.within(first('.board [data-testid="issue-count-badge"]')) do expect(page).to have_content('1') end diff --git a/spec/features/groups/board_spec.rb b/spec/features/groups/board_spec.rb index afe36dabcb5..aece6d790b5 100644 --- a/spec/features/groups/board_spec.rb +++ b/spec/features/groups/board_spec.rb @@ -24,7 +24,7 @@ RSpec.describe 'Group Boards' do it 'adds an issue to the backlog' do page.within(find('.board', match: :first)) do issue_title = 'New Issue' - find(:css, '.issue-count-badge-add-button').click + click_button 'New issue' wait_for_requests diff --git a/spec/features/issues/related_issues_spec.rb b/spec/features/issues/related_issues_spec.rb index 837859bbe26..a8933ed9c30 100644 --- a/spec/features/issues/related_issues_spec.rb +++ b/spec/features/issues/related_issues_spec.rb @@ -41,13 +41,13 @@ RSpec.describe 'Related issues', :js do visit project_issue_path(project, issue) expect(page).to have_css('.related-issues-block') - expect(page).not_to have_selector('.js-issue-count-badge-add-button') + expect(page).not_to have_button 'Add a related issue' end end context 'when logged in but not a member' do before do - gitlab_sign_in(user) + sign_in(user) end it 'shows widget when internal project' do @@ -57,7 +57,7 @@ RSpec.describe 'Related issues', :js do visit project_issue_path(project, issue) expect(page).to have_css('.related-issues-block') - expect(page).not_to have_selector('.js-issue-count-badge-add-button') + expect(page).not_to have_button 'Add a related issue' end it 'does not show widget when private project' do @@ -76,7 +76,7 @@ RSpec.describe 'Related issues', :js do visit project_issue_path(project, issue) expect(page).to have_css('.related-issues-block') - expect(page).not_to have_selector('.js-issue-count-badge-add-button') + expect(page).not_to have_button 'Add a related issue' end it 'shows widget on their own public issue' do @@ -86,13 +86,13 @@ RSpec.describe 'Related issues', :js do visit project_issue_path(project, issue) expect(page).to have_css('.related-issues-block') - expect(page).not_to have_selector('.js-issue-count-badge-add-button') + expect(page).not_to have_button 'Add a related issue' end end context 'when logged in and a guest' do before do - gitlab_sign_in(user) + sign_in(user) end it 'shows widget when internal project' do @@ -103,7 +103,7 @@ RSpec.describe 'Related issues', :js do visit project_issue_path(project, issue) expect(page).to have_css('.related-issues-block') - expect(page).not_to have_selector('.js-issue-count-badge-add-button') + expect(page).not_to have_button 'Add a related issue' end it 'shows widget when private project' do @@ -114,7 +114,7 @@ RSpec.describe 'Related issues', :js do visit project_issue_path(project, issue) expect(page).to have_css('.related-issues-block') - expect(page).not_to have_selector('.js-issue-count-badge-add-button') + expect(page).not_to have_button 'Add a related issue' end it 'shows widget when public project' do @@ -125,13 +125,13 @@ RSpec.describe 'Related issues', :js do visit project_issue_path(project, issue) expect(page).to have_css('.related-issues-block') - expect(page).not_to have_selector('.js-issue-count-badge-add-button') + expect(page).not_to have_button 'Add a related issue' end end context 'when logged in and a reporter' do before do - gitlab_sign_in(user) + sign_in(user) end it 'shows widget when internal project' do @@ -142,7 +142,7 @@ RSpec.describe 'Related issues', :js do visit project_issue_path(project, issue) expect(page).to have_css('.related-issues-block') - expect(page).to have_selector('.js-issue-count-badge-add-button') + expect(page).to have_button 'Add a related issue' end it 'shows widget when private project' do @@ -153,7 +153,7 @@ RSpec.describe 'Related issues', :js do visit project_issue_path(project, issue) expect(page).to have_css('.related-issues-block') - expect(page).to have_selector('.js-issue-count-badge-add-button') + expect(page).to have_button 'Add a related issue' end it 'shows widget when public project' do @@ -164,7 +164,7 @@ RSpec.describe 'Related issues', :js do visit project_issue_path(project, issue) expect(page).to have_css('.related-issues-block') - expect(page).to have_selector('.js-issue-count-badge-add-button') + expect(page).to have_button 'Add a related issue' end it 'shows widget on their own public issue' do @@ -175,7 +175,7 @@ RSpec.describe 'Related issues', :js do visit project_issue_path(project, issue) expect(page).to have_css('.related-issues-block') - expect(page).to have_selector('.js-issue-count-badge-add-button') + expect(page).to have_button 'Add a related issue' end end end @@ -186,7 +186,7 @@ RSpec.describe 'Related issues', :js do before do project.add_guest(user) - gitlab_sign_in(user) + sign_in(user) end context 'visiting some issue someone else created' do @@ -216,7 +216,7 @@ RSpec.describe 'Related issues', :js do before do project.add_maintainer(user) project_b.add_maintainer(user) - gitlab_sign_in(user) + sign_in(user) end context 'without existing related issues' do @@ -230,7 +230,7 @@ RSpec.describe 'Related issues', :js do end it 'add related issue' do - find('.js-issue-count-badge-add-button').click + click_button 'Add a related issue' find('.js-add-issuable-form-input').set "#{issue_b.to_reference(project)} " find('.js-add-issuable-form-add-button').click @@ -247,7 +247,7 @@ RSpec.describe 'Related issues', :js do end it 'add cross-project related issue' do - find('.js-issue-count-badge-add-button').click + click_button 'Add a related issue' find('.js-add-issuable-form-input').set "#{issue_project_b_a.to_reference(project)} " find('.js-add-issuable-form-add-button').click @@ -261,7 +261,7 @@ RSpec.describe 'Related issues', :js do end it 'pressing enter should submit the form' do - find('.js-issue-count-badge-add-button').click + click_button 'Add a related issue' find('.js-add-issuable-form-input').set "#{issue_project_b_a.to_reference(project)} " find('.js-add-issuable-form-input').native.send_key(:enter) @@ -275,7 +275,7 @@ RSpec.describe 'Related issues', :js do end it 'disallows duplicate entries' do - find('.js-issue-count-badge-add-button').click + click_button 'Add a related issue' find('.js-add-issuable-form-input').set 'duplicate duplicate duplicate' items = all('.js-add-issuable-form-token-list-item') @@ -288,7 +288,7 @@ RSpec.describe 'Related issues', :js do it 'allows us to remove pending issues' do # Tests against https://gitlab.com/gitlab-org/gitlab/issues/11625 - find('.js-issue-count-badge-add-button').click + click_button 'Add a related issue' find('.js-add-issuable-form-input').set 'issue1 issue2 issue3 ' items = all('.js-add-issuable-form-token-list-item') @@ -351,7 +351,7 @@ RSpec.describe 'Related issues', :js do end it 'add related issue' do - find('.js-issue-count-badge-add-button').click + click_button 'Add a related issue' find('.js-add-issuable-form-input').set "##{issue_d.iid} " find('.js-add-issuable-form-add-button').click @@ -367,7 +367,7 @@ RSpec.describe 'Related issues', :js do end it 'add invalid related issue' do - find('.js-issue-count-badge-add-button').click + click_button 'Add a related issue' find('.js-add-issuable-form-input').set "#9999999 " find('.js-add-issuable-form-add-button').click @@ -382,7 +382,7 @@ RSpec.describe 'Related issues', :js do end it 'add unauthorized related issue' do - find('.js-issue-count-badge-add-button').click + click_button 'Add a related issue' find('.js-add-issuable-form-input').set "#{issue_project_unauthorized_a.to_reference(project)} " find('.js-add-issuable-form-add-button').click diff --git a/spec/services/ci/stuck_builds/drop_running_service_spec.rb b/spec/services/ci/stuck_builds/drop_running_service_spec.rb new file mode 100644 index 00000000000..d2132914a02 --- /dev/null +++ b/spec/services/ci/stuck_builds/drop_running_service_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::StuckBuilds::DropRunningService do + let!(:runner) { create :ci_runner } + let!(:job) { create :ci_build, runner: runner } + let(:created_at) { } + let(:updated_at) { } + + subject(:service) { described_class.new } + + before do + job_attributes = { status: status } + job_attributes[:created_at] = created_at if created_at + job_attributes[:updated_at] = updated_at if updated_at + job.update!(job_attributes) + end + + context 'when job is running' do + let(:status) { 'running' } + + context 'when job was updated_at more than an hour ago' do + let(:updated_at) { 2.hours.ago } + + it_behaves_like 'job is dropped' + end + + context 'when job was updated in less than 1 hour ago' do + let(:updated_at) { 30.minutes.ago } + + it_behaves_like 'job is unchanged' + end + end + + %w(success skipped failed canceled scheduled pending).each do |status| + context "when job is #{status}" do + let(:status) { status } + let(:updated_at) { 2.days.ago } + + context 'when created_at is the same as updated_at' do + let(:created_at) { 2.days.ago } + + it_behaves_like 'job is unchanged' + end + + context 'when created_at is before updated_at' do + let(:created_at) { 3.days.ago } + + it_behaves_like 'job is unchanged' + end + end + end + + context 'for deleted project' do + let(:status) { 'running' } + let(:updated_at) { 2.days.ago } + + before do + job.project.update!(pending_delete: true) + end + + it_behaves_like 'job is dropped' + end +end diff --git a/spec/services/ci/stuck_builds/drop_service_spec.rb b/spec/services/ci/stuck_builds/drop_service_spec.rb index 8dfd1bc1b3d..4aca19ae7b9 100644 --- a/spec/services/ci/stuck_builds/drop_service_spec.rb +++ b/spec/services/ci/stuck_builds/drop_service_spec.rb @@ -17,48 +17,6 @@ RSpec.describe Ci::StuckBuilds::DropService do job.update!(job_attributes) end - shared_examples 'job is dropped' do - it 'changes status' do - expect(service).to receive(:drop).exactly(3).times.and_call_original - expect(service).to receive(:drop_stuck).exactly(:once).and_call_original - - service.execute - job.reload - - expect(job).to be_failed - expect(job).to be_stuck_or_timeout_failure - end - - context 'when job have data integrity problem' do - it "does drop the job and logs the reason" do - job.update_columns(yaml_variables: '[{"key" => "value"}]') - - expect(Gitlab::ErrorTracking).to receive(:track_exception) - .with(anything, a_hash_including(build_id: job.id)) - .once - .and_call_original - - service.execute - job.reload - - expect(job).to be_failed - expect(job).to be_data_integrity_failure - end - end - end - - shared_examples 'job is unchanged' do - it 'does not change status' do - expect(service).to receive(:drop).exactly(3).times.and_call_original - expect(service).to receive(:drop_stuck).exactly(:once).and_call_original - - service.execute - job.reload - - expect(job.status).to eq(status) - end - end - context 'when job is pending' do let(:status) { 'pending' } @@ -195,7 +153,7 @@ RSpec.describe Ci::StuckBuilds::DropService do context 'when job was updated_at more than an hour ago' do let(:updated_at) { 2.hours.ago } - it_behaves_like 'job is dropped' + it_behaves_like 'job is unchanged' end context 'when job was updated in less than 1 hour ago' do @@ -238,7 +196,7 @@ RSpec.describe Ci::StuckBuilds::DropService do job.project.update!(pending_delete: true) end - it_behaves_like 'job is dropped' + it_behaves_like 'job is unchanged' end describe 'drop stale scheduled builds' do diff --git a/spec/support/helpers/feature_flag_helpers.rb b/spec/support/helpers/feature_flag_helpers.rb index 51ba9039b70..4e57002a7c6 100644 --- a/spec/support/helpers/feature_flag_helpers.rb +++ b/spec/support/helpers/feature_flag_helpers.rb @@ -71,7 +71,7 @@ module FeatureFlagHelpers end def add_linked_issue_button - find('.js-issue-count-badge-add-button') + find_button 'Add a related issue' end def remove_linked_issue_button diff --git a/spec/support/shared_examples/ci/stuck_builds_shared_examples.rb b/spec/support/shared_examples/ci/stuck_builds_shared_examples.rb new file mode 100644 index 00000000000..2f26dac46e2 --- /dev/null +++ b/spec/support/shared_examples/ci/stuck_builds_shared_examples.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'job is dropped' do + it 'changes status' do + service.execute + job.reload + + expect(job).to be_failed + expect(job).to be_stuck_or_timeout_failure + end + + context 'when job has data integrity problem' do + it 'drops the job and logs the reason' do + job.update_columns(yaml_variables: '[{"key" => "value"}]') + + expect(Gitlab::ErrorTracking) + .to receive(:track_exception) + .with(anything, a_hash_including(build_id: job.id)) + .once + .and_call_original + + service.execute + job.reload + + expect(job).to be_failed + expect(job).to be_data_integrity_failure + end + end +end + +RSpec.shared_examples 'job is unchanged' do + it 'does not change status' do + service.execute + job.reload + + expect(job.status).to eq(status) + end +end diff --git a/spec/tooling/quality/test_level_spec.rb b/spec/tooling/quality/test_level_spec.rb index 89abe337347..0623a67a60e 100644 --- a/spec/tooling/quality/test_level_spec.rb +++ b/spec/tooling/quality/test_level_spec.rb @@ -63,7 +63,14 @@ RSpec.describe Quality::TestLevel do context 'with a prefix' do it 'returns a pattern' do expect(described_class.new('ee/').pattern(:system)) - .to eq("ee/spec/{features}{,/**/}*_spec.rb") + .to eq("{ee/}spec/{features}{,/**/}*_spec.rb") + end + end + + context 'with several prefixes' do + it 'returns a pattern' do + expect(described_class.new(['', 'ee/', 'jh/']).pattern(:system)) + .to eq("{,ee/,jh/}spec/{features}{,/**/}*_spec.rb") end end @@ -138,7 +145,14 @@ RSpec.describe Quality::TestLevel do context 'with a prefix' do it 'returns a regexp' do expect(described_class.new('ee/').regexp(:system)) - .to eq(%r{ee/spec/(features)}) + .to eq(%r{(ee/)spec/(features)}) + end + end + + context 'with several prefixes' do + it 'returns a regexp' do + expect(described_class.new(['', 'ee/', 'jh/']).regexp(:system)) + .to eq(%r{(|ee/|jh/)spec/(features)}) end end diff --git a/spec/workers/ci/stuck_builds/drop_running_worker_spec.rb b/spec/workers/ci/stuck_builds/drop_running_worker_spec.rb new file mode 100644 index 00000000000..68b0696145a --- /dev/null +++ b/spec/workers/ci/stuck_builds/drop_running_worker_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::StuckBuilds::DropRunningWorker do + include ExclusiveLeaseHelpers + + let(:worker_lease_key) { Ci::StuckBuilds::DropRunningWorker::EXCLUSIVE_LEASE_KEY } + let(:worker_lease_uuid) { SecureRandom.uuid } + let(:worker2) { described_class.new } + + subject(:worker) { described_class.new } + + before do + stub_exclusive_lease(worker_lease_key, worker_lease_uuid) + end + + describe '#perform' do + it_behaves_like 'an idempotent worker' + + it 'executes an instance of Ci::StuckBuilds::DropRunningService' do + expect_next_instance_of(Ci::StuckBuilds::DropRunningService) do |service| + expect(service).to receive(:execute).exactly(:once) + end + + worker.perform + end + + context 'with an exclusive lease' do + it 'does not execute concurrently' do + expect(worker).to receive(:remove_lease).exactly(:once) + expect(worker2).not_to receive(:remove_lease) + + worker.perform + + stub_exclusive_lease_taken(worker_lease_key) + + worker2.perform + end + + it 'can execute in sequence' do + expect(worker).to receive(:remove_lease).at_least(:once) + expect(worker2).to receive(:remove_lease).at_least(:once) + + worker.perform + worker2.perform + end + + it 'cancels exclusive leases after worker perform' do + expect_to_cancel_exclusive_lease(worker_lease_key, worker_lease_uuid) + + worker.perform + end + + context 'when the DropRunningService fails' do + it 'ensures cancellation of the exclusive lease' do + expect_to_cancel_exclusive_lease(worker_lease_key, worker_lease_uuid) + + allow_next_instance_of(Ci::StuckBuilds::DropRunningService) do |service| + allow(service).to receive(:execute) do + raise 'The query timed out' + end + end + + expect { worker.perform }.to raise_error(/The query timed out/) + end + end + end + end +end diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb index e0a5d3c6c1c..ca7d2a7016b 100644 --- a/spec/workers/stuck_ci_jobs_worker_spec.rb +++ b/spec/workers/stuck_ci_jobs_worker_spec.rb @@ -16,7 +16,13 @@ RSpec.describe StuckCiJobsWorker do end describe '#perform' do - it 'executes an instance of Ci::StuckBuildsDropService' do + it 'enqueues a Ci::StuckBuilds::DropRunningWorker job' do + expect(Ci::StuckBuilds::DropRunningWorker).to receive(:perform_in).with(20.minutes).exactly(:once) + + worker.perform + end + + it 'executes an instance of Ci::StuckBuilds::DropService' do expect_next_instance_of(Ci::StuckBuilds::DropService) do |service| expect(service).to receive(:execute).exactly(:once) end diff --git a/tooling/quality/test_level.rb b/tooling/quality/test_level.rb index ad9de067375..83cbe7a1f19 100644 --- a/tooling/quality/test_level.rb +++ b/tooling/quality/test_level.rb @@ -60,20 +60,20 @@ module Quality system: ['features'] }.freeze - attr_reader :prefix + attr_reader :prefixes - def initialize(prefix = nil) - @prefix = prefix + def initialize(prefixes = nil) + @prefixes = Array(prefixes) @patterns = {} @regexps = {} end def pattern(level) - @patterns[level] ||= "#{prefix}spec/#{folders_pattern(level)}{,/**/}*#{suffix(level)}" + @patterns[level] ||= "#{prefixes_for_pattern}spec/#{folders_pattern(level)}{,/**/}*#{suffix(level)}" end def regexp(level) - @regexps[level] ||= Regexp.new("#{prefix}spec/#{folders_regex(level)}").freeze + @regexps[level] ||= Regexp.new("#{prefixes_for_regex}spec/#{folders_regex(level)}").freeze end def level_for(file_path) @@ -102,6 +102,20 @@ module Quality private + def prefixes_for_pattern + return '' if prefixes.empty? + + "{#{prefixes.join(',')}}" + end + + def prefixes_for_regex + return '' if prefixes.empty? + + regex_prefix = prefixes.map(&Regexp.method(:escape)).join('|') + + "(#{regex_prefix})" + end + def suffix(level) case level when :frontend_fixture diff --git a/yarn.lock b/yarn.lock index 3e303259e36..c75060bdcf2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -964,20 +964,20 @@ stylelint-declaration-strict-value "1.7.7" stylelint-scss "3.18.0" -"@gitlab/svgs@1.212.0": - version "1.212.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.212.0.tgz#21a5df04c52b10cc1b8521cd8ff7c7d6d13716db" - integrity sha512-dv0bYTHA3hwi3mNU3bGMq1cd4HVKKFNwCNPgkF91JSp4Xt8DDtJ0Yq4X49ASsq4zCJ3odgkq2aPjEa/Sr5nINQ== +"@gitlab/svgs@1.213.0": + version "1.213.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.213.0.tgz#fcd9794049d2b15f5796dbab2a3d501679153582" + integrity sha512-3d9EGpEkPDeW92Xx3FueFCJFZ/yL+uv5MWCUHmSt1tP9YmUhMXw/51c43c5+V17FuCyvhJS5tm3aEg3VYoWIRA== "@gitlab/tributejs@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8" integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw== -"@gitlab/ui@32.11.4": - version "32.11.4" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.11.4.tgz#ead781c4fd57b4f7bc6818de99e56d023f94c397" - integrity sha512-plxOvtAIo+PIng1jBssj/QCx2ATIbZqQvLvCeezgAABgY107XhqZDw50VZL0texTX+0jGwB5PaXthmU/wnCk2w== +"@gitlab/ui@32.12.0": + version "32.12.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.12.0.tgz#a2e45a7587beb2b9a5014f36c10bac30e6f90f78" + integrity sha512-WP7uyf8Ujd2ONOIYLG6s3oIl8/IWpI2H12r7rwNG+yD8EOhvHdK8QS9+b5gpAewYJjT4r2AV8m9QJXZChmmO6w== dependencies: "@babel/standalone" "^7.0.0" bootstrap-vue "2.18.1"