New upstream version 13.11.2+ds1

This commit is contained in:
Pirate Praveen 2021-04-29 21:17:54 +05:30
parent 941bf6661b
commit 2b1ea5b95e
8774 changed files with 246487 additions and 86706 deletions

View file

@ -17,7 +17,7 @@ stages:
# in cases where jobs require Docker-in-Docker, the job # in cases where jobs require Docker-in-Docker, the job
# definition must be extended with `.use-docker-in-docker` # definition must be extended with `.use-docker-in-docker`
default: default:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.31-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.36"
tags: tags:
- gitlab-org - gitlab-org
# All jobs are interruptible by default # All jobs are interruptible by default
@ -38,7 +38,7 @@ workflow:
when: never when: never
# For merge requests, create a pipeline. # For merge requests, create a pipeline.
- if: '$CI_MERGE_REQUEST_IID' - if: '$CI_MERGE_REQUEST_IID'
# For `master` branch, create a pipeline (this includes on schedules, pushes, merges, etc.). # For `$CI_DEFAULT_BRANCH` branch, create a pipeline (this includes on schedules, pushes, merges, etc.).
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
# For tags, create a pipeline. # For tags, create a pipeline.
- if: '$CI_COMMIT_TAG' - if: '$CI_COMMIT_TAG'
@ -53,6 +53,8 @@ workflow:
variables: variables:
RAILS_ENV: "test" RAILS_ENV: "test"
NODE_ENV: "test" NODE_ENV: "test"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
SIMPLECOV: "true" SIMPLECOV: "true"
GIT_DEPTH: "20" GIT_DEPTH: "20"
GIT_SUBMODULE_STRATEGY: "none" GIT_SUBMODULE_STRATEGY: "none"
@ -100,6 +102,7 @@ include:
- local: .gitlab/ci/qa.gitlab-ci.yml - local: .gitlab/ci/qa.gitlab-ci.yml
- local: .gitlab/ci/reports.gitlab-ci.yml - local: .gitlab/ci/reports.gitlab-ci.yml
- local: .gitlab/ci/rails.gitlab-ci.yml - local: .gitlab/ci/rails.gitlab-ci.yml
- local: .gitlab/ci/vendored-gems.gitlab-ci.yml
- local: .gitlab/ci/review.gitlab-ci.yml - local: .gitlab/ci/review.gitlab-ci.yml
- local: .gitlab/ci/rules.gitlab-ci.yml - local: .gitlab/ci/rules.gitlab-ci.yml
- local: .gitlab/ci/setup.gitlab-ci.yml - local: .gitlab/ci/setup.gitlab-ci.yml
@ -111,4 +114,7 @@ include:
- local: .gitlab/ci/dast.gitlab-ci.yml - local: .gitlab/ci/dast.gitlab-ci.yml
- local: .gitlab/ci/workhorse.gitlab-ci.yml - local: .gitlab/ci/workhorse.gitlab-ci.yml
- local: .gitlab/ci/graphql.gitlab-ci.yml - local: .gitlab/ci/graphql.gitlab-ci.yml
- remote: 'https://gitlab.com/gitlab-org/frontend/untamper-my-lockfile/-/raw/main/.gitlab-ci-template.yml' # switch the remote include to a local include until this is resolved:
# https://gitlab.com/gitlab-org/gitlab/-/issues/327299
# - remote: 'https://gitlab.com/gitlab-org/frontend/untamper-my-lockfile/-/raw/main/.gitlab-ci-template.yml'
- local: .gitlab/ci/untamper-my-lockfile.yml

View file

@ -135,7 +135,7 @@
/doc/api/invitations.md @aqualls /doc/api/invitations.md @aqualls
/doc/api/experiments.md @aqualls /doc/api/experiments.md @aqualls
/doc/development/experiment_guide/ @aqualls /doc/development/experiment_guide/ @aqualls
/doc/development/snowplow.md @aqualls /doc/development/snowplow/ @aqualls
/doc/development/usage_ping/ @aqualls /doc/development/usage_ping/ @aqualls
/doc/user/admin_area/license.md @aqualls /doc/user/admin_area/license.md @aqualls
@ -193,7 +193,7 @@ Dangerfile @gl-quality/eng-prod
/lib/gitlab/auth/ldap/ @dblessing @mkozono /lib/gitlab/auth/ldap/ @dblessing @mkozono
[Templates] [Templates]
/lib/gitlab/ci/templates/ @nolith @shinya.maeda /lib/gitlab/ci/templates/ @nolith @shinya.maeda @matteeyah
/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @DylanGriffith @mayra-cabrera @tkuah /lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @DylanGriffith @mayra-cabrera @tkuah
/lib/gitlab/ci/templates/Security/ @plafoucriere @gonzoyumo @twoodham @sethgitlab /lib/gitlab/ci/templates/Security/ @plafoucriere @gonzoyumo @twoodham @sethgitlab
@ -277,6 +277,7 @@ Dangerfile @gl-quality/eng-prod
/lib/gitlab/experimentation/ @gitlab-org/growth/experiment-devs /lib/gitlab/experimentation/ @gitlab-org/growth/experiment-devs
/lib/gitlab/experimentation.rb @gitlab-org/growth/experiment-devs /lib/gitlab/experimentation.rb @gitlab-org/growth/experiment-devs
/lib/gitlab/experimentation_logger.rb @gitlab-org/growth/experiment-devs /lib/gitlab/experimentation_logger.rb @gitlab-org/growth/experiment-devs
/ee/spec/requests/api/experiments_spec.rb @gitlab-org/growth/experiment-devs
[Legal] [Legal]
/config/dependency_decisions.yml @gitlab-org/legal-reviewers /config/dependency_decisions.yml @gitlab-org/legal-reviewers

View file

@ -0,0 +1,38 @@
---
# Settings for generating changelogs using the GitLab API. See
# https://docs.gitlab.com/ee/api/repositories.html#generate-changelog-data for
# more information.
categories:
added: Added
fixed: Fixed
changed: Changed
deprecated: Deprecated
removed: Removed
security: Security
performance: Performance
other: Other
template: |
{% if categories %}
{% each categories %}
### {{ title }} ({% if single_change %}1 change{% else %}{{ count }} changes{% end %})
{% each entries %}
- [{{ title }}]({{ commit.reference }})\
{% if author.contributor %} by {{ author.reference }}{% end %}\
{% if commit.trailers.MR %}\
([merge request]({{ commit.trailers.MR }}))\
{% else %}\
{% if merge_request %}\
([merge request]({{ merge_request.reference }}))\
{% end %}\
{% end %}\
{% if commit.trailers.EE %}\
**GitLab Enterprise Edition**\
{% end %}
{% end %}
{% end %}
{% else %}
No changes.
{% end %}

View file

@ -1,6 +1,8 @@
# This image is used by the `review-qa-*` jobs. Not currently used by the `omnibus-gitlab` pipelines which rebuild this # This image is used by the `review-qa-*` jobs. The image name is also passed to the downstream `omnibus-gitlab-mirror` pipeline
# image, e.g. https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror/-/jobs/587107399, which we could probably avoid. # triggered by `package-and-qa` so that it doesn't have to rebuild it a second time. The downstream `omnibus-gitlab-mirror` pipeline
# See https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5429. # itself passes the image name to the `gitlab-qa-mirror` pipeline so that it can use it instead of inferring an end-to-end image
# from the GitLab image built by the downstream `omnibus-gitlab-mirror` pipeline.
# See https://docs.gitlab.com/ee/development/testing_guide/end_to_end/index.html#testing-code-in-merge-requests for more details.
build-qa-image: build-qa-image:
extends: extends:
- .use-kaniko - .use-kaniko

View file

@ -1,4 +1,4 @@
# Builds a cached .tar.gz of the master branch with full history and # Builds a cached .tar.gz of the $CI_DEFAULT_BRANCH branch with full history and
# uploads it to Google Cloud Storage. This archive is downloaded by a # uploads it to Google Cloud Storage. This archive is downloaded by a
# script defined by a CI/CD variable named CI_PRE_CLONE_SCRIPT. This has # script defined by a CI/CD variable named CI_PRE_CLONE_SCRIPT. This has
# two benefits: # two benefits:
@ -41,6 +41,7 @@ cache-repo:
cd $CI_PROJECT_NAME; cd $CI_PROJECT_NAME;
time git repack -d; time git repack -d;
echo "Archiving $CI_PROJECT_NAME into /tmp/$SHALLOW_CLONE_TAR_FILENAME."; echo "Archiving $CI_PROJECT_NAME into /tmp/$SHALLOW_CLONE_TAR_FILENAME.";
time git remote rm origin;
time tar cf /tmp/$SHALLOW_CLONE_TAR_FILENAME .; time tar cf /tmp/$SHALLOW_CLONE_TAR_FILENAME .;
echo "GZipping /tmp/$SHALLOW_CLONE_TAR_FILENAME."; echo "GZipping /tmp/$SHALLOW_CLONE_TAR_FILENAME.";
time gzip /tmp/$SHALLOW_CLONE_TAR_FILENAME; time gzip /tmp/$SHALLOW_CLONE_TAR_FILENAME;
@ -52,7 +53,9 @@ cache-repo:
echo "Cloning $CI_REPOSITORY_URL into $CI_PROJECT_NAME."; echo "Cloning $CI_REPOSITORY_URL into $CI_PROJECT_NAME.";
time git clone --progress $CI_REPOSITORY_URL $CI_PROJECT_NAME; time git clone --progress $CI_REPOSITORY_URL $CI_PROJECT_NAME;
cd $CI_PROJECT_NAME; cd $CI_PROJECT_NAME;
time git repack -d;
echo "Archiving $CI_PROJECT_NAME into /tmp/$FULL_CLONE_TAR_FILENAME."; echo "Archiving $CI_PROJECT_NAME into /tmp/$FULL_CLONE_TAR_FILENAME.";
time git remote rm origin;
time tar cf /tmp/$FULL_CLONE_TAR_FILENAME .; time tar cf /tmp/$FULL_CLONE_TAR_FILENAME .;
echo "GZipping /tmp/$FULL_CLONE_TAR_FILENAME."; echo "GZipping /tmp/$FULL_CLONE_TAR_FILENAME.";
time gzip /tmp/$FULL_CLONE_TAR_FILENAME; time gzip /tmp/$FULL_CLONE_TAR_FILENAME;

View file

@ -97,7 +97,7 @@ DAST-fullscan-ruleset5:
variables: variables:
DAST_USERNAME: "user5" DAST_USERNAME: "user5"
script: script:
- export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 10010 | enable_rule 10011 | enable_rule 10015 | enable_rule 10017 | enable_rule 10019) - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 10010 | enable_rule 10011 | enable_rule 10017 | enable_rule 10019)
- echo $DAST_EXCLUDE_RULES - echo $DAST_EXCLUDE_RULES
- /analyze -t $DAST_WEBSITE -d - /analyze -t $DAST_WEBSITE -d

View file

@ -10,17 +10,18 @@
# because some repos are private and CI_JOB_TOKEN cannot access files. # because some repos are private and CI_JOB_TOKEN cannot access files.
# See https://gitlab.com/gitlab-org/gitlab/issues/191273 # See https://gitlab.com/gitlab-org/gitlab/issues/191273
GIT_DEPTH: 1 GIT_DEPTH: 1
# By default, deploy the Review App using the `master` branch of the `gitlab-org/gitlab-docs` project
DOCS_BRANCH: master
environment: environment:
name: review-docs/$DOCS_GITLAB_REPO_SUFFIX-$CI_MERGE_REQUEST_IID name: review-docs/mr-${CI_MERGE_REQUEST_IID}
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are CI variables # DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are CI variables
# Discussion: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/14236/diffs#note_40140693 # Discussion: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/14236/diffs#note_40140693
auto_stop_in: 2 weeks auto_stop_in: 2 weeks
url: http://docs-preview-$DOCS_GITLAB_REPO_SUFFIX-$CI_MERGE_REQUEST_IID.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX url: http://${DOCS_BRANCH}-${DOCS_GITLAB_REPO_SUFFIX}-${CI_MERGE_REQUEST_IID}.${DOCS_REVIEW_APPS_DOMAIN}/${DOCS_GITLAB_REPO_SUFFIX}
on_stop: review-docs-cleanup on_stop: review-docs-cleanup
before_script: before_script:
- apk add --update openssl - source ./scripts/utils.sh
- gem install httparty --no-document --version 0.17.3 - install_gitlab_gem
- gem install gitlab --no-document --version 4.13.0
# Always trigger a docs build in gitlab-docs only on docs-only branches. # Always trigger a docs build in gitlab-docs only on docs-only branches.
# Useful to preview the docs changes live. # Useful to preview the docs changes live.
@ -33,7 +34,7 @@ review-docs-deploy:
review-docs-cleanup: review-docs-cleanup:
extends: .review-docs extends: .review-docs
environment: environment:
name: review-docs/$DOCS_GITLAB_REPO_SUFFIX-$CI_MERGE_REQUEST_IID name: review-docs/mr-${CI_MERGE_REQUEST_IID}
action: stop action: stop
script: script:
- ./scripts/trigger-build docs cleanup - ./scripts/trigger-build docs cleanup
@ -64,10 +65,8 @@ docs-lint links:
- cd /tmp/gitlab-docs - cd /tmp/gitlab-docs
# Build HTML from Markdown # Build HTML from Markdown
- bundle exec nanoc - bundle exec nanoc
# Check the internal links # Check the internal links and anchors (in parallel)
- bundle exec nanoc check internal_links - "parallel time bundle exec nanoc check ::: internal_links internal_anchors"
# Check the internal anchor links
- bundle exec nanoc check internal_anchors
ui-docs-links lint: ui-docs-links lint:
extends: extends:

View file

@ -1,22 +1,15 @@
.frontend-base:
extends:
- .default-retry
- .default-before_script
variables:
SETUP_DB: "false"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
.yarn-install: &yarn-install .yarn-install: &yarn-install
- source scripts/utils.sh - source scripts/utils.sh
- run_timed_command "retry yarn install --frozen-lockfile" - run_timed_command "retry yarn install --frozen-lockfile"
.compile-assets-base: .compile-assets-base:
extends: extends:
- .frontend-base - .default-retry
- .default-before_script
- .assets-compile-cache - .assets-compile-cache
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-git-2.29-lfs-2.9-node-14.15-yarn-1.22-graphicsmagick-1.3.34 image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-git-2.31-lfs-2.9-node-14.15-yarn-1.22-graphicsmagick-1.3.36
variables: variables:
SETUP_DB: "false"
WEBPACK_VENDOR_DLL: "true" WEBPACK_VENDOR_DLL: "true"
stage: prepare stage: prepare
script: script:
@ -93,13 +86,13 @@ update-yarn-cache:
.frontend-fixtures-base: .frontend-fixtures-base:
extends: extends:
- .frontend-base - .default-retry
- .default-before_script
- .rails-cache - .rails-cache
- .use-pg11 - .use-pg11
stage: fixtures stage: fixtures
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"] needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
variables: variables:
SETUP_DB: "true"
WEBPACK_VENDOR_DLL: "true" WEBPACK_VENDOR_DLL: "true"
script: script:
- run_timed_command "gem install knapsack --no-document" - run_timed_command "gem install knapsack --no-document"
@ -151,10 +144,8 @@ graphql-schema-dump:
.frontend-test-base: .frontend-test-base:
extends: extends:
- .frontend-base - .default-retry
- .yarn-cache - .yarn-cache
variables:
USE_BUNDLE_INSTALL: "false"
stage: test stage: test
eslint-as-if-foss: eslint-as-if-foss:
@ -246,7 +237,7 @@ coverage-frontend:
extends: extends:
- .default-retry - .default-retry
- .yarn-cache - .yarn-cache
- .frontend:rules:ee-mr-and-master-only - .frontend:rules:ee-mr-and-default-branch-only
needs: ["jest"] needs: ["jest"]
stage: post-test stage: post-test
before_script: before_script:

View file

@ -27,7 +27,7 @@
.rails-cache: .rails-cache:
cache: cache:
key: "rails-v4" key: "rails-v5"
paths: paths:
- vendor/ruby/ - vendor/ruby/
- vendor/gitaly-ruby/ - vendor/gitaly-ruby/
@ -87,7 +87,7 @@
policy: pull policy: pull
.use-pg11: .use-pg11:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.31-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.36"
services: services:
- name: postgres:11.6 - name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@ -96,7 +96,7 @@
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
.use-pg12: .use-pg12:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-12-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.31-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-12-graphicsmagick-1.3.36"
services: services:
- name: postgres:12 - name: postgres:12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@ -105,7 +105,7 @@
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
.use-pg11-ee: .use-pg11-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.31-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.36"
services: services:
- name: postgres:11.6 - name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@ -116,7 +116,7 @@
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
.use-pg12-ee: .use-pg12-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-12-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.31-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-12-graphicsmagick-1.3.36"
services: services:
- name: postgres:12 - name: postgres:12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]

View file

@ -44,8 +44,6 @@ memory-on-boot:
NODE_ENV: "production" NODE_ENV: "production"
RAILS_ENV: "production" RAILS_ENV: "production"
SETUP_DB: "true" SETUP_DB: "true"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
script: script:
- PATH_TO_HIT="/users/sign_in" CUT_OFF=0.3 bundle exec derailed exec perf:mem >> 'tmp/memory_on_boot.txt' - PATH_TO_HIT="/users/sign_in" CUT_OFF=0.3 bundle exec derailed exec perf:mem >> 'tmp/memory_on_boot.txt'
- scripts/generate-memory-metrics-on-boot tmp/memory_on_boot.txt >> 'tmp/memory_on_boot_metrics.txt' - scripts/generate-memory-metrics-on-boot tmp/memory_on_boot.txt >> 'tmp/memory_on_boot_metrics.txt'

View file

@ -142,7 +142,7 @@
############################ ############################
####################################################### #######################################################
# EE/FOSS: default refs (MRs, master, schedules) jobs # # EE/FOSS: default refs (MRs, default branch, schedules) jobs #
setup-test-env: setup-test-env:
extends: extends:
- .rails-job-base - .rails-job-base
@ -183,6 +183,7 @@ setup-test-env:
- tmp/tests/gitlab-workhorse/gitlab-workhorse - tmp/tests/gitlab-workhorse/gitlab-workhorse
- tmp/tests/gitlab-workhorse/gitlab-resize-image - tmp/tests/gitlab-workhorse/gitlab-resize-image
- tmp/tests/gitlab-workhorse/config.toml - tmp/tests/gitlab-workhorse/config.toml
- tmp/tests/gitlab-workhorse/WORKHORSE_TREE
- tmp/tests/repositories/ - tmp/tests/repositories/
- tmp/tests/second_storage/ - tmp/tests/second_storage/
when: always when: always
@ -256,17 +257,6 @@ static-analysis:
- run_timed_command "retry yarn install --frozen-lockfile" - run_timed_command "retry yarn install --frozen-lockfile"
- scripts/static-analysis - scripts/static-analysis
downtime_check:
extends:
- .rails-job-base
- .rails:rules:downtime_check
needs: []
stage: test
variables:
SETUP_DB: "false"
script:
- bundle exec rake downtime_check
rspec migration pg11: rspec migration pg11:
extends: extends:
- .rspec-base-pg11 - .rspec-base-pg11
@ -346,7 +336,7 @@ db:migrate:reset:
db:check-schema: db:check-schema:
extends: extends:
- .db-job-base - .db-job-base
- .rails:rules:ee-mr-and-master-only - .rails:rules:ee-mr-and-default-branch-only
script: script:
- source scripts/schema_changed.sh - source scripts/schema_changed.sh
@ -358,32 +348,31 @@ db:check-migrations:
- scripts/validate_migration_schema - scripts/validate_migration_schema
allow_failure: true allow_failure: true
db:migrate-from-v12.10.0: db:migrate-from-previous-major-version:
extends: .db-job-base extends: .db-job-base
variables: variables:
USE_BUNDLE_INSTALL: "false"
SETUP_DB: "false" SETUP_DB: "false"
PROJECT_TO_CHECKOUT: "gitlab-foss"
TAG_TO_CHECKOUT: "v12.10.14"
script: script:
- export PROJECT_TO_CHECKOUT="gitlab" - '[[ -d "ee/" ]] || export PROJECT_TO_CHECKOUT="gitlab"'
- export TAG_TO_CHECKOUT="v12.10.0-ee" - '[[ -d "ee/" ]] || export TAG_TO_CHECKOUT="${TAG_TO_CHECKOUT}-ee"'
- '[[ -d "ee/" ]] || export PROJECT_TO_CHECKOUT="gitlab-foss"'
- '[[ -d "ee/" ]] || export TAG_TO_CHECKOUT="v12.10.0"'
- retry 'git fetch https://gitlab.com/gitlab-org/$PROJECT_TO_CHECKOUT.git $TAG_TO_CHECKOUT' - retry 'git fetch https://gitlab.com/gitlab-org/$PROJECT_TO_CHECKOUT.git $TAG_TO_CHECKOUT'
- git checkout -f FETCH_HEAD - git checkout -f FETCH_HEAD
# Patch Gemfile of the previous major version for compatibility.
- sed -i -e "s/gem 'grpc', '~> 1.24.0'/gem 'grpc', '~> 1.30.2'/" Gemfile # Update gRPC for Ruby 2.7 - sed -i -e "s/gem 'grpc', '~> 1.24.0'/gem 'grpc', '~> 1.30.2'/" Gemfile # Update gRPC for Ruby 2.7
- sed -i -e "s/gem 'google-protobuf', '~> 3.8.0'/gem 'google-protobuf', '~> 3.12.0'/" Gemfile - sed -i -e "s/gem 'google-protobuf', '~> 3.8.0'/gem 'google-protobuf', '~> 3.12'/" Gemfile
- gem install bundler:1.17.3 - sed -i -e "s/gem 'nokogiri', '~> 1.10.5'/gem 'nokogiri', '~> 1.11.0'/" Gemfile
- bundle update google-protobuf grpc bootsnap - sed -i -e "s/gem 'mimemagic', '~> 0.3.2'/gem 'ruby-magic', '~> 0.3.2'/" Gemfile
- bundle install $BUNDLE_INSTALL_FLAGS - run_timed_command "gem install bundler:1.17.3"
- date - run_timed_command "bundle update google-protobuf nokogiri grpc mimemagic bootsnap"
- run_timed_command "bundle install ${BUNDLE_INSTALL_FLAGS}"
- cp config/gitlab.yml.example config/gitlab.yml - cp config/gitlab.yml.example config/gitlab.yml
- bundle exec rake db:drop db:create db:structure:load db:seed_fu - run_timed_command "bundle exec rake db:drop db:create db:structure:load db:migrate db:seed_fu"
- date
- git checkout -f $CI_COMMIT_SHA - git checkout -f $CI_COMMIT_SHA
- bundle install $BUNDLE_INSTALL_FLAGS - run_timed_command "bundle install ${BUNDLE_INSTALL_FLAGS}"
- date - run_timed_command "bundle exec rake db:migrate"
- . scripts/prepare_build.sh
- date
- bundle exec rake db:migrate
db:rollback: db:rollback:
extends: .db-job-base extends: .db-job-base
@ -535,11 +524,11 @@ rspec:feature-flags:
run_timed_command "bundle exec scripts/used-feature-flags"; run_timed_command "bundle exec scripts/used-feature-flags";
fi fi
# EE/FOSS: default refs (MRs, master, schedules) jobs # # EE/FOSS: default refs (MRs, default branch, schedules) jobs #
####################################################### #######################################################
################################################## ##################################################
# EE: default refs (MRs, master, schedules) jobs # # EE: default refs (MRs, default branch, schedules) jobs #
rspec migration pg11-as-if-foss: rspec migration pg11-as-if-foss:
extends: extends:
- .rspec-base-pg11-as-if-foss - .rspec-base-pg11-as-if-foss
@ -682,81 +671,81 @@ db:rollback geo:
script: script:
- bundle exec rake geo:db:migrate VERSION=20170627195211 - bundle exec rake geo:db:migrate VERSION=20170627195211
- bundle exec rake geo:db:migrate - bundle exec rake geo:db:migrate
# EE: default refs (MRs, master, schedules) jobs # # EE: default refs (MRs, default branch, schedules) jobs #
################################################## ##################################################
########################################## ##########################################
# EE/FOSS: master nightly scheduled jobs # # EE/FOSS: default branch nightly scheduled jobs #
rspec migration pg12: rspec migration pg12:
extends: extends:
- .rspec-base-pg12 - .rspec-base-pg12
- .rspec-base-migration - .rspec-base-migration
- .rails:rules:master-schedule-nightly--code-backstage - .rails:rules:default-branch-schedule-nightly--code-backstage
- .rspec-migration-parallel - .rspec-migration-parallel
rspec unit pg12: rspec unit pg12:
extends: extends:
- .rspec-base-pg12 - .rspec-base-pg12
- .rails:rules:master-schedule-nightly--code-backstage - .rails:rules:default-branch-schedule-nightly--code-backstage
- .rspec-unit-parallel - .rspec-unit-parallel
rspec integration pg12: rspec integration pg12:
extends: extends:
- .rspec-base-pg12 - .rspec-base-pg12
- .rails:rules:master-schedule-nightly--code-backstage - .rails:rules:default-branch-schedule-nightly--code-backstage
- .rspec-integration-parallel - .rspec-integration-parallel
rspec system pg12: rspec system pg12:
extends: extends:
- .rspec-base-pg12 - .rspec-base-pg12
- .rails:rules:master-schedule-nightly--code-backstage - .rails:rules:default-branch-schedule-nightly--code-backstage
- .rspec-system-parallel - .rspec-system-parallel
# EE/FOSS: master nightly scheduled jobs # # EE/FOSS: default branch nightly scheduled jobs #
########################################## ##########################################
##################################### #####################################
# EE: master nightly scheduled jobs # # EE: default branch nightly scheduled jobs #
rspec-ee migration pg12: rspec-ee migration pg12:
extends: extends:
- .rspec-ee-base-pg12 - .rspec-ee-base-pg12
- .rspec-base-migration - .rspec-base-migration
- .rails:rules:master-schedule-nightly--code-backstage-ee-only - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
- .rspec-ee-migration-parallel - .rspec-ee-migration-parallel
rspec-ee unit pg12: rspec-ee unit pg12:
extends: extends:
- .rspec-ee-base-pg12 - .rspec-ee-base-pg12
- .rails:rules:master-schedule-nightly--code-backstage-ee-only - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
- .rspec-ee-unit-parallel - .rspec-ee-unit-parallel
rspec-ee integration pg12: rspec-ee integration pg12:
extends: extends:
- .rspec-ee-base-pg12 - .rspec-ee-base-pg12
- .rails:rules:master-schedule-nightly--code-backstage-ee-only - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
- .rspec-ee-integration-parallel - .rspec-ee-integration-parallel
rspec-ee system pg12: rspec-ee system pg12:
extends: extends:
- .rspec-ee-base-pg12 - .rspec-ee-base-pg12
- .rails:rules:master-schedule-nightly--code-backstage-ee-only - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
- .rspec-ee-system-parallel - .rspec-ee-system-parallel
rspec-ee unit pg12 geo: rspec-ee unit pg12 geo:
extends: extends:
- .rspec-ee-base-geo-pg12 - .rspec-ee-base-geo-pg12
- .rails:rules:master-schedule-nightly--code-backstage-ee-only - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
- .rspec-ee-unit-geo-parallel - .rspec-ee-unit-geo-parallel
rspec-ee integration pg12 geo: rspec-ee integration pg12 geo:
extends: extends:
- .rspec-ee-base-geo-pg12 - .rspec-ee-base-geo-pg12
- .rails:rules:master-schedule-nightly--code-backstage-ee-only - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
rspec-ee system pg12 geo: rspec-ee system pg12 geo:
extends: extends:
- .rspec-ee-base-geo-pg12 - .rspec-ee-base-geo-pg12
- .rails:rules:master-schedule-nightly--code-backstage-ee-only - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
# EE: master nightly scheduled jobs # # EE: default branch nightly scheduled jobs #
##################################### #####################################
################################################## ##################################################
@ -799,7 +788,7 @@ fail-pipeline-early:
GIT_DEPTH: 1 GIT_DEPTH: 1
before_script: before_script:
- source scripts/utils.sh - source scripts/utils.sh
- install_api_client_dependencies_with_apt - install_gitlab_gem
script: script:
- fail_pipeline_early - fail_pipeline_early
# EE: Canonical MR pipelines # EE: Canonical MR pipelines

View file

@ -29,7 +29,6 @@ review-build-cng:
stage: review-prepare stage: review-prepare
before_script: before_script:
- source ./scripts/utils.sh - source ./scripts/utils.sh
- install_api_client_dependencies_with_apk
- install_gitlab_gem - install_gitlab_gem
needs: needs:
- job: compile-production-assets - job: compile-production-assets
@ -161,7 +160,7 @@ review-qa-smoke:
review-qa-all: review-qa-all:
extends: extends:
- .review-qa-base - .review-qa-base
- .review:rules:mr-only-manual - .review:rules:review-qa-all
parallel: 5 parallel: 5
script: script:
- export KNAPSACK_REPORT_PATH=knapsack/master_report.json - export KNAPSACK_REPORT_PATH=knapsack/master_report.json
@ -198,7 +197,7 @@ review-performance:
parallel-spec-reports: parallel-spec-reports:
extends: extends:
- .review:rules:mr-only-manual - .review:rules:review-qa-all
image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
stage: post-qa stage: post-qa
dependencies: ["review-qa-all"] dependencies: ["review-qa-all"]
@ -234,7 +233,15 @@ danger-review:
- run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --with danger" - run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --with danger"
- run_timed_command "retry yarn install --frozen-lockfile" - run_timed_command "retry yarn install --frozen-lockfile"
script: script:
- run_timed_command "bundle exec danger --fail-on-errors=true --verbose" - >
if [ -z "$DANGER_GITLAB_API_TOKEN" ]; then
# Force danger to skip CI source GitLab and fallback to "local only git repo".
unset GITLAB_CI
# We need to base SHA to help danger determine the base commit for this shallow clone.
run_timed_command "bundle exec danger dry_run --fail-on-errors=true --verbose --base='$CI_MERGE_REQUEST_DIFF_BASE_SHA'"
else
run_timed_command "bundle exec danger --fail-on-errors=true --verbose"
fi
update-danger-review-cache: update-danger-review-cache:
extends: extends:

View file

@ -11,25 +11,25 @@
if: '$CI_PROJECT_NAME != "gitlab-foss" && $CI_PROJECT_NAME != "gitlab-ce" && $CI_PROJECT_NAME != "gitlabhq"' if: '$CI_PROJECT_NAME != "gitlab-foss" && $CI_PROJECT_NAME != "gitlab-ce" && $CI_PROJECT_NAME != "gitlabhq"'
.if-default-refs: &if-default-refs .if-default-refs: &if-default-refs
if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME == "main" || $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: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH || $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-default-branch-refs: &if-default-branch-refs
if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME == "main"' if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'
.if-master-push: &if-master-push .if-default-branch-push: &if-default-branch-push
if: '($CI_COMMIT_BRANCH == "master" || $CI_COMMIT_REF_NAME == "main") && $CI_PIPELINE_SOURCE == "push"' if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push"'
.if-master-schedule-2-hourly: &if-master-schedule-2-hourly .if-default-branch-schedule-2-hourly: &if-default-branch-schedule-2-hourly
if: '($CI_COMMIT_BRANCH == "master" || $CI_COMMIT_REF_NAME == "main") && $CI_PIPELINE_SOURCE == "schedule" && $FREQUENCY == "2-hourly"' if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule" && $FREQUENCY == "2-hourly"'
.if-master-schedule-nightly: &if-master-schedule-nightly .if-default-branch-schedule-nightly: &if-default-branch-schedule-nightly
if: '($CI_COMMIT_BRANCH == "master" || $CI_COMMIT_REF_NAME == "main") && $CI_PIPELINE_SOURCE == "schedule" && $FREQUENCY == "nightly"' if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule" && $FREQUENCY == "nightly"'
.if-auto-deploy-branches: &if-auto-deploy-branches .if-auto-deploy-branches: &if-auto-deploy-branches
if: '$CI_COMMIT_BRANCH =~ /^\d+-\d+-auto-deploy-\d+$/' if: '$CI_COMMIT_BRANCH =~ /^\d+-\d+-auto-deploy-\d+$/'
.if-master-or-tag: &if-master-or-tag .if-default-branch-or-tag: &if-default-branch-or-tag
if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME == "main" || $CI_COMMIT_TAG' if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG'
.if-merge-request: &if-merge-request .if-merge-request: &if-merge-request
if: '$CI_MERGE_REQUEST_IID' if: '$CI_MERGE_REQUEST_IID'
@ -52,8 +52,8 @@
.if-dot-com-gitlab-org-schedule: &if-dot-com-gitlab-org-schedule .if-dot-com-gitlab-org-schedule: &if-dot-com-gitlab-org-schedule
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_PIPELINE_SOURCE == "schedule"' if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_PIPELINE_SOURCE == "schedule"'
.if-dot-com-gitlab-org-master: &if-dot-com-gitlab-org-master .if-dot-com-gitlab-org-default-branch: &if-dot-com-gitlab-org-default-branch
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && ($CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME == "main")' if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'
.if-dot-com-gitlab-org-merge-request: &if-dot-com-gitlab-org-merge-request .if-dot-com-gitlab-org-merge-request: &if-dot-com-gitlab-org-merge-request
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_MERGE_REQUEST_IID' if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_MERGE_REQUEST_IID'
@ -101,6 +101,7 @@
- ".gitlab/ci/frontend.gitlab-ci.yml" - ".gitlab/ci/frontend.gitlab-ci.yml"
- ".gitlab/ci/build-images.gitlab-ci.yml" - ".gitlab/ci/build-images.gitlab-ci.yml"
- ".gitlab/ci/review.gitlab-ci.yml" - ".gitlab/ci/review.gitlab-ci.yml"
- "scripts/review_apps/base-config.yaml"
- "scripts/trigger-build" - "scripts/trigger-build"
.ci-qa-patterns: &ci-qa-patterns .ci-qa-patterns: &ci-qa-patterns
@ -131,6 +132,14 @@
- "config/webpack.config.js" - "config/webpack.config.js"
- "config/helpers/*.js" - "config/helpers/*.js"
.frontend-build-patterns: &frontend-build-patterns
- "{package.json,yarn.lock}"
- "babel.config.js"
- "config/webpack.config.js"
- "config/**/*.js"
- "vendor/assets/**/*"
- "{,ee/}app/assets/**/*"
.frontend-patterns: &frontend-patterns .frontend-patterns: &frontend-patterns
- "{package.json,yarn.lock}" - "{package.json,yarn.lock}"
- "babel.config.js" - "babel.config.js"
@ -293,7 +302,7 @@
################ ################
.shared:rules:update-cache: .shared:rules:update-cache:
rules: rules:
- <<: *if-master-schedule-2-hourly - <<: *if-default-branch-schedule-2-hourly
- <<: *if-security-schedule - <<: *if-security-schedule
- <<: *if-merge-request-title-update-caches - <<: *if-merge-request-title-update-caches
@ -314,6 +323,7 @@
rules: rules:
- <<: *if-not-canonical-namespace - <<: *if-not-canonical-namespace
when: never when: never
- <<: *if-auto-deploy-branches
- changes: *ci-build-images-patterns - changes: *ci-build-images-patterns
- changes: *code-qa-patterns - changes: *code-qa-patterns
@ -394,8 +404,8 @@
rules: rules:
- <<: *if-not-canonical-namespace - <<: *if-not-canonical-namespace
when: never when: never
- <<: *if-default-refs - <<: *if-auto-deploy-branches
changes: *code-qa-patterns - changes: *code-qa-patterns
.frontend:rules:compile-test-assets: .frontend:rules:compile-test-assets:
rules: rules:
@ -434,26 +444,26 @@
- <<: *if-merge-request - <<: *if-merge-request
changes: *frontend-patterns changes: *frontend-patterns
.frontend:rules:ee-mr-and-master-only: .frontend:rules:ee-mr-and-default-branch-only:
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-merge-request - <<: *if-merge-request
changes: *code-backstage-patterns changes: *code-backstage-patterns
when: always when: always
- <<: *if-master-refs - <<: *if-default-branch-refs
changes: *code-backstage-patterns changes: *code-backstage-patterns
.frontend:rules:qa-frontend-node: .frontend:rules:qa-frontend-node:
rules: rules:
- <<: *if-master-refs - <<: *if-default-branch-refs
changes: *frontend-dependency-patterns changes: *frontend-dependency-patterns
- <<: *if-merge-request - <<: *if-merge-request
changes: *frontend-dependency-patterns changes: *frontend-dependency-patterns
.frontend:rules:qa-frontend-node-latest: .frontend:rules:qa-frontend-node-latest:
rules: rules:
- <<: *if-master-refs - <<: *if-default-branch-refs
changes: *frontend-dependency-patterns changes: *frontend-dependency-patterns
allow_failure: true allow_failure: true
- <<: *if-merge-request - <<: *if-merge-request
@ -464,8 +474,8 @@
rules: rules:
- <<: *if-not-canonical-namespace - <<: *if-not-canonical-namespace
when: never when: never
- if: '$DANGER_GITLAB_API_TOKEN && $CI_MERGE_REQUEST_IID && ($CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main")' - if: '$DANGER_GITLAB_API_TOKEN && $CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH'
changes: *frontend-patterns changes: *frontend-build-patterns
allow_failure: true allow_failure: true
################ ################
@ -484,7 +494,7 @@
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-master-schedule-2-hourly - <<: *if-default-branch-schedule-2-hourly
############ ############
# QA rules # # QA rules #
@ -553,6 +563,7 @@
when: never when: never
- <<: *if-merge-request - <<: *if-merge-request
changes: *db-patterns changes: *db-patterns
when: manual
.rails:rules:ee-and-foss-unit: .rails:rules:ee-and-foss-unit:
rules: rules:
@ -824,14 +835,14 @@
- changes: *db-library-patterns - changes: *db-library-patterns
- <<: *if-merge-request-title-run-all-rspec - <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-mr-and-master-only: .rails:rules:ee-mr-and-default-branch-only:
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-merge-request-title-run-all-rspec - <<: *if-merge-request-title-run-all-rspec
- <<: *if-merge-request - <<: *if-merge-request
changes: *code-backstage-patterns changes: *code-backstage-patterns
- <<: *if-master-refs - <<: *if-default-branch-refs
changes: *code-backstage-patterns changes: *code-backstage-patterns
.rails:rules:detect-tests: .rails:rules:detect-tests:
@ -878,16 +889,11 @@
changes: *code-backstage-patterns changes: *code-backstage-patterns
when: on_failure when: on_failure
.rails:rules:downtime_check:
rules:
- <<: *if-merge-request
changes: *code-backstage-patterns
.rails:rules:deprecations: .rails:rules:deprecations:
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-master-schedule-nightly - <<: *if-default-branch-schedule-nightly
- <<: *if-merge-request-title-run-all-rspec - <<: *if-merge-request-title-run-all-rspec
.rails:rules:rspec-coverage: .rails:rules:rspec-coverage:
@ -897,7 +903,7 @@
- <<: *if-merge-request - <<: *if-merge-request
changes: *code-backstage-patterns changes: *code-backstage-patterns
when: always when: always
- <<: *if-master-schedule-2-hourly - <<: *if-default-branch-schedule-2-hourly
- <<: *if-merge-request-title-run-all-rspec - <<: *if-merge-request-title-run-all-rspec
when: always when: always
@ -905,24 +911,34 @@
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-master-schedule-2-hourly - <<: *if-default-branch-schedule-2-hourly
allow_failure: true allow_failure: true
- <<: *if-merge-request-title-run-all-rspec - <<: *if-merge-request-title-run-all-rspec
.rails:rules:master-schedule-nightly--code-backstage: .rails:rules:default-branch-schedule-nightly--code-backstage:
rules: rules:
- <<: *if-master-schedule-nightly - <<: *if-default-branch-schedule-nightly
- <<: *if-merge-request - <<: *if-merge-request
changes: [".gitlab/ci/rails.gitlab-ci.yml"] changes: [".gitlab/ci/rails.gitlab-ci.yml"]
.rails:rules:master-schedule-nightly--code-backstage-ee-only: .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only:
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-master-schedule-nightly - <<: *if-default-branch-schedule-nightly
- <<: *if-merge-request - <<: *if-merge-request
changes: [".gitlab/ci/rails.gitlab-ci.yml"] changes: [".gitlab/ci/rails.gitlab-ci.yml"]
#######################
# Vendored gems rules #
#######################
.vendor:rules:mail-smtp_pool:
rules:
- <<: *if-merge-request
changes: ["vendor/gems/mail-smtp_pool/**/*"]
- <<: *if-merge-request-title-run-all-rspec
################## ##################
# Releases rules # # Releases rules #
################## ##################
@ -945,7 +961,7 @@
rules: rules:
- if: '$CODE_QUALITY_DISABLED' - if: '$CODE_QUALITY_DISABLED'
when: never when: never
# - <<: *if-master-refs # To be done in a later iteration: https://gitlab.com/gitlab-org/gitlab/issues/31160#note_278188255 # - <<: *if-default-branch-refs # To be done in a later iteration: https://gitlab.com/gitlab-org/gitlab/issues/31160#note_278188255
- <<: *if-default-refs - <<: *if-default-refs
changes: *code-backstage-patterns changes: *code-backstage-patterns
allow_failure: true allow_failure: true
@ -954,7 +970,7 @@
rules: rules:
- if: '$SAST_DISABLED || $GITLAB_FEATURES !~ /\bsast\b/' - if: '$SAST_DISABLED || $GITLAB_FEATURES !~ /\bsast\b/'
when: never when: never
# - <<: *if-master-refs # To be done in a later iteration: https://gitlab.com/gitlab-org/gitlab/issues/31160#note_278188255 # - <<: *if-default-branch-refs # To be done in a later iteration: https://gitlab.com/gitlab-org/gitlab/issues/31160#note_278188255
- <<: *if-default-refs - <<: *if-default-refs
changes: *code-backstage-qa-patterns changes: *code-backstage-qa-patterns
allow_failure: true allow_failure: true
@ -963,7 +979,7 @@
rules: rules:
- if: '$DEPENDENCY_SCANNING_DISABLED || $GITLAB_FEATURES !~ /\bdependency_scanning\b/' - if: '$DEPENDENCY_SCANNING_DISABLED || $GITLAB_FEATURES !~ /\bdependency_scanning\b/'
when: never when: never
# - <<: *if-master-refs # To be done in a later iteration: https://gitlab.com/gitlab-org/gitlab/issues/31160#note_278188255 # - <<: *if-default-branch-refs # To be done in a later iteration: https://gitlab.com/gitlab-org/gitlab/issues/31160#note_278188255
- <<: *if-default-refs - <<: *if-default-refs
changes: *code-backstage-qa-patterns changes: *code-backstage-qa-patterns
allow_failure: true allow_failure: true
@ -984,7 +1000,7 @@
rules: rules:
- if: '$DAST_DISABLED || $GITLAB_FEATURES !~ /\bdast\b/' - if: '$DAST_DISABLED || $GITLAB_FEATURES !~ /\bdast\b/'
when: never when: never
- <<: *if-master-schedule-nightly - <<: *if-default-branch-schedule-nightly
allow_failure: true allow_failure: true
.reports:rules:license_scanning: .reports:rules:license_scanning:
@ -1007,9 +1023,12 @@
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-merge-request
changes: *frontend-patterns changes: *frontend-patterns
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-merge-request
changes: *code-qa-patterns changes: *code-patterns
when: manual when: manual
allow_failure: true allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
changes: *qa-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-schedule - <<: *if-dot-com-gitlab-org-schedule
.review:rules:review-deploy: .review:rules:review-deploy:
@ -1022,9 +1041,12 @@
changes: *frontend-patterns changes: *frontend-patterns
allow_failure: true allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-merge-request
changes: *code-qa-patterns changes: *code-patterns
when: manual when: manual
allow_failure: true allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
changes: *qa-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-schedule - <<: *if-dot-com-gitlab-org-schedule
allow_failure: true allow_failure: true
@ -1067,14 +1089,17 @@
when: manual when: manual
allow_failure: true allow_failure: true
.review:rules:mr-only-manual: .review:rules:review-qa-all:
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-merge-request
changes: *code-qa-patterns changes: *code-patterns
when: manual when: manual
allow_failure: true allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
changes: *qa-patterns
allow_failure: true
.review:rules:review-cleanup: .review:rules:review-cleanup:
rules: rules:
@ -1100,7 +1125,7 @@
.review:rules:danger: .review:rules:danger:
rules: rules:
- if: '$DANGER_GITLAB_API_TOKEN && $CI_MERGE_REQUEST_IID' - if: '$CI_MERGE_REQUEST_IID'
############### ###############
# Setup rules # # Setup rules #
@ -1109,13 +1134,13 @@
rules: rules:
- <<: *if-not-canonical-namespace - <<: *if-not-canonical-namespace
when: never when: never
- <<: *if-master-or-tag - <<: *if-default-branch-or-tag
changes: *code-backstage-qa-patterns changes: *code-backstage-qa-patterns
when: on_success when: on_success
.setup:rules:dont-interrupt-me: .setup:rules:dont-interrupt-me:
rules: rules:
- <<: *if-master-or-tag - <<: *if-default-branch-or-tag
allow_failure: true allow_failure: true
- <<: *if-auto-deploy-branches - <<: *if-auto-deploy-branches
allow_failure: true allow_failure: true

View file

@ -0,0 +1,26 @@
untamper-my-lockfile:
image: registry.gitlab.com/gitlab-org/frontend/untamper-my-lockfile:main
stage: test
needs: []
before_script: []
after_script: []
cache: {}
retry: 1
script:
- untamper-my-lockfile --lockfile yarn.lock
rules:
# Create a pipeline if the branch is named 'add-untamper-my-lockfile' in
# order to have an integration check added in the MR that introduces it
- if: $CI_COMMIT_REF_NAME == "add-untamper-my-lockfile"
# Create a pipeline if there are changes in yarn.lock _and_ we are in a
# merge request _or_ branch pipeline.
#
# This ensures that the pipeline isn't run in scheduled jobs for example
#
# Also our best effort to support both branch and MR pipelines. In certain
# projects this might trigger _two_ pipelines. These projects can be fixed
# by adding proper workflow:rules
# https://docs.gitlab.com/ee/ci/yaml/#workflowrules
- if: $CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH
changes:
- yarn.lock

View file

@ -0,0 +1,7 @@
vendor mail-smtp_pool:
extends:
- .vendor:rules:mail-smtp_pool
needs: []
trigger:
include: vendor/gems/mail-smtp_pool/.gitlab-ci.yml
strategy: depend

View file

@ -1,6 +1,6 @@
workhorse:verify: workhorse:verify:
extends: .workhorse:rules:workhorse extends: .workhorse:rules:workhorse
image: ${GITLAB_DEPENDENCY_PROXY}golang:1.15 image: ${GITLAB_DEPENDENCY_PROXY}golang:1.16
stage: test stage: test
needs: [] needs: []
script: script:
@ -23,14 +23,10 @@ workhorse:verify:
- apt-get update && apt-get -y install libimage-exiftool-perl - apt-get update && apt-get -y install libimage-exiftool-perl
- make -C workhorse test - make -C workhorse test
workhorse:test using go 1.13:
extends: .workhorse:test
image: ${GITLAB_DEPENDENCY_PROXY}golang:1.13
workhorse:test using go 1.14:
extends: .workhorse:test
image: ${GITLAB_DEPENDENCY_PROXY}golang:1.14
workhorse:test using go 1.15: workhorse:test using go 1.15:
extends: .workhorse:test extends: .workhorse:test
image: ${GITLAB_DEPENDENCY_PROXY}golang:1.15 image: ${GITLAB_DEPENDENCY_PROXY}golang:1.15
workhorse:test using go 1.16:
extends: .workhorse:test
image: ${GITLAB_DEPENDENCY_PROXY}golang:1.16

View file

@ -15,5 +15,6 @@ The changes need to become an official part of the product.
- [ ] Optional: Migrate experiment to a default enabled [feature flag](https://docs.gitlab.com/ee/development/feature_flags) for one milestone and add a changelog. Converting to a feature flag can be skipped at the ICs discretion if risk is deemed low with consideration to both SaaS and (if applicable) self managed - [ ] Optional: Migrate experiment to a default enabled [feature flag](https://docs.gitlab.com/ee/development/feature_flags) for one milestone and add a changelog. Converting to a feature flag can be skipped at the ICs discretion if risk is deemed low with consideration to both SaaS and (if applicable) self managed
- [ ] In the next milestone, [remove the feature flag](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up) if applicable - [ ] In the next milestone, [remove the feature flag](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up) if applicable
- [ ] After the flag removal is deployed, [clean up the feature/experiment feature flags](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up) by running chatops command in `#production` channel - [ ] After the flag removal is deployed, [clean up the feature/experiment feature flags](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up) by running chatops command in `#production` channel
- [ ] Ensure the corresponding [Experiment Tracking](https://gitlab.com/groups/gitlab-org/-/boards/1352542?label_name[]=devops%3A%3Agrowth&label_name[]=growth%20experiment&label_name[]=experiment%20tracking) issue is updated
/label ~"feature" ~"feature::maintenance" ~"workflow::scheduling" ~"growth experiment" ~"feature flag" /label ~"feature" ~"feature::maintenance" ~"workflow::scheduling" ~"growth experiment" ~"feature flag"

View file

@ -18,7 +18,7 @@
# Tracking Details # Tracking Details
- [json schema](https://gitlab.com/gitlab-org/iglu/-/blob/master/public/schemas/com.gitlab/gitlab_experiment/jsonschema/0-3-0) used in `gitlab-experiment` tracking. - [json schema](https://gitlab.com/gitlab-org/iglu/-/blob/master/public/schemas/com.gitlab/gitlab_experiment/jsonschema/0-3-0) used in `gitlab-experiment` tracking.
- see [taxonomy](https://docs.gitlab.com/ee/development/snowplow.html#structured-event-taxonomy) for a guide. - see [taxonomy](https://docs.gitlab.com/ee/development/snowplow/index.html#structured-event-taxonomy) for a guide.
| activity | category | action | label | context | property | value | | activity | category | action | label | context | property | value |
| -------- | -------- | ------ | ----- | ------- | -------- | ----- | | -------- | -------- | ------ | ----- | ------- | -------- | ----- |

View file

@ -1,48 +1,107 @@
<!-- Title suggestion: [Feature flag] Enable description of feature --> <!-- Title suggestion: [Feature flag] Enable description of feature -->
## What ## Feature
Remove the `:feature_name` feature flag ... This feature uses the `:feature_name` feature flag!
<!-- Short description of what the feature is about and link to relevant other issues. -->
- [Issue Name](ISSUE LINK)
## Owners ## Owners
- Team: NAME_OF_TEAM - Team: NAME_OF_TEAM
- Most appropriate slack channel to reach out to: `#g_TEAM_NAME` - Most appropriate slack channel to reach out to: `#g_TEAM_NAME`
- Best individual to reach out to: NAME - Best individual to reach out to: NAME
- PM: NAME
## Expectations ## Stakeholders
### What are we expecting to happen? <!--
Are there any other stages or teams involved that need to be kept in the loop?
### What might happen if this goes wrong? - Name of a PM
- The Support Team
- The Delivery Team
-->
### What can we monitor to detect problems with this? ## The Rollout Plan
- Partial Rollout on GitLab.com with beta groups
- Rollout on GitLab.com for a certain period (How long)
- Percentage Rollout on GitLab.com
- Rollout Feature for everyone as soon as it's ready
<!-- Which dashboards from https://dashboards.gitlab.net are most relevant? Sentry errors reports can also be useful to review --> <!-- Which dashboards from https://dashboards.gitlab.net are most relevant? Sentry errors reports can also be useful to review -->
**Beta Groups/Projects:**
## Beta groups/projects <!-- If applicable, any groups/projects that are happy to have this feature turned on early. Some organizations may wish to test big changes they are interested in with a small subset of users ahead of time for example. -->
If applicable, any groups/projects that are happy to have this feature turned on early. Some organizations may wish to test big changes they are interested in with a small subset of users ahead of time for example.
- `gitlab-org/gitlab` project - `gitlab-org/gitlab` project
- `gitlab-org`/`gitlab-com` groups - `gitlab-org`/`gitlab-com` groups
- ... - ...
## Roll Out Steps
## Expectations
### What are we expecting to happen?
<!-- Describe the expected outcome when rolling out this feature -->
### What might happen if this goes wrong?
<!-- Should the feature flag be turned off? Any MRs that need to be rolled back? Communication that needs to happen? What are some things you can think of that could go wrong - data loss or broken pages? -->
### What can we monitor to detect problems with this?
<!-- Which dashboards from https://dashboards.gitlab.net are most relevant? -->
## Rollout Timeline
<!-- Please check which steps are needed and remove those which don't apply -->
**Initial Rollout**
*Preparation Phase*
- [ ] Enable on staging (`/chatops run feature set feature_name true --staging`) - [ ] Enable on staging (`/chatops run feature set feature_name true --staging`)
- [ ] Test on staging - [ ] Test on staging
- [ ] Ensure that documentation has been updated
- [ ] Enable on GitLab.com for individual groups/projects listed above and verify behaviour (`/chatops run feature set --project=gitlab-org/gitlab feature_name true`) - [ ] Ensure that documentation has been updated ([More info](https://docs.gitlab.com/ee/development/documentation/feature_flags.html#features-that-became-enabled-by-default))
- [ ] Coordinate a time to enable the flag with the SRE oncall and release managers
- In `#production` mention `@sre-oncall` and `@release-managers`. Once an SRE on call and Release Manager on call confirm, you can proceed with the rollout
- [ ] Announce on the issue an estimated time this will be enabled on GitLab.com - [ ] Announce on the issue an estimated time this will be enabled on GitLab.com
- [ ] Enable on GitLab.com by running chatops command in `#production` (`/chatops run feature set feature_name true`)
- [ ] Cross post chatops Slack command to `#support_gitlab-com` ([more guidance when this is necessary in the dev docs](https://docs.gitlab.com/ee/development/feature_flags/controls.html#where-to-run-commands)) and in your team channel *Partial Rollout Phase*
- [ ] Enable on GitLab.com for individual groups/projects listed above and verify behaviour (`/chatops run feature set --project=gitlab-org/gitlab feature_name true`)
- [ ] Verify behaviour (See Beta Groups) and add details with screenshots as a comment on this issue
- [ ] If it is possible to perform an incremental rollout, this should be preferred. Proposed increments are: `10%`, `50%`, `100%`. Proposed minimum time between increments is 15 minutes.
- When setting percentages, make sure that the feature works correctly between feature checks. See https://gitlab.com/gitlab-org/gitlab/-/issues/327117 for more information
- For actor-based rollout: `/chatops run feature set feature_name 10 --actors`
- For time-based rollout: `/chatops run feature set feature_name 10`
- [ ] Make the feature flag enabled by default i.e. Change `default_enabled` to `true`
- [ ] Cross post chatops slack command to `#support_gitlab-com` ([more guidance when this is necessary in the dev docs](https://docs.gitlab.com/ee/development/feature_flags/controls.html#where-to-run-commands)) and in your team channel
**Cleanup**
This is an __important__ phase, that should be either done in the next Milestone or as soon as possible. For the cleanup phase, please follow our documentation on how to [clean up the feature flag](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up).
<!-- The checklist here is to keep track of it's status for stakeholders -->
- [ ] Announce on the issue that the flag has been enabled - [ ] Announce on the issue that the flag has been enabled
- [ ] Remove feature flag and add changelog entry. Ensure that the feature flag definition YAML file has been removed in the **same MR** that is removing the feature flag from the code
- [ ] After the flag removal is deployed, [clean up the feature flag](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up) by running chatops command in `#production` channel - [ ] Remove `:feature_name` feature flag
- [ ] Remove all references to the feature flag from the codebase
- [ ] Remove the YAML definitions for the feature from the repository
- [ ] Create a Changelog Entry
- [ ] Clean up the feature flag from all environments by running this chatops command in `#production` channel `/chatops run feature delete some_feature`.
**Final Step**
- [ ] Close this rollout issue for the feature flag after the feature flag is removed from the codebase.
## Rollback Steps ## Rollback Steps
@ -53,3 +112,4 @@ If applicable, any groups/projects that are happy to have this feature turned on
``` ```
/label ~"feature flag" /label ~"feature flag"
/assign DRI

View file

@ -113,3 +113,5 @@ Use the following resources to find the appropriate labels:
/label ~devops:: ~group: ~Category: /label ~devops:: ~group: ~Category:
/label ~"GitLab Core"/~"GitLab Premium"/~"GitLab Ultimate" /label ~"GitLab Core"/~"GitLab Premium"/~"GitLab Ultimate"
/label ~feature /label ~feature
/label ~documentation
/label ~direction

View file

@ -0,0 +1,756 @@
<!--
This template is based on a model named `CoolWidget`.
To adapt this template, find and replace the following tokens:
- `CoolWidget`
- `Cool Widget`
- `cool_widget`
- `coolWidget`
If your Model's pluralized form is non-standard, i.e. it doesn't just end in `s`, then find and replace the following tokens *first*:
- `CoolWidgets`
- `Cool Widgets`
- `cool_widgets`
- `coolWidgets`
-->
## Replicate Cool Widgets
This issue is for implementing Geo replication and verification of Cool Widgets.
For more background, see [Geo self-service framework](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/geo/framework.md).
In order to implement and test this feature, you need to first [set up Geo locally](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/geo.md).
There are three main sections below. It is a good idea to structure your merge requests this way as well:
1. Modify database schemas to prepare to add Geo support for Cool Widgets
1. Implement Geo support of Cool Widgets behind a feature flag
1. Release Geo support of Cool Widgets
It is also a good idea to first open a proof-of-concept merge request. It can be helpful for working out kinks and getting initial support and feedback from the Geo team. As an example, see the [Proof of Concept to replicate Pipeline Artifacts](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56423).
### Modify database schemas to prepare to add Geo support for Cool Widgets
You might do this section in its own merge request, but it is not required.
#### Add the registry table to track replication and verification state
Geo secondary sites have a [Geo tracking database](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/geo.md#tracking-database) independent of the main database. It is used to track the replication and verification state of all replicables. Every Model has a corresponding "registry" table in the Geo tracking database.
- [ ] Create the migration file in `ee/db/geo/migrate`:
```shell
bin/rails generate geo_migration CreateCoolWidgetRegistry
```
- [ ] Replace the contents of the migration file with the following. Note that we cannot add a foreign key constraint on `cool_widget_id` because the `cool_widgets` table is in a different database. The application code must handle logic such as propagating deletions.
```ruby
# frozen_string_literal: true
class CreateCoolWidgetRegistry < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
unless table_exists?(:cool_widget_registry)
ActiveRecord::Base.transaction do
create_table :cool_widget_registry, id: :bigserial, force: :cascade do |t|
t.bigint :cool_widget_id, null: false
t.datetime_with_timezone :created_at, null: false
t.datetime_with_timezone :last_synced_at
t.datetime_with_timezone :retry_at
t.datetime_with_timezone :verified_at
t.datetime_with_timezone :verification_started_at
t.datetime_with_timezone :verification_retry_at
t.integer :state, default: 0, null: false, limit: 2
t.integer :verification_state, default: 0, null: false, limit: 2
t.integer :retry_count, default: 0, limit: 2, null: false
t.integer :verification_retry_count, default: 0, limit: 2, null: false
t.boolean :checksum_mismatch, default: false, null: false
t.boolean :force_to_redownload, default: false, null: false
t.boolean :missing_on_primary, default: false, null: false
t.binary :verification_checksum
t.binary :verification_checksum_mismatched
t.string :verification_failure, limit: 255 # rubocop:disable Migration/PreventStrings see https://gitlab.com/gitlab-org/gitlab/-/issues/323806
t.string :last_sync_failure, limit: 255 # rubocop:disable Migration/PreventStrings see https://gitlab.com/gitlab-org/gitlab/-/issues/323806
t.index :cool_widget_id, name: :index_cool_widget_registry_on_cool_widget_id, unique: true
t.index :retry_at
t.index :state
# To optimize performance of CoolWidgetRegistry.verification_failed_batch
t.index :verification_retry_at, name: :cool_widget_registry_failed_verification, order: "NULLS FIRST", where: "((state = 2) AND (verification_state = 3))"
# To optimize performance of CoolWidgetRegistry.needs_verification_count
t.index :verification_state, name: :cool_widget_registry_needs_verification, where: "((state = 2) AND (verification_state = ANY (ARRAY[0, 3])))"
# To optimize performance of CoolWidgetRegistry.verification_pending_batch
t.index :verified_at, name: :cool_widget_registry_pending_verification, order: "NULLS FIRST", where: "((state = 2) AND (verification_state = 0))"
end
end
end
end
def down
drop_table :cool_widget_registry
end
end
```
- [ ] If deviating from the above example, then be sure to order columns according to [our guidelines](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/ordering_table_columns.md).
- [ ] Run Geo tracking database migrations:
```shell
bin/rake geo:db:migrate
```
- [ ] Be sure to commit the relevant changes in `ee/db/geo/schema.rb`
### Add verification state fields on the Geo primary site
The Geo primary site needs to checksum every replicable in order for secondaries to verify their own checksums. To do this, Geo requires fields on the Model. There are two ways to add the necessary verification state fields. If the table is large and wide, then it may be a good idea to add verification state fields to a separate table (Option 2). Consult a database expert if needed.
#### Add verification state fields to the model table (Option 1)
- [ ] Create the migration file in `db/migrate`:
```shell
bin/rails generate migration AddVerificationStateToCoolWidgets
```
- [ ] Replace the contents of the migration file with:
```ruby
# frozen_string_literal: true
class AddVerificationStateToCoolWidgets < ActiveRecord::Migration[6.0]
def change
change_table(:cool_widgets) do |t|
t.integer :verification_state, default: 0, limit: 2, null: false
t.column :verification_started_at, :datetime_with_timezone
t.integer :verification_retry_count, limit: 2, null: false
t.column :verification_retry_at, :datetime_with_timezone
t.column :verified_at, :datetime_with_timezone
t.binary :verification_checksum, using: 'verification_checksum::bytea'
t.text :verification_failure # rubocop:disable Migration/AddLimitToTextColumns
end
end
end
```
- [ ] If deviating from the above example, then be sure to order columns according to [our guidelines](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/ordering_table_columns.md).
- [ ] If `cool_widgets` is a high-traffic table, follow [the database documentation to use `with_lock_retries`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/migration_style_guide.md#when-to-use-the-helper-method)
- [ ] Adding a `text` column also [requires](../database/strings_and_the_text_data_type.md#add-a-text-column-to-an-existing-table) setting a limit. Create the migration file in `db/migrate`:
```shell
bin/rails generate migration AddVerificationFailureLimitToCoolWidgets
```
- [ ] Replace the contents of the migration file with:
```ruby
# frozen_string_literal: true
class AddVerificationFailureLimitToCoolWidgets < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
CONSTRAINT_NAME = 'cool_widget_verification_failure_text_limit'
def up
add_text_limit :cool_widget, :verification_failure, 255, constraint_name: CONSTRAINT_NAME
end
def down
remove_check_constraint(:cool_widget, CONSTRAINT_NAME)
end
end
```
- [ ] Add indexes on verification fields to ensure verification can be performed efficiently. Some or all of these indexes can be omitted if the table is guaranteed to be small. Ask a database expert if you are considering omitting indexes. Create the migration file in `db/migrate`:
```shell
bin/rails generate migration AddVerificationIndexesToCoolWidgets
```
- [ ] Replace the contents of the migration file with:
```ruby
# frozen_string_literal: true
class AddVerificationIndexesToCoolWidgets < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
VERIFICATION_STATE_INDEX_NAME = "index_cool_widgets_on_verification_state"
PENDING_VERIFICATION_INDEX_NAME = "index_cool_widgets_pending_verification"
FAILED_VERIFICATION_INDEX_NAME = "index_cool_widgets_failed_verification"
NEEDS_VERIFICATION_INDEX_NAME = "index_cool_widgets_needs_verification"
disable_ddl_transaction!
def up
add_concurrent_index :cool_widgets, :verification_state, name: VERIFICATION_STATE_INDEX_NAME
add_concurrent_index :cool_widgets, :verified_at, where: "(verification_state = 0)", order: { verified_at: 'ASC NULLS FIRST' }, name: PENDING_VERIFICATION_INDEX_NAME
add_concurrent_index :cool_widgets, :verification_retry_at, where: "(verification_state = 3)", order: { verification_retry_at: 'ASC NULLS FIRST' }, name: FAILED_VERIFICATION_INDEX_NAME
add_concurrent_index :cool_widgets, :verification_state, where: "(verification_state = 0 OR verification_state = 3)", name: NEEDS_VERIFICATION_INDEX_NAME
end
def down
remove_concurrent_index_by_name :cool_widgets, VERIFICATION_STATE_INDEX_NAME
remove_concurrent_index_by_name :cool_widgets, PENDING_VERIFICATION_INDEX_NAME
remove_concurrent_index_by_name :cool_widgets, FAILED_VERIFICATION_INDEX_NAME
remove_concurrent_index_by_name :cool_widgets, NEEDS_VERIFICATION_INDEX_NAME
end
end
```
- [ ] Run database migrations:
```shell
bin/rake db:migrate
```
- [ ] Be sure to commit the relevant changes in `db/structure.sql`
#### Add verification state fields to a separate table (Option 2)
- [ ] Create the migration file in `db/migrate`:
```shell
bin/rails generate migration CreateCoolWidgetStates
```
- [ ] Replace the contents of the migration file with:
```ruby
# frozen_string_literal: true
class CreateCoolWidgetStates < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
VERIFICATION_STATE_INDEX_NAME = "index_cool_widget_states_on_verification_state"
PENDING_VERIFICATION_INDEX_NAME = "index_cool_widget_states_pending_verification"
FAILED_VERIFICATION_INDEX_NAME = "index_cool_widget_states_failed_verification"
NEEDS_VERIFICATION_INDEX_NAME = "index_cool_widget_states_needs_verification"
disable_ddl_transaction!
def up
unless table_exists?(:cool_widget_states)
with_lock_retries do
create_table :cool_widget_states, id: false do |t|
t.references :cool_widget, primary_key: true, null: false, foreign_key: { on_delete: :cascade }
t.integer :verification_state, default: 0, limit: 2, null: false
t.column :verification_started_at, :datetime_with_timezone
t.datetime_with_timezone :verification_retry_at
t.datetime_with_timezone :verified_at
t.integer :verification_retry_count, limit: 2
t.binary :verification_checksum, using: 'verification_checksum::bytea'
t.text :verification_failure
t.index :verification_state, name: VERIFICATION_STATE_INDEX_NAME
t.index :verified_at, where: "(verification_state = 0)", order: { verified_at: 'ASC NULLS FIRST' }, name: PENDING_VERIFICATION_INDEX_NAME
t.index :verification_retry_at, where: "(verification_state = 3)", order: { verification_retry_at: 'ASC NULLS FIRST' }, name: FAILED_VERIFICATION_INDEX_NAME
t.index :verification_state, where: "(verification_state = 0 OR verification_state = 3)", name: NEEDS_VERIFICATION_INDEX_NAME
end
end
end
add_text_limit :cool_widget_states, :verification_failure, 255
end
def down
drop_table :cool_widget_states
end
end
```
- [ ] If deviating from the above example, then be sure to order columns according to [our guidelines](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/ordering_table_columns.md).
- [ ] Run database migrations:
```shell
bin/rake db:migrate
```
- [ ] Be sure to commit the relevant changes in `db/structure.sql`
That's all of the required database changes.
### Implement Geo support of Cool Widgets behind a feature flag
#### Step 1. Implement replication and verification
- [ ] Include `Gitlab::Geo::ReplicableModel` in the `CoolWidget` class, and specify the Replicator class `with_replicator Geo::CoolWidgetReplicator`.
Pay some attention to method `pool_repository`. Not every repository type uses repository pooling. As Geo prefers to use repository snapshotting, it can lead to data loss. Make sure to overwrite `pool_repository` so it returns nil for repositories that do not have pools.
At this point the `CoolWidget` class should look like this:
```ruby
# frozen_string_literal: true
class CoolWidget < ApplicationRecord
include ::Gitlab::Geo::ReplicableModel
include ::Gitlab::Geo::VerificationState
with_replicator Geo::CoolWidgetReplicator
mount_uploader :file, CoolWidgetUploader
# Override the `all` default if not all records can be replicated. For an
# example of an existing Model that needs to do this, see
# `EE::MergeRequestDiff`.
# scope :available_replicables, -> { all }
# @param primary_key_in [Range, CoolWidget] arg to pass to primary_key_in scope
# @return [ActiveRecord::Relation<CoolWidget>] everything that should be synced to this node, restricted by primary key
def self.replicables_for_current_secondary(primary_key_in)
# This issue template does not help you write this method.
#
# This method is called only on Geo secondary sites. It is called when
# we want to know which records to replicate. This is not easy to automate
# because for example:
#
# * The "selective sync" feature allows admins to choose which namespaces # to replicate, per secondary site. Most Models are scoped to a
# namespace, but the nature of the relationship to a namespace varies
# between Models.
# * The "selective sync" feature allows admins to choose which shards to
# replicate, per secondary site. Repositories are associated with
# shards. Most blob types are not, but Project Uploads are.
# * Remote stored replicables are not replicated, by default. But the
# setting `sync_object_storage` enables replication of remote stored
# replicables.
#
# Search the codebase for examples, and consult a Geo expert if needed.
end
# Geo checks this method in FrameworkRepositorySyncService to avoid
# snapshotting repositories using object pools
def pool_repository
nil
end
...
end
```
- [ ] Implement `CoolWidget.replicables_for_current_secondary` above.
- [ ] Ensure `CoolWidget.replicables_for_current_secondary` is well-tested. Search the codebase for `replicables_for_current_secondary` to find examples of parameterized table specs. You may need to add more `FactoryBot` traits.
- [ ] If you are using a separate table `cool_widget_states` to track verification state on the Geo primary site, then:
- [ ] Do not include `::Gitlab::Geo::VerificationState` on the `CoolWidget` class.
- [ ] Add the following lines to the `cool_widget_state.rb` model:
```ruby
class CoolWidgetState < ApplicationRecord
...
self.primary_key = :cool_widget_id
include ::Gitlab::Geo::VerificationState
belongs_to :cool_widget, inverse_of: :cool_widget_state
...
end
```
- [ ] Add the following lines to the `cool_widget` model:
```ruby
class CoolWidget < ApplicationRecord
...
has_one :cool_widget_state, inverse_of: :cool_widget
delegate :verification_retry_at, :verification_retry_at=,
:verified_at, :verified_at=,
:verification_checksum, :verification_checksum=,
:verification_failure, :verification_failure=,
:verification_retry_count, :verification_retry_count=,
to: :cool_widget_state
...
end
```
- [ ] Create `ee/app/replicators/geo/cool_widget_replicator.rb`. Implement the `#repository` method which should return a `<Repository>` instance, and implement the class method `.model` to return the `CoolWidget` class:
```ruby
# frozen_string_literal: true
module Geo
class CoolWidgetReplicator < Gitlab::Geo::Replicator
include ::Geo::RepositoryReplicatorStrategy
def self.model
::CoolWidget
end
def repository
model_record.repository
end
def self.git_access_class
::Gitlab::GitAccessCoolWidget
end
# The feature flag follows the format `geo_#{replicable_name}_replication`,
# so here it would be `geo_cool_widget_replication`
def self.replication_enabled_by_default?
false
end
override :verification_feature_flag_enabled?
def self.verification_feature_flag_enabled?
# We are adding verification at the same time as replication, so we
# don't need to toggle verification separately from replication. When
# the replication feature flag is off, then verification is also off
# (see `VerifiableReplicator.verification_enabled?`)
true
end
end
end
```
- [ ] Make sure Geo push events are created. Usually it needs some change in the `app/workers/post_receive.rb` file. Example:
```ruby
def replicate_cool_widget_changes(cool_widget)
if ::Gitlab::Geo.primary?
cool_widget.replicator.handle_after_update if cool_widget
end
end
```
See `app/workers/post_receive.rb` for more examples.
- [ ] Make sure the repository removal is also handled. You may need to add something like the following in the destroy service of the repository:
```ruby
cool_widget.replicator.handle_after_destroy if cool_widget.repository
```
- [ ] Make sure a Geo secondary site can request and download Cool Widgets on the Geo primary site. You may need to make some changes to `Gitlab::GitAccessCoolWidget`. For example, see [this change for Group-level Wikis](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54914/diffs?commit_id=0f2b36f66697b4addbc69bd377ee2818f648dd33).
- [ ] Generate the feature flag definition file by running the feature flag command and following the command prompts:
```shell
bin/feature-flag --ee geo_cool_widget_replication --type development --group 'group::geo'
```
- [ ] Add this replicator class to the method `replicator_classes` in
`ee/lib/gitlab/geo.rb`:
```ruby
REPLICATOR_CLASSES = [
::Geo::PackageFileReplicator,
::Geo::CoolWidgetReplicator
]
end
```
- [ ] Create `ee/spec/replicators/geo/cool_widget_replicator_spec.rb` and perform the necessary setup to define the `model_record` variable for the shared examples:
```ruby
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Geo::CoolWidgetReplicator do
let(:model_record) { build(:cool_widget) }
include_examples 'a repository replicator'
include_examples 'a verifiable replicator'
end
```
- [ ] Create `ee/app/models/geo/cool_widget_registry.rb`:
```ruby
# frozen_string_literal: true
class Geo::CoolWidgetRegistry < Geo::BaseRegistry
include ::Geo::ReplicableRegistry
include ::Geo::VerifiableRegistry
MODEL_CLASS = ::CoolWidget
MODEL_FOREIGN_KEY = :cool_widget_id
belongs_to :cool_widget, class_name: 'CoolWidget'
end
```
- [ ] Update `REGISTRY_CLASSES` in `ee/app/workers/geo/secondary/registry_consistency_worker.rb`.
- [ ] Update `def model_class_factory_name` in `ee/spec/services/geo/registry_consistency_service_spec.rb`.
- [ ] Update `it 'creates missing registries for each registry class'` in `ee/spec/workers/geo/secondary/registry_consistency_worker_spec.rb`.
- [ ] Add `cool_widget_registry` to `ActiveSupport::Inflector.inflections` in `config/initializers_before_autoloader/000_inflections.rb`.
- [ ] Create `ee/spec/factories/geo/cool_widget_registry.rb`:
```ruby
# frozen_string_literal: true
FactoryBot.define do
factory :geo_cool_widget_registry, class: 'Geo::CoolWidgetRegistry' do
cool_widget
state { Geo::CoolWidgetRegistry.state_value(:pending) }
trait :synced do
state { Geo::CoolWidgetRegistry.state_value(:synced) }
last_synced_at { 5.days.ago }
end
trait :failed do
state { Geo::CoolWidgetRegistry.state_value(:failed) }
last_synced_at { 1.day.ago }
retry_count { 2 }
last_sync_failure { 'Random error' }
end
trait :started do
state { Geo::CoolWidgetRegistry.state_value(:started) }
last_synced_at { 1.day.ago }
retry_count { 0 }
end
end
end
```
- [ ] Create `ee/spec/models/geo/cool_widget_registry_spec.rb`:
```ruby
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Geo::CoolWidgetRegistry, :geo, type: :model do
let_it_be(:registry) { create(:geo_cool_widget_registry) }
specify 'factory is valid' do
expect(registry).to be_valid
end
include_examples 'a Geo framework registry'
include_examples 'a Geo verifiable registry'
end
```
#### Step 2. Implement metrics gathering
Metrics are gathered by `Geo::MetricsUpdateWorker`, persisted in `GeoNodeStatus` for display in the UI, and sent to Prometheus:
- [ ] Add the following fields to Geo Node Status example responses in `doc/api/geo_nodes.md`:
- `cool_widgets_count`
- `cool_widgets_checksum_total_count`
- `cool_widgets_checksummed_count`
- `cool_widgets_checksum_failed_count`
- `cool_widgets_synced_count`
- `cool_widgets_failed_count`
- `cool_widgets_registry_count`
- `cool_widgets_verification_total_count`
- `cool_widgets_verified_count`
- `cool_widgets_verification_failed_count`
- `cool_widgets_synced_in_percentage`
- `cool_widgets_verified_in_percentage`
- [ ] Add the same fields to `GET /geo_nodes/status` example response in
`ee/spec/fixtures/api/schemas/public_api/v4/geo_node_status.json`.
- [ ] Add the following fields to the `Sidekiq metrics` table in `doc/administration/monitoring/prometheus/gitlab_metrics.md`:
- `geo_cool_widgets`
- `geo_cool_widgets_checksum_total`
- `geo_cool_widgets_checksummed`
- `geo_cool_widgets_checksum_failed`
- `geo_cool_widgets_synced`
- `geo_cool_widgets_failed`
- `geo_cool_widgets_registry`
- `geo_cool_widgets_verification_total`
- `geo_cool_widgets_verified`
- `geo_cool_widgets_verification_failed`
- [ ] Add the following to the parameterized table in the `context 'Replicator stats' do` block in `ee/spec/models/geo_node_status_spec.rb`:
```ruby
Geo::CoolWidgetReplicator | :cool_widget | :geo_cool_widget_registry
```
- [ ] Add the following to `spec/factories/cool_widgets.rb`:
```ruby
trait(:verification_succeeded) do
with_file
verification_checksum { 'abc' }
verification_state { CoolWidget.verification_state_value(:verification_succeeded) }
end
trait(:verification_failed) do
with_file
verification_failure { 'Could not calculate the checksum' }
verification_state { CoolWidget.verification_state_value(:verification_failed) }
end
```
- [ ] Make sure the factory also allows setting a `project` attribute. If the model does not have a direct relation to a project, you can use a `transient` attribute. Check out `spec/factories/merge_request_diffs.rb` for an example.
Cool Widget replication and verification metrics should now be available in the API, the `Admin > Geo > Nodes` view, and Prometheus.
#### Step 3. Implement the GraphQL API
The GraphQL API is used by `Admin > Geo > Replication Details` views, and is directly queryable by administrators.
- [ ] Add a new field to `GeoNodeType` in `ee/app/graphql/types/geo/geo_node_type.rb`:
```ruby
field :cool_widget_registries, ::Types::Geo::CoolWidgetRegistryType.connection_type,
null: true,
resolver: ::Resolvers::Geo::CoolWidgetRegistriesResolver,
description: 'Find Cool Widget registries on this Geo node',
feature_flag: :geo_cool_widget_replication
```
- [ ] Add the new `cool_widget_registries` field name to the `expected_fields` array in `ee/spec/graphql/types/geo/geo_node_type_spec.rb`.
- [ ] Create `ee/app/graphql/resolvers/geo/cool_widget_registries_resolver.rb`:
```ruby
# frozen_string_literal: true
module Resolvers
module Geo
class CoolWidgetRegistriesResolver < BaseResolver
type ::Types::Geo::GeoNodeType.connection_type, null: true
include RegistriesResolver
end
end
end
```
- [ ] Create `ee/spec/graphql/resolvers/geo/cool_widget_registries_resolver_spec.rb`:
```ruby
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Geo::CoolWidgetRegistriesResolver do
it_behaves_like 'a Geo registries resolver', :geo_cool_widget_registry
end
```
- [ ] Create `ee/app/finders/geo/cool_widget_registry_finder.rb`:
```ruby
# frozen_string_literal: true
module Geo
class CoolWidgetRegistryFinder
include FrameworkRegistryFinder
end
end
```
- [ ] Create `ee/spec/finders/geo/cool_widget_registry_finder_spec.rb`:
```ruby
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Geo::CoolWidgetRegistryFinder do
it_behaves_like 'a framework registry finder', :geo_cool_widget_registry
end
```
- [ ] Create `ee/app/graphql/types/geo/cool_widget_registry_type.rb`:
```ruby
# frozen_string_literal: true
module Types
module Geo
# rubocop:disable Graphql/AuthorizeTypes because it is included
class CoolWidgetRegistryType < BaseObject
include ::Types::Geo::RegistryType
graphql_name 'CoolWidgetRegistry'
description 'Represents the Geo replication and verification state of a cool_widget'
field :cool_widget_id, GraphQL::ID_TYPE, null: false, description: 'ID of the Cool Widget'
end
end
end
```
- [ ] Create `ee/spec/graphql/types/geo/cool_widget_registry_type_spec.rb`:
```ruby
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['CoolWidgetRegistry'] do
it_behaves_like 'a Geo registry type'
it 'has the expected fields (other than those included in RegistryType)' do
expected_fields = %i[cool_widget_id]
expect(described_class).to have_graphql_fields(*expected_fields).at_least
end
end
```
- [ ] Add integration tests for providing CoolWidget registry data to the frontend via the GraphQL API, by duplicating and modifying the following shared examples in `ee/spec/requests/api/graphql/geo/registries_spec.rb`:
```ruby
it_behaves_like 'gets registries for', {
field_name: 'coolWidgetRegistries',
registry_class_name: 'CoolWidgetRegistry',
registry_factory: :geo_cool_widget_registry,
registry_foreign_key_field_name: 'coolWidgetId'
}
```
- [ ] Update the GraphQL reference documentation:
```shell
bundle exec rake gitlab:graphql:compile_docs
```
Individual Cool Widget replication and verification data should now be available via the GraphQL API.
### Release Geo support of Cool Widgets
- [ ] In the rollout issue you created when creating the feature flag, modify the Roll Out Steps:
- [ ] Cross out any steps related to testing on production GitLab.com, because Geo is not running on production GitLab.com at the moment.
- [ ] Add a step to `Test replication and verification of Cool Widgets on a non-GDK-deployment. For example, using GitLab Environment Toolkit`.
- [ ] Add a step to `Ping the Geo PM and EM to coordinate testing`. For example, you might add steps to generate Cool Widgets, and then a Geo engineer may take it from there.
- [ ] In `ee/config/feature_flags/development/geo_cool_widget_replication.yml`, set `default_enabled: true`
- [ ] In `ee/app/replicators/geo/cool_widget_replicator.rb`, delete the `self.replication_enabled_by_default?` method:
```ruby
module Geo
class CoolWidgetReplicator < Gitlab::Geo::Replicator
...
# REMOVE THIS METHOD
def self.replication_enabled_by_default?
false
end
# REMOVE THIS METHOD
...
end
end
```
- [ ] In `ee/app/graphql/types/geo/geo_node_type.rb`, remove the `feature_flag` option for the released type:
```ruby
field :cool_widget_registries, ::Types::Geo::CoolWidgetRegistryType.connection_type,
null: true,
resolver: ::Resolvers::Geo::CoolWidgetRegistriesResolver,
description: 'Find Cool Widget registries on this Geo node',
feature_flag: :geo_cool_widget_replication # REMOVE THIS LINE
```
- [ ] Add a row for Cool Widgets to the `Data types` table in [Geo data types support](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/administration/geo/replication/datatypes.md#data-types)
- [ ] Add a row for Cool Widgets to the `Limitations on replication/verification` table in [Geo data types support](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/administration/geo/replication/datatypes.md#limitations-on-replicationverification). If the row already exists, then update it to show that Replication and Verification is released in the current version.

View file

@ -0,0 +1,722 @@
<!--
This template is based on a model named `CoolWidget`.
To adapt this template, find and replace the following tokens:
- `CoolWidget`
- `Cool Widget`
- `cool_widget`
- `coolWidget`
If your Model's pluralized form is non-standard, i.e. it doesn't just end in `s`, find and replace the following tokens *first*:
- `CoolWidgets`
- `Cool Widgets`
- `cool_widgets`
- `coolWidgets`
-->
## Replicate Cool Widgets
This issue is for implementing Geo replication and verification of Cool Widgets.
For more background, see [Geo self-service framework](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/geo/framework.md).
In order to implement and test this feature, you need to first [set up Geo locally](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/geo.md).
There are three main sections below. It is a good idea to structure your merge requests this way as well:
1. Modify database schemas to prepare to add Geo support for Cool Widgets
1. Implement Geo support of Cool Widgets behind a feature flag
1. Release Geo support of Cool Widgets
It is also a good idea to first open a proof-of-concept merge request. It can be helpful for working out kinks and getting initial support and feedback from the Geo team. As an example, see the [Proof of Concept to replicate Pipeline Artifacts](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56423).
### Modify database schemas to prepare to add Geo support for Cool Widgets
You might do this section in its own merge request, but it is not required.
#### Add the registry table to track replication and verification state
Geo secondary sites have a [Geo tracking database](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/geo.md#tracking-database) independent of the main database. It is used to track the replication and verification state of all replicables. Every Model has a corresponding "registry" table in the Geo tracking database.
- [ ] Create the migration file in `ee/db/geo/migrate`:
```shell
bin/rails generate geo_migration CreateCoolWidgetRegistry
```
- [ ] Replace the contents of the migration file with the following. Note that we cannot add a foreign key constraint on `cool_widget_id` because the `cool_widgets` table is in a different database. The application code must handle logic such as propagating deletions.
```ruby
# frozen_string_literal: true
class CreateCoolWidgetRegistry < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
unless table_exists?(:cool_widget_registry)
ActiveRecord::Base.transaction do
create_table :cool_widget_registry, id: :bigserial, force: :cascade do |t|
t.bigint :cool_widget_id, null: false
t.datetime_with_timezone :created_at, null: false
t.datetime_with_timezone :last_synced_at
t.datetime_with_timezone :retry_at
t.datetime_with_timezone :verified_at
t.datetime_with_timezone :verification_started_at
t.datetime_with_timezone :verification_retry_at
t.integer :state, default: 0, null: false, limit: 2
t.integer :verification_state, default: 0, null: false, limit: 2
t.integer :retry_count, default: 0, limit: 2, null: false
t.integer :verification_retry_count, default: 0, limit: 2, null: false
t.boolean :checksum_mismatch, default: false, null: false
t.binary :verification_checksum
t.binary :verification_checksum_mismatched
t.string :verification_failure, limit: 255 # rubocop:disable Migration/PreventStrings see https://gitlab.com/gitlab-org/gitlab/-/issues/323806
t.string :last_sync_failure, limit: 255 # rubocop:disable Migration/PreventStrings see https://gitlab.com/gitlab-org/gitlab/-/issues/323806
t.index :cool_widget_id, name: :index_cool_widget_registry_on_cool_widget_id, unique: true
t.index :retry_at
t.index :state
# To optimize performance of CoolWidgetRegistry.verification_failed_batch
t.index :verification_retry_at, name: :cool_widget_registry_failed_verification, order: "NULLS FIRST", where: "((state = 2) AND (verification_state = 3))"
# To optimize performance of CoolWidgetRegistry.needs_verification_count
t.index :verification_state, name: :cool_widget_registry_needs_verification, where: "((state = 2) AND (verification_state = ANY (ARRAY[0, 3])))"
# To optimize performance of CoolWidgetRegistry.verification_pending_batch
t.index :verified_at, name: :cool_widget_registry_pending_verification, order: "NULLS FIRST", where: "((state = 2) AND (verification_state = 0))"
end
end
end
end
def down
drop_table :cool_widget_registry
end
end
```
- [ ] If deviating from the above example, then be sure to order columns according to [our guidelines](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/ordering_table_columns.md).
- [ ] Run Geo tracking database migrations:
```shell
bin/rake geo:db:migrate
```
- [ ] Be sure to commit the relevant changes in `ee/db/geo/schema.rb`
### Add verification state fields on the Geo primary site
The Geo primary site needs to checksum every replicable in order for secondaries to verify their own checksums. To do this, Geo requires fields on the Model. There are two ways to add the necessary verification state fields. If the table is large and wide, then it may be a good idea to add verification state fields to a separate table (Option 2). Consult a database expert if needed.
#### Add verification state fields to the model table (Option 1)
- [ ] Create the migration file in `db/migrate`:
```shell
bin/rails generate migration AddVerificationStateToCoolWidgets
```
- [ ] Replace the contents of the migration file with:
```ruby
# frozen_string_literal: true
class AddVerificationStateToCoolWidgets < ActiveRecord::Migration[6.0]
def change
change_table(:cool_widgets) do |t|
t.integer :verification_state, default: 0, limit: 2, null: false
t.column :verification_started_at, :datetime_with_timezone
t.integer :verification_retry_count, limit: 2, null: false
t.column :verification_retry_at, :datetime_with_timezone
t.column :verified_at, :datetime_with_timezone
t.binary :verification_checksum, using: 'verification_checksum::bytea'
t.text :verification_failure # rubocop:disable Migration/AddLimitToTextColumns
end
end
end
```
- [ ] If deviating from the above example, then be sure to order columns according to [our guidelines](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/ordering_table_columns.md).
- [ ] If `cool_widgets` is a high-traffic table, follow [the database documentation to use `with_lock_retries`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/migration_style_guide.md#when-to-use-the-helper-method)
- [ ] Adding a `text` column also [requires](../database/strings_and_the_text_data_type.md#add-a-text-column-to-an-existing-table) setting a limit. Create the migration file in `db/migrate`:
```shell
bin/rails generate migration AddVerificationFailureLimitToCoolWidgets
```
- [ ] Replace the contents of the migration file with:
```ruby
# frozen_string_literal: true
class AddVerificationFailureLimitToCoolWidgets < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
CONSTRAINT_NAME = 'cool_widget_verification_failure_text_limit'
def up
add_text_limit :cool_widget, :verification_failure, 255, constraint_name: CONSTRAINT_NAME
end
def down
remove_check_constraint(:cool_widget, CONSTRAINT_NAME)
end
end
```
- [ ] Add indexes on verification fields to ensure verification can be performed efficiently. Some or all of these indexes can be omitted if the table is guaranteed to be small. Ask a database expert if you are considering omitting indexes. Create the migration file in `db/migrate`:
```shell
bin/rails generate migration AddVerificationIndexesToCoolWidgets
```
- [ ] Replace the contents of the migration file with:
```ruby
# frozen_string_literal: true
class AddVerificationIndexesToCoolWidgets < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
VERIFICATION_STATE_INDEX_NAME = "index_cool_widgets_on_verification_state"
PENDING_VERIFICATION_INDEX_NAME = "index_cool_widgets_pending_verification"
FAILED_VERIFICATION_INDEX_NAME = "index_cool_widgets_failed_verification"
NEEDS_VERIFICATION_INDEX_NAME = "index_cool_widgets_needs_verification"
disable_ddl_transaction!
def up
add_concurrent_index :cool_widgets, :verification_state, name: VERIFICATION_STATE_INDEX_NAME
add_concurrent_index :cool_widgets, :verified_at, where: "(verification_state = 0)", order: { verified_at: 'ASC NULLS FIRST' }, name: PENDING_VERIFICATION_INDEX_NAME
add_concurrent_index :cool_widgets, :verification_retry_at, where: "(verification_state = 3)", order: { verification_retry_at: 'ASC NULLS FIRST' }, name: FAILED_VERIFICATION_INDEX_NAME
add_concurrent_index :cool_widgets, :verification_state, where: "(verification_state = 0 OR verification_state = 3)", name: NEEDS_VERIFICATION_INDEX_NAME
end
def down
remove_concurrent_index_by_name :cool_widgets, VERIFICATION_STATE_INDEX_NAME
remove_concurrent_index_by_name :cool_widgets, PENDING_VERIFICATION_INDEX_NAME
remove_concurrent_index_by_name :cool_widgets, FAILED_VERIFICATION_INDEX_NAME
remove_concurrent_index_by_name :cool_widgets, NEEDS_VERIFICATION_INDEX_NAME
end
end
```
- [ ] Run database migrations:
```shell
bin/rake db:migrate
```
- [ ] Be sure to commit the relevant changes in `db/structure.sql`
#### Add verification state fields to a separate table (Option 2)
- [ ] Create the migration file in `db/migrate`:
```shell
bin/rails generate migration CreateCoolWidgetStates
```
- [ ] Replace the contents of the migration file with:
```ruby
# frozen_string_literal: true
class CreateCoolWidgetStates < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
VERIFICATION_STATE_INDEX_NAME = "index_cool_widget_states_on_verification_state"
PENDING_VERIFICATION_INDEX_NAME = "index_cool_widget_states_pending_verification"
FAILED_VERIFICATION_INDEX_NAME = "index_cool_widget_states_failed_verification"
NEEDS_VERIFICATION_INDEX_NAME = "index_cool_widget_states_needs_verification"
disable_ddl_transaction!
def up
unless table_exists?(:cool_widget_states)
with_lock_retries do
create_table :cool_widget_states, id: false do |t|
t.references :cool_widget, primary_key: true, null: false, foreign_key: { on_delete: :cascade }
t.integer :verification_state, default: 0, limit: 2, null: false
t.column :verification_started_at, :datetime_with_timezone
t.datetime_with_timezone :verification_retry_at
t.datetime_with_timezone :verified_at
t.integer :verification_retry_count, limit: 2
t.binary :verification_checksum, using: 'verification_checksum::bytea'
t.text :verification_failure
t.index :verification_state, name: VERIFICATION_STATE_INDEX_NAME
t.index :verified_at, where: "(verification_state = 0)", order: { verified_at: 'ASC NULLS FIRST' }, name: PENDING_VERIFICATION_INDEX_NAME
t.index :verification_retry_at, where: "(verification_state = 3)", order: { verification_retry_at: 'ASC NULLS FIRST' }, name: FAILED_VERIFICATION_INDEX_NAME
t.index :verification_state, where: "(verification_state = 0 OR verification_state = 3)", name: NEEDS_VERIFICATION_INDEX_NAME
end
end
end
add_text_limit :cool_widget_states, :verification_failure, 255
end
def down
drop_table :cool_widget_states
end
end
```
- [ ] If deviating from the above example, then be sure to order columns according to [our guidelines](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/ordering_table_columns.md).
- [ ] Run database migrations:
```shell
bin/rake db:migrate
```
- [ ] Be sure to commit the relevant changes in `db/structure.sql`
That's all of the required database changes.
### Implement Geo support of Cool Widgets behind a feature flag
#### Step 1. Implement replication and verification
- [ ] Include `Gitlab::Geo::ReplicableModel` in the `CoolWidget` class, and specify the Replicator class `with_replicator Geo::CoolWidgetReplicator`.
At this point the `CoolWidget` class should look like this:
```ruby
# frozen_string_literal: true
class CoolWidget < ApplicationRecord
include ::Gitlab::Geo::ReplicableModel
include ::Gitlab::Geo::VerificationState
with_replicator Geo::CoolWidgetReplicator
mount_uploader :file, CoolWidgetUploader
# Override the `all` default if not all records can be replicated. For an
# example of an existing Model that needs to do this, see
# `EE::MergeRequestDiff`.
# scope :available_replicables, -> { all }
# @param primary_key_in [Range, CoolWidget] arg to pass to primary_key_in scope
# @return [ActiveRecord::Relation<CoolWidget>] everything that should be synced to this node, restricted by primary key
def self.replicables_for_current_secondary(primary_key_in)
# This issue template does not help you write this method.
#
# This method is called only on Geo secondary sites. It is called when
# we want to know which records to replicate. This is not easy to automate
# because for example:
#
# * The "selective sync" feature allows admins to choose which namespaces # to replicate, per secondary site. Most Models are scoped to a
# namespace, but the nature of the relationship to a namespace varies
# between Models.
# * The "selective sync" feature allows admins to choose which shards to
# replicate, per secondary site. Repositories are associated with
# shards. Most blob types are not, but Project Uploads are.
# * Remote stored replicables are not replicated, by default. But the
# setting `sync_object_storage` enables replication of remote stored
# replicables.
#
# Search the codebase for examples, and consult a Geo expert if needed.
end
...
end
```
- [ ] Implement `CoolWidget.replicables_for_current_secondary` above.
- [ ] Ensure `CoolWidget.replicables_for_current_secondary` is well-tested. Search the codebase for `replicables_for_current_secondary` to find examples of parameterized table specs. You may need to add more `FactoryBot` traits.
- [ ] If you are using a separate table `cool_widget_states` to track verification state on the Geo primary site, then:
- [ ] Do not include `::Gitlab::Geo::VerificationState` on the `CoolWidget` class.
- [ ] Add the following lines to the `cool_widget_state.rb` model:
```ruby
class CoolWidgetState < ApplicationRecord
...
self.primary_key = :cool_widget_id
include ::Gitlab::Geo::VerificationState
belongs_to :cool_widget, inverse_of: :cool_widget_state
...
end
```
- [ ] Add the following lines to the `cool_widget` model:
```ruby
class CoolWidget < ApplicationRecord
...
has_one :cool_widget_state, inverse_of: :cool_widget
delegate :verification_retry_at, :verification_retry_at=,
:verified_at, :verified_at=,
:verification_checksum, :verification_checksum=,
:verification_failure, :verification_failure=,
:verification_retry_count, :verification_retry_count=,
to: :cool_widget_state
...
end
```
- [ ] Create `ee/app/replicators/geo/cool_widget_replicator.rb`. Implement the `#carrierwave_uploader` method which should return a `CarrierWave::Uploader`, and implement the class method `.model` to return the `CoolWidget` class:
```ruby
# frozen_string_literal: true
module Geo
class CoolWidgetReplicator < Gitlab::Geo::Replicator
include ::Geo::BlobReplicatorStrategy
def self.model
::CoolWidget
end
def carrierwave_uploader
model_record.file
end
# The feature flag follows the format `geo_#{replicable_name}_replication`,
# so here it would be `geo_cool_widget_replication`
def self.replication_enabled_by_default?
false
end
override :verification_feature_flag_enabled?
def self.verification_feature_flag_enabled?
# We are adding verification at the same time as replication, so we
# don't need to toggle verification separately from replication. When
# the replication feature flag is off, then verification is also off
# (see `VerifiableReplicator.verification_enabled?`)
true
end
end
end
```
- [ ] Generate the feature flag definition file by running the feature flag command and following the command prompts:
```shell
bin/feature-flag --ee geo_cool_widget_replication --type development --group 'group::geo'
```
- [ ] Add this replicator class to the method `replicator_classes` in
`ee/lib/gitlab/geo.rb`:
```ruby
REPLICATOR_CLASSES = [
::Geo::PackageFileReplicator,
::Geo::CoolWidgetReplicator
]
end
```
- [ ] Create `ee/spec/replicators/geo/cool_widget_replicator_spec.rb` and perform the necessary setup to define the `model_record` variable for the shared examples:
```ruby
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Geo::CoolWidgetReplicator do
let(:model_record) { build(:cool_widget) }
include_examples 'a blob replicator'
include_examples 'a verifiable replicator'
end
```
- [ ] Create `ee/app/models/geo/cool_widget_registry.rb`:
```ruby
# frozen_string_literal: true
class Geo::CoolWidgetRegistry < Geo::BaseRegistry
include ::Geo::ReplicableRegistry
include ::Geo::VerifiableRegistry
MODEL_CLASS = ::CoolWidget
MODEL_FOREIGN_KEY = :cool_widget_id
belongs_to :cool_widget, class_name: 'CoolWidget'
end
```
- [ ] Update `REGISTRY_CLASSES` in `ee/app/workers/geo/secondary/registry_consistency_worker.rb`.
- [ ] Update `def model_class_factory_name` in `ee/spec/services/geo/registry_consistency_service_spec.rb`.
- [ ] Update `it 'creates missing registries for each registry class'` in `ee/spec/workers/geo/secondary/registry_consistency_worker_spec.rb`.
- [ ] Add `cool_widget_registry` to `ActiveSupport::Inflector.inflections` in `config/initializers_before_autoloader/000_inflections.rb`.
- [ ] Create `ee/spec/factories/geo/cool_widget_registry.rb`:
```ruby
# frozen_string_literal: true
FactoryBot.define do
factory :geo_cool_widget_registry, class: 'Geo::CoolWidgetRegistry' do
cool_widget
state { Geo::CoolWidgetRegistry.state_value(:pending) }
trait :synced do
state { Geo::CoolWidgetRegistry.state_value(:synced) }
last_synced_at { 5.days.ago }
end
trait :failed do
state { Geo::CoolWidgetRegistry.state_value(:failed) }
last_synced_at { 1.day.ago }
retry_count { 2 }
last_sync_failure { 'Random error' }
end
trait :started do
state { Geo::CoolWidgetRegistry.state_value(:started) }
last_synced_at { 1.day.ago }
retry_count { 0 }
end
end
end
```
- [ ] Create `ee/spec/models/geo/cool_widget_registry_spec.rb`:
```ruby
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Geo::CoolWidgetRegistry, :geo, type: :model do
let_it_be(:registry) { create(:geo_cool_widget_registry) }
specify 'factory is valid' do
expect(registry).to be_valid
end
include_examples 'a Geo framework registry'
include_examples 'a Geo verifiable registry'
end
```
#### Step 2. Implement metrics gathering
Metrics are gathered by `Geo::MetricsUpdateWorker`, persisted in `GeoNodeStatus` for display in the UI, and sent to Prometheus:
- [ ] Add the following fields to Geo Node Status example responses in `doc/api/geo_nodes.md`:
- `cool_widgets_count`
- `cool_widgets_checksum_total_count`
- `cool_widgets_checksummed_count`
- `cool_widgets_checksum_failed_count`
- `cool_widgets_synced_count`
- `cool_widgets_failed_count`
- `cool_widgets_registry_count`
- `cool_widgets_verification_total_count`
- `cool_widgets_verified_count`
- `cool_widgets_verification_failed_count`
- `cool_widgets_synced_in_percentage`
- `cool_widgets_verified_in_percentage`
- [ ] Add the same fields to `GET /geo_nodes/status` example response in
`ee/spec/fixtures/api/schemas/public_api/v4/geo_node_status.json`.
- [ ] Add the following fields to the `Sidekiq metrics` table in `doc/administration/monitoring/prometheus/gitlab_metrics.md`:
- `geo_cool_widgets`
- `geo_cool_widgets_checksum_total`
- `geo_cool_widgets_checksummed`
- `geo_cool_widgets_checksum_failed`
- `geo_cool_widgets_synced`
- `geo_cool_widgets_failed`
- `geo_cool_widgets_registry`
- `geo_cool_widgets_verification_total`
- `geo_cool_widgets_verified`
- `geo_cool_widgets_verification_failed`
- [ ] Add the following to the parameterized table in the `context 'Replicator stats' do` block in `ee/spec/models/geo_node_status_spec.rb`:
```ruby
Geo::CoolWidgetReplicator | :cool_widget | :geo_cool_widget_registry
```
- [ ] Add the following to `spec/factories/cool_widgets.rb`:
```ruby
trait(:verification_succeeded) do
with_file
verification_checksum { 'abc' }
verification_state { CoolWidget.verification_state_value(:verification_succeeded) }
end
trait(:verification_failed) do
with_file
verification_failure { 'Could not calculate the checksum' }
verification_state { CoolWidget.verification_state_value(:verification_failed) }
end
```
- [ ] Make sure the factory also allows setting a `project` attribute. If the model does not have a direct relation to a project, you can use a `transient` attribute. Check out `spec/factories/merge_request_diffs.rb` for an example.
Cool Widget replication and verification metrics should now be available in the API, the `Admin > Geo > Nodes` view, and Prometheus.
#### Step 3. Implement the GraphQL API
The GraphQL API is used by `Admin > Geo > Replication Details` views, and is directly queryable by administrators.
- [ ] Add a new field to `GeoNodeType` in `ee/app/graphql/types/geo/geo_node_type.rb`:
```ruby
field :cool_widget_registries, ::Types::Geo::CoolWidgetRegistryType.connection_type,
null: true,
resolver: ::Resolvers::Geo::CoolWidgetRegistriesResolver,
description: 'Find Cool Widget registries on this Geo node',
feature_flag: :geo_cool_widget_replication
```
- [ ] Add the new `cool_widget_registries` field name to the `expected_fields` array in `ee/spec/graphql/types/geo/geo_node_type_spec.rb`.
- [ ] Create `ee/app/graphql/resolvers/geo/cool_widget_registries_resolver.rb`:
```ruby
# frozen_string_literal: true
module Resolvers
module Geo
class CoolWidgetRegistriesResolver < BaseResolver
type ::Types::Geo::GeoNodeType.connection_type, null: true
include RegistriesResolver
end
end
end
```
- [ ] Create `ee/spec/graphql/resolvers/geo/cool_widget_registries_resolver_spec.rb`:
```ruby
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Geo::CoolWidgetRegistriesResolver do
it_behaves_like 'a Geo registries resolver', :geo_cool_widget_registry
end
```
- [ ] Create `ee/app/finders/geo/cool_widget_registry_finder.rb`:
```ruby
# frozen_string_literal: true
module Geo
class CoolWidgetRegistryFinder
include FrameworkRegistryFinder
end
end
```
- [ ] Create `ee/spec/finders/geo/cool_widget_registry_finder_spec.rb`:
```ruby
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Geo::CoolWidgetRegistryFinder do
it_behaves_like 'a framework registry finder', :geo_cool_widget_registry
end
```
- [ ] Create `ee/app/graphql/types/geo/cool_widget_registry_type.rb`:
```ruby
# frozen_string_literal: true
module Types
module Geo
# rubocop:disable Graphql/AuthorizeTypes because it is included
class CoolWidgetRegistryType < BaseObject
include ::Types::Geo::RegistryType
graphql_name 'CoolWidgetRegistry'
description 'Represents the Geo replication and verification state of a cool_widget'
field :cool_widget_id, GraphQL::ID_TYPE, null: false, description: 'ID of the Cool Widget'
end
end
end
```
- [ ] Create `ee/spec/graphql/types/geo/cool_widget_registry_type_spec.rb`:
```ruby
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['CoolWidgetRegistry'] do
it_behaves_like 'a Geo registry type'
it 'has the expected fields (other than those included in RegistryType)' do
expected_fields = %i[cool_widget_id]
expect(described_class).to have_graphql_fields(*expected_fields).at_least
end
end
```
- [ ] Add integration tests for providing CoolWidget registry data to the frontend via the GraphQL API, by duplicating and modifying the following shared examples in `ee/spec/requests/api/graphql/geo/registries_spec.rb`:
```ruby
it_behaves_like 'gets registries for', {
field_name: 'coolWidgetRegistries',
registry_class_name: 'CoolWidgetRegistry',
registry_factory: :geo_cool_widget_registry,
registry_foreign_key_field_name: 'coolWidgetId'
}
```
- [ ] Update the GraphQL reference documentation:
```shell
bundle exec rake gitlab:graphql:compile_docs
```
Individual Cool Widget replication and verification data should now be available via the GraphQL API.
### Release Geo support of Cool Widgets
- [ ] In the rollout issue you created when creating the feature flag, modify the Roll Out Steps:
- [ ] Cross out any steps related to testing on production GitLab.com, because Geo is not running on production GitLab.com at the moment.
- [ ] Add a step to `Test replication and verification of Cool Widgets on a non-GDK-deployment. For example, using GitLab Environment Toolkit`.
- [ ] Add a step to `Ping the Geo PM and EM to coordinate testing`. For example, you might add steps to generate Cool Widgets, and then a Geo engineer may take it from there.
- [ ] In `ee/config/feature_flags/development/geo_cool_widget_replication.yml`, set `default_enabled: true`
- [ ] In `ee/app/replicators/geo/cool_widget_replicator.rb`, delete the `self.replication_enabled_by_default?` method:
```ruby
module Geo
class CoolWidgetReplicator < Gitlab::Geo::Replicator
...
# REMOVE THIS METHOD
def self.replication_enabled_by_default?
false
end
# REMOVE THIS METHOD
...
end
end
```
- [ ] In `ee/app/graphql/types/geo/geo_node_type.rb`, remove the `feature_flag` option for the released type:
```ruby
field :cool_widget_registries, ::Types::Geo::CoolWidgetRegistryType.connection_type,
null: true,
resolver: ::Resolvers::Geo::CoolWidgetRegistriesResolver,
description: 'Find Cool Widget registries on this Geo node',
feature_flag: :geo_cool_widget_replication # REMOVE THIS LINE
```
- [ ] Add a row for Cool Widgets to the `Data types` table in [Geo data types support](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/administration/geo/replication/datatypes.md#data-types)
- [ ] Add a row for Cool Widgets to the `Limitations on replication/verification` table in [Geo data types support](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/administration/geo/replication/datatypes.md#limitations-on-replicationverification). If the row already exists, then update it to show that Replication and Verification is released in the current version.

View file

@ -39,24 +39,25 @@ Add details for required items and delete others.
<!-- <!--
Steps and the parts of the code that will need to get updated. The plan can also Steps and the parts of the code that will need to get updated. The plan can also
call-out responsibilities for other team members or teams. call-out responsibilities for other team members or teams.
-->
e.g.:
- [ ] ~frontend Step 1 - [ ] ~frontend Step 1
- [ ] `@person` Step 1a - [ ] `@person` Step 1a
- [ ] ~frontend Step 2 - [ ] ~frontend Step 2
-->
<!-- <!--
Workflow and other relevant labels Workflow and other relevant labels
~"group::" ~"Category:" ~"GitLab Ultimate" # ~"group::" ~"Category:" ~"GitLab Ultimate"
-->
/label ~"workflow::refinement"
<!--
Other settings you might want to include when creating the issue. Other settings you might want to include when creating the issue.
/milestone %"Next 1-3 releases" # /assign @
/assign @ # /epic &
/epic &
--> -->
/label ~"workflow::refinement"
/milestone %Backlog

View file

@ -101,3 +101,6 @@ In which enterprise tier should this feature go? See https://about.gitlab.com/ha
### Is this a cross-stage feature? ### Is this a cross-stage feature?
Communicate if this change will affect multiple Stage Groups or product areas. We recommend always start with the assumption that a feature request will have an impact into another Group. Loop in the most relevant PM and Product Designer from that Group to provide strategic support to help align the Group's broader plan and vision, as well as to avoid UX and technical debt. https://about.gitlab.com/handbook/product/#cross-stage-features --> Communicate if this change will affect multiple Stage Groups or product areas. We recommend always start with the assumption that a feature request will have an impact into another Group. Loop in the most relevant PM and Product Designer from that Group to provide strategic support to help align the Group's broader plan and vision, as well as to avoid UX and technical debt. https://about.gitlab.com/handbook/product/#cross-stage-features -->
/label ~documentation
/label ~direction

View file

@ -1,67 +0,0 @@
# Project Name | Migration Tracker
<!-- Please edit this header with your project / organization's name. -->
## Background
<!--
Please add information here about why you're planning on migrating. Include any initial announcements that have been made about the decision or status.
-->
### Goals
<!-- What are some of the goals of your migration to GitLab? Delete this section if you don't want to enumerate goals. -->
## Quick Facts
<!-- Please complete as many items in this list as possible. If you're not sure yet, add "TBD" (To be Decided) or "Unknown" -->
* **Timeline.** -
* **Product.** - GitLab Gold/Ultimate or Community Edition
* **Project's License.** What kind of OSI-approved license does your project use?
## Current Tooling and Replacements
<!--
Please fill in the table to give an overview of your current tooling. Here's a description of what to include in each column:
- Tool: which tool or platform you are currently using
- Feature: which particular feature you are using in that tool or platform
- GitLab feature: equivalent GitLab feature (the GitLab team can help fill this in, as well as the info in the next column)
- GitLab edition: in which GitLab edition (CE or EE) is this feature available?
Here's an example of a replacements overview from one of the projects which migrated to GitLab: https://gitlab.com/gitlab-org/gitlab/-/issues/25657#gitlab-replacements
-->
| Tool | Feature | GitLab feature | GitLab edition |
| --- | --- | --- | --- |
| | | | |
## Collaborators
<!-- Please add names of collaborators in the format: Name, Title, Role (what will you be helping to do, or how should you be involved), GitLab username -->
## Related Issues
<!-- Add any related issues that are important for your project by adding the title of the issue and a link to it (preferably as an embedded link). You will probably keep editing this section as the migration progresses, so don't worry if it's mostly blank for now.
Here is an example of what this list might look like once populated: https://gitlab.com/gitlab-org/gitlab-foss/-/issues/55039#outstanding-issues
-->
### Blockers
* [ ] ADD_LINK_TO_ISSUE_HERE
### Urgent
* [ ]
### Important but not urgent
* [ ]
### Nice to have
* [ ]
------
/label ~"Open Source" ~movingtogitlab
/cc @nuritzi

View file

@ -0,0 +1,68 @@
<!-- Please title your issue with the following format: "Project Name | Issue Tracker". -->
## Background
<!--
Please add information here about why your project is considering a migration to GitLab, or why it decided to do so. Include any initial announcements that have been / were made about the decision or status.
-->
### Goals
<!-- What are some of the goals of your migration to GitLab? Delete this section if you don't want to enumerate goals. -->
## Quick Facts
<!-- Please complete as many items in this list as possible. If you're not sure yet, add "TBD" (To be Decided) or "Unknown" -->
* **Timeline.** -
* **Product.** - SaaS-Ultimate/Self-Managed-Ultimate or Community Edition
* **Project's License.** What kind of OSI-approved license does your project use?
## Current Tooling and Replacements
<!--
Please fill in the table to give an overview of your current tooling. Here's a description of what to include in each column:
- Tool: which tool or platform you are currently using
- Feature: which particular feature you are using in that tool or platform
- GitLab feature: equivalent GitLab feature (the GitLab team can help fill this in, as well as the info in the next column)
- GitLab edition: in which GitLab edition (CE or EE) is this feature available?
Here's an example of a replacements overview from one of the projects which migrated to GitLab: https://gitlab.com/gitlab-org/gitlab/-/issues/25657#gitlab-replacements
Consider deleting the table below if you are unable to expand upon your current tooling.
-->
| Tool | Feature | GitLab feature | GitLab edition |
| --- | --- | --- | --- |
| | | | |
## Collaborators
<!-- Please add names of collaborators in the format: Name, Title, Role (what will you be helping to do, or how should you be involved), GitLab username -->
## Related Issues
<!-- Add any related issues that are important for your project by adding the title of the issue and a link to it (preferably as an embedded link). You will probably keep editing this section as the migration progresses, so don't worry if it's mostly blank for now.
Here is an example of what this list might look like once populated: https://gitlab.com/gitlab-org/gitlab-foss/-/issues/55039#outstanding-issues
-->
### Blockers
* [ ] ADD_LINK_TO_ISSUE_HERE
### Urgent
* [ ]
### Important but not urgent
* [ ]
### Nice to have
* [ ]
------
/label ~"Open Source Partners"
/cc @nuritzi @greg

View file

@ -34,6 +34,6 @@ after the implementation is merged/deployed/released.
- [ ] The solution improved the situation. - [ ] The solution improved the situation.
- If yes, check this box and close the issue. Well done! :tada: - If yes, check this box and close the issue. Well done! :tada:
- Otherwise, create a new "Productivity Improvement" issue. You can re-use the description from this issue, but obviously another solution should be chosen this time. - Otherwise, create a new "Productivity Improvement" issue. You can re-use the description from this issue, but another solution should be chosen this time.
/label ~"Engineering Productivity" ~meta /label ~"Engineering Productivity" ~meta

View file

@ -10,6 +10,16 @@ As the name implies, the purpose of the template is to detail underperforming qu
- [ ] Provide [priority and severity labels](https://about.gitlab.com/handbook/engineering/quality/issue-triage/#availability) - [ ] Provide [priority and severity labels](https://about.gitlab.com/handbook/engineering/quality/issue-triage/#availability)
- [ ] If this requires immediate attention cc `@gitlab-org/database-team` and reach out in the #g_database slack channel - [ ] If this requires immediate attention cc `@gitlab-org/database-team` and reach out in the #g_database slack channel
### SQL Statement
```sql
```
### Data from Elastic
Instructions on collecting data from [PostgreSQL slow logs stored in Elasticsearch](https://gitlab.com/gitlab-com/runbooks/-/merge_requests/3361/diffs)
### Requested Data points ### Requested Data points
Please provide as many of these fields as possible when submitting a query performance report. Please provide as many of these fields as possible when submitting a query performance report.
@ -20,7 +30,6 @@ Please provide as many of these fields as possible when submitting a query perfo
- Database time relative to total database time - Database time relative to total database time
- Source of calls (Sidekiq, WebAPI, etc) - Source of calls (Sidekiq, WebAPI, etc)
- Query ID - Query ID
- SQL Statement
- Query Plan - Query Plan
- Query Example - Query Example
- Total number of calls (relative) - Total number of calls (relative)

View file

@ -10,9 +10,10 @@ Set the title to: `Description of the original issue`
- [ ] Read the [security process for developers] if you are not familiar with it. - [ ] Read the [security process for developers] if you are not familiar with it.
- Verify if the issue you're working on `gitlab-org/gitlab` is confidential, if it's public fix should be placed on GitLab canonical and no backports are required. - Verify if the issue you're working on `gitlab-org/gitlab` is confidential, if it's public fix should be placed on GitLab canonical and no backports are required.
- [ ] Mark this [issue as related] to the Security Release Tracking Issue. You can find it on the topic of the `#releases` Slack channel. - [ ] Mark this [issue as linked] to the Security Release Tracking Issue. You can find it on the topic of the `#releases` Slack channel.
- Fill out the [Links section](#links): - Fill out the [Links section](#links):
- [ ] Next to **Issue on GitLab**, add a link to the `gitlab-org/gitlab` issue that describes the security vulnerability. - [ ] Next to **Issue on GitLab**, add a link to the `gitlab-org/gitlab` issue that describes the security vulnerability.
- [ ] Add one of the `~severity::x` labels to the issue and all associated merge requests.
## Development ## Development
@ -64,6 +65,6 @@ After your merge request has been approved according to our [approval guidelines
[secpick documentation]: https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/utilities/secpick_script.md [secpick documentation]: https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/utilities/secpick_script.md
[security Release merge request template]: https://gitlab.com/gitlab-org/security/gitlab/blob/master/.gitlab/merge_request_templates/Security%20Release.md [security Release merge request template]: https://gitlab.com/gitlab-org/security/gitlab/blob/master/.gitlab/merge_request_templates/Security%20Release.md
[approval guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines [approval guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines
[issue as related]: https://docs.gitlab.com/ee/user/project/issues/related_issues.html#adding-a-related-issue [issue as linked]: https://docs.gitlab.com/ee/user/project/issues/related_issues.html#add-a-linked-issue
/label ~security /label ~security

View file

@ -27,7 +27,7 @@ As well as defining the experiment rollout and cleanup, this issue incorporates
### What might happen if this goes wrong? ### What might happen if this goes wrong?
### What can we monitor to detect problems with this? ### What can we monitor to detect problems with this?
<!-- Which dashboards from https://dashboards.gitlab.net are most relevant? Sentry errors reports can alse be useful to review --> <!-- Which dashboards from https://dashboards.gitlab.net are most relevant? Sentry errors reports can also be useful to review -->
### Tracked data ### Tracked data
<!-- brief description or link to issue or Sisense dashboard --> <!-- brief description or link to issue or Sisense dashboard -->
@ -81,6 +81,7 @@ If applicable, any groups/projects that are happy to have this feature turned on
- [ ] Announce on the issue that the flag has been enabled - [ ] Announce on the issue that the flag has been enabled
- [ ] Remove experiment code and feature flag and add changelog entry - a separate [cleanup issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Experiment%20Successful%20Cleanup) might be required - [ ] Remove experiment code and feature flag and add changelog entry - a separate [cleanup issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Experiment%20Successful%20Cleanup) might be required
- [ ] After the flag removal is deployed, [clean up the feature flag](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up) by running chatops command in `#production` channel - [ ] After the flag removal is deployed, [clean up the feature flag](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up) by running chatops command in `#production` channel
- [ ] Assign to the product manager to update the [knowledge base](https://about.gitlab.com/direction/growth/#growth-insights-knowledge-base) (if applicable)
## Rollback Steps ## Rollback Steps

View file

@ -10,7 +10,7 @@ Please link to the respective test case in the testcases project
- [ ] Note if the test is intended to run in specific scenarios. If a scenario is new, add a link to the MR that adds the new scenario. - [ ] Note if the test is intended to run in specific scenarios. If a scenario is new, add a link to the MR that adds the new scenario.
- [ ] Follow the end-to-end tests [style guide](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/style_guide.html) and [best practices](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/best_practices.html). - [ ] Follow the end-to-end tests [style guide](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/style_guide.html) and [best practices](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/best_practices.html).
- [ ] Use the appropriate [RSpec metadata tag(s)](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/rspec_metadata_tests.html#rspec-metadata-for-end-to-end-tests). - [ ] Use the appropriate [RSpec metadata tag(s)](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/rspec_metadata_tests.html#rspec-metadata-for-end-to-end-tests).
- [ ] Ensure that a created resource is removed after test execution. - [ ] Ensure that a created resource is removed after test execution. A `Group` resource can be shared between multiple tests. Do not remove it unless it has a unique path. Note that we have a cleanup job that periodically removes groups under `gitlab-qa-sandbox-group`.
- [ ] Ensure that no [transient bugs](https://about.gitlab.com/handbook/engineering/quality/issue-triage/#transient-bugs) are hidden accidentally due to the usage of `waits` and `reloads`. - [ ] Ensure that no [transient bugs](https://about.gitlab.com/handbook/engineering/quality/issue-triage/#transient-bugs) are hidden accidentally due to the usage of `waits` and `reloads`.
- [ ] Verify the tags to ensure it runs on the desired test environments. - [ ] Verify the tags to ensure it runs on the desired test environments.
- [ ] If this MR has a dependency on another MR, such as a GitLab QA MR, specify the order in which the MRs should be merged. - [ ] If this MR has a dependency on another MR, such as a GitLab QA MR, specify the order in which the MRs should be merged.

View file

@ -0,0 +1,44 @@
## What does this MR do?
<!--
Please describe why the end-to-end test is being quarantined/ de-quarantined.
Please note that the aim of quarantining a test is not to get back a green pipeline, but rather to reduce
the noise (due to constantly failing tests, flaky tests, and so on) so that new failures are not missed.
-->
### E2E Test Failure issue(s)
<!-- Please link to the respective E2E test failure issue. -->
### Check-list
- [ ] General code guidelines check-list
- [ ] [Code review guidelines](https://docs.gitlab.com/ee/development/code_review.html)
- [ ] [Style guides](https://docs.gitlab.com/ee/development/contributing/style_guides.html)
- [ ] Quarantine test check-list
- [ ] Follow the [Quarantining Tests guide](https://about.gitlab.com/handbook/engineering/quality/guidelines/debugging-qa-test-failures/#quarantining-tests).
- [ ] Confirm the test has a [`quarantine:` tag with the specified quarantine type](https://about.gitlab.com/handbook/engineering/quality/guidelines/debugging-qa-test-failures/#quarantined-test-types).
- [ ] Note if the test should be [quarantined for a specific environment](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/environment_selection.html#quarantining-a-test-for-a-specific-environment).
- [ ] Dequarantine test check-list
- [ ] Follow the [Dequarantining Tests guide](https://about.gitlab.com/handbook/engineering/quality/guidelines/debugging-qa-test-failures/#dequarantining-tests).
- [ ] Confirm the test consistently passes on the target GitLab environment(s).
- [ ] (Optionally) [Trigger a manual GitLab-QA pipeline](https://about.gitlab.com/handbook/engineering/quality/guidelines/tips-and-tricks/#running-gitlab-qa-pipeline-against-a-specific-gitlab-release) against a specific GitLab environment using the `RELEASE` variable from the `package-and-qa` job of the current merge request.
- [ ] To ensure a faster turnaround, ask in the `#quality` Slack channel for someone to review and merge the merge request, rather than assigning it directly.
<!-- Base labels. -->
/label ~"Quality" ~"QA" ~"feature" ~"feature::maintenance"
<!-- Labels to pick into auto-deploy. -->
/label ~"Pick into auto-deploy" ~"priority::1" ~"severity::1"
<!--
Choose the stage that appears in the test path, e.g. ~"devops::create" for
`qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb`.
-->
/label ~devops::
<!-- Select the current milestone. -->
/milestone %

View file

@ -104,22 +104,18 @@ linters:
# These cops should eventually get enabled # These cops should eventually get enabled
- Cop/LineBreakAfterGuardClauses - Cop/LineBreakAfterGuardClauses
- Cop/LineBreakAroundConditionalBlock
- Cop/ProjectPathHelper - Cop/ProjectPathHelper
- Gitlab/FeatureAvailableUsage
- GitlabSecurity/PublicSend - GitlabSecurity/PublicSend
- Layout/EmptyLineAfterGuardClause - Layout/EmptyLineAfterGuardClause
- Layout/LeadingCommentSpace - Layout/LeadingCommentSpace
- Layout/SpaceAfterColon
- Layout/SpaceAfterComma
- Layout/SpaceAroundOperators - Layout/SpaceAroundOperators
- Layout/SpaceBeforeBlockBraces - Layout/SpaceBeforeBlockBraces
- Layout/SpaceBeforeComma - Layout/SpaceBeforeComma
- Layout/SpaceBeforeFirstArg - Layout/SpaceBeforeFirstArg
- Layout/SpaceInsideArrayLiteralBrackets
- Layout/SpaceInsideHashLiteralBraces - Layout/SpaceInsideHashLiteralBraces
- Layout/SpaceInsideStringInterpolation - Layout/SpaceInsideStringInterpolation
- Layout/TrailingEmptyLines - Layout/TrailingEmptyLines
- Lint/BooleanSymbol
- Lint/LiteralInInterpolation - Lint/LiteralInInterpolation
- Lint/ParenthesesAsGroupedExpression - Lint/ParenthesesAsGroupedExpression
- Lint/RedundantWithIndex - Lint/RedundantWithIndex
@ -131,18 +127,14 @@ linters:
- Rails/LinkToBlank - Rails/LinkToBlank
- Rails/Presence - Rails/Presence
- Rails/RequestReferer - Rails/RequestReferer
- Style/AndOr
- Style/ColonMethodCall - Style/ColonMethodCall
- Style/ConditionalAssignment - Style/ConditionalAssignment
- Style/HashSyntax - Style/HashSyntax
- Style/IdenticalConditionalBranches - Style/IdenticalConditionalBranches
- Style/NegatedIf - Style/NegatedIf
- Style/NestedTernaryOperator - Style/NestedTernaryOperator
- Style/Not
- Style/ParenthesesAroundCondition - Style/ParenthesesAroundCondition
- Style/RedundantParentheses
- Style/SelfAssignment - Style/SelfAssignment
- Style/Semicolon
- Style/TernaryParentheses - Style/TernaryParentheses
- Style/TrailingCommaInHashLiteral - Style/TrailingCommaInHashLiteral
- Style/UnlessElse - Style/UnlessElse
@ -150,6 +142,9 @@ linters:
- Style/WordArray - Style/WordArray
- Style/ZeroLengthPredicate - Style/ZeroLengthPredicate
# WIP See https://gitlab.com/gitlab-org/gitlab/-/issues/207950
- Cop/UserAdmin
RubyComments: RubyComments:
enabled: true enabled: true

View file

@ -1,363 +1,7 @@
# This configuration was generated by # This configuration was generated by
# `haml-lint --auto-gen-config` # `haml-lint --auto-gen-config`
# on 2020-05-21 10:58:59 -0400 using Haml-Lint version 0.34.0. # on 2021-04-01 00:00:00 +0000 using Haml-Lint version 0.36.0.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the lints are removed from the code base. # one by one as the lints are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
# versions of Haml-Lint, may require this file to be generated again. # versions of Haml-Lint, may require this file to be generated again.
linters:
# Offense count: 1552
NoPlainNodes:
enabled: true
exclude:
- 'app/views/admin/abuse_reports/_abuse_report.html.haml'
- 'app/views/admin/abuse_reports/index.html.haml'
- 'app/views/admin/appearances/_form.html.haml'
- 'app/views/admin/application_settings/_abuse.html.haml'
- 'app/views/admin/application_settings/_diff_limits.html.haml'
- 'app/views/admin/application_settings/_gitaly.html.haml'
- 'app/views/admin/application_settings/_ip_limits.html.haml'
- 'app/views/admin/application_settings/_performance.html.haml'
- 'app/views/admin/application_settings/_plantuml.html.haml'
- 'app/views/admin/application_settings/_prometheus.html.haml'
- 'app/views/admin/application_settings/_realtime.html.haml'
- 'app/views/admin/application_settings/_repository_check.html.haml'
- 'app/views/admin/application_settings/_signin.html.haml'
- 'app/views/admin/application_settings/_signup.html.haml'
- 'app/views/admin/application_settings/_spam.html.haml'
- 'app/views/admin/application_settings/_terminal.html.haml'
- 'app/views/admin/application_settings/_usage.html.haml'
- 'app/views/admin/application_settings/_visibility_and_access.html.haml'
- 'app/views/admin/applications/_delete_form.html.haml'
- 'app/views/admin/applications/_form.html.haml'
- 'app/views/admin/applications/edit.html.haml'
- 'app/views/admin/applications/index.html.haml'
- 'app/views/admin/applications/new.html.haml'
- 'app/views/admin/applications/show.html.haml'
- 'app/views/admin/background_jobs/show.html.haml'
- 'app/views/admin/broadcast_messages/index.html.haml'
- 'app/views/admin/dashboard/index.html.haml'
- 'app/views/admin/deploy_keys/new.html.haml'
- 'app/views/admin/health_check/show.html.haml'
- 'app/views/admin/hook_logs/_index.html.haml'
- 'app/views/admin/hook_logs/show.html.haml'
- 'app/views/admin/hooks/_form.html.haml'
- 'app/views/admin/hooks/edit.html.haml'
- 'app/views/admin/logs/show.html.haml'
- 'app/views/admin/projects/_projects.html.haml'
- 'app/views/admin/requests_profiles/index.html.haml'
- 'app/views/admin/runners/_runner.html.haml'
- 'app/views/admin/runners/index.html.haml'
- 'app/views/admin/runners/show.html.haml'
- 'app/views/admin/services/_form.html.haml'
- 'app/views/admin/services/index.html.haml'
- '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/_form.html.haml'
- 'app/views/admin/users/_head.html.haml'
- 'app/views/admin/users/_profile.html.haml'
- 'app/views/admin/users/_projects.html.haml'
- 'app/views/admin/users/new.html.haml'
- 'app/views/admin/users/projects.html.haml'
- 'app/views/admin/users/show.html.haml'
- 'app/views/authentication/_authenticate.html.haml'
- 'app/views/authentication/_register.html.haml'
- 'app/views/clusters/clusters/_cluster.html.haml'
- 'app/views/clusters/clusters/new.html.haml'
- 'app/views/dashboard/milestones/index.html.haml'
- 'app/views/dashboard/projects/_blank_state_admin_welcome.html.haml'
- 'app/views/dashboard/projects/_blank_state_welcome.html.haml'
- 'app/views/dashboard/todos/_todo.html.haml'
- 'app/views/dashboard/todos/index.html.haml'
- 'app/views/devise/confirmations/almost_there.haml'
- 'app/views/devise/mailer/_confirmation_instructions_account.html.haml'
- 'app/views/devise/mailer/_confirmation_instructions_secondary.html.haml'
- 'app/views/devise/mailer/email_changed.html.haml'
- 'app/views/devise/mailer/password_change.html.haml'
- 'app/views/devise/mailer/reset_password_instructions.html.haml'
- 'app/views/devise/mailer/unlock_instructions.html.haml'
- 'app/views/devise/passwords/edit.html.haml'
- 'app/views/devise/sessions/_new_base.html.haml'
- 'app/views/devise/sessions/_new_crowd.html.haml'
- 'app/views/devise/sessions/_new_ldap.html.haml'
- 'app/views/devise/sessions/new.html.haml'
- 'app/views/devise/sessions/two_factor.html.haml'
- 'app/views/devise/shared/_omniauth_box.html.haml'
- 'app/views/devise/shared/_sign_in_link.html.haml'
- 'app/views/devise/shared/_tabs_normal.html.haml'
- 'app/views/discussions/_discussion.html.haml'
- 'app/views/discussions/_headline.html.haml'
- 'app/views/discussions/_notes.html.haml'
- 'app/views/doorkeeper/applications/_delete_form.html.haml'
- 'app/views/doorkeeper/authorized_applications/_delete_form.html.haml'
- 'app/views/errors/encoding.html.haml'
- 'app/views/errors/git_not_found.html.haml'
- 'app/views/errors/omniauth_error.html.haml'
- 'app/views/errors/precondition_failed.html.haml'
- 'app/views/events/_event_push.atom.haml'
- 'app/views/events/event/_push.html.haml'
- 'app/views/groups/_create_chat_team.html.haml'
- 'app/views/groups/_group_admin_settings.html.haml'
- 'app/views/groups/labels/edit.html.haml'
- 'app/views/groups/labels/new.html.haml'
- 'app/views/groups/milestones/edit.html.haml'
- 'app/views/groups/milestones/index.html.haml'
- 'app/views/groups/milestones/new.html.haml'
- 'app/views/groups/projects.html.haml'
- 'app/views/groups/runners/edit.html.haml'
- 'app/views/groups/settings/_advanced.html.haml'
- 'app/views/groups/settings/_lfs.html.haml'
- 'app/views/help/index.html.haml'
- 'app/views/help/instance_configuration.html.haml'
- 'app/views/help/instance_configuration/_gitlab_ci.html.haml'
- 'app/views/help/instance_configuration/_gitlab_pages.html.haml'
- 'app/views/import/bitbucket/status.html.haml'
- 'app/views/import/bitbucket_server/status.html.haml'
- 'app/views/invites/show.html.haml'
- 'app/views/jira_connect/subscriptions/index.html.haml'
- 'app/views/layouts/_mailer.html.haml'
- 'app/views/layouts/header/_default.html.haml'
- 'app/views/layouts/header/_new_dropdown.haml'
- 'app/views/layouts/jira_connect.html.haml'
- 'app/views/layouts/notify.html.haml'
- 'app/views/notify/_failed_builds.html.haml'
- 'app/views/notify/_reassigned_issuable_email.html.haml'
- 'app/views/notify/_removal_notification.html.haml'
- 'app/views/notify/_successful_pipeline.html.haml'
- 'app/views/notify/autodevops_disabled_email.html.haml'
- 'app/views/notify/changed_milestone_email.html.haml'
- 'app/views/notify/import_issues_csv_email.html.haml'
- 'app/views/notify/issue_moved_email.html.haml'
- 'app/views/notify/member_access_denied_email.html.haml'
- 'app/views/notify/member_invite_accepted_email.html.haml'
- 'app/views/notify/member_invited_email.html.haml'
- 'app/views/notify/new_gpg_key_email.html.haml'
- 'app/views/notify/new_mention_in_issue_email.html.haml'
- 'app/views/notify/new_ssh_key_email.html.haml'
- 'app/views/notify/new_user_email.html.haml'
- 'app/views/notify/pages_domain_disabled_email.html.haml'
- 'app/views/notify/pages_domain_enabled_email.html.haml'
- 'app/views/notify/pages_domain_verification_failed_email.html.haml'
- 'app/views/notify/pages_domain_verification_succeeded_email.html.haml'
- 'app/views/notify/pipeline_failed_email.html.haml'
- 'app/views/notify/project_was_exported_email.html.haml'
- 'app/views/notify/project_was_moved_email.html.haml'
- 'app/views/notify/project_was_not_exported_email.html.haml'
- 'app/views/notify/push_to_merge_request_email.html.haml'
- 'app/views/notify/remote_mirror_update_failed_email.html.haml'
- 'app/views/notify/removed_milestone_issue_email.html.haml'
- 'app/views/notify/removed_milestone_merge_request_email.html.haml'
- 'app/views/notify/repository_push_email.html.haml'
- 'app/views/profiles/chat_names/_chat_name.html.haml'
- 'app/views/profiles/chat_names/index.html.haml'
- 'app/views/profiles/chat_names/new.html.haml'
- 'app/views/projects/_bitbucket_import_modal.html.haml'
- 'app/views/projects/_customize_workflow.html.haml'
- 'app/views/projects/_deletion_failed.html.haml'
- 'app/views/projects/_fork_suggestion.html.haml'
- 'app/views/projects/_gitlab_import_modal.html.haml'
- 'app/views/projects/_home_panel.html.haml'
- 'app/views/projects/_import_project_pane.html.haml'
- 'app/views/projects/_readme.html.haml'
- 'app/views/projects/artifacts/_artifact.html.haml'
- 'app/views/projects/artifacts/_tree_file.html.haml'
- 'app/views/projects/artifacts/browse.html.haml'
- 'app/views/projects/blame/_age_map_legend.html.haml'
- 'app/views/projects/blame/show.html.haml'
- 'app/views/projects/blob/_editor.html.haml'
- 'app/views/projects/blob/_header_content.html.haml'
- 'app/views/projects/blob/_remove.html.haml'
- 'app/views/projects/blob/_render_error.html.haml'
- 'app/views/projects/blob/edit.html.haml'
- 'app/views/projects/blob/new.html.haml'
- 'app/views/projects/blob/preview.html.haml'
- 'app/views/projects/blob/viewers/_empty.html.haml'
- 'app/views/projects/blob/viewers/_stl.html.haml'
- 'app/views/projects/branches/_branch.html.haml'
- 'app/views/projects/branches/_delete_protected_modal.html.haml'
- 'app/views/projects/branches/new.html.haml'
- 'app/views/projects/ci/builds/_build.html.haml'
- 'app/views/projects/ci/lints/_create.html.haml'
- 'app/views/projects/compare/_form.html.haml'
- 'app/views/projects/compare/index.html.haml'
- 'app/views/projects/cycle_analytics/_empty_stage.html.haml'
- 'app/views/projects/cycle_analytics/_no_access.html.haml'
- 'app/views/projects/cycle_analytics/_overview.html.haml'
- 'app/views/projects/cycle_analytics/show.html.haml'
- 'app/views/projects/deploy_keys/_form.html.haml'
- 'app/views/projects/deploy_keys/_index.html.haml'
- 'app/views/projects/deploy_keys/edit.html.haml'
- 'app/views/projects/deployments/_deployment.html.haml'
- 'app/views/projects/diffs/_file_header.html.haml'
- 'app/views/projects/diffs/_replaced_image_diff.html.haml'
- 'app/views/projects/diffs/_stats.html.haml'
- 'app/views/projects/empty.html.haml'
- 'app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml'
- 'app/views/projects/hook_logs/_index.html.haml'
- 'app/views/projects/hook_logs/show.html.haml'
- 'app/views/projects/hooks/edit.html.haml'
- 'app/views/projects/imports/new.html.haml'
- 'app/views/projects/imports/show.html.haml'
- 'app/views/projects/issues/_new_branch.html.haml'
- 'app/views/projects/issues/show.html.haml'
- 'app/views/projects/jobs/_header.html.haml'
- 'app/views/projects/jobs/_table.html.haml'
- 'app/views/projects/jobs/index.html.haml'
- 'app/views/projects/labels/edit.html.haml'
- 'app/views/projects/labels/new.html.haml'
- 'app/views/projects/mattermosts/_no_teams.html.haml'
- 'app/views/projects/mattermosts/_team_selection.html.haml'
- 'app/views/projects/mattermosts/new.html.haml'
- 'app/views/projects/merge_requests/_commits.html.haml'
- 'app/views/projects/merge_requests/_mr_title.html.haml'
- 'app/views/projects/merge_requests/creations/_diffs.html.haml'
- 'app/views/projects/merge_requests/creations/_new_compare.html.haml'
- 'app/views/projects/merge_requests/creations/_new_submit.html.haml'
- 'app/views/projects/merge_requests/diffs/_different_base.html.haml'
- 'app/views/projects/merge_requests/diffs/_diffs.html.haml'
- 'app/views/projects/merge_requests/diffs/_version_controls.html.haml'
- 'app/views/projects/merge_requests/invalid.html.haml'
- 'app/views/projects/merge_requests/widget/open/_error.html.haml'
- 'app/views/projects/mirrors/_regenerate_public_ssh_key_confirm_modal.html.haml'
- 'app/views/projects/mirrors/_ssh_host_keys.html.haml'
- 'app/views/projects/no_repo.html.haml'
- 'app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml'
- 'app/views/projects/pipelines/_info.html.haml'
- 'app/views/projects/protected_branches/shared/_dropdown.html.haml'
- 'app/views/projects/protected_branches/shared/_index.html.haml'
- 'app/views/projects/protected_branches/shared/_matching_branch.html.haml'
- 'app/views/projects/protected_branches/shared/_protected_branch.html.haml'
- 'app/views/projects/protected_branches/show.html.haml'
- 'app/views/projects/protected_tags/shared/_create_protected_tag.html.haml'
- 'app/views/projects/protected_tags/shared/_dropdown.html.haml'
- 'app/views/projects/protected_tags/shared/_index.html.haml'
- 'app/views/projects/protected_tags/shared/_matching_tag.html.haml'
- 'app/views/projects/protected_tags/shared/_protected_tag.html.haml'
- 'app/views/projects/protected_tags/shared/_tags_list.html.haml'
- 'app/views/projects/protected_tags/show.html.haml'
- 'app/views/projects/registry/repositories/_tag.html.haml'
- 'app/views/projects/repositories/_feed.html.haml'
- 'app/views/projects/runners/_shared_runners.html.haml'
- 'app/views/projects/runners/edit.html.haml'
- 'app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml'
- 'app/views/projects/services/mattermost_slash_commands/_help.html.haml'
- 'app/views/projects/services/prometheus/_metrics.html.haml'
- 'app/views/projects/services/slack_slash_commands/_help.html.haml'
- 'app/views/projects/settings/ci_cd/_badge.html.haml'
- 'app/views/projects/settings/ci_cd/_form.html.haml'
- 'app/views/projects/tags/index.html.haml'
- 'app/views/projects/tags/releases/edit.html.haml'
- 'app/views/projects/tree/_tree_row.html.haml'
- 'app/views/projects/tree/_truncated_notice_tree_row.html.haml'
- 'app/views/projects/triggers/_form.html.haml'
- 'app/views/projects/triggers/_index.html.haml'
- 'app/views/projects/triggers/_trigger.html.haml'
- 'app/views/projects/triggers/edit.html.haml'
- 'app/views/search/results/_issue.html.haml'
- 'app/views/search/results/_note.html.haml'
- 'app/views/search/results/_snippet_blob.html.haml'
- 'app/views/search/results/_snippet_title.html.haml'
- 'app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml'
- 'app/views/shared/_commit_message_container.html.haml'
- 'app/views/shared/_delete_label_modal.html.haml'
- 'app/views/shared/_group_form.html.haml'
- 'app/views/shared/_group_tips.html.haml'
- 'app/views/shared/_md_preview.html.haml'
- 'app/views/shared/_milestone_expired.html.haml'
- '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/_sidebar.html.haml'
- 'app/views/shared/boards/components/sidebar/_due_date.html.haml'
- 'app/views/shared/boards/components/sidebar/_labels.html.haml'
- 'app/views/shared/boards/components/sidebar/_milestone.html.haml'
- 'app/views/shared/hook_logs/_content.html.haml'
- 'app/views/shared/issuable/_assignees.html.haml'
- 'app/views/shared/issuable/_board_create_list_dropdown.html.haml'
- 'app/views/shared/issuable/_form.html.haml'
- 'app/views/shared/issuable/_search_bar.html.haml'
- 'app/views/shared/issuable/_sidebar.html.haml'
- 'app/views/shared/issuable/form/_default_templates.html.haml'
- 'app/views/shared/issuable/form/_template_selector.html.haml'
- 'app/views/shared/issuable/form/_title.html.haml'
- 'app/views/shared/labels/_form.html.haml'
- 'app/views/shared/members/_member.html.haml'
- 'app/views/shared/milestones/_form_dates.html.haml'
- 'app/views/shared/milestones/_issuable.html.haml'
- 'app/views/shared/milestones/_milestone.html.haml'
- 'app/views/shared/milestones/_sidebar.html.haml'
- 'app/views/shared/milestones/_top.html.haml'
- 'app/views/shared/notes/_hints.html.haml'
- 'app/views/shared/runners/_runner_description.html.haml'
- 'app/views/shared/runners/show.html.haml'
- 'app/views/shared/snippets/_header.html.haml'
- 'app/views/shared/snippets/_snippet.html.haml'
- 'app/views/shared/web_hooks/_form.html.haml'
- 'app/views/shared/web_hooks/_hook.html.haml'
- 'app/views/shared/wikis/_pages_wiki_page.html.haml'
- 'app/views/users/_deletion_guidance.html.haml'
- 'ee/app/views/admin/_namespace_plan_info.html.haml'
- 'ee/app/views/admin/application_settings/_templates.html.haml'
- 'ee/app/views/admin/audit_logs/index.html.haml'
- 'ee/app/views/admin/emails/show.html.haml'
- 'ee/app/views/admin/geo/projects/_registry_failed.html.haml'
- 'ee/app/views/admin/geo/projects/_registry_never.html.haml'
- 'ee/app/views/admin/licenses/_upload_trial_license.html.haml'
- 'ee/app/views/admin/licenses/new.html.haml'
- 'ee/app/views/admin/monitoring/ee/_nav.html.haml'
- 'ee/app/views/admin/projects/_shared_runner_status.html.haml'
- 'ee/app/views/admin/users/_auditor_access_level_radio.html.haml'
- 'ee/app/views/admin/users/_auditor_user_badge.html.haml'
- 'ee/app/views/admin/users/_limits.html.haml'
- 'ee/app/views/admin/users/_user_detail_note.html.haml'
- 'ee/app/views/dashboard/projects/_blank_state_ee_trial.html.haml'
- 'ee/app/views/errors/kerberos_denied.html.haml'
- 'ee/app/views/groups/ee/_settings_nav.html.haml'
- 'ee/app/views/groups/group_members/_ldap_sync.html.haml'
- 'ee/app/views/groups/hooks/edit.html.haml'
- 'ee/app/views/groups/ldap_group_links/index.html.haml'
- 'ee/app/views/layouts/nav/ee/admin/_new_monitoring_sidebar.html.haml'
- 'ee/app/views/layouts/service_desk.html.haml'
- 'ee/app/views/ldap_group_links/_form.html.haml'
- 'ee/app/views/ldap_group_links/_ldap_group_link.html.haml'
- 'ee/app/views/ldap_group_links/_ldap_group_links.html.haml'
- 'ee/app/views/ldap_group_links/_ldap_group_links_show.html.haml'
- 'ee/app/views/namespaces/_shared_runner_status.html.haml'
- 'ee/app/views/namespaces/_shared_runners_minutes_setting.html.haml'
- 'ee/app/views/namespaces/pipelines_quota/_extra_shared_runners_minutes_quota.html.haml'
- 'ee/app/views/namespaces/pipelines_quota/_list.haml'
- 'ee/app/views/notify/approved_merge_request_email.html.haml'
- 'ee/app/views/notify/epic_status_changed_email.html.haml'
- 'ee/app/views/notify/new_review_email.html.haml'
- 'ee/app/views/notify/send_admin_notification.html.haml'
- 'ee/app/views/notify/send_unsubscribed_notification.html.haml'
- '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/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'
- 'ee/app/views/projects/protected_branches/_update_protected_branch.html.haml'
- 'ee/app/views/projects/protected_branches/ee/_create_protected_branch.html.haml'
- 'ee/app/views/projects/protected_branches/ee/_dropdown.html.haml'
- 'ee/app/views/projects/protected_tags/_protected_tag_extra_create_access_levels.haml'
- 'ee/app/views/projects/protected_tags/ee/_create_protected_tag.html.haml'
- 'ee/app/views/projects/push_rules/_index.html.haml'
- 'ee/app/views/projects/services/gitlab_slack_application/_help.html.haml'
- 'ee/app/views/projects/services/gitlab_slack_application/_slack_integration_form.html.haml'
- 'ee/app/views/projects/settings/slacks/edit.html.haml'
- 'ee/app/views/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'
- 'ee/app/views/shared/issuable/_filter_weight.html.haml'
- 'ee/app/views/shared/members/ee/_ldap_tag.html.haml'
- 'ee/app/views/shared/members/ee/_sso_badge.html.haml'
- 'ee/app/views/shared/milestones/_burndown.html.haml'
- 'ee/app/views/shared/milestones/_weight.html.haml'
- 'ee/app/views/shared/promotions/_promote_issue_weights.html.haml'
- 'ee/app/views/shared/promotions/_promote_repository_features.html.haml'
- 'ee/app/views/shared/promotions/_promote_servicedesk.html.haml'
- 'ee/app/views/shared/push_rules/_form.html.haml'
- 'ee/app/views/unsubscribes/show.html.haml'

View file

@ -629,3 +629,31 @@ Lint/RedundantSafeNavigation:
Style/ClassEqualityComparison: Style/ClassEqualityComparison:
Enabled: true Enabled: true
# WIP See https://gitlab.com/gitlab-org/gitlab/-/issues/207950
Cop/UserAdmin:
Enabled: true
Exclude:
- 'app/controllers/admin/sessions_controller.rb'
- 'app/controllers/concerns/enforces_admin_authentication.rb'
- 'app/policies/base_policy.rb'
- 'lib/gitlab/auth/current_user_mode.rb'
- 'spec/**/*.rb'
- 'ee/spec/**/*.rb'
Performance/OpenStruct:
Exclude:
- 'ee/spec/**/*.rb'
# See https://gitlab.com/gitlab-org/gitlab/-/issues/327495
Style/RegexpLiteral:
Enabled: false
Style/RegexpLiteralMixedPreserve:
Enabled: true
SupportedStyles:
- slashes
- percent_r
- mixed
- mixed_preserve
EnforcedStyle: mixed_preserve

File diff suppressed because it is too large Load diff

View file

@ -32,24 +32,6 @@ Graphql/IDType:
Layout/ArgumentAlignment: Layout/ArgumentAlignment:
Enabled: false Enabled: false
# Offense count: 11
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyleAlignWith, Severity.
# SupportedStylesAlignWith: start_of_line, begin
Layout/BeginEndAlignment:
Exclude:
- 'app/controllers/groups/shared_projects_controller.rb'
- 'app/workers/concerns/reactive_cacheable_worker.rb'
- 'ee/app/services/security/token_revocation_service.rb'
- 'ee/lib/gitlab/analytics/cycle_analytics/summary/group/deploy.rb'
- 'ee/lib/gitlab/ci/config/entry/vault/secret.rb'
- 'lib/api/internal/base.rb'
- 'lib/atlassian/jira_connect/serializers/build_entity.rb'
- 'lib/gitlab/ci/jwt.rb'
- 'lib/gitlab/external_authorization/client.rb'
- 'lib/gitlab/phabricator_import/project_creator.rb'
- 'scripts/gitaly_test.rb'
# Offense count: 54 # Offense count: 54
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: AllowAliasSyntax, AllowedMethods. # Configuration parameters: AllowAliasSyntax, AllowedMethods.
@ -94,20 +76,6 @@ Layout/LineLength:
Layout/MultilineOperationIndentation: Layout/MultilineOperationIndentation:
Enabled: false Enabled: false
# Offense count: 11
# Cop supports --auto-correct.
Layout/RescueEnsureAlignment:
Exclude:
- 'app/models/blob_viewer/dependency_manager.rb'
- 'app/models/project.rb'
- 'app/services/prometheus/proxy_service.rb'
- 'app/workers/concerns/reactive_cacheable_worker.rb'
- 'app/workers/delete_stored_files_worker.rb'
- 'config/initializers/1_settings.rb'
- 'config/initializers/trusted_proxies.rb'
- 'lib/api/internal/base.rb'
- 'lib/gitlab/highlight.rb'
# Offense count: 53 # Offense count: 53
# Cop supports --auto-correct. # Cop supports --auto-correct.
Layout/SpaceAroundMethodCallOperator: Layout/SpaceAroundMethodCallOperator:
@ -202,21 +170,6 @@ Lint/MixedRegexpCaptureTypes:
Lint/RedundantCopDisableDirective: Lint/RedundantCopDisableDirective:
Enabled: false Enabled: false
# Offense count: 9
# Cop supports --auto-correct.
# Configuration parameters: AllowedMethods.
# AllowedMethods: instance_of?, kind_of?, is_a?, eql?, respond_to?, equal?
Lint/RedundantSafeNavigation:
Exclude:
- 'app/controllers/concerns/labels_as_hash.rb'
- 'app/policies/note_policy.rb'
- 'app/services/users/update_canonical_email_service.rb'
- 'ee/app/presenters/iteration_presenter.rb'
- 'ee/app/services/ee/members/destroy_service.rb'
- 'ee/lib/ee/gitlab/email/handler/reply_processing.rb'
- 'qa/qa/specs/helpers/quarantine.rb'
- 'spec/controllers/boards/issues_controller_spec.rb'
# Offense count: 1 # Offense count: 1
Lint/SelfAssignment: Lint/SelfAssignment:
Exclude: Exclude:
@ -255,12 +208,6 @@ Metrics/CyclomaticComplexity:
Metrics/PerceivedComplexity: Metrics/PerceivedComplexity:
Max: 25 Max: 25
# Offense count: 1
# Cop supports --auto-correct.
Migration/DepartmentName:
Exclude:
- 'app/models/commit.rb'
# Offense count: 196 # Offense count: 196
# Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, Regex, IgnoreExecutableScripts, AllowedAcronyms. # Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, Regex, IgnoreExecutableScripts, AllowedAcronyms.
# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS # AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
@ -693,11 +640,6 @@ Rails/WhereEquals:
Rails/WhereExists: Rails/WhereExists:
Enabled: false Enabled: false
# Offense count: 21
# Cop supports --auto-correct.
Rails/WhereNot:
Enabled: false
# Offense count: 8 # Offense count: 8
# Cop supports --auto-correct. # Cop supports --auto-correct.
Security/YAMLLoad: Security/YAMLLoad:
@ -903,11 +845,6 @@ Style/Next:
Style/NumericLiteralPrefix: Style/NumericLiteralPrefix:
Enabled: false Enabled: false
# Offense count: 140
# Cop supports --auto-correct.
Style/ParallelAssignment:
Enabled: false
# Offense count: 2698 # Offense count: 2698
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: PreferredDelimiters. # Configuration parameters: PreferredDelimiters.
@ -922,11 +859,6 @@ Style/RaiseArgs:
Enabled: false Enabled: false
EnforcedStyle: exploded EnforcedStyle: exploded
# Offense count: 73
# Cop supports --auto-correct.
Style/RedundantAssignment:
Enabled: false
# Offense count: 2 # Offense count: 2
# Cop supports --auto-correct. # Cop supports --auto-correct.
Style/RedundantBegin: Style/RedundantBegin:
@ -951,11 +883,6 @@ Style/RedundantFetchBlock:
Style/RedundantFileExtensionInRequire: Style/RedundantFileExtensionInRequire:
Enabled: false Enabled: false
# Offense count: 248
# Cop supports --auto-correct.
Style/RedundantFreeze:
Enabled: false
# Offense count: 206 # Offense count: 206
# Cop supports --auto-correct. # Cop supports --auto-correct.
Style/RedundantInterpolation: Style/RedundantInterpolation:
@ -985,20 +912,6 @@ Style/RedundantRegexpEscape:
Style/RedundantSelf: Style/RedundantSelf:
Enabled: false Enabled: false
# Offense count: 2
# Cop supports --auto-correct.
Style/RedundantSelfAssignment:
Exclude:
- 'app/models/concerns/issuable.rb'
- 'spec/db/schema_spec.rb'
# Offense count: 213
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral:
Enabled: false
# Offense count: 53 # Offense count: 53
# Cop supports --auto-correct. # Cop supports --auto-correct.
Style/RescueModifier: Style/RescueModifier:

View file

@ -2,18 +2,834 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 13.10.4 (2021-04-27) ## 13.11.2 (2021-04-27)
### Security (6 changes) ### Security (5 changes)
- Prevent tokens with only read_api scope from executing mutations. - Prevent tokens with only read_api scope from executing mutations.
- Update mermaid to version 8.9.2.
- Do not allow deploy tokens in the dependency proxy authentication service. - Do not allow deploy tokens in the dependency proxy authentication service.
- Disable keyset pagination for branches by default. - Disable keyset pagination for branches by default.
- Bump Carrierwave gem to v1.3.2. - Bump Carrierwave gem to v1.3.2.
- Restrict setting system_note_timestamp to owners. - Restrict setting system_note_timestamp to owners.
## 13.11.1 (2021-04-22)
### Changed (1 change)
- Change unsubscribe language for email campaign on self managed. !59121
### Added (1 change)
- Add documentation about Pages deployment migration. !59475
## 13.11.0 (2021-04-22)
### Security (3 changes)
- Update to Rails v6.0.3.6. !59328
- Update mermaid to version 8.9.2.
- Allow to disable exiftool depending on env variable.
### Removed (10 changes, 1 of them is from the community)
- Redirect deprecated pipeline routes. !53990
- Remove CI lint button from Jobs page nav. !56854
- Remove graphql_individual_release_page feature flag. !56882
- Remove deprecated repository archive routes. !57236
- Remove add issues modal from issue boards (this has been disabled since 13.6). !57329
- Remove unused feature flag ':roadmap_buffered_rendering'. !57486
- Remove HipChat integration from frontend and docs. !57556
- Remove temporary index from vulnerabilities table. !57656 (Huzaifa Iftikhar @huzaifaiftikhar)
- Remove unused feature flag checks. !58469
- Remove ability to create new service templates. !58624
### Fixed (175 changes, 90 of them are from the community)
- Update gatsby project template to address the pipeline failure. !37410 (Takuya Noguchi)
- Fixed an issue where the link commit message did not end with a newline. !49086 (Kazuya Kojima)
- Partially fix incorrect icons for non-standard license files. !53207
- Add language- prefix to CSS class of markdown code blocks. !55076 (Camil Staps)
- Filter out pipelines that were excluded in the relation scope in Ci::Pipeline#latest_pipeline_per_commit. !55657 (Cong Chen @gentcys)
- Fix mermaid diagrams in dark mode. !56183
- Catch network errors. !56457 (Shubham Kumar (@imskr))
- Fix the Maven sync worker to not fail if the versionless package is not found. !56514
- Fix `#current_authenticated_job` when used with `.authenticate_with` in Grape APIs. !56564
- Move graphql timelogs to CE. !56633 (Lee Tickett @leetickett)
- Fix bug in wiki link rewriter filter. !56636
- Fix bug in Gollum Tags filter. !56638
- Fix derivation of effective permissions (access level) of group members. !56677 (Jonas Wälter @wwwjon)
- Fix word wrapping in parallel diffs. !56713
- Don't close issue label select box on click if only mouseup outside. !56721 (Simon Stieger @sim0)
- Fix reference widget icon and text spacing. !56759
- Fix test report merge request widget summary and issues alignment. !56768
- Fix artifacts section from showing up when no artifacts are present. !56784
- Push confidential_notes feature flag to mr frontend. !56798 (Lee Tickett @leetickett)
- Fixed offenses Layout/BeginEndAlignment. !56827 (Shubham Kumar (@imskr))
- Close DropLab dropdowns on click instead of mousedown. !56847 (Simon Stieger @sim0)
- Add labels to UI toggles. !56848
- Fix offense Layout/RescueEnsureAlignment. !56870 (Shubham Kumar (@imskr))
- Fixes offense Lint/RedundantSafeNavigation. !56884 (Shubham Kumar (@imskr))
- In admin new user page, fix external checkbox warning hide with keyboard interaction. !56896
- Fix Conan project-level API to return correct download-urls and fix Conan project-level functionality. !56899
- Remove Kramdown patch and update to v2.3.1 gem. !56917
- Fixed styling of commit comment buttons. !56982
- Update weight transaltion for Russian locale. !56986 (Gennady Kovalev (@belolap))
- Fixes rubocop offense Migration/DepartmentName. !56997 (Shubham Kumar (@imskr))
- Do not render empty title in HelpPopover. !57025
- Validate import manifest url scheme. !57071
- Inherit default branch name for subgroups. !57101
- Fix ruby alpine CI template. !57109
- Fix rails binding for ruby alpine template. !57112
- Update admin edit button icon class. !57151
- Fix branch switch to be exact instead of partial match. !57197
- Add aria labels to icon buttons. !57261
- Ensure search param is kept in scrolled commit. !57307
- Fix remote_mirrors usage ping metric. !57332
- Remove calls to jQuery animations to fix infinite scrolling on the Repository commits page. !57379
- Hide project-specific views on group / instance level integrations. !57381
- A blocked URL for a push mirror is a hard failure. !57392
- Fix usage data count start/finish export issue. !57403
- Fix tooltip position in mini pipeline chart. !57425
- Use search param in refs call to filter revisions. !57442
- Update the Package settings to use the blue primary button. !57468
- Always save default on empty values in Exp Policies. !57470
- Allow all file types to be uploaded from the repo file upload tool. !57498
- Fix Assignee dropdown showing assignee(s) twice. !57513
- Fix inconsistent production environment definition on VSA. !57557
- Fix namespace validation (unique path) on group creation. !57563 (Jonas Wälter @wwwjon)
- Give better feedback when quick actions have no effect. !57570 (Hilco van der Wilk)
- Fix security report fetching in Merge Requests. !57574
- Display error message when runner installation instructions modal cannot be loaded correctly. !57588
- Fix two data races in the branch names cache. !57607
- Add aria labels to icon-only buttons. !57610
- Fix Rails/SaveBang rubocop offenses in spec/controllers/projects/*. !57643 (Abdul Wadood @abdulwd)
- Fix Rails/SaveBang Rubocop offenses for admin controllers. !57644 (Huzaifa Iftikhar @huzaifaiftikhar)
- Make NuGet SearchQueryService q parameter optional. !57654 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix MR Source Branch styling. !57662
- Fix updating GraphQL boards cards on assignees update. !57687
- Revert Ignore default_enabled value in Feature.enabled?. !57707
- Simplify Build Group name correction. !57739
- Fix force_random_password option when creating Users via API. !57751
- Fix issue where merge description not showing when merged with merge train. !57787
- Covert has-tooltip on commit page to pajamas. !57858
- Fix Rails/SaveBang rubocop offenses in spec/controllers/groups*. !57879 (Abdul Wadood @abdulwd)
- Fix Rails/SaveBang Rubocop offenses for requests module. !57883 (Huzaifa Iftikhar @huzaifaiftikhar)
- Disable trigger manual job button after click. !57885
- Fix Rails/SaveBang rubocop offenses in auth controllers. !57886 (Abdul Wadood @abdulwd)
- Fix Rails/SaveBang Rubocop offenses for requests/api module. !57887 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix Rails/SaveBang Rubocop offenses for presenters. !57888 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix Rails/SaveBang rubocop offenses in profiles & projects controllers. !57890 (Abdul Wadood @abdulwd)
- Fix Rails/SaveBang rubocop offenses in spec/features/admin. !57891 (Abdul Wadood @abdulwd)
- Fix Rails/SaveBang rubocop offenses in spec/features/dashboard. !57898 (Abdul Wadood @abdulwd)
- Fix Rails/SaveBang rubocop offenses in spec/features/issues. !57900 (Abdul Wadood @abdulwd)
- Fix Rails/SaveBang rubocop offenses in spec/features/projects. !57904 (Abdul Wadood @abdulwd)
- Fix Rails/SaveBang rubocop offenses in spec/features/. !57907 (Abdul Wadood @abdulwd)
- Fix Rails/SaveBang Rubocop offenses for hooks module. !57918 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fixes rubocop offenses Style/RedundantSelfAssignment. !57920 (Shubham Kumar (@imskr))
- Fix closed icon for merge requests to match close issue icon. !57981 (jesus beltran)
- Resolves offenses Style/ParallelAssignment. !57999 (Shubham Kumar (@imskr))
- Resolves offenses Style/RedundantAssignment. !58013 (Shubham Kumar (@imskr))
- Fix Rails/SaveBang Rubocop offenses for deployment modules. !58040 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix Rails/SaveBang Rubocop offenses for mattermost modules. !58048 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix Rails/SaveBang rubocop offenses in spec/initializers. !58049 (Abdul Wadood @abdulwd)
- Fix Rails/SaveBang Rubocop offenses for issue models. !58052 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix Rails/SaveBang Rubocop offenses for legacy github import. !58054 (Huzaifa Iftikhar @huzaifaiftikhar)
- Resolves rubocop offenses Rails/WhereNot. !58062 (Shubham Kumar (@imskr))
- Fix Rails/SaveBang Rubocop offenses for markdown cache modules. !58063 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix Rails/SaveBang Rubocop offenses for commit models. !58069 (Huzaifa Iftikhar @huzaifaiftikhar)
- Only link merge requests to successful deployments. !58072
- Fix Rails/SaveBang Rubocop offenses for gitaly client models. !58089 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix Rails/SaveBang Rubocop offenses for email handlers. !58095 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix Rails/SaveBang rubocop offenses in spec/factories_spec.rb. !58102 (Abdul Wadood @abdulwd)
- Fix Rails/SaveBang Rubocop offenses for ci models. !58104 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix Rails/SaveBang Rubocop offenses for banzai modules. !58108 (Huzaifa Iftikhar @huzaifaiftikhar)
- Ensures that the "Suggest GitLab CI" popover is shown after selecting a template type. !58120
- Fix EmptyLineAfterFinalLetItBe Rubocop offenses for groups controller. !58174 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe Rubocop offenses for boards module. !58180 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses for error tracking module. !58182 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe Rubocop offenses for groups module. !58183 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses for design management module. !58189 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe Rubocop offenses for metrics module. !58190 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe Rubocop offenses for helpers. !58192 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe Rubocop offenses for api entities. !58193 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe Rubocop offenses for api helpers. !58194 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix overflow UI bug with longer commit title on Wiki Page History. !58212 (Takuya Noguchi)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/graphql/types. !58241 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/lib/banzai. !58242 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe in spec/lib/gitlab/alert_management. !58244 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/lib/gitlab/analytics. !58245 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/lib/gitlab/auth. !58246 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/lib/gitlab/checks. !58248 (Huzaifa Iftikhar @huzaifaiftikhar)
- Disable pages_serve_with_zip_file_protocol by default. !58253
- Fix EmptyLineAfterFinalLetItBe offenses spec/lib/gitlab/github_import. !58256 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/lib/gitlab/graphql. !58261 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/lib/gitlab/hook_data. !58262 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses spec/lib/gitlab/import_export. !58264 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/lib/gitlab/jira_import. !58266 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix visibility filter on explore projects page. !58293 (Jonas Wälter @wwwjon)
- Fix EmptyLineAfterFinalLetItBe in spec/lib/gitlab/phabricator_import. !58297 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/lib/gitlab. !58314 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/mailers. !58319 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/models/blob_viewer. !58325 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/models/ci. !58327 (Huzaifa Iftikhar @huzaifaiftikhar)
- Update the group permission check in packages finder helper. !58329
- Fix EmptyLineAfterFinalLetItBe offenses in spec/models/concerns. !58367 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/models/project. !58372 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/policies. !58393 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/serializers. !58406 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/services/award_emojis. !58407 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/services/boards. !58413 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe in spec/services/design_management. !58416 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/services/environments. !58418 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/services/groups. !58423 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/services/ide. !58424 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/services/issues. !58425 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fix EmptyLineAfterFinalLetItBe offenses in spec/services/merge_requests. !58429 (Huzaifa Iftikhar @huzaifaiftikhar)
- Add aria labels to icon-only buttons. !58459
- Fixes admin ci variables not showing up. !58496
- Fix previous deployment fetches wrong deployment. !58567
- Fix delete source branch status message. !58605
- Fix member autocomplete sort order. !58652
- Show bottom border on milestones sidebar widget for incident issues. !58662
- Fix project access token creation group settings link. !58686
- Avoid listing snippets through GraphQL when user profile is private. !58739
- Fix notification when new Service Desk Issue is created. !58803
- Fix dark mode colors of retried jobs in job details page. !58855
- Fix Forward Deployment Worker causes deadlock. !58861
- Fix select2 dropdowns in dark mode. !58862
- Fix badge s and borders in dark mode info wells. !58875
- Dark mode nav improvements. !58891
- Fix style for adding a related issue in free tiers. !58893 (Michael Telgkamp @michael.telgkamp)
- Fix user reference transformation in EpicsPipeline. !58913
- Avoid inflating Redis memory when aborting pipelines. !59018
- Fix sign out button in error pages. !59030
- Add aria labels to icon-only buttons. !59037
- Skip Rack Attack rate limiting for container registry event API. !59085
- Fix loading pipelines by commit SHA for GraphQL. !59110
- Drop user pipelines async when user is blocked. !59129
- IPython KaTeX rendering of comparison operators for markdown. !59132 (Reinhold Gschweicher <pyro4hell@gmail.com>)
- Fix MR diff file tree being hidden behind review bar. !59150
- Add invited group members to search results on assignees widget. !59152
- Fix tooltip not rendering. !59202
- Fix revert commit query. !59356
- Do not show sort by project in Package project page. !59367
- Return 403 status code to the Runner when CI Job is deleted. !59382
- Fix character escaping in Resolved By tooltips. !59428
- Fix Jenkins integration for GitLab FOSS. !59476
- Exclude projects dropdown from revert modal. !59504
- Ensure all tooltips appear with a 500ms delay. !59561
- Added feature flag to show/hide assignees GraphQL widget. !59620
- Fix rare race condition in GitLab-internal feature flags with database load balancing enabled.
### Deprecated (5 changes, 1 of them is from the community)
- Rename event to action in Snowplow helpers and FE event handlers. !55698
- Deprecate Product Intelligence test aggregated metrics. !57377
- Bump recommended Redis version from 4.0 to 5.0. !59072 (Takuya Noguchi)
- Deprecate Alerts for Managed Prometheus. !59433
- Deprecate assigneeUsername issue filter in GraphQL. !59538
### Changed (211 changes, 76 of them are from the community)
- Adds CI pipeline and job features to GraphQL API. !44703
- Unify the Docker Image build CI template and use the default branch instead of hardcoded 'master'. !51931 (dnsmichi)
- Update performance bar background color to use Pajamas compliant colour palette. !52775 (Yogi (@yo))
- Remove extra tooltip from pipelines overview page. !52902 (Yogi (@yo))
- Update RubyGems metadata constraints and add gem metadata extraction. !53673
- Returns deep stringified keys for merged_yaml in linting endpoint. !54336
- Add space to graph in contributor page. !54431 (Yogi (@yo))
- Move from btn-success to btn-confirm in app/views/profiles directory. !54748 (Yogi (@yo))
- Add multi-line styling within contribution tooltip. !54765 (Yogi (@yo))
- Update import statuses texts and icons. !54957
- Add branch names field to repository GraphQL type. !55074
- Remove referencing TokenWithIv model in the codebase and dynamic nonce creation feature flag. !55209
- Move to btn-confirm from btn-success in views/invites directory. !55293 (Yogi (@yo))
- Add validation for emails on push recipients. !55550
- Migrate bootstrap modal to GlModal for repo single file uploads. !55587
- Record sent in-product marketing emails and don't send the same email twice. !55840
- Alerts integration form UX cleanup. !55892
- Sync single-file mode user preference when changed from the MR cog menu checkbox. !55931
- Remove group member: add option to also remove direct user membership from subgroups and projects. !55980 (Jonas Wälter @wwwjon)
- Move to btn-danger for delete button in applications. !56088 (Yogi (@yo))
- Apply new GitLab UI for badge in starrers page. !56091 (Yogi (@yo))
- Move to btn-confirm from btn-primary in wiki empty state. !56192 (Yogi (@yo))
- Move to btn-confirm in download directory dropdown. !56193 (Yogi (@yo))
- Add btn-default class for Service Desk toggle in settings. !56195 (Yogi (@yo))
- Move to confirm variant from success in pipeline_new directory. !56199 (Yogi (@yo))
- Move to confirm varient from success in pipeline_editor directory. !56200 (Yogi (@yo))
- Move from btn-success to btn-confirm in pipeline_schedules directory. !56201 (Yogi (@yo))
- Move to confirm variant from success in feature_flags directory. !56202 (Yogi (@yo))
- Move to confirm variant from success in alert_management directory. !56206 (Yogi (@yo))
- Move from btn-success to btn-confirm in tracings directory. !56209 (Yogi (@yo))
- Move from btn-success to btn-confirm in logs directory. !56211 (Yogi (@yo))
- Move from btn-success to btn-confirm in environments directory. !56212 (Yogi (@yo))
- Move from btn-success to btn-confirm in blob directory. !56213 (Yogi (@yo))
- BulkImports: Track pipeline worker with BulkImports::Tracker#status. !56242
- Update master to main inside monitor copy. !56264
- Project Settings Operations headers Alerts/Error tracking/Jeager tracing/Jeager tracing expand/collapse on-click/on-tap. !56269 (Daniel Schömer)
- Project Settings Operations header Grafana authentication expand/collapse on-click/on-tap. !56270 (Daniel Schömer)
- Add support for commit_email to Users API. !56272
- Project Settings Operations header Incidents expand/collapse on-click/on-tap. !56273 (Daniel Schömer)
- Project Settings Operations header Metrics dashboard expand/collapse on-click/on-tap. !56274 (Daniel Schömer)
- Clean up integration form titles and password fields. !56309
- Move from btn-success to btn-confirm in branches directory. !56325 (Yogi (@yo))
- Move from btn-success to btn-confirm in cleanup directory. !56329 (Yogi (@yo))
- Move from btn-success to btn-confirm in default_branch directory. !56330 (Yogi (@yo))
- Move from btn-success to btn-confirm in deploy_keys directory. !56331 (Yogi (@yo))
- Move from btn-success to btn-confirm in forks directory. !56333 (Yogi (@yo))
- Move from btn-success to btn-confirm in hooks directory. !56334 (Yogi (@yo))
- Move from btn-success to btn-confirm in imports directory. !56336 (Yogi (@yo))
- Allow setting the shard/replica separately for standalone indexes. !56344
- Move from btn-success to btn-confirm in network directory. !56345 (Yogi (@yo))
- Move usage of delayed_project_removal to namespace settings. !56397
- Update buttons on issue page. !56425
- Create new policies for read, destroy, and create tokens. !56464
- Update Jira issues list to use new UI components. !56465
- Move from btn-success to btn-confirm in protected_branches directory. !56477 (Yogi (@yo))
- Move from btn-success to btn-confirm in protected_tags directory. !56478 (Yogi (@yo))
- Move from btn-success to btn-confirm in runners directory. !56485 (Yogi (@yo))
- Enable new RPC call to retrieve wiki files. !56491
- Center the pipeline stages dropdown in the commit details page. !56505
- Update mini pipeline appearance in commit page to match other mini pipelines in the application. !56510
- Disable pipeline schedules when a user is blocked. !56513
- Add Username to Email From Header in Notifications. !56588
- WebIDE show fork button when cannot push code. !56608
- Add empty state CTA in pipeline editor section for new root CI files. !56665
- Support newlines for the chatops "run" command. !56668
- Adds skipped state to duration cell for single stage manual pipelines. !56669
- Support for --prefer-source option for Composer registry. !56693
- Allow Email Replies to Notes to Create Discussions. !56711
- Add id and short_sha GraphQL fields to jobType in the CI namespace. !56714
- Remove the commit message from the package details UI. !56716
- Assignee dropdown in issue page displays only participants by default. !56742
- Render Kramdown format using Gitlab markup. !56750
- Relax version validation on generic packages. !56755
- Show popovers on hover and focus by default. !56778
- Change icon size in the pipeline editor. !56780
- Show password hint only if password_authentication_enabled_for_web? on new location logins. !56783 (Roger Meier)
- Add Vulnerabilities::FindingEvidence model. !56790
- Update compare branches button to btn-confirm. !56791
- Update buttons and spacing on commit page. !56793
- Update secondary nav elements right margin to 8px. !56794
- Add created_at to job webhooks. !56835
- Rename pipelines setting to CI/CD and move out from under repository section. !56857
- Change the way deprecation information is presented in GraphQL documentation. !56864
- Validate null constraint for cluster token name. !56868
- Move from btn-success to btn-confirm in projects/services directory. !56937 (Yogi (@yo))
- Move from btn-success to btn-confirm in projects/settings directory. !56938 (Yogi (@yo))
- Move from btn-success to btn-confirm in projects/snippets directory. !56939 (Yogi (@yo))
- Move from btn-success to btn-confirm in projects/tags directory. !56940 (Yogi (@yo))
- Move from btn-success to btn-confirm in projects directory. !56943 (Yogi (@yo))
- Move from btn-success to btn-confirm in registrations directory. !56944 (Yogi (@yo))
- Move from btn-success to btn-confirm in users directory. !56945 (Yogi (@yo))
- Adjust gitlab_database_transaction_seconds histogram bucket. !56952
- Add extra fields to the external pipeline validation payload. !56969
- Change assignee dropdown invite to utilize invite modal. !57002
- Enable DISTINCT optimization for ObjectHierarchy globally. !57052
- Redirect to the pipeline editor when clicking on CI/CD quick links. !57085
- Update learn gitlab template for new registrations. !57098
- Add loading icon to create merge request button. !57105
- Move Pipeline Editor repo link outside of feature flag conditional. !57144
- Show the Contribution Analytics promotion page for users without permission. !57222
- Show skipped duration state for all skipped pipelines. !57242
- Add Runner ID as title in Runner details page. !57247
- Remove feature flag usage_data_track_ci_templates_unique_projects. !57280
- Deprecate but keep support for Klar up to version 3. A new analyzer based on Trivy will be used from version 4 onwards. !57281
- Hydrate some of the variables in the Overview tab suggestion commit placeholder by switching the Diffs data source for it. !57419
- Remove Slack attachment from new issues created via Slash commands. !57431
- Make VALIDATION_REQUEST_TIMEOUT configurable. !57521
- Remove programmatic access to registration tokens. !57524
- Update Jira subscriptions list to use Vue. !57561
- Update runner badges look and feel in admin runners table. !57566
- Rename jobs to promote a smoother transition between Klar and Trivy based scanners. !57593
- Do not trim input for sample & test payload on alerts integration form. !57617
- Allow a Global ID to be used when filtering issue by iterationId in GraphQL. !57620
- Add tags field to jobType in the CI namespace. !57631
- Expose createdAt and updatedAt fields for Board in the GraphQL API. !57645
- Update validation trigger flow on the alerts integration form. !57697
- Remove groupId and projectId arguments to Runner install instructions. !57720
- GraphQL: expose milestone iid. !57732
- Move commit neighbor buttons to sticky MR controls. !57743
- Update title on revoke member invite modal and hide unneeded related issues and merge requests checkbox. !57755
- Deprecate btn-warning on admin area delete user modal. !57761
- Remove deprecated button classes from issue detail view. !57763
- Utilize btn-tertiary for copy project id on project overview. !57766
- Remove top margin for print layout. !57824
- Fail batch-aborted pipelines with reason. !57838
- Replace deprecated Close Milestone button on list view. !57871
- Replace deprecated button on new epic creation form. !57874
- Hide pipeline filtered search when no pipeline exists. !57881
- Add gl-badge for badges in group members page. !57933 (Yogi (@yo))
- Add gl-badge for badges in project members page. !57934 (Yogi (@yo))
- Display error message when dashboard activity fetch fails. !57935
- Add gl-badge for badges in dashboard nav. !57936 (Yogi (@yo))
- Update GIicon size in geo_node_header.vue. !57952 (singhanshuman)
- Move to confirm variant for buttons in vulnerabilities page. !57961 (Yogi (@yo))
- Add gl-badge for badges in MR page nav. !57969 (Yogi (@yo))
- Align project stars and date to center of project in groups page. !57972 (Yogi (@yo))
- Add btn-icon class for GPG key delete button. !57974 (Yogi (@yo))
- Add btn-default for mirror update button. !57978 (Yogi (@yo))
- Update ruby-magic-static to v0.3.5. !57984
- Reduce button size for revoke button in PAT page. !57989 (Yogi (@yo))
- Apply gl-form-input for fields in GPG keys page. !58002 (Yogi (@yo))
- Apply gl-form-input for fields in new schedule page. !58015 (Yogi (@yo))
- Move to btn-confirm from btn-success in licenses directory. !58024 (Yogi (@yo))
- Move to btn-confirm from btn-success in geo directory. !58031 (Yogi (@yo))
- Move to btn-confirm from btn-success in push_rules directory. !58033 (Yogi (@yo))
- Move to btn-confirm from btn-success in devise directory. !58035 (Yogi (@yo))
- Add btn-default class for toggle button in admin templates. !58041 (Yogi (@yo))
- Move to btn-confirm from btn-success in ee project settings. !58047 (Yogi (@yo))
- Improve UI of Runner Installation instructions: add a loading indicator, use checkmark on selected options, reduce height of modal. !58055
- Update New Issue form description copy from 'wite a comment' to 'wite a description'. !58068
- BulkImports: Import milestone iid. !58107
- Replace deprecated buttons on epic detail view. !58152
- Replace deprecated buttons on board view. !58153
- Small text updates on the SAST Config UI page. !58188
- Update GlIcon size in environments.vue. !58208 (Md. Pial Ahamed (@root.pial))
- Link to revision in version on admin dashboard. !58225 (Yogi (@yo))
- Rename Gitlab to GitLab in admin dashboard. !58228 (Yogi (@yo))
- Remove underline in apply for credit button in k8s page alert. !58232 (Yogi (@yo))
- Add btn-default class for file picker button. !58238 (Yogi (@yo))
- Rename Submit issue to Create issue in boards and docs. !58243 (Yogi (@yo))
- Update label container background and border colour from dark grey to use the same light grey as the board's containers. !58279
- UI improvement of Admin Dashboard top page. !58373 (Takuya Noguchi)
- Add warning icon beside in progress text if pipeline is stuck. !58427
- Set workhorse_extract_filename_base feature flag to default. !58504
- Update resolving alert system notes to use term Recovery Alert. !58513
- Update default spinner color to pajamas. !58517
- Update ruby-magic to v0.3.2. !58537
- Fix HAML in _promote_issue_weights.html.haml. !58546 (Yogi (@yo))
- Update popover placement and cursor on warning icon in PB. !58552 (Yogi (@yo))
- Remove vertical-align-middle from user location and work in profile. !58554 (Yogi (@yo))
- Enable chronological sort order for other items in the performance bar. !58572
- Use GlTable design system component for pipelines table. !58581
- Update MobSF to version 3.4.0 in the SAST template. !58594
- Add count of unique users to receive on-call notification to usage ping. !58606
- Add global callout for Service template deprecation. !58613
- Remove cached_api_merge_request_version feature flag. !58670
- Bump minimum git version to v2.31.0. !58737
- Add a chaos endpoint that signals QUIT. !58755
- Improve runners status icon usability and accessibility in the project settings view. !58781
- Make ref parameter optional in get raw file api. !58787
- Centralize shared state in Authoring section. !58790
- Update default branch in divergence graph. !58871
- Update Pipeline Graph Visualization. !58889
- Move initial pipeline processing to Sidekiq. !58901
- Display runner token and description consistently in the job sidebar and admin list. !58904
- Update ruby-magic to v0.4.0. !58947
- Update search and sort from the branches page. !58951
- Return email confirmation time from email entity. !58957
- Update runner type indicators in view/edit pages. !59005
- Default enable cascading settings feature flag. !59026
- Fix gl-emoji in abuse report page. !59078 (Yogi (@yo))
- Adds new clusters_integrations_prometheus table and model for Prometheus Cluster Integration. !59091
- Include project and build ID in Pages tmp directory. !59106
- Deactivate prune webhook logs worker. !59120
- Reduce pipeline tooltip delay to 0. !59155
- Remove gldropdown_branches feature flag. !59179
- Clarify on welcome page that we do not share any data. !59183
- Schedule artifact expiry backfill again. !59270
- Create prometheus service asynchronously by default when creating a project. !59273
- Show archive notice on empty project. !59286
- Enable in-product emails only for free instances. !59290
- Log all API uploads that exceed max attachment size. !59292
- Pages: Add feature flag to disable deployment to legacy storage. !59298
- Hide What's New for unauthenticated users. !59330
- Add queue label to metrics dispatched by background transaction. !59344
- Update Ruby from 2.5 to 2.7 in Dockerfile templates. !59345 (Takuya Noguchi)
- Update profile SSH key labels to refer to expired keys as "Expired". !59381
- Display project settings runners identifiers consistently. !59383
- Migrate Start Review button on MRs to use confirm variant. !59523
- Update auto-build-image to v0.6.0, updating the included docker to 20.10.6 and pack to 0.18.0. !59525
- Apply new GitLab UI for buttons in create tag page. (Yogi (@yo))
### Performance (107 changes, 1 of them is from the community)
- Cache namespace traversal path. !52854
- Use empty-query by default to check database connection. !54366 (Leandro Gomes @leandrogs)
- API JSON caching for tags endpoint. !54975
- Cache open merge requests count in group sidebar. !55971
- Add index on ci_stages to speed up batch pipeline cancellation. !56126
- Backfill traversal_ids for gitlab-org staging. !56293
- Linear version of Namespace#self_and_descendants. !56296
- Add database index for cancelable ci_pipelines on user and id. !56314
- Improve the performance of Merge Request Analytics table. !56380
- Move fetching projects and groups on todos page to API call. !56507
- Fix Workhorse acceleration for encoded project IDs in API. !56731
- Prevent sticking to DB primary when experiments are tracked. !56852
- Move link icon to CSS. !56980
- Drop unused preload from PipelineSerializer. !56988
- Speed up destroying of group Todos when user leaves group. !56995
- Optimise query for Deployment#previous_environment_deployment in LinkMergeRequestWorker. !57039
- Optimize database performance of loading assigned issue count on header bar. !57073
- Backfill traversal_ids for gitlab-org .com. !57075
- Check access only for requesting user when checking if subscribed. !57201
- Add gin index for namespaces.traversal_ids. !57207
- Accelerate uploads via API with Workhorse. !57250
- Add additional index to merge_requests table for project/status/created_at. !57267
- Preload group parent to fix N+1 queries for project search. !57277
- Preload additional data to fix N+1 queries for merge request search. !57284
- Remove N+1 for API commits/:sha/merge_requests. !57290
- Remove N+1 for API :id/deploy_keys. !57295
- Reduce query count for ExpirePipelineCacheWorker. !57304
- Remove N + 1 for milestones issues. !57349
- Add partial index to improve mirrors update. !57353
- Apply optimizations to JobsController#show.json. !57367
- Fix N+1 issue when loading merge request comments. !57374
- Perform more merge request creation tasks asynchronously to improve response times. !57453
- Fix N+1 for searching notes (comments) scope. !57460
- Resolve N + 1 for JIRA pulls. !57482
- Make `ci_runner_builds_queue_on_replicas` default. !57484
- Reduce queries on group labels controller. !57517
- Reduce number of queries in mergeRequestSetAssignees GraphQL mutation. !57523
- Reduce N+1 queries in creating todos after user mentions in a note. !57525
- Optimize Deploy Keys Presenter. !57551
- Add index to improve project deployments endpoint performance. !57554
- Resolve N + 1 for deployments API. !57558
- Cache merge request diff version API. !57568
- Reduce SQL requests number for issue links. !57602
- Avoid N+1 query when updating todo count cache. !57622
- Resolve N + 1 for commits notes API. !57641
- Resolve more N+1 issues in Jira pulls API. !57658
- Reduce number of SQL queries in Profiles::SlacksController#edit. !57674
- Preload all user callouts in a single request. !57679
- Add TargetProject And SourceBranch Index To MergeRequest. !57691
- Optimize group level Maven package finder query. !57692
- Remove ci_lower_frequency_trace_update feature flag. !57713
- Cache MRs count on milestone page. !57714
- Fix N+1 for searching milestone scope. !57715
- Avoid N+1 queries in breadcrumbs. !57725
- Move project hooks routes under /-/ scope. !57734
- Add composite index to support epic filtering by award emoji. !57759
- Reduce query count for popular worker ExpireJobCacheWorker. !57773
- Remove feature flag optimize_issue_filter_assigned_to_self. !57775
- Ensure a project iid is set before transitioning on pipeline error. !57783
- Fix N+1 in projects REST endpoint with forked projects. !57798
- Bulk-abort user pipelines on block. !57801
- Move pipelines calculation from widget.json to cached_widget.json. !57822
- Delete all issuable todos asynchronously when issuable is destroyed. !57830
- Reduce queries on projects labels controller. !57864
- Optimize database query for last deployment. !57979
- Fix N + 1 for MilestonesController#merge_requests. !57980
- Minor performance improvement for ref finder. !58099
- Reduce milestone issue list display limit to 500. !58168
- Partial index optimization for namespaces id. !58220
- Add caching to variables calculation of builds. !58286
- Reduce SQL requests on building artifacts. !58339
- Drop unused mirror_data index. !58349
- Add index on file_store for pages_deployments table. !58355
- Eliminage N+1 database queries on the user notifications page. !58397
- Create finder for searching branch names via redis. !58439
- Preload associations in Ci::Pipeline#cancel_running. !58484
- Add new MergeRequests::SyncCodeOwnerApprovalRulesWorker. !58512
- Create the pipelines asynchronously when refreshing merge requests. !58542
- Optimize searching cherry-picked merge requests for linking deployments. !58568
- Use object quarantine directory to enumerate new LFS pointers. !58634
- Resolve merge request todos asynchronously on update. !58647
- Enable cached avatar lookups by email. !58659
- Resolve group_member policy n+1. !58668
- Move CI related paths to cached MR widget. !58711
- Fix N+1 in REST projects and service desk. !58747
- Optimize environment serializer to reduce N+1 problems. !58748
- Handle assignee changes side effects asynchronously. !58783
- Remove paths from BuildArtifactEntity. !58818
- Use fast path helpers in BuildDetailsEntity. !58824
- Add framework for using specialized services to improve performance of MergeRequests::UpdateService. !58836
- Fix N+1 for searching commits. !58867
- Fix N+1 queries to find or initialize services. !58879
- Adjust indices to improve query performance for notification_settings. !58895
- Fix N+1 queries for issues search. !58915
- Optimize query for cherry picked merge requests. !58967
- Cache issues count in sidebar at group level. !59004
- Improve performance by moving TODO creation out of the jobs/request path. !59022
- Eliminate N+1 database queries on the user notifications page within the project notifications section. !59029
- Add migration to index members on user_id, source_id, and source_type. !59051
- Reduce the number of SQL queries executed on Maven file API endpoints. !59136
- Add user index on spam logs. !59151
- Limit number of GraphQL requests tracked in performance bar to 10. !59158
- Add index for the path column on the packages_maven_metadata table. !59241
- Reduce timeouts on tab counts for searches to 5s. !59435
- Add partial index on members to optimize highest access level query. !59455
- Optimize issuable updates. !59468
- Ensure the project iid is set before dropping pipeline. !59626
### Added (108 changes, 11 of them are from the community)
- Support adding and removing assignees w/ push opts. !25904
- Add Go Packages as a cache for the Go proxy. !34558 (Ethan Reesor (@firelizzard))
- Allow admin users to define admin notes on groups. !47825
- Resolve nested variable values sent to the runner. !48627
- Hide "Resolve conflicts" button when source branch is protected. !51121 (Marcin Majkowski @marcinmajkowski)
- Allow Add Comment To Review. !51718 (Lee Tickett @leetickett)
- Add click to copy button over project ID. !53224 (Virgile MATHIEU @vmathieu)
- Convert admin mode feature flag to system application setting. !53610 (Diego Louzán)
- Send in-product marketing emails to guide users setting up their groups. !53715
- Automatically try to migrate gitlab pages to zip storage. !54578
- Add user-merge request interaction type. !54588
- Save usage_data_id from versions app in raw_usage_data. !54738
- Create UserPreferences API. !55033
- Support group applications. !55152 (Jonas Wälter @wwwjon, Bastian Blank)
- Ability to add Prometheus as cluster integration. !55244
- Add JavaScript, TypeScript, and React support to the semgrep analyzer. !55257
- Added local_store to Pages settings in gitlab.yml file. !55470
- Add additional fields to dast_site_profiles database table. !55579
- Cascade delayed project removal setting lookup to parent namespace. !55678
- Support automatic transitions of Jira issues. !55773
- Add blocked issues detail popover for boards cards. !55821
- Allow users to mark pages projects as not deployed during migration to zip storage. !55862
- Add dast_profile_secret_variables table. !56067
- Support daily DORA metrics API. !56080
- Track agent token last_used. !56143
- Add CI_COMMIT_AUTHOR predefined variable. !56144 (Craig Andrews @candrews)
- Linking to a single line number in Web IDE. !56159
- Migrate group badges when using Bulk Import. !56357
- Add Ability to Edit Freeze Periods. !56407
- Add GraphQL mutation to delete an existing release asset link. !56417
- Personal access token revoke for managed accounts (feature flag removed). !56427
- Migration: add trial extension type to gitlab_subscription. !56460
- Geo: Prepare snippet_repositories and snippet_repository_registry tables for adding verification. !56596
- User Availability - Allow users to schedule un-setting of their status values. !56649
- Add missing icon for files with .c++ extension. !56650 (Peter Kovář @peter.kovar)
- Add in-page search for all settings pages. !56659
- Support include_ancestors when querying group milestones via GraphQL. !56667
- Add recaptcha to top-level group creation behind feature flag. !56707
- Configure issue and merge request description templates at group level and rolldown description templates in the group hierarchy. !56737
- Enabled phabricator importer by default. !56765
- Generalize alert details status. !56800
- Create database table dast_profiles_pipelines. !56821
- Allow selecting a CI template by providing the template name as a URL param gitlab_ci_yml. !56861
- Group SAML - Check SSO status on Git activity. !56867
- Send email notification on SSH key expiration. !56888
- Support custom tag formats for changelogs. !56889
- Delete records from security_findings table with missing UUID values. !56975
- Link squashed commits using the changelog API. !56985
- Allow users to enable force push to protected branches. !57053
- Add rake tasks for Pages deployment migration. !57120
- Code suggestions correctly add based on multi-line comments. !57125
- BulkImports: Add `BulkImports::PipelineWorker` to process each BulkImport pipeline on its own background job. !57153
- Connect Registries searches to URL. !57251
- Sort code quality degradations in MR Widget comparison reports. !57258
- Add unified metrics definition YAML file API endpoint. !57270
- Clarify what coverage means on the merge request pipeline section. !57275
- Improve payload format of DORA metrics API. !57314
- Expose timelogs against issues and merge requests in GraphQL. !57321 (Lee Tickett @leetickett)
- Populate missing dismissal information for vulnerabilities. !57347
- Clarify the impact of selecting incidents in the new issue form. !57373
- Add jobs field to the project type. !57376
- When removing a user, warn Admin user is part of an on-call schedule. !57397
- Exposes schedulingType on CiJobType and adds usesNeeds to PipelineType. !57398
- '/projects/:id/repository/compare' supports comparing branches/commits on different projects. !57418 (Exchizz (@Exchizz))
- Add geo database changes for pipeline artifact replication. !57506
- Add more fields to the job type. !57530
- Capture test report summary widget views via usage ping. !57543
- Allow filtering GraphQL alertManagementIntegrations and alertManagementHttpIntegrations by ID. !57590
- Add search functionality to Jira Connect App namespaces. !57669
- Add Conan GraphQL type to package. !57719
- Log message when upload via API exceeds limit. !57774
- Migration: Add cloud column to licenses. !57781
- Re-add swap revisions feature (legacy). !57802
- Add support for SMTP connection pooling when sending emails. !57805
- Add a migration to insert trail plans within SAAS for Ultimate and Premium plans. !57814
- Add link to test case file in the test report for merge requests. !57911
- Upgrade GitLab Pages to v1.37.0. !57946
- Add negative filters for merge requests API. !58021
- Add setting to change default target project for merge requests from forks. !58093
- Support negated filtering of issues by iids, label_name, milestone_title, assignee_usernames and assignee_id in GraphQL. !58154
- User notification when SSH key is set to expire soon. !58171
- Allow user to filter epics by their reaction emoji via GraphQL. !58211
- Add config support for using Microsoft Graph with MailRoom. !58250
- Let users create groups and projects at signup and onboard them through issues on gitlab.com. !58301
- Reschedule background migration to copy projects.container_registry_enabled to project_features.container_registry_access_level. !58360
- Prettify JSON of sample alert payload. !58433
- Add spent quick action alias. !58539 (Lee Tickett @leetickett)
- Add GraphQL endpoint for test report summary for pipelines. !58596
- Show pipeline finished timestamp on MR widget. !58618
- Add Hello World CI Template. !58649
- Make blobs directly accessible through the graphql repository. !58677
- Add target_type column to dast_site_profiles database table. !58723
- Add GraphQL endpoint for a specific test suite in pipelines. !58924
- Add blob filename to attachment content disposition. !58977
- Rollout product_intelligence_metrics_names_suggestions feature flag. !58995
- Support filtering by assignee wildcard in GraphQL board list issues query. !58996
- Remove pages_serve_from_migrated_zip feature flag. !59002
- Enables multiple_cache_per_job feature flag by default. !59016
- Add CODECLIMATE_PREFIX variable to code quality template. !59041
- Add instance_url column to the jira_connect_installations table. !59148
- Remove codequality_backend_comparison feature flag. !59320
- Allow cherry-picking to a fork's parent. !59399
- Add kotlin support to spotbugs-sast job. !59431
- Upgrade GitLab Pages to 1.38.0. !59464
- Add documentation about Pages deployment migration. !59475
- Re-enable serving pages with zip file protocol. !59486
- Enable pipeline_status_for_pipeline_editor by default. !59495
- Extract creation of prometheus service from Projects::CreateService.
### Other (160 changes, 74 of them are from the community)
- Resolve Improve text for error No issue found for given params in UI. !45064
- Update gon gem to 6.4.0. !51210
- Initialize conversion of events.id to bigint, and add execute_batched_migrations_on_schedule feature flag to control scheduled background migrations. !51332
- Apply new GitLab UI buttons in the webhooks list. !51977 (Yogi (@yo))
- Fix alignment of folder-caret and actions button in the subgroup list. !52400 (Yogi (@yo))
- Remove JSON endpoint for project container index. !52407 (Takuya Noguchi)
- Update HIPAA logo for project templates. !53270
- Apply GitLab UI button styles to buttons in app/views/shared directory. !53474 (Yogi (@yo))
- Drop non-partitioned audit_events_archived table. !53880
- Add message for repository backup skip. !54285
- Updated MR Approvals to specify settings section. !54985
- Remove markdown from comment search result. !55255
- Deduplicate issue_metrics table. !55285
- Document how to use custom omniauth button icon. !55388 (Diego Louzán)
- Create Cop to enforce using policies framework for administrators. !55693 (Diego Louzán)
- Remove tabindex on skip link that could negatively impact keyboard focus management and order. !55756
- Mark merge request as preparing on create. !56086
- Update Search and Apply buttons to confirm variant to align with Pajamas design system. !56122
- Decrease spacing between controls on the Commit page header. !56129
- Create new unit test tables. !56137
- Convert Commit dropdown to Vue. !56142
- Enable the instance variables UI. !56255
- Set the scope in search context from group issue and MR pages. !56383
- Remove On-call Edit feature flag. !56445
- Fix cop offenses for Style/HashTransformation in app directory. !56579 (Karthik Sivadas @karthik.sivadas)
- Fix cop offenses for Style/HashTransformation in ee directory. !56581 (Karthik Sivadas @karthik.sivadas)
- Fix cop offenses for Style/HashTransformation in lib directory. !56583 (Karthik Sivadas @karthik.sivadas)
- Fix cop offenses for Style/HashTransformation in spec directory. !56586 (Karthik Sivadas @karthik.sivadas)
- Track epic note created via usage ping. !56609
- Aggregate code review metrics. !56734
- Update android template to default branch. !56738
- Stop using json-schema gem for production. !56745
- Refactor docs and UI for Jaeger tracing. !56819
- Add support for the MATERIALIZED keyword when using WITH (CTE) queries in PostgreSQL 12. !56976
- Externalize project deploy keys (edit) strings. !57015 (Jonston Chan @JonstonChan)
- Migrates the expand button in MR reports to GitLab UI. !57021
- Update GitLab Runner Helm Chart to 0.27.0. !57048
- Remove unnecessary use of freeze. !57056 (Lee Tickett @leetickett)
- Remove unnecessary use of freeze. !57057 (Lee Tickett @leetickett)
- Remove unnecessary use of freeze. !57058 (Lee Tickett @leetickett)
- Remove unnecessary use of freeze. !57059 (Lee Tickett @leetickett)
- Remove unnecessary use of freeze. !57060 (Lee Tickett @leetickett)
- Remove the FF skip_dag_manual_and_delayed_jobs. !57086
- Remove the FF ci_trigger_payload_into_pipeline. !57087
- Updated documented K8s snippet to undeprecated API. !57100 (Raimund Hook (@stingrayza))
- Validate NOT NULL constraint on gitlab_subscriptions namespace_id. !57113
- Update button variants on the project boards controller to better align with the Pajamas Design System. !57129
- Remove the recursive_namespace_lookup_as_inner_join feature flag. !57131
- Only display focus mode button at md+ breakpoint and make it the tertiary style. !57139
- Remove feature flag for customize homepage banner. !57147
- Update issuable submit content order, button variants, and button alignment. !57172
- Send invited users to sign up instead of sign in when possible. !57240
- Updated UI text to match style guidelines. !57276
- Enable RedundantFreeze Cop and Remove Remaining Offenses. !57288 (Lee Tickett @leetickett)
- Review and revise Integrations/Asana UI text. !57362
- Add enqueueing of Onboarding Progress to the Invite Service. !57372
- Validate foreign key on ServiceHooks. !57483
- Removed migrate_delayed_project_removal feature flag. !57541
- Migration to cleanup after partitioned web_hook_logs backfill. !57580
- Update BulkImport default page size to 500 in order to process larger page of data. !57594
- Refactor member/invitation services to share common code. !57618
- Fix triggers page externalization. !57637 (Jonston Chan @JonstonChan)
- Add foreign key from web_hooks to groups. !57735
- Remove batch_suggestions feature flag. !57745
- Remove remove_resolve_note feature flag. !57757
- Remove deprecated info button from issue list view. !57762
- Track the different overflows for diff collections. !57790
- Update Jira plugin UI copy. !57793 (Russell Dickenson rdickenson@gitlab.com)
- Rename table/model vulnerability_finding_fingerprints to *_signatures. !57840
- Move to btn-confirm from btn-success in pipelines quotas page. !57861 (Yogi (@yo))
- Remove records without group from webhooks table. !57863
- Updated UI text to match style guidelines. !57884
- Add a template for using Indeni Cloudrail in GitLab. !57919
- Externalise-strings in _ip_limits.html.haml. !58003 (nuwe1)
- Externalise strings in application_settings/_pages.html.haml. !58011 (nuwe1)
- Externalize strings in _performance.html.haml. !58016 (nuwe1)
- Externalise strings in application_settings/_performance_bar.html.haml. !58018 (nuwe1)
- Externalise strings in /application_settings/_realtime.html.haml. !58039 (nuwe1)
- Externalise strings in _registry.html.haml. !58051 (nuwe1)
- Externalise strings in /application_settings/_repository_check.html.haml. !58058 (nuwe1)
- Update Design Management added design icon to be slightly smaller which conforms to the Pajamas design guide. !58086 (Andreas Resch @reschandreas)
- Externalise strings in admin/users/_head.html.haml. !58101 (nuwe1)
- Updating success button to confirm variant and reordering buttons per Pajamas Design System guidelines for buttons. !58112
- Externalize strings in /abuse_reports/index.html.haml. !58132 (nuwe1)
- Use Gitlab::AppLogger in settings. !58134 (Huzaifa Iftikhar @huzaifaiftikhar)
- Fill in all placeholder values in the apply suggestion commit message placeholder text. !58136
- Externalize strings in broadcast_messages/index.html.haml. !58146 (nuwe1)
- Externalize strings in deploy_keys/new.html.haml. !58148 (nuwe1)
- Externalize strings in hook_logs/_index.html.haml. !58155 (nuwe1)
- Externalize strings in projects/_projects.html.haml. !58158 (nuwe1)
- Externalize strings in projects/index.html.haml. !58160 (nuwe1)
- Externalize strings in services/index.html.haml. !58167 (nuwe1)
- Externalise strings in runners/_runner.html.haml. !58168 (nuwe1)
- Externalise strings in spam_logs/_spam_log.html.haml. !58169 (nuwe1)
- Fix EmptyLineAfterFinalLetItBe Rubocop offenses for projects controller. !58176 (Huzaifa Iftikhar @huzaifaiftikhar)
- Externalize strings in _confirmation_instructions_account.html.haml. !58214 (nuwe1)
- Externalize strings in _confirmation_instructions_account.text.erb. !58215 (nuwe1)
- Externalize strings in _confirmation_instructions_secondary.text.erb. !58218 (nuwe1)
- Externalise strings in password_change files. !58219 (nuwe1)
- Externalize strings in unlock_instructions.html.haml. !58227 (nuwe1)
- Externalize strings in passwords/edit.html.haml. !58233 (nuwe1)
- Externalize strings in passwords/new.html.haml. !58236 (nuwe1)
- Externalize strings in sessions/_new_ldap.html.haml. !58267 (nuwe1)
- Externalize strings in registrations/edit.html.erb. !58268 (nuwe1)
- Externalize strings in sessions/_new_crowd.html.haml. !58269 (nuwe1)
- Externalise strings in sessions/new.html.haml. !58274 (nuwe1)
- Externalize strings in sessions/two_factor.html.haml. !58275 (nuwe1)
- Externalize strings in shared/_omniauth_box.html.haml. !58281 (nuwe1)
- Externalize strings in shared/_sign_in_link.html.haml. !58283 (nuwe1)
- Externalise strings in shared/_tabs_ldap.html.haml. !58285 (nuwe1)
- Externalize strings in unlocks/new.html.haml. !58289 (nuwe1)
- Externalise strings in labels/edit.html.haml. !58294 (nuwe1)
- Externalize strings in milestones/_form.html.haml. !58298 (nuwe1)
- Externalize strings in milestones/edit.html.haml. !58306 (nuwe1)
- Externalise strings in runners/edit.html.haml. !58315 (nuwe1)
- Externalise strings in groups/_activities.html.haml. !58324 (nuwe1)
- Externalize strings in groups/_create_chat_team.html.haml. !58328 (nuwe1)
- Externalise strings in groups/_group_admin_settings.html.haml. !58331 (nuwe1)
- Externalises strings in groups/activity.html.haml. !58332 (nuwe1)
- Update pipeline email service UI text. !58377
- Update pot file. !58392
- Updated UI text for Assembla integration to match style guidelines. !58400
- Externalize strings in instance_configuration/_gitlab_ci.html.haml. !58435 (nuwe1)
- Externalize strings in instance_configuration/_gitlab_pages.html.haml. !58437 (nuwe1)
- Externalize strings in help/index.html.haml. !58441 (nuwe1)
- Externalize strings in instance_configuration.html.haml. !58443 (nuwe1)
- Externalize strings in chat_names/_chat_name.html.haml. !58444 (nuwe1)
- Externalizes strings in viewers/_empty.html.haml. !58451 (nuwe1)
- Externalize strings in viewers/_loading_auxiliary.html.haml. !58454 (nuwe1)
- Review and revise Pages settings-related UI text. !58479
- Updated Alert integration UI text to match style guidelines. !58507
- If creating a new issue fails in boards, remove the issue card from a list. !58558
- Enable Layout/SpaceAfterColon cop for HAML. !58564 (Takuya Noguchi)
- Update mattermost integration UI text. !58570
- Update Emails on push UI Text to match style guidelines. !58597
- Updated outdated UI text and docs. !58600
- Update UI text of Jenkins integration. !58623
- Track total_tuple_count for batched migrations. !58675
- Update Project Management metrics definitions. !58710
- Add correlation id in X-Request-ID for external pipeline validation. !58741
- Update UI text from timing to Duration. !58838
- Update Discord integration UI text. !58842
- Update UI text for slack notifications integration. !58845
- Review and revise Redmine Integration UI text. !58899
- Bump devise-two-factor version. !58929
- Update metric definition under verify testing group. !59028
- Remove issue_perform_after_creation_tasks_async feature flag. !59042
- Obtain pipeline validation service token from config not ENV. !59101
- Bump rspec-rails to 4.1.2. !59130
- Add index services on project and type where inherit is null. !59168
- Replace deprecated skeleton loader in the user popover with slightly darker SVG based skelton loader. !59180
- Bump rspec-rails to 5.0.1. !59194
- Update drone integration UI text. !59231
- Add index on (created_at, web_hook_id) to the partitioned web_hook_logs. !59261
- Add index on web_hook_id to partitioned web_hook_logs. !59266
- Add a foreign key from the partitioned web_hook_logs to web_hooks. !59282
- Bump minimum required Go version for workhorse to 1.15. !59347
- Update UI text for TeamCity integration. !59493
- Remove redundant index from epics. !59494
- Externalize strings in labels/new.html.haml. (nuwe1)
## 13.10.3 (2021-04-13) ## 13.10.3 (2021-04-13)
### Security (3 changes) ### Security (3 changes)
@ -583,6 +1399,43 @@ entry.
- Convert mattermost alert to pajamas. !56556 - Convert mattermost alert to pajamas. !56556
## 13.9.6 (2021-04-13)
### Security (2 changes)
- Clean only legitimate JPG and TIFF files.
- Update ruby-saml and rexml gems.
## 13.9.5 (2021-03-31)
### Security (6 changes)
- Leave pool repository on fork unlinking.
- Fixed XSS in merge requests sidebar.
- Fix arbitrary read/write in AsciiDoctor and Kroki gems.
- Prevent infinite loop when checking if collaboration is allowed.
- Disable arbitrary URI and file reads in JSON validator.
- Require POST request to trigger system hooks.
### Removed (1 change)
- Make HipChat project service do nothing. !57434
### Other (3 changes)
- Remove direct mimemagic dependency. !57387
- Refactor MimeMagic calls to new MimeType class. !57421
- Switch to using a fake mimemagic gem. !57443
## 13.9.4 (2021-03-17)
### Security (1 change)
- Patch Kramdown syntax highlighter gem.
## 13.9.3 (2021-03-08) ## 13.9.3 (2021-03-08)
### Fixed (4 changes) ### Fixed (4 changes)
@ -1191,6 +2044,42 @@ entry.
- Apply new GitLab UI for buttons in pipeline schedules. - Apply new GitLab UI for buttons in pipeline schedules.
## 13.8.8 (2021-04-13)
### Security (2 changes)
- Clean only legitimate JPG and TIFF files.
- Update ruby-saml and rexml gems.
## 13.8.7 (2021-03-31)
### Security (5 changes)
- Fixed XSS in merge requests sidebar.
- Leave pool repository on fork unlinking.
- Fix arbitrary read/write in AsciiDoctor and Kroki gems.
- Prevent infinite loop when checking if collaboration is allowed.
- Require POST request to trigger system hooks.
### Removed (1 change)
- Make HipChat project service do nothing. !57434
### Other (3 changes)
- Remove direct mimemagic dependency. !57387
- Refactor MimeMagic calls to new MimeType class. !57421
- Switch to using a fake mimemagic gem. !57443
## 13.8.6 (2021-03-17)
### Security (1 change)
- Patch Kramdown syntax highlighter gem.
## 13.8.5 (2021-03-04) ## 13.8.5 (2021-03-04)
### Security (6 changes) ### Security (6 changes)
@ -1603,6 +2492,13 @@ entry.
- Add verbiage + link sast to show it's in core. !51935 - Add verbiage + link sast to show it's in core. !51935
## 13.7.9 (2021-03-17)
### Security (1 change)
- Patch Kramdown syntax highlighter gem.
## 13.7.8 (2021-03-04) ## 13.7.8 (2021-03-04)
### Security (5 changes) ### Security (5 changes)

View file

@ -1 +1 @@
13.10.4 13.11.2

View file

@ -1 +1 @@
13.9.1 13.11.1

View file

@ -1 +1 @@
1.36.0 1.38.0

50
Gemfile
View file

@ -2,7 +2,7 @@
source 'https://rubygems.org' source 'https://rubygems.org'
gem 'rails', '~> 6.0.3.1' gem 'rails', '~> 6.0.3.6'
gem 'bootsnap', '~> 1.4.6' gem 'bootsnap', '~> 1.4.6'
@ -61,7 +61,7 @@ gem 'akismet', '~> 3.0'
gem 'invisible_captcha', '~> 1.1.0' gem 'invisible_captcha', '~> 1.1.0'
# Two-factor authentication # Two-factor authentication
gem 'devise-two-factor', '~> 3.1.0' gem 'devise-two-factor', '~> 4.0.0'
gem 'rqrcode-rails3', '~> 0.1.7' gem 'rqrcode-rails3', '~> 0.1.7'
gem 'attr_encrypted', '~> 3.1.0' gem 'attr_encrypted', '~> 3.1.0'
gem 'u2f', '~> 0.2.1' gem 'u2f', '~> 0.2.1'
@ -110,7 +110,7 @@ gem 'hashie-forbidden_attributes'
gem 'kaminari', '~> 1.0' gem 'kaminari', '~> 1.0'
# HAML # HAML
gem 'hamlit', '~> 2.14.4' gem 'hamlit', '~> 2.15.0'
# Files attachments # Files attachments
gem 'carrierwave', '~> 1.3' gem 'carrierwave', '~> 1.3'
@ -152,7 +152,7 @@ gem 'deckar01-task_list', '2.3.1'
gem 'gitlab-markup', '~> 1.7.1' gem 'gitlab-markup', '~> 1.7.1'
gem 'github-markup', '~> 1.7.0', require: 'github/markup' gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'commonmarker', '~> 0.21' gem 'commonmarker', '~> 0.21'
gem 'kramdown', '~> 2.3.0' gem 'kramdown', '~> 2.3.1'
gem 'RedCloth', '~> 4.3.2' gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~> 6.1.2' gem 'rdoc', '~> 6.1.2'
gem 'org-ruby', '~> 0.9.12' gem 'org-ruby', '~> 0.9.12'
@ -200,7 +200,7 @@ gem 'acts-as-taggable-on', '~> 7.0'
gem 'sidekiq', '~> 5.2.7' gem 'sidekiq', '~> 5.2.7'
gem 'sidekiq-cron', '~> 1.0' gem 'sidekiq-cron', '~> 1.0'
gem 'redis-namespace', '~> 1.7.0' gem 'redis-namespace', '~> 1.7.0'
gem 'gitlab-sidekiq-fetcher', '0.5.5', require: 'sidekiq-reliable-fetch' gem 'gitlab-sidekiq-fetcher', '0.5.6', require: 'sidekiq-reliable-fetch'
# Cron Parser # Cron Parser
gem 'fugit', '~> 1.2.1' gem 'fugit', '~> 1.2.1'
@ -276,10 +276,7 @@ gem 'licensee', '~> 9.14.1'
gem 'charlock_holmes', '~> 0.7.7' gem 'charlock_holmes', '~> 0.7.7'
# Detect mime content type from content # Detect mime content type from content
gem 'ruby-magic-static', '~> 0.3.4' gem 'ruby-magic', '~> 0.4'
# Fake version of the gem to trick bundler
gem 'mimemagic', '0.3.7', path: 'vendor/shims/mimemagic', require: false
# Faster blank # Faster blank
gem 'fast_blank' gem 'fast_blank'
@ -296,11 +293,11 @@ gem 'terser', '1.0.2'
gem 'addressable', '~> 2.7' gem 'addressable', '~> 2.7'
gem 'gemojione', '~> 3.3' gem 'gemojione', '~> 3.3'
gem 'gon', '~> 6.2' gem 'gon', '~> 6.4.0'
gem 'request_store', '~> 1.5' gem 'request_store', '~> 1.5'
gem 'base32', '~> 0.3.0' gem 'base32', '~> 0.3.0'
gem "gitlab-license", "~> 1.3" gem "gitlab-license", "~> 1.4"
# Protect against bruteforcing # Protect against bruteforcing
gem 'rack-attack', '~> 6.3.0' gem 'rack-attack', '~> 6.3.0'
@ -314,7 +311,7 @@ gem 'pg_query', '~> 1.3.0'
gem 'premailer-rails', '~> 1.10.3' gem 'premailer-rails', '~> 1.10.3'
# LabKit: Tracing and Correlation # LabKit: Tracing and Correlation
gem 'gitlab-labkit', '~> 0.16.1' gem 'gitlab-labkit', '~> 0.16.2'
# Thrift is a dependency of gitlab-labkit, we want a version higher than 0.14.0 # Thrift is a dependency of gitlab-labkit, we want a version higher than 0.14.0
# because of https://gitlab.com/gitlab-org/gitlab/-/issues/321900 # because of https://gitlab.com/gitlab-org/gitlab/-/issues/321900
gem 'thrift', '>= 0.14.0' gem 'thrift', '>= 0.14.0'
@ -345,13 +342,12 @@ group :metrics do
end end
group :development do group :development do
gem 'brakeman', '~> 4.2', require: false gem 'lefthook', '~> 0.7.0', require: false
gem 'lefthook', '~> 0.7', require: false
gem 'letter_opener_web', '~> 1.3.4' gem 'letter_opener_web', '~> 1.4.0'
# Better errors handler # Better errors handler
gem 'better_errors', '~> 2.7.1' gem 'better_errors', '~> 2.9.0'
# thin instead webrick # thin instead webrick
gem 'thin', '~> 1.8.0' gem 'thin', '~> 1.8.0'
@ -368,7 +364,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.7.0' gem 'database_cleaner', '~> 1.7.0'
gem 'factory_bot_rails', '~> 6.1.0' gem 'factory_bot_rails', '~> 6.1.0'
gem 'rspec-rails', '~> 4.0.2' gem 'rspec-rails', '~> 5.0.1'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.11.0' gem 'minitest', '~> 5.11.0'
@ -379,14 +375,14 @@ group :development, :test do
gem 'spring', '~> 2.1.0' gem 'spring', '~> 2.1.0'
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'gitlab-styles', '~> 6.1.0', require: false gem 'gitlab-styles', '~> 6.2.0', require: false
gem 'haml_lint', '~> 0.36.0', require: false gem 'haml_lint', '~> 0.36.0', require: false
gem 'bundler-audit', '~> 0.7.0.1', require: false gem 'bundler-audit', '~> 0.7.0.1', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false gem 'benchmark-ips', '~> 2.3.0', require: false
gem 'knapsack', '~> 1.17' gem 'knapsack', '~> 1.21.1'
gem 'crystalball', '~> 0.7.0', require: false gem 'crystalball', '~> 0.7.0', require: false
gem 'simple_po_parser', '~> 1.1.2', require: false gem 'simple_po_parser', '~> 1.1.2', require: false
@ -398,11 +394,12 @@ group :development, :test do
gem 'parallel', '~> 1.19', require: false gem 'parallel', '~> 1.19', require: false
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
gem 'test_file_finder', '~> 0.1.3'
end end
group :development, :test, :danger do group :development, :test, :danger do
gem 'danger-gitlab', '~> 8.0', require: false gem 'gitlab-dangerfiles', '~> 1.1.1', require: false
gem 'gitlab-dangerfiles', '~> 0.8.0', require: false
end end
group :development, :test, :coverage do group :development, :test, :coverage do
@ -416,6 +413,7 @@ group :development, :test, :omnibus do
end end
group :test do group :test do
gem 'json-schema', '~> 2.8.0'
gem 'fuubar', '~> 2.2.0' gem 'fuubar', '~> 2.2.0'
gem 'rspec-retry', '~> 0.6.1' gem 'rspec-retry', '~> 0.6.1'
gem 'rspec_profiling', '~> 0.0.6' gem 'rspec_profiling', '~> 0.0.6'
@ -477,11 +475,11 @@ group :ed25519 do
end end
# Gitaly GRPC protocol definitions # Gitaly GRPC protocol definitions
gem 'gitaly', '~> 13.9.0.pre.rc1' gem 'gitaly', '~> 13.11.0.pre.rc1'
gem 'grpc', '~> 1.30.2' gem 'grpc', '~> 1.30.2'
gem 'google-protobuf', '~> 3.12' gem 'google-protobuf', '~> 3.14.0'
gem 'toml-rb', '~> 1.0.0' gem 'toml-rb', '~> 1.0.0'
@ -490,7 +488,7 @@ gem 'flipper', '~> 0.17.1'
gem 'flipper-active_record', '~> 0.17.1' gem 'flipper-active_record', '~> 0.17.1'
gem 'flipper-active_support_cache_store', '~> 0.17.1' gem 'flipper-active_support_cache_store', '~> 0.17.1'
gem 'unleash', '~> 0.1.5' gem 'unleash', '~> 0.1.5'
gem 'gitlab-experiment', '~> 0.5.0' gem 'gitlab-experiment', '~> 0.5.3'
# Structured logging # Structured logging
gem 'lograge', '~> 0.5' gem 'lograge', '~> 0.5'
@ -513,16 +511,16 @@ gem 'erubi', '~> 1.9.0'
# Monkey-patched in `config/initializers/mail_encoding_patch.rb` # Monkey-patched in `config/initializers/mail_encoding_patch.rb`
# See https://gitlab.com/gitlab-org/gitlab/issues/197386 # See https://gitlab.com/gitlab-org/gitlab/issues/197386
gem 'mail', '= 2.7.1' gem 'mail', '= 2.7.1'
gem 'mail-smtp_pool', '~> 0.1.0', path: 'vendor/gems/mail-smtp_pool', require: false
# File encryption # File encryption
gem 'lockbox', '~> 0.3.3' gem 'lockbox', '~> 0.6.2'
# Email validation # Email validation
gem 'valid_email', '~> 0.1' gem 'valid_email', '~> 0.1'
# JSON # JSON
gem 'json', '~> 2.3.0' gem 'json', '~> 2.3.0'
gem 'json-schema', '~> 2.8.0'
gem 'json_schemer', '~> 0.2.12' gem 'json_schemer', '~> 0.2.12'
gem 'oj', '~> 3.10.6' gem 'oj', '~> 3.10.6'
gem 'multi_json', '~> 1.14.1' gem 'multi_json', '~> 1.14.1'

View file

@ -1,7 +1,9 @@
PATH PATH
remote: vendor/shims/mimemagic remote: vendor/gems/mail-smtp_pool
specs: specs:
mimemagic (0.3.7) mail-smtp_pool (0.1.0)
connection_pool (~> 2.0)
mail (~> 2.7)
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
@ -10,59 +12,59 @@ GEM
abstract_type (0.0.7) abstract_type (0.0.7)
acme-client (2.0.6) acme-client (2.0.6)
faraday (>= 0.17, < 2.0.0) faraday (>= 0.17, < 2.0.0)
actioncable (6.0.3.4) actioncable (6.0.3.6)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.6)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (6.0.3.4) actionmailbox (6.0.3.6)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.6)
activejob (= 6.0.3.4) activejob (= 6.0.3.6)
activerecord (= 6.0.3.4) activerecord (= 6.0.3.6)
activestorage (= 6.0.3.4) activestorage (= 6.0.3.6)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.6)
mail (>= 2.7.1) mail (>= 2.7.1)
actionmailer (6.0.3.4) actionmailer (6.0.3.6)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.6)
actionview (= 6.0.3.4) actionview (= 6.0.3.6)
activejob (= 6.0.3.4) activejob (= 6.0.3.6)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (6.0.3.4) actionpack (6.0.3.6)
actionview (= 6.0.3.4) actionview (= 6.0.3.6)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.6)
rack (~> 2.0, >= 2.0.8) rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.0.3.4) actiontext (6.0.3.6)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.6)
activerecord (= 6.0.3.4) activerecord (= 6.0.3.6)
activestorage (= 6.0.3.4) activestorage (= 6.0.3.6)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.6)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (6.0.3.4) actionview (6.0.3.6)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.6)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.0.3.4) activejob (6.0.3.6)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.6)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (6.0.3.4) activemodel (6.0.3.6)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.6)
activerecord (6.0.3.4) activerecord (6.0.3.6)
activemodel (= 6.0.3.4) activemodel (= 6.0.3.6)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.6)
activerecord-explain-analyze (0.1.0) activerecord-explain-analyze (0.1.0)
activerecord (>= 4) activerecord (>= 4)
pg pg
activestorage (6.0.3.4) activestorage (6.0.3.6)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.6)
activejob (= 6.0.3.4) activejob (= 6.0.3.6)
activerecord (= 6.0.3.4) activerecord (= 6.0.3.6)
marcel (~> 0.3.1) marcel (~> 1.0.0)
activesupport (6.0.3.4) activesupport (6.0.3.6)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
@ -138,7 +140,7 @@ GEM
benchmark-ips (2.3.0) benchmark-ips (2.3.0)
benchmark-memory (0.1.2) benchmark-memory (0.1.2)
memory_profiler (~> 0.9) memory_profiler (~> 0.9)
better_errors (2.7.1) better_errors (2.9.1)
coderay (>= 1.0.0) coderay (>= 1.0.0)
erubi (>= 1.0.0) erubi (>= 1.0.0)
rack (>= 0.9.0) rack (>= 0.9.0)
@ -149,7 +151,6 @@ GEM
bootstrap_form (4.2.0) bootstrap_form (4.2.0)
actionpack (>= 5.0) actionpack (>= 5.0)
activemodel (>= 5.0) activemodel (>= 5.0)
brakeman (4.2.1)
browser (4.2.0) browser (4.2.0)
builder (3.2.4) builder (3.2.4)
bullet (6.1.3) bullet (6.1.3)
@ -265,12 +266,12 @@ GEM
railties (>= 4.1.0) railties (>= 4.1.0)
responders responders
warden (~> 1.2.3) warden (~> 1.2.3)
devise-two-factor (3.1.0) devise-two-factor (4.0.0)
activesupport (< 6.1) activesupport (< 6.2)
attr_encrypted (>= 1.3, < 4, != 2) attr_encrypted (>= 1.3, < 4, != 2)
devise (~> 4.0) devise (~> 4.0)
railties (< 6.1) railties (< 6.2)
rotp (~> 2.0) rotp (~> 6.0)
diff-lcs (1.4.4) diff-lcs (1.4.4)
diff_match_patch (0.1.0) diff_match_patch (0.1.0)
diffy (3.3.0) diffy (3.3.0)
@ -434,7 +435,7 @@ GEM
rails (>= 3.2.0) rails (>= 3.2.0)
git (1.7.0) git (1.7.0)
rchardet (~> 1.8) rchardet (~> 1.8)
gitaly (13.9.0.pre.rc1) gitaly (13.11.0.pre.rc1)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab (4.16.1) gitlab (4.16.1)
@ -442,11 +443,11 @@ GEM
terminal-table (~> 1.5, >= 1.5.1) terminal-table (~> 1.5, >= 1.5.1)
gitlab-chronic (0.10.5) gitlab-chronic (0.10.5)
numerizer (~> 0.2) numerizer (~> 0.2)
gitlab-dangerfiles (0.8.0) gitlab-dangerfiles (1.1.1)
danger danger-gitlab
gitlab-experiment (0.5.0) gitlab-experiment (0.5.3)
activesupport (>= 3.0) activesupport (>= 3.0)
scientist (~> 1.5, >= 1.5.0) scientist (~> 1.6, >= 1.6.0)
gitlab-fog-azure-rm (1.0.1) gitlab-fog-azure-rm (1.0.1)
azure-storage-blob (~> 2.0) azure-storage-blob (~> 2.0)
azure-storage-common (~> 2.0) azure-storage-common (~> 2.0)
@ -461,7 +462,7 @@ GEM
fog-xml (~> 0.1.0) fog-xml (~> 0.1.0)
google-api-client (>= 0.44.2, < 0.51) google-api-client (>= 0.44.2, < 0.51)
google-cloud-env (~> 1.2) google-cloud-env (~> 1.2)
gitlab-labkit (0.16.1) gitlab-labkit (0.16.2)
actionpack (>= 5.0.0, < 7.0.0) actionpack (>= 5.0.0, < 7.0.0)
activesupport (>= 5.0.0, < 7.0.0) activesupport (>= 5.0.0, < 7.0.0)
grpc (~> 1.19) grpc (~> 1.19)
@ -469,16 +470,16 @@ GEM
opentracing (~> 0.4) opentracing (~> 0.4)
pg_query (~> 1.3) pg_query (~> 1.3)
redis (> 3.0.0, < 5.0.0) redis (> 3.0.0, < 5.0.0)
gitlab-license (1.3.1) gitlab-license (1.4.0)
gitlab-mail_room (0.0.9) gitlab-mail_room (0.0.9)
gitlab-markup (1.7.1) gitlab-markup (1.7.1)
gitlab-net-dns (0.9.1) gitlab-net-dns (0.9.1)
gitlab-pry-byebug (3.9.0) gitlab-pry-byebug (3.9.0)
byebug (~> 11.0) byebug (~> 11.0)
pry (~> 0.13.0) pry (~> 0.13.0)
gitlab-sidekiq-fetcher (0.5.5) gitlab-sidekiq-fetcher (0.5.6)
sidekiq (~> 5) sidekiq (~> 5)
gitlab-styles (6.1.0) gitlab-styles (6.2.0)
rubocop (~> 0.91, >= 0.91.1) rubocop (~> 0.91, >= 0.91.1)
rubocop-gitlab-security (~> 0.1.1) rubocop-gitlab-security (~> 0.1.1)
rubocop-performance (~> 1.9.2) rubocop-performance (~> 1.9.2)
@ -493,8 +494,9 @@ GEM
rubyntlm (~> 0.5) rubyntlm (~> 0.5)
globalid (0.4.2) globalid (0.4.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
gon (6.2.0) gon (6.4.0)
actionpack (>= 3.0) actionpack (>= 3.0.20)
i18n (>= 0.7)
multi_json multi_json
request_store (>= 1.0) request_store (>= 1.0)
google-api-client (0.50.0) google-api-client (0.50.0)
@ -508,7 +510,7 @@ GEM
signet (~> 0.12) signet (~> 0.12)
google-cloud-env (1.4.0) google-cloud-env (1.4.0)
faraday (>= 0.17.3, < 2.0) faraday (>= 0.17.3, < 2.0)
google-protobuf (3.12.4) google-protobuf (3.14.0)
googleapis-common-protos-types (1.0.5) googleapis-common-protos-types (1.0.5)
google-protobuf (~> 3.11) google-protobuf (~> 3.11)
googleauth (0.14.0) googleauth (0.14.0)
@ -585,7 +587,7 @@ GEM
rainbow rainbow
rubocop (>= 0.50.0) rubocop (>= 0.50.0)
sysexits (~> 1.1) sysexits (~> 1.1)
hamlit (2.14.4) hamlit (2.15.0)
temple (>= 0.8.2) temple (>= 0.8.2)
thor thor
tilt tilt
@ -620,7 +622,7 @@ GEM
mime-types (~> 3.0) mime-types (~> 3.0)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpclient (2.8.3) httpclient (2.8.3)
i18n (1.8.9) i18n (1.8.10)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
i18n_data (0.8.0) i18n_data (0.8.0)
icalendar (2.4.1) icalendar (2.4.1)
@ -646,7 +648,7 @@ GEM
activesupport (>= 4.2) activesupport (>= 4.2)
aes_key_wrap aes_key_wrap
bindata bindata
json-schema (2.8.0) json-schema (2.8.1)
addressable (>= 2.4) addressable (>= 2.4)
json_schemer (0.2.12) json_schemer (0.2.12)
ecma-re-validator (~> 0.2) ecma-re-validator (~> 0.2)
@ -670,9 +672,9 @@ GEM
kaminari-core (= 1.2.1) kaminari-core (= 1.2.1)
kaminari-core (1.2.1) kaminari-core (1.2.1)
kgio (2.11.3) kgio (2.11.3)
knapsack (1.17.0) knapsack (1.21.1)
rake rake
kramdown (2.3.0) kramdown (2.3.1)
rexml rexml
kramdown-parser-gfm (1.1.0) kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0) kramdown (~> 2.0)
@ -681,12 +683,12 @@ GEM
jsonpath (~> 1.0) jsonpath (~> 1.0)
recursive-open-struct (~> 1.1, >= 1.1.1) recursive-open-struct (~> 1.1, >= 1.1.1)
rest-client (~> 2.0) rest-client (~> 2.0)
launchy (2.4.3) launchy (2.5.0)
addressable (~> 2.3) addressable (~> 2.7)
lefthook (0.7.2) lefthook (0.7.2)
letter_opener (1.7.0) letter_opener (1.7.0)
launchy (~> 2.2) launchy (~> 2.2)
letter_opener_web (1.3.4) letter_opener_web (1.4.0)
actionmailer (>= 3.2) actionmailer (>= 3.2)
letter_opener (~> 1.0) letter_opener (~> 1.0)
railties (>= 3.2) railties (>= 3.2)
@ -708,21 +710,20 @@ GEM
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
locale (2.1.3) locale (2.1.3)
lockbox (0.3.3) lockbox (0.6.2)
lograge (0.11.2) lograge (0.11.2)
actionpack (>= 4) actionpack (>= 4)
activesupport (>= 4) activesupport (>= 4)
railties (>= 4) railties (>= 4)
request_store (~> 1.0) request_store (~> 1.0)
loofah (2.8.0) loofah (2.9.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
lru_redux (1.1.0) lru_redux (1.1.0)
lumberjack (1.2.7) lumberjack (1.2.7)
mail (2.7.1) mail (2.7.1)
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
marcel (0.3.3) marcel (1.0.1)
mimemagic (~> 0.3.2)
marginalia (1.10.0) marginalia (1.10.0)
actionpack (>= 2.3) actionpack (>= 2.3)
activerecord (>= 2.3) activerecord (>= 2.3)
@ -736,7 +737,7 @@ GEM
mime-types-data (3.2020.0512) mime-types-data (3.2020.0512)
mini_histogram (0.3.1) mini_histogram (0.3.1)
mini_magick (4.10.1) mini_magick (4.10.1)
mini_mime (1.0.2) mini_mime (1.1.0)
mini_portile2 (2.5.0) mini_portile2 (2.5.0)
minitest (5.11.3) minitest (5.11.3)
mixlib-cli (2.1.8) mixlib-cli (2.1.8)
@ -775,7 +776,7 @@ GEM
netrc (0.11.0) netrc (0.11.0)
nio4r (2.5.4) nio4r (2.5.4)
no_proxy_fix (0.1.2) no_proxy_fix (0.1.2)
nokogiri (1.11.1) nokogiri (1.11.3)
mini_portile2 (~> 2.5.0) mini_portile2 (~> 2.5.0)
racc (~> 1.4) racc (~> 1.4)
nokogumbo (2.0.2) nokogumbo (2.0.2)
@ -954,20 +955,20 @@ GEM
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rack-timeout (0.5.2) rack-timeout (0.5.2)
rails (6.0.3.4) rails (6.0.3.6)
actioncable (= 6.0.3.4) actioncable (= 6.0.3.6)
actionmailbox (= 6.0.3.4) actionmailbox (= 6.0.3.6)
actionmailer (= 6.0.3.4) actionmailer (= 6.0.3.6)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.6)
actiontext (= 6.0.3.4) actiontext (= 6.0.3.6)
actionview (= 6.0.3.4) actionview (= 6.0.3.6)
activejob (= 6.0.3.4) activejob (= 6.0.3.6)
activemodel (= 6.0.3.4) activemodel (= 6.0.3.6)
activerecord (= 6.0.3.4) activerecord (= 6.0.3.6)
activestorage (= 6.0.3.4) activestorage (= 6.0.3.6)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.6)
bundler (>= 1.3.0) bundler (>= 1.3.0)
railties (= 6.0.3.4) railties (= 6.0.3.6)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
@ -981,9 +982,9 @@ GEM
rails-i18n (6.0.0) rails-i18n (6.0.0)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 7) railties (>= 6.0.0, < 7)
railties (6.0.3.4) railties (6.0.3.6)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.6)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.6)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0) thor (>= 0.20.3, < 2.0)
@ -1045,7 +1046,7 @@ GEM
nokogiri nokogiri
rexml (3.2.5) rexml (3.2.5)
rinku (2.0.0) rinku (2.0.0)
rotp (2.1.2) rotp (6.2.0)
rouge (3.26.0) rouge (3.26.0)
rqrcode (0.7.0) rqrcode (0.7.0)
chunky_png chunky_png
@ -1069,10 +1070,10 @@ GEM
proc_to_ast proc_to_ast
rspec (>= 2.13, < 4) rspec (>= 2.13, < 4)
unparser unparser
rspec-rails (4.0.2) rspec-rails (5.0.1)
actionpack (>= 4.2) actionpack (>= 5.2)
activesupport (>= 4.2) activesupport (>= 5.2)
railties (>= 4.2) railties (>= 5.2)
rspec-core (~> 3.10) rspec-core (~> 3.10)
rspec-expectations (~> 3.10) rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10) rspec-mocks (~> 3.10)
@ -1114,7 +1115,8 @@ GEM
i18n i18n
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-magic-static (0.3.4) ruby-magic (0.4.0)
mini_portile2 (~> 2.5.0)
ruby-prof (1.3.1) ruby-prof (1.3.1)
ruby-progressbar (1.11.0) ruby-progressbar (1.11.0)
ruby-saml (1.12.1) ruby-saml (1.12.1)
@ -1226,6 +1228,8 @@ GEM
terser (1.0.2) terser (1.0.2)
execjs (>= 0.3.0, < 3) execjs (>= 0.3.0, < 3)
test-prof (0.12.0) test-prof (0.12.0)
test_file_finder (0.1.3)
faraday (~> 1.0.1)
text (1.3.1) text (1.3.1)
thin (1.8.0) thin (1.8.0)
daemons (~> 1.0, >= 1.0.9) daemons (~> 1.0, >= 1.0.9)
@ -1363,10 +1367,9 @@ DEPENDENCIES
bcrypt_pbkdf (~> 1.0) bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0) benchmark-ips (~> 2.3.0)
benchmark-memory (~> 0.1) benchmark-memory (~> 0.1)
better_errors (~> 2.7.1) better_errors (~> 2.9.0)
bootsnap (~> 1.4.6) bootsnap (~> 1.4.6)
bootstrap_form (~> 4.2.0) bootstrap_form (~> 4.2.0)
brakeman (~> 4.2)
browser (~> 4.2) browser (~> 4.2)
bullet (~> 6.1.3) bullet (~> 6.1.3)
bundler-audit (~> 0.7.0.1) bundler-audit (~> 0.7.0.1)
@ -1380,7 +1383,6 @@ DEPENDENCIES
countries (~> 3.0) countries (~> 3.0)
creole (~> 0.5.0) creole (~> 0.5.0)
crystalball (~> 0.7.0) crystalball (~> 0.7.0)
danger-gitlab (~> 8.0)
database_cleaner (~> 1.7.0) database_cleaner (~> 1.7.0)
deckar01-task_list (= 2.3.1) deckar01-task_list (= 2.3.1)
default_value_for (~> 3.4.0) default_value_for (~> 3.4.0)
@ -1388,7 +1390,7 @@ DEPENDENCIES
derailed_benchmarks derailed_benchmarks
device_detector device_detector
devise (~> 4.7.2) devise (~> 4.7.2)
devise-two-factor (~> 3.1.0) devise-two-factor (~> 4.0.0)
diff_match_patch (~> 0.1.0) diff_match_patch (~> 0.1.0)
diffy (~> 3.3) diffy (~> 3.3)
discordrb-webhooks (~> 3.4) discordrb-webhooks (~> 3.4)
@ -1423,26 +1425,26 @@ DEPENDENCIES
gettext (~> 3.3) gettext (~> 3.3)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly (~> 13.9.0.pre.rc1) gitaly (~> 13.11.0.pre.rc1)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5) gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 0.8.0) gitlab-dangerfiles (~> 1.1.1)
gitlab-experiment (~> 0.5.0) gitlab-experiment (~> 0.5.3)
gitlab-fog-azure-rm (~> 1.0.1) gitlab-fog-azure-rm (~> 1.0.1)
gitlab-fog-google (~> 1.13) gitlab-fog-google (~> 1.13)
gitlab-labkit (~> 0.16.1) gitlab-labkit (~> 0.16.2)
gitlab-license (~> 1.3) gitlab-license (~> 1.4)
gitlab-mail_room (~> 0.0.9) gitlab-mail_room (~> 0.0.9)
gitlab-markup (~> 1.7.1) gitlab-markup (~> 1.7.1)
gitlab-net-dns (~> 0.9.1) gitlab-net-dns (~> 0.9.1)
gitlab-pry-byebug gitlab-pry-byebug
gitlab-sidekiq-fetcher (= 0.5.5) gitlab-sidekiq-fetcher (= 0.5.6)
gitlab-styles (~> 6.1.0) gitlab-styles (~> 6.2.0)
gitlab_chronic_duration (~> 0.10.6.2) gitlab_chronic_duration (~> 0.10.6.2)
gitlab_omniauth-ldap (~> 2.1.1) gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2) gon (~> 6.4.0)
google-api-client (~> 0.33) google-api-client (~> 0.33)
google-protobuf (~> 3.12) google-protobuf (~> 3.14.0)
gpgme (~> 2.0.19) gpgme (~> 2.0.19)
grape (~> 1.5.2) grape (~> 1.5.2)
grape-entity (~> 0.7.1) grape-entity (~> 0.7.1)
@ -1456,7 +1458,7 @@ DEPENDENCIES
gssapi gssapi
guard-rspec guard-rspec
haml_lint (~> 0.36.0) haml_lint (~> 0.36.0)
hamlit (~> 2.14.4) hamlit (~> 2.15.0)
hangouts-chat (~> 0.0.5) hangouts-chat (~> 0.0.5)
hashie hashie
hashie-forbidden_attributes hashie-forbidden_attributes
@ -1474,22 +1476,22 @@ DEPENDENCIES
json_schemer (~> 0.2.12) json_schemer (~> 0.2.12)
jwt (~> 2.1.0) jwt (~> 2.1.0)
kaminari (~> 1.0) kaminari (~> 1.0)
knapsack (~> 1.17) knapsack (~> 1.21.1)
kramdown (~> 2.3.0) kramdown (~> 2.3.1)
kubeclient (~> 4.9.1) kubeclient (~> 4.9.1)
lefthook (~> 0.7) lefthook (~> 0.7.0)
letter_opener_web (~> 1.3.4) letter_opener_web (~> 1.4.0)
license_finder (~> 6.0) license_finder (~> 6.0)
licensee (~> 9.14.1) licensee (~> 9.14.1)
lockbox (~> 0.3.3) lockbox (~> 0.6.2)
lograge (~> 0.5) lograge (~> 0.5)
loofah (~> 2.2) loofah (~> 2.2)
lru_redux lru_redux
mail (= 2.7.1) mail (= 2.7.1)
mail-smtp_pool (~> 0.1.0)!
marginalia (~> 1.10.0) marginalia (~> 1.10.0)
memory_profiler (~> 0.9) memory_profiler (~> 0.9)
method_source (~> 1.0) method_source (~> 1.0)
mimemagic (= 0.3.7)!
mini_magick (~> 4.10.1) mini_magick (~> 4.10.1)
minitest (~> 5.11.0) minitest (~> 5.11.0)
multi_json (~> 1.14.1) multi_json (~> 1.14.1)
@ -1539,7 +1541,7 @@ DEPENDENCIES
rack-oauth2 (~> 1.16.0) rack-oauth2 (~> 1.16.0)
rack-proxy (~> 0.6.0) rack-proxy (~> 0.6.0)
rack-timeout (~> 0.5.1) rack-timeout (~> 0.5.1)
rails (~> 6.0.3.1) rails (~> 6.0.3.6)
rails-controller-testing rails-controller-testing
rails-i18n (~> 6.0) rails-i18n (~> 6.0)
rainbow (~> 3.0) rainbow (~> 3.0)
@ -1559,12 +1561,12 @@ DEPENDENCIES
rouge (~> 3.26.0) rouge (~> 3.26.0)
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-parameterized rspec-parameterized
rspec-rails (~> 4.0.2) rspec-rails (~> 5.0.1)
rspec-retry (~> 0.6.1) rspec-retry (~> 0.6.1)
rspec_junit_formatter rspec_junit_formatter
rspec_profiling (~> 0.0.6) rspec_profiling (~> 0.0.6)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-magic-static (~> 0.3.4) ruby-magic (~> 0.4)
ruby-prof (~> 1.3.0) ruby-prof (~> 1.3.0)
ruby-progressbar (~> 1.10) ruby-progressbar (~> 1.10)
ruby-saml (~> 1.12.1) ruby-saml (~> 1.12.1)
@ -1594,6 +1596,7 @@ DEPENDENCIES
sys-filesystem (~> 1.1.6) sys-filesystem (~> 1.1.6)
terser (= 1.0.2) terser (= 1.0.2)
test-prof (~> 0.12.0) test-prof (~> 0.12.0)
test_file_finder (~> 0.1.3)
thin (~> 1.8.0) thin (~> 1.8.0)
thrift (>= 0.14.0) thrift (>= 0.14.0)
timecop (~> 0.9.1) timecop (~> 0.9.1)

View file

@ -80,7 +80,7 @@ GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL/OpenSUSE - Ubuntu/Debian/CentOS/RHEL/OpenSUSE
- Ruby (MRI) 2.7.2 - Ruby (MRI) 2.7.2
- Git 2.24+ - Git 2.31+
- Redis 4.0+ - Redis 4.0+
- PostgreSQL 11+ - PostgreSQL 11+

View file

@ -4,6 +4,8 @@
# Add your own tasks in files placed in lib/tasks ending in .rake, # Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
Rake::TaskManager.record_task_metadata = true
require File.expand_path('config/application', __dir__) require File.expand_path('config/application', __dir__)
relative_url_conf = File.expand_path('config/initializers/relative_url', __dir__) relative_url_conf = File.expand_path('config/initializers/relative_url', __dir__)

View file

@ -1 +1 @@
13.10.4 13.11.2

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 29 KiB

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="35px" height="34px" viewBox="0 0 35 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 64 (93537) - https://sketch.com -->
<title>Group</title>
<desc>Created with Sketch.</desc>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard" transform="translate(-463.000000, -393.000000)">
<g id="Group" transform="translate(463.000000, 393.000000)">
<path d="M19.3557,25.2338 C17.7369,25.9233 16.1619,26.1062 14.5861,25.6013 C13.996,26.033 13.2711,26.2395 12.5422,26.1834 C11.8132,26.1273 11.1284,25.8124 10.6114,25.2955 L8.75506,23.4392 C8.19927,22.8833 7.87849,22.1351 7.8591,21.3493 C7.83972,20.5635 8.12323,19.8003 8.65094,19.2177 C8.34425,17.8947 8.46763,16.5564 8.93356,15.1901 L6.183,14.1287 C5.81926,13.9884 5.49427,13.7633 5.23503,13.472 C4.9758,13.1808 4.7898,12.832 4.69251,12.4544 C4.59521,12.0769 4.58942,11.6816 4.67562,11.3013 C4.76182,10.9211 4.93753,10.5669 5.18813,10.2682 L8.79006,5.9759 C9.11841,5.5846 9.56087,5.3057 10.0555,5.1782 C10.5502,5.0508 11.0724,5.0811 11.5489,5.265 L15.0686,6.6234 C15.4064,6.2747 15.7529,5.9234 16.1073,5.5691 C19.6177,2.05856 24.1782,0.20312 29.7892,0.00275 C29.9524,-0.00304 30.1159,0.00032 30.2787,0.01281 C32.6881,0.19656 34.4919,2.29918 34.3077,4.7085 C33.8986,10.0569 31.957,14.4687 28.4824,17.9429 C28.2408,18.1846 27.9986,18.4257 27.7557,18.6661 L29.1763,22.3463 C29.3602,22.8229 29.3905,23.345 29.263,23.8397 C29.1355,24.3344 28.8566,24.7768 28.4653,25.1052 L24.1734,28.7071 C23.8748,28.9578 23.5206,29.1336 23.1403,29.2198 C22.76,29.3061 22.3646,29.3003 21.987,29.203 C21.6094,29.1057 21.2605,28.9197 20.9692,28.6604 C20.678,28.4011 20.4528,28.0761 20.3125,27.7122 L19.3557,25.2338 Z M9.32819,13.455 L13.8808,8.0291 L10.8962,6.8772 C10.7601,6.8247 10.611,6.816 10.4697,6.8524 C10.3284,6.8888 10.2021,6.9685 10.1082,7.0802 L6.50631,11.3725 C6.43473,11.4578 6.38455,11.559 6.35993,11.6677 C6.33532,11.7763 6.33699,11.8893 6.3648,11.9971 C6.39261,12.105 6.44576,12.2046 6.51983,12.2878 C6.59391,12.371 6.68676,12.4353 6.79069,12.4754 L9.32819,13.455 Z M20.9858,24.5675 L21.9654,27.105 C22.0055,27.2089 22.0699,27.3017 22.1531,27.3757 C22.2363,27.4498 22.336,27.5029 22.4438,27.5306 C22.5517,27.5584 22.6646,27.56 22.7732,27.5354 C22.8818,27.5107 22.983,27.4605 23.0683,27.3889 L27.3602,23.7874 C27.4721,23.6936 27.5518,23.5672 27.5883,23.4259 C27.6248,23.2845 27.6162,23.1353 27.5636,22.9991 L26.4117,20.0144 L20.9858,24.5675 Z M12.3714,22.1057 C16.2,25.9347 19.3999,24.55 27.2442,16.7056 C30.4161,13.5337 32.1845,9.5162 32.5621,4.5751 C32.5903,4.2067 32.5404,3.83648 32.4156,3.4887 C32.2909,3.14091 32.0942,2.82338 31.8383,2.55685 C31.5825,2.29033 31.2732,2.08082 30.9308,1.94203 C30.5884,1.80323 30.2205,1.73829 29.8513,1.75143 C24.6687,1.9365 20.5312,3.62 17.3449,6.8063 C9.89431,14.2569 8.41381,18.1481 12.3714,22.1057 Z" id="Shape" fill="#6E49CB"></path>
<path d="M22.9861,11.6834 C23.148,11.8489 23.341,11.9806 23.5541,12.071 C23.7672,12.1613 23.9962,12.2085 24.2276,12.2098236 C24.4591,12.211 24.6885,12.1664 24.9026,12.0784 C25.1166,11.9904 25.3111,11.8608 25.4748,11.6971 C25.6384,11.5334 25.768,11.3389 25.856,11.1248 C25.9439,10.9107 25.9885,10.6813 25.9872276,10.4499 C25.9859,10.2184 25.9387,9.9895 25.8483,9.7764 C25.7579,9.5633 25.6262,9.3703 25.4606,9.2085 C25.1325,8.8803 24.6873,8.6959 24.2232,8.6959 C23.759,8.6959 23.3139,8.8803 22.9857,9.2085 C22.6575,9.5367 22.4731,9.9818 22.4731,10.446 C22.4731,10.9101 22.6575,11.3552 22.9857,11.6834 L22.9861,11.6834 Z M21.748,12.9211 C21.423,12.5961 21.1651,12.2102 20.9892,11.7855 C20.8133,11.3608 20.7228,10.9056 20.7228,10.446 C20.7228,9.9863 20.8133,9.5311 20.9892,9.1064 C21.1651,8.6817 21.423,8.2958 21.748,7.9708 C22.0731,7.6458 22.4589,7.3879 22.8836,7.212 C23.3083,7.0361 23.7635,6.9456 24.2232,6.9456 C24.6829,6.9456 25.138,7.0361 25.5627,7.212 C25.9874,7.3879 26.3733,7.6458 26.6983,7.9708 C27.3482,8.6285 27.7113,9.5166 27.708615,10.4412 C27.7058,11.3658 27.3373,12.2517 26.6836,12.9055 C26.0298,13.5593 25.1439,13.9278 24.2194,13.9307161 C23.2948,13.9335 22.4067,13.5704 21.7489,12.9207 L21.748,12.9211 Z" id="Shape" fill="#C2B7E6" fill-rule="nonzero"></path>
<path d="M6.58996,23.1303 C6.754,23.2943 6.84615,23.5169 6.84615,23.7489 C6.84615,23.9809 6.754,24.2034 6.58996,24.3675 L1.64009,29.3165 C1.5594,29.4001 1.46287,29.4668 1.35614,29.5127 C1.2494,29.5586 1.13459,29.5828 1.01841,29.5838391 C0.902228,29.5849 0.787001,29.5628 0.679451,29.5188 C0.571902,29.4749 0.474183,29.4099 0.391998,29.3278 C0.309813,29.2457 0.244808,29.148 0.200774,29.0405 C0.15674,28.933 0.13456,28.8177 0.135497628,28.7016 C0.136497,28.5854 0.160594,28.4706 0.206414,28.3638 C0.252233,28.257 0.318858,28.1604 0.4024,28.0797 L5.35228,23.1298 C5.43353,23.0485 5.53001,22.984 5.63619,22.9401 C5.74237,22.8961 5.85618,22.8734 5.97112,22.8734 C6.08606,22.8734 6.19987,22.8961 6.30605,22.9401 C6.41223,22.984 6.50871,23.0485 6.58996,23.1298 L6.58996,23.1303 Z M10.9208,27.4611 C11.0848,27.6252 11.177,27.8477 11.177,28.0797 C11.177,28.3117 11.0848,28.5342 10.9208,28.6983 L7.20859,32.4105 C7.12735,32.4918 7.0309,32.5562 6.92474,32.6002 C6.81859,32.6442 6.70481,32.6669 6.5899,32.6669 C6.47499,32.6669 6.3612,32.6443 6.25503,32.6004 C6.14886,32.5564 6.05239,32.492 5.97112,32.4107 C5.88985,32.3295 5.82538,32.233 5.78139,32.1269 C5.7374,32.0207 5.71471999,31.9069 5.71471999,31.792 C5.71471999,31.6771 5.73731,31.5633 5.78127,31.4572 C5.82522,31.351 5.88966,31.2545 5.9709,31.1733 L9.68353,27.4611 C9.84761,27.297 10.0701,27.2049 10.3022,27.2049 C10.5342,27.2049 10.7567,27.297 10.9208,27.4611 L10.9208,27.4611 Z" id="Shape" fill="#E0DBF2"></path>
<path d="M8.75534,25.2954 C8.91937,25.4595 9.01152,25.682 9.01152,25.914 C9.01152,26.1461 8.91937,26.3686 8.75534,26.5327 L1.94959,33.3389 C1.78546,33.503 1.56286,33.5952 1.33074,33.5952 C1.09863,33.5952 0.876027,33.503 0.7119,33.3389 C0.547772,33.1747 0.455566,32.9521 0.455566,32.72 C0.455566,32.4879 0.547772,32.2653 0.7119,32.1012 L7.51809,25.2959 C7.68217,25.1318 7.90469,25.0397 8.13671,25.0397 C8.36873,25.0397 8.59125,25.1318 8.75534,25.2959 L8.75534,25.2954 Z" id="Path" fill="#C2B7E6"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

View file

@ -0,0 +1 @@
<svg width="40" height="40" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.343 6c.82 0 1.516.145 1.968.34.198.085.315.165.375.218v31.441a1.45 1.45 0 01-.375.218c-.452.195-1.148.34-1.968.34-.82 0-1.516-.145-1.968-.34A1.45 1.45 0 011 37.999V6.558c.06-.053.177-.133.375-.218C1.827 6.145 2.523 6 3.343 6z" fill="#EFEDF8" stroke="#6E49CB" stroke-width="2"/><path fill-rule="evenodd" clip-rule="evenodd" d="M6.686 6.41l21.724 6.692c2.085.642 2.122 1.774.095 2.523L6.686 23.69V6.412z" fill="#6E49CB"/></svg>

After

Width:  |  Height:  |  Size: 512 B

View file

@ -0,0 +1 @@
<svg width="40" height="40" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M14.544 6.44C14.816 5.58 15.581 5 16.442 5h1.116c.861 0 1.626.58 1.898 1.44l1.05 3.32c.744.238 1.456.55 2.13.93l2.974-1.566c.77-.405 1.7-.247 2.309.394l.79.832c.608.64.76 1.62.374 2.43l-1.486 3.131c.359.71.656 1.46.882 2.243l3.153 1.106c.817.287 1.368 1.091 1.368 1.998v1.176c0 .906-.55 1.71-1.368 1.998l-3.153 1.106a12.936 12.936 0 01-.882 2.242l1.486 3.131c.385.81.234 1.79-.374 2.43l-.79.832c-.609.641-1.539.8-2.309.395l-2.973-1.566c-.675.379-1.387.692-2.13.93l-1.051 3.32c-.272.86-1.037 1.44-1.898 1.44h-1.116c-.861 0-1.626-.58-1.898-1.44l-1.05-3.32a11.604 11.604 0 01-2.13-.93L8.39 34.569c-.77.406-1.7.247-2.309-.395l-.79-.831a2.189 2.189 0 01-.374-2.43l1.487-3.131c-.36-.71-.657-1.46-.883-2.243l-3.153-1.106C1.55 24.145 1 23.34 1 22.434v-1.176c0-.906.55-1.711 1.368-1.998l3.153-1.106c.226-.783.523-1.533.883-2.243l-1.487-3.13a2.19 2.19 0 01.374-2.431l.79-.832c.609-.64 1.539-.8 2.309-.394l2.973 1.565a11.599 11.599 0 012.13-.93l1.051-3.32zM17 30.269c4.418 0 8-3.771 8-8.423s-3.582-8.423-8-8.423-8 3.771-8 8.423 3.582 8.423 8 8.423z" fill="#EFEDF8" stroke="#6E49CB" stroke-width="2"/><path d="M17 27.11c2.762 0 5-2.357 5-5.264 0-2.908-2.238-5.265-5-5.265-2.76 0-5 2.357-5 5.265 0 2.907 2.24 5.264 5 5.264z" stroke="#6E49CB" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,20 +1,10 @@
import Vue from 'vue'; import Vue from 'vue';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { parseRailsFormFields } from '~/lib/utils/forms';
import { __ } from '~/locale'; import { __ } from '~/locale';
import ExpiresAtField from './components/expires_at_field.vue'; import ExpiresAtField from './components/expires_at_field.vue';
const getInputAttrs = (el) => {
const input = el.querySelector('input');
return {
id: input.id,
name: input.name,
value: input.value,
placeholder: input.placeholder,
};
};
export const initExpiresAtField = () => { export const initExpiresAtField = () => {
const el = document.querySelector('.js-access-tokens-expires-at'); const el = document.querySelector('.js-access-tokens-expires-at');
@ -22,7 +12,7 @@ export const initExpiresAtField = () => {
return null; return null;
} }
const inputAttrs = getInputAttrs(el); const { expiresAt: inputAttrs } = parseRailsFormFields(el);
return new Vue({ return new Vue({
el, el,
@ -43,7 +33,7 @@ export const initProjectsField = () => {
return null; return null;
} }
const inputAttrs = getInputAttrs(el); const { projects: inputAttrs } = parseRailsFormFields(el);
if (window.gon.features.personalAccessTokensScopedToProjects) { if (window.gon.features.personalAccessTokensScopedToProjects) {
return new Promise((resolve) => { return new Promise((resolve) => {

View file

@ -2,14 +2,20 @@
import $ from 'jquery'; import $ from 'jquery';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import { localTimeAgo } from './lib/utils/datetime_utility'; import { localTimeAgo } from './lib/utils/datetime_utility';
import Pager from './pager'; import Pager from './pager';
export default class Activities { export default class Activities {
constructor(container = '') { constructor(containerSelector = '') {
this.container = container; this.containerSelector = containerSelector;
this.containerEl = this.containerSelector
? document.querySelector(this.containerSelector)
: undefined;
this.$contentList = $('.content_list');
Pager.init(20, true, false, (data) => data, this.updateTooltips, this.container); this.loadActivities();
$('.event-filter-link').on('click', (e) => { $('.event-filter-link').on('click', (e) => {
e.preventDefault(); e.preventDefault();
@ -18,13 +24,30 @@ export default class Activities {
}); });
} }
loadActivities() {
Pager.init({
limit: 20,
preload: true,
prepareData: (data) => data,
successCallback: () => this.updateTooltips(),
errorCallback: () =>
createFlash({
message: s__(
'Activity|An error occured while retrieving activity. Reload the page to try again.',
),
parent: this.containerEl,
}),
container: this.containerSelector,
});
}
updateTooltips() { updateTooltips() {
localTimeAgo($('.js-timeago', '.content_list')); localTimeAgo($('.js-timeago', '.content_list'));
} }
reloadActivities() { reloadActivities() {
$('.content_list').html(''); this.$contentList.html('');
Pager.init(20, true, false, (data) => data, this.updateTooltips, this.container); this.loadActivities();
} }
toggleFilter(sender) { toggleFilter(sender) {

View file

@ -3,7 +3,7 @@ import { s__ } from '~/locale';
const statisticsLabels = { const statisticsLabels = {
forks: s__('AdminStatistics|Forks'), forks: s__('AdminStatistics|Forks'),
issues: s__('AdminStatistics|Issues'), issues: s__('AdminStatistics|Issues'),
mergeRequests: s__('AdminStatistics|Merge Requests'), mergeRequests: s__('AdminStatistics|Merge requests'),
notes: s__('AdminStatistics|Notes'), notes: s__('AdminStatistics|Notes'),
snippets: s__('AdminStatistics|Snippets'), snippets: s__('AdminStatistics|Snippets'),
sshKeys: s__('AdminStatistics|SSH Keys'), sshKeys: s__('AdminStatistics|SSH Keys'),

View file

@ -1,9 +1,9 @@
<script> <script>
import { GlTable } from '@gitlab/ui'; import { GlTable } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import UserDate from '~/vue_shared/components/user_date.vue';
import UserActions from './user_actions.vue'; import UserActions from './user_actions.vue';
import UserAvatar from './user_avatar.vue'; import UserAvatar from './user_avatar.vue';
import UserDate from './user_date.vue';
const DEFAULT_TH_CLASSES = const DEFAULT_TH_CLASSES =
'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!'; 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!';

View file

@ -2,8 +2,6 @@ import { s__, __ } from '~/locale';
export const USER_AVATAR_SIZE = 32; export const USER_AVATAR_SIZE = 32;
export const SHORT_DATE_FORMAT = 'd mmm, yyyy';
export const LENGTH_OF_USER_NOTE_TOOLTIP = 100; export const LENGTH_OF_USER_NOTE_TOOLTIP = 100;
export const I18N_USER_ACTIONS = { export const I18N_USER_ACTIONS = {

View file

@ -0,0 +1,55 @@
const DATA_ATTR_REGEX_PATTERN = 'data-user-internal-regex-pattern';
const DATA_ATTR_REGEX_OPTIONS = 'data-user-internal-regex-options';
export const ID_USER_EXTERNAL = 'user_external';
export const ID_WARNING = 'warning_external_automatically_set';
export const ID_USER_EMAIL = 'user_email';
const getAttributeValue = (attr) => document.querySelector(`[${attr}]`)?.getAttribute(attr);
const getRegexPattern = () => getAttributeValue(DATA_ATTR_REGEX_PATTERN);
const getRegexOptions = () => getAttributeValue(DATA_ATTR_REGEX_OPTIONS);
export const setupInternalUserRegexHandler = () => {
const regexPattern = getRegexPattern();
if (!regexPattern) {
return;
}
const regexOptions = getRegexOptions();
const elExternal = document.getElementById(ID_USER_EXTERNAL);
const elWarningMessage = document.getElementById(ID_WARNING);
const elUserEmail = document.getElementById(ID_USER_EMAIL);
const isEmailInternal = (email) => {
const regex = new RegExp(regexPattern, regexOptions);
return regex.test(email);
};
const setExternalCheckbox = (email) => {
const isChecked = elExternal.checked;
if (isEmailInternal(email)) {
if (isChecked) {
elExternal.checked = false;
elWarningMessage.classList.remove('hidden');
}
} else if (!isChecked) {
elExternal.checked = true;
elWarningMessage.classList.add('hidden');
}
};
const setupListeners = () => {
elUserEmail.addEventListener('input', (event) => {
setExternalCheckbox(event.target.value);
});
elExternal.addEventListener('change', () => {
elWarningMessage.classList.add('hidden');
});
};
setupListeners();
};

View file

@ -43,7 +43,7 @@ export default {
</gl-link> </gl-link>
</div> </div>
<div v-if="userCanEnableAlertManagement" class="gl-display-block center gl-pt-4"> <div v-if="userCanEnableAlertManagement" class="gl-display-block center gl-pt-4">
<gl-button category="primary" variant="success" :href="enableAlertManagementPath"> <gl-button category="primary" variant="confirm" :href="enableAlertManagementPath">
{{ $options.i18n.emptyState.buttonText }} {{ $options.i18n.emptyState.buttonText }}
</gl-button> </gl-button>
</div> </div>

View file

@ -5,6 +5,7 @@ import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import { PAGE_CONFIG } from '~/vue_shared/alert_details/constants'; import { PAGE_CONFIG } from '~/vue_shared/alert_details/constants';
import AlertManagementList from './components/alert_management_list_wrapper.vue'; import AlertManagementList from './components/alert_management_list_wrapper.vue';
import alertsHelpUrlQuery from './graphql/queries/alert_help_url.query.graphql';
Vue.use(VueApollo); Vue.use(VueApollo);
@ -41,7 +42,8 @@ export default () => {
), ),
}); });
apolloProvider.clients.defaultClient.cache.writeData({ apolloProvider.clients.defaultClient.cache.writeQuery({
query: alertsHelpUrlQuery,
data: { data: {
alertsHelpUrl, alertsHelpUrl,
}, },

View file

@ -118,17 +118,17 @@ export default {
<template> <template>
<div class="gl-display-table gl-w-full gl-mt-5"> <div class="gl-display-table gl-w-full gl-mt-5">
<div class="gl-display-table-row"> <div class="gl-display-table-row">
<h5 id="gitlabFieldsHeader" class="gl-display-table-cell gl-py-3 gl-pr-3"> <h5 id="gitlabFieldsHeader" class="gl-display-table-cell gl-pb-3 gl-pr-3">
{{ $options.i18n.columns.gitlabKeyTitle }} {{ $options.i18n.columns.gitlabKeyTitle }}
</h5> </h5>
<h5 class="gl-display-table-cell gl-py-3 gl-pr-3">&nbsp;</h5> <h5 class="gl-display-table-cell gl-pb-3 gl-pr-3">&nbsp;</h5>
<h5 id="parsedFieldsHeader" class="gl-display-table-cell gl-py-3 gl-pr-3"> <h5 id="parsedFieldsHeader" class="gl-display-table-cell gl-pb-3 gl-pr-3">
{{ $options.i18n.columns.payloadKeyTitle }} {{ $options.i18n.columns.payloadKeyTitle }}
</h5> </h5>
<h5 <h5
v-if="hasFallbackColumn" v-if="hasFallbackColumn"
id="fallbackFieldsHeader" id="fallbackFieldsHeader"
class="gl-display-table-cell gl-py-3 gl-pr-3" class="gl-display-table-cell gl-pb-3 gl-pr-3"
> >
{{ $options.i18n.columns.fallbackKeyTitle }} {{ $options.i18n.columns.fallbackKeyTitle }}
<gl-icon <gl-icon
@ -140,11 +140,7 @@ export default {
</h5> </h5>
</div> </div>
<div <div v-for="gitlabField in mappingData" :key="gitlabField.name" class="gl-display-table-row">
v-for="(gitlabField, index) in mappingData"
:key="gitlabField.name"
class="gl-display-table-row"
>
<div class="gl-display-table-cell gl-py-3 gl-pr-3 gl-w-30p gl-vertical-align-middle"> <div class="gl-display-table-cell gl-py-3 gl-pr-3 gl-w-30p gl-vertical-align-middle">
<gl-form-input <gl-form-input
aria-labelledby="gitlabFieldsHeader" aria-labelledby="gitlabFieldsHeader"
@ -153,8 +149,8 @@ export default {
/> />
</div> </div>
<div class="gl-display-table-cell gl-py-3 gl-pr-3"> <div class="gl-display-table-cell gl-pr-3 gl-vertical-align-middle">
<div class="right-arrow" :class="{ 'gl-vertical-align-middle': index === 0 }"> <div class="right-arrow">
<i class="right-arrow-head"></i> <i class="right-arrow-head"></i>
</div> </div>
</div> </div>

View file

@ -21,8 +21,10 @@ import {
import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql'; import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
export const i18n = { export const i18n = {
deleteIntegration: s__('AlertSettings|Delete integration'),
editIntegration: s__('AlertSettings|Edit integration'),
title: s__('AlertsIntegrations|Current integrations'), title: s__('AlertsIntegrations|Current integrations'),
emptyState: s__('AlertsIntegrations|No integrations have been added yet'), emptyState: s__('AlertsIntegrations|No integrations have been added yet.'),
status: { status: {
enabled: { enabled: {
name: __('Enabled'), name: __('Enabled'),
@ -139,7 +141,7 @@ export default {
<template> <template>
<div class="incident-management-list"> <div class="incident-management-list">
<h5 class="gl-font-lg">{{ $options.i18n.title }}</h5> <h5 class="gl-font-lg gl-mt-5">{{ $options.i18n.title }}</h5>
<gl-table <gl-table
class="integration-list" class="integration-list"
:items="integrations" :items="integrations"
@ -174,11 +176,16 @@ export default {
<template #cell(actions)="{ item }"> <template #cell(actions)="{ item }">
<gl-button-group class="gl-ml-3"> <gl-button-group class="gl-ml-3">
<gl-button icon="settings" @click="editIntegration(item)" /> <gl-button
icon="settings"
:aria-label="$options.i18n.editIntegration"
@click="editIntegration(item)"
/>
<gl-button <gl-button
v-gl-modal.deleteIntegration v-gl-modal.deleteIntegration
:disabled="item.type === $options.typeSet.prometheus" :disabled="item.type === $options.typeSet.prometheus"
icon="remove" icon="remove"
:aria-label="$options.i18n.deleteIntegration"
@click="setIntegrationToDelete(item)" @click="setIntegrationToDelete(item)"
/> />
</gl-button-group> </gl-button-group>
@ -198,15 +205,15 @@ export default {
</gl-table> </gl-table>
<gl-modal <gl-modal
modal-id="deleteIntegration" modal-id="deleteIntegration"
:title="s__('AlertSettings|Delete integration')" :title="$options.i18n.deleteIntegration"
:ok-title="s__('AlertSettings|Delete integration')" :ok-title="$options.i18n.deleteIntegration"
ok-variant="danger" ok-variant="danger"
@ok="deleteIntegration" @ok="deleteIntegration"
> >
<gl-sprintf <gl-sprintf
:message=" :message="
s__( s__(
'AlertsIntegrations|You have opted to delete the %{integrationName} integration. Do you want to proceed? It means you will no longer receive alerts from this endpoint in your alert list, and this action cannot be undone.', 'AlertsIntegrations|If you delete the %{integrationName} integration, alerts are no longer sent from this endpoint. This action cannot be undone.',
) )
" "
> >

View file

@ -14,7 +14,7 @@ import {
GlTab, GlTab,
} from '@gitlab/ui'; } from '@gitlab/ui';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { isEmpty, omit } from 'lodash'; import { isEqual, isEmpty, omit } from 'lodash';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { import {
integrationTypes, integrationTypes,
@ -24,8 +24,9 @@ import {
JSON_VALIDATE_DELAY, JSON_VALIDATE_DELAY,
targetPrometheusUrlPlaceholder, targetPrometheusUrlPlaceholder,
typeSet, typeSet,
viewCredentialsTabIndex,
i18n, i18n,
tabIndices,
testAlertModalId,
} from '../constants'; } from '../constants';
import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql'; import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
import parseSamplePayloadQuery from '../graphql/queries/parse_sample_payload.query.graphql'; import parseSamplePayloadQuery from '../graphql/queries/parse_sample_payload.query.graphql';
@ -40,6 +41,10 @@ export default {
typeSet, typeSet,
integrationSteps, integrationSteps,
i18n, i18n,
primaryProps: { text: i18n.integrationFormSteps.testPayload.savedAndTest },
secondaryProps: { text: i18n.integrationFormSteps.testPayload.proceedWithoutSave },
cancelProps: { text: i18n.integrationFormSteps.testPayload.cancel },
testAlertModalId,
components: { components: {
ClipboardButton, ClipboardButton,
GlButton, GlButton,
@ -60,11 +65,8 @@ export default {
GlModal: GlModalDirective, GlModal: GlModalDirective,
}, },
inject: { inject: {
generic: { alertsUsageUrl: {
default: {}, default: '#',
},
prometheus: {
default: {},
}, },
multiIntegrations: { multiIntegrations: {
default: false, default: false,
@ -87,6 +89,11 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
tabIndex: {
type: Number,
required: false,
default: tabIndices.configureDetails,
},
}, },
apollo: { apollo: {
currentIntegration: { currentIntegration: {
@ -96,11 +103,10 @@ export default {
data() { data() {
return { return {
integrationTypesOptions: Object.values(integrationTypes), integrationTypesOptions: Object.values(integrationTypes),
selectedIntegration: integrationTypes.none.value,
active: false,
samplePayload: { samplePayload: {
json: null, json: null,
error: null, error: null,
loading: false,
}, },
testPayload: { testPayload: {
json: null, json: null,
@ -108,18 +114,32 @@ export default {
}, },
resetPayloadAndMappingConfirmed: false, resetPayloadAndMappingConfirmed: false,
mapping: [], mapping: [],
parsingPayload: false, integrationForm: {
active: false,
type: integrationTypes.none.value,
name: '',
token: '',
url: '',
apiUrl: '',
},
activeTabIndex: this.tabIndex,
currentIntegration: null, currentIntegration: null,
parsedPayload: [], parsedPayload: [],
activeTabIndex: 0, validationState: {
name: true,
apiUrl: true,
},
}; };
}, },
computed: { computed: {
isPrometheus() { isPrometheus() {
return this.selectedIntegration === this.$options.typeSet.prometheus; return this.integrationForm.type === typeSet.prometheus;
}, },
isHttp() { isHttp() {
return this.selectedIntegration === this.$options.typeSet.http; return this.integrationForm.type === typeSet.http;
},
isNone() {
return !this.isHttp && !this.isPrometheus;
}, },
isCreating() { isCreating() {
return !this.currentIntegration; return !this.currentIntegration;
@ -130,29 +150,6 @@ export default {
isTestPayloadValid() { isTestPayloadValid() {
return this.testPayload.error === null; return this.testPayload.error === null;
}, },
selectedIntegrationType() {
switch (this.selectedIntegration) {
case typeSet.http:
return this.generic;
case typeSet.prometheus:
return this.prometheus;
default:
return {};
}
},
integrationForm() {
return {
name: this.currentIntegration?.name || '',
active: this.currentIntegration?.active || false,
token:
this.currentIntegration?.token ||
(this.selectedIntegrationType !== this.generic ? this.selectedIntegrationType.token : ''),
url:
this.currentIntegration?.url ||
(this.selectedIntegrationType !== this.generic ? this.selectedIntegrationType.url : ''),
apiUrl: this.currentIntegration?.apiUrl || '',
};
},
testAlertPayload() { testAlertPayload() {
return { return {
data: this.testPayload.json, data: this.testPayload.json,
@ -170,13 +167,7 @@ export default {
return this.hasSamplePayload && !this.resetPayloadAndMappingConfirmed; return this.hasSamplePayload && !this.resetPayloadAndMappingConfirmed;
}, },
canParseSamplePayload() { canParseSamplePayload() {
return !this.active || !this.isSampePayloadValid || !this.samplePayload.json; return this.isSampePayloadValid && this.samplePayload.json;
},
isResetAuthKeyDisabled() {
return !this.active && !this.integrationForm.token !== '';
},
isPayloadEditDisabled() {
return !this.active || this.canEditPayload;
}, },
isSelectDisabled() { isSelectDisabled() {
return this.currentIntegration !== null || !this.canAddIntegration; return this.currentIntegration !== null || !this.canAddIntegration;
@ -186,30 +177,105 @@ export default {
? i18n.integrationFormSteps.setupCredentials.prometheusHelp ? i18n.integrationFormSteps.setupCredentials.prometheusHelp
: i18n.integrationFormSteps.setupCredentials.help; : i18n.integrationFormSteps.setupCredentials.help;
}, },
isFormValid() {
return (
Object.values(this.validationState).every(Boolean) &&
!this.isNone &&
this.isSampePayloadValid
);
},
isFormDirty() {
const { type, active, name, apiUrl, payloadAlertFields = [], payloadAttributeMappings = [] } =
this.currentIntegration || {};
const {
name: formName,
apiUrl: formApiUrl,
active: formActive,
type: formType,
} = this.integrationForm;
const isDirty =
type !== formType ||
active !== formActive ||
name !== formName ||
apiUrl !== formApiUrl ||
!isEqual(this.parsedPayload, payloadAlertFields) ||
!isEqual(this.mapping, this.getCleanMapping(payloadAttributeMappings));
return isDirty;
},
canSubmitForm() {
return this.isFormValid && this.isFormDirty;
},
dataForSave() {
const { name, apiUrl, active } = this.integrationForm;
const customMappingVariables = {
payloadAttributeMappings: this.mapping,
payloadExample: this.samplePayload.json || '{}',
};
const variables = this.isHttp
? { name, active, ...customMappingVariables }
: { apiUrl, active };
return { type: this.integrationForm.type, variables };
},
testAlertModal() {
return this.isFormDirty ? testAlertModalId : null;
},
prometheusUrlInvalidFeedback() {
const { blankUrlError, invalidUrlError } = i18n.integrationFormSteps.prometheusFormUrl;
return this.integrationForm.apiUrl?.length ? invalidUrlError : blankUrlError;
},
}, },
watch: { watch: {
tabIndex(val) {
this.activeTabIndex = val;
},
currentIntegration(val) { currentIntegration(val) {
if (val === null) { if (val === null) {
this.reset(); this.reset();
return; return;
} }
const { type, active, payloadExample, payloadAlertFields, payloadAttributeMappings } = val;
this.selectedIntegration = type;
this.active = active;
if (type === typeSet.http && this.showMappingBuilder) { this.resetPayloadAndMapping();
const {
name,
type,
active,
url,
apiUrl,
token,
payloadExample,
payloadAlertFields,
payloadAttributeMappings,
} = val;
this.integrationForm = { type, name, active, url, apiUrl, token };
if (this.showMappingBuilder) {
this.resetPayloadAndMappingConfirmed = false;
this.parsedPayload = payloadAlertFields; this.parsedPayload = payloadAlertFields;
this.samplePayload.json = this.isValidNonEmptyJSON(payloadExample) ? payloadExample : null; this.samplePayload.json = this.getPrettifiedPayload(payloadExample);
const mapping = payloadAttributeMappings.map((mappingItem) => this.updateMapping(this.getCleanMapping(payloadAttributeMappings));
omit(mappingItem, '__typename'),
);
this.updateMapping(mapping);
} }
this.activeTabIndex = viewCredentialsTabIndex;
this.$el.scrollIntoView({ block: 'center' }); this.$el.scrollIntoView({ block: 'center' });
}, },
}, },
methods: { methods: {
getCleanMapping(mapping) {
return mapping.map((mappingItem) => omit(mappingItem, '__typename'));
},
validateName() {
this.validationState.name = Boolean(this.integrationForm.name?.length);
},
validateApiUrl() {
try {
const parsedUrl = new URL(this.integrationForm.apiUrl);
this.validationState.apiUrl = ['http:', 'https:'].includes(parsedUrl.protocol);
} catch (e) {
this.validationState.apiUrl = false;
}
},
isValidNonEmptyJSON(JSONString) { isValidNonEmptyJSON(JSONString) {
if (JSONString) { if (JSONString) {
let parsed; let parsed;
@ -222,29 +288,37 @@ export default {
} }
return false; return false;
}, },
getPrettifiedPayload(payload) {
return this.isValidNonEmptyJSON(payload)
? JSON.stringify(JSON.parse(payload), null, '\t')
: null;
},
triggerValidation() {
if (this.isHttp) {
this.validationState.apiUrl = true;
this.validateName();
if (!this.validationState.name) {
this.$refs.integrationName.$el.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
} else if (this.isPrometheus) {
this.validationState.name = true;
this.validateApiUrl();
}
},
sendTestAlert() { sendTestAlert() {
this.$emit('test-alert-payload', this.testAlertPayload); this.$emit('test-alert-payload', this.testAlertPayload);
}, },
submit() { saveAndSendTestAlert() {
const { name, apiUrl } = this.integrationForm; this.$emit('save-and-test-alert-payload', this.dataForSave, this.testAlertPayload);
const customMappingVariables = { },
payloadAttributeMappings: this.mapping, submit(testAfterSubmit = false) {
payloadExample: this.samplePayload.json || '{}', this.triggerValidation();
};
const variables = if (!this.isFormValid) {
this.selectedIntegration === typeSet.http return;
? { name, active: this.active, ...customMappingVariables }
: { apiUrl, active: this.active };
const integrationPayload = { type: this.selectedIntegration, variables };
if (this.currentIntegration) {
return this.$emit('update-integration', integrationPayload);
} }
const event = this.currentIntegration ? 'update-integration' : 'create-new-integration';
this.reset(); this.$emit(event, this.dataForSave, testAfterSubmit);
return this.$emit('create-new-integration', integrationPayload);
}, },
reset() { reset() {
this.resetFormValues(); this.resetFormValues();
@ -252,14 +326,14 @@ export default {
this.$emit('clear-current-integration', { type: this.currentIntegration?.type }); this.$emit('clear-current-integration', { type: this.currentIntegration?.type });
}, },
resetFormValues() { resetFormValues() {
this.selectedIntegration = integrationTypes.none.value; this.integrationForm.type = integrationTypes.none.value;
this.integrationForm.name = ''; this.integrationForm.name = '';
this.integrationForm.active = false;
this.integrationForm.apiUrl = ''; this.integrationForm.apiUrl = '';
this.samplePayload = { this.samplePayload = {
json: null, json: null,
error: null, error: null,
}; };
this.active = false;
}, },
resetAuthKey() { resetAuthKey() {
if (!this.currentIntegration) { if (!this.currentIntegration) {
@ -267,7 +341,7 @@ export default {
} }
this.$emit('reset-token', { this.$emit('reset-token', {
type: this.selectedIntegration, type: this.integrationForm.type,
variables: { id: this.currentIntegration.id }, variables: { id: this.currentIntegration.id },
}); });
}, },
@ -285,8 +359,8 @@ export default {
payload.error = JSON.stringify(e.message); payload.error = JSON.stringify(e.message);
} }
}, },
parseMapping() { parseSamplePayload() {
this.parsingPayload = true; this.samplePayload.loading = true;
return this.$apollo return this.$apollo
.query({ .query({
@ -303,7 +377,7 @@ export default {
this.resetPayloadAndMappingConfirmed = false; this.resetPayloadAndMappingConfirmed = false;
this.$toast.show( this.$toast.show(
this.$options.i18n.integrationFormSteps.setSamplePayload.payloadParsedSucessMsg, this.$options.i18n.integrationFormSteps.mapFields.payloadParsedSucessMsg,
); );
}, },
) )
@ -311,7 +385,7 @@ export default {
this.samplePayload.error = message; this.samplePayload.error = message;
}) })
.finally(() => { .finally(() => {
this.parsingPayload = false; this.samplePayload.loading = false;
}); });
}, },
updateMapping(mapping) { updateMapping(mapping) {
@ -338,7 +412,7 @@ export default {
<template> <template>
<gl-form class="gl-mt-6" @submit.prevent="submit" @reset.prevent="reset"> <gl-form class="gl-mt-6" @submit.prevent="submit" @reset.prevent="reset">
<gl-tabs v-model="activeTabIndex"> <gl-tabs v-model="activeTabIndex">
<gl-tab :title="$options.i18n.integrationTabs.configureDetails"> <gl-tab :title="$options.i18n.integrationTabs.configureDetails" class="gl-mt-3">
<gl-form-group <gl-form-group
v-if="isCreating" v-if="isCreating"
id="integration-type" id="integration-type"
@ -351,7 +425,7 @@ export default {
label-for="integration-type" label-for="integration-type"
> >
<gl-form-select <gl-form-select
v-model="selectedIntegration" v-model="integrationForm.type"
:disabled="isSelectDisabled" :disabled="isSelectDisabled"
class="gl-max-w-full" class="gl-max-w-full"
:options="integrationTypesOptions" :options="integrationTypesOptions"
@ -369,7 +443,6 @@ export default {
<div class="gl-mt-3"> <div class="gl-mt-3">
<gl-form-group <gl-form-group
v-if="isHttp" v-if="isHttp"
id="name-integration"
:label=" :label="
getLabelWithStepNumber( getLabelWithStepNumber(
$options.integrationSteps.nameIntegration, $options.integrationSteps.nameIntegration,
@ -377,67 +450,82 @@ export default {
) )
" "
label-for="name-integration" label-for="name-integration"
:invalid-feedback="$options.i18n.integrationFormSteps.nameIntegration.error"
:state="validationState.name"
> >
<gl-form-input <gl-form-input
id="name-integration"
ref="integrationName"
v-model="integrationForm.name" v-model="integrationForm.name"
type="text" type="text"
:placeholder="$options.i18n.integrationFormSteps.nameIntegration.placeholder" :placeholder="$options.i18n.integrationFormSteps.nameIntegration.placeholder"
@input="validateName"
/> />
</gl-form-group> </gl-form-group>
<gl-toggle <gl-form-group
v-model="active" v-if="!isNone"
:is-loading="loading" :label="
:label="$options.i18n.integrationFormSteps.nameIntegration.activeToggle" getLabelWithStepNumber(
class="gl-my-4 gl-font-weight-normal" isHttp
/> ? $options.integrationSteps.enableHttpIntegration
: $options.integrationSteps.enablePrometheusIntegration,
$options.i18n.integrationFormSteps.enableIntegration.label,
)
"
>
<span>{{ $options.i18n.integrationFormSteps.enableIntegration.help }}</span>
<div v-if="isPrometheus" class="gl-my-4"> <gl-toggle
<span class="gl-font-weight-bold"> id="enable-integration"
{{ v-model="integrationForm.active"
getLabelWithStepNumber( :is-loading="loading"
$options.integrationSteps.setPrometheusApiUrl, :label="$options.i18n.integrationFormSteps.nameIntegration.activeToggle"
$options.i18n.integrationFormSteps.prometheusFormUrl.label, class="gl-mt-4 gl-font-weight-normal"
) />
}} </gl-form-group>
</span>
<gl-form-group
v-if="isPrometheus"
class="gl-my-4"
:label="$options.i18n.integrationFormSteps.prometheusFormUrl.label"
label-for="api-url"
:invalid-feedback="prometheusUrlInvalidFeedback"
:state="validationState.apiUrl"
>
<gl-form-input <gl-form-input
id="integration-apiUrl" id="api-url"
v-model="integrationForm.apiUrl" v-model="integrationForm.apiUrl"
type="text" type="text"
:placeholder="$options.placeholders.prometheus" :placeholder="$options.placeholders.prometheus"
@input="validateApiUrl"
/> />
<span class="gl-text-gray-400"> <span class="gl-text-gray-400">
{{ $options.i18n.integrationFormSteps.prometheusFormUrl.help }} {{ $options.i18n.integrationFormSteps.prometheusFormUrl.help }}
</span> </span>
</div> </gl-form-group>
<template v-if="showMappingBuilder"> <template v-if="showMappingBuilder">
<gl-form-group <gl-form-group
data-testid="sample-payload-section" data-testid="sample-payload-section"
:label=" :label="
getLabelWithStepNumber( getLabelWithStepNumber(
$options.integrationSteps.setSamplePayload, $options.integrationSteps.customizeMapping,
$options.i18n.integrationFormSteps.setSamplePayload.label, $options.i18n.integrationFormSteps.mapFields.label,
) )
" "
label-for="sample-payload" label-for="sample-payload"
class="gl-mb-0!" class="gl-mb-0!"
:invalid-feedback="samplePayload.error" :invalid-feedback="samplePayload.error"
> >
<alert-settings-form-help-block <span>{{ $options.i18n.integrationFormSteps.mapFields.help }}</span>
:message="$options.i18n.integrationFormSteps.setSamplePayload.testPayloadHelpHttp"
:link="generic.alertsUsageUrl"
/>
<gl-form-textarea <gl-form-textarea
id="sample-payload" id="sample-payload"
v-model.trim="samplePayload.json" v-model="samplePayload.json"
:disabled="isPayloadEditDisabled" :disabled="canEditPayload"
:state="isSampePayloadValid" :state="isSampePayloadValid"
:placeholder="$options.i18n.integrationFormSteps.setSamplePayload.placeholder" :placeholder="$options.i18n.integrationFormSteps.mapFields.placeholder"
class="gl-my-3" class="gl-my-3"
:debounce="$options.JSON_VALIDATE_DELAY" :debounce="$options.JSON_VALIDATE_DELAY"
rows="6" rows="6"
@ -450,71 +538,76 @@ export default {
v-if="canEditPayload" v-if="canEditPayload"
v-gl-modal.resetPayloadModal v-gl-modal.resetPayloadModal
data-testid="payload-action-btn" data-testid="payload-action-btn"
:disabled="!active" :disabled="!integrationForm.active"
class="gl-mt-3" class="gl-mt-3"
> >
{{ $options.i18n.integrationFormSteps.setSamplePayload.editPayload }} {{ $options.i18n.integrationFormSteps.mapFields.editPayload }}
</gl-button> </gl-button>
<gl-button <gl-button
v-else v-else
data-testid="payload-action-btn" data-testid="payload-action-btn"
:class="{ 'gl-mt-3': samplePayload.error }" :class="{ 'gl-mt-3': samplePayload.error }"
:disabled="canParseSamplePayload" :disabled="!canParseSamplePayload"
:loading="parsingPayload" :loading="samplePayload.loading"
@click="parseMapping" @click="parseSamplePayload"
> >
{{ $options.i18n.integrationFormSteps.setSamplePayload.parsePayload }} {{ $options.i18n.integrationFormSteps.mapFields.parsePayload }}
</gl-button> </gl-button>
<gl-modal <gl-modal
modal-id="resetPayloadModal" modal-id="resetPayloadModal"
:title="$options.i18n.integrationFormSteps.setSamplePayload.resetHeader" :title="$options.i18n.integrationFormSteps.mapFields.resetHeader"
:ok-title="$options.i18n.integrationFormSteps.setSamplePayload.resetOk" :ok-title="$options.i18n.integrationFormSteps.mapFields.resetOk"
ok-variant="danger" ok-variant="danger"
@ok="resetPayloadAndMapping" @ok="resetPayloadAndMappingConfirmed = true"
> >
{{ $options.i18n.integrationFormSteps.setSamplePayload.resetBody }} {{ $options.i18n.integrationFormSteps.mapFields.resetBody }}
</gl-modal> </gl-modal>
<gl-form-group <div class="gl-mt-5">
id="mapping-builder" <span>{{ $options.i18n.integrationFormSteps.mapFields.mapIntro }}</span>
class="gl-mt-5"
:label="
getLabelWithStepNumber(
$options.integrationSteps.customizeMapping,
$options.i18n.integrationFormSteps.mapFields.label,
)
"
label-for="mapping-builder"
>
<span>{{ $options.i18n.integrationFormSteps.mapFields.intro }}</span>
<mapping-builder <mapping-builder
:parsed-payload="parsedPayload" :parsed-payload="parsedPayload"
:saved-mapping="mapping" :saved-mapping="mapping"
:alert-fields="alertFields" :alert-fields="alertFields"
@onMappingUpdate="updateMapping" @onMappingUpdate="updateMapping"
/> />
</gl-form-group> </div>
</template> </template>
</div> </div>
<div class="gl-display-flex gl-justify-content-start gl-py-3"> <div class="gl-display-flex gl-justify-content-start gl-py-3">
<gl-button <gl-button
type="submit" :disabled="!canSubmitForm"
variant="confirm" variant="confirm"
class="js-no-auto-disable" class="js-no-auto-disable"
data-testid="integration-form-submit" data-testid="integration-form-submit"
@click="submit(false)"
> >
{{ $options.i18n.saveIntegration }} {{ $options.i18n.saveIntegration }}
</gl-button> </gl-button>
<gl-button
:disabled="!canSubmitForm"
variant="confirm"
category="secondary"
class="gl-ml-3 js-no-auto-disable"
data-testid="integration-form-test-and-submit"
@click="submit(true)"
>
{{ $options.i18n.saveAndTestIntegration }}
</gl-button>
<gl-button type="reset" class="gl-ml-3 js-no-auto-disable">{{ <gl-button type="reset" class="gl-ml-3 js-no-auto-disable">{{
$options.i18n.cancelAndClose $options.i18n.cancelAndClose
}}</gl-button> }}</gl-button>
</div> </div>
</gl-tab> </gl-tab>
<gl-tab :title="$options.i18n.integrationTabs.viewCredentials" :disabled="isCreating"> <gl-tab
:title="$options.i18n.integrationTabs.viewCredentials"
:disabled="isCreating"
class="gl-mt-3"
>
<alert-settings-form-help-block <alert-settings-form-help-block
:message="viewCredentialsHelpMsg" :message="viewCredentialsHelpMsg"
link="https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html" link="https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html"
@ -559,13 +652,15 @@ export default {
</div> </div>
</gl-form-group> </gl-form-group>
<gl-button v-gl-modal.authKeyModal :disabled="isResetAuthKeyDisabled" variant="danger"> <div class="gl-display-flex gl-justify-content-start gl-py-3">
{{ $options.i18n.integrationFormSteps.setupCredentials.reset }} <gl-button v-gl-modal.authKeyModal variant="danger">
</gl-button> {{ $options.i18n.integrationFormSteps.setupCredentials.reset }}
</gl-button>
<gl-button type="reset" class="gl-ml-3 js-no-auto-disable">{{ <gl-button type="reset" class="gl-ml-3 js-no-auto-disable">
$options.i18n.cancelAndClose {{ $options.i18n.cancelAndClose }}
}}</gl-button> </gl-button>
</div>
<gl-modal <gl-modal
modal-id="authKeyModal" modal-id="authKeyModal"
@ -578,18 +673,22 @@ export default {
</gl-modal> </gl-modal>
</gl-tab> </gl-tab>
<gl-tab :title="$options.i18n.integrationTabs.sendTestAlert" :disabled="isCreating"> <gl-tab
:title="$options.i18n.integrationTabs.sendTestAlert"
:disabled="isCreating"
class="gl-mt-3"
>
<gl-form-group id="test-integration" :invalid-feedback="testPayload.error"> <gl-form-group id="test-integration" :invalid-feedback="testPayload.error">
<alert-settings-form-help-block <alert-settings-form-help-block
:message="$options.i18n.integrationFormSteps.setSamplePayload.testPayloadHelp" :message="$options.i18n.integrationFormSteps.testPayload.help"
:link="generic.alertsUsageUrl" :link="alertsUsageUrl"
/> />
<gl-form-textarea <gl-form-textarea
id="test-payload" id="test-payload"
v-model.trim="testPayload.json" v-model="testPayload.json"
:state="isTestPayloadValid" :state="isTestPayloadValid"
:placeholder="$options.i18n.integrationFormSteps.setSamplePayload.placeholder" :placeholder="$options.i18n.integrationFormSteps.testPayload.placeholder"
class="gl-my-3" class="gl-my-3"
:debounce="$options.JSON_VALIDATE_DELAY" :debounce="$options.JSON_VALIDATE_DELAY"
rows="6" rows="6"
@ -597,20 +696,35 @@ export default {
@input="validateJson(false)" @input="validateJson(false)"
/> />
</gl-form-group> </gl-form-group>
<div class="gl-display-flex gl-justify-content-start gl-py-3">
<gl-button
v-gl-modal="testAlertModal"
:disabled="!isTestPayloadValid"
:loading="loading"
data-testid="send-test-alert"
variant="confirm"
class="js-no-auto-disable"
@click="isFormDirty ? null : sendTestAlert()"
>
{{ $options.i18n.send }}
</gl-button>
<gl-button <gl-button type="reset" class="gl-ml-3 js-no-auto-disable">
:disabled="!isTestPayloadValid" {{ $options.i18n.cancelAndClose }}
data-testid="send-test-alert" </gl-button>
variant="confirm" </div>
class="js-no-auto-disable"
@click="sendTestAlert" <gl-modal
:modal-id="$options.testAlertModalId"
:title="$options.i18n.integrationFormSteps.testPayload.modalTitle"
:action-primary="$options.primaryProps"
:action-secondary="$options.secondaryProps"
:action-cancel="$options.cancelProps"
@primary="saveAndSendTestAlert"
@secondary="sendTestAlert"
> >
{{ $options.i18n.send }} {{ $options.i18n.integrationFormSteps.testPayload.modalBody }}
</gl-button> </gl-modal>
<gl-button type="reset" class="gl-ml-3 js-no-auto-disable">{{
$options.i18n.cancelAndClose
}}</gl-button>
</gl-tab> </gl-tab>
</gl-tabs> </gl-tabs>
</gl-form> </gl-form>

View file

@ -1,11 +1,11 @@
<script> <script>
import { GlButton } from '@gitlab/ui'; import { GlButton, GlAlert } from '@gitlab/ui';
import createHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql'; import createHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql';
import updateHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql'; import updateHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql';
import createFlash, { FLASH_TYPES } from '~/flash'; import createFlash, { FLASH_TYPES } from '~/flash';
import { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
import { s__ } from '~/locale'; import httpStatusCodes from '~/lib/utils/http_status';
import { typeSet } from '../constants'; import { typeSet, i18n, tabIndices } from '../constants';
import createPrometheusIntegrationMutation from '../graphql/mutations/create_prometheus_integration.mutation.graphql'; import createPrometheusIntegrationMutation from '../graphql/mutations/create_prometheus_integration.mutation.graphql';
import destroyHttpIntegrationMutation from '../graphql/mutations/destroy_http_integration.mutation.graphql'; import destroyHttpIntegrationMutation from '../graphql/mutations/destroy_http_integration.mutation.graphql';
import resetHttpTokenMutation from '../graphql/mutations/reset_http_token.mutation.graphql'; import resetHttpTokenMutation from '../graphql/mutations/reset_http_token.mutation.graphql';
@ -28,21 +28,12 @@ import {
RESET_INTEGRATION_TOKEN_ERROR, RESET_INTEGRATION_TOKEN_ERROR,
UPDATE_INTEGRATION_ERROR, UPDATE_INTEGRATION_ERROR,
INTEGRATION_PAYLOAD_TEST_ERROR, INTEGRATION_PAYLOAD_TEST_ERROR,
INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR,
DEFAULT_ERROR,
} from '../utils/error_messages'; } from '../utils/error_messages';
import IntegrationsList from './alerts_integrations_list.vue'; import IntegrationsList from './alerts_integrations_list.vue';
import AlertSettingsForm from './alerts_settings_form.vue'; import AlertSettingsForm from './alerts_settings_form.vue';
export const i18n = {
changesSaved: s__(
'AlertsIntegrations|The integration has been successfully saved. Alerts from this new integration should now appear on your alerts list.',
),
integrationRemoved: s__('AlertsIntegrations|The integration has been successfully removed.'),
alertSent: s__(
'AlertsIntegrations|The test alert has been successfully sent, and should now be visible on your alerts list.',
),
addNewIntegration: s__('AlertSettings|Add new integration'),
};
export default { export default {
typeSet, typeSet,
i18n, i18n,
@ -50,14 +41,9 @@ export default {
IntegrationsList, IntegrationsList,
AlertSettingsForm, AlertSettingsForm,
GlButton, GlButton,
GlAlert,
}, },
inject: { inject: {
generic: {
default: {},
},
prometheus: {
default: {},
},
projectPath: { projectPath: {
default: '', default: '',
}, },
@ -124,7 +110,10 @@ export default {
integrations: {}, integrations: {},
httpIntegrations: {}, httpIntegrations: {},
currentIntegration: null, currentIntegration: null,
newIntegration: null,
formVisible: false, formVisible: false,
showSuccessfulCreateAlert: false,
tabIndex: tabIndices.configureDetails,
}; };
}, },
computed: { computed: {
@ -139,10 +128,10 @@ export default {
isHttp(type) { isHttp(type) {
return type === typeSet.http; return type === typeSet.http;
}, },
createNewIntegration({ type, variables }) { createNewIntegration({ type, variables }, testAfterSubmit) {
const { projectPath } = this; const { projectPath } = this;
const isHttp = this.isHttp(type); const isHttp = this.isHttp(type);
this.isUpdating = true; this.isUpdating = true;
this.$apollo this.$apollo
.mutate({ .mutate({
@ -163,16 +152,19 @@ export default {
.then(({ data: { httpIntegrationCreate, prometheusIntegrationCreate } = {} } = {}) => { .then(({ data: { httpIntegrationCreate, prometheusIntegrationCreate } = {} } = {}) => {
const error = httpIntegrationCreate?.errors[0] || prometheusIntegrationCreate?.errors[0]; const error = httpIntegrationCreate?.errors[0] || prometheusIntegrationCreate?.errors[0];
if (error) { if (error) {
return createFlash({ message: error }); createFlash({ message: error });
return;
} }
const { integration } = httpIntegrationCreate || prometheusIntegrationCreate; const { integration } = httpIntegrationCreate || prometheusIntegrationCreate;
this.newIntegration = integration;
this.showSuccessfulCreateAlert = true;
this.editIntegration(integration); if (testAfterSubmit) {
this.viewIntegration(this.newIntegration, tabIndices.sendTestAlert);
return createFlash({ } else {
message: this.$options.i18n.changesSaved, this.setFormVisibility(false);
type: FLASH_TYPES.SUCCESS, }
});
}) })
.catch(() => { .catch(() => {
createFlash({ message: ADD_INTEGRATION_ERROR }); createFlash({ message: ADD_INTEGRATION_ERROR });
@ -181,9 +173,9 @@ export default {
this.isUpdating = false; this.isUpdating = false;
}); });
}, },
updateIntegration({ type, variables }) { updateIntegration({ type, variables }, testAfterSubmit) {
this.isUpdating = true; this.isUpdating = true;
this.$apollo return this.$apollo
.mutate({ .mutate({
mutation: this.isHttp(type) mutation: this.isHttp(type)
? updateHttpIntegrationMutation ? updateHttpIntegrationMutation
@ -196,12 +188,20 @@ export default {
.then(({ data: { httpIntegrationUpdate, prometheusIntegrationUpdate } = {} } = {}) => { .then(({ data: { httpIntegrationUpdate, prometheusIntegrationUpdate } = {} } = {}) => {
const error = httpIntegrationUpdate?.errors[0] || prometheusIntegrationUpdate?.errors[0]; const error = httpIntegrationUpdate?.errors[0] || prometheusIntegrationUpdate?.errors[0];
if (error) { if (error) {
return createFlash({ message: error }); createFlash({ message: error });
return;
} }
this.clearCurrentIntegration({ type }); const integration =
httpIntegrationUpdate?.integration || prometheusIntegrationUpdate?.integration;
return createFlash({ if (testAfterSubmit) {
this.viewIntegration(integration, tabIndices.sendTestAlert);
} else {
this.clearCurrentIntegration(type);
}
createFlash({
message: this.$options.i18n.changesSaved, message: this.$options.i18n.changesSaved,
type: FLASH_TYPES.SUCCESS, type: FLASH_TYPES.SUCCESS,
}); });
@ -261,13 +261,23 @@ export default {
currentIntegration = { ...currentIntegration, ...httpIntegrationMappingData }; currentIntegration = { ...currentIntegration, ...httpIntegrationMappingData };
} }
this.$apollo.mutate({ this.viewIntegration(currentIntegration, tabIndices.viewCredentials);
mutation: this.isHttp(type) },
? updateCurrentHttpIntegrationMutation viewIntegration(integration, tabIndex) {
: updateCurrentPrometheusIntegrationMutation, this.$apollo
variables: currentIntegration, .mutate({
}); mutation: this.isHttp(integration.type)
this.setFormVisibility(true); ? updateCurrentHttpIntegrationMutation
: updateCurrentPrometheusIntegrationMutation,
variables: integration,
})
.then(() => {
this.setFormVisibility(true);
this.tabIndex = tabIndex;
})
.catch(() => {
createFlash({ message: DEFAULT_ERROR });
});
}, },
deleteIntegration({ id, type }) { deleteIntegration({ id, type }) {
const { projectPath } = this; const { projectPath } = this;
@ -319,19 +329,44 @@ export default {
type: FLASH_TYPES.SUCCESS, type: FLASH_TYPES.SUCCESS,
}); });
}) })
.catch(() => { .catch((error) => {
createFlash({ message: INTEGRATION_PAYLOAD_TEST_ERROR }); let message = INTEGRATION_PAYLOAD_TEST_ERROR;
if (error.response?.status === httpStatusCodes.FORBIDDEN) {
message = INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR;
}
createFlash({ message });
}); });
}, },
saveAndTestAlertPayload(integration, payload) {
return this.updateIntegration(integration, false).then(() => {
this.testAlertPayload(payload);
});
},
setFormVisibility(visible) { setFormVisibility(visible) {
this.formVisible = visible; this.formVisible = visible;
}, },
viewCreatedIntegration() {
this.viewIntegration(this.newIntegration, tabIndices.viewCredentials);
this.showSuccessfulCreateAlert = false;
this.newIntegration = null;
},
}, },
}; };
</script> </script>
<template> <template>
<div> <div>
<gl-alert
v-if="showSuccessfulCreateAlert"
class="gl-mt-n2"
:primary-button-text="$options.i18n.integrationCreated.btnCaption"
:title="$options.i18n.integrationCreated.title"
@primaryAction="viewCreatedIntegration"
@dismiss="showSuccessfulCreateAlert = false"
>
{{ $options.i18n.integrationCreated.successMsg }}
</gl-alert>
<integrations-list <integrations-list
:integrations="integrations.list" :integrations="integrations.list"
:loading="loading" :loading="loading"
@ -353,11 +388,13 @@ export default {
:loading="isUpdating" :loading="isUpdating"
:can-add-integration="canAddIntegration" :can-add-integration="canAddIntegration"
:alert-fields="alertFields" :alert-fields="alertFields"
:tab-index="tabIndex"
@create-new-integration="createNewIntegration" @create-new-integration="createNewIntegration"
@update-integration="updateIntegration" @update-integration="updateIntegration"
@reset-token="resetToken" @reset-token="resetToken"
@clear-current-integration="clearCurrentIntegration" @clear-current-integration="clearCurrentIntegration"
@test-alert-payload="testAlertPayload" @test-alert-payload="testAlertPayload"
@save-and-test-alert-payload="saveAndTestAlertPayload"
/> />
</div> </div>
</template> </template>

View file

@ -10,96 +10,118 @@ export const i18n = {
selectType: { selectType: {
label: s__('AlertSettings|Select integration type'), label: s__('AlertSettings|Select integration type'),
enterprise: s__( enterprise: s__(
'AlertSettings|In free versions of GitLab, only one integration for each type can be added. %{linkStart}Upgrade your subscription%{linkEnd} to add additional integrations.', 'AlertSettings|Free versions of GitLab are limited to one integration per type. To add more, %{linkStart}upgrade your subscription%{linkEnd}.',
), ),
}, },
nameIntegration: { nameIntegration: {
label: s__('AlertSettings|Name integration'), label: s__('AlertSettings|Name integration'),
placeholder: s__('AlertSettings|Enter integration name'), placeholder: s__('AlertSettings|Enter integration name'),
activeToggle: __('Active'), activeToggle: __('Active'),
error: __("Name can't be blank"),
},
enableIntegration: {
label: s__('AlertSettings|Enable integration'),
help: s__(
'AlertSettings|A webhook URL and authorization key is generated for the integration. After you save the integration, both are visible under the “View credentials” tab.',
),
}, },
setupCredentials: { setupCredentials: {
help: s__( help: s__(
"AlertSettings|Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.", 'AlertSettings|Use the URL and authorization key below to configure how an external service sends alerts to GitLab. %{linkStart}How do I configure the endpoint?%{linkEnd}',
), ),
prometheusHelp: s__( prometheusHelp: s__(
'AlertSettings|Utilize the URL and authorization key below to authorize Prometheus to send alerts to GitLab. Review the Prometheus documentation to learn where to add these details, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.', 'AlertSettings|Use the URL and authorization key below to configure how Prometheus sends alerts to GitLab. Review the %{linkStart}GitLab documentation%{linkEnd} to learn how to configure your endpoint.',
), ),
webhookUrl: s__('AlertSettings|Webhook URL'), webhookUrl: s__('AlertSettings|Webhook URL'),
authorizationKey: s__('AlertSettings|Authorization key'), authorizationKey: s__('AlertSettings|Authorization key'),
reset: s__('AlertSettings|Reset Key'), reset: s__('AlertSettings|Reset Key'),
}, },
setSamplePayload: { mapFields: {
label: s__('AlertSettings|Sample alert payload (optional)'), label: s__('AlertSettings|Customize alert payload mapping (optional)'),
testPayloadHelpHttp: s__( help: s__(
'AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to create a custom mapping (optional).', 'AlertSettings|To create a custom mapping, enter an example payload from your monitoring tool, in JSON format. Select the "Parse payload fields" button to continue.',
),
testPayloadHelp: s__(
'AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with. This will allow you to send an alert to an active GitLab alerting point.',
), ),
placeholder: s__('AlertSettings|{ "events": [{ "application": "Name of application" }] }'), placeholder: s__('AlertSettings|{ "events": [{ "application": "Name of application" }] }'),
resetHeader: s__('AlertSettings|Reset the mapping'),
resetBody: s__(
"AlertSettings|If you edit the payload, the stored mapping will be reset, and you'll need to re-map the fields.",
),
resetOk: s__('AlertSettings|Proceed with editing'),
editPayload: s__('AlertSettings|Edit payload'), editPayload: s__('AlertSettings|Edit payload'),
parsePayload: s__('AlertSettings|Parse payload for custom mapping'), parsePayload: s__('AlertSettings|Parse payload fields'),
payloadParsedSucessMsg: s__( payloadParsedSucessMsg: s__(
'AlertSettings|Sample payload has been parsed. You can now map the fields.', 'AlertSettings|Sample payload has been parsed. You can now map the fields.',
), ),
}, resetHeader: s__('AlertSettings|Reset the mapping'),
mapFields: { resetBody: s__('AlertSettings|If you edit the payload, you must re-map the fields again.'),
label: s__('AlertSettings|Customize alert payload mapping (optional)'), resetOk: s__('AlertSettings|Proceed with editing'),
intro: s__( mapIntro: s__(
'AlertSettings|If you intend to create a custom mapping, provide an example payload from your monitoring tool and click "parse payload fields" button to continue. The sample payload is required for completing the custom mapping; if you want to skip the mapping step, progress straight to saving your integration.', 'AlertSettings|You can map default GitLab alert fields to your payload keys in the dropdowns below.',
), ),
}, },
testPayload: {
help: s__(
'AlertSettings|Enter an example payload from your selected monitoring tool. This supports sending alerts to a GitLab endpoint.',
),
placeholder: s__('AlertSettings|{ "events": [{ "application": "Name of application" }] }'),
modalTitle: s__('AlertSettings|The form has unsaved changes'),
modalBody: s__('AlertSettings|The form has unsaved changes. How would you like to proceed?'),
savedAndTest: s__('AlertSettings|Save integration & send'),
proceedWithoutSave: s__('AlertSettings|Send without saving'),
cancel: __('Cancel'),
},
prometheusFormUrl: { prometheusFormUrl: {
label: s__('AlertSettings|Prometheus API base URL'), label: s__('AlertSettings|Prometheus API base URL'),
help: s__('AlertSettings|URL cannot be blank and must start with http or https'), help: s__('AlertSettings|URL cannot be blank and must start with http: or https:.'),
blankUrlError: __('URL cannot be blank'),
invalidUrlError: __('URL is invalid'),
}, },
restKeyInfo: { restKeyInfo: {
label: s__( label: s__(
'AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.', 'AlertSettings|If you reset the authorization key for this project, you must update the key in every enabled alert source.',
), ),
}, },
}, },
saveIntegration: s__('AlertSettings|Save integration'), saveIntegration: s__('AlertSettings|Save integration'),
changesSaved: s__('AlertSettings|Your integration was successfully updated.'), saveAndTestIntegration: s__('AlertSettings|Save & create test alert'),
cancelAndClose: __('Cancel and close'), cancelAndClose: __('Cancel and close'),
send: s__('AlertSettings|Send'), send: __('Send'),
copy: __('Copy'), copy: __('Copy'),
integrationCreated: {
title: s__('AlertSettings|Integration successfully saved'),
successMsg: s__(
'AlertSettings|GitLab has created a URL and authorization key for your integration. You can use them to set up a webhook and authorize your endpoint to send alerts to GitLab.',
),
btnCaption: s__('AlertSettings|View URL and authorization key'),
},
changesSaved: s__('AlertsIntegrations|The integration is saved.'),
integrationRemoved: s__('AlertsIntegrations|The integration is deleted.'),
alertSent: s__('AlertsIntegrations|The test alert should now be visible in your alerts list.'),
addNewIntegration: s__('AlertSettings|Add new integration'),
}; };
export const integrationSteps = { export const integrationSteps = {
selectType: 'SELECT_TYPE', selectType: 'SELECT_TYPE',
nameIntegration: 'NAME_INTEGRATION', nameIntegration: 'NAME_INTEGRATION',
setPrometheusApiUrl: 'SET_PROMETHEUS_API_URL', enableHttpIntegration: 'ENABLE_HTTP_INTEGRATION',
setSamplePayload: 'SET_SAMPLE_PAYLOAD', enablePrometheusIntegration: 'ENABLE_PROMETHEUS_INTEGRATION',
customizeMapping: 'CUSTOMIZE_MAPPING', customizeMapping: 'CUSTOMIZE_MAPPING',
}; };
export const createStepNumbers = { export const createStepNumbers = {
[integrationSteps.selectType]: 1, [integrationSteps.selectType]: 1,
[integrationSteps.nameIntegration]: 2, [integrationSteps.nameIntegration]: 2,
[integrationSteps.setPrometheusApiUrl]: 2, [integrationSteps.enableHttpIntegration]: 3,
[integrationSteps.setSamplePayload]: 3, [integrationSteps.enablePrometheusIntegration]: 2,
[integrationSteps.customizeMapping]: 4, [integrationSteps.customizeMapping]: 4,
}; };
export const editStepNumbers = { export const editStepNumbers = {
[integrationSteps.selectType]: 1,
[integrationSteps.nameIntegration]: 1, [integrationSteps.nameIntegration]: 1,
[integrationSteps.setPrometheusApiUrl]: null, [integrationSteps.enableHttpIntegration]: 2,
[integrationSteps.setSamplePayload]: 2, [integrationSteps.enablePrometheusIntegration]: null,
[integrationSteps.customizeMapping]: 3, [integrationSteps.customizeMapping]: 3,
}; };
export const integrationTypes = { export const integrationTypes = {
none: { value: '', text: s__('AlertSettings|Select integration type') }, none: { value: '', text: s__('AlertSettings|Select integration type') },
http: { value: 'HTTP', text: s__('AlertSettings|HTTP Endpoint') }, http: { value: 'HTTP', text: s__('AlertSettings|HTTP Endpoint') },
prometheus: { value: 'PROMETHEUS', text: s__('AlertSettings|External Prometheus') }, prometheus: { value: 'PROMETHEUS', text: s__('AlertSettings|Prometheus') },
}; };
export const typeSet = { export const typeSet = {
@ -127,4 +149,10 @@ export const mappingFields = {
fallback: 'fallback', fallback: 'fallback',
}; };
export const viewCredentialsTabIndex = 1; export const tabIndices = {
configureDetails: 0,
viewCredentials: 1,
sendTestAlert: 2,
};
export const testAlertModalId = 'confirmSendTestAlert';

View file

@ -3,12 +3,15 @@ import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import AlertSettingsWrapper from './components/alerts_settings_wrapper.vue'; import AlertSettingsWrapper from './components/alerts_settings_wrapper.vue';
import apolloProvider from './graphql'; import apolloProvider from './graphql';
import getCurrentIntegrationQuery from './graphql/queries/get_current_integration.query.graphql';
apolloProvider.clients.defaultClient.cache.writeData({ apolloProvider.clients.defaultClient.cache.writeQuery({
query: getCurrentIntegrationQuery,
data: { data: {
currentIntegration: null, currentIntegration: null,
}, },
}); });
Vue.use(GlToast); Vue.use(GlToast);
export default (el) => { export default (el) => {
@ -16,23 +19,7 @@ export default (el) => {
return null; return null;
} }
const { const { alertsUsageUrl, projectPath, multiIntegrations, alertFields } = el.dataset;
prometheusActivated,
prometheusUrl,
prometheusAuthorizationKey,
prometheusFormPath,
prometheusResetKeyPath,
prometheusApiUrl,
activated: activatedStr,
alertsSetupUrl,
alertsUsageUrl,
formPath,
authorizationKey,
url,
projectPath,
multiIntegrations,
alertFields,
} = el.dataset;
return new Vue({ return new Vue({
el, el,
@ -40,22 +27,7 @@ export default (el) => {
AlertSettingsWrapper, AlertSettingsWrapper,
}, },
provide: { provide: {
prometheus: { alertsUsageUrl,
active: parseBoolean(prometheusActivated),
url: prometheusUrl,
token: prometheusAuthorizationKey,
prometheusFormPath,
prometheusResetKeyPath,
prometheusApiUrl,
},
generic: {
alertsSetupUrl,
alertsUsageUrl,
active: parseBoolean(activatedStr),
formPath,
token: authorizationKey,
url,
},
projectPath, projectPath,
multiIntegrations: parseBoolean(multiIntegrations), multiIntegrations: parseBoolean(multiIntegrations),
}, },

View file

@ -1,4 +1,4 @@
import { s__ } from '~/locale'; import { s__, __ } from '~/locale';
export const DELETE_INTEGRATION_ERROR = s__( export const DELETE_INTEGRATION_ERROR = s__(
'AlertsIntegrations|The integration could not be deleted. Please try again.', 'AlertsIntegrations|The integration could not be deleted. Please try again.',
@ -19,3 +19,9 @@ export const RESET_INTEGRATION_TOKEN_ERROR = s__(
export const INTEGRATION_PAYLOAD_TEST_ERROR = s__( export const INTEGRATION_PAYLOAD_TEST_ERROR = s__(
'AlertsIntegrations|Integration payload is invalid.', 'AlertsIntegrations|Integration payload is invalid.',
); );
export const INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR = s__(
'AlertsIntegrations|The integration is currently inactive. Enable the integration to send the test alert.',
);
export const DEFAULT_ERROR = __('Something went wrong on our end.');

View file

@ -83,7 +83,7 @@ export default [
'UsageTrends|Could not load the issues and merge requests chart. Please refresh the page to try again.', 'UsageTrends|Could not load the issues and merge requests chart. Please refresh the page to try again.',
), ),
noDataMessage, noDataMessage,
chartTitle: s__('UsageTrends|Issues & Merge Requests'), chartTitle: s__('UsageTrends|Issues & merge requests'),
yAxisTitle: s__('UsageTrends|Items'), yAxisTitle: s__('UsageTrends|Items'),
xAxisTitle: s__('UsageTrends|Month'), xAxisTitle: s__('UsageTrends|Month'),
queries: [ queries: [

View file

@ -45,7 +45,7 @@ export default {
projects: s__('UsageTrends|Projects'), projects: s__('UsageTrends|Projects'),
groups: s__('UsageTrends|Groups'), groups: s__('UsageTrends|Groups'),
issues: s__('UsageTrends|Issues'), issues: s__('UsageTrends|Issues'),
mergeRequests: s__('UsageTrends|Merge Requests'), mergeRequests: s__('UsageTrends|Merge requests'),
pipelines: s__('UsageTrends|Pipelines'), pipelines: s__('UsageTrends|Pipelines'),
}, },
loadCountsError: s__('Could not load usage counts. Please refresh the page to try again.'), loadCountsError: s__('Could not load usage counts. Please refresh the page to try again.'),

View file

@ -44,7 +44,7 @@ const Api = {
projectMilestonesPath: '/api/:version/projects/:id/milestones', projectMilestonesPath: '/api/:version/projects/:id/milestones',
projectIssuePath: '/api/:version/projects/:id/issues/:issue_iid', projectIssuePath: '/api/:version/projects/:id/issues/:issue_iid',
mergeRequestsPath: '/api/:version/merge_requests', mergeRequestsPath: '/api/:version/merge_requests',
groupLabelsPath: '/groups/:namespace_path/-/labels', groupLabelsPath: '/api/:version/groups/:namespace_path/labels',
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key', issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
issuableTemplatesPath: '/:namespace_path/:project_path/templates/:type', issuableTemplatesPath: '/:namespace_path/:project_path/templates/:type',
projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key', projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key',
@ -79,6 +79,7 @@ const Api = {
issuePath: '/api/:version/projects/:id/issues/:issue_iid', issuePath: '/api/:version/projects/:id/issues/:issue_iid',
tagsPath: '/api/:version/projects/:id/repository/tags', tagsPath: '/api/:version/projects/:id/repository/tags',
freezePeriodsPath: '/api/:version/projects/:id/freeze_periods', freezePeriodsPath: '/api/:version/projects/:id/freeze_periods',
freezePeriodPath: '/api/:version/projects/:id/freeze_periods/:freeze_period_id',
usageDataIncrementCounterPath: '/api/:version/usage_data/increment_counter', usageDataIncrementCounterPath: '/api/:version/usage_data/increment_counter',
usageDataIncrementUniqueUsersPath: '/api/:version/usage_data/increment_unique_users', usageDataIncrementUniqueUsersPath: '/api/:version/usage_data/increment_unique_users',
featureFlagUserLists: '/api/:version/projects/:id/feature_flags_user_lists', featureFlagUserLists: '/api/:version/projects/:id/feature_flags_user_lists',
@ -282,7 +283,7 @@ const Api = {
}, },
/** /**
* Get all Merge Requests for a project, eventually filtering based on * Get all merge requests for a project, eventually filtering based on
* supplied parameters * supplied parameters
* @param projectPath * @param projectPath
* @param params * @param params
@ -306,7 +307,7 @@ const Api = {
return axios.post(url, options); return axios.post(url, options);
}, },
// Return Merge Request for project // Return merge request for project
projectMergeRequest(projectPath, mergeRequestId, params = {}) { projectMergeRequest(projectPath, mergeRequestId, params = {}) {
const url = Api.buildUrl(Api.projectMergeRequestPath) const url = Api.buildUrl(Api.projectMergeRequestPath)
.replace(':id', encodeURIComponent(projectPath)) .replace(':id', encodeURIComponent(projectPath))
@ -401,18 +402,29 @@ const Api = {
newLabel(namespacePath, projectPath, data, callback) { newLabel(namespacePath, projectPath, data, callback) {
let url; let url;
let payload;
if (projectPath) { if (projectPath) {
url = Api.buildUrl(Api.projectLabelsPath) url = Api.buildUrl(Api.projectLabelsPath)
.replace(':namespace_path', namespacePath) .replace(':namespace_path', namespacePath)
.replace(':project_path', projectPath); .replace(':project_path', projectPath);
payload = {
label: data,
};
} else { } else {
url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath); url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
// groupLabelsPath uses public API which accepts
// `name` and `color` props.
payload = {
name: data.title,
color: data.color,
};
} }
return axios return axios
.post(url, { .post(url, {
label: data, ...payload,
}) })
.then((res) => callback(res.data)) .then((res) => callback(res.data))
.catch((e) => callback(e.response.data)); .catch((e) => callback(e.response.data));
@ -784,7 +796,7 @@ const Api = {
return axios.delete(url, { data }); return axios.delete(url, { data });
}, },
getRawFile(id, path, params = { ref: 'master' }) { getRawFile(id, path, params = {}) {
const url = Api.buildUrl(this.rawFilePath) const url = Api.buildUrl(this.rawFilePath)
.replace(':id', encodeURIComponent(id)) .replace(':id', encodeURIComponent(id))
.replace(':path', encodeURIComponent(path)); .replace(':path', encodeURIComponent(path));
@ -832,6 +844,14 @@ const Api = {
return axios.post(url, freezePeriod); return axios.post(url, freezePeriod);
}, },
updateFreezePeriod(id, freezePeriod = {}) {
const url = Api.buildUrl(this.freezePeriodPath)
.replace(':id', encodeURIComponent(id))
.replace(':freeze_period_id', encodeURIComponent(freezePeriod.id));
return axios.put(url, freezePeriod);
},
trackRedisCounterEvent(event) { trackRedisCounterEvent(event) {
if (!gon.features?.usageDataApi) { if (!gon.features?.usageDataApi) {
return null; return null;

View file

@ -55,12 +55,13 @@ export function getUserProjects(userId, query, options, callback) {
.catch(() => flash(__('Something went wrong while fetching projects'))); .catch(() => flash(__('Something went wrong while fetching projects')));
} }
export function updateUserStatus({ emoji, message, availability }) { export function updateUserStatus({ emoji, message, availability, clearStatusAfter }) {
const url = buildApiUrl(USER_POST_STATUS_PATH); const url = buildApiUrl(USER_POST_STATUS_PATH);
return axios.put(url, { return axios.put(url, {
emoji, emoji,
message, message,
availability, availability,
clear_status_after: clearStatusAfter,
}); });
} }

View file

@ -446,7 +446,7 @@ export class AwardsHandler {
createAwardButtonForVotesBlock(votesBlock, emojiName) { createAwardButtonForVotesBlock(votesBlock, emojiName) {
const buttonHtml = ` const buttonHtml = `
<button class="btn award-control js-emoji-btn has-tooltip active" title="You"> <button class="gl-button btn btn-default award-control js-emoji-btn has-tooltip active" title="You">
${this.emoji.glEmojiTag(emojiName)} ${this.emoji.glEmojiTag(emojiName)}
<span class="award-control-text js-counter">1</span> <span class="award-control-text js-counter">1</span>
</button> </button>

View file

@ -1,7 +1,11 @@
<script> <script>
import { GlLoadingIcon, GlTooltipDirective, GlIcon, GlButton } from '@gitlab/ui'; import { GlLoadingIcon, GlTooltipDirective, GlIcon, GlButton } from '@gitlab/ui';
import { s__ } from '~/locale';
export default { export default {
i18n: {
buttonLabel: s__('Badges|Reload badge image'),
},
// name: 'Badge' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25 // name: 'Badge' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
// eslint-disable-next-line @gitlab/require-i18n-strings // eslint-disable-next-line @gitlab/require-i18n-strings
name: 'Badge', name: 'Badge',
@ -94,7 +98,8 @@ export default {
<gl-button <gl-button
v-show="hasError" v-show="hasError"
v-gl-tooltip.hover v-gl-tooltip.hover
:title="s__('Badges|Reload badge image')" :title="$options.i18n.buttonLabel"
:aria-label="$options.i18n.buttonLabel"
category="tertiary" category="tertiary"
variant="confirm" variant="confirm"
type="button" type="button"

View file

@ -41,13 +41,17 @@ export default {
titleText() { titleText() {
const file = this.discussion ? this.discussion.diff_file : this.draft; const file = this.discussion ? this.discussion.diff_file : this.draft;
if (file) { if (file?.file_path) {
return file.file_path; return file.file_path;
} }
return sprintf(__("%{authorsName}'s thread"), { if (this.discussion) {
authorsName: this.discussion.notes.find((note) => !note.system).author.name, return sprintf(__("%{authorsName}'s thread"), {
}); authorsName: this.discussion.notes.find((note) => !note.system).author.name,
});
}
return __('Your new comment');
}, },
linePosition() { linePosition() {
if (this.position?.position_type === IMAGE_DIFF_POSITION_TYPE) { if (this.position?.position_type === IMAGE_DIFF_POSITION_TYPE) {
@ -94,7 +98,7 @@ export default {
<span class="review-preview-item-header"> <span class="review-preview-item-header">
<gl-icon class="flex-shrink-0" :name="iconName" /> <gl-icon class="flex-shrink-0" :name="iconName" />
<span class="bold text-nowrap gl-align-items-center"> <span class="bold text-nowrap gl-align-items-center">
<span class="review-preview-item-header-text block-truncated"> <span class="review-preview-item-header-text block-truncated gl-ml-2">
{{ titleText }} {{ titleText }}
</span> </span>
<template v-if="showLinePosition"> <template v-if="showLinePosition">

View file

@ -1,9 +1,7 @@
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { sprintf, s__, __ } from '~/locale'; import { sprintf, s__, __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default { export default {
mixins: [glFeatureFlagsMixin()],
props: { props: {
discussionId: { discussionId: {
type: String, type: String,
@ -52,16 +50,18 @@ export default {
return this.resolveDiscussion ? 'is-resolving-discussion' : 'is-unresolving-discussion'; return this.resolveDiscussion ? 'is-resolving-discussion' : 'is-unresolving-discussion';
}, },
resolveButtonTitle() { resolveButtonTitle() {
const escapeParameters = false;
if (this.isDraft || this.discussionId) return this.resolvedStatusMessage; if (this.isDraft || this.discussionId) return this.resolvedStatusMessage;
let title = __('Mark as resolved'); let title = __('Resolve thread');
if (this.glFeatures.removeResolveNote) {
title = __('Resolve thread');
}
if (this.resolvedBy) { if (this.resolvedBy) {
title = sprintf(__('Resolved by %{name}'), { name: this.resolvedBy.name }); title = sprintf(
__('Resolved by %{name}'),
{ name: this.resolvedBy.name },
escapeParameters,
);
} }
return title; return title;

View file

@ -0,0 +1,15 @@
import $ from 'jquery';
export default function initDeprecatedRemoveRowBehavior() {
$('.js-remove-row').on('ajax:success', function removeRowAjaxSuccessCallback() {
$(this).closest('li').addClass('gl-display-none!');
});
$('.js-remove-tr').on('ajax:before', function removeTRAjaxBeforeCallback() {
$(this).parent().find('.btn').addClass('disabled');
});
$('.js-remove-tr').on('ajax:success', function removeTRAjaxSuccessCallback() {
$(this).closest('tr').addClass('gl-display-none!');
});
}

View file

@ -1,6 +1,7 @@
import $ from 'jquery'; import $ from 'jquery';
import { once } from 'lodash'; import { once } from 'lodash';
import { deprecatedCreateFlash as flash } from '~/flash'; import { deprecatedCreateFlash as flash } from '~/flash';
import { darkModeEnabled } from '~/lib/utils/color_utils';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
// Renders diagrams and flowcharts from text using Mermaid in any element with the // Renders diagrams and flowcharts from text using Mermaid in any element with the
@ -27,37 +28,34 @@ let renderedMermaidBlocks = 0;
let mermaidModule = {}; let mermaidModule = {};
export function initMermaid(mermaid) {
let theme = 'neutral';
if (darkModeEnabled()) {
theme = 'dark';
}
mermaid.initialize({
// mermaid core options
mermaid: {
startOnLoad: false,
},
// mermaidAPI options
theme,
flowchart: {
useMaxWidth: true,
htmlLabels: false,
},
securityLevel: 'strict',
});
return mermaid;
}
function importMermaidModule() { function importMermaidModule() {
return import(/* webpackChunkName: 'mermaid' */ 'mermaid') return import(/* webpackChunkName: 'mermaid' */ 'mermaid')
.then((mermaid) => { .then((mermaid) => {
let theme = 'neutral'; mermaidModule = initMermaid(mermaid);
const ideDarkThemes = ['dark', 'solarized-dark', 'monokai'];
if (
ideDarkThemes.includes(window.gon?.user_color_scheme) &&
// if on the Web IDE page
document.querySelector('.ide')
) {
theme = 'dark';
}
mermaid.initialize({
// mermaid core options
mermaid: {
startOnLoad: false,
},
// mermaidAPI options
theme,
flowchart: {
useMaxWidth: true,
htmlLabels: false,
},
securityLevel: 'strict',
});
mermaidModule = mermaid;
return mermaid;
}) })
.catch((err) => { .catch((err) => {
flash(sprintf(__("Can't load mermaid module: %{err}"), { err })); flash(sprintf(__("Can't load mermaid module: %{err}"), { err }));

View file

@ -1,6 +1,6 @@
import { memoize } from 'lodash'; import { memoize } from 'lodash';
import AccessorUtilities from '~/lib/utils/accessor'; import AccessorUtilities from '~/lib/utils/accessor';
import { s__ } from '~/locale'; import { __ } from '~/locale';
const isCustomizable = (command) => const isCustomizable = (command) =>
'customizable' in command ? Boolean(command.customizable) : true; 'customizable' in command ? Boolean(command.customizable) : true;
@ -33,42 +33,608 @@ export const getCustomizations = memoize(() => {
}); });
// All available commands // All available commands
export const TOGGLE_KEYBOARD_SHORTCUTS_DIALOG = {
id: 'globalShortcuts.toggleKeyboardShortcutsDialog',
description: __('Toggle keyboard shortcuts help dialog'),
defaultKeys: ['?'],
};
export const GO_TO_YOUR_PROJECTS = {
id: 'globalShortcuts.goToYourProjects',
description: __('Go to your projects'),
defaultKeys: ['shift+p'],
};
export const GO_TO_YOUR_GROUPS = {
id: 'globalShortcuts.goToYourGroups',
description: __('Go to your groups'),
defaultKeys: ['shift+g'],
};
export const GO_TO_ACTIVITY_FEED = {
id: 'globalShortcuts.goToActivityFeed',
description: __('Go to the activity feed'),
defaultKeys: ['shift+a'],
};
export const GO_TO_MILESTONE_LIST = {
id: 'globalShortcuts.goToMilestoneList',
description: __('Go to the milestone list'),
defaultKeys: ['shift+l'],
};
export const GO_TO_YOUR_SNIPPETS = {
id: 'globalShortcuts.goToYourSnippets',
description: __('Go to your snippets'),
defaultKeys: ['shift+s'],
};
export const START_SEARCH = {
id: 'globalShortcuts.startSearch',
description: __('Start search'),
defaultKeys: ['s', '/'],
};
export const FOCUS_FILTER_BAR = {
id: 'globalShortcuts.focusFilterBar',
description: __('Focus filter bar'),
defaultKeys: ['f'],
};
export const GO_TO_YOUR_ISSUES = {
id: 'globalShortcuts.goToYourIssues',
description: __('Go to your issues'),
defaultKeys: ['shift+i'],
};
export const GO_TO_YOUR_MERGE_REQUESTS = {
id: 'globalShortcuts.goToYourMergeRequests',
description: __('Go to your merge requests'),
defaultKeys: ['shift+m'],
};
export const GO_TO_YOUR_TODO_LIST = {
id: 'globalShortcuts.goToYourTodoList',
description: __('Go to your To-Do list'),
defaultKeys: ['shift+t'],
};
export const TOGGLE_PERFORMANCE_BAR = { export const TOGGLE_PERFORMANCE_BAR = {
id: 'globalShortcuts.togglePerformanceBar', id: 'globalShortcuts.togglePerformanceBar',
description: s__('KeyboardShortcuts|Toggle the Performance Bar'), description: __('Toggle the Performance Bar'),
// eslint-disable-next-line @gitlab/require-i18n-strings defaultKeys: ['p b'], // eslint-disable-line @gitlab/require-i18n-strings
defaultKeys: ['p b'],
}; };
export const TOGGLE_CANARY = { export const TOGGLE_CANARY = {
id: 'globalShortcuts.toggleCanary', id: 'globalShortcuts.toggleCanary',
description: s__('KeyboardShortcuts|Toggle GitLab Next'), description: __('Toggle GitLab Next'),
// eslint-disable-next-line @gitlab/require-i18n-strings defaultKeys: ['g x'], // eslint-disable-line @gitlab/require-i18n-strings
defaultKeys: ['g x'], };
export const BOLD_TEXT = {
id: 'editing.boldText',
description: __('Bold text'),
defaultKeys: ['mod+b'],
customizable: false,
};
export const ITALIC_TEXT = {
id: 'editing.italicText',
description: __('Italic text'),
defaultKeys: ['mod+i'],
customizable: false,
};
export const LINK_TEXT = {
id: 'editing.linkText',
description: __('Link text'),
defaultKeys: ['mod+k'],
customizable: false,
};
export const TOGGLE_MARKDOWN_PREVIEW = {
id: 'editing.toggleMarkdownPreview',
description: __('Toggle Markdown preview'),
// Note: Ideally, keyboard shortcuts should be made cross-platform by using the special `mod` key
// instead of binding both `ctrl` and `command` versions of the shortcut.
// See https://docs.gitlab.com/ee/development/fe_guide/keyboard_shortcuts.html#make-cross-platform-shortcuts.
// However, this particular shortcut has been in place since before the `mod` key was available.
// We've chosen to leave this implemented as-is for the time being to avoid breaking people's workflows.
// See discussion in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45308#note_527490548.
defaultKeys: ['ctrl+shift+p', 'command+shift+p'],
};
export const EDIT_RECENT_COMMENT = {
id: 'editing.editRecentComment',
description: __('Edit your most recent comment in a thread (from an empty textarea)'),
defaultKeys: ['up'],
};
export const EDIT_WIKI_PAGE = {
id: 'wiki.editWikiPage',
description: __('Edit wiki page'),
defaultKeys: ['e'],
};
export const REPO_GRAPH_SCROLL_LEFT = {
id: 'repositoryGraph.scrollLeft',
description: __('Scroll left'),
defaultKeys: ['left', 'h'],
};
export const REPO_GRAPH_SCROLL_RIGHT = {
id: 'repositoryGraph.scrollRight',
description: __('Scroll right'),
defaultKeys: ['right', 'l'],
};
export const REPO_GRAPH_SCROLL_UP = {
id: 'repositoryGraph.scrollUp',
description: __('Scroll up'),
defaultKeys: ['up', 'k'],
};
export const REPO_GRAPH_SCROLL_DOWN = {
id: 'repositoryGraph.scrollDown',
description: __('Scroll down'),
defaultKeys: ['down', 'j'],
};
export const REPO_GRAPH_SCROLL_TOP = {
id: 'repositoryGraph.scrollToTop',
description: __('Scroll to top'),
defaultKeys: ['shift+up', 'shift+k'],
};
export const REPO_GRAPH_SCROLL_BOTTOM = {
id: 'repositoryGraph.scrollToBottom',
description: __('Scroll to bottom'),
defaultKeys: ['shift+down', 'shift+j'],
};
export const GO_TO_PROJECT_OVERVIEW = {
id: 'project.goToOverview',
description: __("Go to the project's overview page"),
defaultKeys: ['g p'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const GO_TO_PROJECT_ACTIVITY_FEED = {
id: 'project.goToActivityFeed',
description: __("Go to the project's activity feed"),
defaultKeys: ['g v'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const GO_TO_PROJECT_RELEASES = {
id: 'project.goToReleases',
description: __('Go to releases'),
defaultKeys: ['g r'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const GO_TO_PROJECT_FILES = {
id: 'project.goToFiles',
description: __('Go to files'),
defaultKeys: ['g f'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const GO_TO_PROJECT_FIND_FILE = {
id: 'project.goToFindFile',
description: __('Go to find file'),
defaultKeys: ['t'],
};
export const GO_TO_PROJECT_COMMITS = {
id: 'project.goToCommits',
description: __('Go to commits'),
defaultKeys: ['g c'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const GO_TO_PROJECT_REPO_GRAPH = {
id: 'project.goToRepoGraph',
description: __('Go to repository graph'),
defaultKeys: ['g n'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const GO_TO_PROJECT_REPO_CHARTS = {
id: 'project.goToRepoCharts',
description: __('Go to repository charts'),
defaultKeys: ['g d'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const GO_TO_PROJECT_ISSUES = {
id: 'project.goToIssues',
description: __('Go to issues'),
defaultKeys: ['g i'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const NEW_ISSUE = {
id: 'project.newIssue',
description: __('New issue'),
defaultKeys: ['i'],
};
export const GO_TO_PROJECT_ISSUE_BOARDS = {
id: 'project.goToIssueBoards',
description: __('Go to issue boards'),
defaultKeys: ['g b'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const GO_TO_PROJECT_MERGE_REQUESTS = {
id: 'project.goToMergeRequests',
description: __('Go to merge requests'),
defaultKeys: ['g m'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const GO_TO_PROJECT_JOBS = {
id: 'project.goToJobs',
description: __('Go to jobs'),
defaultKeys: ['g j'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const GO_TO_PROJECT_METRICS = {
id: 'project.goToMetrics',
description: __('Go to metrics'),
defaultKeys: ['g l'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const GO_TO_PROJECT_ENVIRONMENTS = {
id: 'project.goToEnvironments',
description: __('Go to environments'),
defaultKeys: ['g e'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const GO_TO_PROJECT_KUBERNETES = {
id: 'project.goToKubernetes',
description: __('Go to kubernetes'),
defaultKeys: ['g k'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const GO_TO_PROJECT_SNIPPETS = {
id: 'project.goToSnippets',
description: __('Go to snippets'),
defaultKeys: ['g s'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const GO_TO_PROJECT_WIKI = {
id: 'project.goToWiki',
description: __('Go to wiki'),
defaultKeys: ['g w'], // eslint-disable-line @gitlab/require-i18n-strings
};
export const PROJECT_FILES_MOVE_SELECTION_UP = {
id: 'projectFiles.moveSelectionUp',
description: __('Move selection up'),
defaultKeys: ['up'],
};
export const PROJECT_FILES_MOVE_SELECTION_DOWN = {
id: 'projectFiles.moveSelectionDown',
description: __('Move selection down'),
defaultKeys: ['down'],
};
export const PROJECT_FILES_OPEN_SELECTION = {
id: 'projectFiles.openSelection',
description: __('Open Selection'),
defaultKeys: ['enter'],
};
export const PROJECT_FILES_GO_BACK = {
id: 'projectFiles.goBack',
description: __('Go back (while searching for files)'),
defaultKeys: ['esc'],
};
export const PROJECT_FILES_GO_TO_PERMALINK = {
id: 'projectFiles.goToFilePermalink',
description: __('Go to file permalink (while viewing a file)'),
defaultKeys: ['y'],
};
export const ISSUABLE_COMMENT_OR_REPLY = {
id: 'issuables.commentReply',
description: __('Comment/Reply (quoting selected text)'),
defaultKeys: ['r'],
};
export const ISSUABLE_EDIT_DESCRIPTION = {
id: 'issuables.editDescription',
description: __('Edit description'),
defaultKeys: ['e'],
};
export const ISSUABLE_CHANGE_LABEL = {
id: 'issuables.changeLabel',
description: __('Change label'),
defaultKeys: ['l'],
};
export const ISSUE_MR_CHANGE_ASSIGNEE = {
id: 'issuesMRs.changeAssignee',
description: __('Change assignee'),
defaultKeys: ['a'],
};
export const ISSUE_MR_CHANGE_MILESTONE = {
id: 'issuesMRs.changeMilestone',
description: __('Change milestone'),
defaultKeys: ['m'],
};
export const MR_NEXT_FILE_IN_DIFF = {
id: 'mergeRequests.nextFileInDiff',
description: __('Next file in diff'),
defaultKeys: [']', 'j'],
};
export const MR_PREVIOUS_FILE_IN_DIFF = {
id: 'mergeRequests.previousFileInDiff',
description: __('Previous file in diff'),
defaultKeys: ['[', 'k'],
};
export const MR_GO_TO_FILE = {
id: 'mergeRequests.goToFile',
description: __('Go to file'),
defaultKeys: ['t', 'mod+p'],
customizable: false,
};
export const MR_NEXT_UNRESOLVED_DISCUSSION = {
id: 'mergeRequests.nextUnresolvedDiscussion',
description: __('Next unresolved discussion'),
defaultKeys: ['n'],
};
export const MR_PREVIOUS_UNRESOLVED_DISCUSSION = {
id: 'mergeRequests.previousUnresolvedDiscussion',
description: __('Previous unresolved discussion'),
defaultKeys: ['p'],
};
export const MR_COPY_SOURCE_BRANCH_NAME = {
id: 'mergeRequests.copySourceBranchName',
description: __('Copy source branch name'),
defaultKeys: ['b'],
};
export const MR_COMMITS_NEXT_COMMIT = {
id: 'mergeRequestCommits.nextCommit',
description: __('Next commit'),
defaultKeys: ['c'],
};
export const MR_COMMITS_PREVIOUS_COMMIT = {
id: 'mergeRequestCommits.previousCommit',
description: __('Previous commit'),
defaultKeys: ['x'],
};
export const ISSUE_NEXT_DESIGN = {
id: 'issues.nextDesign',
description: __('Next design'),
defaultKeys: ['right'],
};
export const ISSUE_PREVIOUS_DESIGN = {
id: 'issues.previousDesign',
description: __('Previous design'),
defaultKeys: ['left'],
};
export const ISSUE_CLOSE_DESIGN = {
id: 'issues.closeDesign',
description: __('Close design'),
defaultKeys: ['esc'],
};
export const WEB_IDE_GO_TO_FILE = {
id: 'webIDE.goToFile',
description: __('Go to file'),
defaultKeys: ['mod+p'],
}; };
export const WEB_IDE_COMMIT = { export const WEB_IDE_COMMIT = {
id: 'webIDE.commit', id: 'webIDE.commit',
description: s__('KeyboardShortcuts|Commit (when editing commit message)'), description: __('Commit (when editing commit message)'),
defaultKeys: ['mod+enter'], defaultKeys: ['mod+enter'],
customizable: false, customizable: false,
}; };
export const METRICS_EXPAND_PANEL = {
id: 'metrics.expandPanel',
description: __('Expand panel'),
defaultKeys: ['e'],
customizable: false,
};
export const METRICS_VIEW_LOGS = {
id: 'metrics.viewLogs',
description: __('View logs'),
defaultKeys: ['l'],
customizable: false,
};
export const METRICS_DOWNLOAD_CSV = {
id: 'metrics.downloadCSV',
description: __('Download CSV'),
defaultKeys: ['d'],
customizable: false,
};
export const METRICS_COPY_LINK_TO_CHART = {
id: 'metrics.copyLinkToChart',
description: __('Copy link to chart'),
defaultKeys: ['c'],
customizable: false,
};
export const METRICS_SHOW_ALERTS = {
id: 'metrics.showAlerts',
description: __('Alerts'),
defaultKeys: ['a'],
customizable: false,
};
// All keybinding groups // All keybinding groups
export const GLOBAL_SHORTCUTS_GROUP = { export const GLOBAL_SHORTCUTS_GROUP = {
id: 'globalShortcuts', id: 'globalShortcuts',
name: s__('KeyboardShortcuts|Global Shortcuts'), name: __('Global Shortcuts'),
keybindings: [TOGGLE_PERFORMANCE_BAR, TOGGLE_CANARY], keybindings: [
TOGGLE_KEYBOARD_SHORTCUTS_DIALOG,
GO_TO_YOUR_PROJECTS,
GO_TO_YOUR_GROUPS,
GO_TO_ACTIVITY_FEED,
GO_TO_MILESTONE_LIST,
GO_TO_YOUR_SNIPPETS,
START_SEARCH,
FOCUS_FILTER_BAR,
GO_TO_YOUR_ISSUES,
GO_TO_YOUR_MERGE_REQUESTS,
GO_TO_YOUR_TODO_LIST,
TOGGLE_PERFORMANCE_BAR,
],
}; };
export const WEB_IDE_GROUP = { export const EDITING_SHORTCUTS_GROUP = {
id: 'editing',
name: __('Editing'),
keybindings: [BOLD_TEXT, ITALIC_TEXT, LINK_TEXT, TOGGLE_MARKDOWN_PREVIEW, EDIT_RECENT_COMMENT],
};
export const WIKI_SHORTCUTS_GROUP = {
id: 'wiki',
name: __('Wiki'),
keybindings: [EDIT_WIKI_PAGE],
};
export const REPOSITORY_GRAPH_SHORTCUTS_GROUP = {
id: 'repositoryGraph',
name: __('Repository Graph'),
keybindings: [
REPO_GRAPH_SCROLL_LEFT,
REPO_GRAPH_SCROLL_RIGHT,
REPO_GRAPH_SCROLL_UP,
REPO_GRAPH_SCROLL_DOWN,
REPO_GRAPH_SCROLL_TOP,
REPO_GRAPH_SCROLL_BOTTOM,
],
};
export const PROJECT_SHORTCUTS_GROUP = {
id: 'project',
name: __('Project'),
keybindings: [
GO_TO_PROJECT_OVERVIEW,
GO_TO_PROJECT_ACTIVITY_FEED,
GO_TO_PROJECT_RELEASES,
GO_TO_PROJECT_FILES,
GO_TO_PROJECT_FIND_FILE,
GO_TO_PROJECT_COMMITS,
GO_TO_PROJECT_REPO_GRAPH,
GO_TO_PROJECT_REPO_CHARTS,
GO_TO_PROJECT_ISSUES,
NEW_ISSUE,
GO_TO_PROJECT_ISSUE_BOARDS,
GO_TO_PROJECT_MERGE_REQUESTS,
GO_TO_PROJECT_JOBS,
GO_TO_PROJECT_METRICS,
GO_TO_PROJECT_ENVIRONMENTS,
GO_TO_PROJECT_KUBERNETES,
GO_TO_PROJECT_SNIPPETS,
GO_TO_PROJECT_WIKI,
],
};
export const PROJECT_FILES_SHORTCUTS_GROUP = {
id: 'projectFiles',
name: __('Project Files'),
keybindings: [
PROJECT_FILES_MOVE_SELECTION_UP,
PROJECT_FILES_MOVE_SELECTION_DOWN,
PROJECT_FILES_OPEN_SELECTION,
PROJECT_FILES_GO_BACK,
PROJECT_FILES_GO_TO_PERMALINK,
],
};
export const ISSUABLE_SHORTCUTS_GROUP = {
id: 'issuables',
name: __('Epics, issues, and merge requests'),
keybindings: [ISSUABLE_COMMENT_OR_REPLY, ISSUABLE_EDIT_DESCRIPTION, ISSUABLE_CHANGE_LABEL],
};
export const ISSUE_MR_SHORTCUTS_GROUP = {
id: 'issuesMRs',
name: __('Issues and merge requests'),
keybindings: [ISSUE_MR_CHANGE_ASSIGNEE, ISSUE_MR_CHANGE_MILESTONE],
};
export const MR_SHORTCUTS_GROUP = {
id: 'mergeRequests',
name: __('Merge requests'),
keybindings: [
MR_NEXT_FILE_IN_DIFF,
MR_PREVIOUS_FILE_IN_DIFF,
MR_GO_TO_FILE,
MR_NEXT_UNRESOLVED_DISCUSSION,
MR_PREVIOUS_UNRESOLVED_DISCUSSION,
MR_COPY_SOURCE_BRANCH_NAME,
],
};
export const MR_COMMITS_SHORTCUTS_GROUP = {
id: 'mergeRequestCommits',
name: __('Merge request commits'),
keybindings: [MR_COMMITS_NEXT_COMMIT, MR_COMMITS_PREVIOUS_COMMIT],
};
export const ISSUES_SHORTCUTS_GROUP = {
id: 'issues',
name: __('Issues'),
keybindings: [ISSUE_NEXT_DESIGN, ISSUE_PREVIOUS_DESIGN, ISSUE_CLOSE_DESIGN],
};
export const WEB_IDE_SHORTCUTS_GROUP = {
id: 'webIDE', id: 'webIDE',
name: s__('KeyboardShortcuts|Web IDE'), name: __('Web IDE'),
keybindings: [WEB_IDE_COMMIT], keybindings: [WEB_IDE_GO_TO_FILE, WEB_IDE_COMMIT],
};
export const METRICS_SHORTCUTS_GROUP = {
id: 'metrics',
name: __('Metrics'),
keybindings: [
METRICS_EXPAND_PANEL,
METRICS_VIEW_LOGS,
METRICS_DOWNLOAD_CSV,
METRICS_COPY_LINK_TO_CHART,
METRICS_SHOW_ALERTS,
],
};
export const MISC_SHORTCUTS_GROUP = {
id: 'misc',
name: __('Miscellaneous'),
keybindings: [TOGGLE_CANARY],
}; };
/** All keybindings, grouped and ordered with descriptions */ /** All keybindings, grouped and ordered with descriptions */
export const keybindingGroups = [GLOBAL_SHORTCUTS_GROUP, WEB_IDE_GROUP]; export const keybindingGroups = [
GLOBAL_SHORTCUTS_GROUP,
EDITING_SHORTCUTS_GROUP,
WIKI_SHORTCUTS_GROUP,
REPOSITORY_GRAPH_SHORTCUTS_GROUP,
PROJECT_SHORTCUTS_GROUP,
PROJECT_FILES_SHORTCUTS_GROUP,
ISSUABLE_SHORTCUTS_GROUP,
ISSUE_MR_SHORTCUTS_GROUP,
MR_SHORTCUTS_GROUP,
MR_COMMITS_SHORTCUTS_GROUP,
ISSUES_SHORTCUTS_GROUP,
WEB_IDE_SHORTCUTS_GROUP,
METRICS_SHORTCUTS_GROUP,
MISC_SHORTCUTS_GROUP,
];
/** /**
* Gets keyboard shortcuts associated with a command * Gets keyboard shortcuts associated with a command

View file

@ -6,13 +6,29 @@ import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import findAndFollowLink from '~/lib/utils/navigation_utility'; import findAndFollowLink from '~/lib/utils/navigation_utility';
import { refreshCurrentPage, visitUrl } from '~/lib/utils/url_utility'; import { refreshCurrentPage, visitUrl } from '~/lib/utils/url_utility';
import {
import { keysFor, TOGGLE_PERFORMANCE_BAR, TOGGLE_CANARY } from './keybindings'; keysFor,
TOGGLE_KEYBOARD_SHORTCUTS_DIALOG,
START_SEARCH,
FOCUS_FILTER_BAR,
TOGGLE_PERFORMANCE_BAR,
TOGGLE_CANARY,
TOGGLE_MARKDOWN_PREVIEW,
GO_TO_YOUR_TODO_LIST,
GO_TO_ACTIVITY_FEED,
GO_TO_YOUR_ISSUES,
GO_TO_YOUR_MERGE_REQUESTS,
GO_TO_YOUR_PROJECTS,
GO_TO_YOUR_GROUPS,
GO_TO_MILESTONE_LIST,
GO_TO_YOUR_SNIPPETS,
GO_TO_PROJECT_FIND_FILE,
} from './keybindings';
import { disableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle'; import { disableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
const defaultStopCallback = Mousetrap.prototype.stopCallback; const defaultStopCallback = Mousetrap.prototype.stopCallback;
Mousetrap.prototype.stopCallback = function customStopCallback(e, element, combo) { Mousetrap.prototype.stopCallback = function customStopCallback(e, element, combo) {
if (['ctrl+shift+p', 'command+shift+p'].indexOf(combo) !== -1) { if (keysFor(TOGGLE_MARKDOWN_PREVIEW).indexOf(combo) !== -1) {
return false; return false;
} }
@ -58,28 +74,41 @@ export default class Shortcuts {
this.helpModalElement = null; this.helpModalElement = null;
this.helpModalVueInstance = null; this.helpModalVueInstance = null;
Mousetrap.bind('?', this.onToggleHelp); Mousetrap.bind(keysFor(TOGGLE_KEYBOARD_SHORTCUTS_DIALOG), this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch); Mousetrap.bind(keysFor(START_SEARCH), Shortcuts.focusSearch);
Mousetrap.bind('/', Shortcuts.focusSearch); Mousetrap.bind(keysFor(FOCUS_FILTER_BAR), this.focusFilter.bind(this));
Mousetrap.bind('f', this.focusFilter.bind(this));
Mousetrap.bind(keysFor(TOGGLE_PERFORMANCE_BAR), Shortcuts.onTogglePerfBar); Mousetrap.bind(keysFor(TOGGLE_PERFORMANCE_BAR), Shortcuts.onTogglePerfBar);
Mousetrap.bind(keysFor(TOGGLE_CANARY), Shortcuts.onToggleCanary); Mousetrap.bind(keysFor(TOGGLE_CANARY), Shortcuts.onToggleCanary);
const findFileURL = document.body.dataset.findFile; const findFileURL = document.body.dataset.findFile;
Mousetrap.bind('shift+t', () => findAndFollowLink('.shortcuts-todos')); Mousetrap.bind(keysFor(GO_TO_YOUR_TODO_LIST), () => findAndFollowLink('.shortcuts-todos'));
Mousetrap.bind('shift+a', () => findAndFollowLink('.dashboard-shortcuts-activity')); Mousetrap.bind(keysFor(GO_TO_ACTIVITY_FEED), () =>
Mousetrap.bind('shift+i', () => findAndFollowLink('.dashboard-shortcuts-issues')); findAndFollowLink('.dashboard-shortcuts-activity'),
Mousetrap.bind('shift+m', () => findAndFollowLink('.dashboard-shortcuts-merge_requests')); );
Mousetrap.bind('shift+p', () => findAndFollowLink('.dashboard-shortcuts-projects')); Mousetrap.bind(keysFor(GO_TO_YOUR_ISSUES), () =>
Mousetrap.bind('shift+g', () => findAndFollowLink('.dashboard-shortcuts-groups')); findAndFollowLink('.dashboard-shortcuts-issues'),
Mousetrap.bind('shift+l', () => findAndFollowLink('.dashboard-shortcuts-milestones')); );
Mousetrap.bind('shift+s', () => findAndFollowLink('.dashboard-shortcuts-snippets')); Mousetrap.bind(keysFor(GO_TO_YOUR_MERGE_REQUESTS), () =>
findAndFollowLink('.dashboard-shortcuts-merge_requests'),
);
Mousetrap.bind(keysFor(GO_TO_YOUR_PROJECTS), () =>
findAndFollowLink('.dashboard-shortcuts-projects'),
);
Mousetrap.bind(keysFor(GO_TO_YOUR_GROUPS), () =>
findAndFollowLink('.dashboard-shortcuts-groups'),
);
Mousetrap.bind(keysFor(GO_TO_MILESTONE_LIST), () =>
findAndFollowLink('.dashboard-shortcuts-milestones'),
);
Mousetrap.bind(keysFor(GO_TO_YOUR_SNIPPETS), () =>
findAndFollowLink('.dashboard-shortcuts-snippets'),
);
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], Shortcuts.toggleMarkdownPreview); Mousetrap.bind(keysFor(TOGGLE_MARKDOWN_PREVIEW), Shortcuts.toggleMarkdownPreview);
if (typeof findFileURL !== 'undefined' && findFileURL !== null) { if (typeof findFileURL !== 'undefined' && findFileURL !== null) {
Mousetrap.bind('t', () => { Mousetrap.bind(keysFor(GO_TO_PROJECT_FIND_FILE), () => {
visitUrl(findFileURL); visitUrl(findFileURL);
}); });
} }

View file

@ -1,4 +1,5 @@
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import { keysFor, PROJECT_FILES_GO_TO_PERMALINK } from '~/behaviors/shortcuts/keybindings';
import { import {
getLocationHash, getLocationHash,
updateHistory, updateHistory,
@ -28,7 +29,7 @@ export default class ShortcutsBlob extends Shortcuts {
this.shortcircuitPermalinkButton(); this.shortcircuitPermalinkButton();
Mousetrap.bind('y', this.moveToFilePermalink.bind(this)); Mousetrap.bind(keysFor(PROJECT_FILES_GO_TO_PERMALINK), this.moveToFilePermalink.bind(this));
} }
moveToFilePermalink() { moveToFilePermalink() {

View file

@ -1,4 +1,11 @@
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import {
keysFor,
PROJECT_FILES_MOVE_SELECTION_UP,
PROJECT_FILES_MOVE_SELECTION_DOWN,
PROJECT_FILES_OPEN_SELECTION,
PROJECT_FILES_GO_BACK,
} from '~/behaviors/shortcuts/keybindings';
import ShortcutsNavigation from './shortcuts_navigation'; import ShortcutsNavigation from './shortcuts_navigation';
export default class ShortcutsFindFile extends ShortcutsNavigation { export default class ShortcutsFindFile extends ShortcutsNavigation {
@ -10,7 +17,10 @@ export default class ShortcutsFindFile extends ShortcutsNavigation {
Mousetrap.prototype.stopCallback = function customStopCallback(e, element, combo) { Mousetrap.prototype.stopCallback = function customStopCallback(e, element, combo) {
if ( if (
element === projectFindFile.inputElement[0] && element === projectFindFile.inputElement[0] &&
(combo === 'up' || combo === 'down' || combo === 'esc' || combo === 'enter') (keysFor(PROJECT_FILES_MOVE_SELECTION_UP).includes(combo) ||
keysFor(PROJECT_FILES_MOVE_SELECTION_DOWN).includes(combo) ||
keysFor(PROJECT_FILES_GO_BACK).includes(combo) ||
keysFor(PROJECT_FILES_OPEN_SELECTION).includes(combo))
) { ) {
// when press up/down key in textbox, cursor prevent to move to home/end // when press up/down key in textbox, cursor prevent to move to home/end
e.preventDefault(); e.preventDefault();
@ -20,9 +30,9 @@ export default class ShortcutsFindFile extends ShortcutsNavigation {
return oldStopCallback.call(this, e, element, combo); return oldStopCallback.call(this, e, element, combo);
}; };
Mousetrap.bind('up', projectFindFile.selectRowUp); Mousetrap.bind(keysFor(PROJECT_FILES_MOVE_SELECTION_UP), projectFindFile.selectRowUp);
Mousetrap.bind('down', projectFindFile.selectRowDown); Mousetrap.bind(keysFor(PROJECT_FILES_MOVE_SELECTION_DOWN), projectFindFile.selectRowDown);
Mousetrap.bind('esc', projectFindFile.goToTree); Mousetrap.bind(keysFor(PROJECT_FILES_GO_BACK), projectFindFile.goToTree);
Mousetrap.bind('enter', projectFindFile.goToBlob); Mousetrap.bind(keysFor(PROJECT_FILES_OPEN_SELECTION), projectFindFile.goToBlob);
} }
} }

View file

@ -397,7 +397,7 @@ export default {
<tbody> <tbody>
<tr> <tr>
<th></th> <th></th>
<th>{{ __('Epics, Issues, and Merge Requests') }}</th> <th>{{ __('Epics, issues, and merge requests') }}</th>
</tr> </tr>
<tr> <tr>
<td class="shortcut"> <td class="shortcut">
@ -421,7 +421,7 @@ export default {
<tbody> <tbody>
<tr> <tr>
<th></th> <th></th>
<th>{{ __('Issues and Merge Requests') }}</th> <th>{{ __('Issues and merge requests') }}</th>
</tr> </tr>
<tr> <tr>
<td class="shortcut"> <td class="shortcut">
@ -439,7 +439,7 @@ export default {
<tbody> <tbody>
<tr> <tr>
<th></th> <th></th>
<th>{{ __('Merge Requests') }}</th> <th>{{ __('Merge requests') }}</th>
</tr> </tr>
<tr> <tr>
<td class="shortcut"> <td class="shortcut">
@ -485,7 +485,7 @@ export default {
<tbody> <tbody>
<tr> <tr>
<th></th> <th></th>
<th>{{ __('Merge Request Commits') }}</th> <th>{{ __('Merge request commits') }}</th>
</tr> </tr>
<tr> <tr>
<td class="shortcut"> <td class="shortcut">

View file

@ -5,18 +5,33 @@ import { getSelectedFragment } from '~/lib/utils/common_utils';
import { isElementVisible } from '~/lib/utils/dom_utils'; import { isElementVisible } from '~/lib/utils/dom_utils';
import Sidebar from '../../right_sidebar'; import Sidebar from '../../right_sidebar';
import { CopyAsGFM } from '../markdown/copy_as_gfm'; import { CopyAsGFM } from '../markdown/copy_as_gfm';
import {
keysFor,
ISSUE_MR_CHANGE_ASSIGNEE,
ISSUE_MR_CHANGE_MILESTONE,
ISSUABLE_CHANGE_LABEL,
ISSUABLE_COMMENT_OR_REPLY,
ISSUABLE_EDIT_DESCRIPTION,
MR_COPY_SOURCE_BRANCH_NAME,
} from './keybindings';
import Shortcuts from './shortcuts'; import Shortcuts from './shortcuts';
export default class ShortcutsIssuable extends Shortcuts { export default class ShortcutsIssuable extends Shortcuts {
constructor() { constructor() {
super(); super();
Mousetrap.bind('a', () => ShortcutsIssuable.openSidebarDropdown('assignee')); Mousetrap.bind(keysFor(ISSUE_MR_CHANGE_ASSIGNEE), () =>
Mousetrap.bind('m', () => ShortcutsIssuable.openSidebarDropdown('milestone')); ShortcutsIssuable.openSidebarDropdown('assignee'),
Mousetrap.bind('l', () => ShortcutsIssuable.openSidebarDropdown('labels')); );
Mousetrap.bind('r', ShortcutsIssuable.replyWithSelectedText); Mousetrap.bind(keysFor(ISSUE_MR_CHANGE_MILESTONE), () =>
Mousetrap.bind('e', ShortcutsIssuable.editIssue); ShortcutsIssuable.openSidebarDropdown('milestone'),
Mousetrap.bind('b', ShortcutsIssuable.copyBranchName); );
Mousetrap.bind(keysFor(ISSUABLE_CHANGE_LABEL), () =>
ShortcutsIssuable.openSidebarDropdown('labels'),
);
Mousetrap.bind(keysFor(ISSUABLE_COMMENT_OR_REPLY), ShortcutsIssuable.replyWithSelectedText);
Mousetrap.bind(keysFor(ISSUABLE_EDIT_DESCRIPTION), ShortcutsIssuable.editIssue);
Mousetrap.bind(keysFor(MR_COPY_SOURCE_BRANCH_NAME), ShortcutsIssuable.copyBranchName);
} }
static replyWithSelectedText() { static replyWithSelectedText() {
@ -105,7 +120,7 @@ export default class ShortcutsIssuable extends Shortcuts {
static copyBranchName() { static copyBranchName() {
// There are two buttons - one that is shown when the sidebar // There are two buttons - one that is shown when the sidebar
// is expanded, and one that is shown when it's collapsed. // is expanded, and one that is shown when it's collapsed.
const allCopyBtns = Array.from(document.querySelectorAll('.sidebar-source-branch button')); const allCopyBtns = Array.from(document.querySelectorAll('.js-sidebar-source-branch button'));
// Select whichever button is currently visible so that // Select whichever button is currently visible so that
// the "Copied" tooltip is shown when a click is simulated. // the "Copied" tooltip is shown when a click is simulated.

View file

@ -1,27 +1,63 @@
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import findAndFollowLink from '../../lib/utils/navigation_utility'; import findAndFollowLink from '../../lib/utils/navigation_utility';
import {
keysFor,
GO_TO_PROJECT_OVERVIEW,
GO_TO_PROJECT_ACTIVITY_FEED,
GO_TO_PROJECT_RELEASES,
GO_TO_PROJECT_FILES,
GO_TO_PROJECT_COMMITS,
GO_TO_PROJECT_JOBS,
GO_TO_PROJECT_REPO_GRAPH,
GO_TO_PROJECT_REPO_CHARTS,
GO_TO_PROJECT_ISSUES,
GO_TO_PROJECT_ISSUE_BOARDS,
GO_TO_PROJECT_MERGE_REQUESTS,
GO_TO_PROJECT_WIKI,
GO_TO_PROJECT_SNIPPETS,
GO_TO_PROJECT_KUBERNETES,
GO_TO_PROJECT_ENVIRONMENTS,
GO_TO_PROJECT_METRICS,
NEW_ISSUE,
} from './keybindings';
import Shortcuts from './shortcuts'; import Shortcuts from './shortcuts';
export default class ShortcutsNavigation extends Shortcuts { export default class ShortcutsNavigation extends Shortcuts {
constructor() { constructor() {
super(); super();
Mousetrap.bind('g p', () => findAndFollowLink('.shortcuts-project')); Mousetrap.bind(keysFor(GO_TO_PROJECT_OVERVIEW), () => findAndFollowLink('.shortcuts-project'));
Mousetrap.bind('g v', () => findAndFollowLink('.shortcuts-project-activity')); Mousetrap.bind(keysFor(GO_TO_PROJECT_ACTIVITY_FEED), () =>
Mousetrap.bind('g r', () => findAndFollowLink('.shortcuts-project-releases')); findAndFollowLink('.shortcuts-project-activity'),
Mousetrap.bind('g f', () => findAndFollowLink('.shortcuts-tree')); );
Mousetrap.bind('g c', () => findAndFollowLink('.shortcuts-commits')); Mousetrap.bind(keysFor(GO_TO_PROJECT_RELEASES), () =>
Mousetrap.bind('g j', () => findAndFollowLink('.shortcuts-builds')); findAndFollowLink('.shortcuts-project-releases'),
Mousetrap.bind('g n', () => findAndFollowLink('.shortcuts-network')); );
Mousetrap.bind('g d', () => findAndFollowLink('.shortcuts-repository-charts')); Mousetrap.bind(keysFor(GO_TO_PROJECT_FILES), () => findAndFollowLink('.shortcuts-tree'));
Mousetrap.bind('g i', () => findAndFollowLink('.shortcuts-issues')); Mousetrap.bind(keysFor(GO_TO_PROJECT_COMMITS), () => findAndFollowLink('.shortcuts-commits'));
Mousetrap.bind('g b', () => findAndFollowLink('.shortcuts-issue-boards')); Mousetrap.bind(keysFor(GO_TO_PROJECT_JOBS), () => findAndFollowLink('.shortcuts-builds'));
Mousetrap.bind('g m', () => findAndFollowLink('.shortcuts-merge_requests')); Mousetrap.bind(keysFor(GO_TO_PROJECT_REPO_GRAPH), () =>
Mousetrap.bind('g w', () => findAndFollowLink('.shortcuts-wiki')); findAndFollowLink('.shortcuts-network'),
Mousetrap.bind('g s', () => findAndFollowLink('.shortcuts-snippets')); );
Mousetrap.bind('g k', () => findAndFollowLink('.shortcuts-kubernetes')); Mousetrap.bind(keysFor(GO_TO_PROJECT_REPO_CHARTS), () =>
Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-environments')); findAndFollowLink('.shortcuts-repository-charts'),
Mousetrap.bind('g l', () => findAndFollowLink('.shortcuts-metrics')); );
Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue')); Mousetrap.bind(keysFor(GO_TO_PROJECT_ISSUES), () => findAndFollowLink('.shortcuts-issues'));
Mousetrap.bind(keysFor(GO_TO_PROJECT_ISSUE_BOARDS), () =>
findAndFollowLink('.shortcuts-issue-boards'),
);
Mousetrap.bind(keysFor(GO_TO_PROJECT_MERGE_REQUESTS), () =>
findAndFollowLink('.shortcuts-merge_requests'),
);
Mousetrap.bind(keysFor(GO_TO_PROJECT_WIKI), () => findAndFollowLink('.shortcuts-wiki'));
Mousetrap.bind(keysFor(GO_TO_PROJECT_SNIPPETS), () => findAndFollowLink('.shortcuts-snippets'));
Mousetrap.bind(keysFor(GO_TO_PROJECT_KUBERNETES), () =>
findAndFollowLink('.shortcuts-kubernetes'),
);
Mousetrap.bind(keysFor(GO_TO_PROJECT_ENVIRONMENTS), () =>
findAndFollowLink('.shortcuts-environments'),
);
Mousetrap.bind(keysFor(GO_TO_PROJECT_METRICS), () => findAndFollowLink('.shortcuts-metrics'));
Mousetrap.bind(keysFor(NEW_ISSUE), () => findAndFollowLink('.shortcuts-new-issue'));
} }
} }

View file

@ -1,15 +1,24 @@
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import {
keysFor,
REPO_GRAPH_SCROLL_BOTTOM,
REPO_GRAPH_SCROLL_DOWN,
REPO_GRAPH_SCROLL_LEFT,
REPO_GRAPH_SCROLL_RIGHT,
REPO_GRAPH_SCROLL_TOP,
REPO_GRAPH_SCROLL_UP,
} from './keybindings';
import ShortcutsNavigation from './shortcuts_navigation'; import ShortcutsNavigation from './shortcuts_navigation';
export default class ShortcutsNetwork extends ShortcutsNavigation { export default class ShortcutsNetwork extends ShortcutsNavigation {
constructor(graph) { constructor(graph) {
super(); super();
Mousetrap.bind(['left', 'h'], graph.scrollLeft); Mousetrap.bind(keysFor(REPO_GRAPH_SCROLL_LEFT), graph.scrollLeft);
Mousetrap.bind(['right', 'l'], graph.scrollRight); Mousetrap.bind(keysFor(REPO_GRAPH_SCROLL_RIGHT), graph.scrollRight);
Mousetrap.bind(['up', 'k'], graph.scrollUp); Mousetrap.bind(keysFor(REPO_GRAPH_SCROLL_UP), graph.scrollUp);
Mousetrap.bind(['down', 'j'], graph.scrollDown); Mousetrap.bind(keysFor(REPO_GRAPH_SCROLL_DOWN), graph.scrollDown);
Mousetrap.bind(['shift+up', 'shift+k'], graph.scrollTop); Mousetrap.bind(keysFor(REPO_GRAPH_SCROLL_TOP), graph.scrollTop);
Mousetrap.bind(['shift+down', 'shift+j'], graph.scrollBottom); Mousetrap.bind(keysFor(REPO_GRAPH_SCROLL_BOTTOM), graph.scrollBottom);
} }
} }

View file

@ -1,9 +1,13 @@
<script> <script>
import { GlToggle } from '@gitlab/ui'; import { GlToggle } from '@gitlab/ui';
import AccessorUtilities from '~/lib/utils/accessor'; import AccessorUtilities from '~/lib/utils/accessor';
import { __ } from '~/locale';
import { disableShortcuts, enableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle'; import { disableShortcuts, enableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
export default { export default {
i18n: {
toggleLabel: __('Keyboard shortcuts'),
},
components: { components: {
GlToggle, GlToggle,
}, },
@ -31,7 +35,7 @@ export default {
<gl-toggle <gl-toggle
v-model="shortcutsEnabled" v-model="shortcutsEnabled"
aria-describedby="shortcutsToggle" aria-describedby="shortcutsToggle"
label="Keyboard shortcuts" :label="$options.i18n.toggleLabel"
label-position="left" label-position="left"
@change="onChange" @change="onChange"
/> />

View file

@ -1,11 +1,12 @@
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import findAndFollowLink from '../../lib/utils/navigation_utility'; import findAndFollowLink from '../../lib/utils/navigation_utility';
import { keysFor, EDIT_WIKI_PAGE } from './keybindings';
import ShortcutsNavigation from './shortcuts_navigation'; import ShortcutsNavigation from './shortcuts_navigation';
export default class ShortcutsWiki extends ShortcutsNavigation { export default class ShortcutsWiki extends ShortcutsNavigation {
constructor() { constructor() {
super(); super();
Mousetrap.bind('e', ShortcutsWiki.editWiki); Mousetrap.bind(keysFor(EDIT_WIKI_PAGE), ShortcutsWiki.editWiki);
} }
static editWiki() { static editWiki() {

View file

@ -21,6 +21,11 @@ export default {
default: '', default: '',
required: false, required: false,
}, },
isRawContent: {
type: Boolean,
default: false,
required: false,
},
loading: { loading: {
type: Boolean, type: Boolean,
default: true, default: true,
@ -65,6 +70,8 @@ export default {
v-else v-else
ref="contentViewer" ref="contentViewer"
:content="content" :content="content"
:is-raw-content="isRawContent"
:file-name="blob.name"
:type="activeViewer.fileType" :type="activeViewer.fileType"
data-qa-selector="file_content" data-qa-selector="file_content"
/> />

View file

@ -19,6 +19,17 @@ export default class FileTemplateSelector {
this.$dropdownToggleText = this.$wrapper.find('.dropdown-toggle-text'); this.$dropdownToggleText = this.$wrapper.find('.dropdown-toggle-text');
this.initDropdown(); this.initDropdown();
this.selectInitialTemplate();
}
selectInitialTemplate() {
const template = this.$dropdown.data('selected');
if (!template) {
return;
}
this.mediator.selectTemplateFile(this, template);
} }
show() { show() {
@ -27,6 +38,19 @@ export default class FileTemplateSelector {
} }
this.$wrapper.removeClass('hidden'); this.$wrapper.removeClass('hidden');
/**
* We set the focus on the dropdown that was just shown. This is done so that, after selecting
* a template type, the template selector immediately receives the focus.
* This improves the UX of the tour as the suggest_gitlab_ci_yml popover requires its target to
* be have the focus to appear. This way, users don't have to interact with the template
* selector to actually see the first hint: it is shown as soon as the selector becomes visible.
* We also need a timeout here, otherwise the template type selector gets stuck and can not be
* closed anymore.
*/
setTimeout(() => {
this.$dropdown.focus();
}, 0);
} }
hide() { hide() {
@ -36,7 +60,7 @@ export default class FileTemplateSelector {
} }
isHidden() { isHidden() {
return this.$wrapper.hasClass('hidden'); return !this.$wrapper || this.$wrapper.hasClass('hidden');
} }
getToggleText() { getToggleText() {

View file

@ -9,8 +9,8 @@ export default () => {
e.preventDefault(); e.preventDefault();
document.querySelector('.js-material-changer.active').classList.remove('active'); document.querySelector('.js-material-changer.selected').classList.remove('selected');
target.classList.add('active'); target.classList.add('selected');
target.blur(); target.blur();
viewer.changeObjectMaterials(target.dataset.type); viewer.changeObjectMaterials(target.dataset.type);

View file

@ -108,7 +108,6 @@ export default {
show show
:target="target" :target="target"
placement="right" placement="right"
trigger="manual"
container="viewport" container="viewport"
:css-classes="['suggest-gitlab-ci-yml', 'ml-4']" :css-classes="['suggest-gitlab-ci-yml', 'ml-4']"
> >

View file

@ -43,7 +43,7 @@ export default class EditBlob {
blobPath: fileNameEl.value, blobPath: fileNameEl.value,
blobContent: editorEl.innerText, blobContent: editorEl.innerText,
}); });
this.editor.use(new FileTemplateExtension()); this.editor.use(new FileTemplateExtension({ instance: this.editor }));
fileNameEl.addEventListener('change', () => { fileNameEl.addEventListener('change', () => {
this.editor.updateModelLanguage(fileNameEl.value); this.editor.updateModelLanguage(fileNameEl.value);
@ -82,7 +82,7 @@ export default class EditBlob {
this.$editModePanes.hide(); this.$editModePanes.hide();
currentPane.fadeIn(200); currentPane.show();
if (paneId === '#preview') { if (paneId === '#preview') {
this.$toggleButton.hide(); this.$toggleButton.hide();

View file

@ -1,4 +1,4 @@
import { sortBy } from 'lodash'; import { sortBy, cloneDeep } from 'lodash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { ListType, NOT_FILTER } from './constants'; import { ListType, NOT_FILTER } from './constants';
@ -113,6 +113,37 @@ export function formatIssueInput(issueInput, boardConfig) {
}; };
} }
export function shouldCloneCard(fromListType, toListType) {
const involvesClosed = fromListType === ListType.closed || toListType === ListType.closed;
const involvesBacklog = fromListType === ListType.backlog || toListType === ListType.backlog;
if (involvesClosed || involvesBacklog) {
return false;
}
if (fromListType !== toListType) {
return true;
}
return false;
}
export function getMoveData(state, params) {
const { boardItems, boardItemsByListId, boardLists } = state;
const { itemId, fromListId, toListId } = params;
const fromListType = boardLists[fromListId].listType;
const toListType = boardLists[toListId].listType;
return {
reordering: fromListId === toListId,
shouldClone: shouldCloneCard(fromListType, toListType),
itemNotInToList: !boardItemsByListId[toListId].includes(itemId),
originalIssue: cloneDeep(boardItems[itemId]),
originalIndex: boardItemsByListId[fromListId].indexOf(itemId),
...params,
};
}
export function moveItemListHelper(item, fromList, toList) { export function moveItemListHelper(item, fromList, toList) {
const updatedItem = item; const updatedItem = item;
if ( if (

View file

@ -1,23 +1,16 @@
<script> <script>
import { import { GlFormRadio, GlFormRadioGroup, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
GlFormRadio,
GlFormRadioGroup,
GlLabel,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue'; import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
import { ListType } from '~/boards/constants'; import { ListType } from '~/boards/constants';
import boardsStore from '~/boards/stores/boards_store'; import boardsStore from '~/boards/stores/boards_store';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { isScopedLabel } from '~/lib/utils/common_utils';
export default { export default {
components: { components: {
BoardAddNewColumnForm, BoardAddNewColumnForm,
GlFormRadio, GlFormRadio,
GlFormRadioGroup, GlFormRadioGroup,
GlLabel,
}, },
directives: { directives: {
GlTooltip, GlTooltip,
@ -26,17 +19,12 @@ export default {
data() { data() {
return { return {
selectedId: null, selectedId: null,
selectedLabel: null,
}; };
}, },
computed: { computed: {
...mapState(['labels', 'labelsLoading']), ...mapState(['labels', 'labelsLoading']),
...mapGetters(['getListByLabelId', 'shouldUseGraphQL']), ...mapGetters(['getListByLabelId', 'shouldUseGraphQL']),
selectedLabel() {
if (!this.selectedId) {
return null;
}
return this.labels.find(({ id }) => id === this.selectedId);
},
columnForSelected() { columnForSelected() {
return this.getListByLabelId(this.selectedId); return this.getListByLabelId(this.selectedId);
}, },
@ -89,8 +77,13 @@ export default {
this.fetchLabels(searchTerm); this.fetchLabels(searchTerm);
}, },
showScopedLabels(label) { setSelectedItem(selectedId) {
return this.scopedLabelsAvailable && isScopedLabel(label); const label = this.labels.find(({ id }) => id === selectedId);
if (!selectedId || !label) {
this.selectedLabel = null;
} else {
this.selectedLabel = { ...label };
}
}, },
}, },
}; };
@ -99,38 +92,39 @@ export default {
<template> <template>
<board-add-new-column-form <board-add-new-column-form
:loading="labelsLoading" :loading="labelsLoading"
:form-description="__('A label list displays issues with the selected label.')" :none-selected="__('Select a label')"
:search-label="__('Select label')"
:search-placeholder="__('Search labels')" :search-placeholder="__('Search labels')"
:selected-id="selectedId" :selected-id="selectedId"
@filter-items="filterItems" @filter-items="filterItems"
@add-list="addList" @add-list="addList"
> >
<template slot="selected"> <template #selected>
<gl-label <template v-if="selectedLabel">
v-if="selectedLabel" <span
v-gl-tooltip class="dropdown-label-box gl-top-0 gl-flex-shrink-0"
:title="selectedLabel.title" :style="{
:description="selectedLabel.description" backgroundColor: selectedLabel.color,
:background-color="selectedLabel.color" }"
:scoped="showScopedLabels(selectedLabel)" ></span>
/> <div class="gl-text-truncate">{{ selectedLabel.title }}</div>
</template>
</template> </template>
<template slot="items"> <template #items>
<gl-form-radio-group <gl-form-radio-group
v-if="labels.length > 0" v-if="labels.length > 0"
v-model="selectedId" v-model="selectedId"
class="gl-overflow-y-auto gl-px-5 gl-pt-3" class="gl-overflow-y-auto gl-px-5 gl-pt-3"
@change="setSelectedItem"
> >
<label <label
v-for="label in labels" v-for="label in labels"
:key="label.id" :key="label.id"
class="gl-display-flex gl-flex-align-items-center gl-mb-5 gl-font-weight-normal" class="gl-display-flex gl-mb-5 gl-font-weight-normal gl-overflow-break-word"
> >
<gl-form-radio :value="label.id" class="gl-mb-0" /> <gl-form-radio :value="label.id" />
<span <span
class="dropdown-label-box gl-top-0" class="dropdown-label-box gl-top-0 gl-flex-shrink-0"
:style="{ :style="{
backgroundColor: label.color, backgroundColor: label.color,
}" }"

View file

@ -1,5 +1,12 @@
<script> <script>
import { GlButton, GlFormGroup, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui'; import {
GlButton,
GlDropdown,
GlFormGroup,
GlIcon,
GlSearchBoxByType,
GlSkeletonLoader,
} from '@gitlab/ui';
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import { __ } from '~/locale'; import { __ } from '~/locale';
@ -8,13 +15,16 @@ export default {
add: __('Add to board'), add: __('Add to board'),
cancel: __('Cancel'), cancel: __('Cancel'),
newList: __('New list'), newList: __('New list'),
noneSelected: __('None'),
noResults: __('No matching results'), noResults: __('No matching results'),
scope: __('Scope'),
scopeDescription: __('Issues must match this scope to appear in this list.'),
selected: __('Selected'), selected: __('Selected'),
}, },
components: { components: {
GlButton, GlButton,
GlDropdown,
GlFormGroup, GlFormGroup,
GlIcon,
GlSearchBoxByType, GlSearchBoxByType,
GlSkeletonLoader, GlSkeletonLoader,
}, },
@ -23,11 +33,12 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
formDescription: {
type: String,
required: true,
},
searchLabel: { searchLabel: {
type: String,
required: false,
default: null,
},
noneSelected: {
type: String, type: String,
required: true, required: true,
}, },
@ -46,8 +57,23 @@ export default {
searchValue: '', searchValue: '',
}; };
}, },
watch: {
selectedId(val) {
if (val) {
this.$refs.dropdown.hide(true);
}
},
},
methods: { methods: {
...mapActions(['setAddColumnFormVisibility']), ...mapActions(['setAddColumnFormVisibility']),
setFocus() {
this.$refs.searchBox.focusInput();
},
onHide() {
this.searchValue = '';
this.$emit('filter-items', '');
this.$emit('hide');
},
}, },
}; };
</script> </script>
@ -62,51 +88,64 @@ export default {
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base gl-bg-white" class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base gl-bg-white"
> >
<h3 <h3
class="gl-font-base gl-px-5 gl-py-5 gl-m-0 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100" class="gl-font-size-h2 gl-px-5 gl-py-4 gl-m-0 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
data-testid="board-add-column-form-title" data-testid="board-add-column-form-title"
> >
{{ $options.i18n.newList }} {{ $options.i18n.newList }}
</h3> </h3>
<div class="gl-display-flex gl-flex-direction-column gl-h-full gl-overflow-hidden"> <div
<slot name="select-list-type"> class="gl-display-flex gl-flex-direction-column gl-h-full gl-overflow-y-auto gl-align-items-flex-start"
<div class="gl-mb-5"></div> >
</slot> <div class="gl-px-5">
<h3 class="gl-font-lg gl-mt-5 gl-mb-2">
<p class="gl-px-5">{{ formDescription }}</p> {{ $options.i18n.scope }}
</h3>
<div class="gl-px-5 gl-pb-4"> <p class="gl-mb-3">{{ $options.i18n.scopeDescription }}</p>
<label class="gl-mb-2">{{ $options.i18n.selected }}</label>
<slot name="selected">
<div class="gl-text-gray-500">{{ $options.i18n.noneSelected }}</div>
</slot>
</div> </div>
<gl-form-group <slot name="select-list-type"></slot>
class="gl-mx-5 gl-mb-3"
:label="searchLabel" <gl-form-group class="gl-px-5 lg-mb-3 gl-max-w-full" :label="searchLabel">
label-for="board-available-column-entities" <gl-dropdown
> ref="dropdown"
<gl-search-box-by-type class="gl-mb-3 gl-max-w-full"
id="board-available-column-entities" toggle-class="gl-max-w-full gl-display-flex gl-align-items-center gl-text-trunate"
v-model="searchValue" boundary="viewport"
debounce="250" @shown="setFocus"
:placeholder="searchPlaceholder" @hide="onHide"
@input="$emit('filter-items', $event)" >
/> <template #button-content>
<slot name="selected">
<div>{{ noneSelected }}</div>
</slot>
<gl-icon class="dropdown-chevron gl-flex-shrink-0" name="chevron-down" />
</template>
<template #header>
<gl-search-box-by-type
ref="searchBox"
v-model="searchValue"
debounce="250"
class="gl-mt-0!"
:placeholder="searchPlaceholder"
@input="$emit('filter-items', $event)"
/>
</template>
<div v-if="loading" class="gl-px-5">
<gl-skeleton-loader :width="400" :height="172">
<rect width="380" height="20" x="10" y="15" rx="4" />
<rect width="280" height="20" x="10" y="50" rx="4" />
<rect width="330" height="20" x="10" y="85" rx="4" />
</gl-skeleton-loader>
</div>
<slot v-else name="items">
<p class="gl-mx-5">{{ $options.i18n.noResults }}</p>
</slot>
</gl-dropdown>
</gl-form-group> </gl-form-group>
<div v-if="loading" class="gl-px-5">
<gl-skeleton-loader :width="500" :height="172">
<rect width="480" height="20" x="10" y="15" rx="4" />
<rect width="380" height="20" x="10" y="50" rx="4" />
<rect width="430" height="20" x="10" y="85" rx="4" />
</gl-skeleton-loader>
</div>
<slot v-else name="items">
<p class="gl-mx-5">{{ $options.i18n.noResults }}</p>
</slot>
</div> </div>
<div <div
class="gl-display-flex gl-p-3 gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-rounded-bottom-left-base gl-rounded-bottom-right-base" class="gl-display-flex gl-p-3 gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-rounded-bottom-left-base gl-rounded-bottom-right-base"

View file

@ -13,9 +13,9 @@ export default {
</script> </script>
<template> <template>
<span class="gl-ml-3 gl-display-flex gl-align-items-center" data-testid="boards-create-list"> <div class="gl-ml-3 gl-display-flex gl-align-items-center" data-testid="boards-create-list">
<gl-button variant="confirm" @click="setAddColumnFormVisibility(true)" <gl-button variant="confirm" @click="setAddColumnFormVisibility(true)"
>{{ __('Create list') }} >{{ __('Create list') }}
</gl-button> </gl-button>
</span> </div>
</template> </template>

View file

@ -0,0 +1,192 @@
<script>
import { GlIcon, GlLink, GlPopover, GlLoadingIcon } from '@gitlab/ui';
import { blockingIssuablesQueries, issuableTypes } from '~/boards/constants';
import { IssueType } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { truncate } from '~/lib/utils/text_utility';
import { __, n__, s__, sprintf } from '~/locale';
export default {
i18n: {
issuableType: {
[issuableTypes.issue]: __('issue'),
},
},
graphQLIdType: {
[issuableTypes.issue]: IssueType,
},
referenceFormatter: {
[issuableTypes.issue]: (r) => r.split('/')[1],
},
defaultDisplayLimit: 3,
textTruncateWidth: 80,
components: {
GlIcon,
GlPopover,
GlLink,
GlLoadingIcon,
},
blockingIssuablesQueries,
props: {
item: {
type: Object,
required: true,
},
uniqueId: {
type: String,
required: true,
},
issuableType: {
type: String,
required: true,
validator(value) {
return [issuableTypes.issue].includes(value);
},
},
},
apollo: {
blockingIssuables: {
skip() {
return this.skip;
},
query() {
return blockingIssuablesQueries[this.issuableType].query;
},
variables() {
return {
id: convertToGraphQLId(this.$options.graphQLIdType[this.issuableType], this.item.id),
};
},
update(data) {
this.skip = true;
return data?.issuable?.blockingIssuables?.nodes || [];
},
error(error) {
const message = sprintf(s__('Boards|Failed to fetch blocking %{issuableType}s'), {
issuableType: this.issuableTypeText,
});
this.$emit('blocking-issuables-error', { error, message });
},
},
},
data() {
return {
skip: true,
blockingIssuables: [],
};
},
computed: {
displayedIssuables() {
const { defaultDisplayLimit, referenceFormatter } = this.$options;
return this.blockingIssuables.slice(0, defaultDisplayLimit).map((i) => {
return {
...i,
title: truncate(i.title, this.$options.textTruncateWidth),
reference: referenceFormatter[this.issuableType](i.reference),
};
});
},
loading() {
return this.$apollo.queries.blockingIssuables.loading;
},
issuableTypeText() {
return this.$options.i18n.issuableType[this.issuableType];
},
blockedLabel() {
return sprintf(
n__(
'Boards|Blocked by %{blockedByCount} %{issuableType}',
'Boards|Blocked by %{blockedByCount} %{issuableType}s',
this.item.blockedByCount,
),
{
blockedByCount: this.item.blockedByCount,
issuableType: this.issuableTypeText,
},
);
},
glIconId() {
return `blocked-icon-${this.uniqueId}`;
},
hasMoreIssuables() {
return this.item.blockedByCount > this.$options.defaultDisplayLimit;
},
displayedIssuablesCount() {
return this.hasMoreIssuables
? this.item.blockedByCount - this.$options.defaultDisplayLimit
: this.item.blockedByCount;
},
moreIssuablesText() {
return sprintf(
n__(
'Boards|+ %{displayedIssuablesCount} more %{issuableType}',
'Boards|+ %{displayedIssuablesCount} more %{issuableType}s',
this.displayedIssuablesCount,
),
{
displayedIssuablesCount: this.displayedIssuablesCount,
issuableType: this.issuableTypeText,
},
);
},
viewAllIssuablesText() {
return sprintf(s__('Boards|View all blocking %{issuableType}s'), {
issuableType: this.issuableTypeText,
});
},
loadingMessage() {
return sprintf(s__('Boards|Retrieving blocking %{issuableType}s'), {
issuableType: this.issuableTypeText,
});
},
},
methods: {
handleMouseEnter() {
this.skip = false;
},
},
};
</script>
<template>
<div class="gl-display-inline">
<gl-icon
:id="glIconId"
ref="icon"
name="issue-block"
class="issue-blocked-icon gl-mr-2 gl-cursor-pointer"
data-testid="issue-blocked-icon"
@mouseenter="handleMouseEnter"
/>
<gl-popover :target="glIconId" placement="top">
<template #title
><span data-testid="popover-title">{{ blockedLabel }}</span></template
>
<template v-if="loading">
<gl-loading-icon />
<p class="gl-mt-4 gl-mb-0 gl-font-small">{{ loadingMessage }}</p>
</template>
<template v-else>
<ul class="gl-list-style-none gl-p-0">
<li v-for="issuable in displayedIssuables" :key="issuable.id">
<gl-link :href="issuable.webUrl" class="gl-text-blue-500! gl-font-sm">{{
issuable.reference
}}</gl-link>
<p class="gl-mb-3 gl-display-block!" data-testid="issuable-title">
{{ issuable.title }}
</p>
</li>
</ul>
<div v-if="hasMoreIssuables" class="gl-mt-4">
<p class="gl-mb-3" data-testid="hidden-blocking-count">{{ moreIssuablesText }}</p>
<gl-link
data-testid="view-all-issues"
:href="`${item.webUrl}#related-issues`"
class="gl-text-blue-500! gl-font-sm"
>{{ viewAllIssuablesText }}</gl-link
>
</div>
</template>
</gl-popover>
</div>
</template>

View file

@ -10,6 +10,7 @@ import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import { ListType } from '../constants'; import { ListType } from '../constants';
import eventHub from '../eventhub'; import eventHub from '../eventhub';
import BoardBlockedIcon from './board_blocked_icon.vue';
import IssueDueDate from './issue_due_date.vue'; import IssueDueDate from './issue_due_date.vue';
import IssueTimeEstimate from './issue_time_estimate.vue'; import IssueTimeEstimate from './issue_time_estimate.vue';
@ -22,6 +23,7 @@ export default {
IssueDueDate, IssueDueDate,
IssueTimeEstimate, IssueTimeEstimate,
IssueCardWeight: () => import('ee_component/boards/components/issue_card_weight.vue'), IssueCardWeight: () => import('ee_component/boards/components/issue_card_weight.vue'),
BoardBlockedIcon,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
@ -52,7 +54,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['isShowingLabels']), ...mapState(['isShowingLabels', 'issuableType']),
...mapGetters(['isEpicBoard']), ...mapGetters(['isEpicBoard']),
cappedAssignees() { cappedAssignees() {
// e.g. maxRender is 4, // e.g. maxRender is 4,
@ -114,7 +116,7 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions(['performSearch']), ...mapActions(['performSearch', 'setError']),
isIndexLessThanlimit(index) { isIndexLessThanlimit(index) {
return index < this.limitBeforeCounter; return index < this.limitBeforeCounter;
}, },
@ -164,14 +166,12 @@ export default {
<div> <div>
<div class="gl-display-flex" dir="auto"> <div class="gl-display-flex" dir="auto">
<h4 class="board-card-title gl-mb-0 gl-mt-0"> <h4 class="board-card-title gl-mb-0 gl-mt-0">
<gl-icon <board-blocked-icon
v-if="item.blocked" v-if="item.blocked"
v-gl-tooltip :item="item"
name="issue-block" :unique-id="`${item.id}${list.id}`"
:title="blockedLabel" :issuable-type="issuableType"
class="issue-blocked-icon gl-mr-2" @blocking-issuables-error="setError"
:aria-label="blockedLabel"
data-testid="issue-blocked-icon"
/> />
<gl-icon <gl-icon
v-if="item.confidential" v-if="item.confidential"
@ -181,13 +181,9 @@ export default {
class="confidential-icon gl-mr-2" class="confidential-icon gl-mr-2"
:aria-label="__('Confidential')" :aria-label="__('Confidential')"
/> />
<a <a :href="item.path || item.webUrl || ''" :title="item.title" @mousemove.stop>{{
:href="item.path || item.webUrl || ''" item.title
:title="item.title" }}</a>
class="js-no-trigger"
@mousemove.stop
>{{ item.title }}</a
>
</h4> </h4>
</div> </div>
<div v-if="showLabelFooter" class="board-card-labels gl-mt-2 gl-display-flex gl-flex-wrap"> <div v-if="showLabelFooter" class="board-card-labels gl-mt-2 gl-display-flex gl-flex-wrap">

View file

@ -0,0 +1,26 @@
<script>
import { GlSkeletonLoader } from '@gitlab/ui';
export default {
name: 'BoardCardLoading',
components: {
GlSkeletonLoader,
},
};
</script>
<template>
<div
class="board-card-skeleton gl-mb-3 gl-bg-white gl-rounded-base gl-p-5 gl-border-1 gl-border-solid gl-border-gray-50"
>
<div class="board-card-skeleton-inner">
<gl-skeleton-loader :width="340" :height="100">
<rect width="340" height="16" rx="4" />
<rect y="30" width="118" height="16" rx="8" />
<rect x="122" y="30" width="130" height="16" rx="8" />
<rect y="62" width="38" height="16" rx="4" />
<circle cx="320" cy="68" r="16" />
</gl-skeleton-loader>
</div>
</div>
</template>

View file

@ -17,21 +17,20 @@ export default {
gon.features?.graphqlBoardLists || gon.features?.epicBoards gon.features?.graphqlBoardLists || gon.features?.epicBoards
? BoardColumn ? BoardColumn
: BoardColumnDeprecated, : BoardColumnDeprecated,
BoardContentSidebar: () => import('ee_component/boards/components/board_content_sidebar.vue'), BoardContentSidebar: () => import('~/boards/components/board_content_sidebar.vue'),
EpicBoardContentSidebar: () =>
import('ee_component/boards/components/epic_board_content_sidebar.vue'),
EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'), EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'),
GlAlert, GlAlert,
}, },
mixins: [glFeatureFlagMixin()], mixins: [glFeatureFlagMixin()],
inject: ['canAdminList'],
props: { props: {
lists: { lists: {
type: Array, type: Array,
required: false, required: false,
default: () => [], default: () => [],
}, },
canAdminList: {
type: Boolean,
required: true,
},
disabled: { disabled: {
type: Boolean, type: Boolean,
required: true, required: true,
@ -69,7 +68,7 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions(['moveList']), ...mapActions(['moveList', 'unsetError']),
afterFormEnters() { afterFormEnters() {
const el = this.canDragColumns ? this.$refs.list.$el : this.$refs.list; const el = this.canDragColumns ? this.$refs.list.$el : this.$refs.list;
el.scrollTo({ left: el.scrollWidth, behavior: 'smooth' }); el.scrollTo({ left: el.scrollWidth, behavior: 'smooth' });
@ -99,8 +98,8 @@ export default {
</script> </script>
<template> <template>
<div> <div v-cloak data-qa-selector="boards_list">
<gl-alert v-if="error" variant="danger" :dismissible="false"> <gl-alert v-if="error" variant="danger" :dismissible="true" @dismiss="unsetError">
{{ error }} {{ error }}
</gl-alert> </gl-alert>
<component <component
@ -127,13 +126,23 @@ export default {
</component> </component>
<epics-swimlanes <epics-swimlanes
v-else v-else-if="boardListsToUse.length"
ref="swimlanes" ref="swimlanes"
:lists="boardListsToUse" :lists="boardListsToUse"
:can-admin-list="canAdminList" :can-admin-list="canAdminList"
:disabled="disabled" :disabled="disabled"
/> />
<board-content-sidebar v-if="isSwimlanesOn || glFeatures.graphqlBoardLists" /> <board-content-sidebar
v-if="isSwimlanesOn || glFeatures.graphqlBoardLists"
class="boards-sidebar"
data-testid="issue-boards-sidebar"
/>
<epic-board-content-sidebar
v-else-if="isEpicBoard"
class="boards-sidebar"
data-testid="epic-boards-sidebar"
/>
</div> </div>
</template> </template>

View file

@ -0,0 +1,96 @@
<script>
import { GlDrawer } from '@gitlab/ui';
import { mapState, mapActions, mapGetters } from 'vuex';
import BoardSidebarDueDate from '~/boards/components/sidebar/board_sidebar_due_date.vue';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue';
import BoardSidebarSubscription from '~/boards/components/sidebar/board_sidebar_subscription.vue';
import BoardSidebarTimeTracker from '~/boards/components/sidebar/board_sidebar_time_tracker.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants';
import { contentTop } from '~/lib/utils/common_utils';
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
headerHeight: `${contentTop()}px`,
components: {
GlDrawer,
BoardSidebarTitle,
SidebarAssigneesWidget,
BoardSidebarTimeTracker,
BoardSidebarLabelsSelect,
BoardSidebarDueDate,
BoardSidebarSubscription,
BoardSidebarMilestoneSelect,
BoardSidebarEpicSelect: () =>
import('ee_component/boards/components/sidebar/board_sidebar_epic_select.vue'),
BoardSidebarWeightInput: () =>
import('ee_component/boards/components/sidebar/board_sidebar_weight_input.vue'),
SidebarIterationWidget: () =>
import('ee_component/sidebar/components/sidebar_iteration_widget.vue'),
},
mixins: [glFeatureFlagsMixin()],
computed: {
...mapGetters([
'isSidebarOpen',
'activeBoardItem',
'groupPathForActiveIssue',
'projectPathForActiveIssue',
]),
...mapState(['sidebarType', 'issuableType']),
isIssuableSidebar() {
return this.sidebarType === ISSUABLE;
},
showSidebar() {
return this.isIssuableSidebar && this.isSidebarOpen;
},
fullPath() {
return this.activeBoardItem?.referencePath?.split('#')[0] || '';
},
},
methods: {
...mapActions(['toggleBoardItem', 'setAssignees']),
handleClose() {
this.toggleBoardItem({ boardItem: this.activeBoardItem, sidebarType: this.sidebarType });
},
},
};
</script>
<template>
<gl-drawer
v-if="showSidebar"
:open="isSidebarOpen"
:header-height="$options.headerHeight"
@close="handleClose"
>
<template #header>{{ __('Issue details') }}</template>
<template #default>
<board-sidebar-title />
<sidebar-assignees-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:initial-assignees="activeBoardItem.assignees"
class="assignee"
@assignees-updated="setAssignees"
/>
<board-sidebar-epic-select class="epic" />
<div>
<board-sidebar-milestone-select />
<sidebar-iteration-widget
:iid="activeBoardItem.iid"
:workspace-path="projectPathForActiveIssue"
:iterations-workspace-path="groupPathForActiveIssue"
:issuable-type="issuableType"
class="gl-mt-5"
/>
</div>
<board-sidebar-time-tracker class="swimlanes-sidebar-time-tracker" />
<board-sidebar-due-date />
<board-sidebar-labels-select class="labels" />
<board-sidebar-weight-input v-if="glFeatures.issueWeights" class="weight" />
<board-sidebar-subscription class="subscriptions" />
</template>
</gl-drawer>
</template>

View file

@ -1,57 +0,0 @@
<script>
import { GlTooltip, GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
name: 'BoardExtraActions',
components: {
GlTooltip,
GlButton,
},
props: {
canAdminList: {
type: Boolean,
required: true,
},
disabled: {
type: Boolean,
required: true,
},
openModal: {
type: Function,
required: true,
},
},
computed: {
tooltipTitle() {
if (this.disabled) {
return __('Please add a list to your board first');
}
return '';
},
},
};
</script>
<template>
<div class="board-extra-actions">
<span ref="addIssuesButtonTooltip" class="gl-ml-3">
<gl-button
v-if="canAdminList"
type="button"
data-placement="bottom"
data-track-event="click_button"
data-track-label="board_add_issues"
:disabled="disabled"
:aria-disabled="disabled"
@click="openModal"
>
{{ __('Add issues') }}
</gl-button>
</span>
<gl-tooltip v-if="disabled" :target="() => $refs.addIssuesButtonTooltip" placement="bottom">
{{ tooltipTitle }}
</gl-tooltip>
</div>
</template>

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