New upstream version 13.2.1

This commit is contained in:
Pirate Praveen 2020-07-28 23:09:34 +05:30
parent f7a89b7334
commit 029fca15f4
8181 changed files with 344178 additions and 71305 deletions

1
.gitignore vendored
View file

@ -17,6 +17,7 @@ eslint-report.html
.rbx/ .rbx/
/.ruby-gemset /.ruby-gemset
/.ruby-version /.ruby-version
/.tool-versions
/.rvmrc /.rvmrc
.sass-cache/ .sass-cache/
/.secret /.secret

View file

@ -20,6 +20,8 @@ default:
- gitlab-org - gitlab-org
# All jobs are interruptible by default # All jobs are interruptible by default
interruptible: true interruptible: true
# Default job timeout set to 90m https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/10520
timeout: 90m
workflow: workflow:
rules: rules:
@ -61,6 +63,7 @@ variables:
DOCKER_VERSION: "19.03.0" DOCKER_VERSION: "19.03.0"
include: include:
- local: .gitlab/ci/build-images.gitlab-ci.yml
- local: .gitlab/ci/cache-repo.gitlab-ci.yml - local: .gitlab/ci/cache-repo.gitlab-ci.yml
- local: .gitlab/ci/cng.gitlab-ci.yml - local: .gitlab/ci/cng.gitlab-ci.yml
- local: .gitlab/ci/docs.gitlab-ci.yml - local: .gitlab/ci/docs.gitlab-ci.yml

View file

@ -13,6 +13,7 @@
/doc/development/ @marcia @mjang1 /doc/development/ @marcia @mjang1
/doc/development/documentation/ @mikelewis /doc/development/documentation/ @mikelewis
/doc/ci @marcel.amirault @sselhorn /doc/ci @marcel.amirault @sselhorn
/doc/operations @aqualls @eread
/doc/user/clusters @aqualls /doc/user/clusters @aqualls
/doc/user/infrastructure @aqualls /doc/user/infrastructure @aqualls
/doc/user/project/clusters @aqualls /doc/user/project/clusters @aqualls
@ -43,17 +44,12 @@
# Feature specific owners # Feature specific owners
/ee/lib/ee/gitlab/auth/ldap/ @dblessing @mkozono /ee/lib/ee/gitlab/auth/ldap/ @dblessing @mkozono
/lib/gitlab/auth/ldap/ @dblessing @mkozono /lib/gitlab/auth/ldap/ @dblessing @mkozono
/lib/gitlab/ci/templates/ @nolith @zj /lib/gitlab/ci/templates/ @nolith @dosuken123
/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @DylanGriffith @mayra-cabrera @tkuah /lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @DylanGriffith @mayra-cabrera @tkuah
/lib/gitlab/ci/templates/Security/ @plafoucriere @gonzoyumo @twoodham @sethgitlab /lib/gitlab/ci/templates/Security/ @plafoucriere @gonzoyumo @twoodham @sethgitlab
/ee/app/models/project_alias.rb @patrickbajao /ee/app/models/project_alias.rb @patrickbajao
/ee/lib/api/project_aliases.rb @patrickbajao /ee/lib/api/project_aliases.rb @patrickbajao
# Code Owners
#
/ee/lib/gitlab/code_owners/ @reprazent @kerrizor @garyh
/doc/user/project/code_owners.md @reprazent @kerrizor @garyh
# Quality owned files # Quality owned files
/qa/ @gl-quality /qa/ @gl-quality
@ -77,3 +73,9 @@ Dangerfile @gl-quality/eng-prod
/lib/gitlab/usage_data.rb @gitlab-org/growth/telemetry /lib/gitlab/usage_data.rb @gitlab-org/growth/telemetry
/lib/gitlab/cycle_analytics/usage_data.rb @gitlab-org/growth/telemetry /lib/gitlab/cycle_analytics/usage_data.rb @gitlab-org/growth/telemetry
/lib/gitlab/usage_data_counters/ @gitlab-org/growth/telemetry /lib/gitlab/usage_data_counters/ @gitlab-org/growth/telemetry
[Code Owners]
/ee/lib/gitlab/code_owners.rb @reprazent @kerrizor @garyh
/ee/lib/gitlab/code_owners/ @reprazent @kerrizor @garyh
/ee/spec/lib/gitlab/code_owners/ @reprazent @kerrizor @garyh
/doc/user/project/code_owners.md @reprazent @kerrizor @garyh

View file

@ -0,0 +1,31 @@
# This image is used by the `review-qa-*` jobs. Not currently used by the `omnibus-gitlab` pipelines which rebuild this
# image, e.g. https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror/-/jobs/587107399, which we could probably avoid.
# See https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5429.
build-qa-image:
extends:
- .use-kaniko
- .build-images:rules:build-qa-image
stage: build-images
needs: []
script:
- export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
- /kaniko/executor --context=${CI_PROJECT_DIR} --dockerfile=${CI_PROJECT_DIR}/qa/Dockerfile --destination=${QA_IMAGE} --cache=true
retry: 2
# This image is used by:
# - The `CNG` pipelines (via the `review-build-cng` job): https://gitlab.com/gitlab-org/build/CNG/-/blob/cfc67136d711e1c8c409bf8e57427a644393da2f/.gitlab-ci.yml#L335
# - The `omnibus-gitlab` pipelines (via the `package-and-qa` job): https://gitlab.com/gitlab-org/omnibus-gitlab/-/blob/dfd1ad475868fc84e91ab7b5706aa03e46dc3a86/.gitlab-ci.yml#L130
build-assets-image:
extends:
- .use-kaniko
- .build-images:rules:build-assets-image
stage: build-images
needs: ["compile-production-assets"]
variables:
GIT_DEPTH: "1"
script:
# TODO: Change the image tag to be the MD5 of assets files and skip image building if the image exists
# We'll also need to pass GITLAB_ASSETS_TAG to the trigerred omnibus-gitlab pipeline similarly to how we do it for trigerred CNG pipelines
# https://gitlab.com/gitlab-org/gitlab/issues/208389
- run_timed_command "scripts/build_assets_image"
retry: 2

View file

@ -59,6 +59,15 @@ docs lint:
# Check the internal anchor links # Check the internal anchor links
- bundle exec nanoc check internal_anchors - bundle exec nanoc check internal_anchors
ui-docs-links lint:
extends:
- .docs:rules:docs-lint
- .static-analysis-base
stage: test
needs: []
script:
- bundle exec haml-lint -i DocumentationLinks
graphql-reference-verify: graphql-reference-verify:
extends: extends:
- .default-retry - .default-retry

View file

@ -2,16 +2,18 @@
extends: extends:
- .default-retry - .default-retry
- .default-before_script - .default-before_script
- .assets-compile-cache
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
# we override the max_old_space_size to prevent OOM errors # we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584 NODE_OPTIONS: --max_old_space_size=3584
WEBPACK_VENDOR_DLL: "true"
.compile-assets-base: .compile-assets-base:
extends: .frontend-base extends:
- .frontend-base
- .assets-compile-cache
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-git-2.27-lfs-2.9-node-12.x-yarn-1.21-graphicsmagick-1.3.34 image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-git-2.27-lfs-2.9-node-12.x-yarn-1.21-graphicsmagick-1.3.34
variables:
WEBPACK_VENDOR_DLL: "true"
stage: prepare stage: prepare
script: script:
- node --version - node --version
@ -90,21 +92,6 @@ update-yarn-cache:
cache: cache:
policy: push policy: push
build-assets-image:
extends:
- .use-kaniko
- .frontend:rules:compile-production-assets
stage: build-images
needs: ["compile-production-assets"]
variables:
GIT_DEPTH: "1"
script:
# TODO: Change the image tag to be the MD5 of assets files and skip image building if the image exists
# We'll also need to pass GITLAB_ASSETS_TAG to the trigerred omnibus-gitlab pipeline similarly to how we do it for trigerred CNG pipelines
# https://gitlab.com/gitlab-org/gitlab/issues/208389
- run_timed_command "scripts/build_assets_image"
retry: 2
.frontend-fixtures-base: .frontend-fixtures-base:
extends: extends:
- .frontend-base - .frontend-base
@ -114,6 +101,7 @@ build-assets-image:
needs: ["setup-test-env", "compile-test-assets"] needs: ["setup-test-env", "compile-test-assets"]
variables: variables:
SETUP_DB: "true" SETUP_DB: "true"
WEBPACK_VENDOR_DLL: "true"
script: script:
- run_timed_command "scripts/gitaly-test-build" - run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn" - run_timed_command "scripts/gitaly-test-spawn"
@ -138,22 +126,25 @@ frontend-fixtures-as-if-foss:
.frontend-test-base: .frontend-test-base:
extends: extends:
- .default-retry - .frontend-base
- .yarn-cache - .yarn-cache
variables: variables:
USE_BUNDLE_INSTALL: "false" USE_BUNDLE_INSTALL: "false"
SETUP_DB: "false"
stage: test stage: test
before_script:
- source scripts/utils.sh eslint-as-if-foss:
extends:
- .frontend-test-base
- .frontend:rules:eslint-as-if-foss
- .as-if-foss
needs: []
script:
- run_timed_command "retry yarn install --frozen-lockfile"
- yarn run eslint
.karma-base: .karma-base:
extends: .frontend-test-base extends: .frontend-test-base
variables:
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
script: script:
- source scripts/utils.sh
- export BABEL_ENV=coverage CHROME_LOG_FILE=chrome_debug.log - export BABEL_ENV=coverage CHROME_LOG_FILE=chrome_debug.log
- run_timed_command "retry yarn install --frozen-lockfile" - run_timed_command "retry yarn install --frozen-lockfile"
- run_timed_command "yarn karma" - run_timed_command "yarn karma"
@ -174,6 +165,7 @@ karma:
- tmp/tests/frontend/ - tmp/tests/frontend/
reports: reports:
junit: junit_karma.xml junit: junit_karma.xml
cobertura: coverage-javascript/cobertura-coverage.xml
karma-as-if-foss: karma-as-if-foss:
extends: extends:
@ -185,7 +177,6 @@ karma-as-if-foss:
.jest-base: .jest-base:
extends: .frontend-test-base extends: .frontend-test-base
script: script:
- source scripts/utils.sh
- run_timed_command "retry yarn install --frozen-lockfile" - run_timed_command "retry yarn install --frozen-lockfile"
- run_timed_command "yarn jest --ci --coverage --testSequencer ./scripts/frontend/parallel_ci_sequencer.js" - run_timed_command "yarn jest --ci --coverage --testSequencer ./scripts/frontend/parallel_ci_sequencer.js"
@ -211,7 +202,6 @@ jest-integration:
- .frontend-test-base - .frontend-test-base
- .frontend:rules:default-frontend-jobs - .frontend:rules:default-frontend-jobs
script: script:
- source scripts/utils.sh
- run_timed_command "retry yarn install --frozen-lockfile" - run_timed_command "retry yarn install --frozen-lockfile"
- run_timed_command "yarn jest:integration --ci" - run_timed_command "yarn jest:integration --ci"
needs: ["frontend-fixtures"] needs: ["frontend-fixtures"]
@ -236,11 +226,14 @@ coverage-frontend:
- run_timed_command "retry yarn install --frozen-lockfile" - run_timed_command "retry yarn install --frozen-lockfile"
script: script:
- run_timed_command "yarn node scripts/frontend/merge_coverage_frontend.js" - run_timed_command "yarn node scripts/frontend/merge_coverage_frontend.js"
coverage: '/^Statements\s*:\s*?(\d+(?:\.\d+)?)%/'
artifacts: artifacts:
name: coverage-frontend name: coverage-frontend
expire_in: 31d expire_in: 31d
paths: paths:
- coverage-frontend/ - coverage-frontend/
reports:
cobertura: coverage-frontend/cobertura-coverage.xml
.qa-frontend-node: .qa-frontend-node:
extends: extends:

View file

@ -18,7 +18,7 @@
.rails-cache: .rails-cache:
cache: cache:
key: "rails-v1" key: "rails-v2"
paths: paths:
- vendor/ruby/ - vendor/ruby/
- vendor/gitaly-ruby/ - vendor/gitaly-ruby/
@ -72,6 +72,15 @@
variables: variables:
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
.use-pg12:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.27-lfs-2.9-chrome-83-node-12.x-yarn-1.21-postgresql-12-graphicsmagick-1.3.34"
services:
- name: postgres:12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
variables:
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg11-ee: .use-pg11-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.27-lfs-2.9-chrome-83-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.27-lfs-2.9-chrome-83-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
services: services:
@ -82,6 +91,16 @@
variables: variables:
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
.use-pg12-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.27-lfs-2.9-chrome-83-node-12.x-yarn-1.21-postgresql-12-graphicsmagick-1.3.34"
services:
- name: postgres:12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
- name: elasticsearch:6.4.2
variables:
POSTGRES_HOST_AUTH_METHOD: trust
.use-kaniko: .use-kaniko:
image: image:
name: gcr.io/kaniko-project/executor:debug-v0.20.0 name: gcr.io/kaniko-project/executor:debug-v0.20.0

View file

@ -49,7 +49,6 @@ update-qa-cache:
.package-and-qa-base: .package-and-qa-base:
image: ruby:2.6-alpine image: ruby:2.6-alpine
stage: qa stage: qa
dependencies: []
retry: 0 retry: 0
script: script:
- source scripts/utils.sh - source scripts/utils.sh

View file

@ -1,9 +1,129 @@
######################
# rspec job base specs
.rails-job-base: .rails-job-base:
extends: extends:
- .default-retry - .default-retry
- .default-before_script - .default-before_script
- .rails-cache - .rails-cache
.rspec-base:
extends: .rails-job-base
stage: test
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
script:
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
- source scripts/rspec_helpers.sh
- rspec_paralellized_job "--tag ~quarantine --tag ~geo --tag ~level:migration"
artifacts:
expire_in: 31d
when: always
paths:
- coverage/
- knapsack/
- rspec_flaky/
- rspec_profiling/
- tmp/capybara/
- tmp/memory_test/
- log/*.log
reports:
junit: junit_rspec.xml
.rspec-base-migration:
extends: .rails:rules:ee-and-foss-migration
script:
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
- source scripts/rspec_helpers.sh
- rspec_paralellized_job "--tag ~quarantine --tag ~geo --tag level:migration"
.rspec-base-pg11:
extends:
- .rspec-base
- .use-pg11
.rspec-base-pg12:
extends:
- .rspec-base
- .use-pg12
.rspec-base-pg11-as-if-foss:
extends:
- .rspec-base
- .as-if-foss
- .use-pg11
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets as-if-foss"]
.rspec-ee-base-pg11:
extends:
- .rspec-base
- .use-pg11-ee
.rspec-ee-base-pg12:
extends:
- .rspec-base
- .use-pg12-ee
.rspec-ee-base-geo:
extends: .rspec-base
script:
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
- source scripts/rspec_helpers.sh
- scripts/prepare_postgres_fdw.sh
- rspec_paralellized_job "--tag ~quarantine --tag geo"
.rspec-ee-base-geo-pg11:
extends:
- .rspec-ee-base-geo
- .use-pg11-ee
.rspec-ee-base-geo-pg12:
extends:
- .rspec-ee-base-geo
- .use-pg12-ee
.db-job-base:
extends:
- .rails-job-base
- .rails:rules:ee-and-foss-migration
- .use-pg11
stage: test
needs: ["setup-test-env"]
# rspec job base specs
######################
############################
# rspec job parallel configs
.rspec-migration-parallel:
parallel: 5
.rspec-ee-migration-parallel:
parallel: 2
.rspec-unit-parallel:
parallel: 20
.rspec-ee-unit-parallel:
parallel: 10
.rspec-ee-unit-geo-parallel:
parallel: 2
.rspec-integration-parallel:
parallel: 8
.rspec-ee-integration-parallel:
parallel: 4
.rspec-system-parallel:
parallel: 24
.rspec-ee-system-parallel:
parallel: 6
# rspec job parallel configs
############################
####################################################### #######################################################
# EE/FOSS: default refs (MRs, master, schedules) jobs # # EE/FOSS: default refs (MRs, master, schedules) jobs #
setup-test-env: setup-test-env:
@ -86,73 +206,37 @@ downtime_check:
script: script:
- bundle exec rake downtime_check - bundle exec rake downtime_check
.rspec-base:
extends: .rails-job-base
stage: test
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
script:
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
- source scripts/rspec_helpers.sh
- rspec_paralellized_job "--tag ~quarantine --tag ~geo --tag ~level:migration"
artifacts:
expire_in: 31d
when: always
paths:
- coverage/
- knapsack/
- rspec_flaky/
- rspec_profiling/
- tmp/capybara/
- tmp/memory_test/
- log/*.log
reports:
junit: junit_rspec.xml
.rspec-base-pg11:
extends:
- .rspec-base
- .rails:rules:ee-and-foss
- .use-pg11
.rspec-base-migration:
script:
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
- source scripts/rspec_helpers.sh
- rspec_paralellized_job "--tag ~quarantine --tag ~geo --tag level:migration"
rspec migration pg11: rspec migration pg11:
extends: extends:
- .rspec-base-pg11 - .rspec-base-pg11
- .rspec-base-migration - .rspec-base-migration
parallel: 5 - .rspec-migration-parallel
rspec unit pg11: rspec unit pg11:
extends: .rspec-base-pg11 extends:
parallel: 20 - .rspec-base-pg11
- .rails:rules:ee-and-foss-unit
- .rspec-unit-parallel
rspec integration pg11: rspec integration pg11:
extends: .rspec-base-pg11 extends:
parallel: 8 - .rspec-base-pg11
- .rails:rules:ee-and-foss-integration
- .rspec-integration-parallel
rspec system pg11: rspec system pg11:
extends: .rspec-base-pg11 extends:
parallel: 24 - .rspec-base-pg11
- .rails:rules:ee-and-foss-system
- .rspec-system-parallel
rspec fast_spec_helper: rspec fast_spec_helper:
extends: .rspec-base-pg11 extends:
- .rspec-base-pg11
- .rails:rules:ee-and-foss-fast_spec_helper
script: script:
- bin/rspec spec/fast_spec_helper.rb - bin/rspec spec/fast_spec_helper.rb
.db-job-base:
extends:
- .rails-job-base
- .rails:rules:ee-and-foss
- .use-pg11
stage: test
needs: ["setup-test-env"]
db:migrate:reset: db:migrate:reset:
extends: .db-job-base extends: .db-job-base
script: script:
@ -216,7 +300,7 @@ gitlab:setup:
rspec:coverage: rspec:coverage:
extends: extends:
- .rails-job-base - .rails-job-base
- .rails:rules:ee-mr-and-master-only - .rails:rules:rspec-coverage
stage: post-test stage: post-test
# We cannot use needs since it would mean needing 84 jobs (since most are parallelized) # We cannot use needs since it would mean needing 84 jobs (since most are parallelized)
# so we use `dependencies` here. # so we use `dependencies` here.
@ -248,118 +332,180 @@ rspec:coverage:
- coverage/index.html - coverage/index.html
- coverage/assets/ - coverage/assets/
- tmp/memory_test/ - tmp/memory_test/
reports:
cobertura: coverage/coverage.xml
# EE/FOSS: default refs (MRs, master, schedules) jobs # # EE/FOSS: default refs (MRs, master, schedules) jobs #
####################################################### #######################################################
################################################## ##################################################
# EE: default refs (MRs, master, schedules) jobs # # EE: default refs (MRs, master, schedules) jobs #
.rspec-base-ee:
extends:
- .rspec-base
- .rails:rules:ee-only
.rspec-base-pg11-as-if-foss:
extends:
- .rspec-base
- .rails:rules:as-if-foss
- .as-if-foss
- .use-pg11
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets as-if-foss"]
.rspec-ee-base-pg11:
extends:
- .rspec-base-ee
- .use-pg11-ee
rspec migration pg11-as-if-foss: rspec migration pg11-as-if-foss:
extends: extends:
- .rspec-base-pg11-as-if-foss - .rspec-base-pg11-as-if-foss
- .rspec-base-migration - .rspec-base-migration
parallel: 5 - .rails:rules:as-if-foss-migration
- .rspec-migration-parallel
rspec unit pg11-as-if-foss: rspec unit pg11-as-if-foss:
extends: .rspec-base-pg11-as-if-foss extends:
parallel: 20 - .rspec-base-pg11-as-if-foss
- .rails:rules:as-if-foss-unit
- .rspec-unit-parallel
rspec integration pg11-as-if-foss: rspec integration pg11-as-if-foss:
extends: .rspec-base-pg11-as-if-foss extends:
parallel: 8 - .rspec-base-pg11-as-if-foss
- .rails:rules:as-if-foss-integration
- .rspec-integration-parallel
rspec system pg11-as-if-foss: rspec system pg11-as-if-foss:
extends: .rspec-base-pg11-as-if-foss extends:
parallel: 24 - .rspec-base-pg11-as-if-foss
- .rails:rules:as-if-foss-system
- .rspec-system-parallel
rspec-ee migration pg11: rspec-ee migration pg11:
extends: extends:
- .rspec-ee-base-pg11 - .rspec-ee-base-pg11
- .rspec-base-migration - .rspec-base-migration
parallel: 2 - .rails:rules:ee-only-migration
- .rspec-ee-migration-parallel
rspec-ee unit pg11: rspec-ee unit pg11:
extends: .rspec-ee-base-pg11 extends:
parallel: 10 - .rspec-ee-base-pg11
- .rails:rules:ee-only-unit
- .rspec-ee-unit-parallel
rspec-ee integration pg11: rspec-ee integration pg11:
extends: .rspec-ee-base-pg11 extends:
parallel: 4 - .rspec-ee-base-pg11
- .rails:rules:ee-only-integration
- .rspec-ee-integration-parallel
rspec-ee system pg11: rspec-ee system pg11:
extends: .rspec-ee-base-pg11
parallel: 6
.rspec-ee-base-geo:
extends: .rspec-base-ee
script:
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
- source scripts/rspec_helpers.sh
- scripts/prepare_postgres_fdw.sh
- rspec_paralellized_job "--tag ~quarantine --tag geo"
.rspec-ee-base-geo-pg11:
extends: extends:
- .rspec-ee-base-geo - .rspec-ee-base-pg11
- .use-pg11-ee - .rails:rules:ee-only-system
- .rspec-ee-system-parallel
rspec-ee unit pg11 geo: rspec-ee unit pg11 geo:
extends: .rspec-ee-base-geo-pg11 extends:
parallel: 2 - .rspec-ee-base-geo-pg11
- .rails:rules:ee-only-unit
- .rspec-ee-unit-geo-parallel
rspec-ee integration pg11 geo: rspec-ee integration pg11 geo:
extends: .rspec-ee-base-geo-pg11 extends:
- .rspec-ee-base-geo-pg11
- .rails:rules:ee-only-integration
rspec-ee system pg11 geo: rspec-ee system pg11 geo:
extends: .rspec-ee-base-geo-pg11 extends:
- .rspec-ee-base-geo-pg11
- .rails:rules:ee-only-system
db:rollback geo: db:rollback geo:
extends: extends:
- db:rollback - db:rollback
- .rails:rules:ee-only - .rails:rules:ee-only-migration
script: script:
- bundle exec rake geo:db:migrate VERSION=20170627195211 - bundle exec rake geo:db:migrate VERSION=20170627195211
- bundle exec rake geo:db:migrate - bundle exec rake geo:db:migrate
# EE: default refs (MRs, master, schedules) jobs # # EE: default refs (MRs, master, schedules) jobs #
################################################## ##################################################
##########################################
# EE/FOSS: master nightly scheduled jobs #
rspec migration pg12:
extends:
- .rspec-base-pg12
- .rspec-base-migration
- .rails:rules:master-schedule-nightly--code-backstage
- .rspec-migration-parallel
rspec unit pg12:
extends:
- .rspec-base-pg12
- .rails:rules:master-schedule-nightly--code-backstage
- .rspec-unit-parallel
rspec integration pg12:
extends:
- .rspec-base-pg12
- .rails:rules:master-schedule-nightly--code-backstage
- .rspec-integration-parallel
rspec system pg12:
extends:
- .rspec-base-pg12
- .rails:rules:master-schedule-nightly--code-backstage
- .rspec-system-parallel
# EE/FOSS: master nightly scheduled jobs #
##########################################
#####################################
# EE: master nightly scheduled jobs #
rspec-ee migration pg12:
extends:
- .rspec-ee-base-pg12
- .rspec-base-migration
- .rails:rules:master-schedule-nightly--code-backstage-ee-only
- .rspec-ee-migration-parallel
rspec-ee unit pg12:
extends:
- .rspec-ee-base-pg12
- .rails:rules:master-schedule-nightly--code-backstage-ee-only
- .rspec-ee-unit-parallel
rspec-ee integration pg12:
extends:
- .rspec-ee-base-pg12
- .rails:rules:master-schedule-nightly--code-backstage-ee-only
- .rspec-ee-integration-parallel
rspec-ee system pg12:
extends:
- .rspec-ee-base-pg12
- .rails:rules:master-schedule-nightly--code-backstage-ee-only
- .rspec-ee-system-parallel
rspec-ee unit pg12 geo:
extends:
- .rspec-ee-base-geo-pg12
- .rails:rules:master-schedule-nightly--code-backstage-ee-only
- .rspec-ee-unit-geo-parallel
rspec-ee integration pg12 geo:
extends:
- .rspec-ee-base-geo-pg12
- .rails:rules:master-schedule-nightly--code-backstage-ee-only
rspec-ee system pg12 geo:
extends:
- .rspec-ee-base-geo-pg12
- .rails:rules:master-schedule-nightly--code-backstage-ee-only
# EE: master nightly scheduled jobs #
#####################################
################################################## ##################################################
# EE: Canonical MR pipelines # EE: Canonical MR pipelines
rspec foss-impact: rspec foss-impact:
extends: extends:
- .rspec-base - .rspec-base-pg11-as-if-foss
- .as-if-foss
- .rails:rules:ee-mr-only - .rails:rules:ee-mr-only
- .use-pg11
script: script:
- install_gitlab_gem - install_gitlab_gem
- run_timed_command "scripts/gitaly-test-build" - run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn" - run_timed_command "scripts/gitaly-test-spawn"
- source scripts/rspec_helpers.sh - source scripts/rspec_helpers.sh
- tooling/bin/find_foss_tests tmp/matching_foss_tests.txt - tooling/bin/find_foss_tests tmp/matching_foss_tests.txt
- rspec_matched_tests tmp/matching_foss_tests.txt "--tag ~quarantine --tag ~geo --tag ~level:migration" - rspec_matched_tests tmp/matching_foss_tests.txt "--tag ~quarantine"
artifacts: artifacts:
expire_in: 7d expire_in: 7d
paths: paths:
- tmp/matching_foss_tests.txt - tmp/matching_foss_tests.txt
- tmp/capybara/ - tmp/capybara/
# EE: Merge Request pipelines # EE: Canonical MR pipelines
################################################## ##################################################

View file

@ -15,7 +15,7 @@ code_quality:
stage: test stage: test
needs: [] needs: []
variables: variables:
CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.9" CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.10"
script: script:
- | - |
if ! docker info &>/dev/null; then if ! docker info &>/dev/null; then
@ -59,6 +59,7 @@ code_quality:
SAST_ANALYZER_IMAGE_TAG: 2 SAST_ANALYZER_IMAGE_TAG: 2
SAST_BRAKEMAN_LEVEL: 2 # GitLab-specific SAST_BRAKEMAN_LEVEL: 2 # GitLab-specific
SAST_EXCLUDED_PATHS: qa,spec,doc,ee/spec # GitLab-specific SAST_EXCLUDED_PATHS: qa,spec,doc,ee/spec # GitLab-specific
SAST_DISABLE_BABEL: "true"
script: script:
- /analyzer run - /analyzer run
@ -72,11 +73,10 @@ eslint-sast:
image: image:
name: "$SAST_ANALYZER_IMAGE_PREFIX/eslint:$SAST_ANALYZER_IMAGE_TAG" name: "$SAST_ANALYZER_IMAGE_PREFIX/eslint:$SAST_ANALYZER_IMAGE_TAG"
# Temporary disabled as it's constantly failing. See https://gitlab.com/gitlab-org/gitlab/-/issues/213769. nodejs-scan-sast:
# nodejs-scan-sast: extends: .sast
# extends: .sast image:
# image: name: "$SAST_ANALYZER_IMAGE_PREFIX/nodejs-scan:$SAST_ANALYZER_IMAGE_TAG"
# name: "$SAST_ANALYZER_IMAGE_PREFIX/nodejs-scan:$SAST_ANALYZER_IMAGE_TAG"
secrets-sast: secrets-sast:
extends: .sast extends: .sast
@ -172,6 +172,7 @@ dependency_scanning:
# # - 'export DAST_AUTH_URL="${DAST_WEBSITE}/users/sign_in"' # # - 'export DAST_AUTH_URL="${DAST_WEBSITE}/users/sign_in"'
# # - 'export DAST_PASSWORD="${REVIEW_APPS_ROOT_PASSWORD}"' # # - 'export DAST_PASSWORD="${REVIEW_APPS_ROOT_PASSWORD}"'
# - /analyze -t $DAST_WEBSITE # - /analyze -t $DAST_WEBSITE
# timeout: 4h
# artifacts: # artifacts:
# paths: # paths:
# - gl-dast-report.json # GitLab-specific # - gl-dast-report.json # GitLab-specific

View file

@ -1,14 +1,3 @@
build-qa-image:
extends:
- .use-kaniko
- .review:rules:build-qa-image
stage: build-images
needs: []
script:
- export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
- /kaniko/executor --context=${CI_PROJECT_DIR} --dockerfile=${CI_PROJECT_DIR}/qa/Dockerfile --destination=${QA_IMAGE} --cache=true
retry: 2
review-cleanup: review-cleanup:
extends: extends:
- .default-retry - .default-retry
@ -27,25 +16,24 @@ review-cleanup:
- ruby -rrubygems scripts/review_apps/automated_cleanup.rb - ruby -rrubygems scripts/review_apps/automated_cleanup.rb
- gcp_cleanup - gcp_cleanup
# Temporarily disabling review apps review-build-cng:
#review-build-cng: extends:
# extends: - .default-retry
# - .default-retry - .review:rules:review-build-cng
# - .review:rules:review-build-cng image: ruby:2.6-alpine
# image: ruby:2.6-alpine stage: review-prepare
# stage: review-prepare before_script:
# before_script: - source scripts/utils.sh
# - source scripts/utils.sh - install_api_client_dependencies_with_apk
# - install_api_client_dependencies_with_apk - install_gitlab_gem
# - install_gitlab_gem needs:
# needs: - job: compile-production-assets
# - job: compile-production-assets artifacts: false
# artifacts: false script:
# script: - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
# - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng # When the job is manual, review-deploy is also manual and we don't want people
# # When the job is manual, review-deploy is also manual and we don't want people # to have to manually start the jobs in sequence, so we do it for them.
# # to have to manually start the jobs in sequence, so we do it for them. - '[ -z $CI_JOB_MANUAL ] || play_job "review-deploy"'
# - '[ -z $CI_JOB_MANUAL ] || play_job "review-deploy"'
.review-workflow-base: .review-workflow-base:
extends: extends:
@ -53,45 +41,46 @@ review-cleanup:
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-helm3-kubectl1.14 image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-helm3-kubectl1.14
variables: variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}" HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
REVIEW_APPS_DOMAIN: "temp.gitlab-review.app" # FIXME: using temporary domain
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}" DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
GITLAB_HELM_CHART_REF: "master" GITLAB_HELM_CHART_REF: "v4.1.3"
environment: environment:
name: review/${CI_COMMIT_REF_NAME} name: review/${CI_COMMIT_REF_NAME}
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN} url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
on_stop: review-stop on_stop: review-stop
auto_stop_in: 48 hours auto_stop_in: 48 hours
# Temporarily disabling review apps review-deploy:
#review-deploy: extends:
# extends: - .review-workflow-base
# - .review-workflow-base - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
# - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise stage: review
# stage: review dependencies: []
# dependencies: [] resource_group: "review/${CI_COMMIT_REF_NAME}"
# resource_group: "review/${CI_COMMIT_REF_NAME}" before_script:
# before_script: - export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
# - export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION) - export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
# - export GITALY_VERSION=$(<GITALY_SERVER_VERSION) - export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
# - export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION) - echo "${CI_ENVIRONMENT_URL}" > environment_url.txt
# - echo "${CI_ENVIRONMENT_URL}" > environment_url.txt - source ./scripts/utils.sh
# - source ./scripts/utils.sh - install_api_client_dependencies_with_apk
# - install_api_client_dependencies_with_apk - source scripts/review_apps/review-apps.sh
# - source scripts/review_apps/review-apps.sh script:
# script: - check_kube_domain
# - check_kube_domain - ensure_namespace
# - ensure_namespace - install_external_dns
# - install_external_dns - download_chart
# - download_chart - date
# - date - deploy || (display_deployment_debug && exit 1)
# - deploy || (display_deployment_debug && exit 1) - disable_sign_ups
# # When the job is manual, review-qa-smoke is also manual and we don't want people # When the job is manual, review-qa-smoke is also manual and we don't want people
# # to have to manually start the jobs in sequence, so we do it for them. # to have to manually start the jobs in sequence, so we do it for them.
# - '[ -z $CI_JOB_MANUAL ] || play_job "review-qa-smoke"' - '[ -z $CI_JOB_MANUAL ] || play_job "review-qa-smoke"'
# - '[ -z $CI_JOB_MANUAL ] || play_job "review-performance"' - '[ -z $CI_JOB_MANUAL ] || play_job "review-performance"'
# artifacts: artifacts:
# paths: [environment_url.txt] paths: [environment_url.txt]
# expire_in: 2 days expire_in: 2 days
# when: always when: always
.review-stop-base: .review-stop-base:
extends: .review-workflow-base extends: .review-workflow-base
@ -124,110 +113,110 @@ review-stop:
script: script:
- delete_release - delete_release
# Temporarily disabling review apps .review-qa-base:
#.review-qa-base: extends:
# extends: - .default-retry
# - .default-retry - .use-docker-in-docker
# - .use-docker-in-docker image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6
# image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6 stage: qa
# stage: qa # This is needed so that manual jobs with needs don't block the pipeline.
# # This is needed so that manual jobs with needs don't block the pipeline. # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
# # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979. dependencies: ["review-deploy"]
# dependencies: ["review-deploy"] variables:
# variables: QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
# QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa" QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
# QA_CAN_TEST_GIT_PROTOCOL_V2: "false" QA_DEBUG: "true"
# QA_DEBUG: "true" GITLAB_USERNAME: "root"
# GITLAB_USERNAME: "root" GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
# GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}" GITLAB_ADMIN_USERNAME: "root"
# GITLAB_ADMIN_USERNAME: "root" GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
# GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}" GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
# GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}" EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
# EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}" SIGNUP_DISABLED: "true"
# before_script: before_script:
# - export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}" - export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
# - export CI_ENVIRONMENT_URL="$(cat environment_url.txt)" - export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
# - echo "${CI_ENVIRONMENT_URL}" - echo "${CI_ENVIRONMENT_URL}"
# - echo "${QA_IMAGE}" - echo "${QA_IMAGE}"
# - source scripts/utils.sh - source scripts/utils.sh
# - install_api_client_dependencies_with_apk - install_api_client_dependencies_with_apk
# - gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}} - gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}}
# artifacts: artifacts:
# paths: paths:
# - ./qa/gitlab-qa-run-* - ./qa/gitlab-qa-run-*
# expire_in: 7 days expire_in: 7 days
# when: always when: always
#
#review-qa-smoke: review-qa-smoke:
# extends: extends:
# - .review-qa-base - .review-qa-base
# - .review:rules:review-qa-smoke - .review:rules:review-qa-smoke
# script: script:
# - gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}" - gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
#
#review-qa-all: review-qa-all:
# extends: extends:
# - .review-qa-base - .review-qa-base
# - .review:rules:mr-only-manual - .review:rules:mr-only-manual
# parallel: 5 parallel: 5
# script: script:
# - export KNAPSACK_REPORT_PATH=knapsack/master_report.json - export KNAPSACK_REPORT_PATH=knapsack/master_report.json
# - export KNAPSACK_TEST_FILE_PATTERN=qa/specs/features/**/*_spec.rb - export KNAPSACK_TEST_FILE_PATTERN=qa/specs/features/**/*_spec.rb
# - gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}" -- --format RspecJunitFormatter --out tmp/rspec-${CI_JOB_ID}.xml --format html --out tmp/rspec.htm --color --format documentation - gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}" -- --format RspecJunitFormatter --out tmp/rspec-${CI_JOB_ID}.xml --format html --out tmp/rspec.htm --color --format documentation
#
#review-performance: review-performance:
# extends: extends:
# - .default-retry - .default-retry
# - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
# image: image:
# name: sitespeedio/sitespeed.io:6.3.1 name: sitespeedio/sitespeed.io:6.3.1
# entrypoint: [""] entrypoint: [""]
# stage: qa stage: qa
# # This is needed so that manual jobs with needs don't block the pipeline. # This is needed so that manual jobs with needs don't block the pipeline.
# # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979. # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
# dependencies: ["review-deploy"] dependencies: ["review-deploy"]
# before_script: before_script:
# - export CI_ENVIRONMENT_URL="$(cat environment_url.txt)" - export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
# - echo "${CI_ENVIRONMENT_URL}" - echo "${CI_ENVIRONMENT_URL}"
# - mkdir -p gitlab-exporter - mkdir -p gitlab-exporter
# - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
# - mkdir -p sitespeed-results - mkdir -p sitespeed-results
# script: script:
# - /start.sh --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "${CI_ENVIRONMENT_URL}" - /start.sh --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "${CI_ENVIRONMENT_URL}"
# after_script: after_script:
# - mv sitespeed-results/data/performance.json performance.json - mv sitespeed-results/data/performance.json performance.json
# artifacts: artifacts:
# paths: paths:
# - sitespeed-results/ - sitespeed-results/
# reports: reports:
# performance: performance.json performance: performance.json
# expire_in: 31d expire_in: 31d
#
#parallel-spec-reports: parallel-spec-reports:
# extends: extends:
# - .review:rules:mr-only-manual - .review:rules:mr-only-manual
# image: ruby:2.6-alpine image: ruby:2.6-alpine
# stage: post-qa stage: post-qa
# dependencies: ["review-qa-all"] dependencies: ["review-qa-all"]
# variables: variables:
# NEW_PARALLEL_SPECS_REPORT: qa/report-new.html NEW_PARALLEL_SPECS_REPORT: qa/report-new.html
# BASE_ARTIFACT_URL: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/qa/" BASE_ARTIFACT_URL: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/qa/"
# script: script:
# - apk add --update build-base libxml2-dev libxslt-dev && rm -rf /var/cache/apk/* - apk add --update build-base libxml2-dev libxslt-dev && rm -rf /var/cache/apk/*
# - gem install nokogiri --no-document - gem install nokogiri --no-document
# - cd qa/gitlab-qa-run-*/gitlab-* - cd qa/gitlab-qa-run-*/gitlab-*
# - ARTIFACT_DIRS=$(pwd |rev| awk -F / '{print $1,$2}' | rev | sed s_\ _/_) - ARTIFACT_DIRS=$(pwd |rev| awk -F / '{print $1,$2}' | rev | sed s_\ _/_)
# - cd - - cd -
# - '[[ -f $NEW_PARALLEL_SPECS_REPORT ]] || echo "{}" > ${NEW_PARALLEL_SPECS_REPORT}' - '[[ -f $NEW_PARALLEL_SPECS_REPORT ]] || echo "{}" > ${NEW_PARALLEL_SPECS_REPORT}'
# - scripts/merge-html-reports ${NEW_PARALLEL_SPECS_REPORT} ${BASE_ARTIFACT_URL}${ARTIFACT_DIRS} qa/gitlab-qa-run-*/**/rspec.htm - scripts/merge-html-reports ${NEW_PARALLEL_SPECS_REPORT} ${BASE_ARTIFACT_URL}${ARTIFACT_DIRS} qa/gitlab-qa-run-*/**/rspec.htm
# artifacts: artifacts:
# when: always when: always
# paths: paths:
# - qa/report-new.html - qa/report-new.html
# - qa/gitlab-qa-run-* - qa/gitlab-qa-run-*
# reports: reports:
# junit: qa/gitlab-qa-run-*/**/rspec-*.xml junit: qa/gitlab-qa-run-*/**/rspec-*.xml
# expire_in: 31d expire_in: 31d
danger-review: danger-review:
extends: extends:

View file

@ -11,7 +11,7 @@
if: '$CI_PROJECT_NAME != "gitlab-foss" && $CI_PROJECT_NAME != "gitlab-ce" && $CI_PROJECT_NAME != "gitlabhq"' if: '$CI_PROJECT_NAME != "gitlab-foss" && $CI_PROJECT_NAME != "gitlab-ce" && $CI_PROJECT_NAME != "gitlabhq"'
.if-default-refs: &if-default-refs .if-default-refs: &if-default-refs
if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG' if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG || $FORCE_GITLAB_CI'
.if-master-refs: &if-master-refs .if-master-refs: &if-master-refs
if: '$CI_COMMIT_REF_NAME == "master"' if: '$CI_COMMIT_REF_NAME == "master"'
@ -40,6 +40,9 @@
.if-merge-request-title-update-caches: &if-merge-request-title-update-caches .if-merge-request-title-update-caches: &if-merge-request-title-update-caches
if: '$CI_MERGE_REQUEST_TITLE =~ /UPDATE CACHE/' if: '$CI_MERGE_REQUEST_TITLE =~ /UPDATE CACHE/'
.if-merge-request-title-run-all-rspec: &if-merge-request-title-run-all-rspec
if: '$CI_MERGE_REQUEST_TITLE =~ /RUN ALL RSPEC/'
.if-security-merge-request: &if-security-merge-request .if-security-merge-request: &if-security-merge-request
if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_MERGE_REQUEST_IID' if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_MERGE_REQUEST_IID'
@ -71,6 +74,22 @@
- ".gitlab-ci.yml" - ".gitlab-ci.yml"
- ".gitlab/ci/**/*" - ".gitlab/ci/**/*"
.ci-build-images-patterns: &ci-build-images-patterns
- ".gitlab-ci.yml"
- ".gitlab/ci/build-images.gitlab-ci.yml"
.ci-review-patterns: &ci-review-patterns
- ".gitlab-ci.yml"
- ".gitlab/ci/frontend.gitlab-ci.yml"
- ".gitlab/ci/build-images.gitlab-ci.yml"
- ".gitlab/ci/review.gitlab-ci.yml"
.ci-qa-patterns: &ci-qa-patterns
- ".gitlab-ci.yml"
- ".gitlab/ci/frontend.gitlab-ci.yml"
- ".gitlab/ci/build-images.gitlab-ci.yml"
- ".gitlab/ci/qa.gitlab-ci.yml"
.yaml-patterns: &yaml-patterns .yaml-patterns: &yaml-patterns
- "**/*.yml" - "**/*.yml"
@ -92,6 +111,21 @@
- "vendor/assets/**/*" - "vendor/assets/**/*"
- "{,ee/}{app/assets,app/helpers,app/presenters,app/views,locale,public,symbol}/**/*" - "{,ee/}{app/assets,app/helpers,app/presenters,app/views,locale,public,symbol}/**/*"
.backend-patterns: &backend-patterns
- "Gemfile{,.lock}"
- "Rakefile"
- "config.ru"
# List explicitly all the app/ dirs that are backend (i.e. all except app/assets).
- "{,ee/}{app/channels,app/controllers,app/finders,app/graphql,app/helpers,app/mailers,app/models,app/policies,app/presenters,app/serializers,app/services,app/uploaders,app/validators,app/views,app/workers}/**/*"
- "{,ee/}{bin,cable,config,db,lib}/**/*"
- "{,ee/}spec/**/*.rb"
- ".gitlab-ci.yml"
- ".gitlab/ci/**/*"
.db-patterns: &db-patterns
- "{,ee/}{,spec/}{db,migrations}/**/*"
- "{,ee/}{,spec/}lib/{,ee/}gitlab/background_migration/**/*"
.backstage-patterns: &backstage-patterns .backstage-patterns: &backstage-patterns
- "Dangerfile" - "Dangerfile"
- "danger/**/*" - "danger/**/*"
@ -197,6 +231,26 @@
- <<: *if-master-schedule-2-hourly - <<: *if-master-schedule-2-hourly
- <<: *if-merge-request-title-update-caches - <<: *if-merge-request-title-update-caches
######################
# Build images rules #
######################
.build-images:rules:build-qa-image:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *ci-build-images-patterns
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *code-qa-patterns
- <<: *if-dot-com-gitlab-org-schedule
.build-images:rules:build-assets-image:
rules:
- <<: *if-not-canonical-namespace
when: never
- changes: *ci-build-images-patterns
- changes: *code-qa-patterns
#################### ####################
# Cache repo rules # # Cache repo rules #
#################### ####################
@ -263,7 +317,7 @@
- <<: *if-not-canonical-namespace - <<: *if-not-canonical-namespace
when: never when: never
- <<: *if-default-refs - <<: *if-default-refs
changes: *code-backstage-qa-patterns changes: *code-qa-patterns
.frontend:rules:compile-test-assets: .frontend:rules:compile-test-assets:
rules: rules:
@ -273,11 +327,8 @@
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-security-merge-request - <<: *if-merge-request # Always run for MRs since `compile-test-assets as-if-foss` is either needed by `rspec foss-impact` or the `rspec * as-if-foss` jobs.
changes: *code-backstage-qa-patterns changes: *code-backstage-qa-patterns
- <<: *if-merge-request-title-as-if-foss
- <<: *if-merge-request
changes: *ci-patterns
.frontend:rules:default-frontend-jobs: .frontend:rules:default-frontend-jobs:
rules: rules:
@ -294,6 +345,15 @@
- <<: *if-merge-request - <<: *if-merge-request
changes: *ci-patterns changes: *ci-patterns
.frontend:rules:eslint-as-if-foss:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-title-as-if-foss
when: never
- <<: *if-merge-request
changes: *frontend-patterns
.frontend:rules:ee-mr-and-master-only: .frontend:rules:ee-mr-and-master-only:
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
@ -341,9 +401,7 @@
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-dot-com-gitlab-org-master - <<: *if-master-schedule-2-hourly
changes: *code-backstage-qa-patterns
when: on_success
############ ############
# QA rules # # QA rules #
@ -367,7 +425,7 @@
.qa:rules:package-and-qa: .qa:rules:package-and-qa:
rules: rules:
- <<: *if-dot-com-gitlab-org-and-security-merge-request - <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *ci-patterns changes: *ci-qa-patterns
allow_failure: true allow_failure: true
- <<: *if-dot-com-gitlab-org-and-security-merge-request - <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *qa-patterns changes: *qa-patterns
@ -382,24 +440,95 @@
############### ###############
# Rails rules # # Rails rules #
############### ###############
.rails:rules:ee-and-foss: .rails:rules:ee-and-foss-migration:
rules: rules:
- <<: *if-default-refs - changes: *db-patterns
changes: *code-backstage-patterns - <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-and-foss-unit:
rules:
- changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-and-foss-integration:
rules:
- changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-and-foss-system:
rules:
- changes: *code-backstage-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-and-foss-fast_spec_helper:
rules:
- changes: ["config/**/*"]
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:default-refs-code-backstage-qa: .rails:rules:default-refs-code-backstage-qa:
rules: rules:
- <<: *if-default-refs - <<: *if-default-refs
changes: *code-backstage-qa-patterns changes: *code-backstage-qa-patterns
.rails:rules:ee-only: .rails:rules:ee-only-migration:
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-default-refs - changes: *db-patterns
changes: *code-backstage-patterns - <<: *if-merge-request-title-run-all-rspec
.rails:rules:as-if-foss: .rails:rules:ee-only-unit:
rules:
- <<: *if-not-ee
when: never
- changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-only-integration:
rules:
- <<: *if-not-ee
when: never
- changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-only-system:
rules:
- <<: *if-not-ee
when: never
- changes: *code-backstage-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:as-if-foss-migration:
rules:
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
changes: *db-patterns
- <<: *if-merge-request-title-as-if-foss
- <<: *if-merge-request
changes: *ci-patterns
.rails:rules:as-if-foss-unit:
rules:
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
changes: *backend-patterns
- <<: *if-merge-request-title-as-if-foss
- <<: *if-merge-request
changes: *ci-patterns
.rails:rules:as-if-foss-integration:
rules:
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
changes: *backend-patterns
- <<: *if-merge-request-title-as-if-foss
- <<: *if-merge-request
changes: *ci-patterns
.rails:rules:as-if-foss-system:
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
@ -413,6 +542,7 @@
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-merge-request-title-run-all-rspec
- <<: *if-merge-request - <<: *if-merge-request
changes: *code-backstage-patterns changes: *code-backstage-patterns
- <<: *if-master-refs - <<: *if-master-refs
@ -434,6 +564,27 @@
- <<: *if-merge-request - <<: *if-merge-request
changes: *code-backstage-patterns changes: *code-backstage-patterns
.rails:rules:rspec-coverage:
rules:
- <<: *if-not-ee
when: never
- <<: *if-master-schedule-2-hourly
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:master-schedule-nightly--code-backstage:
rules:
- <<: *if-master-schedule-nightly
- <<: *if-merge-request
changes: [".gitlab/ci/rails.gitlab-ci.yml"]
.rails:rules:master-schedule-nightly--code-backstage-ee-only:
rules:
- <<: *if-not-ee
when: never
- <<: *if-master-schedule-nightly
- <<: *if-merge-request
changes: [".gitlab/ci/rails.gitlab-ci.yml"]
################## ##################
# Releases rules # # Releases rules #
################## ##################
@ -496,18 +647,12 @@
################ ################
# Review rules # # Review rules #
################ ################
.review:rules:build-qa-image: .review:rules:review-build-cng:
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *code-qa-patterns
- <<: *if-dot-com-gitlab-org-schedule
.review:rules:review-build-cng:
rules:
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-merge-request
changes: *ci-patterns changes: *ci-review-patterns
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-merge-request
changes: *frontend-patterns changes: *frontend-patterns
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-merge-request
@ -521,7 +666,7 @@
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-merge-request
changes: *ci-patterns changes: *ci-review-patterns
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-merge-request
changes: *frontend-patterns changes: *frontend-patterns
allow_failure: true allow_failure: true
@ -544,7 +689,7 @@
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-merge-request
changes: *ci-patterns changes: *ci-review-patterns
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-merge-request
changes: *frontend-patterns changes: *frontend-patterns
allow_failure: true allow_failure: true

View file

@ -9,6 +9,7 @@ cache gems:
stage: test stage: test
needs: ["setup-test-env"] needs: ["setup-test-env"]
variables: variables:
BUNDLE_INSTALL_FLAGS: --with=production --with=development --with=test --jobs=2 --path=vendor --retry=3 --quiet
SETUP_DB: "false" SETUP_DB: "false"
script: script:
- bundle package --all --all-platforms - bundle package --all --all-platforms

View file

@ -4,11 +4,11 @@ lint-ci-gitlab:
extends: extends:
- .default-retry - .default-retry
- .yaml:rules - .yaml:rules
image: sdesbure/yamllint:latest image: pipelinecomponents/yamllint:latest
stage: test stage: test
needs: [] needs: []
variables: variables:
LINT_PATHS: .gitlab-ci.yml .gitlab/ci lib/gitlab/ci/templates changelogs LINT_PATHS: .gitlab-ci.yml .gitlab/ci lib/gitlab/ci/templates changelogs
script: script:
- '[[ ! -d "ee/" ]] || export LINT_PATHS="$LINT_PATHS ee/changelogs"' - '[[ ! -d "ee/" ]] || export LINT_PATHS="$LINT_PATHS ee/changelogs"'
- yamllint $LINT_PATHS - yamllint -f colored $LINT_PATHS

View file

@ -43,7 +43,14 @@ https://about.gitlab.com/handbook/engineering/ux/ux-research-training/user-story
### Permissions and Security ### Permissions and Security
<!-- What permissions are required to perform the described actions? Are they consistent with the existing permissions as documented for users, groups, and projects as appropriate? Is the proposed behavior consistent between the UI, API, and other access methods (e.g. email replies)?--> <!-- What permissions are required to perform the described actions? Are they consistent with the existing permissions as documented for users, groups, and projects as appropriate? Is the proposed behavior consistent between the UI, API, and other access methods (e.g. email replies)?
Consider adding checkboxes and expectations of users with certain levels of membership https://docs.gitlab.com/ee/user/permissions.html
* [ ] Add expected impact to members with no access (0)
* [ ] Add expected impact to Guest (10) members
* [ ] Add expected impact to Reporter (20) members
* [ ] Add expected impact to Developer (30) members
* [ ] Add expected impact to Maintainer (40) members
* [ ] Add expected impact to Owner (50) members -->
### Documentation ### Documentation

View file

@ -9,19 +9,17 @@ Set the title to: `Description of the original issue`
## Prior to starting the security release work ## Prior to starting the security release work
- [ ] Read the [security process for developers] if you are not familiar with it. - [ ] Read the [security process for developers] if you are not familiar with it.
- [ ] Mark this [issue as related] to the Security Release tracking issue. You can find it on the topic of the `#releases` Slack channel. - [ ] Mark this [issue as related] to the Security Release Tracking Issue. You can find it on the topic of the `#releases` Slack channel.
- [ ] Run `scripts/security-harness` in your local repository to prevent accidentally pushing to any remote besides `gitlab.com/gitlab-org/security`.
- Fill out the [Links section](#links): - Fill out the [Links section](#links):
- [ ] Next to **Issue on GitLab**, add a link to the `gitlab-org/gitlab` issue that describes the security vulnerability. - [ ] Next to **Issue on GitLab**, add a link to the `gitlab-org/gitlab` issue that describes the security vulnerability.
- [ ] Next to **Security Release tracking issue**, add a link to the security release issue that will include this security issue.
## Development ## Development
- [ ] Run `scripts/security-harness` in your local repository to prevent accidentally pushing to any remote besides `gitlab.com/gitlab-org/security`.
- [ ] Create a new branch prefixing it with `security-`. - [ ] Create a new branch prefixing it with `security-`.
- [ ] Create a merge request targeting `master` on `gitlab.com/gitlab-org/security` and use the [Security Release merge request template]. - [ ] Create a merge request targeting `master` on `gitlab.com/gitlab-org/security` and use the [Security Release merge request template].
- [ ] Follow the same [code review process]: Assign to a reviewer, then to a maintainer.
After your merge request has been approved according to our [approval guidelines], you're ready to prepare the backports After your merge request has been approved according to our [approval guidelines] and by a team member of the AppSec team, you're ready to prepare the backports
## Backports ## Backports
@ -41,7 +39,6 @@ After your merge request has been approved according to our [approval guidelines
- [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details) - [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details)
- [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details) - [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details)
- [ ] Add the nickname of the external user who found the issue (and/or HackerOne profile) to the Thanks row in the [details section](#details) - [ ] Add the nickname of the external user who found the issue (and/or HackerOne profile) to the Thanks row in the [details section](#details)
- [ ] Once your `master` MR is merged, comment on the original security issue with a link to that MR indicating the issue is fixed.
## Summary ## Summary
@ -50,7 +47,6 @@ After your merge request has been approved according to our [approval guidelines
| Description | Link | | Description | Link |
| -------- | -------- | | -------- | -------- |
| Issue on [GitLab](https://gitlab.com/gitlab-org/gitlab/issues) | #TODO | | Issue on [GitLab](https://gitlab.com/gitlab-org/gitlab/issues) | #TODO |
| Security Release tracking issue | #TODO |
### Details ### Details
@ -64,7 +60,7 @@ After your merge request has been approved according to our [approval guidelines
| Thanks | | | | Thanks | | |
[security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md [security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md
[secpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script [secpick documentation]: https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/utilities/secpick_script.md
[security Release merge request template]: https://gitlab.com/gitlab-org/security/gitlab/blob/master/.gitlab/merge_request_templates/Security%20Release.md [security Release merge request template]: https://gitlab.com/gitlab-org/security/gitlab/blob/master/.gitlab/merge_request_templates/Security%20Release.md
[code review process]: https://docs.gitlab.com/ee/development/code_review.html [code review process]: https://docs.gitlab.com/ee/development/code_review.html
[approval guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines [approval guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines

View file

@ -45,9 +45,11 @@ All reviewers can help ensure accuracy, clarity, completeness, and adherence to
**2. Technical Writer** **2. Technical Writer**
- [ ] Optional: Technical writer review. If not requested for this MR, must be scheduled post-merge. To request for this MR, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages). - [ ] Technical writer review. If not requested for this MR, must be scheduled post-merge. To request for this MR, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/product-categories/#devops-stages).
- [ ] Add ~"Technical Writing" and `docs::` workflow label. - [ ] Ensure ~"Technical Writing", ~"documentation", and a `docs::` scoped label are added.
- [ ] Add ~docs-only when the only files changed are under `doc/*`. - [ ] Add ~docs-only when the only files changed are under `doc/*`.
- [ ] Add ~"tw::doing" when starting work on the MR.
- [ ] Add ~"tw::finished" if Technical Writing team work on the MR is complete but it remains open.
**3. Maintainer** **3. Maintainer**

View file

@ -13,25 +13,33 @@ See [the general developer security release guidelines](https://gitlab.com/gitla
## Developer checklist ## Developer checklist
- [ ] **On "Related issues" section, write down the [GitLab Security] issue it belongs to (i.e. `Related to <issue_id>`).** - [ ] **On "Related issues" section, write down the [GitLab Security] issue it belongs to (i.e. `Related to <issue_id>`).**
- [ ] Merge request targets `master`, or `X-Y-stable` for backports. - [ ] Merge request targets `master`, or a versioned stable branch (`X-Y-stable-ee`).
- [ ] Milestone is set for the version this merge request applies to. A closed milestone can be assigned via [quick actions]. - [ ] Milestone is set for the version this merge request applies to. A closed milestone can be assigned via [quick actions].
- [ ] Title of this merge request is the same as for all backports. - [ ] Title of this merge request is the same as for all backports.
- [ ] A [CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html) is added without a `merge_request` value, with `type` set to `security` - [ ] A [CHANGELOG entry] is added without a `merge_request` value, with `type` set to `security`
- [ ] Assign to a reviewer and maintainer, per our [Code Review process].
- [ ] For the MR targeting `master`: - [ ] For the MR targeting `master`:
- [ ] Ask for a non-blocking review from the AppSec team member associated to the issue in the [Canonical repository](https://gitlab.com/gitlab-org/gitlab). If you're unsure who to ping, ask on `#sec-appsec` Slack channel. - [ ] Assign to a reviewer and maintainer, per our [Code Review process].
- [ ] Ensure it's approved according to our [Approval Guidelines]. - [ ] Ensure it's approved according to our [Approval Guidelines].
- [ ] Merge request _must not_ close the corresponding security issue, _unless_ it targets `master`. - [ ] Ensure it's approved by an AppSec engineer.
- If you're unsure who should approve, find the AppSec engineer associated to the issue in the [Canonical repository], or ask #sec-appsec on Slack.
- Trigger the [`package-and-qa` build]. The docker image generated will be used by the AppSec engineer to validate the security vulnerability has been remediated.
- [ ] Merge request _must_ close the corresponding security issue.
- [ ] For a backport MR targeting a versioned stable branch (`X-Y-stable-ee`)
- [ ] Ensure it's approved by a maintainer.
**Note:** Reviewer/maintainer should not be a Release Manager **Note:** Reviewer/maintainer should not be a Release Manager
## Maintainer checklist ## Maintainer checklist
- [ ] Correct milestone is applied and the title is matching across all backports - [ ] 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.** - [ ] Assigned to `@gitlab-release-tools-bot` with passing CI pipelines and **when all backports including the MR targeting master are ready.**
/label ~security /label ~security
[GitLab Security]: https://gitlab.com/gitlab-org/security/gitlab [GitLab Security]: https://gitlab.com/gitlab-org/security/gitlab
[approval guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines
[Code Review process]: https://docs.gitlab.com/ee/development/code_review.html
[quick actions]: https://docs.gitlab.com/ee/user/project/quick_actions.html#quick-actions-for-issues-merge-requests-and-epics [quick actions]: https://docs.gitlab.com/ee/user/project/quick_actions.html#quick-actions-for-issues-merge-requests-and-epics
[CHANGELOG entry]: https://docs.gitlab.com/ee/development/changelog.html
[Code Review process]: https://docs.gitlab.com/ee/development/code_review.html
[Approval Guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines
[Canonical repository]: https://gitlab.com/gitlab-org/gitlab
[`package-and-qa` build]: https://docs.gitlab.com/ee/development/testing_guide/end_to_end/#using-the-package-and-qa-job

View file

@ -8,6 +8,7 @@ exclude:
- 'spec/**/*' - 'spec/**/*'
require: require:
- './haml_lint/linter/no_plain_nodes.rb' - './haml_lint/linter/no_plain_nodes.rb'
- './haml_lint/linter/documentation_links.rb'
linters: linters:
AltText: AltText:
@ -26,6 +27,12 @@ linters:
enabled: false enabled: false
max_consecutive: 2 max_consecutive: 2
DocumentationLinks:
enabled: true
include:
- 'app/views/**/*.haml'
- 'ee/app/views/**/*.haml'
EmptyObjectReference: EmptyObjectReference:
enabled: true enabled: true

View file

@ -47,7 +47,6 @@ linters:
- "app/views/admin/hooks/edit.html.haml" - "app/views/admin/hooks/edit.html.haml"
- "app/views/admin/logs/show.html.haml" - "app/views/admin/logs/show.html.haml"
- "app/views/admin/projects/_projects.html.haml" - "app/views/admin/projects/_projects.html.haml"
- "app/views/admin/projects/show.html.haml"
- "app/views/admin/requests_profiles/index.html.haml" - "app/views/admin/requests_profiles/index.html.haml"
- "app/views/admin/runners/_runner.html.haml" - "app/views/admin/runners/_runner.html.haml"
- "app/views/admin/runners/index.html.haml" - "app/views/admin/runners/index.html.haml"
@ -57,7 +56,6 @@ linters:
- "app/views/admin/spam_logs/_spam_log.html.haml" - "app/views/admin/spam_logs/_spam_log.html.haml"
- "app/views/admin/spam_logs/index.html.haml" - "app/views/admin/spam_logs/index.html.haml"
- "app/views/admin/system_info/show.html.haml" - "app/views/admin/system_info/show.html.haml"
- "app/views/admin/users/_access_levels.html.haml"
- "app/views/admin/users/_form.html.haml" - "app/views/admin/users/_form.html.haml"
- "app/views/admin/users/_head.html.haml" - "app/views/admin/users/_head.html.haml"
- "app/views/admin/users/_profile.html.haml" - "app/views/admin/users/_profile.html.haml"
@ -280,7 +278,6 @@ linters:
- "app/views/shared/_no_password.html.haml" - "app/views/shared/_no_password.html.haml"
- "app/views/shared/_ping_consent.html.haml" - "app/views/shared/_ping_consent.html.haml"
- "app/views/shared/_project_limit.html.haml" - "app/views/shared/_project_limit.html.haml"
- "app/views/shared/boards/components/_board.html.haml"
- "app/views/shared/boards/components/_sidebar.html.haml" - "app/views/shared/boards/components/_sidebar.html.haml"
- "app/views/shared/boards/components/sidebar/_due_date.html.haml" - "app/views/shared/boards/components/sidebar/_due_date.html.haml"
- "app/views/shared/boards/components/sidebar/_labels.html.haml" - "app/views/shared/boards/components/sidebar/_labels.html.haml"
@ -358,7 +355,6 @@ linters:
- "ee/app/views/notify/unapproved_merge_request_email.html.haml" - "ee/app/views/notify/unapproved_merge_request_email.html.haml"
- "ee/app/views/oauth/geo_auth/error.html.haml" - "ee/app/views/oauth/geo_auth/error.html.haml"
- "ee/app/views/projects/commits/_mirror_status.html.haml" - "ee/app/views/projects/commits/_mirror_status.html.haml"
- "ee/app/views/projects/jobs/_shared_runner_limit_warning.html.haml"
- "ee/app/views/projects/merge_requests/_approvals_count.html.haml" - "ee/app/views/projects/merge_requests/_approvals_count.html.haml"
- "ee/app/views/projects/merge_requests/widget/open/_geo.html.haml" - "ee/app/views/projects/merge_requests/widget/open/_geo.html.haml"
- "ee/app/views/projects/mirrors/_mirrored_repositories_count.html.haml" - "ee/app/views/projects/mirrors/_mirrored_repositories_count.html.haml"
@ -372,7 +368,6 @@ linters:
- "ee/app/views/projects/services/gitlab_slack_application/_slack_integration_form.html.haml" - "ee/app/views/projects/services/gitlab_slack_application/_slack_integration_form.html.haml"
- "ee/app/views/projects/settings/slacks/edit.html.haml" - "ee/app/views/projects/settings/slacks/edit.html.haml"
- "ee/app/views/shared/_mirror_update_button.html.haml" - "ee/app/views/shared/_mirror_update_button.html.haml"
- "ee/app/views/shared/boards/components/_list_weight.html.haml"
- "ee/app/views/shared/epic/_search_bar.html.haml" - "ee/app/views/shared/epic/_search_bar.html.haml"
- "ee/app/views/shared/issuable/_approvals.html.haml" - "ee/app/views/shared/issuable/_approvals.html.haml"
- "ee/app/views/shared/issuable/_board_create_list_dropdown.html.haml" - "ee/app/views/shared/issuable/_board_create_list_dropdown.html.haml"

View file

@ -79,6 +79,7 @@
"Jira Cloud", "Jira Cloud",
"Jira Server", "Jira Server",
"jQuery", "jQuery",
"JSON",
"JupyterHub", "JupyterHub",
"Karma", "Karma",
"Kerberos", "Kerberos",

View file

@ -3,6 +3,7 @@
/public/ /public/
/vendor/ /vendor/
/tmp/ /tmp/
doc/api/graphql/reference/gitlab_schema.graphql
# ignore stylesheets for now as this clashes with our linter # ignore stylesheets for now as this clashes with our linter
*.css *.css

View file

@ -308,6 +308,18 @@ Gitlab/Union:
- 'spec/**/*' - 'spec/**/*'
- 'ee/spec/**/*' - 'ee/spec/**/*'
API/GrapeAPIInstance:
Enabled: true
Include:
- 'lib/**/api/**/*.rb'
- 'ee/**/api/**/*.rb'
API/GrapeArrayMissingCoerce:
Enabled: true
Include:
- 'lib/**/api/**/*.rb'
- 'ee/**/api/**/*.rb'
Cop/SidekiqOptionsQueue: Cop/SidekiqOptionsQueue:
Enabled: true Enabled: true
Exclude: Exclude:
@ -316,6 +328,9 @@ Cop/SidekiqOptionsQueue:
Graphql/AuthorizeTypes: Graphql/AuthorizeTypes:
Enabled: true Enabled: true
Include:
- 'app/graphql/types/**/*'
- 'ee/app/graphql/types/**/*'
Exclude: Exclude:
- 'spec/**/*.rb' - 'spec/**/*.rb'
- 'ee/spec/**/*.rb' - 'ee/spec/**/*.rb'
@ -450,10 +465,17 @@ Rails/TimeZone:
- 'spec/models/**/*' - 'spec/models/**/*'
- 'ee/app/models/**/*' - 'ee/app/models/**/*'
- 'ee/spec/models/**/*' - 'ee/spec/models/**/*'
- 'app/workers/**/*'
- 'spec/workers/**/*'
- 'ee/app/workers/**/*'
- 'ee/spec/workers/**/*'
# WIP: See https://gitlab.com/gitlab-org/gitlab/-/issues/220040 # WIP: See https://gitlab.com/gitlab-org/gitlab/-/issues/220040
Rails/SaveBang: Rails/SaveBang:
Enabled: true Enabled: true
AllowImplicitReturn: false
AllowedReceivers: ['ActionDispatch::TestRequest']
Include: Include:
- 'spec/**/*.rb' - 'spec/**/*.rb'
- 'ee/spec/**/*.rb' - 'ee/spec/**/*.rb'

View file

@ -303,6 +303,11 @@ Performance/Detect:
RSpec/ContextWording: RSpec/ContextWording:
Enabled: false Enabled: false
# Offense count: 626
# Cop supports --auto-correct.
RSpec/EmptyLineAfterLetBlock:
Enabled: false
# Offense count: 1121 # Offense count: 1121
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle. # Configuration parameters: EnforcedStyle.
@ -560,30 +565,6 @@ Style/GuardClause:
Style/HashEachMethods: Style/HashEachMethods:
Enabled: false Enabled: false
# Offense count: 6
# Cop supports --auto-correct.
Style/HashTransformKeys:
Exclude:
- 'ee/app/models/vulnerabilities/occurrence.rb'
- 'ee/spec/lib/gitlab/ci/templates/dependency_scanning_gitlab_ci_yaml_spec.rb'
- 'lib/banzai/filter/commit_trailers_filter.rb'
- 'lib/gitlab/analytics/cycle_analytics/stage_events.rb'
# Offense count: 10
# Cop supports --auto-correct.
Style/HashTransformValues:
Exclude:
- 'app/validators/addressable_url_validator.rb'
- 'config/initializers/action_dispatch_journey_formatter.rb'
- 'ee/app/helpers/compliance_management/compliance_framework/project_settings_helper.rb'
- 'ee/app/services/packages/nuget/metadata_extraction_service.rb'
- 'lib/gitlab/config/entry/configurable.rb'
- 'lib/gitlab/config/entry/node.rb'
- 'lib/gitlab/discussions_diff/file_collection.rb'
- 'lib/gitlab/error_tracking.rb'
- 'lib/rspec_flaky/flaky_examples_collection.rb'
- 'spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb'
# Offense count: 31 # Offense count: 31
# Configuration parameters: AllowIfModifier. # Configuration parameters: AllowIfModifier.
Style/IfInsideElse: Style/IfInsideElse:
@ -639,12 +620,122 @@ Style/Next:
Style/NumericLiteralPrefix: Style/NumericLiteralPrefix:
Enabled: false Enabled: false
# Offense count: 255 # Offense count: 130
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. # Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods.
# SupportedStyles: predicate, comparison # SupportedStyles: predicate, comparison
# We use EnforcedStyle of comparison here due to it being better
# performing code as seen https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36221#note_375659681
Style/NumericPredicate: Style/NumericPredicate:
Enabled: false EnforcedStyle: comparison
Exclude:
- 'spec/**/*'
- 'app/views/**/*'
- 'ee/app/views/**/*'
- 'app/controllers/concerns/issuable_collections.rb'
- 'app/controllers/concerns/paginated_collection.rb'
- 'app/helpers/graph_helper.rb'
- 'app/helpers/timeboxes_helper.rb'
- 'app/models/ci/pipeline.rb'
- 'app/models/ci/stage.rb'
- 'app/models/concerns/update_project_statistics.rb'
- 'app/models/merge_request_diff.rb'
- 'app/models/milestone.rb'
- 'app/models/network/graph.rb'
- 'app/models/postgresql/replication_slot.rb'
- 'app/models/project.rb'
- 'app/models/suggestion.rb'
- 'app/models/user.rb'
- 'app/serializers/merge_request_widget_entity.rb'
- 'app/services/boards/issues/move_service.rb'
- 'app/services/cohorts_service.rb'
- 'app/services/discussions/resolve_service.rb'
- 'app/services/issues/reorder_service.rb'
- 'app/services/notes/create_service.rb'
- 'app/services/packages/nuget/metadata_extraction_service.rb'
- 'app/services/packages/nuget/search_service.rb'
- 'app/services/projects/auto_devops/disable_service.rb'
- 'app/services/projects/update_pages_service.rb'
- 'app/services/search_service.rb'
- 'app/workers/admin_email_worker.rb'
- 'app/workers/gitlab/import/advance_stage.rb'
- 'config/initializers/validate_puma.rb'
- 'ee/app/controllers/security/projects_controller.rb'
- 'ee/app/graphql/mutations/instance_security_dashboard/remove_project.rb'
- 'ee/app/helpers/ee/timeboxes_helper.rb'
- 'ee/app/models/ci/minutes/quota.rb'
- 'ee/app/models/ee/ci/runner.rb'
- 'ee/app/models/geo_node_status.rb'
- 'ee/app/models/license.rb'
- 'ee/app/models/namespace_statistics.rb'
- 'ee/app/services/ee/issues/base_service.rb'
- 'ee/app/services/ee/merge_requests/approval_service.rb'
- 'ee/app/services/ee/quick_actions/target_service.rb'
- 'ee/app/services/elastic/indexing_control_service.rb'
- 'ee/app/services/geo/hashed_storage_migration_service.rb'
- 'ee/app/services/geo/prune_event_log_service.rb'
- 'ee/app/services/security/waf_anomaly_summary_service.rb'
- 'ee/app/services/update_build_minutes_service.rb'
- 'ee/app/workers/geo/container_repository_sync_dispatch_worker.rb'
- 'ee/app/workers/geo/file_download_dispatch_worker.rb'
- 'ee/app/workers/geo/registry_sync_worker.rb'
- 'ee/app/workers/geo/repository_shard_sync_worker.rb'
- 'ee/app/workers/geo/repository_verification/primary/shard_worker.rb'
- 'ee/lib/api/helpers/packages/conan/api_helpers.rb'
- 'ee/lib/ee/gitlab/auth/ldap/person.rb'
- 'ee/lib/ee/gitlab/background_migration/prune_orphaned_geo_events.rb'
- 'ee/lib/ee/gitlab/checks/push_rules/file_size_check.rb'
- 'ee/lib/ee/gitlab/geo_git_access.rb'
- 'ee/lib/gitlab/geo/fdw.rb'
- 'ee/lib/gitlab/geo/log_cursor/lease.rb'
- 'ee/lib/tasks/gitlab/elastic.rake'
- 'lib/api/entities/feature.rb'
- 'lib/api/helpers/pagination_strategies.rb'
- 'lib/backup/files.rb'
- 'lib/banzai/filter/gollum_tags_filter.rb'
- 'lib/bitbucket_server/paginator.rb'
- 'lib/declarative_policy/runner.rb'
- 'lib/gitlab/auth/ldap/adapter.rb'
- 'lib/gitlab/bare_repository_import/importer.rb'
- 'lib/gitlab/ci/config/external/context.rb'
- 'lib/gitlab/ci/reports/accessibility_reports_comparer.rb'
- 'lib/gitlab/cycle_analytics/summary/value.rb'
- 'lib/gitlab/cycle_analytics/summary_helper.rb'
- 'lib/gitlab/danger/teammate.rb'
- 'lib/gitlab/database.rb'
- 'lib/gitlab/database/connection_timer.rb'
- 'lib/gitlab/database/migration_helpers.rb'
- 'lib/gitlab/exclusive_lease.rb'
- 'lib/gitlab/exclusive_lease_helpers/sleeping_lock.rb'
- 'lib/gitlab/experimentation.rb'
- 'lib/gitlab/file_hook.rb'
- 'lib/gitlab/git/commit.rb'
- 'lib/gitlab/git/repository.rb'
- 'lib/gitlab/git/rugged_impl/blob.rb'
- 'lib/gitlab/gitaly_client.rb'
- 'lib/gitlab/github_import/user_finder.rb'
- 'lib/gitlab/hashed_storage/migrator.rb'
- 'lib/gitlab/import_export/command_line_util.rb'
- 'lib/gitlab/multi_collection_paginator.rb'
- 'lib/gitlab/polling_interval.rb'
- 'lib/gitlab/project_search_results.rb'
- 'lib/gitlab/seeder.rb'
- 'lib/gitlab/sidekiq_cluster.rb'
- 'lib/gitlab/sidekiq_daemon/memory_killer.rb'
- 'lib/gitlab/sidekiq_middleware/memory_killer.rb'
- 'lib/gitlab/sidekiq_status.rb'
- 'lib/gitlab/slash_commands/presenters/issue_show.rb'
- 'lib/gitlab/task_helpers.rb'
- 'lib/gitlab/untrusted_regexp.rb'
- 'lib/gitlab/utils.rb'
- 'lib/system_check/sidekiq_check.rb'
- 'lib/tasks/gitlab/gitaly.rake'
- 'lib/tasks/gitlab/snippets.rake'
- 'lib/tasks/gitlab/workhorse.rake'
- 'qa/qa/git/repository.rb'
- 'qa/qa/support/wait_for_requests.rb'
- 'ee/app/models/ee/project.rb'
- 'lib/gitlab/usage_data/topology.rb'
# Offense count: 117 # Offense count: 117
# Cop supports --auto-correct. # Cop supports --auto-correct.
@ -690,18 +781,6 @@ Style/RedundantFreeze:
Style/RedundantInterpolation: Style/RedundantInterpolation:
Enabled: false Enabled: false
# Offense count: 6
# Cop supports --auto-correct.
Style/RedundantParentheses:
Exclude:
- 'ee/app/models/ee/merge_request.rb'
# Offense count: 33
# Cop supports --auto-correct.
# Configuration parameters: AllowMultipleReturnValues.
Style/RedundantReturn:
Enabled: false
# Offense count: 801 # Offense count: 801
# Cop supports --auto-correct. # Cop supports --auto-correct.
Style/RedundantSelf: Style/RedundantSelf:
@ -711,8 +790,8 @@ Style/RedundantSelf:
# Cop supports --auto-correct. # Cop supports --auto-correct.
Style/RedundantSort: Style/RedundantSort:
Exclude: Exclude:
- 'ee/app/presenters/packages/nuget/search_results_presenter.rb' - 'app/presenters/packages/nuget/search_results_presenter.rb'
- 'ee/spec/presenters/packages/nuget/search_results_presenter_spec.rb' - 'spec/presenters/packages/nuget/search_results_presenter_spec.rb'
# Offense count: 120 # Offense count: 120
# Cop supports --auto-correct. # Cop supports --auto-correct.
@ -769,25 +848,20 @@ Style/StringLiteralsInInterpolation:
Style/SymbolProc: Style/SymbolProc:
Enabled: false Enabled: false
# Offense count: 1478 # Offense count: 2362
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: AllowImplicitReturn, AllowedReceivers. # Configuration parameters: AllowImplicitReturn, AllowedReceivers.
Rails/SaveBang: Rails/SaveBang:
Exclude: Exclude:
- 'ee/spec/controllers/groups/epic_issues_controller_spec.rb'
- 'ee/spec/controllers/groups/epic_links_controller_spec.rb'
- 'ee/spec/controllers/groups/epics_controller_spec.rb'
- 'ee/spec/controllers/groups/roadmap_controller_spec.rb'
- 'ee/spec/controllers/projects/environments_controller_spec.rb'
- 'ee/spec/controllers/projects/issues_controller_spec.rb'
- 'ee/spec/controllers/projects/merge_requests/creations_controller_spec.rb'
- 'ee/spec/controllers/projects/merge_requests_controller_spec.rb' - 'ee/spec/controllers/projects/merge_requests_controller_spec.rb'
- 'ee/spec/controllers/projects/service_desk_controller_spec.rb'
- 'ee/spec/controllers/projects/subscriptions_controller_spec.rb'
- 'ee/spec/controllers/projects/vulnerability_feedback_controller_spec.rb'
- 'ee/spec/controllers/subscriptions_controller_spec.rb' - 'ee/spec/controllers/subscriptions_controller_spec.rb'
- 'ee/spec/factories/ci/job_artifacts.rb'
- 'ee/spec/factories/epics.rb'
- 'ee/spec/factories/licenses.rb'
- 'ee/spec/factories/merge_requests.rb' - 'ee/spec/factories/merge_requests.rb'
- 'ee/spec/features/admin/admin_users_spec.rb'
- 'ee/spec/features/admin/geo/admin_geo_nodes_spec.rb' - 'ee/spec/features/admin/geo/admin_geo_nodes_spec.rb'
- 'ee/spec/features/admin/licenses/admin_views_license_spec.rb'
- 'ee/spec/features/boards/scoped_issue_board_spec.rb' - 'ee/spec/features/boards/scoped_issue_board_spec.rb'
- 'ee/spec/features/ci_shared_runner_warnings_spec.rb' - 'ee/spec/features/ci_shared_runner_warnings_spec.rb'
- 'ee/spec/features/dashboards/operations_spec.rb' - 'ee/spec/features/dashboards/operations_spec.rb'
@ -796,13 +870,19 @@ Rails/SaveBang:
- 'ee/spec/features/merge_requests/user_views_all_merge_requests_spec.rb' - 'ee/spec/features/merge_requests/user_views_all_merge_requests_spec.rb'
- 'ee/spec/features/projects/members/invite_group_and_members_spec.rb' - 'ee/spec/features/projects/members/invite_group_and_members_spec.rb'
- 'ee/spec/features/projects/merge_requests/user_approves_merge_request_spec.rb' - 'ee/spec/features/projects/merge_requests/user_approves_merge_request_spec.rb'
- 'ee/spec/features/projects/mirror_spec.rb'
- 'ee/spec/features/projects/new_project_spec.rb' - 'ee/spec/features/projects/new_project_spec.rb'
- 'ee/spec/features/projects/settings/user_manages_approval_settings_spec.rb' - 'ee/spec/features/projects/settings/user_manages_approval_settings_spec.rb'
- 'ee/spec/features/projects/settings/user_manages_members_spec.rb' - 'ee/spec/features/projects/settings/user_manages_members_spec.rb'
- 'ee/spec/features/search/elastic/global_search_spec.rb' - 'ee/spec/features/search/elastic/global_search_spec.rb'
- 'ee/spec/features/security/project/internal_access_spec.rb'
- 'ee/spec/features/security/project/public_access_spec.rb'
- 'ee/spec/finders/epics_finder_spec.rb' - 'ee/spec/finders/epics_finder_spec.rb'
- 'ee/spec/finders/security/vulnerabilities_finder_spec.rb'
- 'ee/spec/frontend/fixtures/analytics.rb' - 'ee/spec/frontend/fixtures/analytics.rb'
- 'ee/spec/graphql/resolvers/vulnerabilities_resolver_spec.rb'
- 'ee/spec/helpers/application_helper_spec.rb' - 'ee/spec/helpers/application_helper_spec.rb'
- 'ee/spec/helpers/ee/dashboard_helper_spec.rb'
- 'ee/spec/helpers/ee/issues_helper_spec.rb' - 'ee/spec/helpers/ee/issues_helper_spec.rb'
- 'ee/spec/initializers/fog_google_https_private_urls_spec.rb' - 'ee/spec/initializers/fog_google_https_private_urls_spec.rb'
- 'ee/spec/lib/analytics/merge_request_metrics_calculator_spec.rb' - 'ee/spec/lib/analytics/merge_request_metrics_calculator_spec.rb'
@ -811,60 +891,84 @@ Rails/SaveBang:
- 'ee/spec/lib/ee/gitlab/background_migration/move_epic_issues_after_epics_spec.rb' - 'ee/spec/lib/ee/gitlab/background_migration/move_epic_issues_after_epics_spec.rb'
- 'ee/spec/lib/ee/gitlab/background_migration/populate_any_approval_rule_for_merge_requests_spec.rb' - 'ee/spec/lib/ee/gitlab/background_migration/populate_any_approval_rule_for_merge_requests_spec.rb'
- 'ee/spec/lib/ee/gitlab/background_migration/populate_any_approval_rule_for_projects_spec.rb' - 'ee/spec/lib/ee/gitlab/background_migration/populate_any_approval_rule_for_projects_spec.rb'
- 'ee/spec/lib/ee/gitlab/background_migration/prune_orphaned_geo_events_spec.rb'
- 'ee/spec/lib/ee/gitlab/checks/push_rules/commit_check_spec.rb' - 'ee/spec/lib/ee/gitlab/checks/push_rules/commit_check_spec.rb'
- 'ee/spec/lib/ee/gitlab/ci/pipeline/quota/activity_spec.rb' - 'ee/spec/lib/ee/gitlab/ci/pipeline/quota/activity_spec.rb'
- 'ee/spec/lib/gitlab/auth/ldap/access_spec.rb' - 'ee/spec/lib/gitlab/auth/ldap/access_spec.rb'
- 'ee/spec/lib/gitlab/auth/o_auth/user_spec.rb' - 'ee/spec/lib/gitlab/auth/o_auth/user_spec.rb'
- 'ee/spec/lib/gitlab/auth/saml/user_spec.rb' - 'ee/spec/lib/gitlab/auth/saml/user_spec.rb'
- 'ee/spec/lib/gitlab/background_migration/fix_orphan_promoted_issues_spec.rb'
- 'ee/spec/lib/gitlab/elastic/search_results_spec.rb' - 'ee/spec/lib/gitlab/elastic/search_results_spec.rb'
- 'ee/spec/lib/gitlab/email/handler/ee/service_desk_handler_spec.rb' - 'ee/spec/lib/gitlab/email/handler/ee/service_desk_handler_spec.rb'
- 'ee/spec/lib/gitlab/geo/cron_manager_spec.rb'
- 'ee/spec/lib/gitlab/geo/jwt_request_decoder_spec.rb' - 'ee/spec/lib/gitlab/geo/jwt_request_decoder_spec.rb'
- 'ee/spec/lib/gitlab/geo/oauth/session_spec.rb' - 'ee/spec/lib/gitlab/geo/oauth/session_spec.rb'
- 'ee/spec/lib/gitlab/geo_spec.rb' - 'ee/spec/lib/gitlab/geo_spec.rb'
- 'ee/spec/lib/gitlab/git_access_spec.rb' - 'ee/spec/lib/gitlab/git_access_spec.rb'
- 'ee/spec/lib/gitlab/import_export/group/relation_factory_spec.rb'
- 'ee/spec/lib/gitlab/mirror_spec.rb'
- 'ee/spec/mailers/notify_spec.rb' - 'ee/spec/mailers/notify_spec.rb'
- 'ee/spec/migrations/fix_any_approver_rule_for_projects_spec.rb'
- 'ee/spec/migrations/geo/migrate_ci_job_artifacts_to_separate_registry_spec.rb' - 'ee/spec/migrations/geo/migrate_ci_job_artifacts_to_separate_registry_spec.rb'
- 'ee/spec/migrations/geo/migrate_lfs_objects_to_separate_registry_spec.rb' - 'ee/spec/migrations/geo/migrate_lfs_objects_to_separate_registry_spec.rb'
- 'ee/spec/migrations/schedule_merge_request_any_approval_rule_migration_spec.rb'
- 'ee/spec/migrations/schedule_project_any_approval_rule_migration_spec.rb' - 'ee/spec/migrations/schedule_project_any_approval_rule_migration_spec.rb'
- 'ee/spec/models/application_setting_spec.rb' - 'ee/spec/models/application_setting_spec.rb'
- 'ee/spec/models/approval_merge_request_rule_spec.rb' - 'ee/spec/models/approval_merge_request_rule_spec.rb'
- 'ee/spec/models/approval_project_rule_spec.rb'
- 'ee/spec/models/approval_state_spec.rb' - 'ee/spec/models/approval_state_spec.rb'
- 'ee/spec/models/burndown_spec.rb' - 'ee/spec/models/burndown_spec.rb'
- 'ee/spec/models/ci/build_spec.rb' - 'ee/spec/models/ci/build_spec.rb'
- 'ee/spec/models/ci/pipeline_spec.rb' - 'ee/spec/models/ci/pipeline_spec.rb'
- 'ee/spec/models/ci/subscriptions/project_spec.rb' - 'ee/spec/models/ci/subscriptions/project_spec.rb'
- 'ee/spec/models/concerns/approver_migrate_hook_spec.rb'
- 'ee/spec/models/concerns/deprecated_approvals_before_merge_spec.rb' - 'ee/spec/models/concerns/deprecated_approvals_before_merge_spec.rb'
- 'ee/spec/models/concerns/elastic/note_spec.rb' - 'ee/spec/models/concerns/elastic/note_spec.rb'
- 'ee/spec/models/ee/appearance_spec.rb'
- 'ee/spec/models/ee/ci/job_artifact_spec.rb'
- 'ee/spec/models/ee/protected_branch_spec.rb' - 'ee/spec/models/ee/protected_branch_spec.rb'
- 'ee/spec/models/ee/protected_ref_access_spec.rb' - 'ee/spec/models/ee/protected_ref_access_spec.rb'
- 'ee/spec/models/ee/protected_ref_spec.rb' - 'ee/spec/models/ee/protected_ref_spec.rb'
- 'ee/spec/models/elasticsearch_indexed_namespace_spec.rb'
- 'ee/spec/models/environment_spec.rb'
- 'ee/spec/models/epic_spec.rb' - 'ee/spec/models/epic_spec.rb'
- 'ee/spec/models/geo/project_registry_spec.rb' - 'ee/spec/models/geo/project_registry_spec.rb'
- 'ee/spec/models/geo_node_spec.rb' - 'ee/spec/models/geo_node_spec.rb'
- 'ee/spec/models/geo_node_status_spec.rb' - 'ee/spec/models/geo_node_status_spec.rb'
- 'ee/spec/models/gitlab_subscription_spec.rb'
- 'ee/spec/models/group_spec.rb' - 'ee/spec/models/group_spec.rb'
- 'ee/spec/models/issue_spec.rb' - 'ee/spec/models/issue_spec.rb'
- 'ee/spec/models/label_note_spec.rb' - 'ee/spec/models/label_note_spec.rb'
- 'ee/spec/models/lfs_object_spec.rb'
- 'ee/spec/models/license_spec.rb' - 'ee/spec/models/license_spec.rb'
- 'ee/spec/models/merge_request_spec.rb' - 'ee/spec/models/merge_request_spec.rb'
- 'ee/spec/models/merge_train_spec.rb'
- 'ee/spec/models/operations/feature_flag_scope_spec.rb' - 'ee/spec/models/operations/feature_flag_scope_spec.rb'
- 'ee/spec/models/operations/feature_flag_spec.rb' - 'ee/spec/models/operations/feature_flag_spec.rb'
- 'ee/spec/models/operations/feature_flags/strategy_spec.rb' - 'ee/spec/models/operations/feature_flags/strategy_spec.rb'
- 'ee/spec/models/operations/feature_flags/user_list_spec.rb' - 'ee/spec/models/operations/feature_flags/user_list_spec.rb'
- 'spec/models/packages/package_spec.rb'
- 'ee/spec/models/project_ci_cd_setting_spec.rb'
- 'ee/spec/models/project_services/github_service_spec.rb' - 'ee/spec/models/project_services/github_service_spec.rb'
- 'ee/spec/models/project_services/jenkins_service_spec.rb' - 'ee/spec/models/project_services/jenkins_service_spec.rb'
- 'ee/spec/models/project_spec.rb' - 'ee/spec/models/project_spec.rb'
- 'ee/spec/models/protected_environment_spec.rb'
- 'ee/spec/models/repository_spec.rb'
- 'ee/spec/models/scim_identity_spec.rb' - 'ee/spec/models/scim_identity_spec.rb'
- 'ee/spec/models/scim_oauth_access_token_spec.rb' - 'ee/spec/models/scim_oauth_access_token_spec.rb'
- 'ee/spec/models/upload_spec.rb'
- 'ee/spec/models/user_preference_spec.rb' - 'ee/spec/models/user_preference_spec.rb'
- 'ee/spec/models/user_spec.rb' - 'ee/spec/models/user_spec.rb'
- 'ee/spec/models/visible_approvable_spec.rb' - 'ee/spec/models/visible_approvable_spec.rb'
- 'ee/spec/models/vulnerabilities/feedback_spec.rb' - 'ee/spec/models/vulnerabilities/feedback_spec.rb'
- 'ee/spec/models/vulnerabilities/issue_link_spec.rb' - 'ee/spec/models/vulnerabilities/issue_link_spec.rb'
- 'ee/spec/policies/group_policy_spec.rb'
- 'ee/spec/policies/note_policy_spec.rb'
- 'ee/spec/policies/project_policy_spec.rb'
- 'ee/spec/policies/protected_branch_policy_spec.rb' - 'ee/spec/policies/protected_branch_policy_spec.rb'
- 'ee/spec/policies/vulnerabilities/feedback_policy_spec.rb'
- 'ee/spec/presenters/audit_event_presenter_spec.rb' - 'ee/spec/presenters/audit_event_presenter_spec.rb'
- 'ee/spec/presenters/epic_presenter_spec.rb' - 'ee/spec/presenters/epic_presenter_spec.rb'
- 'ee/spec/presenters/packages/conan/package_presenter_spec.rb'
- 'ee/spec/requests/api/boards_spec.rb' - 'ee/spec/requests/api/boards_spec.rb'
- 'ee/spec/requests/api/epic_issues_spec.rb' - 'ee/spec/requests/api/epic_issues_spec.rb'
- 'ee/spec/requests/api/epic_links_spec.rb' - 'ee/spec/requests/api/epic_links_spec.rb'
@ -876,7 +980,6 @@ Rails/SaveBang:
- 'ee/spec/requests/api/groups_spec.rb' - 'ee/spec/requests/api/groups_spec.rb'
- 'ee/spec/requests/api/issues_spec.rb' - 'ee/spec/requests/api/issues_spec.rb'
- 'ee/spec/requests/api/ldap_group_links_spec.rb' - 'ee/spec/requests/api/ldap_group_links_spec.rb'
- 'ee/spec/requests/api/maven_packages_spec.rb'
- 'ee/spec/requests/api/merge_request_approval_rules_spec.rb' - 'ee/spec/requests/api/merge_request_approval_rules_spec.rb'
- 'ee/spec/requests/api/merge_request_approvals_spec.rb' - 'ee/spec/requests/api/merge_request_approvals_spec.rb'
- 'ee/spec/requests/api/merge_requests_spec.rb' - 'ee/spec/requests/api/merge_requests_spec.rb'
@ -885,9 +988,15 @@ Rails/SaveBang:
- 'ee/spec/requests/api/protected_branches_spec.rb' - 'ee/spec/requests/api/protected_branches_spec.rb'
- 'ee/spec/requests/api/scim_spec.rb' - 'ee/spec/requests/api/scim_spec.rb'
- 'ee/spec/requests/api/todos_spec.rb' - 'ee/spec/requests/api/todos_spec.rb'
- 'ee/spec/requests/lfs_http_spec.rb'
- 'ee/spec/services/approval_rules/finalize_service_spec.rb' - 'ee/spec/services/approval_rules/finalize_service_spec.rb'
- 'ee/spec/services/approval_rules/update_service_spec.rb'
- 'ee/spec/services/ci/minutes/email_notification_service_spec.rb'
- 'ee/spec/services/ci/process_build_service_spec.rb'
- 'ee/spec/services/ci/register_job_service_spec.rb' - 'ee/spec/services/ci/register_job_service_spec.rb'
- 'ee/spec/services/ee/boards/issues/create_service_spec.rb' - 'ee/spec/services/ee/boards/issues/create_service_spec.rb'
- 'ee/spec/services/ee/boards/issues/list_service_spec.rb'
- 'ee/spec/services/ee/boards/lists/list_service_spec.rb'
- 'ee/spec/services/ee/issuable/clone/attributes_rewriter_spec.rb' - 'ee/spec/services/ee/issuable/clone/attributes_rewriter_spec.rb'
- 'ee/spec/services/ee/issuable/common_system_notes_service_spec.rb' - 'ee/spec/services/ee/issuable/common_system_notes_service_spec.rb'
- 'ee/spec/services/ee/issues/update_service_spec.rb' - 'ee/spec/services/ee/issues/update_service_spec.rb'
@ -896,13 +1005,15 @@ Rails/SaveBang:
- 'ee/spec/services/ee/notes/quick_actions_service_spec.rb' - 'ee/spec/services/ee/notes/quick_actions_service_spec.rb'
- 'ee/spec/services/ee/notification_service_spec.rb' - 'ee/spec/services/ee/notification_service_spec.rb'
- 'ee/spec/services/ee/resource_events/change_weight_service_spec.rb' - 'ee/spec/services/ee/resource_events/change_weight_service_spec.rb'
- 'ee/spec/services/elastic/index_record_service_spec.rb'
- 'ee/spec/services/epic_links/create_service_spec.rb' - 'ee/spec/services/epic_links/create_service_spec.rb'
- 'ee/spec/services/epics/close_service_spec.rb'
- 'ee/spec/services/epics/issue_promote_service_spec.rb' - 'ee/spec/services/epics/issue_promote_service_spec.rb'
- 'ee/spec/services/epics/reopen_service_spec.rb'
- 'ee/spec/services/epics/tree_reorder_service_spec.rb' - 'ee/spec/services/epics/tree_reorder_service_spec.rb'
- 'ee/spec/services/epics/update_dates_service_spec.rb' - 'ee/spec/services/epics/update_dates_service_spec.rb'
- 'ee/spec/services/epics/update_service_spec.rb' - 'ee/spec/services/epics/update_service_spec.rb'
- 'ee/spec/services/geo/blob_verification_secondary_service_spec.rb' - 'ee/spec/services/geo/blob_verification_secondary_service_spec.rb'
- 'ee/spec/services/geo/files_expire_service_spec.rb'
- 'ee/spec/services/geo/metrics_update_service_spec.rb' - 'ee/spec/services/geo/metrics_update_service_spec.rb'
- 'ee/spec/services/geo/registry_consistency_service_spec.rb' - 'ee/spec/services/geo/registry_consistency_service_spec.rb'
- 'ee/spec/services/geo/repository_verification_secondary_service_spec.rb' - 'ee/spec/services/geo/repository_verification_secondary_service_spec.rb'
@ -911,8 +1022,11 @@ Rails/SaveBang:
- 'ee/spec/services/lfs/unlock_file_service_spec.rb' - 'ee/spec/services/lfs/unlock_file_service_spec.rb'
- 'ee/spec/services/merge_requests/approval_service_spec.rb' - 'ee/spec/services/merge_requests/approval_service_spec.rb'
- 'ee/spec/services/merge_requests/remove_approval_service_spec.rb' - 'ee/spec/services/merge_requests/remove_approval_service_spec.rb'
- 'ee/spec/services/merge_requests/update_blocks_service_spec.rb'
- 'ee/spec/services/merge_trains/refresh_merge_request_service_spec.rb' - 'ee/spec/services/merge_trains/refresh_merge_request_service_spec.rb'
- 'ee/spec/services/projects/after_rename_service_spec.rb' - 'ee/spec/services/projects/after_rename_service_spec.rb'
- 'ee/spec/services/projects/import_export/export_service_spec.rb'
- 'ee/spec/services/projects/update_mirror_service_spec.rb'
- 'ee/spec/services/projects/update_service_spec.rb' - 'ee/spec/services/projects/update_service_spec.rb'
- 'ee/spec/services/quick_actions/interpret_service_spec.rb' - 'ee/spec/services/quick_actions/interpret_service_spec.rb'
- 'ee/spec/services/slash_commands/global_slack_handler_spec.rb' - 'ee/spec/services/slash_commands/global_slack_handler_spec.rb'
@ -920,50 +1034,92 @@ Rails/SaveBang:
- 'ee/spec/services/status_page/trigger_publish_service_spec.rb' - 'ee/spec/services/status_page/trigger_publish_service_spec.rb'
- 'ee/spec/services/todo_service_spec.rb' - 'ee/spec/services/todo_service_spec.rb'
- 'ee/spec/services/update_build_minutes_service_spec.rb' - 'ee/spec/services/update_build_minutes_service_spec.rb'
- 'ee/spec/services/vulnerability_feedback/create_service_spec.rb'
- 'ee/spec/support/helpers/ee/geo_helpers.rb'
- 'ee/spec/support/protected_tags/access_control_shared_examples.rb'
- 'ee/spec/support/shared_examples/features/protected_branches_access_control_shared_examples.rb'
- 'ee/spec/support/shared_examples/finders/geo/framework_registry_finder_shared_examples.rb'
- 'ee/spec/support/shared_examples/graphql/geo/geo_registries_resolver_shared_examples.rb'
- 'ee/spec/support/shared_examples/lib/analytics/common_merge_request_metrics_refresh_shared_examples.rb' - 'ee/spec/support/shared_examples/lib/analytics/common_merge_request_metrics_refresh_shared_examples.rb'
- 'ee/spec/support/shared_examples/models/concerns/replicator_shared_examples.rb' - 'ee/spec/support/shared_examples/models/concerns/replicator_shared_examples.rb'
- 'ee/spec/support/shared_examples/models/elasticsearch_indexed_container_shared_examples.rb'
- 'ee/spec/support/shared_examples/models/geo_framework_registry_shared_examples.rb'
- 'ee/spec/support/shared_examples/models/member_shared_examples.rb' - 'ee/spec/support/shared_examples/models/member_shared_examples.rb'
- 'ee/spec/support/shared_examples/models/mentionable_shared_examples.rb' - 'ee/spec/support/shared_examples/models/mentionable_shared_examples.rb'
- 'ee/spec/support/shared_examples/policies/protected_environments_shared_examples.rb' - 'ee/spec/support/shared_examples/policies/protected_environments_shared_examples.rb'
- 'ee/spec/support/shared_examples/requests/api/graphql/geo/registries_shared_examples.rb'
- 'ee/spec/support/shared_examples/requests/api/project_approval_rules_api_shared_examples.rb' - 'ee/spec/support/shared_examples/requests/api/project_approval_rules_api_shared_examples.rb'
- 'ee/spec/support/shared_examples/services/build_execute_shared_examples.rb'
- 'ee/spec/support/shared_examples/services/issue_epic_shared_examples.rb' - 'ee/spec/support/shared_examples/services/issue_epic_shared_examples.rb'
- 'ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb' - 'ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb'
- 'ee/spec/workers/adjourned_project_deletion_worker_spec.rb' - 'ee/spec/workers/adjourned_project_deletion_worker_spec.rb'
- 'ee/spec/workers/clear_shared_runners_minutes_worker_spec.rb'
- 'ee/spec/workers/create_github_webhook_worker_spec.rb' - 'ee/spec/workers/create_github_webhook_worker_spec.rb'
- 'ee/spec/workers/elastic_indexer_worker_spec.rb' - 'ee/spec/workers/elastic_namespace_rollout_worker_spec.rb'
- 'ee/spec/workers/geo/container_repository_sync_dispatch_worker_spec.rb' - 'ee/spec/workers/geo/container_repository_sync_dispatch_worker_spec.rb'
- 'ee/spec/workers/geo/file_download_dispatch_worker_spec.rb' - 'ee/spec/workers/geo/file_download_dispatch_worker_spec.rb'
- 'ee/spec/workers/geo/prune_event_log_worker_spec.rb'
- 'ee/spec/workers/geo/registry_sync_worker_spec.rb'
- 'ee/spec/workers/geo/repository_shard_sync_worker_spec.rb' - 'ee/spec/workers/geo/repository_shard_sync_worker_spec.rb'
- 'ee/spec/workers/repository_import_worker_spec.rb' - 'ee/spec/workers/repository_import_worker_spec.rb'
- 'ee/spec/workers/update_all_mirrors_worker_spec.rb' - 'ee/spec/workers/update_all_mirrors_worker_spec.rb'
- 'qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb' - 'qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb'
- 'qa/qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb' - 'qa/qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb'
- 'qa/qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb'
- 'spec/controllers/abuse_reports_controller_spec.rb' - 'spec/controllers/abuse_reports_controller_spec.rb'
- 'spec/controllers/admin/impersonations_controller_spec.rb' - 'spec/controllers/admin/impersonations_controller_spec.rb'
- 'spec/controllers/admin/runners_controller_spec.rb' - 'spec/controllers/admin/runners_controller_spec.rb'
- 'spec/controllers/admin/services_controller_spec.rb'
- 'spec/controllers/boards/issues_controller_spec.rb' - 'spec/controllers/boards/issues_controller_spec.rb'
- 'spec/controllers/groups/milestones_controller_spec.rb'
- 'spec/controllers/groups/runners_controller_spec.rb' - 'spec/controllers/groups/runners_controller_spec.rb'
- 'spec/controllers/groups/uploads_controller_spec.rb'
- 'spec/controllers/groups_controller_spec.rb'
- 'spec/controllers/oauth/authorizations_controller_spec.rb' - 'spec/controllers/oauth/authorizations_controller_spec.rb'
- 'spec/controllers/omniauth_callbacks_controller_spec.rb' - 'spec/controllers/omniauth_callbacks_controller_spec.rb'
- 'spec/controllers/profiles/emails_controller_spec.rb' - 'spec/controllers/profiles/emails_controller_spec.rb'
- 'spec/controllers/profiles/notifications_controller_spec.rb' - 'spec/controllers/profiles/notifications_controller_spec.rb'
- 'spec/controllers/projects/artifacts_controller_spec.rb' - 'spec/controllers/projects/artifacts_controller_spec.rb'
- 'spec/controllers/projects/cycle_analytics/events_controller_spec.rb'
- 'spec/controllers/projects/cycle_analytics_controller_spec.rb' - 'spec/controllers/projects/cycle_analytics_controller_spec.rb'
- 'spec/controllers/projects/discussions_controller_spec.rb'
- 'spec/controllers/projects/forks_controller_spec.rb'
- 'spec/controllers/projects/group_links_controller_spec.rb'
- 'spec/controllers/projects/imports_controller_spec.rb'
- 'spec/controllers/projects/issues_controller_spec.rb' - 'spec/controllers/projects/issues_controller_spec.rb'
- 'spec/controllers/projects/labels_controller_spec.rb' - 'spec/controllers/projects/labels_controller_spec.rb'
- 'spec/controllers/projects/merge_requests_controller_spec.rb' - 'spec/controllers/projects/merge_requests_controller_spec.rb'
- 'spec/controllers/projects/milestones_controller_spec.rb' - 'spec/controllers/projects/milestones_controller_spec.rb'
- 'spec/controllers/projects/notes_controller_spec.rb' - 'spec/controllers/projects/notes_controller_spec.rb'
- 'spec/controllers/projects/pipelines_controller_spec.rb' - 'spec/controllers/projects/pipelines_controller_spec.rb'
- 'spec/controllers/projects/releases/evidences_controller_spec.rb'
- 'spec/controllers/projects/runners_controller_spec.rb' - 'spec/controllers/projects/runners_controller_spec.rb'
- 'spec/controllers/projects/starrers_controller_spec.rb'
- 'spec/controllers/projects/uploads_controller_spec.rb'
- 'spec/controllers/projects_controller_spec.rb' - 'spec/controllers/projects_controller_spec.rb'
- 'spec/controllers/sent_notifications_controller_spec.rb'
- 'spec/controllers/sessions_controller_spec.rb'
- 'spec/controllers/users_controller_spec.rb'
- 'spec/factories/alert_management/alerts.rb'
- 'spec/factories/boards.rb'
- 'spec/factories/ci/pipelines.rb' - 'spec/factories/ci/pipelines.rb'
- 'spec/factories/design_management/designs.rb' - 'spec/factories/design_management/designs.rb'
- 'spec/factories/design_management/versions.rb' - 'spec/factories/design_management/versions.rb'
- 'spec/factories/emails.rb'
- 'spec/factories/issues.rb'
- 'spec/factories/labels.rb' - 'spec/factories/labels.rb'
- 'spec/factories/merge_requests.rb'
- 'spec/factories/plans.rb'
- 'spec/factories/projects.rb' - 'spec/factories/projects.rb'
- 'spec/factories/services.rb'
- 'spec/factories/wiki_pages.rb'
- 'spec/factories_spec.rb'
- 'spec/features/admin/admin_appearance_spec.rb'
- 'spec/features/admin/admin_labels_spec.rb'
- 'spec/features/admin/admin_mode/login_spec.rb' - 'spec/features/admin/admin_mode/login_spec.rb'
- 'spec/features/admin/admin_runners_spec.rb' - 'spec/features/admin/admin_runners_spec.rb'
- 'spec/features/admin/admin_sees_project_statistics_spec.rb'
- 'spec/features/admin/admin_sees_projects_statistics_spec.rb'
- 'spec/features/admin/admin_users_impersonation_tokens_spec.rb' - 'spec/features/admin/admin_users_impersonation_tokens_spec.rb'
- 'spec/features/admin/admin_users_spec.rb' - 'spec/features/admin/admin_users_spec.rb'
- 'spec/features/boards/sidebar_spec.rb' - 'spec/features/boards/sidebar_spec.rb'
@ -975,6 +1131,7 @@ Rails/SaveBang:
- 'spec/features/dashboard/projects_spec.rb' - 'spec/features/dashboard/projects_spec.rb'
- 'spec/features/error_tracking/user_sees_error_index_spec.rb' - 'spec/features/error_tracking/user_sees_error_index_spec.rb'
- 'spec/features/groups/members/request_access_spec.rb' - 'spec/features/groups/members/request_access_spec.rb'
- 'spec/features/issuables/close_reopen_report_toggle_spec.rb'
- 'spec/features/issues/bulk_assignment_labels_spec.rb' - 'spec/features/issues/bulk_assignment_labels_spec.rb'
- 'spec/features/issues/gfm_autocomplete_spec.rb' - 'spec/features/issues/gfm_autocomplete_spec.rb'
- 'spec/features/issues/issue_sidebar_spec.rb' - 'spec/features/issues/issue_sidebar_spec.rb'
@ -989,30 +1146,45 @@ Rails/SaveBang:
- 'spec/features/merge_request/user_posts_diff_notes_spec.rb' - 'spec/features/merge_request/user_posts_diff_notes_spec.rb'
- 'spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb' - 'spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb'
- 'spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb' - 'spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb'
- 'spec/features/merge_request/user_sees_discussions_spec.rb'
- 'spec/features/merge_request/user_sees_merge_widget_spec.rb' - 'spec/features/merge_request/user_sees_merge_widget_spec.rb'
- 'spec/features/merge_request/user_sees_versions_spec.rb'
- 'spec/features/merge_requests/user_mass_updates_spec.rb' - 'spec/features/merge_requests/user_mass_updates_spec.rb'
- 'spec/features/profiles/emails_spec.rb' - 'spec/features/profiles/emails_spec.rb'
- 'spec/features/profiles/password_spec.rb' - 'spec/features/profiles/password_spec.rb'
- 'spec/features/profiles/personal_access_tokens_spec.rb' - 'spec/features/profiles/personal_access_tokens_spec.rb'
- 'spec/features/projects/features_visibility_spec.rb' - 'spec/features/projects/features_visibility_spec.rb'
- 'spec/features/projects/fork_spec.rb'
- 'spec/features/projects/jobs/permissions_spec.rb'
- 'spec/features/projects/jobs_spec.rb' - 'spec/features/projects/jobs_spec.rb'
- 'spec/features/projects/members/user_requests_access_spec.rb' - 'spec/features/projects/members/user_requests_access_spec.rb'
- 'spec/features/projects/pages_lets_encrypt_spec.rb' - 'spec/features/projects/pages_lets_encrypt_spec.rb'
- 'spec/features/projects/pages_spec.rb' - 'spec/features/projects/pages_spec.rb'
- 'spec/features/projects/pipelines/pipeline_spec.rb'
- 'spec/features/projects/pipelines/pipelines_spec.rb'
- 'spec/features/projects/remote_mirror_spec.rb' - 'spec/features/projects/remote_mirror_spec.rb'
- 'spec/features/projects/services/user_activates_slack_notifications_spec.rb' - 'spec/features/projects/services/user_activates_slack_notifications_spec.rb'
- 'spec/features/projects/settings/access_tokens_spec.rb' - 'spec/features/projects/settings/access_tokens_spec.rb'
- 'spec/features/projects/show/user_sees_deletion_failure_message_spec.rb' - 'spec/features/projects/show/user_sees_deletion_failure_message_spec.rb'
- 'spec/features/projects/user_sees_sidebar_spec.rb' - 'spec/features/projects/user_sees_sidebar_spec.rb'
- 'spec/features/projects/wiki/user_updates_wiki_page_spec.rb' - 'spec/features/projects/wiki/user_updates_wiki_page_spec.rb'
- 'spec/features/projects/wiki/user_views_wiki_page_spec.rb'
- 'spec/features/projects/wiki/users_views_asciidoc_page_with_includes_spec.rb' - 'spec/features/projects/wiki/users_views_asciidoc_page_with_includes_spec.rb'
- 'spec/features/runners_spec.rb'
- 'spec/features/security/project/internal_access_spec.rb'
- 'spec/features/security/project/private_access_spec.rb' - 'spec/features/security/project/private_access_spec.rb'
- 'spec/features/security/project/public_access_spec.rb'
- 'spec/features/users/login_spec.rb' - 'spec/features/users/login_spec.rb'
- 'spec/features/users/show_spec.rb' - 'spec/features/users/show_spec.rb'
- 'spec/finders/admin/projects_finder_spec.rb'
- 'spec/finders/autocomplete/move_to_project_finder_spec.rb' - 'spec/finders/autocomplete/move_to_project_finder_spec.rb'
- 'spec/finders/ci/pipelines_for_merge_request_finder_spec.rb'
- 'spec/finders/group_descendants_finder_spec.rb' - 'spec/finders/group_descendants_finder_spec.rb'
- 'spec/finders/group_projects_finder_spec.rb'
- 'spec/finders/issues_finder_spec.rb' - 'spec/finders/issues_finder_spec.rb'
- 'spec/finders/joined_groups_finder_spec.rb'
- 'spec/finders/merge_requests_finder_spec.rb' - 'spec/finders/merge_requests_finder_spec.rb'
- 'spec/finders/personal_projects_finder_spec.rb'
- 'spec/finders/projects_finder_spec.rb' - 'spec/finders/projects_finder_spec.rb'
- 'spec/finders/uploader_finder_spec.rb' - 'spec/finders/uploader_finder_spec.rb'
- 'spec/frontend/fixtures/issues.rb' - 'spec/frontend/fixtures/issues.rb'
@ -1021,6 +1193,11 @@ Rails/SaveBang:
- 'spec/graphql/mutations/merge_requests/set_wip_spec.rb' - 'spec/graphql/mutations/merge_requests/set_wip_spec.rb'
- 'spec/graphql/resolvers/boards_resolver_spec.rb' - 'spec/graphql/resolvers/boards_resolver_spec.rb'
- 'spec/helpers/appearances_helper_spec.rb' - 'spec/helpers/appearances_helper_spec.rb'
- 'spec/helpers/auto_devops_helper_spec.rb'
- 'spec/helpers/issuables_helper_spec.rb'
- 'spec/helpers/issues_helper_spec.rb'
- 'spec/helpers/members_helper_spec.rb'
- 'spec/helpers/notes_helper_spec.rb'
- 'spec/helpers/profiles_helper_spec.rb' - 'spec/helpers/profiles_helper_spec.rb'
- 'spec/helpers/projects/alert_management_helper_spec.rb' - 'spec/helpers/projects/alert_management_helper_spec.rb'
- 'spec/helpers/projects_helper_spec.rb' - 'spec/helpers/projects_helper_spec.rb'
@ -1030,18 +1207,23 @@ Rails/SaveBang:
- 'spec/lib/after_commit_queue_spec.rb' - 'spec/lib/after_commit_queue_spec.rb'
- 'spec/lib/backup/manager_spec.rb' - 'spec/lib/backup/manager_spec.rb'
- 'spec/lib/banzai/reference_parser/external_issue_parser_spec.rb' - 'spec/lib/banzai/reference_parser/external_issue_parser_spec.rb'
- 'spec/lib/banzai/reference_redactor_spec.rb'
- 'spec/lib/gitlab/alerting/alert_spec.rb'
- 'spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb' - 'spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb'
- 'spec/lib/gitlab/auth/ldap/user_spec.rb' - 'spec/lib/gitlab/auth/ldap/user_spec.rb'
- 'spec/lib/gitlab/auth/o_auth/user_spec.rb' - 'spec/lib/gitlab/auth/o_auth/user_spec.rb'
- 'spec/lib/gitlab/auth/saml/user_spec.rb' - 'spec/lib/gitlab/auth/saml/user_spec.rb'
- 'spec/lib/gitlab/auth_spec.rb' - 'spec/lib/gitlab/auth_spec.rb'
- 'spec/lib/gitlab/authorized_keys_spec.rb'
- 'spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb' - 'spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb'
- 'spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb' - 'spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb'
- 'spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb' - 'spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb'
- 'spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb' - 'spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb'
- 'spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb'
- 'spec/lib/gitlab/background_migration/digest_column_spec.rb' - 'spec/lib/gitlab/background_migration/digest_column_spec.rb'
- 'spec/lib/gitlab/background_migration/encrypt_columns_spec.rb' - 'spec/lib/gitlab/background_migration/encrypt_columns_spec.rb'
- 'spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb' - 'spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb'
- 'spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb'
- 'spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb' - 'spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb'
- 'spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb' - 'spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb'
- 'spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb' - 'spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb'
@ -1056,82 +1238,130 @@ Rails/SaveBang:
- 'spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb' - 'spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb'
- 'spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb' - 'spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb'
- 'spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb' - 'spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb'
- 'spec/lib/gitlab/background_migration/reset_merge_status_spec.rb'
- 'spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb' - 'spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb'
- 'spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb' - 'spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb'
- 'spec/lib/gitlab/bitbucket_server_import/importer_spec.rb' - 'spec/lib/gitlab/bitbucket_server_import/importer_spec.rb'
- 'spec/lib/gitlab/ci/ansi2json/style_spec.rb' - 'spec/lib/gitlab/ci/ansi2json/style_spec.rb'
- 'spec/lib/gitlab/ci/status/build/common_spec.rb'
- 'spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb' - 'spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb'
- 'spec/lib/gitlab/cycle_analytics/events_spec.rb' - 'spec/lib/gitlab/cycle_analytics/events_spec.rb'
- 'spec/lib/gitlab/database/custom_structure_spec.rb' - 'spec/lib/gitlab/database/custom_structure_spec.rb'
- 'spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb' - 'spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb'
- 'spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb' - 'spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb'
- 'spec/lib/gitlab/email/handler/create_note_handler_spec.rb'
- 'spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb'
- 'spec/lib/gitlab/gfm/reference_rewriter_spec.rb'
- 'spec/lib/gitlab/git/object_pool_spec.rb'
- 'spec/lib/gitlab/git/remote_mirror_spec.rb' - 'spec/lib/gitlab/git/remote_mirror_spec.rb'
- 'spec/lib/gitlab/git/repository_spec.rb'
- 'spec/lib/gitlab/git_access_spec.rb' - 'spec/lib/gitlab/git_access_spec.rb'
- 'spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb'
- 'spec/lib/gitlab/gitaly_client/repository_service_spec.rb' - 'spec/lib/gitlab/gitaly_client/repository_service_spec.rb'
- 'spec/lib/gitlab/import_export/avatar_saver_spec.rb' - 'spec/lib/gitlab/import_export/avatar_saver_spec.rb'
- 'spec/lib/gitlab/import_export/base/relation_factory_spec.rb'
- 'spec/lib/gitlab/import_export/design_repo_restorer_spec.rb' - 'spec/lib/gitlab/import_export/design_repo_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb' - 'spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb'
- 'spec/lib/gitlab/import_export/fork_spec.rb' - 'spec/lib/gitlab/import_export/fork_spec.rb'
- 'spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb' - 'spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb'
- 'spec/lib/gitlab/import_export/group/relation_factory_spec.rb'
- 'spec/lib/gitlab/import_export/group/tree_saver_spec.rb'
- 'spec/lib/gitlab/import_export/importer_spec.rb' - 'spec/lib/gitlab/import_export/importer_spec.rb'
- 'spec/lib/gitlab/import_export/lfs_restorer_spec.rb' - 'spec/lib/gitlab/import_export/lfs_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/lfs_saver_spec.rb' - 'spec/lib/gitlab/import_export/lfs_saver_spec.rb'
- 'spec/lib/gitlab/import_export/members_mapper_spec.rb' - 'spec/lib/gitlab/import_export/members_mapper_spec.rb'
- 'spec/lib/gitlab/import_export/project/relation_factory_spec.rb'
- 'spec/lib/gitlab/import_export/project/tree_restorer_spec.rb' - 'spec/lib/gitlab/import_export/project/tree_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/project/tree_saver_spec.rb' - 'spec/lib/gitlab/import_export/project/tree_saver_spec.rb'
- 'spec/lib/gitlab/import_export/repo_restorer_spec.rb' - 'spec/lib/gitlab/import_export/repo_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/saver_spec.rb' - 'spec/lib/gitlab/import_export/saver_spec.rb'
- 'spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb' - 'spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb'
- 'spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb' - 'spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb' - 'spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb'
- 'spec/lib/gitlab/import_export/uploads_manager_spec.rb' - 'spec/lib/gitlab/import_export/uploads_manager_spec.rb'
- 'spec/lib/gitlab/import_export/uploads_saver_spec.rb' - 'spec/lib/gitlab/import_export/uploads_saver_spec.rb'
- 'spec/lib/gitlab/import_export/wiki_restorer_spec.rb' - 'spec/lib/gitlab/import_export/wiki_restorer_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/importer_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb'
- 'spec/lib/gitlab/lets_encrypt/client_spec.rb' - 'spec/lib/gitlab/lets_encrypt/client_spec.rb'
- 'spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb' - 'spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb'
- 'spec/lib/gitlab/markdown_cache/redis/store_spec.rb' - 'spec/lib/gitlab/markdown_cache/redis/store_spec.rb'
- 'spec/lib/gitlab/middleware/go_spec.rb'
- 'spec/lib/gitlab/shard_health_cache_spec.rb' - 'spec/lib/gitlab/shard_health_cache_spec.rb'
- 'spec/lib/mattermost/command_spec.rb'
- 'spec/lib/mattermost/session_spec.rb'
- 'spec/lib/mattermost/team_spec.rb'
- 'spec/mailers/notify_spec.rb' - 'spec/mailers/notify_spec.rb'
- 'spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb'
- 'spec/migrations/20200122123016_backfill_project_settings_spec.rb' - 'spec/migrations/20200122123016_backfill_project_settings_spec.rb'
- 'spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb' - 'spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb'
- 'spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb' - 'spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb'
- 'spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb'
- 'spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb'
- 'spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb' - 'spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb'
- 'spec/migrations/20200526115436_dedup_mr_metrics_spec.rb'
- 'spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb'
- 'spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb'
- 'spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb' - 'spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb'
- 'spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb' - 'spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb'
- 'spec/migrations/backfill_imported_snippet_repositories_spec.rb'
- 'spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb' - 'spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb'
- 'spec/migrations/backfill_snippet_repositories_spec.rb'
- 'spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb' - 'spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb'
- 'spec/migrations/enqueue_reset_merge_status_second_run_spec.rb'
- 'spec/migrations/enqueue_reset_merge_status_spec.rb'
- 'spec/migrations/fill_file_store_lfs_objects_spec.rb' - 'spec/migrations/fill_file_store_lfs_objects_spec.rb'
- 'spec/migrations/fill_store_uploads_spec.rb' - 'spec/migrations/fill_store_uploads_spec.rb'
- 'spec/migrations/fix_null_type_labels_spec.rb' - 'spec/migrations/fix_null_type_labels_spec.rb'
- 'spec/migrations/fix_pool_repository_source_project_id_spec.rb' - 'spec/migrations/fix_pool_repository_source_project_id_spec.rb'
- 'spec/migrations/fix_projects_without_project_feature_spec.rb'
- 'spec/migrations/fix_projects_without_prometheus_services_spec.rb'
- 'spec/migrations/fix_wrong_pages_access_level_spec.rb' - 'spec/migrations/fix_wrong_pages_access_level_spec.rb'
- 'spec/migrations/insert_project_hooks_plan_limits_spec.rb' - 'spec/migrations/insert_project_hooks_plan_limits_spec.rb'
- 'spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb' - 'spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb'
- 'spec/migrations/move_limits_from_plans_spec.rb'
- 'spec/migrations/populate_project_statistics_packages_size_spec.rb'
- 'spec/migrations/schedule_link_lfs_objects_projects_spec.rb' - 'spec/migrations/schedule_link_lfs_objects_projects_spec.rb'
- 'spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb'
- 'spec/migrations/seed_repository_storages_weighted_spec.rb'
- 'spec/models/appearance_spec.rb' - 'spec/models/appearance_spec.rb'
- 'spec/models/application_record_spec.rb'
- 'spec/models/application_setting_spec.rb' - 'spec/models/application_setting_spec.rb'
- 'spec/models/ci/build_metadata_spec.rb'
- 'spec/models/ci/build_spec.rb' - 'spec/models/ci/build_spec.rb'
- 'spec/models/ci/build_trace_chunk_spec.rb'
- 'spec/models/ci/instance_variable_spec.rb' - 'spec/models/ci/instance_variable_spec.rb'
- 'spec/models/ci/legacy_stage_spec.rb'
- 'spec/models/ci/persistent_ref_spec.rb'
- 'spec/models/ci/pipeline_spec.rb' - 'spec/models/ci/pipeline_spec.rb'
- 'spec/models/ci/runner_spec.rb' - 'spec/models/ci/runner_spec.rb'
- 'spec/models/clusters/applications/helm_spec.rb' - 'spec/models/clusters/applications/helm_spec.rb'
- 'spec/models/commit_spec.rb'
- 'spec/models/commit_status_spec.rb'
- 'spec/models/concerns/avatarable_spec.rb' - 'spec/models/concerns/avatarable_spec.rb'
- 'spec/models/concerns/bulk_insertable_associations_spec.rb' - 'spec/models/concerns/bulk_insertable_associations_spec.rb'
- 'spec/models/concerns/cache_markdown_field_spec.rb' - 'spec/models/concerns/cache_markdown_field_spec.rb'
- 'spec/models/concerns/case_sensitivity_spec.rb'
- 'spec/models/concerns/featurable_spec.rb' - 'spec/models/concerns/featurable_spec.rb'
- 'spec/models/concerns/issuable_spec.rb' - 'spec/models/concerns/issuable_spec.rb'
- 'spec/models/concerns/mentionable_spec.rb' - 'spec/models/concerns/mentionable_spec.rb'
- 'spec/models/concerns/milestoneable_spec.rb' - 'spec/models/concerns/milestoneable_spec.rb'
- 'spec/models/concerns/milestoneish_spec.rb'
- 'spec/models/concerns/routable_spec.rb' - 'spec/models/concerns/routable_spec.rb'
- 'spec/models/concerns/subscribable_spec.rb' - 'spec/models/concerns/subscribable_spec.rb'
- 'spec/models/concerns/token_authenticatable_spec.rb' - 'spec/models/concerns/token_authenticatable_spec.rb'
- 'spec/models/container_repository_spec.rb' - 'spec/models/container_repository_spec.rb'
- 'spec/models/cycle_analytics/issue_spec.rb' - 'spec/models/cycle_analytics/issue_spec.rb'
- 'spec/models/cycle_analytics/plan_spec.rb' - 'spec/models/cycle_analytics/plan_spec.rb'
- 'spec/models/cycle_analytics/production_spec.rb'
- 'spec/models/deploy_keys_project_spec.rb' - 'spec/models/deploy_keys_project_spec.rb'
- 'spec/models/deploy_token_spec.rb' - 'spec/models/deploy_token_spec.rb'
- 'spec/models/deployment_spec.rb' - 'spec/models/deployment_spec.rb'
- 'spec/models/design_management/version_spec.rb' - 'spec/models/design_management/version_spec.rb'
- 'spec/models/diff_discussion_spec.rb'
- 'spec/models/diff_note_spec.rb' - 'spec/models/diff_note_spec.rb'
- 'spec/models/email_spec.rb' - 'spec/models/email_spec.rb'
- 'spec/models/environment_spec.rb' - 'spec/models/environment_spec.rb'
@ -1143,7 +1373,9 @@ Rails/SaveBang:
- 'spec/models/hooks/system_hook_spec.rb' - 'spec/models/hooks/system_hook_spec.rb'
- 'spec/models/hooks/web_hook_spec.rb' - 'spec/models/hooks/web_hook_spec.rb'
- 'spec/models/identity_spec.rb' - 'spec/models/identity_spec.rb'
- 'spec/models/issue/metrics_spec.rb'
- 'spec/models/issue_spec.rb' - 'spec/models/issue_spec.rb'
- 'spec/models/jira_import_state_spec.rb'
- 'spec/models/key_spec.rb' - 'spec/models/key_spec.rb'
- 'spec/models/lfs_objects_project_spec.rb' - 'spec/models/lfs_objects_project_spec.rb'
- 'spec/models/member_spec.rb' - 'spec/models/member_spec.rb'
@ -1155,12 +1387,18 @@ Rails/SaveBang:
- 'spec/models/note_spec.rb' - 'spec/models/note_spec.rb'
- 'spec/models/notification_setting_spec.rb' - 'spec/models/notification_setting_spec.rb'
- 'spec/models/pages_domain_spec.rb' - 'spec/models/pages_domain_spec.rb'
- 'spec/models/project_auto_devops_spec.rb'
- 'spec/models/project_feature_spec.rb'
- 'spec/models/project_services/bamboo_service_spec.rb' - 'spec/models/project_services/bamboo_service_spec.rb'
- 'spec/models/project_services/buildkite_service_spec.rb'
- 'spec/models/project_services/jira_service_spec.rb' - 'spec/models/project_services/jira_service_spec.rb'
- 'spec/models/project_services/packagist_service_spec.rb'
- 'spec/models/project_services/pipelines_email_service_spec.rb' - 'spec/models/project_services/pipelines_email_service_spec.rb'
- 'spec/models/project_services/teamcity_service_spec.rb' - 'spec/models/project_services/teamcity_service_spec.rb'
- 'spec/models/project_spec.rb' - 'spec/models/project_spec.rb'
- 'spec/models/project_team_spec.rb' - 'spec/models/project_team_spec.rb'
- 'spec/models/protectable_dropdown_spec.rb'
- 'spec/models/redirect_route_spec.rb'
- 'spec/models/release_spec.rb' - 'spec/models/release_spec.rb'
- 'spec/models/remote_mirror_spec.rb' - 'spec/models/remote_mirror_spec.rb'
- 'spec/models/resource_milestone_event_spec.rb' - 'spec/models/resource_milestone_event_spec.rb'
@ -1171,47 +1409,77 @@ Rails/SaveBang:
- 'spec/models/upload_spec.rb' - 'spec/models/upload_spec.rb'
- 'spec/models/user_preference_spec.rb' - 'spec/models/user_preference_spec.rb'
- 'spec/models/user_spec.rb' - 'spec/models/user_spec.rb'
- 'spec/models/user_status_spec.rb'
- 'spec/models/wiki_page/meta_spec.rb' - 'spec/models/wiki_page/meta_spec.rb'
- 'spec/models/wiki_page_spec.rb' - 'spec/models/wiki_page_spec.rb'
- 'spec/policies/ci/build_policy_spec.rb' - 'spec/policies/ci/build_policy_spec.rb'
- 'spec/policies/ci/pipeline_policy_spec.rb' - 'spec/policies/ci/pipeline_policy_spec.rb'
- 'spec/policies/ci/pipeline_schedule_policy_spec.rb' - 'spec/policies/ci/pipeline_schedule_policy_spec.rb'
- 'spec/policies/group_policy_spec.rb'
- 'spec/policies/issue_policy_spec.rb'
- 'spec/policies/merge_request_policy_spec.rb' - 'spec/policies/merge_request_policy_spec.rb'
- 'spec/policies/project_policy_spec.rb' - 'spec/policies/project_policy_spec.rb'
- 'spec/presenters/ci/build_runner_presenter_spec.rb'
- 'spec/presenters/ci/trigger_presenter_spec.rb'
- 'spec/presenters/packages/conan/package_presenter_spec.rb'
- 'spec/requests/api/access_requests_spec.rb'
- 'spec/requests/api/boards_spec.rb' - 'spec/requests/api/boards_spec.rb'
- 'spec/requests/api/branches_spec.rb'
- 'spec/requests/api/ci/runner_spec.rb'
- 'spec/requests/api/commit_statuses_spec.rb'
- 'spec/requests/api/conan_packages_spec.rb'
- 'spec/requests/api/deployments_spec.rb' - 'spec/requests/api/deployments_spec.rb'
- 'spec/requests/api/environments_spec.rb' - 'spec/requests/api/environments_spec.rb'
- 'spec/requests/api/go_proxy_spec.rb'
- 'spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb' - 'spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb'
- 'spec/requests/api/graphql/user_query_spec.rb' - 'spec/requests/api/graphql/user_query_spec.rb'
- 'spec/requests/api/graphql_spec.rb' - 'spec/requests/api/graphql_spec.rb'
- 'spec/requests/api/group_import_spec.rb'
- 'spec/requests/api/group_milestones_spec.rb' - 'spec/requests/api/group_milestones_spec.rb'
- 'spec/requests/api/internal/base_spec.rb' - 'spec/requests/api/internal/base_spec.rb'
- 'spec/requests/api/issues/get_group_issues_spec.rb' - 'spec/requests/api/issues/get_group_issues_spec.rb'
- 'spec/requests/api/issues/post_projects_issues_spec.rb'
- 'spec/requests/api/jobs_spec.rb' - 'spec/requests/api/jobs_spec.rb'
- 'spec/requests/api/labels_spec.rb' - 'spec/requests/api/labels_spec.rb'
- 'spec/requests/api/maven_packages_spec.rb'
- 'spec/requests/api/members_spec.rb' - 'spec/requests/api/members_spec.rb'
- 'spec/requests/api/merge_request_diffs_spec.rb' - 'spec/requests/api/merge_request_diffs_spec.rb'
- 'spec/requests/api/merge_requests_spec.rb' - 'spec/requests/api/merge_requests_spec.rb'
- 'spec/requests/api/notes_spec.rb'
- 'spec/requests/api/pages/internal_access_spec.rb'
- 'spec/requests/api/pages/private_access_spec.rb'
- 'spec/requests/api/pages/public_access_spec.rb'
- 'spec/requests/api/pipeline_schedules_spec.rb' - 'spec/requests/api/pipeline_schedules_spec.rb'
- 'spec/requests/api/project_import_spec.rb' - 'spec/requests/api/project_import_spec.rb'
- 'spec/requests/api/project_milestones_spec.rb'
- 'spec/requests/api/projects_spec.rb' - 'spec/requests/api/projects_spec.rb'
- 'spec/requests/api/runners_spec.rb'
- 'spec/requests/api/snippets_spec.rb' - 'spec/requests/api/snippets_spec.rb'
- 'spec/requests/git_http_spec.rb' - 'spec/requests/git_http_spec.rb'
- 'spec/requests/lfs_http_spec.rb'
- 'spec/requests/profiles/notifications_controller_spec.rb' - 'spec/requests/profiles/notifications_controller_spec.rb'
- 'spec/requests/projects/cycle_analytics_events_spec.rb' - 'spec/requests/projects/cycle_analytics_events_spec.rb'
- 'spec/serializers/environment_status_entity_spec.rb' - 'spec/serializers/environment_status_entity_spec.rb'
- 'spec/serializers/issue_entity_spec.rb'
- 'spec/serializers/job_entity_spec.rb'
- 'spec/serializers/merge_request_poll_widget_entity_spec.rb' - 'spec/serializers/merge_request_poll_widget_entity_spec.rb'
- 'spec/serializers/merge_request_widget_entity_spec.rb' - 'spec/serializers/merge_request_widget_entity_spec.rb'
- 'spec/services/auth/container_registry_authentication_service_spec.rb'
- 'spec/services/auto_merge/base_service_spec.rb'
- 'spec/services/auto_merge_service_spec.rb'
- 'spec/services/ci/create_cross_project_pipeline_service_spec.rb'
- 'spec/services/ci/create_pipeline_service_spec.rb' - 'spec/services/ci/create_pipeline_service_spec.rb'
- 'spec/services/ci/register_job_service_spec.rb' - 'spec/services/ci/register_job_service_spec.rb'
- 'spec/services/ci/retry_build_service_spec.rb' - 'spec/services/ci/retry_build_service_spec.rb'
- 'spec/services/ci/update_runner_service_spec.rb'
- 'spec/services/clusters/update_service_spec.rb'
- 'spec/services/deployments/after_create_service_spec.rb' - 'spec/services/deployments/after_create_service_spec.rb'
- 'spec/services/design_management/generate_image_versions_service_spec.rb' - 'spec/services/design_management/generate_image_versions_service_spec.rb'
- 'spec/services/discussions/resolve_service_spec.rb'
- 'spec/services/draft_notes/destroy_service_spec.rb' - 'spec/services/draft_notes/destroy_service_spec.rb'
- 'spec/services/emails/confirm_service_spec.rb' - 'spec/services/emails/confirm_service_spec.rb'
- 'spec/services/groups/destroy_service_spec.rb' - 'spec/services/groups/destroy_service_spec.rb'
- 'spec/services/groups/import_export/import_service_spec.rb' - 'spec/services/groups/import_export/import_service_spec.rb'
- 'spec/services/issuable/bulk_update_service_spec.rb'
- 'spec/services/issuable/clone/attributes_rewriter_spec.rb' - 'spec/services/issuable/clone/attributes_rewriter_spec.rb'
- 'spec/services/issuable/common_system_notes_service_spec.rb' - 'spec/services/issuable/common_system_notes_service_spec.rb'
- 'spec/services/issues/close_service_spec.rb' - 'spec/services/issues/close_service_spec.rb'
@ -1221,6 +1489,7 @@ Rails/SaveBang:
- 'spec/services/issues/update_service_spec.rb' - 'spec/services/issues/update_service_spec.rb'
- 'spec/services/labels/promote_service_spec.rb' - 'spec/services/labels/promote_service_spec.rb'
- 'spec/services/members/destroy_service_spec.rb' - 'spec/services/members/destroy_service_spec.rb'
- 'spec/services/merge_requests/build_service_spec.rb'
- 'spec/services/merge_requests/conflicts/list_service_spec.rb' - 'spec/services/merge_requests/conflicts/list_service_spec.rb'
- 'spec/services/merge_requests/create_service_spec.rb' - 'spec/services/merge_requests/create_service_spec.rb'
- 'spec/services/merge_requests/merge_service_spec.rb' - 'spec/services/merge_requests/merge_service_spec.rb'
@ -1230,11 +1499,16 @@ Rails/SaveBang:
- 'spec/services/milestones/destroy_service_spec.rb' - 'spec/services/milestones/destroy_service_spec.rb'
- 'spec/services/milestones/promote_service_spec.rb' - 'spec/services/milestones/promote_service_spec.rb'
- 'spec/services/milestones/transfer_service_spec.rb' - 'spec/services/milestones/transfer_service_spec.rb'
- 'spec/services/notes/create_service_spec.rb'
- 'spec/services/notification_recipients/build_service_spec.rb' - 'spec/services/notification_recipients/build_service_spec.rb'
- 'spec/services/notification_service_spec.rb' - 'spec/services/notification_service_spec.rb'
- 'spec/services/packages/conan/create_package_file_service_spec.rb'
- 'spec/services/projects/after_rename_service_spec.rb' - 'spec/services/projects/after_rename_service_spec.rb'
- 'spec/services/projects/autocomplete_service_spec.rb'
- 'spec/services/projects/create_service_spec.rb' - 'spec/services/projects/create_service_spec.rb'
- 'spec/services/projects/destroy_service_spec.rb'
- 'spec/services/projects/fork_service_spec.rb' - 'spec/services/projects/fork_service_spec.rb'
- 'spec/services/projects/hashed_storage/base_attachment_service_spec.rb'
- 'spec/services/projects/move_access_service_spec.rb' - 'spec/services/projects/move_access_service_spec.rb'
- 'spec/services/projects/move_project_group_links_service_spec.rb' - 'spec/services/projects/move_project_group_links_service_spec.rb'
- 'spec/services/projects/overwrite_project_service_spec.rb' - 'spec/services/projects/overwrite_project_service_spec.rb'
@ -1243,6 +1517,8 @@ Rails/SaveBang:
- 'spec/services/projects/update_pages_service_spec.rb' - 'spec/services/projects/update_pages_service_spec.rb'
- 'spec/services/projects/update_service_spec.rb' - 'spec/services/projects/update_service_spec.rb'
- 'spec/services/quick_actions/interpret_service_spec.rb' - 'spec/services/quick_actions/interpret_service_spec.rb'
- 'spec/services/reset_project_cache_service_spec.rb'
- 'spec/services/resource_events/change_milestone_service_spec.rb'
- 'spec/services/system_hooks_service_spec.rb' - 'spec/services/system_hooks_service_spec.rb'
- 'spec/services/system_note_service_spec.rb' - 'spec/services/system_note_service_spec.rb'
- 'spec/services/system_notes/issuables_service_spec.rb' - 'spec/services/system_notes/issuables_service_spec.rb'
@ -1250,42 +1526,71 @@ Rails/SaveBang:
- 'spec/services/todos/destroy/confidential_issue_service_spec.rb' - 'spec/services/todos/destroy/confidential_issue_service_spec.rb'
- 'spec/services/users/destroy_service_spec.rb' - 'spec/services/users/destroy_service_spec.rb'
- 'spec/services/users/repair_ldap_blocked_service_spec.rb' - 'spec/services/users/repair_ldap_blocked_service_spec.rb'
- 'spec/services/verify_pages_domain_service_spec.rb'
- 'spec/sidekiq/cron/job_gem_dependency_spec.rb'
- 'spec/support/helpers/cycle_analytics_helpers.rb' - 'spec/support/helpers/cycle_analytics_helpers.rb'
- 'spec/support/helpers/design_management_test_helpers.rb'
- 'spec/support/helpers/jira_service_helper.rb' - 'spec/support/helpers/jira_service_helper.rb'
- 'spec/support/helpers/login_helpers.rb' - 'spec/support/helpers/login_helpers.rb'
- 'spec/support/helpers/notification_helpers.rb' - 'spec/support/helpers/notification_helpers.rb'
- 'spec/support/helpers/stub_action_cable_connection.rb'
- 'spec/support/helpers/stub_object_storage.rb' - 'spec/support/helpers/stub_object_storage.rb'
- 'spec/support/migrations_helpers/cluster_helpers.rb' - 'spec/support/migrations_helpers/cluster_helpers.rb'
- 'spec/support/migrations_helpers/namespaces_helper.rb'
- 'spec/support/migrations_helpers/track_untracked_uploads_helpers.rb' - 'spec/support/migrations_helpers/track_untracked_uploads_helpers.rb'
- 'spec/support/shared_contexts/email_shared_context.rb'
- 'spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb' - 'spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb'
- 'spec/support/shared_contexts/mailers/notify_shared_context.rb' - 'spec/support/shared_contexts/mailers/notify_shared_context.rb'
- 'spec/support/shared_examples/controllers/cache_control_shared_examples.rb'
- 'spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb' - 'spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb'
- 'spec/support/shared_examples/controllers/sessionless_auth_controller_shared_examples.rb' - 'spec/support/shared_examples/controllers/sessionless_auth_controller_shared_examples.rb'
- 'spec/support/shared_examples/features/editable_merge_request_shared_examples.rb' - 'spec/support/shared_examples/features/editable_merge_request_shared_examples.rb'
- 'spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb' - 'spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb'
- 'spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb'
- 'spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb'
- 'spec/support/shared_examples/models/concerns/limitable_shared_examples.rb'
- 'spec/support/shared_examples/models/concerns/timebox_shared_examples.rb'
- 'spec/support/shared_examples/models/diff_note_after_commit_shared_examples.rb' - 'spec/support/shared_examples/models/diff_note_after_commit_shared_examples.rb'
- 'spec/support/shared_examples/models/member_shared_examples.rb' - 'spec/support/shared_examples/models/member_shared_examples.rb'
- 'spec/support/shared_examples/models/members_notifications_shared_example.rb' - 'spec/support/shared_examples/models/members_notifications_shared_example.rb'
- 'spec/support/shared_examples/models/mentionable_shared_examples.rb' - 'spec/support/shared_examples/models/mentionable_shared_examples.rb'
- 'spec/support/shared_examples/models/project_latest_successful_build_for_shared_examples.rb'
- 'spec/support/shared_examples/models/relative_positioning_shared_examples.rb' - 'spec/support/shared_examples/models/relative_positioning_shared_examples.rb'
- 'spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb' - 'spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb'
- 'spec/support/shared_examples/models/update_project_statistics_shared_examples.rb' - 'spec/support/shared_examples/models/update_project_statistics_shared_examples.rb'
- 'spec/support/shared_examples/models/with_uploads_shared_examples.rb' - 'spec/support/shared_examples/models/with_uploads_shared_examples.rb'
- 'spec/support/shared_examples/policies/project_policy_shared_examples.rb'
- 'spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb' - 'spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb'
- 'spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb' - 'spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/award_emoji_todo_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/boards_shared_examples.rb' - 'spec/support/shared_examples/requests/api/boards_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb'
- 'spec/support/shared_examples/serializers/note_entity_shared_examples.rb'
- 'spec/support/shared_examples/services/common_system_notes_shared_examples.rb' - 'spec/support/shared_examples/services/common_system_notes_shared_examples.rb'
- 'spec/support/shared_examples/services/issuable_shared_examples.rb' - 'spec/support/shared_examples/services/issuable_shared_examples.rb'
- 'spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb'
- 'spec/tasks/gitlab/web_hook_rake_spec.rb' - 'spec/tasks/gitlab/web_hook_rake_spec.rb'
- 'spec/uploaders/file_uploader_spec.rb'
- 'spec/uploaders/object_storage_spec.rb'
- 'spec/views/notify/changed_milestone_email.html.haml_spec.rb'
- 'spec/views/projects/imports/new.html.haml_spec.rb' - 'spec/views/projects/imports/new.html.haml_spec.rb'
- 'spec/views/projects/merge_requests/show.html.haml_spec.rb' - 'spec/views/projects/merge_requests/show.html.haml_spec.rb'
- 'spec/views/shared/_label_row.html.haml_spec.rb' - 'spec/views/shared/_label_row.html.haml_spec.rb'
- 'spec/workers/concerns/project_export_options_spec.rb'
- 'spec/workers/gitlab/import/stuck_project_import_jobs_worker_spec.rb'
- 'spec/workers/gitlab/jira_import/stuck_jira_import_jobs_worker_spec.rb'
- 'spec/workers/migrate_external_diffs_worker_spec.rb' - 'spec/workers/migrate_external_diffs_worker_spec.rb'
- 'spec/workers/namespaceless_project_destroy_worker_spec.rb' - 'spec/workers/namespaceless_project_destroy_worker_spec.rb'
- 'spec/workers/namespaces/root_statistics_worker_spec.rb'
- 'spec/workers/pages_domain_verification_worker_spec.rb' - 'spec/workers/pages_domain_verification_worker_spec.rb'
- 'spec/workers/process_commit_worker_spec.rb'
- 'spec/workers/propagate_integration_worker_spec.rb'
- 'spec/workers/propagate_service_template_worker_spec.rb' - 'spec/workers/propagate_service_template_worker_spec.rb'
- 'spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb' - 'spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb'
- 'spec/workers/repository_check/single_repository_worker_spec.rb'
- 'spec/workers/repository_cleanup_worker_spec.rb' - 'spec/workers/repository_cleanup_worker_spec.rb'
- 'spec/workers/repository_import_worker_spec.rb' - 'spec/workers/repository_import_worker_spec.rb'
- 'spec/workers/repository_update_remote_mirror_worker_spec.rb' - 'spec/workers/repository_update_remote_mirror_worker_spec.rb'
- 'spec/workers/stuck_ci_jobs_worker_spec.rb'
- 'spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb'

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@ require_relative 'lib/gitlab/danger/request_helper'
danger.import_plugin('danger/plugins/helper.rb') danger.import_plugin('danger/plugins/helper.rb')
danger.import_plugin('danger/plugins/roulette.rb') danger.import_plugin('danger/plugins/roulette.rb')
danger.import_plugin('danger/plugins/changelog.rb') danger.import_plugin('danger/plugins/changelog.rb')
danger.import_plugin('danger/plugins/sidekiq_queues.rb')
return if helper.release_automation? return if helper.release_automation?

View file

@ -1 +1 @@
13.1.6 13.2.1

View file

@ -1 +1 @@
2.3.0 2.4.0

View file

@ -1 +1 @@
1.18.0 1.21.0

View file

@ -1 +1 @@
8.35.0 8.37.0

32
Gemfile
View file

@ -19,7 +19,7 @@ gem 'default_value_for', '~> 3.3.0'
gem 'pg', '~> 1.1' gem 'pg', '~> 1.1'
gem 'rugged', '~> 0.28' gem 'rugged', '~> 0.28'
gem 'grape-path-helpers', '~> 1.2' gem 'grape-path-helpers', '~> 1.3'
gem 'faraday', '~> 0.12' gem 'faraday', '~> 0.12'
gem 'marginalia', '~> 1.8.0' gem 'marginalia', '~> 1.8.0'
@ -66,7 +66,7 @@ gem 'u2f', '~> 0.2.1'
gem 'validates_hostname', '~> 1.0.10' gem 'validates_hostname', '~> 1.0.10'
gem 'rubyzip', '~> 2.0.0', require: 'zip' gem 'rubyzip', '~> 2.0.0', require: 'zip'
# GitLab Pages letsencrypt support # GitLab Pages letsencrypt support
gem 'acme-client', '~> 2.0.5' gem 'acme-client', '~> 2.0', '>= 2.0.6'
# Browser detection # Browser detection
gem 'browser', '~> 2.5' gem 'browser', '~> 2.5'
@ -81,7 +81,9 @@ gem 'gitlab_omniauth-ldap', '~> 2.1.1', require: 'omniauth-ldap'
gem 'net-ldap' gem 'net-ldap'
# API # API
gem 'grape', '~> 1.1.0' # Locked at Grape v1.4.0 until https://github.com/ruby-grape/grape/pull/2088 is merged
# Remove config/initializers/grape_patch.rb
gem 'grape', '= 1.4.0'
gem 'grape-entity', '~> 0.7.1' gem 'grape-entity', '~> 0.7.1'
gem 'rack-cors', '~> 1.0.6', require: 'rack/cors' gem 'rack-cors', '~> 1.0.6', require: 'rack/cors'
@ -140,6 +142,7 @@ gem 'deckar01-task_list', '2.3.1'
gem 'gitlab-markup', '~> 1.7.1' gem 'gitlab-markup', '~> 1.7.1'
gem 'github-markup', '~> 1.7.0', require: 'github/markup' gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'commonmarker', '~> 0.20' gem 'commonmarker', '~> 0.20'
gem 'kramdown', '~> 2.2.1'
gem 'RedCloth', '~> 4.3.2' gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~> 6.1.2' gem 'rdoc', '~> 6.1.2'
gem 'org-ruby', '~> 0.9.12' gem 'org-ruby', '~> 0.9.12'
@ -148,7 +151,7 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 2.0.10' gem 'asciidoctor', '~> 2.0.10'
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
gem 'asciidoctor-plantuml', '~> 0.0.12' gem 'asciidoctor-plantuml', '~> 0.0.12'
gem 'rouge', '~> 3.19.0' gem 'rouge', '~> 3.21.0'
gem 'truncato', '~> 0.7.11' gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0' gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.10.9' gem 'nokogiri', '~> 1.10.9'
@ -163,6 +166,8 @@ gem 'diff_match_patch', '~> 0.1.0'
# Application server # Application server
gem 'rack', '~> 2.0.9' gem 'rack', '~> 2.0.9'
# https://github.com/sharpstone/rack-timeout/blob/master/README.md#rails-apps-manually
gem 'rack-timeout', '~> 0.5.1', require: 'rack/timeout/base'
group :unicorn do group :unicorn do
gem 'unicorn', '~> 5.5' gem 'unicorn', '~> 5.5'
@ -172,7 +177,6 @@ end
group :puma do group :puma do
gem 'gitlab-puma', '~> 4.3.3.gitlab.2', require: false gem 'gitlab-puma', '~> 4.3.3.gitlab.2', require: false
gem 'gitlab-puma_worker_killer', '~> 0.1.1.gitlab.1', require: false gem 'gitlab-puma_worker_killer', '~> 0.1.1.gitlab.1', require: false
gem 'rack-timeout', require: false
end end
# State machine # State machine
@ -242,7 +246,9 @@ gem 'slack-messenger', '~> 2.3.3'
gem 'hangouts-chat', '~> 0.0.5' gem 'hangouts-chat', '~> 0.0.5'
# Asana integration # Asana integration
gem 'asana', '~> 0.9' # asana 0.10.1 needs faraday 1.0
# https://gitlab.com/gitlab-org/gitlab/-/issues/224296
gem 'asana', '0.10.0'
# FogBugz integration # FogBugz integration
gem 'ruby-fogbugz', '~> 0.2.1' gem 'ruby-fogbugz', '~> 0.2.1'
@ -300,7 +306,7 @@ gem 'sentry-raven', '~> 2.9'
gem 'premailer-rails', '~> 1.10.3' gem 'premailer-rails', '~> 1.10.3'
# LabKit: Tracing and Correlation # LabKit: Tracing and Correlation
gem 'gitlab-labkit', '0.12.0' gem 'gitlab-labkit', '0.12.1'
# I18n # I18n
gem 'ruby_parser', '~> 3.8', require: false gem 'ruby_parser', '~> 3.8', require: false
@ -331,10 +337,9 @@ group :development do
gem 'danger', '~> 6.0', require: false gem 'danger', '~> 6.0', require: false
gem 'letter_opener_web', '~> 1.3.4' gem 'letter_opener_web', '~> 1.3.4'
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
# Better errors handler # Better errors handler
gem 'better_errors', '~> 2.5.0' gem 'better_errors', '~> 2.7.1'
gem 'binding_of_caller', '~> 0.8.0' gem 'binding_of_caller', '~> 0.8.0'
# thin instead webrick # thin instead webrick
@ -361,7 +366,7 @@ group :development, :test do
gem 'spring', '~> 2.0.0' gem 'spring', '~> 2.0.0'
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'gitlab-styles', '~> 4.2.0', require: false gem 'gitlab-styles', '~> 4.3.0', require: false
# Pin these dependencies, otherwise a new rule could break the CI pipelines # Pin these dependencies, otherwise a new rule could break the CI pipelines
gem 'rubocop', '~> 0.82.0' gem 'rubocop', '~> 0.82.0'
gem 'rubocop-performance', '~> 1.5.2' gem 'rubocop-performance', '~> 1.5.2'
@ -370,6 +375,7 @@ group :development, :test do
gem 'scss_lint', '~> 0.56.0', require: false gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.34.0', require: false gem 'haml_lint', '~> 0.34.0', require: false
gem 'simplecov', '~> 0.18.5', require: false gem 'simplecov', '~> 0.18.5', require: false
gem 'simplecov-cobertura', '~> 1.3.1', require: false
gem 'bundler-audit', '~> 0.6.1', require: false gem 'bundler-audit', '~> 0.6.1', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false gem 'benchmark-ips', '~> 2.3.0', require: false
@ -383,6 +389,8 @@ group :development, :test do
gem 'png_quantizator', '~> 0.2.1', require: false gem 'png_quantizator', '~> 0.2.1', require: false
gem 'parallel', '~> 1.19', require: false gem 'parallel', '~> 1.19', require: false
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
end end
# Gems required in omnibus-gitlab pipeline # Gems required in omnibus-gitlab pipeline
@ -452,7 +460,7 @@ group :ed25519 do
end end
# Gitaly GRPC protocol definitions # Gitaly GRPC protocol definitions
gem 'gitaly', '~> 13.1.0.pre.rc1' gem 'gitaly', '~> 13.2.0.pre.rc2'
gem 'grpc', '~> 1.24.0' gem 'grpc', '~> 1.24.0'
@ -497,3 +505,5 @@ gem 'valid_email', '~> 0.1'
# JSON # JSON
gem 'json', '~> 2.3.0' gem 'json', '~> 2.3.0'
gem 'json-schema', '~> 2.8.0' gem 'json-schema', '~> 2.8.0'
gem 'oj', '~> 3.10.6'
gem 'multi_json', '~> 1.14.1'

View file

@ -4,8 +4,8 @@ GEM
RedCloth (4.3.2) RedCloth (4.3.2)
abstract_type (0.0.7) abstract_type (0.0.7)
ace-rails-ap (4.1.2) ace-rails-ap (4.1.2)
acme-client (2.0.5) acme-client (2.0.6)
faraday (~> 0.9, >= 0.9.1) faraday (>= 0.17, < 2.0.0)
actioncable (6.0.3.1) actioncable (6.0.3.1)
actionpack (= 6.0.3.1) actionpack (= 6.0.3.1)
nio4r (~> 2.0) nio4r (~> 2.0)
@ -76,7 +76,7 @@ GEM
apollo_upload_server (2.0.0.beta.3) apollo_upload_server (2.0.0.beta.3)
graphql (>= 1.8) graphql (>= 1.8)
rails (>= 4.2) rails (>= 4.2)
asana (0.9.3) asana (0.10.0)
faraday (~> 0.9) faraday (~> 0.9)
faraday_middleware (~> 0.9) faraday_middleware (~> 0.9)
faraday_middleware-multi_json (~> 0.0) faraday_middleware-multi_json (~> 0.0)
@ -103,10 +103,6 @@ GEM
aws-sdk-core (= 2.11.374) aws-sdk-core (= 2.11.374)
aws-sigv4 (1.1.0) aws-sigv4 (1.1.0)
aws-eventstream (~> 1.0, >= 1.0.2) aws-eventstream (~> 1.0, >= 1.0.2)
axiom-types (0.1.1)
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
babosa (1.0.2) babosa (1.0.2)
base32 (0.3.2) base32 (0.3.2)
batch-loader (1.4.0) batch-loader (1.4.0)
@ -115,7 +111,7 @@ GEM
benchmark-ips (2.3.0) benchmark-ips (2.3.0)
benchmark-memory (0.1.2) benchmark-memory (0.1.2)
memory_profiler (~> 0.9) memory_profiler (~> 0.9)
better_errors (2.5.0) better_errors (2.7.1)
coderay (>= 1.0.0) coderay (>= 1.0.0)
erubi (>= 1.0.0) erubi (>= 1.0.0)
rack (>= 0.9.0) rack (>= 0.9.0)
@ -164,8 +160,6 @@ GEM
nap nap
open4 (~> 1.3) open4 (~> 1.3)
coderay (1.1.2) coderay (1.1.2)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
colored2 (3.1.2) colored2 (3.1.2)
commonmarker (0.20.1) commonmarker (0.20.1)
ruby-enum (~> 0.5) ruby-enum (~> 0.5)
@ -221,8 +215,6 @@ GEM
ruby-statistics (>= 2.1) ruby-statistics (>= 2.1)
thor (>= 0.19, < 2) thor (>= 0.19, < 2)
unicode_plot (>= 0.0.4, < 1.0.0) unicode_plot (>= 0.0.4, < 1.0.0)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
device_detector (1.0.0) device_detector (1.0.0)
devise (4.7.1) devise (4.7.1)
bcrypt (~> 3.0) bcrypt (~> 3.0)
@ -249,6 +241,28 @@ GEM
doorkeeper-openid_connect (1.6.3) doorkeeper-openid_connect (1.6.3)
doorkeeper (>= 5.0, < 5.2) doorkeeper (>= 5.0, < 5.2)
json-jwt (~> 1.6) json-jwt (~> 1.6)
dry-configurable (0.11.5)
concurrent-ruby (~> 1.0)
dry-core (~> 0.4, >= 0.4.7)
dry-equalizer (~> 0.2)
dry-container (0.7.2)
concurrent-ruby (~> 1.0)
dry-configurable (~> 0.1, >= 0.1.3)
dry-core (0.4.9)
concurrent-ruby (~> 1.0)
dry-equalizer (0.3.0)
dry-inflector (0.2.0)
dry-logic (1.0.6)
concurrent-ruby (~> 1.0)
dry-core (~> 0.2)
dry-equalizer (~> 0.2)
dry-types (1.4.0)
concurrent-ruby (~> 1.0)
dry-container (~> 0.3)
dry-core (~> 0.4, >= 0.4.4)
dry-equalizer (~> 0.3)
dry-inflector (~> 0.1, >= 0.1.2)
dry-logic (~> 1.0, >= 1.0.2)
ed25519 (1.2.4) ed25519 (1.2.4)
elasticsearch (6.8.0) elasticsearch (6.8.0)
elasticsearch-api (= 6.8.0) elasticsearch-api (= 6.8.0)
@ -290,7 +304,7 @@ GEM
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
faraday-http-cache (2.0.0) faraday-http-cache (2.0.0)
faraday (~> 0.8) faraday (~> 0.8)
faraday_middleware (0.12.2) faraday_middleware (0.14.0)
faraday (>= 0.7.4, < 1.0) faraday (>= 0.7.4, < 1.0)
faraday_middleware-aws-signers-v4 (0.1.7) faraday_middleware-aws-signers-v4 (0.1.7)
aws-sdk-resources (~> 2) aws-sdk-resources (~> 2)
@ -377,12 +391,12 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
git (1.5.0) git (1.5.0)
gitaly (13.1.0.pre.rc1) gitaly (13.2.0.pre.rc2)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-chronic (0.10.5) gitlab-chronic (0.10.5)
numerizer (~> 0.2) numerizer (~> 0.2)
gitlab-labkit (0.12.0) gitlab-labkit (0.12.1)
actionpack (>= 5.0.0, < 6.1.0) actionpack (>= 5.0.0, < 6.1.0)
activesupport (>= 5.0.0, < 6.1.0) activesupport (>= 5.0.0, < 6.1.0)
grpc (~> 1.19) grpc (~> 1.19)
@ -400,7 +414,7 @@ GEM
gitlab-puma (>= 2.7, < 5) gitlab-puma (>= 2.7, < 5)
gitlab-sidekiq-fetcher (0.5.2) gitlab-sidekiq-fetcher (0.5.2)
sidekiq (~> 5) sidekiq (~> 5)
gitlab-styles (4.2.0) gitlab-styles (4.3.0)
rubocop (~> 0.82.0) rubocop (~> 0.82.0)
rubocop-gitlab-security (~> 0.1.0) rubocop-gitlab-security (~> 0.1.0)
rubocop-performance (~> 1.5.2) rubocop-performance (~> 1.5.2)
@ -439,19 +453,19 @@ GEM
signet (~> 0.14) signet (~> 0.14)
gpgme (2.0.20) gpgme (2.0.20)
mini_portile2 (~> 2.3) mini_portile2 (~> 2.3)
grape (1.1.0) grape (1.4.0)
activesupport activesupport
builder builder
dry-types (>= 1.1)
mustermann-grape (~> 1.0.0) mustermann-grape (~> 1.0.0)
rack (>= 1.3.0) rack (>= 1.3.0)
rack-accept rack-accept
virtus (>= 1.0.0)
grape-entity (0.7.1) grape-entity (0.7.1)
activesupport (>= 4.0) activesupport (>= 4.0)
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
grape-path-helpers (1.2.0) grape-path-helpers (1.3.0)
activesupport activesupport
grape (~> 1.0) grape (~> 1.3)
rake (~> 12) rake (~> 12)
grape_logging (1.8.3) grape_logging (1.8.3)
grape grape
@ -575,7 +589,8 @@ GEM
kgio (2.11.3) kgio (2.11.3)
knapsack (1.17.0) knapsack (1.17.0)
rake rake
kramdown (2.1.0) kramdown (2.2.1)
rexml
kramdown-parser-gfm (1.1.0) kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0) kramdown (~> 2.0)
kubeclient (4.6.0) kubeclient (4.6.0)
@ -641,9 +656,10 @@ GEM
multi_xml (0.6.0) multi_xml (0.6.0)
multipart-post (2.1.1) multipart-post (2.1.1)
murmurhash3 (0.1.6) murmurhash3 (0.1.6)
mustermann (1.0.3) mustermann (1.1.1)
mustermann-grape (1.0.0) ruby2_keywords (~> 0.0.1)
mustermann (~> 1.0.0) mustermann-grape (1.0.1)
mustermann (>= 1.0.0)
nakayoshi_fork (0.0.4) nakayoshi_fork (0.0.4)
nap (1.1.0) nap (1.1.0)
nenv (0.3.0) nenv (0.3.0)
@ -671,6 +687,7 @@ GEM
octokit (4.15.0) octokit (4.15.0)
faraday (>= 0.9) faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.8.0, >= 0.5.3)
oj (3.10.6)
omniauth (1.9.0) omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0) hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3) rack (>= 1.6.2, < 3)
@ -801,7 +818,7 @@ GEM
rack rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rack-timeout (0.5.1) rack-timeout (0.5.2)
rails (6.0.3.1) rails (6.0.3.1)
actioncable (= 6.0.3.1) actioncable (= 6.0.3.1)
actionmailbox (= 6.0.3.1) actionmailbox (= 6.0.3.1)
@ -890,7 +907,7 @@ GEM
rexml (3.2.4) rexml (3.2.4)
rinku (2.0.0) rinku (2.0.0)
rotp (2.1.2) rotp (2.1.2)
rouge (3.19.0) rouge (3.21.0)
rqrcode (0.7.0) rqrcode (0.7.0)
chunky_png chunky_png
rqrcode-rails3 (0.1.7) rqrcode-rails3 (0.1.7)
@ -958,6 +975,7 @@ GEM
ruby-saml (1.7.2) ruby-saml (1.7.2)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
ruby-statistics (2.1.2) ruby-statistics (2.1.2)
ruby2_keywords (0.0.2)
ruby_dep (1.5.0) ruby_dep (1.5.0)
ruby_parser (3.13.1) ruby_parser (3.13.1)
sexp_processor (~> 4.9) sexp_processor (~> 4.9)
@ -1003,11 +1021,11 @@ GEM
shellany (0.0.1) shellany (0.0.1)
shoulda-matchers (4.0.1) shoulda-matchers (4.0.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
sidekiq (5.2.7) sidekiq (5.2.9)
connection_pool (~> 2.2, >= 2.2.2) connection_pool (~> 2.2, >= 2.2.2)
rack (>= 1.5.0) rack (~> 2.0)
rack-protection (>= 1.5.0) rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5) redis (>= 3.3.5, < 4.2)
sidekiq-cron (1.0.4) sidekiq-cron (1.0.4)
fugit (~> 1.1) fugit (~> 1.1)
sidekiq (>= 4.2.1) sidekiq (>= 4.2.1)
@ -1020,6 +1038,8 @@ GEM
simplecov (0.18.5) simplecov (0.18.5)
docile (~> 1.1) docile (~> 1.1)
simplecov-html (~> 0.11) simplecov-html (~> 0.11)
simplecov-cobertura (1.3.1)
simplecov (~> 0.8)
simplecov-html (0.12.2) simplecov-html (0.12.2)
sixarm_ruby_unaccent (1.2.0) sixarm_ruby_unaccent (1.2.0)
slack-messenger (2.3.3) slack-messenger (2.3.3)
@ -1119,11 +1139,6 @@ GEM
activerecord (>= 3.0) activerecord (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
version_sorter (2.2.4) version_sorter (2.2.4)
virtus (1.0.5)
axiom-types (~> 0.1)
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9)
vmstat (2.3.0) vmstat (2.3.0)
warden (1.2.8) warden (1.2.8)
rack (>= 2.0.6) rack (>= 2.0.6)
@ -1155,13 +1170,13 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
RedCloth (~> 4.3.2) RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0) ace-rails-ap (~> 4.1.0)
acme-client (~> 2.0.5) acme-client (~> 2.0, >= 2.0.6)
activerecord-explain-analyze (~> 0.1) activerecord-explain-analyze (~> 0.1)
acts-as-taggable-on (~> 6.0) acts-as-taggable-on (~> 6.0)
addressable (~> 2.7) addressable (~> 2.7)
akismet (~> 3.0) akismet (~> 3.0)
apollo_upload_server (~> 2.0.0.beta3) apollo_upload_server (~> 2.0.0.beta3)
asana (~> 0.9) asana (= 0.10.0)
asciidoctor (~> 2.0.10) asciidoctor (~> 2.0.10)
asciidoctor-include-ext (~> 0.3.1) asciidoctor-include-ext (~> 0.3.1)
asciidoctor-plantuml (~> 0.0.12) asciidoctor-plantuml (~> 0.0.12)
@ -1175,7 +1190,7 @@ DEPENDENCIES
bcrypt_pbkdf (~> 1.0) bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0) benchmark-ips (~> 2.3.0)
benchmark-memory (~> 0.1) benchmark-memory (~> 0.1)
better_errors (~> 2.5.0) better_errors (~> 2.7.1)
binding_of_caller (~> 0.8.0) binding_of_caller (~> 0.8.0)
bootsnap (~> 1.4.6) bootsnap (~> 1.4.6)
bootstrap_form (~> 4.2.0) bootstrap_form (~> 4.2.0)
@ -1236,10 +1251,10 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly (~> 13.1.0.pre.rc1) gitaly (~> 13.2.0.pre.rc2)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5) gitlab-chronic (~> 0.10.5)
gitlab-labkit (= 0.12.0) gitlab-labkit (= 0.12.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
gitlab-mail_room (~> 0.0.6) gitlab-mail_room (~> 0.0.6)
gitlab-markup (~> 1.7.1) gitlab-markup (~> 1.7.1)
@ -1247,16 +1262,16 @@ DEPENDENCIES
gitlab-puma (~> 4.3.3.gitlab.2) gitlab-puma (~> 4.3.3.gitlab.2)
gitlab-puma_worker_killer (~> 0.1.1.gitlab.1) gitlab-puma_worker_killer (~> 0.1.1.gitlab.1)
gitlab-sidekiq-fetcher (= 0.5.2) gitlab-sidekiq-fetcher (= 0.5.2)
gitlab-styles (~> 4.2.0) gitlab-styles (~> 4.3.0)
gitlab_chronic_duration (~> 0.10.6.2) gitlab_chronic_duration (~> 0.10.6.2)
gitlab_omniauth-ldap (~> 2.1.1) gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2) gon (~> 6.2)
google-api-client (~> 0.33) google-api-client (~> 0.33)
google-protobuf (~> 3.8.0) google-protobuf (~> 3.8.0)
gpgme (~> 2.0.19) gpgme (~> 2.0.19)
grape (~> 1.1.0) grape (= 1.4.0)
grape-entity (~> 0.7.1) grape-entity (~> 0.7.1)
grape-path-helpers (~> 1.2) grape-path-helpers (~> 1.3)
grape_logging (~> 1.7) grape_logging (~> 1.7)
graphiql-rails (~> 1.4.10) graphiql-rails (~> 1.4.10)
graphql (~> 1.10.5) graphql (~> 1.10.5)
@ -1282,6 +1297,7 @@ DEPENDENCIES
jwt (~> 2.1.0) jwt (~> 2.1.0)
kaminari (~> 1.0) kaminari (~> 1.0)
knapsack (~> 1.17) knapsack (~> 1.17)
kramdown (~> 2.2.1)
kubeclient (~> 4.6.0) kubeclient (~> 4.6.0)
letter_opener_web (~> 1.3.4) letter_opener_web (~> 1.3.4)
license_finder (~> 5.4) license_finder (~> 5.4)
@ -1297,6 +1313,7 @@ DEPENDENCIES
mimemagic (~> 0.3.2) mimemagic (~> 0.3.2)
mini_magick mini_magick
minitest (~> 5.11.0) minitest (~> 5.11.0)
multi_json (~> 1.14.1)
nakayoshi_fork (~> 0.0.4) nakayoshi_fork (~> 0.0.4)
net-ldap net-ldap
net-ntp net-ntp
@ -1304,6 +1321,7 @@ DEPENDENCIES
nokogiri (~> 1.10.9) nokogiri (~> 1.10.9)
oauth2 (~> 1.4) oauth2 (~> 1.4)
octokit (~> 4.15) octokit (~> 4.15)
oj (~> 3.10.6)
omniauth (~> 1.8) omniauth (~> 1.8)
omniauth-auth0 (~> 2.0.0) omniauth-auth0 (~> 2.0.0)
omniauth-authentiq (~> 0.3.3) omniauth-authentiq (~> 0.3.3)
@ -1335,7 +1353,7 @@ DEPENDENCIES
rack-cors (~> 1.0.6) rack-cors (~> 1.0.6)
rack-oauth2 (~> 1.9.3) rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0) rack-proxy (~> 0.6.0)
rack-timeout rack-timeout (~> 0.5.1)
rails (~> 6.0.3.1) rails (~> 6.0.3.1)
rails-controller-testing rails-controller-testing
rails-i18n (~> 6.0) rails-i18n (~> 6.0)
@ -1352,7 +1370,7 @@ DEPENDENCIES
request_store (~> 1.5) request_store (~> 1.5)
responders (~> 3.0) responders (~> 3.0)
retriable (~> 3.1.2) retriable (~> 3.1.2)
rouge (~> 3.19.0) rouge (~> 3.21.0)
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-parameterized rspec-parameterized
rspec-rails (~> 4.0.0) rspec-rails (~> 4.0.0)
@ -1380,6 +1398,7 @@ DEPENDENCIES
sidekiq-cron (~> 1.0) sidekiq-cron (~> 1.0)
simple_po_parser (~> 1.1.2) simple_po_parser (~> 1.1.2)
simplecov (~> 0.18.5) simplecov (~> 0.18.5)
simplecov-cobertura (~> 1.3.1)
slack-messenger (~> 2.3.3) slack-messenger (~> 2.3.3)
snowplow-tracker (~> 0.6.1) snowplow-tracker (~> 0.6.1)
spring (~> 2.0.0) spring (~> 2.0.0)

View file

@ -1 +1 @@
13.1.6 13.2.1

View file

@ -0,0 +1 @@
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a"><stop offset="0" stop-color="#344563"/><stop offset=".68" stop-color="#637088"/><stop offset="1" stop-color="#7a869a"/></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="14.873" x2="5.739" xlink:href="#a" y1="15.883" y2="10.625"/><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="-168376" x2="-168177" xlink:href="#a" y1="-6722.4" y2="-6493.53"/><path d="m1.517 11.68c-.15.243-.32.53-.453.757a.462.462 0 0 0 .155.63l3.013 1.863a.466.466 0 0 0 .645-.158l.445-.735c1.197-1.97 2.402-1.732 4.571-.703l2.995 1.424a.468.468 0 0 0 .626-.232l1.448-3.24a.466.466 0 0 0 -.229-.606c-.633-.298-1.89-.89-3.016-1.434-4.089-2.004-7.551-1.86-10.2 2.434z" fill="url(#b)"/><path d="m14.479 4.315c.15-.243.324-.53.456-.758a.46.46 0 0 0 -.158-.63l-3.025-1.857a.464.464 0 0 0 -.644.158 22.81 22.81 0 0 1 -.446.736c-1.196 1.972-2.4 1.733-4.567.703l-2.993-1.424a.468.468 0 0 0 -.625.231l-1.437 3.246a.46.46 0 0 0 .225.607c.633.298 1.892.89 3.014 1.435 4.097 1.99 7.556 1.858 10.199-2.446z" fill="url(#c)"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1 @@
<svg id="Logos" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="80" height="80" viewBox="0 0 80 80"><defs><style>.cls-1{fill:#7a869a;}.cls-2{fill:url(#linear-gradient);}.cls-3{fill:url(#linear-gradient-2);}</style><linearGradient id="linear-gradient" x1="38.11" y1="18.54" x2="23.17" y2="33.48" gradientUnits="userSpaceOnUse"><stop offset="0.18" stop-color="#344563"/><stop offset="1" stop-color="#7a869a"/></linearGradient><linearGradient id="linear-gradient-2" x1="42.07" y1="61.47" x2="56.98" y2="46.55" xlink:href="#linear-gradient"/></defs><title>jira software-icon-gradient-neutral</title><path class="cls-1" d="M74.18,38,43,6.9l-3-3h0L16.58,27.32h0L5.86,38a2.86,2.86,0,0,0,0,4.05L27.28,63.51,40,76.25,63.47,52.81l.36-.36L74.18,42.09A2.86,2.86,0,0,0,74.18,38ZM40,50.77l-10.7-10.7L40,29.37l10.7,10.7Z"/><path class="cls-2" d="M40,29.37A18,18,0,0,1,40,4L16.54,27.37,29.28,40.11,40,29.37Z"/><path class="cls-3" d="M50.75,40,40,50.77a18,18,0,0,1,0,25.48h0L63.5,52.78Z"/></svg>

After

Width:  |  Height:  |  Size: 1,016 B

View file

@ -12,18 +12,21 @@ import {
GlTable, GlTable,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import query from '../graphql/queries/details.query.graphql'; import alertQuery from '../graphql/queries/details.query.graphql';
import sidebarStatusQuery from '../graphql/queries/sidebar_status.query.graphql';
import { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user'; import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
import initUserPopovers from '~/user_popovers'; import initUserPopovers from '~/user_popovers';
import { ALERTS_SEVERITY_LABELS, trackAlertsDetailsViewsOptions } from '../constants'; import { ALERTS_SEVERITY_LABELS, trackAlertsDetailsViewsOptions } from '../constants';
import createIssueQuery from '../graphql/mutations/create_issue_from_alert.graphql'; import createIssueMutation from '../graphql/mutations/create_issue_from_alert.mutation.graphql';
import toggleSidebarStatusMutation from '../graphql/mutations/toggle_sidebar_status.mutation.graphql';
import { visitUrl, joinPaths } from '~/lib/utils/url_utility'; import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import { toggleContainerClasses } from '~/lib/utils/dom_utils'; import { toggleContainerClasses } from '~/lib/utils/dom_utils';
import SystemNote from './system_notes/system_note.vue'; import SystemNote from './system_notes/system_note.vue';
import AlertSidebar from './alert_sidebar.vue'; import AlertSidebar from './alert_sidebar.vue';
import AlertMetrics from './alert_metrics.vue';
const containerEl = document.querySelector('.page-with-contextual-sidebar'); const containerEl = document.querySelector('.page-with-contextual-sidebar');
@ -34,6 +37,7 @@ export default {
), ),
fullAlertDetailsTitle: s__('AlertManagement|Alert details'), fullAlertDetailsTitle: s__('AlertManagement|Alert details'),
overviewTitle: s__('AlertManagement|Overview'), overviewTitle: s__('AlertManagement|Overview'),
metricsTitle: s__('AlertManagement|Metrics'),
reportedAt: s__('AlertManagement|Reported %{when}'), reportedAt: s__('AlertManagement|Reported %{when}'),
reportedAtWithTool: s__('AlertManagement|Reported %{when} by %{tool}'), reportedAtWithTool: s__('AlertManagement|Reported %{when} by %{tool}'),
}, },
@ -51,25 +55,29 @@ export default {
TimeAgoTooltip, TimeAgoTooltip,
AlertSidebar, AlertSidebar,
SystemNote, SystemNote,
AlertMetrics,
},
inject: {
projectPath: {
default: '',
}, },
props: {
alertId: { alertId: {
type: String, type: String,
required: true, default: '',
}, },
projectPath: { projectId: {
type: String, type: String,
required: true, default: '',
}, },
projectIssuesPath: { projectIssuesPath: {
type: String, type: String,
required: true, default: '',
}, },
}, },
apollo: { apollo: {
alert: { alert: {
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK, fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
query, query: alertQuery,
variables() { variables() {
return { return {
fullPath: this.projectPath, fullPath: this.projectPath,
@ -84,15 +92,18 @@ export default {
Sentry.captureException(error); Sentry.captureException(error);
}, },
}, },
sidebarStatus: {
query: sidebarStatusQuery,
},
}, },
data() { data() {
return { return {
alert: null, alert: null,
errored: false, errored: false,
sidebarStatus: false,
isErrorDismissed: false, isErrorDismissed: false,
createIssueError: '', createIssueError: '',
issueCreationInProgress: false, issueCreationInProgress: false,
sidebarCollapsed: false,
sidebarErrorMessage: '', sidebarErrorMessage: '',
}; };
}, },
@ -128,10 +139,10 @@ export default {
this.sidebarErrorMessage = ''; this.sidebarErrorMessage = '';
}, },
toggleSidebar() { toggleSidebar() {
this.sidebarCollapsed = !this.sidebarCollapsed; this.$apollo.mutate({ mutation: toggleSidebarStatusMutation });
toggleContainerClasses(containerEl, { toggleContainerClasses(containerEl, {
'right-sidebar-collapsed': this.sidebarCollapsed, 'right-sidebar-collapsed': !this.sidebarStatus,
'right-sidebar-expanded': !this.sidebarCollapsed, 'right-sidebar-expanded': this.sidebarStatus,
}); });
}, },
handleAlertSidebarError(errorMessage) { handleAlertSidebarError(errorMessage) {
@ -143,7 +154,7 @@ export default {
this.$apollo this.$apollo
.mutate({ .mutate({
mutation: createIssueQuery, mutation: createIssueMutation,
variables: { variables: {
iid: this.alert.iid, iid: this.alert.iid,
projectPath: this.projectPath, projectPath: this.projectPath,
@ -169,9 +180,6 @@ export default {
const { category, action } = trackAlertsDetailsViewsOptions; const { category, action } = trackAlertsDetailsViewsOptions;
Tracking.event(category, action); Tracking.event(category, action);
}, },
alertRefresh() {
this.$apollo.queries.alert.refetch();
},
}, },
}; };
</script> </script>
@ -179,7 +187,7 @@ export default {
<template> <template>
<div> <div>
<gl-alert v-if="showErrorMsg" variant="danger" @dismiss="dismissError"> <gl-alert v-if="showErrorMsg" variant="danger" @dismiss="dismissError">
{{ sidebarErrorMessage || $options.i18n.errorMsg }} <p v-html="sidebarErrorMessage || $options.i18n.errorMsg"></p>
</gl-alert> </gl-alert>
<gl-alert <gl-alert
v-if="createIssueError" v-if="createIssueError"
@ -193,10 +201,10 @@ export default {
<div <div
v-if="alert" v-if="alert"
class="alert-management-details gl-relative" class="alert-management-details gl-relative"
:class="{ 'pr-sm-8': sidebarCollapsed }" :class="{ 'pr-sm-8': sidebarStatus }"
> >
<div <div
class="gl-display-flex gl-justify-content-space-between gl-align-items-baseline gl-px-1 py-3 py-md-4 gl-border-b-1 gl-border-b-gray-200 gl-border-b-solid flex-column flex-sm-row" class="gl-display-flex gl-justify-content-space-between gl-align-items-baseline gl-px-1 py-3 py-md-4 gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid flex-column flex-sm-row"
> >
<div <div
data-testid="alert-header" data-testid="alert-header"
@ -324,14 +332,14 @@ export default {
</template> </template>
</gl-table> </gl-table>
</gl-tab> </gl-tab>
<gl-tab data-testId="metricsTab" :title="$options.i18n.metricsTitle">
<alert-metrics :dashboard-url="alert.metricsDashboardUrl" />
</gl-tab>
</gl-tabs> </gl-tabs>
<alert-sidebar <alert-sidebar
:project-path="projectPath"
:alert="alert" :alert="alert"
:sidebar-collapsed="sidebarCollapsed"
@alert-refresh="alertRefresh"
@toggle-sidebar="toggleSidebar" @toggle-sidebar="toggleSidebar"
@alert-sidebar-error="handleAlertSidebarError" @alert-error="handleAlertSidebarError"
/> />
</div> </div>
</div> </div>

View file

@ -0,0 +1,90 @@
<script>
import { GlEmptyState, GlButton } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
i18n: {
emptyState: {
opsgenie: {
title: s__('AlertManagement|Opsgenie is enabled'),
info: s__(
'AlertManagement|You have enabled the Opsgenie integration. Your alerts will be visible directly in Opsgenie.',
),
buttonText: s__('AlertManagement|View alerts in Opsgenie'),
},
gitlab: {
title: s__('AlertManagement|Surface alerts in GitLab'),
info: s__(
'AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents.',
),
buttonText: s__('AlertManagement|Authorize external service'),
},
},
moreInformation: s__('AlertManagement|More information'),
},
components: {
GlEmptyState,
GlButton,
},
props: {
enableAlertManagementPath: {
type: String,
required: true,
},
userCanEnableAlertManagement: {
type: Boolean,
required: true,
},
emptyAlertSvgPath: {
type: String,
required: true,
},
opsgenieMvcEnabled: {
type: Boolean,
required: false,
default: false,
},
opsgenieMvcTargetUrl: {
type: String,
required: false,
default: '',
},
},
computed: {
emptyState() {
return {
...(this.opsgenieMvcEnabled
? this.$options.i18n.emptyState.opsgenie
: this.$options.i18n.emptyState.gitlab),
link: this.opsgenieMvcEnabled ? this.opsgenieMvcTargetUrl : this.enableAlertManagementPath,
};
},
alertsCanBeEnabled() {
return this.userCanEnableAlertManagement || this.opsgenieMvcEnabled;
},
},
};
</script>
<template>
<div>
<gl-empty-state :title="emptyState.title" :svg-path="emptyAlertSvgPath">
<template #description>
<div class="gl-display-block">
<span>{{ emptyState.info }}</span>
<a
v-if="!opsgenieMvcEnabled"
href="/help/user/project/operations/alert_management.html"
target="_blank"
>
{{ $options.i18n.moreInformation }}
</a>
</div>
<div v-if="alertsCanBeEnabled" class="gl-display-block center gl-pt-4">
<gl-button category="primary" variant="success" :href="emptyState.link">
{{ emptyState.buttonText }}
</gl-button>
</div>
</template>
</gl-empty-state>
</div>
</template>

View file

@ -0,0 +1,75 @@
<script>
import Tracking from '~/tracking';
import { trackAlertListViewsOptions } from '../constants';
import AlertManagementEmptyState from './alert_management_empty_state.vue';
import AlertManagementTable from './alert_management_table.vue';
export default {
components: {
AlertManagementEmptyState,
AlertManagementTable,
},
props: {
projectPath: {
type: String,
required: true,
},
alertManagementEnabled: {
type: Boolean,
required: true,
},
enableAlertManagementPath: {
type: String,
required: true,
},
populatingAlertsHelpUrl: {
type: String,
required: true,
},
userCanEnableAlertManagement: {
type: Boolean,
required: true,
},
emptyAlertSvgPath: {
type: String,
required: true,
},
opsgenieMvcEnabled: {
type: Boolean,
required: false,
default: false,
},
opsgenieMvcTargetUrl: {
type: String,
required: false,
default: '',
},
},
mounted() {
this.trackPageViews();
},
methods: {
trackPageViews() {
const { category, action } = trackAlertListViewsOptions;
Tracking.event(category, action);
},
},
};
</script>
<template>
<div>
<alert-management-table
v-if="alertManagementEnabled"
:populating-alerts-help-url="populatingAlertsHelpUrl"
:project-path="projectPath"
/>
<alert-management-empty-state
v-else
:empty-alert-svg-path="emptyAlertSvgPath"
:enable-alert-management-path="enableAlertManagementPath"
:user-can-enable-alert-management="userCanEnableAlertManagement"
:opsgenie-mvc-enabled="opsgenieMvcEnabled"
:opsgenie-mvc-target-url="opsgenieMvcTargetUrl"
/>
</div>
</template>

View file

@ -1,23 +1,24 @@
<script> <script>
import { import {
GlEmptyState,
GlDeprecatedButton,
GlLoadingIcon, GlLoadingIcon,
GlTable, GlTable,
GlAlert, GlAlert,
GlIcon, GlIcon,
GlDropdown, GlLink,
GlDropdownItem,
GlTabs, GlTabs,
GlTab, GlTab,
GlBadge, GlBadge,
GlPagination, GlPagination,
GlSearchBoxByType,
GlSprintf,
} from '@gitlab/ui'; } from '@gitlab/ui';
import createFlash from '~/flash'; import { __, s__ } from '~/locale';
import { s__ } from '~/locale'; import { debounce, trim } from 'lodash';
import { joinPaths, visitUrl } from '~/lib/utils/url_utility'; import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
import { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { convertToSnakeCase } from '~/lib/utils/text_utility';
import Tracking from '~/tracking';
import getAlerts from '../graphql/queries/get_alerts.query.graphql'; import getAlerts from '../graphql/queries/get_alerts.query.graphql';
import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql'; import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql';
import { import {
@ -27,11 +28,10 @@ import {
trackAlertListViewsOptions, trackAlertListViewsOptions,
trackAlertStatusUpdateOptions, trackAlertStatusUpdateOptions,
} from '../constants'; } from '../constants';
import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql'; import AlertStatus from './alert_status.vue';
import { convertToSnakeCase } from '~/lib/utils/text_utility';
import Tracking from '~/tracking';
const tdClass = 'table-col gl-display-flex d-md-table-cell gl-align-items-center'; const tdClass =
'table-col gl-display-flex d-md-table-cell gl-align-items-center gl-white-space-nowrap';
const thClass = 'gl-hover-bg-blue-50'; const thClass = 'gl-hover-bg-blue-50';
const bodyTrClass = const bodyTrClass =
'gl-border-1 gl-border-t-solid gl-border-gray-100 gl-hover-bg-blue-50 gl-hover-cursor-pointer gl-hover-border-b-solid gl-hover-border-blue-200'; 'gl-border-1 gl-border-t-solid gl-border-gray-100 gl-hover-bg-blue-50 gl-hover-cursor-pointer gl-hover-border-b-solid gl-hover-border-blue-200';
@ -44,54 +44,57 @@ const initialPaginationState = {
lastPageSize: null, lastPageSize: null,
}; };
const TWELVE_HOURS_IN_MS = 12 * 60 * 60 * 1000;
export default { export default {
i18n: { i18n: {
noAlertsMsg: s__( noAlertsMsg: s__(
"AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page.", 'AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list.',
), ),
errorMsg: s__( errorMsg: s__(
"AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear.", "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear.",
), ),
searchPlaceholder: __('Search or filter results...'),
}, },
fields: [ fields: [
{ {
key: 'severity', key: 'severity',
label: s__('AlertManagement|Severity'), label: s__('AlertManagement|Severity'),
tdClass: `${tdClass} rounded-top text-capitalize`, tdClass: `${tdClass} rounded-top text-capitalize`,
thClass, thClass: `${thClass} gl-w-eighth`,
sortable: true, sortable: true,
}, },
{ {
key: 'startedAt', key: 'startedAt',
label: s__('AlertManagement|Start time'), label: s__('AlertManagement|Start time'),
thClass: `${thClass} js-started-at`, thClass: `${thClass} js-started-at w-15p`,
tdClass,
sortable: true,
},
{
key: 'endedAt',
label: s__('AlertManagement|End time'),
thClass,
tdClass, tdClass,
sortable: true, sortable: true,
}, },
{ {
key: 'title', key: 'title',
label: s__('AlertManagement|Alert'), label: s__('AlertManagement|Alert'),
thClass: `${thClass} w-30p gl-pointer-events-none`, thClass: `gl-pointer-events-none`,
tdClass, tdClass,
sortable: false,
}, },
{ {
key: 'eventCount', key: 'eventCount',
label: s__('AlertManagement|Events'), label: s__('AlertManagement|Events'),
thClass: `${thClass} text-right gl-pr-9 w-3rem`, thClass: `${thClass} text-right gl-w-12`,
tdClass: `${tdClass} text-md-right`, tdClass: `${tdClass} text-md-right`,
sortable: true, sortable: true,
}, },
{
key: 'issue',
label: s__('AlertManagement|Issue'),
thClass: 'gl-w-12 gl-pointer-events-none',
tdClass,
sortable: false,
},
{ {
key: 'assignees', key: 'assignees',
label: s__('AlertManagement|Assignees'), label: s__('AlertManagement|Assignees'),
thClass: 'gl-w-eighth gl-pointer-events-none',
tdClass, tdClass,
}, },
{ {
@ -102,46 +105,29 @@ export default {
sortable: true, sortable: true,
}, },
], ],
statuses: {
TRIGGERED: s__('AlertManagement|Triggered'),
ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
RESOLVED: s__('AlertManagement|Resolved'),
},
severityLabels: ALERTS_SEVERITY_LABELS, severityLabels: ALERTS_SEVERITY_LABELS,
statusTabs: ALERTS_STATUS_TABS, statusTabs: ALERTS_STATUS_TABS,
components: { components: {
GlEmptyState,
GlLoadingIcon, GlLoadingIcon,
GlTable, GlTable,
GlAlert, GlAlert,
GlDeprecatedButton,
TimeAgo, TimeAgo,
GlDropdown,
GlDropdownItem,
GlIcon, GlIcon,
GlLink,
GlTabs, GlTabs,
GlTab, GlTab,
GlBadge, GlBadge,
GlPagination, GlPagination,
GlSearchBoxByType,
GlSprintf,
AlertStatus,
}, },
props: { props: {
projectPath: { projectPath: {
type: String, type: String,
required: true, required: true,
}, },
alertManagementEnabled: { populatingAlertsHelpUrl: {
type: Boolean,
required: true,
},
enableAlertManagementPath: {
type: String,
required: true,
},
userCanEnableAlertManagement: {
type: Boolean,
required: true,
},
emptyAlertSvgPath: {
type: String, type: String,
required: true, required: true,
}, },
@ -152,6 +138,7 @@ export default {
query: getAlerts, query: getAlerts,
variables() { variables() {
return { return {
searchTerm: this.searchTerm,
projectPath: this.projectPath, projectPath: this.projectPath,
statuses: this.statusFilter, statuses: this.statusFilter,
sort: this.sort, sort: this.sort,
@ -164,9 +151,20 @@ export default {
update(data) { update(data) {
const { alertManagementAlerts: { nodes: list = [], pageInfo = {} } = {} } = const { alertManagementAlerts: { nodes: list = [], pageInfo = {} } = {} } =
data.project || {}; data.project || {};
const now = new Date();
const listWithData = list.map(alert => {
const then = new Date(alert.startedAt);
const diff = now - then;
return { return {
list, ...alert,
isNew: diff < TWELVE_HOURS_IN_MS,
};
});
return {
list: listWithData,
pageInfo, pageInfo,
}; };
}, },
@ -178,6 +176,7 @@ export default {
query: getAlertsCountByStatus, query: getAlertsCountByStatus,
variables() { variables() {
return { return {
searchTerm: this.searchTerm,
projectPath: this.projectPath, projectPath: this.projectPath,
}; };
}, },
@ -188,7 +187,9 @@ export default {
}, },
data() { data() {
return { return {
searchTerm: '',
errored: false, errored: false,
errorMessage: '',
isAlertDismissed: false, isAlertDismissed: false,
isErrorAlertDismissed: false, isErrorAlertDismissed: false,
sort: 'STARTED_AT_DESC', sort: 'STARTED_AT_DESC',
@ -203,7 +204,11 @@ export default {
computed: { computed: {
showNoAlertsMsg() { showNoAlertsMsg() {
return ( return (
!this.errored && !this.loading && this.alertsCount?.all === 0 && !this.isAlertDismissed !this.errored &&
!this.loading &&
this.alertsCount?.all === 0 &&
!this.searchTerm &&
!this.isAlertDismissed
); );
}, },
showErrorMsg() { showErrorMsg() {
@ -215,9 +220,6 @@ export default {
hasAlerts() { hasAlerts() {
return this.alerts?.list?.length; return this.alerts?.list?.length;
}, },
tbodyTrClass() {
return !this.loading && this.hasAlerts ? bodyTrClass : '';
},
showPaginationControls() { showPaginationControls() {
return Boolean(this.prevPage || this.nextPage); return Boolean(this.prevPage || this.nextPage);
}, },
@ -249,30 +251,13 @@ export default {
this.resetPagination(); this.resetPagination();
this.sort = `${sortingColumn}_${sortingDirection}`; this.sort = `${sortingColumn}_${sortingDirection}`;
}, },
updateAlertStatus(status, iid) { onInputChange: debounce(function debounceSearch(input) {
this.$apollo const trimmedInput = trim(input);
.mutate({ if (trimmedInput !== this.searchTerm) {
mutation: updateAlertStatus,
variables: {
iid,
status: status.toUpperCase(),
projectPath: this.projectPath,
},
})
.then(() => {
this.trackStatusUpdate(status);
this.$apollo.queries.alerts.refetch();
this.$apollo.queries.alertsCount.refetch();
this.resetPagination(); this.resetPagination();
}) this.searchTerm = trimmedInput;
.catch(() => { }
createFlash( }, 500),
s__(
'AlertManagement|There was an error while updating the status of the alert. Please try again.',
),
);
});
},
navigateToAlertDetails({ iid }) { navigateToAlertDetails({ iid }) {
return visitUrl(joinPaths(window.location.pathname, iid, 'details')); return visitUrl(joinPaths(window.location.pathname, iid, 'details'));
}, },
@ -290,6 +275,9 @@ export default {
? assignees.nodes[0]?.username ? assignees.nodes[0]?.username
: s__('AlertManagement|Unassigned'); : s__('AlertManagement|Unassigned');
}, },
getIssueLink(item) {
return joinPaths('/', this.projectPath, '-', 'issues', item.issueIid);
},
handlePageChange(page) { handlePageChange(page) {
const { startCursor, endCursor } = this.alerts.pageInfo; const { startCursor, endCursor } = this.alerts.pageInfo;
@ -312,20 +300,49 @@ export default {
resetPagination() { resetPagination() {
this.pagination = initialPaginationState; this.pagination = initialPaginationState;
}, },
tbodyTrClass(item) {
return {
[bodyTrClass]: !this.loading && this.hasAlerts,
'new-alert': item?.isNew,
};
},
handleAlertError(errorMessage) {
this.errored = true;
this.errorMessage = errorMessage;
},
dismissError() {
this.isErrorAlertDismissed = true;
this.errorMessage = '';
},
}, },
}; };
</script> </script>
<template> <template>
<div> <div>
<div v-if="alertManagementEnabled" class="alert-management-list"> <div class="alert-management-list">
<gl-alert v-if="showNoAlertsMsg" @dismiss="isAlertDismissed = true"> <gl-alert v-if="showNoAlertsMsg" @dismiss="isAlertDismissed = true">
{{ $options.i18n.noAlertsMsg }} <gl-sprintf :message="$options.i18n.noAlertsMsg">
<template #link="{ content }">
<gl-link
class="gl-display-inline-block"
:href="populatingAlertsHelpUrl"
target="_blank"
>
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</gl-alert> </gl-alert>
<gl-alert v-if="showErrorMsg" variant="danger" @dismiss="isErrorAlertDismissed = true"> <gl-alert
{{ $options.i18n.errorMsg }} v-if="showErrorMsg"
variant="danger"
data-testid="alert-error"
@dismiss="dismissError"
>
<p v-html="errorMessage || $options.i18n.errorMsg"></p>
</gl-alert> </gl-alert>
<gl-tabs @input="filterAlertsByStatus"> <gl-tabs content-class="gl-p-0" @input="filterAlertsByStatus">
<gl-tab v-for="tab in $options.statusTabs" :key="tab.status"> <gl-tab v-for="tab in $options.statusTabs" :key="tab.status">
<template slot="title"> <template slot="title">
<span>{{ tab.title }}</span> <span>{{ tab.title }}</span>
@ -336,11 +353,19 @@ export default {
</gl-tab> </gl-tab>
</gl-tabs> </gl-tabs>
<div class="gl-bg-gray-10 gl-p-5 gl-border-b-solid gl-border-b-1 gl-border-gray-100">
<gl-search-box-by-type
class="gl-bg-white"
:placeholder="$options.i18n.searchPlaceholder"
@input="onInputChange"
/>
</div>
<h4 class="d-block d-md-none my-3"> <h4 class="d-block d-md-none my-3">
{{ s__('AlertManagement|Alerts') }} {{ s__('AlertManagement|Alerts') }}
</h4> </h4>
<gl-table <gl-table
class="alert-management-table mt-3" class="alert-management-table"
:items="alerts ? alerts.list : []" :items="alerts ? alerts.list : []"
:fields="$options.fields" :fields="$options.fields"
:show-empty="true" :show-empty="true"
@ -352,6 +377,7 @@ export default {
:sort-desc.sync="sortDesc" :sort-desc.sync="sortDesc"
:sort-by.sync="sortBy" :sort-by.sync="sortBy"
sort-icon-left sort-icon-left
fixed
@row-clicked="navigateToAlertDetails" @row-clicked="navigateToAlertDetails"
@sort-changed="fetchSortedData" @sort-changed="fetchSortedData"
> >
@ -374,16 +400,19 @@ export default {
<time-ago v-if="item.startedAt" :time="item.startedAt" /> <time-ago v-if="item.startedAt" :time="item.startedAt" />
</template> </template>
<template #cell(endedAt)="{ item }">
<time-ago v-if="item.endedAt" :time="item.endedAt" />
</template>
<template #cell(eventCount)="{ item }"> <template #cell(eventCount)="{ item }">
{{ item.eventCount }} {{ item.eventCount }}
</template> </template>
<template #cell(title)="{ item }"> <template #cell(title)="{ item }">
<div class="gl-max-w-full text-truncate">{{ item.title }}</div> <div class="gl-max-w-full text-truncate" :title="item.title">{{ item.title }}</div>
</template>
<template #cell(issue)="{ item }">
<gl-link v-if="item.issueIid" data-testid="issueField" :href="getIssueLink(item)">
#{{ item.issueIid }}
</gl-link>
<div v-else data-testid="issueField">{{ s__('AlertManagement|None') }}</div>
</template> </template>
<template #cell(assignees)="{ item }"> <template #cell(assignees)="{ item }">
@ -393,22 +422,12 @@ export default {
</template> </template>
<template #cell(status)="{ item }"> <template #cell(status)="{ item }">
<gl-dropdown :text="$options.statuses[item.status]" class="w-100" right> <alert-status
<gl-dropdown-item :alert="item"
v-for="(label, field) in $options.statuses" :project-path="projectPath"
:key="field" :is-sidebar="false"
@click="updateAlertStatus(label, item.iid)" @alert-error="handleAlertError"
>
<span class="d-flex">
<gl-icon
class="flex-shrink-0 append-right-4"
:class="{ invisible: label.toUpperCase() !== item.status }"
name="mobile-issue-close"
/> />
{{ label }}
</span>
</gl-dropdown-item>
</gl-dropdown>
</template> </template>
<template #empty> <template #empty>
@ -426,36 +445,9 @@ export default {
:prev-page="prevPage" :prev-page="prevPage"
:next-page="nextPage" :next-page="nextPage"
align="center" align="center"
class="gl-pagination prepend-top-default" class="gl-pagination gl-mt-3"
@input="handlePageChange" @input="handlePageChange"
/> />
</div> </div>
<gl-empty-state
v-else
:title="s__('AlertManagement|Surface alerts in GitLab')"
:svg-path="emptyAlertSvgPath"
>
<template #description>
<div class="d-block">
<span>{{
s__(
'AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents.',
)
}}</span>
<a href="/help/user/project/operations/alert_management.html" target="_blank">
{{ s__('AlertManagement|More information') }}
</a>
</div>
<div v-if="userCanEnableAlertManagement" class="d-block center pt-4">
<gl-deprecated-button
category="primary"
variant="success"
:href="enableAlertManagementPath"
>
{{ s__('AlertManagement|Authorize external service') }}
</gl-deprecated-button>
</div>
</template>
</gl-empty-state>
</div> </div>
</template> </template>

View file

@ -0,0 +1,56 @@
<script>
import Vue from 'vue';
import Vuex from 'vuex';
import * as Sentry from '@sentry/browser';
Vue.use(Vuex);
export default {
props: {
dashboardUrl: {
type: String,
required: false,
default: '',
},
},
data() {
return {
metricEmbedComponent: null,
namespace: 'alertMetrics',
};
},
mounted() {
if (this.dashboardUrl) {
Promise.all([
import('~/monitoring/components/embeds/metric_embed.vue'),
import('~/monitoring/stores'),
])
.then(([{ default: MetricEmbed }, { monitoringDashboard }]) => {
this.$store = new Vuex.Store({
modules: {
[this.namespace]: monitoringDashboard,
},
});
this.metricEmbedComponent = MetricEmbed;
})
.catch(e => Sentry.captureException(e));
}
},
};
</script>
<template>
<div class="gl-py-3">
<div v-if="dashboardUrl" ref="metricsChart">
<component
:is="metricEmbedComponent"
v-if="metricEmbedComponent"
:dashboard-url="dashboardUrl"
:namespace="namespace"
/>
</div>
<div v-else ref="emptyState">
{{ s__("AlertManagement|Metrics weren't available in the alerts payload.") }}
</div>
</div>
</template>

View file

@ -4,6 +4,8 @@ import SidebarTodo from './sidebar/sidebar_todo.vue';
import SidebarStatus from './sidebar/sidebar_status.vue'; import SidebarStatus from './sidebar/sidebar_status.vue';
import SidebarAssignees from './sidebar/sidebar_assignees.vue'; import SidebarAssignees from './sidebar/sidebar_assignees.vue';
import sidebarStatusQuery from '../graphql/queries/sidebar_status.query.graphql';
export default { export default {
components: { components: {
SidebarAssignees, SidebarAssignees,
@ -11,23 +13,34 @@ export default {
SidebarTodo, SidebarTodo,
SidebarStatus, SidebarStatus,
}, },
props: { inject: {
sidebarCollapsed: {
type: Boolean,
required: true,
},
projectPath: { projectPath: {
type: String, default: '',
required: true,
}, },
projectId: {
type: String,
default: '',
},
},
props: {
alert: { alert: {
type: Object, type: Object,
required: true, required: true,
}, },
}, },
apollo: {
sidebarStatus: {
query: sidebarStatusQuery,
},
},
data() {
return {
sidebarStatus: false,
};
},
computed: { computed: {
sidebarCollapsedClass() { sidebarCollapsedClass() {
return this.sidebarCollapsed ? 'right-sidebar-collapsed' : 'right-sidebar-expanded'; return this.sidebarStatus ? 'right-sidebar-collapsed' : 'right-sidebar-expanded';
}, },
}, },
}; };
@ -37,23 +50,32 @@ export default {
<aside :class="sidebarCollapsedClass" class="right-sidebar alert-sidebar"> <aside :class="sidebarCollapsedClass" class="right-sidebar alert-sidebar">
<div class="issuable-sidebar js-issuable-update"> <div class="issuable-sidebar js-issuable-update">
<sidebar-header <sidebar-header
:sidebar-collapsed="sidebarCollapsed" :sidebar-collapsed="sidebarStatus"
:project-path="projectPath"
:alert="alert"
@toggle-sidebar="$emit('toggle-sidebar')" @toggle-sidebar="$emit('toggle-sidebar')"
@alert-error="$emit('alert-error', $event)"
/>
<sidebar-todo
v-if="sidebarStatus"
:project-path="projectPath"
:alert="alert"
:sidebar-collapsed="sidebarStatus"
@alert-error="$emit('alert-error', $event)"
/> />
<sidebar-todo v-if="sidebarCollapsed" :sidebar-collapsed="sidebarCollapsed" />
<sidebar-status <sidebar-status
:project-path="projectPath" :project-path="projectPath"
:alert="alert" :alert="alert"
@toggle-sidebar="$emit('toggle-sidebar')" @toggle-sidebar="$emit('toggle-sidebar')"
@alert-sidebar-error="$emit('alert-sidebar-error', $event)" @alert-error="$emit('alert-error', $event)"
/> />
<sidebar-assignees <sidebar-assignees
:project-path="projectPath" :project-path="projectPath"
:project-id="projectId"
:alert="alert" :alert="alert"
:sidebar-collapsed="sidebarCollapsed" :sidebar-collapsed="sidebarStatus"
@alert-refresh="$emit('alert-refresh')"
@toggle-sidebar="$emit('toggle-sidebar')" @toggle-sidebar="$emit('toggle-sidebar')"
@alert-sidebar-error="$emit('alert-sidebar-error', $event)" @alert-error="$emit('alert-error', $event)"
/> />
<div class="block"></div> <div class="block"></div>
</div> </div>

View file

@ -0,0 +1,129 @@
<script>
import { GlDropdown, GlDropdownItem, GlButton } from '@gitlab/ui';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
import { trackAlertStatusUpdateOptions } from '../constants';
import updateAlertStatus from '../graphql/mutations/update_alert_status.mutation.graphql';
export default {
i18n: {
UPDATE_ALERT_STATUS_ERROR: s__(
'AlertManagement|There was an error while updating the status of the alert.',
),
UPDATE_ALERT_STATUS_INSTRUCTION: s__('AlertManagement|Please try again.'),
},
statuses: {
TRIGGERED: s__('AlertManagement|Triggered'),
ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
RESOLVED: s__('AlertManagement|Resolved'),
},
components: {
GlDropdown,
GlDropdownItem,
GlButton,
},
props: {
projectPath: {
type: String,
required: true,
},
alert: {
type: Object,
required: true,
},
isDropdownShowing: {
type: Boolean,
required: false,
},
isSidebar: {
type: Boolean,
required: true,
},
},
computed: {
dropdownClass() {
// eslint-disable-next-line no-nested-ternary
return this.isSidebar ? (this.isDropdownShowing ? 'show' : 'gl-display-none') : '';
},
},
methods: {
updateAlertStatus(status) {
this.$emit('handle-updating', true);
this.$apollo
.mutate({
mutation: updateAlertStatus,
variables: {
iid: this.alert.iid,
status: status.toUpperCase(),
projectPath: this.projectPath,
},
})
.then(resp => {
this.trackStatusUpdate(status);
this.$emit('hide-dropdown');
const errors = resp.data?.updateAlertStatus?.errors || [];
if (errors[0]) {
this.$emit(
'alert-error',
`${this.$options.i18n.UPDATE_ALERT_STATUS_ERROR} ${errors[0]}`,
);
}
})
.catch(() => {
this.$emit(
'alert-error',
`${this.$options.i18n.UPDATE_ALERT_STATUS_ERROR} ${this.$options.i18n.UPDATE_ALERT_STATUS_INSTRUCTION}`,
);
})
.finally(() => {
this.$emit('handle-updating', false);
});
},
trackStatusUpdate(status) {
const { category, action, label } = trackAlertStatusUpdateOptions;
Tracking.event(category, action, { label, property: status });
},
},
};
</script>
<template>
<div class="dropdown dropdown-menu-selectable" :class="dropdownClass">
<gl-dropdown
ref="dropdown"
right
:text="$options.statuses[alert.status]"
class="w-100"
toggle-class="dropdown-menu-toggle"
variant="outline-default"
@keydown.esc.native="$emit('hide-dropdown')"
@hide="$emit('hide-dropdown')"
>
<div v-if="isSidebar" class="dropdown-title text-center">
<span class="alert-title">{{ s__('AlertManagement|Assign status') }}</span>
<gl-button
:aria-label="__('Close')"
variant="link"
class="dropdown-title-button dropdown-menu-close"
icon="close"
@click="$emit('hide-dropdown')"
/>
</div>
<div class="dropdown-content dropdown-body">
<gl-dropdown-item
v-for="(label, field) in $options.statuses"
:key="field"
data-testid="statusDropdownItem"
class="gl-vertical-align-middle"
:active="label.toUpperCase() === alert.status"
:active-class="'is-active'"
@click="updateAlertStatus(label)"
>
{{ label }}
</gl-dropdown-item>
</div>
</gl-dropdown>
</div>
</template>

View file

@ -11,20 +11,26 @@ import {
GlSprintf, GlSprintf,
} from '@gitlab/ui'; } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale'; import { s__, __ } from '~/locale';
import alertSetAssignees from '../../graphql/mutations/alert_set_assignees.graphql'; import alertSetAssignees from '../../graphql/mutations/alert_set_assignees.mutation.graphql';
import SidebarAssignee from './sidebar_assignee.vue'; import SidebarAssignee from './sidebar_assignee.vue';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
const DATA_REFETCH_DELAY = 250; const DATA_REFETCH_DELAY = 250;
export default { export default {
i18n: {
FETCH_USERS_ERROR: s__( FETCH_USERS_ERROR: s__(
'AlertManagement|There was an error while updating the assignee(s) list. Please try again.', 'AlertManagement|There was an error while updating the assignee(s) list. Please try again.',
), ),
UPDATE_ALERT_ASSIGNEES_ERROR: s__( UPDATE_ALERT_ASSIGNEES_ERROR: s__(
'AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again.', 'AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again.',
), ),
UPDATE_ALERT_ASSIGNEES_GRAPHQL_ERROR: s__(
'AlertManagement|This assignee cannot be assigned to this alert.',
),
ASSIGNEES_BLOCK: s__('AlertManagement|Alert assignee(s): %{assignees}'),
},
components: { components: {
GlIcon, GlIcon,
GlDropdown, GlDropdown,
@ -38,6 +44,10 @@ export default {
SidebarAssignee, SidebarAssignee,
}, },
props: { props: {
projectId: {
type: String,
required: true,
},
projectPath: { projectPath: {
type: String, type: String,
required: true, required: true,
@ -73,7 +83,7 @@ export default {
return this.alert?.assignees?.nodes[0]?.username; return this.alert?.assignees?.nodes[0]?.username;
}, },
assignedUser() { assignedUser() {
return this.userName || s__('AlertManagement|None'); return this.userName || __('None');
}, },
sortedUsers() { sortedUsers() {
return this.users return this.users
@ -122,20 +132,20 @@ export default {
updateAssigneesDropdown() { updateAssigneesDropdown() {
this.isDropdownSearching = true; this.isDropdownSearching = true;
return axios return axios
.get(this.buildUrl(gon.relative_url_root, '/autocomplete/users.json'), { .get(this.buildUrl(gon.relative_url_root, '/-/autocomplete/users.json'), {
params: { params: {
search: this.search, search: this.search,
per_page: 20, per_page: 20,
active: true, active: true,
current_user: true, current_user: true,
project_id: gon?.current_project_id, project_id: this.projectId,
}, },
}) })
.then(({ data }) => { .then(({ data }) => {
this.users = data; this.users = data;
}) })
.catch(() => { .catch(() => {
this.$emit('alert-sidebar-error', this.$options.FETCH_USERS_ERROR); this.$emit('alert-error', this.$options.i18n.FETCH_USERS_ERROR);
}) })
.finally(() => { .finally(() => {
this.isDropdownSearching = false; this.isDropdownSearching = false;
@ -152,12 +162,18 @@ export default {
projectPath: this.projectPath, projectPath: this.projectPath,
}, },
}) })
.then(() => { .then(({ data: { alertSetAssignees: { errors } = [] } = {} } = {}) => {
this.hideDropdown(); this.hideDropdown();
this.$emit('alert-refresh');
if (errors[0]) {
this.$emit(
'alert-error',
`${this.$options.i18n.UPDATE_ALERT_ASSIGNEES_GRAPHQL_ERROR} ${errors[0]}.`,
);
}
}) })
.catch(() => { .catch(() => {
this.$emit('alert-sidebar-error', this.$options.UPDATE_ALERT_ASSIGNEES_ERROR); this.$emit('alert-error', this.$options.i18n.UPDATE_ALERT_ASSIGNEES_ERROR);
}) })
.finally(() => { .finally(() => {
this.isUpdating = false; this.isUpdating = false;
@ -174,7 +190,7 @@ export default {
<gl-loading-icon v-if="isUpdating" /> <gl-loading-icon v-if="isUpdating" />
</div> </div>
<gl-tooltip :target="() => $refs.status" boundary="viewport" placement="left"> <gl-tooltip :target="() => $refs.status" boundary="viewport" placement="left">
<gl-sprintf :message="s__('AlertManagement|Alert assignee(s): %{assignees}')"> <gl-sprintf :message="$options.i18n.ASSIGNEES_BLOCK">
<template #assignees> <template #assignees>
{{ assignedUser }} {{ assignedUser }}
</template> </template>
@ -183,7 +199,7 @@ export default {
<div class="hide-collapsed"> <div class="hide-collapsed">
<p class="title gl-display-flex gl-justify-content-space-between"> <p class="title gl-display-flex gl-justify-content-space-between">
{{ s__('AlertManagement|Assignee') }} {{ __('Assignee') }}
<a <a
v-if="isEditable" v-if="isEditable"
ref="editButton" ref="editButton"
@ -192,7 +208,7 @@ export default {
@click="toggleFormDropdown" @click="toggleFormDropdown"
@keydown.esc="hideDropdown" @keydown.esc="hideDropdown"
> >
{{ s__('AlertManagement|Edit') }} {{ __('Edit') }}
</a> </a>
</p> </p>
@ -207,7 +223,7 @@ export default {
@hide="hideDropdown" @hide="hideDropdown"
> >
<div class="dropdown-title"> <div class="dropdown-title">
<span class="alert-title">{{ s__('AlertManagement|Assign To') }}</span> <span class="alert-title">{{ __('Assign To') }}</span>
<gl-button <gl-button
:aria-label="__('Close')" :aria-label="__('Close')"
variant="link" variant="link"
@ -232,12 +248,12 @@ export default {
active-class="is-active" active-class="is-active"
@click="updateAlertAssignees('')" @click="updateAlertAssignees('')"
> >
{{ s__('AlertManagement|Unassigned') }} {{ __('Unassigned') }}
</gl-dropdown-item> </gl-dropdown-item>
<gl-dropdown-divider /> <gl-dropdown-divider />
<gl-dropdown-header class="mt-0"> <gl-dropdown-header class="mt-0">
{{ s__('AlertManagement|Assignee') }} {{ __('Assignee') }}
</gl-dropdown-header> </gl-dropdown-header>
<sidebar-assignee <sidebar-assignee
v-for="user in sortedUsers" v-for="user in sortedUsers"
@ -248,7 +264,7 @@ export default {
/> />
</template> </template>
<gl-dropdown-item v-else-if="userListEmpty"> <gl-dropdown-item v-else-if="userListEmpty">
{{ s__('AlertManagement|No Matching Results') }} {{ __('No Matching Results') }}
</gl-dropdown-item> </gl-dropdown-item>
<gl-loading-icon v-else /> <gl-loading-icon v-else />
</div> </div>
@ -261,7 +277,7 @@ export default {
assignedUser assignedUser
}}</span> }}</span>
<span v-else class="gl-display-flex gl-align-items-center"> <span v-else class="gl-display-flex gl-align-items-center">
{{ s__('AlertManagement|None -') }} {{ __('None') }} -
<gl-button <gl-button
class="gl-pl-2" class="gl-pl-2"
href="#" href="#"
@ -269,7 +285,7 @@ export default {
data-testid="unassigned-users" data-testid="unassigned-users"
@click="updateAlertAssignees(currentUser)" @click="updateAlertAssignees(currentUser)"
> >
{{ s__('AlertManagement| assign yourself') }} {{ __('assign yourself') }}
</gl-button> </gl-button>
</span> </span>
</p> </p>

View file

@ -8,6 +8,14 @@ export default {
SidebarTodo, SidebarTodo,
}, },
props: { props: {
alert: {
type: Object,
required: true,
},
projectPath: {
type: String,
required: true,
},
sidebarCollapsed: { sidebarCollapsed: {
type: Boolean, type: Boolean,
required: true, required: true,
@ -17,18 +25,17 @@ export default {
</script> </script>
<template> <template>
<div class="block d-flex justify-content-between"> <div class="block gl-display-flex gl-justify-content-space-between">
<span class="issuable-header-text hide-collapsed"> <span class="issuable-header-text hide-collapsed">
{{ __('Quick actions') }} {{ __('To Do') }}
</span> </span>
<toggle-sidebar <sidebar-todo
:collapsed="sidebarCollapsed" v-if="!sidebarCollapsed"
css-classes="ml-auto" :project-path="projectPath"
@toggle="$emit('toggle-sidebar')" :alert="alert"
:sidebar-collapsed="sidebarCollapsed"
@alert-error="$emit('alert-error', $event)"
/> />
<!-- TODO: Implement after or as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/215946 --> <toggle-sidebar :collapsed="sidebarCollapsed" @toggle="$emit('toggle-sidebar')" />
<template v-if="false">
<sidebar-todo v-if="!sidebarCollapsed" :sidebar-collapsed="sidebarCollapsed" />
</template>
</div> </div>
</template> </template>

View file

@ -1,17 +1,7 @@
<script> <script>
import { import { GlIcon, GlLoadingIcon, GlTooltip, GlSprintf } from '@gitlab/ui';
GlIcon,
GlDropdown,
GlDropdownItem,
GlLoadingIcon,
GlTooltip,
GlButton,
GlSprintf,
} from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import Tracking from '~/tracking'; import AlertStatus from '../alert_status.vue';
import { trackAlertStatusUpdateOptions } from '../../constants';
import updateAlertStatus from '../../graphql/mutations/update_alert_status.graphql';
export default { export default {
statuses: { statuses: {
@ -21,12 +11,10 @@ export default {
}, },
components: { components: {
GlIcon, GlIcon,
GlDropdown,
GlDropdownItem,
GlLoadingIcon, GlLoadingIcon,
GlTooltip, GlTooltip,
GlButton,
GlSprintf, GlSprintf,
AlertStatus,
}, },
props: { props: {
projectPath: { projectPath: {
@ -60,44 +48,13 @@ export default {
}, },
toggleFormDropdown() { toggleFormDropdown() {
this.isDropdownShowing = !this.isDropdownShowing; this.isDropdownShowing = !this.isDropdownShowing;
const { dropdown } = this.$refs.dropdown.$refs; const { dropdown } = this.$children[2].$refs.dropdown.$refs;
if (dropdown && this.isDropdownShowing) { if (dropdown && this.isDropdownShowing) {
dropdown.show(); dropdown.show();
} }
}, },
isSelected(status) { handleUpdating(updating) {
return this.alert.status === status; this.isUpdating = updating;
},
updateAlertStatus(status) {
this.isUpdating = true;
this.$apollo
.mutate({
mutation: updateAlertStatus,
variables: {
iid: this.alert.iid,
status: status.toUpperCase(),
projectPath: this.projectPath,
},
})
.then(() => {
this.trackStatusUpdate(status);
this.hideDropdown();
})
.catch(() => {
this.$emit(
'alert-sidebar-error',
s__(
'AlertManagement|There was an error while updating the status of the alert. Please try again.',
),
);
})
.finally(() => {
this.isUpdating = false;
});
},
trackStatusUpdate(status) {
const { category, action, label } = trackAlertStatusUpdateOptions;
Tracking.event(category, action, { label, property: status });
}, },
}, },
}; };
@ -132,41 +89,15 @@ export default {
</a> </a>
</p> </p>
<div class="dropdown dropdown-menu-selectable" :class="dropdownClass"> <alert-status
<gl-dropdown :alert="alert"
ref="dropdown" :project-path="projectPath"
:text="$options.statuses[alert.status]" :is-dropdown-showing="isDropdownShowing"
class="w-100" :is-sidebar="true"
toggle-class="dropdown-menu-toggle" @alert-error="$emit('alert-error', $event)"
variant="outline-default" @hide-dropdown="hideDropdown"
@keydown.esc.native="hideDropdown" @handle-updating="handleUpdating"
@hide="hideDropdown"
>
<div class="dropdown-title">
<span class="alert-title">{{ s__('AlertManagement|Assign status') }}</span>
<gl-button
:aria-label="__('Close')"
variant="link"
class="dropdown-title-button dropdown-menu-close"
icon="close"
@click="hideDropdown"
/> />
</div>
<div class="dropdown-content dropdown-body">
<gl-dropdown-item
v-for="(label, field) in $options.statuses"
:key="field"
data-testid="statusDropdownItem"
class="gl-vertical-align-middle"
:active="label.toUpperCase() === alert.status"
:active-class="'is-active'"
@click="updateAlertStatus(label)"
>
{{ label }}
</gl-dropdown-item>
</div>
</gl-dropdown>
</div>
<gl-loading-icon v-if="isUpdating" :inline="true" /> <gl-loading-icon v-if="isUpdating" :inline="true" />
<p <p

View file

@ -1,29 +1,123 @@
<script> <script>
import { s__ } from '~/locale';
import Todo from '~/sidebar/components/todo_toggle/todo.vue'; import Todo from '~/sidebar/components/todo_toggle/todo.vue';
import axios from '~/lib/utils/axios_utils';
import createAlertTodo from '../../graphql/mutations/alert_todo_create.graphql';
export default { export default {
i18n: {
UPDATE_ALERT_TODO_ERROR: s__(
'AlertManagement|There was an error while updating the To Do of the alert.',
),
},
components: { components: {
Todo, Todo,
}, },
props: { props: {
alert: {
type: Object,
required: true,
},
projectPath: {
type: String,
required: true,
},
sidebarCollapsed: { sidebarCollapsed: {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
}, },
data() {
return {
isUpdating: false,
isTodo: false,
todo: '',
};
},
computed: {
alertID() {
return parseInt(this.alert.iid, 10);
},
},
methods: {
updateToDoCount(add) {
const oldCount = parseInt(document.querySelector('.todos-count').innerText, 10);
const count = add ? oldCount + 1 : oldCount - 1;
const headerTodoEvent = new CustomEvent('todo:toggle', {
detail: {
count,
},
});
return document.dispatchEvent(headerTodoEvent);
},
toggleTodo() {
if (this.todo) {
return this.markAsDone();
}
this.isUpdating = true;
return this.$apollo
.mutate({
mutation: createAlertTodo,
variables: {
iid: this.alert.iid,
projectPath: this.projectPath,
},
})
.then(({ data: { alertTodoCreate: { todo = {}, errors = [] } } = {} } = {}) => {
if (errors[0]) {
return this.$emit(
'alert-error',
`${this.$options.i18n.UPDATE_ALERT_TODO_ERROR} ${errors[0]}.`,
);
}
this.todo = todo.id;
return this.updateToDoCount(true);
})
.catch(() => {
this.$emit(
'alert-error',
`${this.$options.i18n.UPDATE_ALERT_TODO_ERROR} ${s__(
'AlertManagement|Please try again.',
)}`,
);
})
.finally(() => {
this.isUpdating = false;
});
},
markAsDone() {
this.isUpdating = true;
return axios
.delete(`/dashboard/todos/${this.todo.split('/').pop()}`)
.then(() => {
this.todo = '';
return this.updateToDoCount(false);
})
.catch(() => {
this.$emit('alert-error', this.$options.i18n.UPDATE_ALERT_TODO_ERROR);
})
.finally(() => {
this.isUpdating = false;
});
},
},
}; };
</script> </script>
<!-- TODO: Implement after or as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/215946 -->
<template> <template>
<div v-if="false" :class="{ 'block todo': sidebarCollapsed }"> <div :class="{ 'block todo': sidebarCollapsed, 'gl-ml-auto': !sidebarCollapsed }">
<todo <todo
data-testid="alert-todo-button"
:collapsed="sidebarCollapsed" :collapsed="sidebarCollapsed"
:issuable-id="1" :issuable-id="alertID"
:is-todo="false" :is-todo="todo !== ''"
:is-action-active="false" :is-action-active="isUpdating"
issuable-type="alert" issuable-type="alert"
@toggleTodo="() => {}" @toggleTodo="toggleTodo"
/> />
</div> </div>
</template> </template>

View file

@ -24,7 +24,7 @@ export default {
return { ...author, id: id?.split('/').pop() }; return { ...author, id: id?.split('/').pop() };
}, },
iconHtml() { iconHtml() {
return spriteIcon('user'); return spriteIcon(this.note?.systemNoteIconName);
}, },
}, },
}; };

View file

@ -3,17 +3,26 @@ import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import { defaultDataIdFromObject } from 'apollo-cache-inmemory'; import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import AlertDetails from './components/alert_details.vue'; import AlertDetails from './components/alert_details.vue';
import sidebarStatusQuery from './graphql/queries/sidebar_status.query.graphql';
Vue.use(VueApollo); Vue.use(VueApollo);
export default selector => { export default selector => {
const domEl = document.querySelector(selector); const domEl = document.querySelector(selector);
const { alertId, projectPath, projectIssuesPath } = domEl.dataset; const { alertId, projectPath, projectIssuesPath, projectId } = domEl.dataset;
const resolvers = {
Mutation: {
toggleSidebarStatus: (_, __, { cache }) => {
const data = cache.readQuery({ query: sidebarStatusQuery });
data.sidebarStatus = !data.sidebarStatus;
cache.writeQuery({ query: sidebarStatusQuery, data });
},
},
};
const apolloProvider = new VueApollo({ const apolloProvider = new VueApollo({
defaultClient: createDefaultClient( defaultClient: createDefaultClient(resolvers, {
{},
{
cacheConfig: { cacheConfig: {
dataIdFromObject: object => { dataIdFromObject: object => {
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
@ -23,25 +32,30 @@ export default selector => {
return defaultDataIdFromObject(object); return defaultDataIdFromObject(object);
}, },
}, },
}),
});
apolloProvider.clients.defaultClient.cache.writeData({
data: {
sidebarStatus: false,
}, },
),
}); });
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
el: selector, el: selector,
provide: {
projectPath,
alertId,
projectIssuesPath,
projectId,
},
apolloProvider, apolloProvider,
components: { components: {
AlertDetails, AlertDetails,
}, },
render(createElement) { render(createElement) {
return createElement('alert-details', { return createElement('alert-details', {});
props: {
alertId,
projectPath,
projectIssuesPath,
},
});
}, },
}); });
}; };

View file

@ -13,4 +13,5 @@ fragment AlertNote on Note {
discussion { discussion {
id id
} }
systemNoteIconName
} }

View file

@ -5,9 +5,11 @@ fragment AlertDetailItem on AlertManagementAlert {
...AlertListItem ...AlertListItem
createdAt createdAt
monitoringTool monitoringTool
metricsDashboardUrl
service service
description description
updatedAt updatedAt
endedAt
details details
notes { notes {
nodes { nodes {

View file

@ -4,7 +4,6 @@ fragment AlertListItem on AlertManagementAlert {
severity severity
status status
startedAt startedAt
endedAt
eventCount eventCount
issueIid issueIid
assignees { assignees {

View file

@ -1,4 +1,6 @@
mutation($projectPath: ID!, $assigneeUsernames: [String!]!, $iid: String!) { #import "../fragments/alert_note.fragment.graphql"
mutation alertSetAssignees($projectPath: ID!, $assigneeUsernames: [String!]!, $iid: String!) {
alertSetAssignees( alertSetAssignees(
input: { iid: $iid, assigneeUsernames: $assigneeUsernames, projectPath: $projectPath } input: { iid: $iid, assigneeUsernames: $assigneeUsernames, projectPath: $projectPath }
) { ) {
@ -10,6 +12,11 @@ mutation($projectPath: ID!, $assigneeUsernames: [String!]!, $iid: String!) {
username username
} }
} }
notes {
nodes {
...AlertNote
}
}
} }
} }
} }

View file

@ -0,0 +1,11 @@
mutation($projectPath: ID!, $iid: String!) {
alertTodoCreate(input: { iid: $iid, projectPath: $projectPath }) {
errors
alert {
iid
}
todo {
id
}
}
}

View file

@ -1,8 +0,0 @@
mutation ($projectPath: ID!, $iid: String!) {
createAlertIssue(input: { iid: $iid, projectPath: $projectPath }) {
errors
issue {
iid
}
}
}

View file

@ -0,0 +1,8 @@
mutation createAlertIssue($projectPath: ID!, $iid: String!) {
createAlertIssue(input: { iid: $iid, projectPath: $projectPath }) {
errors
issue {
iid
}
}
}

View file

@ -0,0 +1,3 @@
mutation toggleSidebarStatus {
toggleSidebarStatus @client
}

View file

@ -1,10 +0,0 @@
mutation ($projectPath: ID!, $status: AlertManagementStatus!, $iid: String!) {
updateAlertStatus(input: { iid: $iid, status: $status, projectPath: $projectPath }) {
errors
alert {
iid,
status,
endedAt
}
}
}

View file

@ -0,0 +1,17 @@
#import "../fragments/alert_note.fragment.graphql"
mutation updateAlertStatus($projectPath: ID!, $status: AlertManagementStatus!, $iid: String!) {
updateAlertStatus(input: { iid: $iid, status: $status, projectPath: $projectPath }) {
errors
alert {
iid
status
endedAt
notes {
nodes {
...AlertNote
}
}
}
}
}

View file

@ -1,26 +1,28 @@
#import "../fragments/list_item.fragment.graphql" #import "../fragments/list_item.fragment.graphql"
query getAlerts( query getAlerts(
$projectPath: ID!, $searchTerm: String
$statuses: [AlertManagementStatus!], $projectPath: ID!
$sort: AlertManagementAlertSort, $statuses: [AlertManagementStatus!]
$firstPageSize: Int, $sort: AlertManagementAlertSort
$lastPageSize: Int, $firstPageSize: Int
$lastPageSize: Int
$prevPageCursor: String = "" $prevPageCursor: String = ""
$nextPageCursor: String = "" $nextPageCursor: String = ""
) { ) {
project(fullPath: $projectPath, ) { project(fullPath: $projectPath) {
alertManagementAlerts( alertManagementAlerts(
statuses: $statuses, search: $searchTerm
sort: $sort, statuses: $statuses
sort: $sort
first: $firstPageSize first: $firstPageSize
last: $lastPageSize, last: $lastPageSize
after: $nextPageCursor, after: $nextPageCursor
before: $prevPageCursor before: $prevPageCursor
) { ) {
nodes { nodes {
...AlertListItem ...AlertListItem
}, }
pageInfo { pageInfo {
hasNextPage hasNextPage
endCursor endCursor

View file

@ -1,6 +1,6 @@
query getAlertsCount($projectPath: ID!) { query getAlertsCount($searchTerm: String, $projectPath: ID!) {
project(fullPath: $projectPath) { project(fullPath: $projectPath) {
alertManagementAlertStatusCounts { alertManagementAlertStatusCounts(search: $searchTerm) {
all all
open open
acknowledged acknowledged

View file

@ -0,0 +1,3 @@
query sidebarStatus {
sidebarStatus @client
}

View file

@ -3,7 +3,7 @@ import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import { defaultDataIdFromObject } from 'apollo-cache-inmemory'; import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import AlertManagementList from './components/alert_management_list.vue'; import AlertManagementList from './components/alert_management_list_wrapper.vue';
Vue.use(VueApollo); Vue.use(VueApollo);
@ -11,11 +11,18 @@ export default () => {
const selector = '#js-alert_management'; const selector = '#js-alert_management';
const domEl = document.querySelector(selector); const domEl = document.querySelector(selector);
const { projectPath, enableAlertManagementPath, emptyAlertSvgPath } = domEl.dataset; const {
let { alertManagementEnabled, userCanEnableAlertManagement } = domEl.dataset; projectPath,
enableAlertManagementPath,
emptyAlertSvgPath,
populatingAlertsHelpUrl,
opsgenieMvcTargetUrl,
} = domEl.dataset;
let { alertManagementEnabled, userCanEnableAlertManagement, opsgenieMvcEnabled } = domEl.dataset;
alertManagementEnabled = parseBoolean(alertManagementEnabled); alertManagementEnabled = parseBoolean(alertManagementEnabled);
userCanEnableAlertManagement = parseBoolean(userCanEnableAlertManagement); userCanEnableAlertManagement = parseBoolean(userCanEnableAlertManagement);
opsgenieMvcEnabled = parseBoolean(opsgenieMvcEnabled);
const apolloProvider = new VueApollo({ const apolloProvider = new VueApollo({
defaultClient: createDefaultClient( defaultClient: createDefaultClient(
@ -45,9 +52,12 @@ export default () => {
props: { props: {
projectPath, projectPath,
enableAlertManagementPath, enableAlertManagementPath,
populatingAlertsHelpUrl,
emptyAlertSvgPath, emptyAlertSvgPath,
alertManagementEnabled, alertManagementEnabled,
userCanEnableAlertManagement, userCanEnableAlertManagement,
opsgenieMvcTargetUrl,
opsgenieMvcEnabled,
}, },
}); });
}, },

View file

@ -64,6 +64,11 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
isDisabled: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
return { return {
@ -142,7 +147,7 @@ export default {
<gl-form-group :label="__('Active')" label-for="activated" label-class="label-bold"> <gl-form-group :label="__('Active')" label-for="activated" label-class="label-bold">
<toggle-button <toggle-button
id="activated" id="activated"
:disabled-input="loadingActivated" :disabled-input="loadingActivated || isDisabled"
:is-loading="loadingActivated" :is-loading="loadingActivated"
:value="activated" :value="activated"
@change="toggleActivated" @change="toggleActivated"
@ -152,7 +157,11 @@ export default {
<div class="input-group"> <div class="input-group">
<gl-form-input id="url" :readonly="true" :value="url" /> <gl-form-input id="url" :readonly="true" :value="url" />
<span class="input-group-append"> <span class="input-group-append">
<clipboard-button :text="url" :title="$options.COPY_TO_CLIPBOARD" /> <clipboard-button
:text="url"
:title="$options.COPY_TO_CLIPBOARD"
:disabled="isDisabled"
/>
</span> </span>
</div> </div>
</gl-form-group> </gl-form-group>
@ -164,10 +173,16 @@ export default {
<div class="input-group"> <div class="input-group">
<gl-form-input id="authorization-key" :readonly="true" :value="authorizationKey" /> <gl-form-input id="authorization-key" :readonly="true" :value="authorizationKey" />
<span class="input-group-append"> <span class="input-group-append">
<clipboard-button :text="authorizationKey" :title="$options.COPY_TO_CLIPBOARD" /> <clipboard-button
:text="authorizationKey"
:title="$options.COPY_TO_CLIPBOARD"
:disabled="isDisabled"
/>
</span> </span>
</div> </div>
<gl-button v-gl-modal.authKeyModal class="mt-2">{{ $options.RESET_KEY }}</gl-button> <gl-button v-gl-modal.authKeyModal class="mt-2" :disabled="isDisabled">{{
$options.RESET_KEY
}}</gl-button>
<gl-modal <gl-modal
modal-id="authKeyModal" modal-id="authKeyModal"
:title="$options.RESET_KEY" :title="$options.RESET_KEY"

View file

@ -14,8 +14,11 @@ export default el => {
formPath, formPath,
authorizationKey, authorizationKey,
url, url,
disabled,
} = el.dataset; } = el.dataset;
const activated = parseBoolean(activatedStr); const activated = parseBoolean(activatedStr);
const isDisabled = parseBoolean(disabled);
return new Vue({ return new Vue({
el, el,
@ -28,6 +31,7 @@ export default el => {
formPath, formPath,
initialAuthorizationKey: authorizationKey, initialAuthorizationKey: authorizationKey,
url, url,
isDisabled,
}, },
}); });
}, },

View file

@ -0,0 +1,563 @@
<script>
import {
GlAlert,
GlButton,
GlForm,
GlFormGroup,
GlFormInput,
GlFormInputGroup,
GlFormTextarea,
GlLink,
GlModal,
GlModalDirective,
GlSprintf,
GlFormSelect,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ToggleButton from '~/vue_shared/components/toggle_button.vue';
import csrf from '~/lib/utils/csrf';
import service from '../services';
import {
i18n,
serviceOptions,
JSON_VALIDATE_DELAY,
targetPrometheusUrlPlaceholder,
targetOpsgenieUrlPlaceholder,
} from '../constants';
export default {
i18n,
csrf,
targetOpsgenieUrlPlaceholder,
targetPrometheusUrlPlaceholder,
components: {
GlAlert,
GlButton,
GlForm,
GlFormGroup,
GlFormInput,
GlFormInputGroup,
GlFormSelect,
GlFormTextarea,
GlLink,
GlModal,
GlSprintf,
ClipboardButton,
ToggleButton,
},
directives: {
'gl-modal': GlModalDirective,
},
mixins: [glFeatureFlagsMixin()],
props: {
prometheus: {
type: Object,
required: true,
validator: ({ activated }) => {
return activated !== undefined;
},
},
generic: {
type: Object,
required: true,
validator: ({ formPath }) => {
return formPath !== undefined;
},
},
opsgenie: {
type: Object,
required: true,
},
},
data() {
return {
activated: {
generic: this.generic.activated,
prometheus: this.prometheus.activated,
opsgenie: this.opsgenie?.activated,
},
loading: false,
authorizationKey: {
generic: this.generic.initialAuthorizationKey,
prometheus: this.prometheus.prometheusAuthorizationKey,
},
selectedEndpoint: serviceOptions[0].value,
options: serviceOptions,
targetUrl: null,
feedback: {
variant: 'danger',
feedbackMessage: null,
isFeedbackDismissed: false,
},
serverError: null,
testAlert: {
json: null,
error: null,
},
canSaveForm: false,
};
},
computed: {
sections() {
return [
{
text: this.$options.i18n.usageSection,
url: this.generic.alertsUsageUrl,
},
{
text: this.$options.i18n.setupSection,
url: this.generic.alertsSetupUrl,
},
];
},
isPrometheus() {
return this.selectedEndpoint === 'prometheus';
},
isOpsgenie() {
return this.selectedEndpoint === 'opsgenie';
},
selectedService() {
switch (this.selectedEndpoint) {
case 'generic': {
return {
url: this.generic.url,
authKey: this.authorizationKey.generic,
active: this.activated.generic,
resetKey: this.resetGenericKey.bind(this),
};
}
case 'prometheus': {
return {
url: this.prometheus.prometheusUrl,
authKey: this.authorizationKey.prometheus,
active: this.activated.prometheus,
resetKey: this.resetPrometheusKey.bind(this),
targetUrl: this.prometheus.prometheusApiUrl,
};
}
case 'opsgenie': {
return {
targetUrl: this.opsgenie.opsgenieMvcTargetUrl,
active: this.activated.opsgenie,
};
}
default: {
return {};
}
}
},
showFeedbackMsg() {
return this.feedback.feedbackMessage && !this.isFeedbackDismissed;
},
showAlertSave() {
return (
this.feedback.feedbackMessage === this.$options.i18n.testAlertFailed &&
!this.isFeedbackDismissed
);
},
prometheusInfo() {
return this.isPrometheus ? this.$options.i18n.prometheusInfo : '';
},
jsonIsValid() {
return this.testAlert.error === null;
},
canTestAlert() {
return this.selectedService.active && this.testAlert.json !== null;
},
canSaveConfig() {
return !this.loading && this.canSaveForm;
},
baseUrlPlaceholder() {
return this.isOpsgenie
? this.$options.targetOpsgenieUrlPlaceholder
: this.$options.targetPrometheusUrlPlaceholder;
},
},
watch: {
'testAlert.json': debounce(function debouncedJsonValidate() {
this.validateJson();
}, JSON_VALIDATE_DELAY),
targetUrl(oldVal, newVal) {
if (newVal && oldVal !== this.selectedService.targetUrl) {
this.canSaveForm = true;
}
},
},
mounted() {
if (
this.activated.prometheus ||
this.activated.generic ||
!this.opsgenie.opsgenieMvcIsAvailable
) {
this.removeOpsGenieOption();
} else if (this.activated.opsgenie) {
this.setOpsgenieAsDefault();
}
},
methods: {
createUserErrorMessage(errors) {
// eslint-disable-next-line prefer-destructuring
this.serverError = Object.values(errors)[0][0];
},
setOpsgenieAsDefault() {
this.options = this.options.map(el => {
if (el.value !== 'opsgenie') {
return { ...el, disabled: true };
}
return { ...el, disabled: false };
});
this.selectedEndpoint = this.options.find(({ value }) => value === 'opsgenie').value;
if (this.targetUrl === null) {
this.targetUrl = this.selectedService.targetUrl;
}
},
removeOpsGenieOption() {
this.options = this.options.map(el => {
if (el.value !== 'opsgenie') {
return { ...el, disabled: false };
}
return { ...el, disabled: true };
});
},
resetFormValues() {
this.testAlert.json = null;
this.targetUrl = this.selectedService.targetUrl;
},
dismissFeedback() {
this.serverError = null;
this.feedback = { ...this.feedback, feedbackMessage: null };
this.isFeedbackDismissed = false;
},
resetGenericKey() {
return service
.updateGenericKey({ endpoint: this.generic.formPath, params: { service: { token: '' } } })
.then(({ data: { token } }) => {
this.authorizationKey.generic = token;
this.setFeedback({ feedbackMessage: this.$options.i18n.authKeyRest, variant: 'success' });
})
.catch(() => {
this.setFeedback({ feedbackMessage: this.$options.i18n.errorKeyMsg, variant: 'danger' });
});
},
resetPrometheusKey() {
return service
.updatePrometheusKey({ endpoint: this.prometheus.prometheusResetKeyPath })
.then(({ data: { token } }) => {
this.authorizationKey.prometheus = token;
this.setFeedback({ feedbackMessage: this.$options.i18n.authKeyRest, variant: 'success' });
})
.catch(() => {
this.setFeedback({ feedbackMessage: this.$options.i18n.errorKeyMsg, variant: 'danger' });
});
},
toggleService(value) {
this.canSaveForm = true;
if (this.isPrometheus) {
this.activated.prometheus = value;
} else {
this.activated[this.selectedEndpoint] = value;
}
},
toggle(value) {
return this.isPrometheus ? this.togglePrometheusActive(value) : this.toggleActivated(value);
},
toggleActivated(value) {
this.loading = true;
return service
.updateGenericActive({
endpoint: this[this.selectedEndpoint].formPath,
params: this.isOpsgenie
? { service: { opsgenie_mvc_target_url: this.targetUrl, opsgenie_mvc_enabled: value } }
: { service: { active: value } },
})
.then(() => {
this.activated[this.selectedEndpoint] = value;
this.toggleSuccess(value);
if (!this.isOpsgenie && value) {
if (!this.selectedService.authKey) {
return window.location.reload();
}
return this.removeOpsGenieOption();
}
if (this.isOpsgenie && value) {
return this.setOpsgenieAsDefault();
}
// eslint-disable-next-line no-return-assign
return (this.options = serviceOptions);
})
.catch(({ response: { data: { errors } = {} } = {} }) => {
this.createUserErrorMessage(errors);
this.setFeedback({
feedbackMessage: `${this.$options.i18n.errorMsg}.`,
variant: 'danger',
});
})
.finally(() => {
this.loading = false;
this.canSaveForm = false;
});
},
togglePrometheusActive(value) {
this.loading = true;
return service
.updatePrometheusActive({
endpoint: this.prometheus.prometheusFormPath,
params: {
token: this.$options.csrf.token,
config: value,
url: this.targetUrl,
redirect: window.location,
},
})
.then(() => {
this.activated.prometheus = value;
this.toggleSuccess(value);
this.removeOpsGenieOption();
})
.catch(({ response: { data: { errors } = {} } = {} }) => {
this.createUserErrorMessage(errors);
this.setFeedback({
feedbackMessage: `${this.$options.i18n.errorMsg}.`,
variant: 'danger',
});
})
.finally(() => {
this.loading = false;
this.canSaveForm = false;
});
},
toggleSuccess(value) {
if (value) {
this.setFeedback({
feedbackMessage: this.$options.i18n.endPointActivated,
variant: 'info',
});
} else {
this.setFeedback({
feedbackMessage: this.$options.i18n.changesSaved,
variant: 'info',
});
}
},
setFeedback({ feedbackMessage, variant }) {
this.feedback = { feedbackMessage, variant };
},
validateJson() {
this.testAlert.error = null;
try {
JSON.parse(this.testAlert.json);
} catch (e) {
this.testAlert.error = JSON.stringify(e.message);
}
},
validateTestAlert() {
this.loading = true;
this.validateJson();
return service
.updateTestAlert({
endpoint: this.selectedService.url,
data: this.testAlert.json,
authKey: this.selectedService.authKey,
})
.then(() => {
this.setFeedback({
feedbackMessage: this.$options.i18n.testAlertSuccess,
variant: 'success',
});
})
.catch(() => {
this.setFeedback({
feedbackMessage: this.$options.i18n.testAlertFailed,
variant: 'danger',
});
})
.finally(() => {
this.loading = false;
});
},
onSubmit() {
this.toggle(this.selectedService.active);
},
onReset() {
this.testAlert.json = null;
this.dismissFeedback();
this.targetUrl = this.selectedService.targetUrl;
if (this.canSaveForm) {
this.canSaveForm = false;
this.activated[this.selectedEndpoint] = this[this.selectedEndpoint].activated;
}
},
},
};
</script>
<template>
<div>
<gl-alert v-if="showFeedbackMsg" :variant="feedback.variant" @dismiss="dismissFeedback">
{{ feedback.feedbackMessage }}
<br />
<i v-if="serverError">{{ __('Error message:') }} {{ serverError }}</i>
<gl-button
v-if="showAlertSave"
variant="danger"
category="primary"
class="gl-display-block gl-mt-3"
@click="toggle(selectedService.active)"
>
{{ __('Save anyway') }}
</gl-button>
</gl-alert>
<div data-testid="alert-settings-description" class="gl-mt-5">
<p v-for="section in sections" :key="section.text">
<gl-sprintf :message="section.text">
<template #link="{ content }">
<gl-link :href="section.url" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</div>
<gl-form @submit.prevent="onSubmit" @reset.prevent="onReset">
<gl-form-group
:label="$options.i18n.integrationsLabel"
label-for="integrations"
label-class="label-bold"
>
<gl-form-select
v-model="selectedEndpoint"
:options="options"
data-testid="alert-settings-select"
@change="resetFormValues"
/>
<span class="gl-text-gray-400">
<gl-sprintf :message="$options.i18n.integrationsInfo">
<template #link="{ content }">
<gl-link
class="gl-display-inline-block"
href="https://gitlab.com/groups/gitlab-org/-/epics/3362"
target="_blank"
>{{ content }}</gl-link
>
</template>
</gl-sprintf>
</span>
</gl-form-group>
<gl-form-group
:label="$options.i18n.activeLabel"
label-for="activated"
label-class="label-bold"
>
<toggle-button
id="activated"
:disabled-input="loading"
:is-loading="loading"
:value="selectedService.active"
@change="toggleService"
/>
</gl-form-group>
<gl-form-group
v-if="isOpsgenie || isPrometheus"
:label="$options.i18n.apiBaseUrlLabel"
label-for="api-url"
label-class="label-bold"
>
<gl-form-input
id="api-url"
v-model="targetUrl"
type="url"
:placeholder="baseUrlPlaceholder"
:disabled="!selectedService.active"
/>
<span class="gl-text-gray-400">
{{ $options.i18n.apiBaseUrlHelpText }}
</span>
</gl-form-group>
<template v-if="!isOpsgenie">
<gl-form-group :label="$options.i18n.urlLabel" label-for="url" label-class="label-bold">
<gl-form-input-group id="url" readonly :value="selectedService.url">
<template #append>
<clipboard-button
:text="selectedService.url"
:title="$options.i18n.copyToClipboard"
class="gl-m-0!"
/>
</template>
</gl-form-input-group>
<span class="gl-text-gray-400">
{{ prometheusInfo }}
</span>
</gl-form-group>
<gl-form-group
:label="$options.i18n.authKeyLabel"
label-for="authorization-key"
label-class="label-bold"
>
<gl-form-input-group
id="authorization-key"
class="gl-mb-2"
readonly
:value="selectedService.authKey"
>
<template #append>
<clipboard-button
:text="selectedService.authKey || ''"
:title="$options.i18n.copyToClipboard"
class="gl-m-0!"
/>
</template>
</gl-form-input-group>
<gl-button v-gl-modal.authKeyModal :disabled="!selectedService.active" class="gl-mt-3">{{
$options.i18n.resetKey
}}</gl-button>
<gl-modal
modal-id="authKeyModal"
:title="$options.i18n.resetKey"
:ok-title="$options.i18n.resetKey"
ok-variant="danger"
@ok="selectedService.resetKey"
>
{{ $options.i18n.restKeyInfo }}
</gl-modal>
</gl-form-group>
<gl-form-group
:label="$options.i18n.alertJson"
label-for="alert-json"
label-class="label-bold"
:invalid-feedback="testAlert.error"
>
<gl-form-textarea
id="alert-json"
v-model.trim="testAlert.json"
:disabled="!selectedService.active"
:state="jsonIsValid"
:placeholder="$options.i18n.alertJsonPlaceholder"
rows="6"
max-rows="10"
/>
</gl-form-group>
<gl-button :disabled="!canTestAlert" @click="validateTestAlert">{{
$options.i18n.testAlertInfo
}}</gl-button>
</template>
<div class="footer-block row-content-block gl-display-flex gl-justify-content-space-between">
<gl-button
variant="success"
category="primary"
:disabled="!canSaveConfig"
@click="onSubmit"
>
{{ __('Save changes') }}
</gl-button>
<gl-button variant="default" category="primary" :disabled="!canSaveConfig" @click="onReset">
{{ __('Cancel') }}
</gl-button>
</div>
</gl-form>
</div>
</template>

View file

@ -0,0 +1,50 @@
import { s__ } from '~/locale';
export const i18n = {
usageSection: s__(
'AlertSettings|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.',
),
setupSection: s__(
"AlertSettings|Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.",
),
errorMsg: s__('AlertSettings|There was an error updating the alert settings'),
errorKeyMsg: s__(
'AlertSettings|There was an error while trying to reset the key. Please refresh the page to try again.',
),
restKeyInfo: s__(
'AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.',
),
endPointActivated: s__('AlertSettings|Alerts endpoint successfully activated.'),
changesSaved: s__('AlertSettings|Your changes were successfully updated.'),
prometheusInfo: s__('AlertSettings|Add URL and auth key to your Prometheus config file'),
integrationsInfo: s__(
'AlertSettings|Learn more about our %{linkStart}upcoming integrations%{linkEnd}',
),
resetKey: s__('AlertSettings|Reset key'),
copyToClipboard: s__('AlertSettings|Copy'),
integrationsLabel: s__('AlertSettings|Integrations'),
apiBaseUrlLabel: s__('AlertSettings|API URL'),
authKeyLabel: s__('AlertSettings|Authorization key'),
urlLabel: s__('AlertSettings|Webhook URL'),
activeLabel: s__('AlertSettings|Active'),
apiBaseUrlHelpText: s__('AlertSettings|URL cannot be blank and must start with http or https'),
testAlertInfo: s__('AlertSettings|Test alert payload'),
alertJson: s__('AlertSettings|Alert test payload'),
alertJsonPlaceholder: s__('AlertSettings|Enter test alert JSON....'),
testAlertFailed: s__('AlertSettings|Test failed. Do you still want to save your changes anyway?'),
testAlertSuccess: s__(
'AlertSettings|Test alert sent successfully. If you have made other changes, please save them now.',
),
authKeyRest: s__('AlertSettings|Authorization key has been successfully reset'),
};
export const serviceOptions = [
{ value: 'generic', text: s__('AlertSettings|Generic') },
{ value: 'prometheus', text: s__('AlertSettings|External Prometheus') },
{ value: 'opsgenie', text: s__('AlertSettings|Opsgenie') },
];
export const JSON_VALIDATE_DELAY = 250;
export const targetPrometheusUrlPlaceholder = 'http://prometheus.example.com/';
export const targetOpsgenieUrlPlaceholder = 'https://app.opsgenie.com/alert/list/';

View file

@ -0,0 +1,67 @@
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import AlertSettingsForm from './components/alerts_settings_form.vue';
export default el => {
if (!el) {
return null;
}
const {
prometheusActivated,
prometheusUrl,
prometheusAuthorizationKey,
prometheusFormPath,
prometheusResetKeyPath,
prometheusApiUrl,
activated: activatedStr,
alertsSetupUrl,
alertsUsageUrl,
formPath,
authorizationKey,
url,
opsgenieMvcAvailable,
opsgenieMvcFormPath,
opsgenieMvcEnabled,
opsgenieMvcTargetUrl,
} = el.dataset;
const genericActivated = parseBoolean(activatedStr);
const prometheusIsActivated = parseBoolean(prometheusActivated);
const opsgenieMvcActivated = parseBoolean(opsgenieMvcEnabled);
const opsgenieMvcIsAvailable = parseBoolean(opsgenieMvcAvailable);
const props = {
prometheus: {
activated: prometheusIsActivated,
prometheusUrl,
prometheusAuthorizationKey,
prometheusFormPath,
prometheusResetKeyPath,
prometheusApiUrl,
},
generic: {
alertsSetupUrl,
alertsUsageUrl,
activated: genericActivated,
formPath,
initialAuthorizationKey: authorizationKey,
url,
},
opsgenie: {
formPath: opsgenieMvcFormPath,
activated: opsgenieMvcActivated,
opsgenieMvcTargetUrl,
opsgenieMvcIsAvailable,
},
};
return new Vue({
el,
render(createElement) {
return createElement(AlertSettingsForm, {
props,
});
},
});
};

View file

@ -0,0 +1,36 @@
/* eslint-disable @gitlab/require-i18n-strings */
import axios from '~/lib/utils/axios_utils';
export default {
updateGenericKey({ endpoint, params }) {
return axios.put(endpoint, params);
},
updatePrometheusKey({ endpoint }) {
return axios.post(endpoint);
},
updateGenericActive({ endpoint, params }) {
return axios.put(endpoint, params);
},
updatePrometheusActive({ endpoint, params: { token, config, url, redirect } }) {
const data = new FormData();
data.set('_method', 'put');
data.set('authenticity_token', token);
data.set('service[manual_configuration]', config);
data.set('service[api_url]', url);
data.set('redirect_to', redirect);
return axios.post(endpoint, data, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
},
updateTestAlert({ endpoint, data, authKey }) {
return axios.post(endpoint, data, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${authKey}`,
},
});
},
};

View file

@ -11,6 +11,9 @@ const Api = {
groupMembersPath: '/api/:version/groups/:id/members', groupMembersPath: '/api/:version/groups/:id/members',
subgroupsPath: '/api/:version/groups/:id/subgroups', subgroupsPath: '/api/:version/groups/:id/subgroups',
namespacesPath: '/api/:version/namespaces.json', namespacesPath: '/api/:version/namespaces.json',
groupPackagesPath: '/api/:version/groups/:id/packages',
projectPackagesPath: '/api/:version/projects/:id/packages',
projectPackagePath: '/api/:version/projects/:id/packages/:package_id',
groupProjectsPath: '/api/:version/groups/:id/projects.json', groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json', projectsPath: '/api/:version/projects.json',
projectPath: '/api/:version/projects/:id', projectPath: '/api/:version/projects/:id',
@ -36,7 +39,9 @@ const Api = {
userStatusPath: '/api/:version/users/:id/status', userStatusPath: '/api/:version/users/:id/status',
userProjectsPath: '/api/:version/users/:id/projects', userProjectsPath: '/api/:version/users/:id/projects',
userPostStatusPath: '/api/:version/user/status', userPostStatusPath: '/api/:version/user/status',
commitPath: '/api/:version/projects/:id/repository/commits', commitPath: '/api/:version/projects/:id/repository/commits/:sha',
commitsPath: '/api/:version/projects/:id/repository/commits',
applySuggestionPath: '/api/:version/suggestions/:id/apply', applySuggestionPath: '/api/:version/suggestions/:id/apply',
applySuggestionBatchPath: '/api/:version/suggestions/batch_apply', applySuggestionBatchPath: '/api/:version/suggestions/batch_apply',
commitPipelinesPath: '/:project_id/commit/:sha/pipelines', commitPipelinesPath: '/:project_id/commit/:sha/pipelines',
@ -64,6 +69,32 @@ const Api = {
}); });
}, },
groupPackages(id, options = {}) {
const url = Api.buildUrl(this.groupPackagesPath).replace(':id', id);
return axios.get(url, options);
},
projectPackages(id, options = {}) {
const url = Api.buildUrl(this.projectPackagesPath).replace(':id', id);
return axios.get(url, options);
},
buildProjectPackageUrl(projectId, packageId) {
return Api.buildUrl(this.projectPackagePath)
.replace(':id', projectId)
.replace(':package_id', packageId);
},
projectPackage(projectId, packageId) {
const url = this.buildProjectPackageUrl(projectId, packageId);
return axios.get(url);
},
deleteProjectPackage(projectId, packageId) {
const url = this.buildProjectPackageUrl(projectId, packageId);
return axios.delete(url);
},
groupMembers(id, options) { groupMembers(id, options) {
const url = Api.buildUrl(this.groupMembersPath).replace(':id', encodeURIComponent(id)); const url = Api.buildUrl(this.groupMembersPath).replace(':id', encodeURIComponent(id));
@ -308,9 +339,17 @@ const Api = {
.catch(() => flash(__('Something went wrong while fetching projects'))); .catch(() => flash(__('Something went wrong while fetching projects')));
}, },
commit(id, sha, params = {}) {
const url = Api.buildUrl(this.commitPath)
.replace(':id', encodeURIComponent(id))
.replace(':sha', encodeURIComponent(sha));
return axios.get(url, { params });
},
commitMultiple(id, data) { commitMultiple(id, data) {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions // see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const url = Api.buildUrl(Api.commitPath).replace(':id', encodeURIComponent(id)); const url = Api.buildUrl(Api.commitsPath).replace(':id', encodeURIComponent(id));
return axios.post(url, JSON.stringify(data), { return axios.post(url, JSON.stringify(data), {
headers: { headers: {
'Content-Type': 'application/json; charset=utf-8', 'Content-Type': 'application/json; charset=utf-8',

View file

@ -9,14 +9,10 @@ import { updateTooltipTitle } from './lib/utils/common_utils';
import { isInVueNoteablePage } from './lib/utils/dom_utils'; import { isInVueNoteablePage } from './lib/utils/dom_utils';
import flash from './flash'; import flash from './flash';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import * as Emoji from '~/emoji';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
const requestAnimationFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.setTimeout;
const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence
@ -619,7 +615,7 @@ export class AwardsHandler {
let awardsHandlerPromise = null; let awardsHandlerPromise = null;
export default function loadAwardsHandler(reload = false) { export default function loadAwardsHandler(reload = false) {
if (!awardsHandlerPromise || reload) { if (!awardsHandlerPromise || reload) {
awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji').then(Emoji => { awardsHandlerPromise = Emoji.initEmojiMap().then(() => {
const awardsHandler = new AwardsHandler(Emoji); const awardsHandler = new AwardsHandler(Emoji);
awardsHandler.bindEvents(); awardsHandler.bindEvents();
return awardsHandler; return awardsHandler;

View file

@ -164,7 +164,7 @@ export default {
<template> <template>
<form <form
:class="{ 'was-validated': wasValidated }" :class="{ 'was-validated': wasValidated }"
class="prepend-top-default append-bottom-default needs-validation" class="gl-mt-3 gl-mb-3 needs-validation"
novalidate novalidate
@submit.prevent.stop="onSubmit" @submit.prevent.stop="onSubmit"
> >

View file

@ -51,6 +51,7 @@ export default {
'scrollToDraft', 'scrollToDraft',
'toggleResolveDiscussion', 'toggleResolveDiscussion',
]), ]),
...mapActions(['setSelectedCommentPositionHover']),
update(data) { update(data) {
this.updateDraft(data); this.updateDraft(data);
}, },
@ -67,12 +68,16 @@ export default {
}; };
</script> </script>
<template> <template>
<article class="draft-note-component note-wrapper"> <article
class="draft-note-component note-wrapper"
@mouseenter="setSelectedCommentPositionHover(draft.position.line_range)"
@mouseleave="setSelectedCommentPositionHover()"
>
<ul class="notes draft-notes"> <ul class="notes draft-notes">
<noteable-note <noteable-note
:note="draft" :note="draft"
:diff-lines="diffFile.highlighted_diff_lines"
:line="line" :line="line"
:discussion-root="true"
class="draft-note" class="draft-note"
@handleEdit="handleEditing" @handleEdit="handleEditing"
@cancelForm="handleNotEditing" @cancelForm="handleNotEditing"
@ -81,7 +86,7 @@ export default {
@handleUpdateNote="update" @handleUpdateNote="update"
@toggleResolveStatus="toggleResolveDiscussion(draft.id)" @toggleResolveStatus="toggleResolveDiscussion(draft.id)"
> >
<strong slot="note-header-info" class="badge draft-pending-label append-right-4"> <strong slot="note-header-info" class="badge draft-pending-label gl-mr-2">
{{ __('Pending') }} {{ __('Pending') }}
</strong> </strong>
</noteable-note> </noteable-note>

View file

@ -35,11 +35,15 @@ export default {
<tr :class="className" class="notes_holder"> <tr :class="className" class="notes_holder">
<td class="notes_line old"></td> <td class="notes_line old"></td>
<td class="notes-content parallel old" colspan="2"> <td class="notes-content parallel old" colspan="2">
<div v-if="leftDraft.isDraft" class="content"><draft-note :draft="leftDraft" /></div> <div v-if="leftDraft.isDraft" class="content">
<draft-note :draft="leftDraft" :line="line.left" />
</div>
</td> </td>
<td class="notes_line new"></td> <td class="notes_line new"></td>
<td class="notes-content parallel new" colspan="2"> <td class="notes-content parallel new" colspan="2">
<div v-if="rightDraft.isDraft" class="content"><draft-note :draft="rightDraft" /></div> <div v-if="rightDraft.isDraft" class="content">
<draft-note :draft="rightDraft" :line="line.right" />
</div>
</td> </td>
</tr> </tr>
</template> </template>

View file

@ -96,7 +96,7 @@ export default {
<preview-item :draft="draft" :is-last="isLast(index)" /> <preview-item :draft="draft" :is-last="isLast(index)" />
</li> </li>
</ul> </ul>
<gl-loading-icon v-else size="lg" class="prepend-top-default append-bottom-default" /> <gl-loading-icon v-else size="lg" class="gl-mt-3 gl-mb-3" />
</div> </div>
<div class="dropdown-footer"> <div class="dropdown-footer">
<publish-button <publish-button

View file

@ -52,14 +52,12 @@ export default {
}); });
}, },
linePosition() { linePosition() {
if (this.draft.position && this.draft.position.position_type === IMAGE_DIFF_POSITION_TYPE) { if (this.position?.position_type === IMAGE_DIFF_POSITION_TYPE) {
// eslint-disable-next-line @gitlab/require-i18n-strings // eslint-disable-next-line @gitlab/require-i18n-strings
return `${this.draft.position.x}x ${this.draft.position.y}y`; return `${this.position.x}x ${this.position.y}y`;
} }
const position = this.discussion ? this.discussion.position : this.draft.position; return this.position?.new_line || this.position?.old_line;
return position?.new_line || position?.old_line;
}, },
content() { content() {
const el = document.createElement('div'); const el = document.createElement('div');
@ -70,11 +68,14 @@ export default {
showLinePosition() { showLinePosition() {
return this.draft.file_hash || this.isDiffDiscussion; return this.draft.file_hash || this.isDiffDiscussion;
}, },
position() {
return this.draft.position || this.discussion.position;
},
startLineNumber() { startLineNumber() {
return getStartLineNumber(this.draft.position?.line_range); return getStartLineNumber(this.position?.line_range);
}, },
endLineNumber() { endLineNumber() {
return getEndLineNumber(this.draft.position?.line_range); return getEndLineNumber(this.position?.line_range);
}, },
}, },
methods: { methods: {

View file

@ -0,0 +1,41 @@
import $ from 'jquery';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
/**
* This behavior collapses the right sidebar
* if the window size changes
*
* @sentrify
*/
export default () => {
const $sidebarGutterToggle = $('.js-sidebar-toggle');
let bootstrapBreakpoint = bp.getBreakpointSize();
$(window).on('resize.app', () => {
const oldBootstrapBreakpoint = bootstrapBreakpoint;
bootstrapBreakpoint = bp.getBreakpointSize();
if (bootstrapBreakpoint !== oldBootstrapBreakpoint) {
const breakpointSizes = ['md', 'sm', 'xs'];
if (breakpointSizes.includes(bootstrapBreakpoint)) {
const $gutterIcon = $sidebarGutterToggle.find('i');
if ($gutterIcon.hasClass('fa-angle-double-right')) {
$sidebarGutterToggle.trigger('click');
}
const sidebarGutterVueToggleEl = document.querySelector('.js-sidebar-vue-toggle');
// Sidebar has an icon which corresponds to collapsing the sidebar
// only then trigger the click.
if (sidebarGutterVueToggleEl) {
const collapseIcon = sidebarGutterVueToggleEl.querySelector('i.fa-angle-double-right');
if (collapseIcon) {
collapseIcon.click();
}
}
}
}
});
};

View file

@ -1,22 +1,51 @@
import 'document-register-element'; import 'document-register-element';
import isEmojiUnicodeSupported from '../emoji/support'; import isEmojiUnicodeSupported from '../emoji/support';
import { initEmojiMap, getEmojiInfo, emojiFallbackImageSrc, emojiImageTag } from '../emoji';
class GlEmoji extends HTMLElement { class GlEmoji extends HTMLElement {
constructor() { constructor() {
super(); super();
const emojiUnicode = this.textContent.trim(); this.initialize();
const { name, unicodeVersion, fallbackSrc, fallbackSpriteClass } = this.dataset; }
initialize() {
let emojiUnicode = this.textContent.trim();
const { fallbackSpriteClass, fallbackSrc } = this.dataset;
let { name, unicodeVersion } = this.dataset;
return initEmojiMap().then(() => {
if (!unicodeVersion) {
const emojiInfo = getEmojiInfo(name);
if (emojiInfo) {
if (name !== emojiInfo.name) {
({ name } = emojiInfo);
this.dataset.name = emojiInfo.name;
}
unicodeVersion = emojiInfo.u;
this.dataset.unicodeVersion = unicodeVersion;
emojiUnicode = emojiInfo.e;
this.innerHTML = emojiInfo.e;
this.title = emojiInfo.d;
}
}
const isEmojiUnicode = const isEmojiUnicode =
this.childNodes && this.childNodes &&
Array.prototype.every.call(this.childNodes, childNode => childNode.nodeType === 3); Array.prototype.every.call(this.childNodes, childNode => childNode.nodeType === 3);
const hasImageFallback = fallbackSrc && fallbackSrc.length > 0;
const hasCssSpriteFalback = fallbackSpriteClass && fallbackSpriteClass.length > 0;
if (emojiUnicode && isEmojiUnicode && !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)) { if (
emojiUnicode &&
isEmojiUnicode &&
!isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)
) {
const hasImageFallback = fallbackSrc && fallbackSrc.length > 0;
const hasCssSpriteFallback = fallbackSpriteClass && fallbackSpriteClass.length > 0;
// CSS sprite fallback takes precedence over image fallback // CSS sprite fallback takes precedence over image fallback
if (hasCssSpriteFalback) { if (hasCssSpriteFallback) {
if (!gon.emoji_sprites_css_added && gon.emoji_sprites_css_path) { if (!gon.emoji_sprites_css_added && gon.emoji_sprites_css_path) {
const emojiSpriteLinkTag = document.createElement('link'); const emojiSpriteLinkTag = document.createElement('link');
emojiSpriteLinkTag.setAttribute('rel', 'stylesheet'); emojiSpriteLinkTag.setAttribute('rel', 'stylesheet');
@ -27,22 +56,15 @@ class GlEmoji extends HTMLElement {
// IE 11 doesn't like adding multiple at once :( // IE 11 doesn't like adding multiple at once :(
this.classList.add('emoji-icon'); this.classList.add('emoji-icon');
this.classList.add(fallbackSpriteClass); this.classList.add(fallbackSpriteClass);
} else { } else if (hasImageFallback) {
import(/* webpackChunkName: 'emoji' */ '../emoji')
.then(({ emojiImageTag, emojiFallbackImageSrc }) => {
if (hasImageFallback) {
this.innerHTML = emojiImageTag(name, fallbackSrc); this.innerHTML = emojiImageTag(name, fallbackSrc);
} else { } else {
const src = emojiFallbackImageSrc(name); const src = emojiFallbackImageSrc(name);
this.innerHTML = emojiImageTag(name, src); this.innerHTML = emojiImageTag(name, src);
} }
}) }
.catch(() => {
// do nothing
}); });
} }
}
}
} }
export default function installGlEmojiElement() { export default function installGlEmojiElement() {

View file

@ -11,9 +11,13 @@ import './requires_input';
import initPageShortcuts from './shortcuts'; import initPageShortcuts from './shortcuts';
import './toggler_behavior'; import './toggler_behavior';
import './preview_markdown'; import './preview_markdown';
import initCollapseSidebarOnWindowResize from './collapse_sidebar_on_window_resize';
import initSelect2Dropdowns from './select2';
installGlEmojiElement(); installGlEmojiElement();
initGFMInput(); initGFMInput();
initCopyAsGFM(); initCopyAsGFM();
initCopyToClipboard(); initCopyToClipboard();
initPageShortcuts(); initPageShortcuts();
initCollapseSidebarOnWindowResize();
initSelect2Dropdowns();

View file

@ -1,5 +1,5 @@
import $ from 'jquery'; import $ from 'jquery';
import { getSelectedFragment } from '~/lib/utils/common_utils'; import { getSelectedFragment, insertText } from '~/lib/utils/common_utils';
export class CopyAsGFM { export class CopyAsGFM {
constructor() { constructor() {
@ -79,7 +79,7 @@ export class CopyAsGFM {
} }
static insertPastedText(target, text, gfm) { static insertPastedText(target, text, gfm) {
window.gl.utils.insertText(target, textBefore => { insertText(target, textBefore => {
// If the text before the cursor contains an odd number of backticks, // If the text before the cursor contains an odd number of backticks,
// we are either inside an inline code span that starts with 1 backtick // we are either inside an inline code span that starts with 1 backtick
// or a code block that starts with 3 backticks. // or a code block that starts with 3 backticks.

View file

@ -174,7 +174,7 @@ export default function renderMermaid($els) {
if (!$els.length) return; if (!$els.length) return;
const visibleMermaids = $els.filter(function filter() { const visibleMermaids = $els.filter(function filter() {
return $(this).closest('details').length === 0; return $(this).closest('details').length === 0 && $(this).is(':visible');
}); });
renderMermaids(visibleMermaids); renderMermaids(visibleMermaids);

View file

@ -0,0 +1,23 @@
import $ from 'jquery';
export default () => {
if ($('select.select2').length) {
import(/* webpackChunkName: 'select2' */ 'select2/select2')
.then(() => {
$('select.select2').select2({
width: 'resolve',
minimumResultsForSearch: 10,
dropdownAutoWidth: true,
});
// Close select2 on escape
$('.js-select2').on('select2-close', () => {
setTimeout(() => {
$('.select2-container-active').removeClass('select2-container-active');
$(':focus').blur();
}, 1);
});
})
.catch(() => {});
}
};

View file

@ -1,11 +1,10 @@
<script> <script>
import { GlToggle, GlSprintf } from '@gitlab/ui'; import { GlToggle } from '@gitlab/ui';
import AccessorUtilities from '~/lib/utils/accessor'; import AccessorUtilities from '~/lib/utils/accessor';
import { disableShortcuts, enableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle'; import { disableShortcuts, enableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
export default { export default {
components: { components: {
GlSprintf,
GlToggle, GlToggle,
}, },
data() { data() {
@ -32,29 +31,10 @@ export default {
<gl-toggle <gl-toggle
v-model="shortcutsEnabled" v-model="shortcutsEnabled"
aria-describedby="shortcutsToggle" aria-describedby="shortcutsToggle"
class="prepend-left-10 mb-0" label="Keyboard shortcuts"
label-position="right" label-position="left"
@change="onChange" @change="onChange"
> />
<template #labelOn>
<gl-sprintf
:message="__('%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled')"
>
<template #screenreaderOnly="{ content }">
<span class="sr-only">{{ content }}</span>
</template>
</gl-sprintf>
</template>
<template #labelOff>
<gl-sprintf
:message="__('%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled')"
>
<template #screenreaderOnly="{ content }">
<span class="sr-only">{{ content }}</span>
</template>
</gl-sprintf>
</template>
</gl-toggle>
<div id="shortcutsToggle" class="sr-only">{{ __('Enable or disable keyboard shortcuts') }}</div> <div id="shortcutsToggle" class="sr-only">{{ __('Enable or disable keyboard shortcuts') }}</div>
</div> </div>
</template> </template>

View file

@ -1,5 +1,5 @@
<script> <script>
import { GlDeprecatedButton, GlButtonGroup, GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { GlButton, GlButtonGroup, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { import {
RICH_BLOB_VIEWER, RICH_BLOB_VIEWER,
RICH_BLOB_VIEWER_TITLE, RICH_BLOB_VIEWER_TITLE,
@ -11,7 +11,7 @@ export default {
components: { components: {
GlIcon, GlIcon,
GlButtonGroup, GlButtonGroup,
GlDeprecatedButton, GlButton,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
@ -46,7 +46,7 @@ export default {
</script> </script>
<template> <template>
<gl-button-group class="js-blob-viewer-switcher mx-2"> <gl-button-group class="js-blob-viewer-switcher mx-2">
<gl-deprecated-button <gl-button
v-gl-tooltip.hover v-gl-tooltip.hover
:aria-label="$options.SIMPLE_BLOB_VIEWER_TITLE" :aria-label="$options.SIMPLE_BLOB_VIEWER_TITLE"
:title="$options.SIMPLE_BLOB_VIEWER_TITLE" :title="$options.SIMPLE_BLOB_VIEWER_TITLE"
@ -55,8 +55,8 @@ export default {
@click="switchToViewer($options.SIMPLE_BLOB_VIEWER)" @click="switchToViewer($options.SIMPLE_BLOB_VIEWER)"
> >
<gl-icon name="code" :size="14" /> <gl-icon name="code" :size="14" />
</gl-deprecated-button> </gl-button>
<gl-deprecated-button <gl-button
v-gl-tooltip.hover v-gl-tooltip.hover
:aria-label="$options.RICH_BLOB_VIEWER_TITLE" :aria-label="$options.RICH_BLOB_VIEWER_TITLE"
:title="$options.RICH_BLOB_VIEWER_TITLE" :title="$options.RICH_BLOB_VIEWER_TITLE"
@ -65,6 +65,6 @@ export default {
@click="switchToViewer($options.RICH_BLOB_VIEWER)" @click="switchToViewer($options.RICH_BLOB_VIEWER)"
> >
<gl-icon name="document" :size="14" /> <gl-icon name="document" :size="14" />
</gl-deprecated-button> </gl-button>
</gl-button-group> </gl-button-group>
</template> </template>

View file

@ -25,7 +25,7 @@ export const BLOB_RENDER_ERRORS = {
TOO_LARGE: { TOO_LARGE: {
id: 'too_large', id: 'too_large',
text: sprintf(__('it is larger than %{limit}'), { text: sprintf(__('it is larger than %{limit}'), {
limit: numberToHumanSize(104857600), // 100MB in bytes limit: numberToHumanSize(10485760), // 10MB in bytes
}), }),
}, },
EXTERNAL: { EXTERNAL: {

View file

@ -62,9 +62,7 @@ export default {
</script> </script>
<template> <template>
<div <div class="js-notebook-viewer-mounted container-fluid md gl-mt-3 gl-mb-3">
class="js-notebook-viewer-mounted container-fluid md prepend-top-default append-bottom-default"
>
<div v-if="loading && !error" class="text-center loading"> <div v-if="loading && !error" class="text-center loading">
<gl-loading-icon class="mt-5" size="lg" /> <gl-loading-icon class="mt-5" size="lg" />
</div> </div>

View file

@ -34,7 +34,7 @@ export default {
</script> </script>
<template> <template>
<div class="js-pdf-viewer container-fluid md prepend-top-default append-bottom-default"> <div class="js-pdf-viewer container-fluid md gl-mt-3 gl-mb-3">
<div v-if="loading && !error" class="text-center loading"> <div v-if="loading && !error" class="text-center loading">
<gl-loading-icon class="mt-5" size="lg" /> <gl-loading-icon class="mt-5" size="lg" />
</div> </div>

View file

@ -56,7 +56,7 @@ export default class SketchLoader {
error() { error() {
const errorMsg = document.createElement('p'); const errorMsg = document.createElement('p');
errorMsg.className = 'prepend-top-default append-bottom-default text-center'; errorMsg.className = 'gl-mt-3 gl-mb-3 text-center';
errorMsg.textContent = __(` errorMsg.textContent = __(`
Cannot show preview. For previews on sketch files, they must have the file format Cannot show preview. For previews on sketch files, they must have the file format
introduced by Sketch version 43 and above. introduced by Sketch version 43 and above.

View file

@ -2,7 +2,6 @@
import { GlPopover, GlSprintf, GlDeprecatedButton, GlIcon } from '@gitlab/ui'; import { GlPopover, GlSprintf, GlDeprecatedButton, GlIcon } from '@gitlab/ui';
import { parseBoolean, scrollToElement, setCookie, getCookie } from '~/lib/utils/common_utils'; import { parseBoolean, scrollToElement, setCookie, getCookie } from '~/lib/utils/common_utils';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { glEmojiTag } from '~/emoji';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
const trackingMixin = Tracking.mixin(); const trackingMixin = Tracking.mixin();
@ -11,14 +10,16 @@ const popoverStates = {
suggest_gitlab_ci_yml: { suggest_gitlab_ci_yml: {
title: s__(`suggestPipeline|1/2: Choose a template`), title: s__(`suggestPipeline|1/2: Choose a template`),
content: s__( content: s__(
`suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way youll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}`, `suggestPipeline|Were adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box.`,
),
footer: s__(
`suggestPipeline|Choose %{boldStart}Code Quality%{boldEnd} to add a pipeline that tests the quality of your code.`,
), ),
emoji: glEmojiTag('wave'),
}, },
suggest_commit_first_project_gitlab_ci_yml: { suggest_commit_first_project_gitlab_ci_yml: {
title: s__(`suggestPipeline|2/2: Commit your changes`), title: s__(`suggestPipeline|2/2: Commit your changes`),
content: s__( content: s__(
`suggestPipeline|Commit the changes and your pipeline will automatically run for the first time.`, `suggestPipeline|The template is ready! You can now commit it to create your first pipeline.`,
), ),
}, },
}; };
@ -66,6 +67,9 @@ export default {
suggestContent() { suggestContent() {
return popoverStates[this.trackLabel].content || ''; return popoverStates[this.trackLabel].content || '';
}, },
suggestFooter() {
return popoverStates[this.trackLabel].footer || '';
},
emoji() { emoji() {
return popoverStates[this.trackLabel].emoji || ''; return popoverStates[this.trackLabel].emoji || '';
}, },
@ -123,16 +127,13 @@ export default {
</span> </span>
</template> </template>
<gl-sprintf :message="suggestContent"> <gl-sprintf :message="suggestContent" />
<template #bold="{content}"> <div class="mt-3">
<gl-sprintf :message="suggestFooter">
<template #bold="{ content }">
<strong> {{ content }} </strong> <strong> {{ content }} </strong>
</template> </template>
<template #footer="{content}">
<div class="mt-3">
{{ content }}
<span v-html="emoji"></span>
</div>
</template>
</gl-sprintf> </gl-sprintf>
</div>
</gl-popover> </gl-popover>
</template> </template>

View file

@ -3,6 +3,7 @@ import '~/behaviors/markdown/render_gfm';
import Flash from '../../flash'; import Flash from '../../flash';
import { handleLocationHash } from '../../lib/utils/common_utils'; import { handleLocationHash } from '../../lib/utils/common_utils';
import axios from '../../lib/utils/axios_utils'; import axios from '../../lib/utils/axios_utils';
import eventHub from '../../notes/event_hub';
import { __ } from '~/locale'; import { __ } from '~/locale';
const loadRichBlobViewer = type => { const loadRichBlobViewer = type => {
@ -178,6 +179,10 @@ export default class BlobViewer {
viewer.innerHTML = data.html; viewer.innerHTML = data.html;
viewer.setAttribute('data-loaded', 'true'); viewer.setAttribute('data-loaded', 'true');
if (window.gon?.features?.codeNavigation) {
eventHub.$emit('showBlobInteractionZones', viewer.dataset.path);
}
return viewer; return viewer;
}); });
} }

View file

@ -5,7 +5,7 @@ import NewCommitForm from '../new_commit_form';
import EditBlob from './edit_blob'; import EditBlob from './edit_blob';
import BlobFileDropzone from '../blob/blob_file_dropzone'; import BlobFileDropzone from '../blob/blob_file_dropzone';
import initPopover from '~/blob/suggest_gitlab_ci_yml'; import initPopover from '~/blob/suggest_gitlab_ci_yml';
import { setCookie } from '~/lib/utils/common_utils'; import { disableButtonIfEmptyField, setCookie } from '~/lib/utils/common_utils';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
export default () => { export default () => {
@ -51,10 +51,7 @@ export default () => {
new BlobFileDropzone(uploadBlobForm, method); new BlobFileDropzone(uploadBlobForm, method);
new NewCommitForm(uploadBlobForm); new NewCommitForm(uploadBlobForm);
window.gl.utils.disableButtonIfEmptyField( disableButtonIfEmptyField(uploadBlobForm.find('.js-commit-message'), '.btn-upload-file');
uploadBlobForm.find('.js-commit-message'),
'.btn-upload-file',
);
} }
if (deleteBlobForm.length) { if (deleteBlobForm.length) {

View file

@ -0,0 +1,4 @@
import { __ } from '~/locale';
export const BLOB_EDITOR_ERROR = __('An error occurred while rendering the editor');
export const BLOB_PREVIEW_ERROR = __('An error occurred previewing the blob');

View file

@ -3,39 +3,87 @@
import $ from 'jquery'; import $ from 'jquery';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { __ } from '~/locale'; import { BLOB_EDITOR_ERROR, BLOB_PREVIEW_ERROR } from './constants';
import TemplateSelectorMediator from '../blob/file_template_mediator'; import TemplateSelectorMediator from '../blob/file_template_mediator';
import getModeByFileExtension from '~/lib/utils/ace_utils'; import getModeByFileExtension from '~/lib/utils/ace_utils';
import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown'; import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
const monacoEnabledGlobally = window.gon.features?.monacoBlobs;
export default class EditBlob { export default class EditBlob {
// The options object has: // The options object has:
// assetsPath, filePath, currentAction, projectId, isMarkdown // assetsPath, filePath, currentAction, projectId, isMarkdown
constructor(options) { constructor(options) {
this.options = options; this.options = options;
this.configureAceEditor(); this.options.monacoEnabled = this.options.monacoEnabled ?? monacoEnabledGlobally;
const { isMarkdown, monacoEnabled } = this.options;
return Promise.resolve()
.then(() => {
return monacoEnabled ? this.configureMonacoEditor() : this.configureAceEditor();
})
.then(() => {
this.initModePanesAndLinks(); this.initModePanesAndLinks();
this.initSoftWrap();
this.initFileSelectors(); this.initFileSelectors();
this.initSoftWrap();
if (isMarkdown) {
addEditorMarkdownListeners(this.editor);
}
this.editor.focus();
})
.catch(() => createFlash(BLOB_EDITOR_ERROR));
}
configureMonacoEditor() {
const EditorPromise = import(
/* webpackChunkName: 'monaco_editor_lite' */ '~/editor/editor_lite'
);
const MarkdownExtensionPromise = this.options.isMarkdown
? import('~/editor/editor_markdown_ext')
: Promise.resolve(false);
return Promise.all([EditorPromise, MarkdownExtensionPromise])
.then(([EditorModule, MarkdownExtension]) => {
const EditorLite = EditorModule.default;
const editorEl = document.getElementById('editor');
const fileNameEl =
document.getElementById('file_path') || document.getElementById('file_name');
const fileContentEl = document.getElementById('file-content');
const form = document.querySelector('.js-edit-blob-form');
this.editor = new EditorLite();
if (MarkdownExtension) {
this.editor.use(MarkdownExtension.default);
}
this.editor.createInstance({
el: editorEl,
blobPath: fileNameEl.value,
blobContent: editorEl.innerText,
});
fileNameEl.addEventListener('change', () => {
this.editor.updateModelLanguage(fileNameEl.value);
});
form.addEventListener('submit', () => {
fileContentEl.value = this.editor.getValue();
});
})
.catch(() => createFlash(BLOB_EDITOR_ERROR));
} }
configureAceEditor() { configureAceEditor() {
const { filePath, assetsPath, isMarkdown } = this.options; const { filePath, assetsPath } = this.options;
ace.config.set('modePath', `${assetsPath}/ace`); ace.config.set('modePath', `${assetsPath}/ace`);
ace.config.loadModule('ace/ext/searchbox'); ace.config.loadModule('ace/ext/searchbox');
ace.config.loadModule('ace/ext/modelist'); ace.config.loadModule('ace/ext/modelist');
this.editor = ace.edit('editor'); this.editor = ace.edit('editor');
if (isMarkdown) {
addEditorMarkdownListeners(this.editor);
}
// This prevents warnings re: automatic scrolling being logged // This prevents warnings re: automatic scrolling being logged
this.editor.$blockScrolling = Infinity; this.editor.$blockScrolling = Infinity;
this.editor.focus();
if (filePath) { if (filePath) {
this.editor.getSession().setMode(getModeByFileExtension(filePath)); this.editor.getSession().setMode(getModeByFileExtension(filePath));
} }
@ -81,7 +129,7 @@ export default class EditBlob {
currentPane.empty().append(data); currentPane.empty().append(data);
currentPane.renderGFM(); currentPane.renderGFM();
}) })
.catch(() => createFlash(__('An error occurred previewing the blob'))); .catch(() => createFlash(BLOB_PREVIEW_ERROR));
} }
this.$toggleButton.show(); this.$toggleButton.show();
@ -90,14 +138,19 @@ export default class EditBlob {
} }
initSoftWrap() { initSoftWrap() {
this.isSoftWrapped = false; this.isSoftWrapped = Boolean(this.options.monacoEnabled);
this.$toggleButton = $('.soft-wrap-toggle'); this.$toggleButton = $('.soft-wrap-toggle');
this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
this.$toggleButton.on('click', () => this.toggleSoftWrap()); this.$toggleButton.on('click', () => this.toggleSoftWrap());
} }
toggleSoftWrap() { toggleSoftWrap() {
this.isSoftWrapped = !this.isSoftWrapped; this.isSoftWrapped = !this.isSoftWrapped;
this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped); this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
if (this.options.monacoEnabled) {
this.editor.updateOptions({ wordWrap: this.isSoftWrapped ? 'on' : 'off' });
} else {
this.editor.getSession().setUseWrapMode(this.isSoftWrapped); this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
} }
}
} }

View file

@ -1,192 +0,0 @@
import $ from 'jquery';
import Sortable from 'sortablejs';
import Vue from 'vue';
import { GlButtonGroup, GlDeprecatedButton, GlLabel, GlTooltip } from '@gitlab/ui';
import isWipLimitsOn from 'ee_else_ce/boards/mixins/is_wip_limits';
import { s__, __, sprintf } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import Tooltip from '~/vue_shared/directives/tooltip';
import AccessorUtilities from '../../lib/utils/accessor';
import BoardBlankState from './board_blank_state.vue';
import BoardDelete from './board_delete';
import BoardList from './board_list.vue';
import IssueCount from './issue_count.vue';
import boardsStore from '../stores/boards_store';
import { getBoardSortableDefaultOptions, sortableEnd } from '../mixins/sortable_default_options';
import { ListType } from '../constants';
import { isScopedLabel } from '~/lib/utils/common_utils';
/**
* Please don't edit this file, have a look at:
* ./board_column.vue
* https://gitlab.com/gitlab-org/gitlab/-/issues/212300
*
* This file here will be deleted soon
* @deprecated
*/
export default Vue.extend({
components: {
BoardBlankState,
BoardDelete,
BoardList,
Icon,
GlButtonGroup,
IssueCount,
GlDeprecatedButton,
GlLabel,
GlTooltip,
},
directives: {
Tooltip,
},
mixins: [isWipLimitsOn],
props: {
list: {
type: Object,
default: () => ({}),
required: false,
},
disabled: {
type: Boolean,
required: true,
},
issueLinkBase: {
type: String,
required: true,
},
rootPath: {
type: String,
required: true,
},
boardId: {
type: String,
required: true,
},
// Does not do anything but is used
// to support the API of the new board_column.vue
canAdminList: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
detailIssue: boardsStore.detail,
filter: boardsStore.filter,
};
},
computed: {
isLoggedIn() {
return Boolean(gon.current_user_id);
},
showListHeaderButton() {
return (
!this.disabled && this.list.type !== ListType.closed && this.list.type !== ListType.blank
);
},
issuesTooltip() {
const { issuesSize } = this.list;
return sprintf(__('%{issuesSize} issues'), { issuesSize });
},
// Only needed to make karma pass.
weightCountToolTip() {}, // eslint-disable-line vue/return-in-computed-property
caretTooltip() {
return this.list.isExpanded ? s__('Boards|Collapse') : s__('Boards|Expand');
},
isNewIssueShown() {
return this.list.type === ListType.backlog || this.showListHeaderButton;
},
isSettingsShown() {
return (
this.list.type !== ListType.backlog &&
this.showListHeaderButton &&
this.list.isExpanded &&
this.isWipLimitsOn
);
},
showBoardListAndBoardInfo() {
return this.list.type !== ListType.blank && this.list.type !== ListType.promotion;
},
uniqueKey() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `boards.${this.boardId}.${this.list.type}.${this.list.id}`;
},
},
watch: {
filter: {
handler() {
this.list.page = 1;
this.list.getIssues(true).catch(() => {
// TODO: handle request error
});
},
deep: true,
},
},
mounted() {
const instance = this;
const sortableOptions = getBoardSortableDefaultOptions({
disabled: this.disabled,
group: 'boards',
draggable: '.is-draggable',
handle: '.js-board-handle',
onEnd(e) {
sortableEnd();
const sortable = this;
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
const order = sortable.toArray();
const list = boardsStore.findList('id', parseInt(e.item.dataset.id, 10));
instance.$nextTick(() => {
boardsStore.moveList(list, order);
});
}
},
});
Sortable.create(this.$el.parentNode, sortableOptions);
},
created() {
if (
this.list.isExpandable &&
AccessorUtilities.isLocalStorageAccessSafe() &&
!this.isLoggedIn
) {
const isCollapsed = localStorage.getItem(`${this.uniqueKey}.expanded`) === 'false';
this.list.isExpanded = !isCollapsed;
}
},
methods: {
showScopedLabels(label) {
return boardsStore.scopedLabels.enabled && isScopedLabel(label);
},
showNewIssueForm() {
this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
},
toggleExpanded() {
if (this.list.isExpandable) {
this.list.isExpanded = !this.list.isExpanded;
if (AccessorUtilities.isLocalStorageAccessSafe() && !this.isLoggedIn) {
localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded);
}
if (this.isLoggedIn) {
this.list.update();
}
// When expanding/collapsing, the tooltip on the caret button sometimes stays open.
// Close all tooltips manually to prevent dangling tooltips.
$('.tooltip').tooltip('hide');
}
},
},
template: '#js-board-template',
});

View file

@ -54,7 +54,7 @@ export default {
<div> <div>
<div <div
v-if="!isSwimlanesOn" v-if="!isSwimlanesOn"
class="boards-list w-100 py-3 px-2 text-nowrap" class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
data-qa-selector="boards_list" data-qa-selector="boards_list"
> >
<board-column <board-column
@ -77,6 +77,7 @@ export default {
:can-admin-list="canAdminList" :can-admin-list="canAdminList"
:disabled="disabled" :disabled="disabled"
:board-id="boardId" :board-id="boardId"
:group-id="groupId"
/> />
</div> </div>
</template> </template>

View file

@ -5,10 +5,11 @@ import {
GlLabel, GlLabel,
GlTooltip, GlTooltip,
GlIcon, GlIcon,
GlSprintf,
GlTooltipDirective, GlTooltipDirective,
} from '@gitlab/ui'; } from '@gitlab/ui';
import isWipLimitsOn from 'ee_else_ce/boards/mixins/is_wip_limits'; import isWipLimitsOn from 'ee_else_ce/boards/mixins/is_wip_limits';
import { s__, __, sprintf } from '~/locale'; import { n__, s__ } from '~/locale';
import AccessorUtilities from '../../lib/utils/accessor'; import AccessorUtilities from '../../lib/utils/accessor';
import BoardDelete from './board_delete'; import BoardDelete from './board_delete';
import IssueCount from './issue_count.vue'; import IssueCount from './issue_count.vue';
@ -25,6 +26,7 @@ export default {
GlLabel, GlLabel,
GlTooltip, GlTooltip,
GlIcon, GlIcon,
GlSprintf,
IssueCount, IssueCount,
}, },
directives: { directives: {
@ -82,10 +84,20 @@ export default {
this.listType !== ListType.promotion this.listType !== ListType.promotion
); );
}, },
issuesTooltip() { showMilestoneListDetails() {
return (
this.list.type === 'milestone' &&
this.list.milestone &&
(this.list.isExpanded || !this.isSwimlanesHeader)
);
},
showAssigneeListDetails() {
return this.list.type === 'assignee' && (this.list.isExpanded || !this.isSwimlanesHeader);
},
issuesTooltipLabel() {
const { issuesSize } = this.list; const { issuesSize } = this.list;
return sprintf(__('%{issuesSize} issues'), { issuesSize }); return n__(`%d issue`, `%d issues`, issuesSize);
}, },
chevronTooltip() { chevronTooltip() {
return this.list.isExpanded ? s__('Boards|Collapse') : s__('Boards|Expand'); return this.list.isExpanded ? s__('Boards|Collapse') : s__('Boards|Expand');
@ -111,6 +123,9 @@ export default {
// eslint-disable-next-line @gitlab/require-i18n-strings // eslint-disable-next-line @gitlab/require-i18n-strings
return `boards.${this.boardId}.${this.listType}.${this.list.id}`; return `boards.${this.boardId}.${this.listType}.${this.list.id}`;
}, },
collapsedTooltipTitle() {
return this.listTitle || this.listAssignee;
},
}, },
methods: { methods: {
showScopedLabels(label) { showScopedLabels(label) {
@ -147,7 +162,7 @@ export default {
'has-border': list.label && list.label.color, 'has-border': list.label && list.label.color,
'gl-relative': list.isExpanded, 'gl-relative': list.isExpanded,
'gl-h-full': !list.isExpanded, 'gl-h-full': !list.isExpanded,
'board-inner gl-rounded-base gl-border-b-0': isSwimlanesHeader, 'board-inner gl-rounded-top-left-base gl-rounded-top-right-base': isSwimlanesHeader,
}" }"
:style="{ borderTopColor: list.label && list.label.color ? list.label.color : null }" :style="{ borderTopColor: list.label && list.label.color ? list.label.color : null }"
class="board-header gl-relative" class="board-header gl-relative"
@ -157,7 +172,9 @@ export default {
<h3 <h3
:class="{ :class="{
'user-can-drag': !disabled && !list.preset, 'user-can-drag': !disabled && !list.preset,
'gl-border-b-0': !list.isExpanded, 'gl-py-3': !list.isExpanded && !isSwimlanesHeader,
'gl-border-b-0': !list.isExpanded || isSwimlanesHeader,
'gl-py-2': !list.isExpanded && isSwimlanesHeader,
}" }"
class="board-title gl-m-0 gl-display-flex js-board-handle" class="board-title gl-m-0 gl-display-flex js-board-handle"
> >
@ -167,21 +184,17 @@ export default {
:aria-label="chevronTooltip" :aria-label="chevronTooltip"
:title="chevronTooltip" :title="chevronTooltip"
:icon="chevronIcon" :icon="chevronIcon"
class="board-title-caret no-drag" class="board-title-caret no-drag gl-cursor-pointer"
variant="link" variant="link"
@click="toggleExpanded" @click="toggleExpanded"
/> />
<!-- The following is only true in EE and if it is a milestone --> <!-- The following is only true in EE and if it is a milestone -->
<span <span v-if="showMilestoneListDetails" aria-hidden="true" class="gl-mr-2 milestone-icon">
v-if="list.type === 'milestone' && list.milestone"
aria-hidden="true"
class="gl-mr-2 milestone-icon"
>
<gl-icon name="timer" /> <gl-icon name="timer" />
</span> </span>
<a <a
v-if="list.type === 'assignee'" v-if="showAssigneeListDetails"
:href="list.assignee.path" :href="list.assignee.path"
class="user-avatar-link js-no-trigger" class="user-avatar-link js-no-trigger"
> >
@ -195,7 +208,10 @@ export default {
width="20" width="20"
/> />
</a> </a>
<div class="board-title-text"> <div
class="board-title-text"
:class="{ 'gl-display-none': !list.isExpanded && isSwimlanesHeader }"
>
<span <span
v-if="list.type !== 'label'" v-if="list.type !== 'label'"
v-gl-tooltip.hover v-gl-tooltip.hover
@ -208,7 +224,7 @@ export default {
{{ list.title }} {{ list.title }}
</span> </span>
<span v-if="list.type === 'assignee'" class="board-title-sub-text gl-ml-2"> <span v-if="list.type === 'assignee'" class="board-title-sub-text gl-ml-2">
@{{ list.assignee.username }} @{{ listAssignee }}
</span> </span>
<gl-label <gl-label
v-if="list.type === 'label'" v-if="list.type === 'label'"
@ -220,6 +236,33 @@ export default {
:title="list.label.title" :title="list.label.title"
/> />
</div> </div>
<span
v-if="isSwimlanesHeader && !list.isExpanded"
ref="collapsedInfo"
aria-hidden="true"
class="board-header-collapsed-info-icon gl-mt-2 gl-cursor-pointer gl-text-gray-700"
>
<gl-icon name="information" />
</span>
<gl-tooltip v-if="isSwimlanesHeader && !list.isExpanded" :target="() => $refs.collapsedInfo">
<div class="gl-font-weight-bold gl-pb-2">{{ collapsedTooltipTitle }}</div>
<div v-if="list.maxIssueCount !== 0">
&#8226;
<gl-sprintf :message="__('%{issuesSize} with a limit of %{maxIssueCount}')">
<template #issuesSize>{{ issuesTooltipLabel }}</template>
<template #maxIssueCount>{{ list.maxIssueCount }}</template>
</gl-sprintf>
</div>
<div v-else>&#8226; {{ issuesTooltipLabel }}</div>
<div v-if="weightFeatureAvailable">
&#8226;
<gl-sprintf :message="__('%{totalWeight} total weight')">
<template #totalWeight>{{ list.totalWeight }}</template>
</gl-sprintf>
</div>
</gl-tooltip>
<board-delete <board-delete
v-if="canAdminList && !list.preset && list.id" v-if="canAdminList && !list.preset && list.id"
:list="list" :list="list"
@ -229,7 +272,7 @@ export default {
v-gl-tooltip.hover.bottom v-gl-tooltip.hover.bottom
:class="{ 'gl-display-none': !list.isExpanded }" :class="{ 'gl-display-none': !list.isExpanded }"
:aria-label="__('Delete list')" :aria-label="__('Delete list')"
class="board-delete no-drag gl-pr-0 gl-shadow-none gl-mr-3" class="board-delete no-drag gl-pr-0 gl-shadow-none! gl-mr-3"
:title="__('Delete list')" :title="__('Delete list')"
icon="remove" icon="remove"
size="small" size="small"
@ -238,10 +281,11 @@ export default {
</board-delete> </board-delete>
<div <div
v-if="showBoardListAndBoardInfo" v-if="showBoardListAndBoardInfo"
class="issue-count-badge gl-pr-0 no-drag text-secondary" class="issue-count-badge gl-display-inline-flex gl-pr-0 no-drag text-secondary"
:class="{ 'gl-display-none': !list.isExpanded && isSwimlanesHeader }"
> >
<span class="gl-display-inline-flex"> <span class="gl-display-inline-flex">
<gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltip" /> <gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltipLabel" />
<span ref="issueCount" class="issue-count-badge-count"> <span ref="issueCount" class="issue-count-badge-count">
<gl-icon class="gl-mr-2" name="issues" /> <gl-icon class="gl-mr-2" name="issues" />
<issue-count :issues-size="list.issuesSize" :max-issue-count="list.maxIssueCount" /> <issue-count :issues-size="list.issuesSize" :max-issue-count="list.maxIssueCount" />

View file

@ -1,6 +1,6 @@
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import { GlDeprecatedButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { getMilestone } from 'ee_else_ce/boards/boards_util'; import { getMilestone } from 'ee_else_ce/boards/boards_util';
import ListIssue from 'ee_else_ce/boards/models/issue'; import ListIssue from 'ee_else_ce/boards/models/issue';
import eventHub from '../eventhub'; import eventHub from '../eventhub';
@ -11,7 +11,7 @@ export default {
name: 'BoardNewIssue', name: 'BoardNewIssue',
components: { components: {
ProjectSelect, ProjectSelect,
GlDeprecatedButton, GlButton,
}, },
props: { props: {
groupId: { groupId: {
@ -120,21 +120,18 @@ export default {
/> />
<project-select v-if="groupId" :group-id="groupId" :list="list" /> <project-select v-if="groupId" :group-id="groupId" :list="list" />
<div class="clearfix prepend-top-10"> <div class="clearfix prepend-top-10">
<gl-deprecated-button <gl-button
ref="submit-button" ref="submit-button"
:disabled="disabled" :disabled="disabled"
class="float-left" class="float-left"
variant="success" variant="success"
category="primary"
type="submit" type="submit"
>{{ __('Submit issue') }}</gl-deprecated-button >{{ __('Submit issue') }}</gl-button
>
<gl-deprecated-button
class="float-right"
type="button"
variant="default"
@click="cancel"
>{{ __('Cancel') }}</gl-deprecated-button
> >
<gl-button class="float-right" type="button" variant="default" @click="cancel">{{
__('Cancel')
}}</gl-button>
</div> </div>
</form> </form>
</div> </div>

View file

@ -233,7 +233,7 @@ export default {
</script> </script>
<template> <template>
<div class="boards-switcher js-boards-selector append-right-10"> <div class="boards-switcher js-boards-selector gl-mr-3">
<span class="boards-selector-wrapper js-boards-selector-wrapper"> <span class="boards-selector-wrapper js-boards-selector-wrapper">
<gl-dropdown <gl-dropdown
data-qa-selector="boards_dropdown" data-qa-selector="boards_dropdown"

View file

@ -153,7 +153,7 @@ export default {
v-gl-tooltip v-gl-tooltip
name="issue-block" name="issue-block"
:title="__('Blocked issue')" :title="__('Blocked issue')"
class="issue-blocked-icon append-right-4" class="issue-blocked-icon gl-mr-2"
:aria-label="__('Blocked issue')" :aria-label="__('Blocked issue')"
/> />
<icon <icon
@ -161,7 +161,7 @@ export default {
v-gl-tooltip v-gl-tooltip
name="eye-slash" name="eye-slash"
:title="__('Confidential')" :title="__('Confidential')"
class="confidential-icon append-right-4" class="confidential-icon gl-mr-2"
:aria-label="__('Confidential')" :aria-label="__('Confidential')"
/> />
<a :href="issue.path" :title="issue.title" class="js-no-trigger" @mousemove.stop>{{ <a :href="issue.path" :title="issue.title" class="js-no-trigger" @mousemove.stop>{{

View file

@ -72,7 +72,7 @@ export default {
<button <button
ref="selectAllBtn" ref="selectAllBtn"
type="button" type="button"
class="btn btn-success btn-inverted prepend-left-10" class="btn btn-success btn-inverted gl-ml-3"
@click="toggleAll" @click="toggleAll"
> >
{{ selectAllText }} {{ selectAllText }}

View file

@ -80,15 +80,7 @@ export default () => {
el: $boardApp, el: $boardApp,
components: { components: {
BoardContent, BoardContent,
Board: () => Board: () => import('ee_else_ce/boards/components/board_column.vue'),
window?.gon?.features?.sfcIssueBoards
? import('ee_else_ce/boards/components/board_column.vue')
: /**
* Please have a look at, we are moving to the SFC soon:
* https://gitlab.com/gitlab-org/gitlab/-/issues/212300
* @deprecated
*/
import('ee_else_ce/boards/components/board'),
BoardSidebar, BoardSidebar,
BoardAddIssuesModal, BoardAddIssuesModal,
BoardSettingsSidebar: () => BoardSettingsSidebar: () =>
@ -360,7 +352,7 @@ export default () => {
template: ` template: `
<div class="board-extra-actions"> <div class="board-extra-actions">
<button <button
class="btn btn-success prepend-left-10" class="btn btn-success gl-ml-3"
type="button" type="button"
data-placement="bottom" data-placement="bottom"
ref="addIssuesButton" ref="addIssuesButton"

Some files were not shown because too many files have changed in this diff Show more