New upstream version 13.2.1

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

1
.gitignore vendored
View file

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

View file

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

View file

@ -13,6 +13,7 @@
/doc/development/ @marcia @mjang1
/doc/development/documentation/ @mikelewis
/doc/ci @marcel.amirault @sselhorn
/doc/operations @aqualls @eread
/doc/user/clusters @aqualls
/doc/user/infrastructure @aqualls
/doc/user/project/clusters @aqualls
@ -43,17 +44,12 @@
# Feature specific owners
/ee/lib/ee/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/Security/ @plafoucriere @gonzoyumo @twoodham @sethgitlab
/ee/app/models/project_alias.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
/qa/ @gl-quality
@ -77,3 +73,9 @@ Dangerfile @gl-quality/eng-prod
/lib/gitlab/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
[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
- 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:
extends:
- .default-retry

View file

@ -2,16 +2,18 @@
extends:
- .default-retry
- .default-before_script
- .assets-compile-cache
variables:
SETUP_DB: "false"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
WEBPACK_VENDOR_DLL: "true"
.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
variables:
WEBPACK_VENDOR_DLL: "true"
stage: prepare
script:
- node --version
@ -90,21 +92,6 @@ update-yarn-cache:
cache:
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:
extends:
- .frontend-base
@ -114,6 +101,7 @@ build-assets-image:
needs: ["setup-test-env", "compile-test-assets"]
variables:
SETUP_DB: "true"
WEBPACK_VENDOR_DLL: "true"
script:
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
@ -138,22 +126,25 @@ frontend-fixtures-as-if-foss:
.frontend-test-base:
extends:
- .default-retry
- .frontend-base
- .yarn-cache
variables:
USE_BUNDLE_INSTALL: "false"
SETUP_DB: "false"
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:
extends: .frontend-test-base
variables:
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
script:
- source scripts/utils.sh
- export BABEL_ENV=coverage CHROME_LOG_FILE=chrome_debug.log
- run_timed_command "retry yarn install --frozen-lockfile"
- run_timed_command "yarn karma"
@ -174,6 +165,7 @@ karma:
- tmp/tests/frontend/
reports:
junit: junit_karma.xml
cobertura: coverage-javascript/cobertura-coverage.xml
karma-as-if-foss:
extends:
@ -185,7 +177,6 @@ karma-as-if-foss:
.jest-base:
extends: .frontend-test-base
script:
- source scripts/utils.sh
- run_timed_command "retry yarn install --frozen-lockfile"
- run_timed_command "yarn jest --ci --coverage --testSequencer ./scripts/frontend/parallel_ci_sequencer.js"
@ -211,7 +202,6 @@ jest-integration:
- .frontend-test-base
- .frontend:rules:default-frontend-jobs
script:
- source scripts/utils.sh
- run_timed_command "retry yarn install --frozen-lockfile"
- run_timed_command "yarn jest:integration --ci"
needs: ["frontend-fixtures"]
@ -236,11 +226,14 @@ coverage-frontend:
- run_timed_command "retry yarn install --frozen-lockfile"
script:
- run_timed_command "yarn node scripts/frontend/merge_coverage_frontend.js"
coverage: '/^Statements\s*:\s*?(\d+(?:\.\d+)?)%/'
artifacts:
name: coverage-frontend
expire_in: 31d
paths:
- coverage-frontend/
reports:
cobertura: coverage-frontend/cobertura-coverage.xml
.qa-frontend-node:
extends:

View file

@ -18,7 +18,7 @@
.rails-cache:
cache:
key: "rails-v1"
key: "rails-v2"
paths:
- vendor/ruby/
- vendor/gitaly-ruby/
@ -72,6 +72,15 @@
variables:
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:
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:
@ -82,6 +91,16 @@
variables:
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:
image:
name: gcr.io/kaniko-project/executor:debug-v0.20.0

View file

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

View file

@ -1,9 +1,129 @@
######################
# rspec job base specs
.rails-job-base:
extends:
- .default-retry
- .default-before_script
- .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 #
setup-test-env:
@ -86,73 +206,37 @@ downtime_check:
script:
- 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:
extends:
- .rspec-base-pg11
- .rspec-base-migration
parallel: 5
- .rspec-migration-parallel
rspec unit pg11:
extends: .rspec-base-pg11
parallel: 20
extends:
- .rspec-base-pg11
- .rails:rules:ee-and-foss-unit
- .rspec-unit-parallel
rspec integration pg11:
extends: .rspec-base-pg11
parallel: 8
extends:
- .rspec-base-pg11
- .rails:rules:ee-and-foss-integration
- .rspec-integration-parallel
rspec system pg11:
extends: .rspec-base-pg11
parallel: 24
extends:
- .rspec-base-pg11
- .rails:rules:ee-and-foss-system
- .rspec-system-parallel
rspec fast_spec_helper:
extends: .rspec-base-pg11
extends:
- .rspec-base-pg11
- .rails:rules:ee-and-foss-fast_spec_helper
script:
- 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:
extends: .db-job-base
script:
@ -216,7 +300,7 @@ gitlab:setup:
rspec:coverage:
extends:
- .rails-job-base
- .rails:rules:ee-mr-and-master-only
- .rails:rules:rspec-coverage
stage: post-test
# We cannot use needs since it would mean needing 84 jobs (since most are parallelized)
# so we use `dependencies` here.
@ -248,118 +332,180 @@ rspec:coverage:
- coverage/index.html
- coverage/assets/
- tmp/memory_test/
reports:
cobertura: coverage/coverage.xml
# EE/FOSS: 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:
extends:
- .rspec-base-pg11-as-if-foss
- .rspec-base-migration
parallel: 5
- .rails:rules:as-if-foss-migration
- .rspec-migration-parallel
rspec unit pg11-as-if-foss:
extends: .rspec-base-pg11-as-if-foss
parallel: 20
extends:
- .rspec-base-pg11-as-if-foss
- .rails:rules:as-if-foss-unit
- .rspec-unit-parallel
rspec integration pg11-as-if-foss:
extends: .rspec-base-pg11-as-if-foss
parallel: 8
extends:
- .rspec-base-pg11-as-if-foss
- .rails:rules:as-if-foss-integration
- .rspec-integration-parallel
rspec system pg11-as-if-foss:
extends: .rspec-base-pg11-as-if-foss
parallel: 24
extends:
- .rspec-base-pg11-as-if-foss
- .rails:rules:as-if-foss-system
- .rspec-system-parallel
rspec-ee migration pg11:
extends:
- .rspec-ee-base-pg11
- .rspec-base-migration
parallel: 2
- .rails:rules:ee-only-migration
- .rspec-ee-migration-parallel
rspec-ee unit pg11:
extends: .rspec-ee-base-pg11
parallel: 10
extends:
- .rspec-ee-base-pg11
- .rails:rules:ee-only-unit
- .rspec-ee-unit-parallel
rspec-ee integration pg11:
extends: .rspec-ee-base-pg11
parallel: 4
extends:
- .rspec-ee-base-pg11
- .rails:rules:ee-only-integration
- .rspec-ee-integration-parallel
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:
- .rspec-ee-base-geo
- .use-pg11-ee
- .rspec-ee-base-pg11
- .rails:rules:ee-only-system
- .rspec-ee-system-parallel
rspec-ee unit pg11 geo:
extends: .rspec-ee-base-geo-pg11
parallel: 2
extends:
- .rspec-ee-base-geo-pg11
- .rails:rules:ee-only-unit
- .rspec-ee-unit-geo-parallel
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:
extends: .rspec-ee-base-geo-pg11
extends:
- .rspec-ee-base-geo-pg11
- .rails:rules:ee-only-system
db:rollback geo:
extends:
- db:rollback
- .rails:rules:ee-only
- .rails:rules:ee-only-migration
script:
- bundle exec rake geo:db:migrate VERSION=20170627195211
- bundle exec rake geo:db:migrate
# 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
rspec foss-impact:
extends:
- .rspec-base
- .as-if-foss
- .rspec-base-pg11-as-if-foss
- .rails:rules:ee-mr-only
- .use-pg11
script:
- install_gitlab_gem
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
- source scripts/rspec_helpers.sh
- 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:
expire_in: 7d
paths:
- tmp/matching_foss_tests.txt
- tmp/capybara/
# EE: Merge Request pipelines
# EE: Canonical MR pipelines
##################################################

View file

@ -15,7 +15,7 @@ code_quality:
stage: test
needs: []
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:
- |
if ! docker info &>/dev/null; then
@ -59,6 +59,7 @@ code_quality:
SAST_ANALYZER_IMAGE_TAG: 2
SAST_BRAKEMAN_LEVEL: 2 # GitLab-specific
SAST_EXCLUDED_PATHS: qa,spec,doc,ee/spec # GitLab-specific
SAST_DISABLE_BABEL: "true"
script:
- /analyzer run
@ -72,11 +73,10 @@ eslint-sast:
image:
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:
# extends: .sast
# image:
# name: "$SAST_ANALYZER_IMAGE_PREFIX/nodejs-scan:$SAST_ANALYZER_IMAGE_TAG"
nodejs-scan-sast:
extends: .sast
image:
name: "$SAST_ANALYZER_IMAGE_PREFIX/nodejs-scan:$SAST_ANALYZER_IMAGE_TAG"
secrets-sast:
extends: .sast
@ -172,6 +172,7 @@ dependency_scanning:
# # - 'export DAST_AUTH_URL="${DAST_WEBSITE}/users/sign_in"'
# # - 'export DAST_PASSWORD="${REVIEW_APPS_ROOT_PASSWORD}"'
# - /analyze -t $DAST_WEBSITE
# timeout: 4h
# artifacts:
# paths:
# - 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:
extends:
- .default-retry
@ -27,25 +16,24 @@ review-cleanup:
- ruby -rrubygems scripts/review_apps/automated_cleanup.rb
- gcp_cleanup
# Temporarily disabling review apps
#review-build-cng:
# extends:
# - .default-retry
# - .review:rules:review-build-cng
# image: ruby:2.6-alpine
# stage: review-prepare
# before_script:
# - source scripts/utils.sh
# - install_api_client_dependencies_with_apk
# - install_gitlab_gem
# needs:
# - job: compile-production-assets
# artifacts: false
# script:
# - 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
# # to have to manually start the jobs in sequence, so we do it for them.
# - '[ -z $CI_JOB_MANUAL ] || play_job "review-deploy"'
review-build-cng:
extends:
- .default-retry
- .review:rules:review-build-cng
image: ruby:2.6-alpine
stage: review-prepare
before_script:
- source scripts/utils.sh
- install_api_client_dependencies_with_apk
- install_gitlab_gem
needs:
- job: compile-production-assets
artifacts: false
script:
- 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
# to have to manually start the jobs in sequence, so we do it for them.
- '[ -z $CI_JOB_MANUAL ] || play_job "review-deploy"'
.review-workflow-base:
extends:
@ -53,45 +41,46 @@ review-cleanup:
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-helm3-kubectl1.14
variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
REVIEW_APPS_DOMAIN: "temp.gitlab-review.app" # FIXME: using temporary domain
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
GITLAB_HELM_CHART_REF: "master"
GITLAB_HELM_CHART_REF: "v4.1.3"
environment:
name: review/${CI_COMMIT_REF_NAME}
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
on_stop: review-stop
auto_stop_in: 48 hours
# Temporarily disabling review apps
#review-deploy:
# extends:
# - .review-workflow-base
# - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
# stage: review
# dependencies: []
# resource_group: "review/${CI_COMMIT_REF_NAME}"
# before_script:
# - export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
# - export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
# - export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
# - echo "${CI_ENVIRONMENT_URL}" > environment_url.txt
# - source ./scripts/utils.sh
# - install_api_client_dependencies_with_apk
# - source scripts/review_apps/review-apps.sh
# script:
# - check_kube_domain
# - ensure_namespace
# - install_external_dns
# - download_chart
# - date
# - deploy || (display_deployment_debug && exit 1)
# # 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.
# - '[ -z $CI_JOB_MANUAL ] || play_job "review-qa-smoke"'
# - '[ -z $CI_JOB_MANUAL ] || play_job "review-performance"'
# artifacts:
# paths: [environment_url.txt]
# expire_in: 2 days
# when: always
review-deploy:
extends:
- .review-workflow-base
- .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
stage: review
dependencies: []
resource_group: "review/${CI_COMMIT_REF_NAME}"
before_script:
- export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
- export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
- export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
- echo "${CI_ENVIRONMENT_URL}" > environment_url.txt
- source ./scripts/utils.sh
- install_api_client_dependencies_with_apk
- source scripts/review_apps/review-apps.sh
script:
- check_kube_domain
- ensure_namespace
- install_external_dns
- download_chart
- date
- 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
# 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-performance"'
artifacts:
paths: [environment_url.txt]
expire_in: 2 days
when: always
.review-stop-base:
extends: .review-workflow-base
@ -124,110 +113,110 @@ review-stop:
script:
- delete_release
# Temporarily disabling review apps
#.review-qa-base:
# extends:
# - .default-retry
# - .use-docker-in-docker
# image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6
# stage: qa
# # This is needed so that manual jobs with needs don't block the pipeline.
# # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
# dependencies: ["review-deploy"]
# variables:
# QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
# QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
# QA_DEBUG: "true"
# GITLAB_USERNAME: "root"
# GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
# GITLAB_ADMIN_USERNAME: "root"
# GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
# GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
# EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
# before_script:
# - export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
# - export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
# - echo "${CI_ENVIRONMENT_URL}"
# - echo "${QA_IMAGE}"
# - source scripts/utils.sh
# - install_api_client_dependencies_with_apk
# - gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}}
# artifacts:
# paths:
# - ./qa/gitlab-qa-run-*
# expire_in: 7 days
# when: always
#
#review-qa-smoke:
# extends:
# - .review-qa-base
# - .review:rules:review-qa-smoke
# script:
# - gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
#
#review-qa-all:
# extends:
# - .review-qa-base
# - .review:rules:mr-only-manual
# parallel: 5
# script:
# - export KNAPSACK_REPORT_PATH=knapsack/master_report.json
# - 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
#
#review-performance:
# extends:
# - .default-retry
# - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
# image:
# name: sitespeedio/sitespeed.io:6.3.1
# entrypoint: [""]
# stage: qa
# # This is needed so that manual jobs with needs don't block the pipeline.
# # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
# dependencies: ["review-deploy"]
# before_script:
# - export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
# - echo "${CI_ENVIRONMENT_URL}"
# - mkdir -p gitlab-exporter
# - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
# - mkdir -p sitespeed-results
# script:
# - /start.sh --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "${CI_ENVIRONMENT_URL}"
# after_script:
# - mv sitespeed-results/data/performance.json performance.json
# artifacts:
# paths:
# - sitespeed-results/
# reports:
# performance: performance.json
# expire_in: 31d
#
#parallel-spec-reports:
# extends:
# - .review:rules:mr-only-manual
# image: ruby:2.6-alpine
# stage: post-qa
# dependencies: ["review-qa-all"]
# variables:
# NEW_PARALLEL_SPECS_REPORT: qa/report-new.html
# BASE_ARTIFACT_URL: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/qa/"
# script:
# - apk add --update build-base libxml2-dev libxslt-dev && rm -rf /var/cache/apk/*
# - gem install nokogiri --no-document
# - cd qa/gitlab-qa-run-*/gitlab-*
# - ARTIFACT_DIRS=$(pwd |rev| awk -F / '{print $1,$2}' | rev | sed s_\ _/_)
# - cd -
# - '[[ -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
# artifacts:
# when: always
# paths:
# - qa/report-new.html
# - qa/gitlab-qa-run-*
# reports:
# junit: qa/gitlab-qa-run-*/**/rspec-*.xml
# expire_in: 31d
.review-qa-base:
extends:
- .default-retry
- .use-docker-in-docker
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6
stage: qa
# This is needed so that manual jobs with needs don't block the pipeline.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
dependencies: ["review-deploy"]
variables:
QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
QA_DEBUG: "true"
GITLAB_USERNAME: "root"
GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
GITLAB_ADMIN_USERNAME: "root"
GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
SIGNUP_DISABLED: "true"
before_script:
- export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
- export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
- echo "${CI_ENVIRONMENT_URL}"
- echo "${QA_IMAGE}"
- source scripts/utils.sh
- install_api_client_dependencies_with_apk
- gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}}
artifacts:
paths:
- ./qa/gitlab-qa-run-*
expire_in: 7 days
when: always
review-qa-smoke:
extends:
- .review-qa-base
- .review:rules:review-qa-smoke
script:
- gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
review-qa-all:
extends:
- .review-qa-base
- .review:rules:mr-only-manual
parallel: 5
script:
- export KNAPSACK_REPORT_PATH=knapsack/master_report.json
- 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
review-performance:
extends:
- .default-retry
- .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
image:
name: sitespeedio/sitespeed.io:6.3.1
entrypoint: [""]
stage: qa
# This is needed so that manual jobs with needs don't block the pipeline.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
dependencies: ["review-deploy"]
before_script:
- export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
- echo "${CI_ENVIRONMENT_URL}"
- mkdir -p gitlab-exporter
- wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
- mkdir -p sitespeed-results
script:
- /start.sh --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "${CI_ENVIRONMENT_URL}"
after_script:
- mv sitespeed-results/data/performance.json performance.json
artifacts:
paths:
- sitespeed-results/
reports:
performance: performance.json
expire_in: 31d
parallel-spec-reports:
extends:
- .review:rules:mr-only-manual
image: ruby:2.6-alpine
stage: post-qa
dependencies: ["review-qa-all"]
variables:
NEW_PARALLEL_SPECS_REPORT: qa/report-new.html
BASE_ARTIFACT_URL: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/qa/"
script:
- apk add --update build-base libxml2-dev libxslt-dev && rm -rf /var/cache/apk/*
- gem install nokogiri --no-document
- cd qa/gitlab-qa-run-*/gitlab-*
- ARTIFACT_DIRS=$(pwd |rev| awk -F / '{print $1,$2}' | rev | sed s_\ _/_)
- cd -
- '[[ -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
artifacts:
when: always
paths:
- qa/report-new.html
- qa/gitlab-qa-run-*
reports:
junit: qa/gitlab-qa-run-*/**/rspec-*.xml
expire_in: 31d
danger-review:
extends:

View file

@ -11,7 +11,7 @@
if: '$CI_PROJECT_NAME != "gitlab-foss" && $CI_PROJECT_NAME != "gitlab-ce" && $CI_PROJECT_NAME != "gitlabhq"'
.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: '$CI_COMMIT_REF_NAME == "master"'
@ -40,6 +40,9 @@
.if-merge-request-title-update-caches: &if-merge-request-title-update-caches
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: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_MERGE_REQUEST_IID'
@ -71,6 +74,22 @@
- ".gitlab-ci.yml"
- ".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
- "**/*.yml"
@ -92,6 +111,21 @@
- "vendor/assets/**/*"
- "{,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
- "Dangerfile"
- "danger/**/*"
@ -197,6 +231,26 @@
- <<: *if-master-schedule-2-hourly
- <<: *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 #
####################
@ -263,7 +317,7 @@
- <<: *if-not-canonical-namespace
when: never
- <<: *if-default-refs
changes: *code-backstage-qa-patterns
changes: *code-qa-patterns
.frontend:rules:compile-test-assets:
rules:
@ -273,11 +327,8 @@
rules:
- <<: *if-not-ee
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
- <<: *if-merge-request-title-as-if-foss
- <<: *if-merge-request
changes: *ci-patterns
.frontend:rules:default-frontend-jobs:
rules:
@ -294,6 +345,15 @@
- <<: *if-merge-request
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:
rules:
- <<: *if-not-ee
@ -341,9 +401,7 @@
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-master
changes: *code-backstage-qa-patterns
when: on_success
- <<: *if-master-schedule-2-hourly
############
# QA rules #
@ -367,7 +425,7 @@
.qa:rules:package-and-qa:
rules:
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *ci-patterns
changes: *ci-qa-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *qa-patterns
@ -382,24 +440,95 @@
###############
# Rails rules #
###############
.rails:rules:ee-and-foss:
.rails:rules:ee-and-foss-migration:
rules:
- <<: *if-default-refs
changes: *code-backstage-patterns
- changes: *db-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:
rules:
- <<: *if-default-refs
changes: *code-backstage-qa-patterns
.rails:rules:ee-only:
.rails:rules:ee-only-migration:
rules:
- <<: *if-not-ee
when: never
- <<: *if-default-refs
changes: *code-backstage-patterns
- changes: *db-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:
- <<: *if-not-ee
when: never
@ -413,6 +542,7 @@
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-title-run-all-rspec
- <<: *if-merge-request
changes: *code-backstage-patterns
- <<: *if-master-refs
@ -434,6 +564,27 @@
- <<: *if-merge-request
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 #
##################
@ -496,18 +647,12 @@
################
# Review rules #
################
.review:rules:build-qa-image:
.review:rules:review-build-cng:
rules:
- <<: *if-not-ee
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
changes: *ci-patterns
changes: *ci-review-patterns
- <<: *if-dot-com-gitlab-org-merge-request
changes: *frontend-patterns
- <<: *if-dot-com-gitlab-org-merge-request
@ -521,7 +666,7 @@
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
changes: *ci-patterns
changes: *ci-review-patterns
- <<: *if-dot-com-gitlab-org-merge-request
changes: *frontend-patterns
allow_failure: true
@ -544,7 +689,7 @@
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
changes: *ci-patterns
changes: *ci-review-patterns
- <<: *if-dot-com-gitlab-org-merge-request
changes: *frontend-patterns
allow_failure: true

View file

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

View file

@ -4,11 +4,11 @@ lint-ci-gitlab:
extends:
- .default-retry
- .yaml:rules
image: sdesbure/yamllint:latest
image: pipelinecomponents/yamllint:latest
stage: test
needs: []
variables:
LINT_PATHS: .gitlab-ci.yml .gitlab/ci lib/gitlab/ci/templates changelogs
script:
- '[[ ! -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
<!-- 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

View file

@ -9,19 +9,17 @@ Set the title to: `Description of the original issue`
## Prior to starting the security release work
- [ ] 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.
- [ ] Run `scripts/security-harness` in your local repository to prevent accidentally pushing to any remote besides `gitlab.com/gitlab-org/security`.
- [ ] Mark this [issue as related] to the Security Release Tracking Issue. You can find it on the topic of the `#releases` Slack channel.
- 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 **Security Release tracking issue**, add a link to the security release issue that will include this security issue.
## 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 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
@ -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)
- [ ] 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)
- [ ] Once your `master` MR is merged, comment on the original security issue with a link to that MR indicating the issue is fixed.
## Summary
@ -50,7 +47,6 @@ After your merge request has been approved according to our [approval guidelines
| Description | Link |
| -------- | -------- |
| Issue on [GitLab](https://gitlab.com/gitlab-org/gitlab/issues) | #TODO |
| Security Release tracking issue | #TODO |
### Details
@ -64,7 +60,7 @@ After your merge request has been approved according to our [approval guidelines
| Thanks | | |
[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
[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

View file

@ -45,9 +45,11 @@ All reviewers can help ensure accuracy, clarity, completeness, and adherence to
**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).
- [ ] Add ~"Technical Writing" and `docs::` workflow label.
- [ ] 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).
- [ ] Ensure ~"Technical Writing", ~"documentation", and a `docs::` scoped label are added.
- [ ] 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**

View file

@ -13,25 +13,33 @@ See [the general developer security release guidelines](https://gitlab.com/gitla
## Developer checklist
- [ ] **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].
- [ ] 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`
- [ ] Assign to a reviewer and maintainer, per our [Code Review process].
- [ ] A [CHANGELOG entry] is added without a `merge_request` value, with `type` set to `security`
- [ ] 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].
- [ ] 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
## Maintainer checklist
- [ ] Correct milestone is applied and the title is matching across all backports
- [ ] Assigned to `@gitlab-release-tools-bot` with passing CI pipelines and **when all backports including the MR targeting master are ready.**
/label ~security
[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
[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/**/*'
require:
- './haml_lint/linter/no_plain_nodes.rb'
- './haml_lint/linter/documentation_links.rb'
linters:
AltText:
@ -26,6 +27,12 @@ linters:
enabled: false
max_consecutive: 2
DocumentationLinks:
enabled: true
include:
- 'app/views/**/*.haml'
- 'ee/app/views/**/*.haml'
EmptyObjectReference:
enabled: true

View file

@ -47,7 +47,6 @@ linters:
- "app/views/admin/hooks/edit.html.haml"
- "app/views/admin/logs/show.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/runners/_runner.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/index.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/_head.html.haml"
- "app/views/admin/users/_profile.html.haml"
@ -280,7 +278,6 @@ linters:
- "app/views/shared/_no_password.html.haml"
- "app/views/shared/_ping_consent.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/_due_date.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/oauth/geo_auth/error.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/widget/open/_geo.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/settings/slacks/edit.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/issuable/_approvals.html.haml"
- "ee/app/views/shared/issuable/_board_create_list_dropdown.html.haml"

View file

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

View file

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

View file

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

View file

@ -303,6 +303,11 @@ Performance/Detect:
RSpec/ContextWording:
Enabled: false
# Offense count: 626
# Cop supports --auto-correct.
RSpec/EmptyLineAfterLetBlock:
Enabled: false
# Offense count: 1121
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
@ -560,30 +565,6 @@ Style/GuardClause:
Style/HashEachMethods:
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
# Configuration parameters: AllowIfModifier.
Style/IfInsideElse:
@ -639,12 +620,122 @@ Style/Next:
Style/NumericLiteralPrefix:
Enabled: false
# Offense count: 255
# Offense count: 130
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods.
# 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:
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
# Cop supports --auto-correct.
@ -690,18 +781,6 @@ Style/RedundantFreeze:
Style/RedundantInterpolation:
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
# Cop supports --auto-correct.
Style/RedundantSelf:
@ -711,8 +790,8 @@ Style/RedundantSelf:
# Cop supports --auto-correct.
Style/RedundantSort:
Exclude:
- 'ee/app/presenters/packages/nuget/search_results_presenter.rb'
- 'ee/spec/presenters/packages/nuget/search_results_presenter_spec.rb'
- 'app/presenters/packages/nuget/search_results_presenter.rb'
- 'spec/presenters/packages/nuget/search_results_presenter_spec.rb'
# Offense count: 120
# Cop supports --auto-correct.
@ -769,25 +848,20 @@ Style/StringLiteralsInInterpolation:
Style/SymbolProc:
Enabled: false
# Offense count: 1478
# Offense count: 2362
# Cop supports --auto-correct.
# Configuration parameters: AllowImplicitReturn, AllowedReceivers.
Rails/SaveBang:
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/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/factories/ci/job_artifacts.rb'
- 'ee/spec/factories/epics.rb'
- 'ee/spec/factories/licenses.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/licenses/admin_views_license_spec.rb'
- 'ee/spec/features/boards/scoped_issue_board_spec.rb'
- 'ee/spec/features/ci_shared_runner_warnings_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/projects/members/invite_group_and_members_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/settings/user_manages_approval_settings_spec.rb'
- 'ee/spec/features/projects/settings/user_manages_members_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/security/vulnerabilities_finder_spec.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/ee/dashboard_helper_spec.rb'
- 'ee/spec/helpers/ee/issues_helper_spec.rb'
- 'ee/spec/initializers/fog_google_https_private_urls_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/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/prune_orphaned_geo_events_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/gitlab/auth/ldap/access_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/background_migration/fix_orphan_promoted_issues_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/geo/cron_manager_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_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/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_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/models/application_setting_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/burndown_spec.rb'
- 'ee/spec/models/ci/build_spec.rb'
- 'ee/spec/models/ci/pipeline_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/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_ref_access_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/geo/project_registry_spec.rb'
- 'ee/spec/models/geo_node_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/issue_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/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_spec.rb'
- 'ee/spec/models/operations/feature_flags/strategy_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/jenkins_service_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_oauth_access_token_spec.rb'
- 'ee/spec/models/upload_spec.rb'
- 'ee/spec/models/user_preference_spec.rb'
- 'ee/spec/models/user_spec.rb'
- 'ee/spec/models/visible_approvable_spec.rb'
- 'ee/spec/models/vulnerabilities/feedback_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/vulnerabilities/feedback_policy_spec.rb'
- 'ee/spec/presenters/audit_event_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/epic_issues_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/issues_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_approvals_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/scim_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/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/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/common_system_notes_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/notification_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/epics/close_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/update_dates_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/files_expire_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/repository_verification_secondary_service_spec.rb'
@ -911,8 +1022,11 @@ Rails/SaveBang:
- 'ee/spec/services/lfs/unlock_file_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/update_blocks_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/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/quick_actions/interpret_service_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/todo_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/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/mentionable_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/services/build_execute_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/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/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/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/repository_import_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/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/admin/impersonations_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/groups/milestones_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/omniauth_callbacks_controller_spec.rb'
- 'spec/controllers/profiles/emails_controller_spec.rb'
- 'spec/controllers/profiles/notifications_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/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/labels_controller_spec.rb'
- 'spec/controllers/projects/merge_requests_controller_spec.rb'
- 'spec/controllers/projects/milestones_controller_spec.rb'
- 'spec/controllers/projects/notes_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/starrers_controller_spec.rb'
- 'spec/controllers/projects/uploads_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/design_management/designs.rb'
- 'spec/factories/design_management/versions.rb'
- 'spec/factories/emails.rb'
- 'spec/factories/issues.rb'
- 'spec/factories/labels.rb'
- 'spec/factories/merge_requests.rb'
- 'spec/factories/plans.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_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_spec.rb'
- 'spec/features/boards/sidebar_spec.rb'
@ -975,6 +1131,7 @@ Rails/SaveBang:
- 'spec/features/dashboard/projects_spec.rb'
- 'spec/features/error_tracking/user_sees_error_index_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/gfm_autocomplete_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_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_discussions_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/profiles/emails_spec.rb'
- 'spec/features/profiles/password_spec.rb'
- 'spec/features/profiles/personal_access_tokens_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/members/user_requests_access_spec.rb'
- 'spec/features/projects/pages_lets_encrypt_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/services/user_activates_slack_notifications_spec.rb'
- 'spec/features/projects/settings/access_tokens_spec.rb'
- 'spec/features/projects/show/user_sees_deletion_failure_message_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_views_wiki_page_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/public_access_spec.rb'
- 'spec/features/users/login_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/ci/pipelines_for_merge_request_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/joined_groups_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/uploader_finder_spec.rb'
- 'spec/frontend/fixtures/issues.rb'
@ -1021,6 +1193,11 @@ Rails/SaveBang:
- 'spec/graphql/mutations/merge_requests/set_wip_spec.rb'
- 'spec/graphql/resolvers/boards_resolver_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/projects/alert_management_helper_spec.rb'
- 'spec/helpers/projects_helper_spec.rb'
@ -1030,18 +1207,23 @@ Rails/SaveBang:
- 'spec/lib/after_commit_queue_spec.rb'
- 'spec/lib/backup/manager_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/auth/ldap/user_spec.rb'
- 'spec/lib/gitlab/auth/o_auth/user_spec.rb'
- 'spec/lib/gitlab/auth/saml/user_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_project_repositories_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_snippet_repositories_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/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_user_namespace_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/recalculate_project_authorizations_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_webhooks_spec.rb'
- 'spec/lib/gitlab/bitbucket_server_import/importer_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/events_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_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/repository_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/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/fast_hash_serializer_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/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/lfs_restorer_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/project/relation_factory_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/repo_restorer_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/snippets_repo_restorer_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_saver_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/markdown_cache/active_record/extension_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/mattermost/command_spec.rb'
- 'spec/lib/mattermost/session_spec.rb'
- 'spec/lib/mattermost/team_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/20200123155929_remove_invalid_jira_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/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/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_snippet_repositories_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_store_uploads_spec.rb'
- 'spec/migrations/fix_null_type_labels_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/insert_project_hooks_plan_limits_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_populate_merge_request_assignees_table_spec.rb'
- 'spec/migrations/seed_repository_storages_weighted_spec.rb'
- 'spec/models/appearance_spec.rb'
- 'spec/models/application_record_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_trace_chunk_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/runner_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/bulk_insertable_associations_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/issuable_spec.rb'
- 'spec/models/concerns/mentionable_spec.rb'
- 'spec/models/concerns/milestoneable_spec.rb'
- 'spec/models/concerns/milestoneish_spec.rb'
- 'spec/models/concerns/routable_spec.rb'
- 'spec/models/concerns/subscribable_spec.rb'
- 'spec/models/concerns/token_authenticatable_spec.rb'
- 'spec/models/container_repository_spec.rb'
- 'spec/models/cycle_analytics/issue_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_token_spec.rb'
- 'spec/models/deployment_spec.rb'
- 'spec/models/design_management/version_spec.rb'
- 'spec/models/diff_discussion_spec.rb'
- 'spec/models/diff_note_spec.rb'
- 'spec/models/email_spec.rb'
- 'spec/models/environment_spec.rb'
@ -1143,7 +1373,9 @@ Rails/SaveBang:
- 'spec/models/hooks/system_hook_spec.rb'
- 'spec/models/hooks/web_hook_spec.rb'
- 'spec/models/identity_spec.rb'
- 'spec/models/issue/metrics_spec.rb'
- 'spec/models/issue_spec.rb'
- 'spec/models/jira_import_state_spec.rb'
- 'spec/models/key_spec.rb'
- 'spec/models/lfs_objects_project_spec.rb'
- 'spec/models/member_spec.rb'
@ -1155,12 +1387,18 @@ Rails/SaveBang:
- 'spec/models/note_spec.rb'
- 'spec/models/notification_setting_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/buildkite_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/teamcity_service_spec.rb'
- 'spec/models/project_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/remote_mirror_spec.rb'
- 'spec/models/resource_milestone_event_spec.rb'
@ -1171,47 +1409,77 @@ Rails/SaveBang:
- 'spec/models/upload_spec.rb'
- 'spec/models/user_preference_spec.rb'
- 'spec/models/user_spec.rb'
- 'spec/models/user_status_spec.rb'
- 'spec/models/wiki_page/meta_spec.rb'
- 'spec/models/wiki_page_spec.rb'
- 'spec/policies/ci/build_policy_spec.rb'
- 'spec/policies/ci/pipeline_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/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/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/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/user_query_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/internal/base_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/labels_spec.rb'
- 'spec/requests/api/maven_packages_spec.rb'
- 'spec/requests/api/members_spec.rb'
- 'spec/requests/api/merge_request_diffs_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/project_import_spec.rb'
- 'spec/requests/api/project_milestones_spec.rb'
- 'spec/requests/api/projects_spec.rb'
- 'spec/requests/api/runners_spec.rb'
- 'spec/requests/api/snippets_spec.rb'
- 'spec/requests/git_http_spec.rb'
- 'spec/requests/lfs_http_spec.rb'
- 'spec/requests/profiles/notifications_controller_spec.rb'
- 'spec/requests/projects/cycle_analytics_events_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_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/register_job_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/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/emails/confirm_service_spec.rb'
- 'spec/services/groups/destroy_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/common_system_notes_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/labels/promote_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/create_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/promote_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_service_spec.rb'
- 'spec/services/packages/conan/create_package_file_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/destroy_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_project_group_links_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_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_note_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/users/destroy_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/design_management_test_helpers.rb'
- 'spec/support/helpers/jira_service_helper.rb'
- 'spec/support/helpers/login_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/migrations_helpers/cluster_helpers.rb'
- 'spec/support/migrations_helpers/namespaces_helper.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/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/sessionless_auth_controller_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/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/member_shared_examples.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/project_latest_successful_build_for_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/update_project_statistics_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/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/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/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/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/merge_requests/show.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/namespaceless_project_destroy_worker_spec.rb'
- 'spec/workers/namespaces/root_statistics_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/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_import_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/roulette.rb')
danger.import_plugin('danger/plugins/changelog.rb')
danger.import_plugin('danger/plugins/sidekiq_queues.rb')
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 'rugged', '~> 0.28'
gem 'grape-path-helpers', '~> 1.2'
gem 'grape-path-helpers', '~> 1.3'
gem 'faraday', '~> 0.12'
gem 'marginalia', '~> 1.8.0'
@ -66,7 +66,7 @@ gem 'u2f', '~> 0.2.1'
gem 'validates_hostname', '~> 1.0.10'
gem 'rubyzip', '~> 2.0.0', require: 'zip'
# GitLab Pages letsencrypt support
gem 'acme-client', '~> 2.0.5'
gem 'acme-client', '~> 2.0', '>= 2.0.6'
# Browser detection
gem 'browser', '~> 2.5'
@ -81,7 +81,9 @@ gem 'gitlab_omniauth-ldap', '~> 2.1.1', require: 'omniauth-ldap'
gem 'net-ldap'
# 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 '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 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'commonmarker', '~> 0.20'
gem 'kramdown', '~> 2.2.1'
gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~> 6.1.2'
gem 'org-ruby', '~> 0.9.12'
@ -148,7 +151,7 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 2.0.10'
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
gem 'asciidoctor-plantuml', '~> 0.0.12'
gem 'rouge', '~> 3.19.0'
gem 'rouge', '~> 3.21.0'
gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.10.9'
@ -163,6 +166,8 @@ gem 'diff_match_patch', '~> 0.1.0'
# Application server
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
gem 'unicorn', '~> 5.5'
@ -172,7 +177,6 @@ end
group :puma do
gem 'gitlab-puma', '~> 4.3.3.gitlab.2', require: false
gem 'gitlab-puma_worker_killer', '~> 0.1.1.gitlab.1', require: false
gem 'rack-timeout', require: false
end
# State machine
@ -242,7 +246,9 @@ gem 'slack-messenger', '~> 2.3.3'
gem 'hangouts-chat', '~> 0.0.5'
# 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
gem 'ruby-fogbugz', '~> 0.2.1'
@ -300,7 +306,7 @@ gem 'sentry-raven', '~> 2.9'
gem 'premailer-rails', '~> 1.10.3'
# LabKit: Tracing and Correlation
gem 'gitlab-labkit', '0.12.0'
gem 'gitlab-labkit', '0.12.1'
# I18n
gem 'ruby_parser', '~> 3.8', require: false
@ -331,10 +337,9 @@ group :development do
gem 'danger', '~> 6.0', require: false
gem 'letter_opener_web', '~> 1.3.4'
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
# Better errors handler
gem 'better_errors', '~> 2.5.0'
gem 'better_errors', '~> 2.7.1'
gem 'binding_of_caller', '~> 0.8.0'
# thin instead webrick
@ -361,7 +366,7 @@ group :development, :test do
gem 'spring', '~> 2.0.0'
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
gem 'rubocop', '~> 0.82.0'
gem 'rubocop-performance', '~> 1.5.2'
@ -370,6 +375,7 @@ group :development, :test do
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.34.0', 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 'benchmark-ips', '~> 2.3.0', require: false
@ -383,6 +389,8 @@ group :development, :test do
gem 'png_quantizator', '~> 0.2.1', require: false
gem 'parallel', '~> 1.19', require: false
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
end
# Gems required in omnibus-gitlab pipeline
@ -452,7 +460,7 @@ group :ed25519 do
end
# Gitaly GRPC protocol definitions
gem 'gitaly', '~> 13.1.0.pre.rc1'
gem 'gitaly', '~> 13.2.0.pre.rc2'
gem 'grpc', '~> 1.24.0'
@ -497,3 +505,5 @@ gem 'valid_email', '~> 0.1'
# JSON
gem 'json', '~> 2.3.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)
abstract_type (0.0.7)
ace-rails-ap (4.1.2)
acme-client (2.0.5)
faraday (~> 0.9, >= 0.9.1)
acme-client (2.0.6)
faraday (>= 0.17, < 2.0.0)
actioncable (6.0.3.1)
actionpack (= 6.0.3.1)
nio4r (~> 2.0)
@ -76,7 +76,7 @@ GEM
apollo_upload_server (2.0.0.beta.3)
graphql (>= 1.8)
rails (>= 4.2)
asana (0.9.3)
asana (0.10.0)
faraday (~> 0.9)
faraday_middleware (~> 0.9)
faraday_middleware-multi_json (~> 0.0)
@ -103,10 +103,6 @@ GEM
aws-sdk-core (= 2.11.374)
aws-sigv4 (1.1.0)
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)
base32 (0.3.2)
batch-loader (1.4.0)
@ -115,7 +111,7 @@ GEM
benchmark-ips (2.3.0)
benchmark-memory (0.1.2)
memory_profiler (~> 0.9)
better_errors (2.5.0)
better_errors (2.7.1)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
@ -164,8 +160,6 @@ GEM
nap
open4 (~> 1.3)
coderay (1.1.2)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
colored2 (3.1.2)
commonmarker (0.20.1)
ruby-enum (~> 0.5)
@ -221,8 +215,6 @@ GEM
ruby-statistics (>= 2.1)
thor (>= 0.19, < 2)
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)
devise (4.7.1)
bcrypt (~> 3.0)
@ -249,6 +241,28 @@ GEM
doorkeeper-openid_connect (1.6.3)
doorkeeper (>= 5.0, < 5.2)
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)
elasticsearch (6.8.0)
elasticsearch-api (= 6.8.0)
@ -290,7 +304,7 @@ GEM
multipart-post (>= 1.2, < 3)
faraday-http-cache (2.0.0)
faraday (~> 0.8)
faraday_middleware (0.12.2)
faraday_middleware (0.14.0)
faraday (>= 0.7.4, < 1.0)
faraday_middleware-aws-signers-v4 (0.1.7)
aws-sdk-resources (~> 2)
@ -377,12 +391,12 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
git (1.5.0)
gitaly (13.1.0.pre.rc1)
gitaly (13.2.0.pre.rc2)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-chronic (0.10.5)
numerizer (~> 0.2)
gitlab-labkit (0.12.0)
gitlab-labkit (0.12.1)
actionpack (>= 5.0.0, < 6.1.0)
activesupport (>= 5.0.0, < 6.1.0)
grpc (~> 1.19)
@ -400,7 +414,7 @@ GEM
gitlab-puma (>= 2.7, < 5)
gitlab-sidekiq-fetcher (0.5.2)
sidekiq (~> 5)
gitlab-styles (4.2.0)
gitlab-styles (4.3.0)
rubocop (~> 0.82.0)
rubocop-gitlab-security (~> 0.1.0)
rubocop-performance (~> 1.5.2)
@ -439,19 +453,19 @@ GEM
signet (~> 0.14)
gpgme (2.0.20)
mini_portile2 (~> 2.3)
grape (1.1.0)
grape (1.4.0)
activesupport
builder
dry-types (>= 1.1)
mustermann-grape (~> 1.0.0)
rack (>= 1.3.0)
rack-accept
virtus (>= 1.0.0)
grape-entity (0.7.1)
activesupport (>= 4.0)
multi_json (>= 1.3.2)
grape-path-helpers (1.2.0)
grape-path-helpers (1.3.0)
activesupport
grape (~> 1.0)
grape (~> 1.3)
rake (~> 12)
grape_logging (1.8.3)
grape
@ -575,7 +589,8 @@ GEM
kgio (2.11.3)
knapsack (1.17.0)
rake
kramdown (2.1.0)
kramdown (2.2.1)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
kubeclient (4.6.0)
@ -641,9 +656,10 @@ GEM
multi_xml (0.6.0)
multipart-post (2.1.1)
murmurhash3 (0.1.6)
mustermann (1.0.3)
mustermann-grape (1.0.0)
mustermann (~> 1.0.0)
mustermann (1.1.1)
ruby2_keywords (~> 0.0.1)
mustermann-grape (1.0.1)
mustermann (>= 1.0.0)
nakayoshi_fork (0.0.4)
nap (1.1.0)
nenv (0.3.0)
@ -671,6 +687,7 @@ GEM
octokit (4.15.0)
faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3)
oj (3.10.6)
omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3)
@ -801,7 +818,7 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rack-timeout (0.5.1)
rack-timeout (0.5.2)
rails (6.0.3.1)
actioncable (= 6.0.3.1)
actionmailbox (= 6.0.3.1)
@ -890,7 +907,7 @@ GEM
rexml (3.2.4)
rinku (2.0.0)
rotp (2.1.2)
rouge (3.19.0)
rouge (3.21.0)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
@ -958,6 +975,7 @@ GEM
ruby-saml (1.7.2)
nokogiri (>= 1.5.10)
ruby-statistics (2.1.2)
ruby2_keywords (0.0.2)
ruby_dep (1.5.0)
ruby_parser (3.13.1)
sexp_processor (~> 4.9)
@ -1003,11 +1021,11 @@ GEM
shellany (0.0.1)
shoulda-matchers (4.0.1)
activesupport (>= 4.2.0)
sidekiq (5.2.7)
sidekiq (5.2.9)
connection_pool (~> 2.2, >= 2.2.2)
rack (>= 1.5.0)
rack (~> 2.0)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
redis (>= 3.3.5, < 4.2)
sidekiq-cron (1.0.4)
fugit (~> 1.1)
sidekiq (>= 4.2.1)
@ -1020,6 +1038,8 @@ GEM
simplecov (0.18.5)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov-cobertura (1.3.1)
simplecov (~> 0.8)
simplecov-html (0.12.2)
sixarm_ruby_unaccent (1.2.0)
slack-messenger (2.3.3)
@ -1119,11 +1139,6 @@ GEM
activerecord (>= 3.0)
activesupport (>= 3.0)
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)
warden (1.2.8)
rack (>= 2.0.6)
@ -1155,13 +1170,13 @@ PLATFORMS
DEPENDENCIES
RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0)
acme-client (~> 2.0.5)
acme-client (~> 2.0, >= 2.0.6)
activerecord-explain-analyze (~> 0.1)
acts-as-taggable-on (~> 6.0)
addressable (~> 2.7)
akismet (~> 3.0)
apollo_upload_server (~> 2.0.0.beta3)
asana (~> 0.9)
asana (= 0.10.0)
asciidoctor (~> 2.0.10)
asciidoctor-include-ext (~> 0.3.1)
asciidoctor-plantuml (~> 0.0.12)
@ -1175,7 +1190,7 @@ DEPENDENCIES
bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0)
benchmark-memory (~> 0.1)
better_errors (~> 2.5.0)
better_errors (~> 2.7.1)
binding_of_caller (~> 0.8.0)
bootsnap (~> 1.4.6)
bootstrap_form (~> 4.2.0)
@ -1236,10 +1251,10 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly (~> 13.1.0.pre.rc1)
gitaly (~> 13.2.0.pre.rc2)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-labkit (= 0.12.0)
gitlab-labkit (= 0.12.1)
gitlab-license (~> 1.0)
gitlab-mail_room (~> 0.0.6)
gitlab-markup (~> 1.7.1)
@ -1247,16 +1262,16 @@ DEPENDENCIES
gitlab-puma (~> 4.3.3.gitlab.2)
gitlab-puma_worker_killer (~> 0.1.1.gitlab.1)
gitlab-sidekiq-fetcher (= 0.5.2)
gitlab-styles (~> 4.2.0)
gitlab-styles (~> 4.3.0)
gitlab_chronic_duration (~> 0.10.6.2)
gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2)
google-api-client (~> 0.33)
google-protobuf (~> 3.8.0)
gpgme (~> 2.0.19)
grape (~> 1.1.0)
grape (= 1.4.0)
grape-entity (~> 0.7.1)
grape-path-helpers (~> 1.2)
grape-path-helpers (~> 1.3)
grape_logging (~> 1.7)
graphiql-rails (~> 1.4.10)
graphql (~> 1.10.5)
@ -1282,6 +1297,7 @@ DEPENDENCIES
jwt (~> 2.1.0)
kaminari (~> 1.0)
knapsack (~> 1.17)
kramdown (~> 2.2.1)
kubeclient (~> 4.6.0)
letter_opener_web (~> 1.3.4)
license_finder (~> 5.4)
@ -1297,6 +1313,7 @@ DEPENDENCIES
mimemagic (~> 0.3.2)
mini_magick
minitest (~> 5.11.0)
multi_json (~> 1.14.1)
nakayoshi_fork (~> 0.0.4)
net-ldap
net-ntp
@ -1304,6 +1321,7 @@ DEPENDENCIES
nokogiri (~> 1.10.9)
oauth2 (~> 1.4)
octokit (~> 4.15)
oj (~> 3.10.6)
omniauth (~> 1.8)
omniauth-auth0 (~> 2.0.0)
omniauth-authentiq (~> 0.3.3)
@ -1335,7 +1353,7 @@ DEPENDENCIES
rack-cors (~> 1.0.6)
rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0)
rack-timeout
rack-timeout (~> 0.5.1)
rails (~> 6.0.3.1)
rails-controller-testing
rails-i18n (~> 6.0)
@ -1352,7 +1370,7 @@ DEPENDENCIES
request_store (~> 1.5)
responders (~> 3.0)
retriable (~> 3.1.2)
rouge (~> 3.19.0)
rouge (~> 3.21.0)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 4.0.0)
@ -1380,6 +1398,7 @@ DEPENDENCIES
sidekiq-cron (~> 1.0)
simple_po_parser (~> 1.1.2)
simplecov (~> 0.18.5)
simplecov-cobertura (~> 1.3.1)
slack-messenger (~> 2.3.3)
snowplow-tracker (~> 0.6.1)
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,
} from '@gitlab/ui';
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 TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
import initUserPopovers from '~/user_popovers';
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 Tracking from '~/tracking';
import { toggleContainerClasses } from '~/lib/utils/dom_utils';
import SystemNote from './system_notes/system_note.vue';
import AlertSidebar from './alert_sidebar.vue';
import AlertMetrics from './alert_metrics.vue';
const containerEl = document.querySelector('.page-with-contextual-sidebar');
@ -34,6 +37,7 @@ export default {
),
fullAlertDetailsTitle: s__('AlertManagement|Alert details'),
overviewTitle: s__('AlertManagement|Overview'),
metricsTitle: s__('AlertManagement|Metrics'),
reportedAt: s__('AlertManagement|Reported %{when}'),
reportedAtWithTool: s__('AlertManagement|Reported %{when} by %{tool}'),
},
@ -51,25 +55,29 @@ export default {
TimeAgoTooltip,
AlertSidebar,
SystemNote,
AlertMetrics,
},
props: {
inject: {
projectPath: {
default: '',
},
alertId: {
type: String,
required: true,
default: '',
},
projectPath: {
projectId: {
type: String,
required: true,
default: '',
},
projectIssuesPath: {
type: String,
required: true,
default: '',
},
},
apollo: {
alert: {
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
query,
query: alertQuery,
variables() {
return {
fullPath: this.projectPath,
@ -84,15 +92,18 @@ export default {
Sentry.captureException(error);
},
},
sidebarStatus: {
query: sidebarStatusQuery,
},
},
data() {
return {
alert: null,
errored: false,
sidebarStatus: false,
isErrorDismissed: false,
createIssueError: '',
issueCreationInProgress: false,
sidebarCollapsed: false,
sidebarErrorMessage: '',
};
},
@ -128,10 +139,10 @@ export default {
this.sidebarErrorMessage = '';
},
toggleSidebar() {
this.sidebarCollapsed = !this.sidebarCollapsed;
this.$apollo.mutate({ mutation: toggleSidebarStatusMutation });
toggleContainerClasses(containerEl, {
'right-sidebar-collapsed': this.sidebarCollapsed,
'right-sidebar-expanded': !this.sidebarCollapsed,
'right-sidebar-collapsed': !this.sidebarStatus,
'right-sidebar-expanded': this.sidebarStatus,
});
},
handleAlertSidebarError(errorMessage) {
@ -143,7 +154,7 @@ export default {
this.$apollo
.mutate({
mutation: createIssueQuery,
mutation: createIssueMutation,
variables: {
iid: this.alert.iid,
projectPath: this.projectPath,
@ -169,9 +180,6 @@ export default {
const { category, action } = trackAlertsDetailsViewsOptions;
Tracking.event(category, action);
},
alertRefresh() {
this.$apollo.queries.alert.refetch();
},
},
};
</script>
@ -179,7 +187,7 @@ export default {
<template>
<div>
<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
v-if="createIssueError"
@ -193,10 +201,10 @@ export default {
<div
v-if="alert"
class="alert-management-details gl-relative"
:class="{ 'pr-sm-8': sidebarCollapsed }"
:class="{ 'pr-sm-8': sidebarStatus }"
>
<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
data-testid="alert-header"
@ -324,14 +332,14 @@ export default {
</template>
</gl-table>
</gl-tab>
<gl-tab data-testId="metricsTab" :title="$options.i18n.metricsTitle">
<alert-metrics :dashboard-url="alert.metricsDashboardUrl" />
</gl-tab>
</gl-tabs>
<alert-sidebar
:project-path="projectPath"
:alert="alert"
:sidebar-collapsed="sidebarCollapsed"
@alert-refresh="alertRefresh"
@toggle-sidebar="toggleSidebar"
@alert-sidebar-error="handleAlertSidebarError"
@alert-error="handleAlertSidebarError"
/>
</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>
import {
GlEmptyState,
GlDeprecatedButton,
GlLoadingIcon,
GlTable,
GlAlert,
GlIcon,
GlDropdown,
GlDropdownItem,
GlLink,
GlTabs,
GlTab,
GlBadge,
GlPagination,
GlSearchBoxByType,
GlSprintf,
} 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 { fetchPolicies } from '~/lib/graphql';
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 getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql';
import {
@ -27,11 +28,10 @@ import {
trackAlertListViewsOptions,
trackAlertStatusUpdateOptions,
} from '../constants';
import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql';
import { convertToSnakeCase } from '~/lib/utils/text_utility';
import Tracking from '~/tracking';
import AlertStatus from './alert_status.vue';
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 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';
@ -44,54 +44,57 @@ const initialPaginationState = {
lastPageSize: null,
};
const TWELVE_HOURS_IN_MS = 12 * 60 * 60 * 1000;
export default {
i18n: {
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__(
"AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear.",
),
searchPlaceholder: __('Search or filter results...'),
},
fields: [
{
key: 'severity',
label: s__('AlertManagement|Severity'),
tdClass: `${tdClass} rounded-top text-capitalize`,
thClass,
thClass: `${thClass} gl-w-eighth`,
sortable: true,
},
{
key: 'startedAt',
label: s__('AlertManagement|Start time'),
thClass: `${thClass} js-started-at`,
tdClass,
sortable: true,
},
{
key: 'endedAt',
label: s__('AlertManagement|End time'),
thClass,
thClass: `${thClass} js-started-at w-15p`,
tdClass,
sortable: true,
},
{
key: 'title',
label: s__('AlertManagement|Alert'),
thClass: `${thClass} w-30p gl-pointer-events-none`,
thClass: `gl-pointer-events-none`,
tdClass,
sortable: false,
},
{
key: 'eventCount',
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`,
sortable: true,
},
{
key: 'issue',
label: s__('AlertManagement|Issue'),
thClass: 'gl-w-12 gl-pointer-events-none',
tdClass,
sortable: false,
},
{
key: 'assignees',
label: s__('AlertManagement|Assignees'),
thClass: 'gl-w-eighth gl-pointer-events-none',
tdClass,
},
{
@ -102,46 +105,29 @@ export default {
sortable: true,
},
],
statuses: {
TRIGGERED: s__('AlertManagement|Triggered'),
ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
RESOLVED: s__('AlertManagement|Resolved'),
},
severityLabels: ALERTS_SEVERITY_LABELS,
statusTabs: ALERTS_STATUS_TABS,
components: {
GlEmptyState,
GlLoadingIcon,
GlTable,
GlAlert,
GlDeprecatedButton,
TimeAgo,
GlDropdown,
GlDropdownItem,
GlIcon,
GlLink,
GlTabs,
GlTab,
GlBadge,
GlPagination,
GlSearchBoxByType,
GlSprintf,
AlertStatus,
},
props: {
projectPath: {
type: String,
required: true,
},
alertManagementEnabled: {
type: Boolean,
required: true,
},
enableAlertManagementPath: {
type: String,
required: true,
},
userCanEnableAlertManagement: {
type: Boolean,
required: true,
},
emptyAlertSvgPath: {
populatingAlertsHelpUrl: {
type: String,
required: true,
},
@ -152,6 +138,7 @@ export default {
query: getAlerts,
variables() {
return {
searchTerm: this.searchTerm,
projectPath: this.projectPath,
statuses: this.statusFilter,
sort: this.sort,
@ -164,9 +151,20 @@ export default {
update(data) {
const { alertManagementAlerts: { nodes: list = [], pageInfo = {} } = {} } =
data.project || {};
const now = new Date();
const listWithData = list.map(alert => {
const then = new Date(alert.startedAt);
const diff = now - then;
return {
...alert,
isNew: diff < TWELVE_HOURS_IN_MS,
};
});
return {
list,
list: listWithData,
pageInfo,
};
},
@ -178,6 +176,7 @@ export default {
query: getAlertsCountByStatus,
variables() {
return {
searchTerm: this.searchTerm,
projectPath: this.projectPath,
};
},
@ -188,7 +187,9 @@ export default {
},
data() {
return {
searchTerm: '',
errored: false,
errorMessage: '',
isAlertDismissed: false,
isErrorAlertDismissed: false,
sort: 'STARTED_AT_DESC',
@ -203,7 +204,11 @@ export default {
computed: {
showNoAlertsMsg() {
return (
!this.errored && !this.loading && this.alertsCount?.all === 0 && !this.isAlertDismissed
!this.errored &&
!this.loading &&
this.alertsCount?.all === 0 &&
!this.searchTerm &&
!this.isAlertDismissed
);
},
showErrorMsg() {
@ -215,9 +220,6 @@ export default {
hasAlerts() {
return this.alerts?.list?.length;
},
tbodyTrClass() {
return !this.loading && this.hasAlerts ? bodyTrClass : '';
},
showPaginationControls() {
return Boolean(this.prevPage || this.nextPage);
},
@ -249,30 +251,13 @@ export default {
this.resetPagination();
this.sort = `${sortingColumn}_${sortingDirection}`;
},
updateAlertStatus(status, iid) {
this.$apollo
.mutate({
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();
})
.catch(() => {
createFlash(
s__(
'AlertManagement|There was an error while updating the status of the alert. Please try again.',
),
);
});
},
onInputChange: debounce(function debounceSearch(input) {
const trimmedInput = trim(input);
if (trimmedInput !== this.searchTerm) {
this.resetPagination();
this.searchTerm = trimmedInput;
}
}, 500),
navigateToAlertDetails({ iid }) {
return visitUrl(joinPaths(window.location.pathname, iid, 'details'));
},
@ -290,6 +275,9 @@ export default {
? assignees.nodes[0]?.username
: s__('AlertManagement|Unassigned');
},
getIssueLink(item) {
return joinPaths('/', this.projectPath, '-', 'issues', item.issueIid);
},
handlePageChange(page) {
const { startCursor, endCursor } = this.alerts.pageInfo;
@ -312,20 +300,49 @@ export default {
resetPagination() {
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>
<template>
<div>
<div v-if="alertManagementEnabled" class="alert-management-list">
<div class="alert-management-list">
<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 v-if="showErrorMsg" variant="danger" @dismiss="isErrorAlertDismissed = true">
{{ $options.i18n.errorMsg }}
<gl-alert
v-if="showErrorMsg"
variant="danger"
data-testid="alert-error"
@dismiss="dismissError"
>
<p v-html="errorMessage || $options.i18n.errorMsg"></p>
</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">
<template slot="title">
<span>{{ tab.title }}</span>
@ -336,11 +353,19 @@ export default {
</gl-tab>
</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">
{{ s__('AlertManagement|Alerts') }}
</h4>
<gl-table
class="alert-management-table mt-3"
class="alert-management-table"
:items="alerts ? alerts.list : []"
:fields="$options.fields"
:show-empty="true"
@ -352,6 +377,7 @@ export default {
:sort-desc.sync="sortDesc"
:sort-by.sync="sortBy"
sort-icon-left
fixed
@row-clicked="navigateToAlertDetails"
@sort-changed="fetchSortedData"
>
@ -374,16 +400,19 @@ export default {
<time-ago v-if="item.startedAt" :time="item.startedAt" />
</template>
<template #cell(endedAt)="{ item }">
<time-ago v-if="item.endedAt" :time="item.endedAt" />
</template>
<template #cell(eventCount)="{ item }">
{{ item.eventCount }}
</template>
<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 #cell(assignees)="{ item }">
@ -393,22 +422,12 @@ export default {
</template>
<template #cell(status)="{ item }">
<gl-dropdown :text="$options.statuses[item.status]" class="w-100" right>
<gl-dropdown-item
v-for="(label, field) in $options.statuses"
:key="field"
@click="updateAlertStatus(label, item.iid)"
>
<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>
<alert-status
:alert="item"
:project-path="projectPath"
:is-sidebar="false"
@alert-error="handleAlertError"
/>
</template>
<template #empty>
@ -426,36 +445,9 @@ export default {
:prev-page="prevPage"
:next-page="nextPage"
align="center"
class="gl-pagination prepend-top-default"
class="gl-pagination gl-mt-3"
@input="handlePageChange"
/>
</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>
</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 SidebarAssignees from './sidebar/sidebar_assignees.vue';
import sidebarStatusQuery from '../graphql/queries/sidebar_status.query.graphql';
export default {
components: {
SidebarAssignees,
@ -11,23 +13,34 @@ export default {
SidebarTodo,
SidebarStatus,
},
props: {
sidebarCollapsed: {
type: Boolean,
required: true,
},
inject: {
projectPath: {
type: String,
required: true,
default: '',
},
projectId: {
type: String,
default: '',
},
},
props: {
alert: {
type: Object,
required: true,
},
},
apollo: {
sidebarStatus: {
query: sidebarStatusQuery,
},
},
data() {
return {
sidebarStatus: false,
};
},
computed: {
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">
<div class="issuable-sidebar js-issuable-update">
<sidebar-header
:sidebar-collapsed="sidebarCollapsed"
:sidebar-collapsed="sidebarStatus"
:project-path="projectPath"
:alert="alert"
@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
:project-path="projectPath"
:alert="alert"
@toggle-sidebar="$emit('toggle-sidebar')"
@alert-sidebar-error="$emit('alert-sidebar-error', $event)"
@alert-error="$emit('alert-error', $event)"
/>
<sidebar-assignees
:project-path="projectPath"
:project-id="projectId"
:alert="alert"
:sidebar-collapsed="sidebarCollapsed"
@alert-refresh="$emit('alert-refresh')"
:sidebar-collapsed="sidebarStatus"
@toggle-sidebar="$emit('toggle-sidebar')"
@alert-sidebar-error="$emit('alert-sidebar-error', $event)"
@alert-error="$emit('alert-error', $event)"
/>
<div class="block"></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,
} from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
import alertSetAssignees from '../../graphql/mutations/alert_set_assignees.graphql';
import { s__, __ } from '~/locale';
import alertSetAssignees from '../../graphql/mutations/alert_set_assignees.mutation.graphql';
import SidebarAssignee from './sidebar_assignee.vue';
import { debounce } from 'lodash';
const DATA_REFETCH_DELAY = 250;
export default {
FETCH_USERS_ERROR: s__(
'AlertManagement|There was an error while updating the assignee(s) list. Please try again.',
),
UPDATE_ALERT_ASSIGNEES_ERROR: s__(
'AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again.',
),
i18n: {
FETCH_USERS_ERROR: s__(
'AlertManagement|There was an error while updating the assignee(s) list. Please try again.',
),
UPDATE_ALERT_ASSIGNEES_ERROR: s__(
'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: {
GlIcon,
GlDropdown,
@ -38,6 +44,10 @@ export default {
SidebarAssignee,
},
props: {
projectId: {
type: String,
required: true,
},
projectPath: {
type: String,
required: true,
@ -73,7 +83,7 @@ export default {
return this.alert?.assignees?.nodes[0]?.username;
},
assignedUser() {
return this.userName || s__('AlertManagement|None');
return this.userName || __('None');
},
sortedUsers() {
return this.users
@ -122,20 +132,20 @@ export default {
updateAssigneesDropdown() {
this.isDropdownSearching = true;
return axios
.get(this.buildUrl(gon.relative_url_root, '/autocomplete/users.json'), {
.get(this.buildUrl(gon.relative_url_root, '/-/autocomplete/users.json'), {
params: {
search: this.search,
per_page: 20,
active: true,
current_user: true,
project_id: gon?.current_project_id,
project_id: this.projectId,
},
})
.then(({ data }) => {
this.users = data;
})
.catch(() => {
this.$emit('alert-sidebar-error', this.$options.FETCH_USERS_ERROR);
this.$emit('alert-error', this.$options.i18n.FETCH_USERS_ERROR);
})
.finally(() => {
this.isDropdownSearching = false;
@ -152,12 +162,18 @@ export default {
projectPath: this.projectPath,
},
})
.then(() => {
.then(({ data: { alertSetAssignees: { errors } = [] } = {} } = {}) => {
this.hideDropdown();
this.$emit('alert-refresh');
if (errors[0]) {
this.$emit(
'alert-error',
`${this.$options.i18n.UPDATE_ALERT_ASSIGNEES_GRAPHQL_ERROR} ${errors[0]}.`,
);
}
})
.catch(() => {
this.$emit('alert-sidebar-error', this.$options.UPDATE_ALERT_ASSIGNEES_ERROR);
this.$emit('alert-error', this.$options.i18n.UPDATE_ALERT_ASSIGNEES_ERROR);
})
.finally(() => {
this.isUpdating = false;
@ -174,7 +190,7 @@ export default {
<gl-loading-icon v-if="isUpdating" />
</div>
<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>
{{ assignedUser }}
</template>
@ -183,7 +199,7 @@ export default {
<div class="hide-collapsed">
<p class="title gl-display-flex gl-justify-content-space-between">
{{ s__('AlertManagement|Assignee') }}
{{ __('Assignee') }}
<a
v-if="isEditable"
ref="editButton"
@ -192,7 +208,7 @@ export default {
@click="toggleFormDropdown"
@keydown.esc="hideDropdown"
>
{{ s__('AlertManagement|Edit') }}
{{ __('Edit') }}
</a>
</p>
@ -207,7 +223,7 @@ export default {
@hide="hideDropdown"
>
<div class="dropdown-title">
<span class="alert-title">{{ s__('AlertManagement|Assign To') }}</span>
<span class="alert-title">{{ __('Assign To') }}</span>
<gl-button
:aria-label="__('Close')"
variant="link"
@ -232,12 +248,12 @@ export default {
active-class="is-active"
@click="updateAlertAssignees('')"
>
{{ s__('AlertManagement|Unassigned') }}
{{ __('Unassigned') }}
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-dropdown-header class="mt-0">
{{ s__('AlertManagement|Assignee') }}
{{ __('Assignee') }}
</gl-dropdown-header>
<sidebar-assignee
v-for="user in sortedUsers"
@ -248,7 +264,7 @@ export default {
/>
</template>
<gl-dropdown-item v-else-if="userListEmpty">
{{ s__('AlertManagement|No Matching Results') }}
{{ __('No Matching Results') }}
</gl-dropdown-item>
<gl-loading-icon v-else />
</div>
@ -261,7 +277,7 @@ export default {
assignedUser
}}</span>
<span v-else class="gl-display-flex gl-align-items-center">
{{ s__('AlertManagement|None -') }}
{{ __('None') }} -
<gl-button
class="gl-pl-2"
href="#"
@ -269,7 +285,7 @@ export default {
data-testid="unassigned-users"
@click="updateAlertAssignees(currentUser)"
>
{{ s__('AlertManagement| assign yourself') }}
{{ __('assign yourself') }}
</gl-button>
</span>
</p>

View file

@ -8,6 +8,14 @@ export default {
SidebarTodo,
},
props: {
alert: {
type: Object,
required: true,
},
projectPath: {
type: String,
required: true,
},
sidebarCollapsed: {
type: Boolean,
required: true,
@ -17,18 +25,17 @@ export default {
</script>
<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">
{{ __('Quick actions') }}
{{ __('To Do') }}
</span>
<toggle-sidebar
:collapsed="sidebarCollapsed"
css-classes="ml-auto"
@toggle="$emit('toggle-sidebar')"
<sidebar-todo
v-if="!sidebarCollapsed"
:project-path="projectPath"
: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 -->
<template v-if="false">
<sidebar-todo v-if="!sidebarCollapsed" :sidebar-collapsed="sidebarCollapsed" />
</template>
<toggle-sidebar :collapsed="sidebarCollapsed" @toggle="$emit('toggle-sidebar')" />
</div>
</template>

View file

@ -1,17 +1,7 @@
<script>
import {
GlIcon,
GlDropdown,
GlDropdownItem,
GlLoadingIcon,
GlTooltip,
GlButton,
GlSprintf,
} from '@gitlab/ui';
import { GlIcon, GlLoadingIcon, GlTooltip, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
import { trackAlertStatusUpdateOptions } from '../../constants';
import updateAlertStatus from '../../graphql/mutations/update_alert_status.graphql';
import AlertStatus from '../alert_status.vue';
export default {
statuses: {
@ -21,12 +11,10 @@ export default {
},
components: {
GlIcon,
GlDropdown,
GlDropdownItem,
GlLoadingIcon,
GlTooltip,
GlButton,
GlSprintf,
AlertStatus,
},
props: {
projectPath: {
@ -60,44 +48,13 @@ export default {
},
toggleFormDropdown() {
this.isDropdownShowing = !this.isDropdownShowing;
const { dropdown } = this.$refs.dropdown.$refs;
const { dropdown } = this.$children[2].$refs.dropdown.$refs;
if (dropdown && this.isDropdownShowing) {
dropdown.show();
}
},
isSelected(status) {
return this.alert.status === status;
},
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 });
handleUpdating(updating) {
this.isUpdating = updating;
},
},
};
@ -132,41 +89,15 @@ export default {
</a>
</p>
<div class="dropdown dropdown-menu-selectable" :class="dropdownClass">
<gl-dropdown
ref="dropdown"
:text="$options.statuses[alert.status]"
class="w-100"
toggle-class="dropdown-menu-toggle"
variant="outline-default"
@keydown.esc.native="hideDropdown"
@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>
<alert-status
:alert="alert"
:project-path="projectPath"
:is-dropdown-showing="isDropdownShowing"
:is-sidebar="true"
@alert-error="$emit('alert-error', $event)"
@hide-dropdown="hideDropdown"
@handle-updating="handleUpdating"
/>
<gl-loading-icon v-if="isUpdating" :inline="true" />
<p

View file

@ -1,29 +1,123 @@
<script>
import { s__ } from '~/locale';
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 {
i18n: {
UPDATE_ALERT_TODO_ERROR: s__(
'AlertManagement|There was an error while updating the To Do of the alert.',
),
},
components: {
Todo,
},
props: {
alert: {
type: Object,
required: true,
},
projectPath: {
type: String,
required: true,
},
sidebarCollapsed: {
type: Boolean,
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>
<!-- TODO: Implement after or as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/215946 -->
<template>
<div v-if="false" :class="{ 'block todo': sidebarCollapsed }">
<div :class="{ 'block todo': sidebarCollapsed, 'gl-ml-auto': !sidebarCollapsed }">
<todo
data-testid="alert-todo-button"
:collapsed="sidebarCollapsed"
:issuable-id="1"
:is-todo="false"
:is-action-active="false"
:issuable-id="alertID"
:is-todo="todo !== ''"
:is-action-active="isUpdating"
issuable-type="alert"
@toggleTodo="() => {}"
@toggleTodo="toggleTodo"
/>
</div>
</template>

View file

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

View file

@ -3,45 +3,59 @@ import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import AlertDetails from './components/alert_details.vue';
import sidebarStatusQuery from './graphql/queries/sidebar_status.query.graphql';
Vue.use(VueApollo);
export default 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({
defaultClient: createDefaultClient(
{},
{
cacheConfig: {
dataIdFromObject: object => {
// eslint-disable-next-line no-underscore-dangle
if (object.__typename === 'AlertManagementAlert') {
return object.iid;
}
return defaultDataIdFromObject(object);
},
defaultClient: createDefaultClient(resolvers, {
cacheConfig: {
dataIdFromObject: object => {
// eslint-disable-next-line no-underscore-dangle
if (object.__typename === 'AlertManagementAlert') {
return object.iid;
}
return defaultDataIdFromObject(object);
},
},
),
}),
});
apolloProvider.clients.defaultClient.cache.writeData({
data: {
sidebarStatus: false,
},
});
// eslint-disable-next-line no-new
new Vue({
el: selector,
provide: {
projectPath,
alertId,
projectIssuesPath,
projectId,
},
apolloProvider,
components: {
AlertDetails,
},
render(createElement) {
return createElement('alert-details', {
props: {
alertId,
projectPath,
projectIssuesPath,
},
});
return createElement('alert-details', {});
},
});
};

View file

@ -1,16 +1,17 @@
#import "~/graphql_shared/fragments/author.fragment.graphql"
fragment AlertNote on Note {
id
author {
id
author {
id
state
...Author
}
body
bodyHtml
createdAt
discussion {
id
}
state
...Author
}
body
bodyHtml
createdAt
discussion {
id
}
systemNoteIconName
}

View file

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

View file

@ -4,7 +4,6 @@ fragment AlertListItem on AlertManagementAlert {
severity
status
startedAt
endedAt
eventCount
issueIid
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(
input: { iid: $iid, assigneeUsernames: $assigneeUsernames, projectPath: $projectPath }
) {
@ -10,6 +12,11 @@ mutation($projectPath: ID!, $assigneeUsernames: [String!]!, $iid: String!) {
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,11 +1,11 @@
#import "../fragments/detail_item.fragment.graphql"
query alertDetails($fullPath: ID!, $alertId: String) {
project(fullPath: $fullPath) {
alertManagementAlerts(iid: $alertId) {
nodes {
...AlertDetailItem
}
}
project(fullPath: $fullPath) {
alertManagementAlerts(iid: $alertId) {
nodes {
...AlertDetailItem
}
}
}
}

View file

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

View file

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

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 { defaultDataIdFromObject } from 'apollo-cache-inmemory';
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);
@ -11,11 +11,18 @@ export default () => {
const selector = '#js-alert_management';
const domEl = document.querySelector(selector);
const { projectPath, enableAlertManagementPath, emptyAlertSvgPath } = domEl.dataset;
let { alertManagementEnabled, userCanEnableAlertManagement } = domEl.dataset;
const {
projectPath,
enableAlertManagementPath,
emptyAlertSvgPath,
populatingAlertsHelpUrl,
opsgenieMvcTargetUrl,
} = domEl.dataset;
let { alertManagementEnabled, userCanEnableAlertManagement, opsgenieMvcEnabled } = domEl.dataset;
alertManagementEnabled = parseBoolean(alertManagementEnabled);
userCanEnableAlertManagement = parseBoolean(userCanEnableAlertManagement);
opsgenieMvcEnabled = parseBoolean(opsgenieMvcEnabled);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
@ -45,9 +52,12 @@ export default () => {
props: {
projectPath,
enableAlertManagementPath,
populatingAlertsHelpUrl,
emptyAlertSvgPath,
alertManagementEnabled,
userCanEnableAlertManagement,
opsgenieMvcTargetUrl,
opsgenieMvcEnabled,
},
});
},

View file

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

View file

@ -14,8 +14,11 @@ export default el => {
formPath,
authorizationKey,
url,
disabled,
} = el.dataset;
const activated = parseBoolean(activatedStr);
const isDisabled = parseBoolean(disabled);
return new Vue({
el,
@ -28,6 +31,7 @@ export default el => {
formPath,
initialAuthorizationKey: authorizationKey,
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',
subgroupsPath: '/api/:version/groups/:id/subgroups',
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',
projectsPath: '/api/:version/projects.json',
projectPath: '/api/:version/projects/:id',
@ -36,7 +39,9 @@ const Api = {
userStatusPath: '/api/:version/users/:id/status',
userProjectsPath: '/api/:version/users/:id/projects',
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',
applySuggestionBatchPath: '/api/:version/suggestions/batch_apply',
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) {
const url = Api.buildUrl(this.groupMembersPath).replace(':id', encodeURIComponent(id));
@ -308,9 +339,17 @@ const Api = {
.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) {
// 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), {
headers: {
'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 flash from './flash';
import axios from './lib/utils/axios_utils';
import * as Emoji from '~/emoji';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
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
@ -619,7 +615,7 @@ export class AwardsHandler {
let awardsHandlerPromise = null;
export default function loadAwardsHandler(reload = false) {
if (!awardsHandlerPromise || reload) {
awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji').then(Emoji => {
awardsHandlerPromise = Emoji.initEmojiMap().then(() => {
const awardsHandler = new AwardsHandler(Emoji);
awardsHandler.bindEvents();
return awardsHandler;

View file

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

View file

@ -51,6 +51,7 @@ export default {
'scrollToDraft',
'toggleResolveDiscussion',
]),
...mapActions(['setSelectedCommentPositionHover']),
update(data) {
this.updateDraft(data);
},
@ -67,12 +68,16 @@ export default {
};
</script>
<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">
<noteable-note
:note="draft"
:diff-lines="diffFile.highlighted_diff_lines"
:line="line"
:discussion-root="true"
class="draft-note"
@handleEdit="handleEditing"
@cancelForm="handleNotEditing"
@ -81,7 +86,7 @@ export default {
@handleUpdateNote="update"
@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') }}
</strong>
</noteable-note>

View file

@ -35,11 +35,15 @@ export default {
<tr :class="className" class="notes_holder">
<td class="notes_line old"></td>
<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 class="notes_line new"></td>
<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>
</tr>
</template>

View file

@ -96,7 +96,7 @@ export default {
<preview-item :draft="draft" :is-last="isLast(index)" />
</li>
</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 class="dropdown-footer">
<publish-button

View file

@ -52,14 +52,12 @@ export default {
});
},
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
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 position?.new_line || position?.old_line;
return this.position?.new_line || this.position?.old_line;
},
content() {
const el = document.createElement('div');
@ -70,11 +68,14 @@ export default {
showLinePosition() {
return this.draft.file_hash || this.isDiffDiscussion;
},
position() {
return this.draft.position || this.discussion.position;
},
startLineNumber() {
return getStartLineNumber(this.draft.position?.line_range);
return getStartLineNumber(this.position?.line_range);
},
endLineNumber() {
return getEndLineNumber(this.draft.position?.line_range);
return getEndLineNumber(this.position?.line_range);
},
},
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,47 +1,69 @@
import 'document-register-element';
import isEmojiUnicodeSupported from '../emoji/support';
import { initEmojiMap, getEmojiInfo, emojiFallbackImageSrc, emojiImageTag } from '../emoji';
class GlEmoji extends HTMLElement {
constructor() {
super();
const emojiUnicode = this.textContent.trim();
const { name, unicodeVersion, fallbackSrc, fallbackSpriteClass } = this.dataset;
this.initialize();
}
initialize() {
let emojiUnicode = this.textContent.trim();
const { fallbackSpriteClass, fallbackSrc } = this.dataset;
let { name, unicodeVersion } = this.dataset;
const isEmojiUnicode =
this.childNodes &&
Array.prototype.every.call(this.childNodes, childNode => childNode.nodeType === 3);
const hasImageFallback = fallbackSrc && fallbackSrc.length > 0;
const hasCssSpriteFalback = fallbackSpriteClass && fallbackSpriteClass.length > 0;
return initEmojiMap().then(() => {
if (!unicodeVersion) {
const emojiInfo = getEmojiInfo(name);
if (emojiUnicode && isEmojiUnicode && !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)) {
// CSS sprite fallback takes precedence over image fallback
if (hasCssSpriteFalback) {
if (!gon.emoji_sprites_css_added && gon.emoji_sprites_css_path) {
const emojiSpriteLinkTag = document.createElement('link');
emojiSpriteLinkTag.setAttribute('rel', 'stylesheet');
emojiSpriteLinkTag.setAttribute('href', gon.emoji_sprites_css_path);
document.head.appendChild(emojiSpriteLinkTag);
gon.emoji_sprites_css_added = true;
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;
}
// IE 11 doesn't like adding multiple at once :(
this.classList.add('emoji-icon');
this.classList.add(fallbackSpriteClass);
} else {
import(/* webpackChunkName: 'emoji' */ '../emoji')
.then(({ emojiImageTag, emojiFallbackImageSrc }) => {
if (hasImageFallback) {
this.innerHTML = emojiImageTag(name, fallbackSrc);
} else {
const src = emojiFallbackImageSrc(name);
this.innerHTML = emojiImageTag(name, src);
}
})
.catch(() => {
// do nothing
});
}
}
const isEmojiUnicode =
this.childNodes &&
Array.prototype.every.call(this.childNodes, childNode => childNode.nodeType === 3);
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
if (hasCssSpriteFallback) {
if (!gon.emoji_sprites_css_added && gon.emoji_sprites_css_path) {
const emojiSpriteLinkTag = document.createElement('link');
emojiSpriteLinkTag.setAttribute('rel', 'stylesheet');
emojiSpriteLinkTag.setAttribute('href', gon.emoji_sprites_css_path);
document.head.appendChild(emojiSpriteLinkTag);
gon.emoji_sprites_css_added = true;
}
// IE 11 doesn't like adding multiple at once :(
this.classList.add('emoji-icon');
this.classList.add(fallbackSpriteClass);
} else if (hasImageFallback) {
this.innerHTML = emojiImageTag(name, fallbackSrc);
} else {
const src = emojiFallbackImageSrc(name);
this.innerHTML = emojiImageTag(name, src);
}
}
});
}
}

View file

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

View file

@ -1,5 +1,5 @@
import $ from 'jquery';
import { getSelectedFragment } from '~/lib/utils/common_utils';
import { getSelectedFragment, insertText } from '~/lib/utils/common_utils';
export class CopyAsGFM {
constructor() {
@ -79,7 +79,7 @@ export class CopyAsGFM {
}
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,
// we are either inside an inline code span that starts with 1 backtick
// or a code block that starts with 3 backticks.

View file

@ -174,7 +174,7 @@ export default function renderMermaid($els) {
if (!$els.length) return;
const visibleMermaids = $els.filter(function filter() {
return $(this).closest('details').length === 0;
return $(this).closest('details').length === 0 && $(this).is(':visible');
});
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>
import { GlToggle, GlSprintf } from '@gitlab/ui';
import { GlToggle } from '@gitlab/ui';
import AccessorUtilities from '~/lib/utils/accessor';
import { disableShortcuts, enableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
export default {
components: {
GlSprintf,
GlToggle,
},
data() {
@ -32,29 +31,10 @@ export default {
<gl-toggle
v-model="shortcutsEnabled"
aria-describedby="shortcutsToggle"
class="prepend-left-10 mb-0"
label-position="right"
label="Keyboard shortcuts"
label-position="left"
@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>
</template>

View file

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

View file

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

View file

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

View file

@ -34,7 +34,7 @@ export default {
</script>
<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">
<gl-loading-icon class="mt-5" size="lg" />
</div>

View file

@ -56,7 +56,7 @@ export default class SketchLoader {
error() {
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 = __(`
Cannot show preview. For previews on sketch files, they must have the file format
introduced by Sketch version 43 and above.

View file

@ -2,7 +2,6 @@
import { GlPopover, GlSprintf, GlDeprecatedButton, GlIcon } from '@gitlab/ui';
import { parseBoolean, scrollToElement, setCookie, getCookie } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import { glEmojiTag } from '~/emoji';
import Tracking from '~/tracking';
const trackingMixin = Tracking.mixin();
@ -11,14 +10,16 @@ const popoverStates = {
suggest_gitlab_ci_yml: {
title: s__(`suggestPipeline|1/2: Choose a template`),
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: {
title: s__(`suggestPipeline|2/2: Commit your changes`),
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() {
return popoverStates[this.trackLabel].content || '';
},
suggestFooter() {
return popoverStates[this.trackLabel].footer || '';
},
emoji() {
return popoverStates[this.trackLabel].emoji || '';
},
@ -123,16 +127,13 @@ export default {
</span>
</template>
<gl-sprintf :message="suggestContent">
<template #bold="{content}">
<strong> {{ content }} </strong>
</template>
<template #footer="{content}">
<div class="mt-3">
{{ content }}
<span v-html="emoji"></span>
</div>
</template>
</gl-sprintf>
<gl-sprintf :message="suggestContent" />
<div class="mt-3">
<gl-sprintf :message="suggestFooter">
<template #bold="{ content }">
<strong> {{ content }} </strong>
</template>
</gl-sprintf>
</div>
</gl-popover>
</template>

View file

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

View file

@ -5,7 +5,7 @@ import NewCommitForm from '../new_commit_form';
import EditBlob from './edit_blob';
import BlobFileDropzone from '../blob/blob_file_dropzone';
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';
export default () => {
@ -51,10 +51,7 @@ export default () => {
new BlobFileDropzone(uploadBlobForm, method);
new NewCommitForm(uploadBlobForm);
window.gl.utils.disableButtonIfEmptyField(
uploadBlobForm.find('.js-commit-message'),
'.btn-upload-file',
);
disableButtonIfEmptyField(uploadBlobForm.find('.js-commit-message'), '.btn-upload-file');
}
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 axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { __ } from '~/locale';
import { BLOB_EDITOR_ERROR, BLOB_PREVIEW_ERROR } from './constants';
import TemplateSelectorMediator from '../blob/file_template_mediator';
import getModeByFileExtension from '~/lib/utils/ace_utils';
import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
const monacoEnabledGlobally = window.gon.features?.monacoBlobs;
export default class EditBlob {
// The options object has:
// assetsPath, filePath, currentAction, projectId, isMarkdown
constructor(options) {
this.options = options;
this.configureAceEditor();
this.initModePanesAndLinks();
this.initSoftWrap();
this.initFileSelectors();
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.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() {
const { filePath, assetsPath, isMarkdown } = this.options;
const { filePath, assetsPath } = this.options;
ace.config.set('modePath', `${assetsPath}/ace`);
ace.config.loadModule('ace/ext/searchbox');
ace.config.loadModule('ace/ext/modelist');
this.editor = ace.edit('editor');
if (isMarkdown) {
addEditorMarkdownListeners(this.editor);
}
// This prevents warnings re: automatic scrolling being logged
this.editor.$blockScrolling = Infinity;
this.editor.focus();
if (filePath) {
this.editor.getSession().setMode(getModeByFileExtension(filePath));
}
@ -81,7 +129,7 @@ export default class EditBlob {
currentPane.empty().append(data);
currentPane.renderGFM();
})
.catch(() => createFlash(__('An error occurred previewing the blob')));
.catch(() => createFlash(BLOB_PREVIEW_ERROR));
}
this.$toggleButton.show();
@ -90,14 +138,19 @@ export default class EditBlob {
}
initSoftWrap() {
this.isSoftWrapped = false;
this.isSoftWrapped = Boolean(this.options.monacoEnabled);
this.$toggleButton = $('.soft-wrap-toggle');
this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
this.$toggleButton.on('click', () => this.toggleSoftWrap());
}
toggleSoftWrap() {
this.isSoftWrapped = !this.isSoftWrapped;
this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
if (this.options.monacoEnabled) {
this.editor.updateOptions({ wordWrap: this.isSoftWrapped ? 'on' : 'off' });
} else {
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
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"
>
<board-column
@ -77,6 +77,7 @@ export default {
:can-admin-list="canAdminList"
:disabled="disabled"
:board-id="boardId"
:group-id="groupId"
/>
</div>
</template>

View file

@ -5,10 +5,11 @@ import {
GlLabel,
GlTooltip,
GlIcon,
GlSprintf,
GlTooltipDirective,
} from '@gitlab/ui';
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 BoardDelete from './board_delete';
import IssueCount from './issue_count.vue';
@ -25,6 +26,7 @@ export default {
GlLabel,
GlTooltip,
GlIcon,
GlSprintf,
IssueCount,
},
directives: {
@ -82,10 +84,20 @@ export default {
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;
return sprintf(__('%{issuesSize} issues'), { issuesSize });
return n__(`%d issue`, `%d issues`, issuesSize);
},
chevronTooltip() {
return this.list.isExpanded ? s__('Boards|Collapse') : s__('Boards|Expand');
@ -111,6 +123,9 @@ export default {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `boards.${this.boardId}.${this.listType}.${this.list.id}`;
},
collapsedTooltipTitle() {
return this.listTitle || this.listAssignee;
},
},
methods: {
showScopedLabels(label) {
@ -147,7 +162,7 @@ export default {
'has-border': list.label && list.label.color,
'gl-relative': 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 }"
class="board-header gl-relative"
@ -157,7 +172,9 @@ export default {
<h3
:class="{
'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"
>
@ -167,21 +184,17 @@ export default {
:aria-label="chevronTooltip"
:title="chevronTooltip"
:icon="chevronIcon"
class="board-title-caret no-drag"
class="board-title-caret no-drag gl-cursor-pointer"
variant="link"
@click="toggleExpanded"
/>
<!-- The following is only true in EE and if it is a milestone -->
<span
v-if="list.type === 'milestone' && list.milestone"
aria-hidden="true"
class="gl-mr-2 milestone-icon"
>
<span v-if="showMilestoneListDetails" aria-hidden="true" class="gl-mr-2 milestone-icon">
<gl-icon name="timer" />
</span>
<a
v-if="list.type === 'assignee'"
v-if="showAssigneeListDetails"
:href="list.assignee.path"
class="user-avatar-link js-no-trigger"
>
@ -195,7 +208,10 @@ export default {
width="20"
/>
</a>
<div class="board-title-text">
<div
class="board-title-text"
:class="{ 'gl-display-none': !list.isExpanded && isSwimlanesHeader }"
>
<span
v-if="list.type !== 'label'"
v-gl-tooltip.hover
@ -208,7 +224,7 @@ export default {
{{ list.title }}
</span>
<span v-if="list.type === 'assignee'" class="board-title-sub-text gl-ml-2">
@{{ list.assignee.username }}
@{{ listAssignee }}
</span>
<gl-label
v-if="list.type === 'label'"
@ -220,6 +236,33 @@ export default {
:title="list.label.title"
/>
</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
v-if="canAdminList && !list.preset && list.id"
:list="list"
@ -229,7 +272,7 @@ export default {
v-gl-tooltip.hover.bottom
:class="{ 'gl-display-none': !list.isExpanded }"
: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')"
icon="remove"
size="small"
@ -238,10 +281,11 @@ export default {
</board-delete>
<div
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">
<gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltip" />
<gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltipLabel" />
<span ref="issueCount" class="issue-count-badge-count">
<gl-icon class="gl-mr-2" name="issues" />
<issue-count :issues-size="list.issuesSize" :max-issue-count="list.maxIssueCount" />

View file

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

View file

@ -233,7 +233,7 @@ export default {
</script>
<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">
<gl-dropdown
data-qa-selector="boards_dropdown"

View file

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

View file

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

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