New upstream version 13.6.5

This commit is contained in:
Pirate Praveen 2021-01-29 00:20:46 +05:30
parent be82141550
commit e087feee83
5313 changed files with 244970 additions and 60390 deletions

View file

@ -3,6 +3,7 @@ extends:
- plugin:@gitlab/i18n - plugin:@gitlab/i18n
- plugin:no-jquery/slim - plugin:no-jquery/slim
- plugin:no-jquery/deprecated-3.4 - plugin:no-jquery/deprecated-3.4
- ./tooling/eslint-config/conditionally_ignore_ee.js
globals: globals:
__webpack_public_path__: true __webpack_public_path__: true
gl: false gl: false
@ -25,9 +26,6 @@ rules:
- _links - _links
import/no-unresolved: import/no-unresolved:
- error - error
- ignore:
# https://gitlab.com/gitlab-org/gitlab/issues/38226
- '^ee_component/'
# Disabled for now, to make the airbnb-base 12.1.0 -> 13.1.0 update smoother # Disabled for now, to make the airbnb-base 12.1.0 -> 13.1.0 update smoother
no-else-return: no-else-return:
- error - error

1
.gitattributes vendored
View file

@ -1,3 +1,4 @@
VERSION merge=ours VERSION merge=ours
Dangerfile gitlab-language=ruby Dangerfile gitlab-language=ruby
*.pdf filter=lfs diff=lfs merge=lfs -text *.pdf filter=lfs diff=lfs merge=lfs -text
*.rb diff=ruby

6
.gitignore vendored
View file

@ -56,12 +56,15 @@ eslint-report.html
/doc/code/* /doc/code/*
/dump.rdb /dump.rdb
/jsconfig.json /jsconfig.json
/lefthook-local.yml
/log/*.log* /log/*.log*
/node_modules /node_modules
/nohup.out /nohup.out
/public/assets/ /public/assets/
/public/uploads.* /public/uploads.*
/public/uploads/ /public/uploads/
/public/sitemap.xml
/public/sitemap.xml.gz
/shared/artifacts/ /shared/artifacts/
/spec/examples.txt /spec/examples.txt
/rails_best_practices_output.html /rails_best_practices_output.html
@ -73,6 +76,7 @@ eslint-report.html
/.gitlab_pages_secret /.gitlab_pages_secret
/.gitlab_kas_secret /.gitlab_kas_secret
/webpack-report/ /webpack-report/
/crystalball/
/knapsack/ /knapsack/
/rspec_flaky/ /rspec_flaky/
/locale/**/LC_MESSAGES /locale/**/LC_MESSAGES
@ -97,3 +101,5 @@ apollo.config.js
/tmp/matching_tests.txt /tmp/matching_tests.txt
ee/changelogs/unreleased-ee ee/changelogs/unreleased-ee
/sitespeed-result /sitespeed-result
tags.lock
tags.temp

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.6.6-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
tags: tags:
- gitlab-org - gitlab-org
# All jobs are interruptible by default # All jobs are interruptible by default
@ -59,10 +59,13 @@ variables:
GET_SOURCES_ATTEMPTS: "3" GET_SOURCES_ATTEMPTS: "3"
KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/report-master.json KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/report-master.json
FLAKY_RSPEC_SUITE_REPORT_PATH: rspec_flaky/report-suite.json FLAKY_RSPEC_SUITE_REPORT_PATH: rspec_flaky/report-suite.json
RSPEC_TESTS_MAPPING_PATH: crystalball/mapping.json
RSPEC_PACKED_TESTS_MAPPING_PATH: crystalball/packed-mapping.json
BUILD_ASSETS_IMAGE: "false" BUILD_ASSETS_IMAGE: "false"
ES_JAVA_OPTS: "-Xms256m -Xmx256m" ES_JAVA_OPTS: "-Xms256m -Xmx256m"
ELASTIC_URL: "http://elastic:changeme@elasticsearch:9200" ELASTIC_URL: "http://elastic:changeme@elasticsearch:9200"
DOCKER_VERSION: "19.03.0" DOCKER_VERSION: "19.03.0"
CACHE_CLASSES: "true"
# Preparing custom clone path to reduce space used by all random forks # Preparing custom clone path to reduce space used by all random forks
# on GitLab.com's Shared Runners. Our main forks - especially the security # on GitLab.com's Shared Runners. Our main forks - especially the security

View file

@ -207,6 +207,35 @@ Dangerfile @gl-quality/eng-prod
/ee/lib/gitlab/ci/reports/license_scanning/ @gitlab-org/secure/composition-analysis-be /ee/lib/gitlab/ci/reports/license_scanning/ @gitlab-org/secure/composition-analysis-be
/ee/lib/gitlab/ci/reports/security/ @gitlab-org/secure/composition-analysis-be @gitlab-org/secure/dynamic-analysis-be @gitlab-org/secure/static-analysis-be @gitlab-org/secure/fuzzing-be /ee/lib/gitlab/ci/reports/security/ @gitlab-org/secure/composition-analysis-be @gitlab-org/secure/dynamic-analysis-be @gitlab-org/secure/static-analysis-be @gitlab-org/secure/fuzzing-be
[Container Security]
/ee/app/views/projects/threat_monitoring/** @gitlab-org/threat-management/defend/container-security/frontend
/ee/app/assets/javascripts/pages/projects/threat_monitoring/** @gitlab-org/threat-management/defend/container-security/frontend
/ee/app/assets/javascripts/threat_monitoring/** @gitlab-org/threat-management/defend/container-security/frontend
/ee/spec/frontend/threat_monitoring/** @gitlab-org/threat-management/defend/container-security/frontend
/ee/app/controllers/projects/threat_monitoring_controller.rb @gitlab-org/threat-management/defend/container-security/backend
/ee/spec/controllers/projects/threat_monitoring_controller_spec.rb @gitlab-org/threat-management/defend/container-security/backend
/lib/gitlab/kubernetes/cilium_network_policy.rb @gitlab-org/threat-management/defend/container-security/backend
/spec/lib/gitlab/kubernetes/cilium_network_policy_spec.rb @gitlab-org/threat-management/defend/container-security/backend
/lib/gitlab/kubernetes/network_policy_common.rb @gitlab-org/threat-management/defend/container-security/backend
/spec/support/shared_examples/lib/gitlab/kubernetes/network_policy_common_shared_examples.rb @gitlab-org/threat-management/defend/container-security/backend
/lib/gitlab/kubernetes/network_policy.rb @gitlab-org/threat-management/defend/container-security/backend
/spec/lib/gitlab/kubernetes/network_policy_spec.rb @gitlab-org/threat-management/defend/container-security/backend
/ee/app/services/network_policies/** @gitlab-org/threat-management/defend/container-security/backend
/ee/spec/services/network_policies/** @gitlab-org/threat-management/defend/container-security/backend
/ee/app/controllers/projects/security/waf_anomalies_controller.rb @gitlab-org/threat-management/defend/container-security/backend
/ee/spec/controllers/projects/security/waf_anomalies_controller_spec.rb @gitlab-org/threat-management/defend/container-security/backend
/app/models/clusters/applications/cilium.rb @gitlab-org/threat-management/defend/container-security/backend
/spec/models/clusters/applications/cilium_spec.rb @gitlab-org/threat-management/defend/container-security/backend
/ee/app/controllers/projects/security/network_policies_controller.rb @gitlab-org/threat-management/defend/container-security/backend
/ee/spec/controllers/projects/security/network_policies_controller_spec.rb @gitlab-org/threat-management/defend/container-security/backend
/ee/app/workers/network_policy_metrics_worker.rb @gitlab-org/threat-management/defend/container-security/backend
/ee/spec/workers/network_policy_metrics_worker_spec.rb @gitlab-org/threat-management/defend/container-security/backend
/ee/app/services/network_policies/** @gitlab-org/threat-management/defend/container-security/backend
/ee/spec/services/network_policies/** @gitlab-org/threat-management/defend/container-security/backend
/ee/lib/gitlab/usage_data_counters/network_policy_counter.rb @gitlab-org/threat-management/defend/container-security/backend
/ee/spec/lib/gitlab/usage_data_counters/network_policy_counter_spec.rb @gitlab-org/threat-management/defend/container-security/backend
[Code Owners] [Code Owners]
/ee/lib/gitlab/code_owners.rb @reprazent @kerrizor @garyh /ee/lib/gitlab/code_owners.rb @reprazent @kerrizor @garyh
/ee/lib/gitlab/code_owners/ @reprazent @kerrizor @garyh /ee/lib/gitlab/code_owners/ @reprazent @kerrizor @garyh

View file

@ -23,14 +23,36 @@ cache-repo:
stage: sync stage: sync
variables: variables:
GIT_STRATEGY: none GIT_STRATEGY: none
TAR_FILENAME: /tmp/gitlab-master.tar SHALLOW_CLONE_TAR_FILENAME: gitlab-master-shallow.tar
FULL_CLONE_TAR_FILENAME: gitlab-master.tar
before_script:
- '[ -z "$CI_REPO_CACHE_CREDENTIALS" ] || gcloud auth activate-service-account --key-file=$CI_REPO_CACHE_CREDENTIALS'
script: script:
- cd .. # Enable shallow repo caching only if the $ENABLE_SHALLOW_REPO_CACHING variable exists
- rm -rf $CI_PROJECT_NAME - if [ -n "$ENABLE_SHALLOW_REPO_CACHING" ]; then
- git clone --progress $CI_REPOSITORY_URL $CI_PROJECT_NAME cd .. && rm -rf $CI_PROJECT_NAME;
- cd $CI_PROJECT_NAME today=$(date +%Y-%m-%d);
- gcloud auth activate-service-account --key-file=$CI_REPO_CACHE_CREDENTIALS year=$(date +%Y);
- git remote rm origin last_year=`expr $year - 1`;
- tar cf $TAR_FILENAME . one_year_ago=$(echo $today | sed "s/$year/$last_year/");
- gzip $TAR_FILENAME echo "Cloning $CI_REPOSITORY_URL into $CI_PROJECT_NAME with commits from $one_year_ago.";
- gsutil cp $TAR_FILENAME.gz gs://gitlab-ci-git-repo-cache/project-$CI_PROJECT_ID/gitlab-master.tar.gz time git clone --progress --no-checkout --shallow-since=$one_year_ago $CI_REPOSITORY_URL $CI_PROJECT_NAME;
cd $CI_PROJECT_NAME;
echo "Archiving $CI_PROJECT_NAME into /tmp/$SHALLOW_CLONE_TAR_FILENAME.";
time tar cf /tmp/$SHALLOW_CLONE_TAR_FILENAME .;
echo "GZipping /tmp/$SHALLOW_CLONE_TAR_FILENAME.";
time gzip /tmp/$SHALLOW_CLONE_TAR_FILENAME;
[ -z "$CI_REPO_CACHE_CREDENTIALS" ] || (echo "Uploading /tmp/$SHALLOW_CLONE_TAR_FILENAME.gz to GCloud." && time gsutil cp /tmp/$SHALLOW_CLONE_TAR_FILENAME.gz gs://gitlab-ci-git-repo-cache/project-$CI_PROJECT_ID/$SHALLOW_CLONE_TAR_FILENAME.gz);
fi
# By default, we want to cache the full repo, unless the $DISABLE_FULL_REPO_CACHING variable exists (in the case the shallow clone caching is working well)
- if [ -z "$DISABLE_FULL_REPO_CACHING" ]; then
cd .. && rm -rf $CI_PROJECT_NAME;
echo "Cloning $CI_REPOSITORY_URL into $CI_PROJECT_NAME.";
time git clone --progress $CI_REPOSITORY_URL $CI_PROJECT_NAME;
cd $CI_PROJECT_NAME;
echo "Archiving $CI_PROJECT_NAME into /tmp/$FULL_CLONE_TAR_FILENAME.";
time tar cf /tmp/$FULL_CLONE_TAR_FILENAME .;
echo "GZipping /tmp/$FULL_CLONE_TAR_FILENAME.";
time gzip /tmp/$FULL_CLONE_TAR_FILENAME;
[ -z "$CI_REPO_CACHE_CREDENTIALS" ] || (echo "Uploading /tmp/$FULL_CLONE_TAR_FILENAME.gz to GCloud." && time gsutil cp /tmp/$FULL_CLONE_TAR_FILENAME.gz gs://gitlab-ci-git-repo-cache/project-$CI_PROJECT_ID/$FULL_CLONE_TAR_FILENAME.gz);
fi

View file

@ -1,6 +1,6 @@
cloud-native-image: cloud-native-image:
extends: .cng:rules extends: .cng:rules
image: ruby:2.6-alpine image: ruby:2.7-alpine
dependencies: [] dependencies: []
stage: post-test stage: post-test
variables: variables:

View file

@ -2,7 +2,7 @@
extends: extends:
- .default-retry - .default-retry
- .docs:rules:review-docs - .docs:rules:review-docs
image: ruby:2.6-alpine image: ruby:2.7-alpine
stage: review stage: review
needs: [] needs: []
variables: variables:
@ -38,7 +38,18 @@ review-docs-cleanup:
script: script:
- ./scripts/trigger-build docs cleanup - ./scripts/trigger-build docs cleanup
docs lint: docs-lint markdown:
extends:
- .default-retry
- .docs:rules:docs-lint
# When updating the image version here, update it in /scripts/lint-doc.sh too.
image: "registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.12-vale-2.6.1-markdownlint-0.24.0"
stage: test
needs: []
script:
- scripts/lint-doc.sh
docs-lint links:
extends: extends:
- .default-retry - .default-retry
- .docs:rules:docs-lint - .docs:rules:docs-lint
@ -46,7 +57,6 @@ docs lint:
stage: test stage: test
needs: [] needs: []
script: script:
- scripts/lint-doc.sh
# Prepare docs for build # Prepare docs for build
# The path must be 'ee/' because we have hardcoded links relying on it # The path must be 'ee/' because we have hardcoded links relying on it
# https://gitlab.com/gitlab-org/gitlab-docs/-/blob/887850752fc0e72856da6632db132f005ba77f16/content/index.erb#L44-63 # https://gitlab.com/gitlab-org/gitlab-docs/-/blob/887850752fc0e72856da6632db132f005ba77f16/content/index.erb#L44-63

View file

@ -15,7 +15,7 @@
extends: extends:
- .frontend-base - .frontend-base
- .assets-compile-cache - .assets-compile-cache
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-git-2.28-lfs-2.9-node-12.18-yarn-1.22-graphicsmagick-1.3.34 image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-git-2.29-lfs-2.9-node-12.18-yarn-1.22-graphicsmagick-1.3.34
variables: variables:
WEBPACK_VENDOR_DLL: "true" WEBPACK_VENDOR_DLL: "true"
stage: prepare stage: prepare
@ -97,32 +97,41 @@ update-yarn-cache:
- .rails-cache - .rails-cache
- .use-pg11 - .use-pg11
stage: fixtures stage: fixtures
needs: ["setup-test-env", "compile-test-assets"] needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
variables: variables:
SETUP_DB: "true" SETUP_DB: "true"
WEBPACK_VENDOR_DLL: "true" WEBPACK_VENDOR_DLL: "true"
script: script:
- run_timed_command "gem install knapsack --no-document"
- run_timed_command "scripts/gitaly-test-build" - run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn" - run_timed_command "scripts/gitaly-test-spawn"
- run_timed_command "bin/rake frontend:fixtures" - source ./scripts/rspec_helpers.sh
- rspec_paralellized_job "--tag frontend_fixture"
artifacts: artifacts:
name: frontend-fixtures name: frontend-fixtures
expire_in: 31d expire_in: 31d
when: always when: always
paths: paths:
- tmp/tests/frontend/ - tmp/tests/frontend/
- knapsack/
frontend-fixtures: rspec frontend_fixture:
extends: extends:
- .frontend-fixtures-base - .frontend-fixtures-base
- .frontend:rules:default-frontend-jobs - .frontend:rules:default-frontend-jobs
frontend-fixtures-as-if-foss: rspec frontend_fixture as-if-foss:
extends: extends:
- .frontend-fixtures-base - .frontend-fixtures-base
- .frontend:rules:default-frontend-jobs-as-if-foss - .frontend:rules:default-frontend-jobs-as-if-foss
- .as-if-foss - .as-if-foss
rspec-ee frontend_fixture:
extends:
- .frontend-fixtures-base
- .frontend:rules:default-frontend-jobs
parallel: 2
.frontend-test-base: .frontend-test-base:
extends: extends:
- .frontend-base - .frontend-base
@ -152,7 +161,8 @@ karma:
extends: extends:
- .karma-base - .karma-base
- .frontend:rules:default-frontend-jobs - .frontend:rules:default-frontend-jobs
needs: ["frontend-fixtures"] # Don't use `needs` since `rspec-ee frontend_fixture` doesn't exist in `gitlab-foss` pipelines.
dependencies: ["rspec frontend_fixture", "rspec-ee frontend_fixture"]
coverage: '/^Statements *: (\d+\.\d+%)/' coverage: '/^Statements *: (\d+\.\d+%)/'
artifacts: artifacts:
name: coverage-javascript name: coverage-javascript
@ -171,7 +181,7 @@ karma-as-if-foss:
- .karma-base - .karma-base
- .frontend:rules:default-frontend-jobs-as-if-foss - .frontend:rules:default-frontend-jobs-as-if-foss
- .as-if-foss - .as-if-foss
needs: ["frontend-fixtures-as-if-foss"] needs: ["rspec frontend_fixture as-if-foss"]
.jest-base: .jest-base:
extends: .frontend-test-base extends: .frontend-test-base
@ -183,7 +193,8 @@ jest:
extends: extends:
- .jest-base - .jest-base
- .frontend:rules:default-frontend-jobs - .frontend:rules:default-frontend-jobs
needs: ["frontend-fixtures"] # Don't use `needs` since `rspec-ee frontend_fixture` doesn't exist in `gitlab-foss` pipelines.
dependencies: ["rspec frontend_fixture", "rspec-ee frontend_fixture"]
artifacts: artifacts:
name: coverage-frontend name: coverage-frontend
expire_in: 31d expire_in: 31d
@ -203,14 +214,15 @@ jest-integration:
script: script:
- *yarn-install - *yarn-install
- run_timed_command "yarn jest:integration --ci" - run_timed_command "yarn jest:integration --ci"
needs: ["frontend-fixtures"] # Don't use `needs` since `rspec-ee frontend_fixture` doesn't exist in `gitlab-foss` pipelines.
dependencies: ["rspec frontend_fixture", "rspec-ee frontend_fixture"]
jest-as-if-foss: jest-as-if-foss:
extends: extends:
- .jest-base - .jest-base
- .frontend:rules:default-frontend-jobs-as-if-foss - .frontend:rules:default-frontend-jobs-as-if-foss
- .as-if-foss - .as-if-foss
needs: ["frontend-fixtures-as-if-foss"] needs: ["rspec frontend_fixture as-if-foss"]
parallel: 2 parallel: 2
coverage-frontend: coverage-frontend:

View file

@ -18,7 +18,7 @@
.rails-cache: .rails-cache:
cache: cache:
key: "rails-v2" key: "rails-v3"
paths: paths:
- vendor/ruby/ - vendor/ruby/
- vendor/gitaly-ruby/ - vendor/gitaly-ruby/
@ -27,7 +27,7 @@
.static-analysis-cache: .static-analysis-cache:
cache: cache:
key: "static-analysis-v1" key: "static-analysis-v2"
paths: paths:
- vendor/ruby/ - vendor/ruby/
- node_modules/ - node_modules/
@ -43,7 +43,7 @@
.qa-cache: .qa-cache:
cache: cache:
key: "qa-v1" key: "qa-v2"
paths: paths:
- qa/vendor/ruby/ - qa/vendor/ruby/
policy: pull policy: pull
@ -71,7 +71,7 @@
policy: pull policy: pull
.use-pg11: .use-pg11:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
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"]
@ -80,7 +80,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.6.6-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34"
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"]
@ -89,7 +89,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.6.6-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
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"]
@ -100,7 +100,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.6.6-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34"
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

@ -7,7 +7,7 @@
before_script: before_script:
- '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/ qa/spec/ee/ qa/qa/specs/features/ee/ qa/qa/ee/ qa/qa/ee.rb' - '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/ qa/spec/ee/ qa/qa/specs/features/ee/ qa/qa/ee/ qa/qa/ee.rb'
- cd qa/ - cd qa/
- bundle install --clean --jobs=$(nproc) --path=vendor --retry=3 --quiet - bundle install --clean --jobs=$(nproc) --path=vendor --retry=3 --without=development --quiet
- bundle check - bundle check
qa:internal: qa:internal:
@ -47,7 +47,7 @@ update-qa-cache:
policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up. policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
.package-and-qa-base: .package-and-qa-base:
image: ruby:2.6-alpine image: ruby:2.7-alpine
stage: qa stage: qa
retry: 0 retry: 0
script: script:

View file

@ -1,4 +1,4 @@
###################### #######################
# rspec job base specs # rspec job base specs
.rails-job-base: .rails-job-base:
extends: extends:
@ -20,6 +20,7 @@
variables: variables:
RUBY_GC_MALLOC_LIMIT: 67108864 RUBY_GC_MALLOC_LIMIT: 67108864
RUBY_GC_MALLOC_LIMIT_MAX: 134217728 RUBY_GC_MALLOC_LIMIT_MAX: 134217728
CRYSTALBALL: "true"
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"] needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
script: script:
- *base-script - *base-script
@ -29,6 +30,7 @@
when: always when: always
paths: paths:
- coverage/ - coverage/
- crystalball/
- knapsack/ - knapsack/
- rspec_flaky/ - rspec_flaky/
- rspec_profiling/ - rspec_profiling/
@ -284,6 +286,9 @@ db:migrate-from-v12.10.0:
- '[[ -d "ee/" ]] || export TAG_TO_CHECKOUT="v12.10.0"' - '[[ -d "ee/" ]] || export TAG_TO_CHECKOUT="v12.10.0"'
- git fetch https://gitlab.com/gitlab-org/$PROJECT_TO_CHECKOUT.git $TAG_TO_CHECKOUT - git fetch https://gitlab.com/gitlab-org/$PROJECT_TO_CHECKOUT.git $TAG_TO_CHECKOUT
- git checkout -f FETCH_HEAD - git checkout -f FETCH_HEAD
- 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
- gem install bundler:1.17.3
- bundle update google-protobuf grpc bootsnap - bundle update google-protobuf grpc bootsnap
- bundle install $BUNDLE_INSTALL_FLAGS - bundle install $BUNDLE_INSTALL_FLAGS
- date - date

View file

@ -15,7 +15,7 @@ code_quality:
stage: test stage: test
needs: [] needs: []
variables: variables:
CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.10-gitlab.1" CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.18"
script: script:
- | - |
if ! docker info &>/dev/null; then if ! docker info &>/dev/null; then
@ -152,6 +152,26 @@ dependency_scanning:
dependency_scanning: gl-dependency-scanning-report.json dependency_scanning: gl-dependency-scanning-report.json
expire_in: 1 week # GitLab-specific expire_in: 1 week # GitLab-specific
# The job below analysis dependencies for malicous behavior
package_hunter:
extends:
- .reports:schedule-dast
stage: test
image:
name: registry.gitlab.com/gitlab-com/gl-security/security-research/package-hunter-cli:latest
entrypoint: [""]
needs: []
script:
- rm -r spec locale .git app/assets/images doc/
- cd .. && tar -I "gzip --best" -cf gitlab.tgz gitlab/
- DEBUG=* HTR_user=$PACKAGE_HUNTER_USER HTR_pass=$PACKAGE_HUNTER_PASS node /usr/src/app/cli.js analyze --format gitlab gitlab.tgz | tee $CI_PROJECT_DIR/gl-dependency-scanning-report.json
artifacts:
paths:
- gl-dependency-scanning-report.json # GitLab-specific
reports:
dependency_scanning: gl-dependency-scanning-report.json
expire_in: 1 week # GitLab-specific
license_scanning: license_scanning:
extends: extends:
- .default-retry - .default-retry

View file

@ -25,7 +25,7 @@ review-build-cng:
extends: extends:
- .default-retry - .default-retry
- .review:rules:review-build-cng - .review:rules:review-build-cng
image: ruby:2.6-alpine image: ruby:2.7-alpine
stage: review-prepare stage: review-prepare
before_script: before_script:
- source ./scripts/utils.sh - source ./scripts/utils.sh
@ -122,7 +122,7 @@ review-stop:
extends: extends:
- .default-retry - .default-retry
- .use-docker-in-docker - .use-docker-in-docker
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6 image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.7
stage: qa stage: qa
# This is needed so that manual jobs with needs don't block the pipeline. # This is needed so that manual jobs with needs don't block the pipeline.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/199979. # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
@ -199,7 +199,7 @@ review-performance:
parallel-spec-reports: parallel-spec-reports:
extends: extends:
- .review:rules:mr-only-manual - .review:rules:mr-only-manual
image: ruby:2.6-alpine image: ruby:2.7-alpine
stage: post-qa stage: post-qa
dependencies: ["review-qa-all"] dependencies: ["review-qa-all"]
variables: variables:

View file

@ -103,8 +103,11 @@
- ".gitlab/ci/build-images.gitlab-ci.yml" - ".gitlab/ci/build-images.gitlab-ci.yml"
- ".gitlab/ci/qa.gitlab-ci.yml" - ".gitlab/ci/qa.gitlab-ci.yml"
.yaml-patterns: &yaml-patterns .yaml-lint-patterns: &yaml-lint-patterns
- "**/*.yml" - ".gitlab-ci.yml"
- ".gitlab/ci/**/*.yml"
- "lib/gitlab/ci/templates/**/*.yml"
- "{,ee/}changelogs/**/*.yml"
.docs-patterns: &docs-patterns .docs-patterns: &docs-patterns
- ".gitlab/route-map.yml" - ".gitlab/route-map.yml"
@ -161,7 +164,7 @@
- "vendor/assets/**/*" - "vendor/assets/**/*"
- ".gitlab/ci/**/*" - ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}" - ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml" - ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,rubocop_manual_todo,scss-lint}.yml"
- "*_VERSION" - "*_VERSION"
- "Gemfile{,.lock}" - "Gemfile{,.lock}"
- "Rakefile" - "Rakefile"
@ -183,7 +186,7 @@
- "vendor/assets/**/*" - "vendor/assets/**/*"
- ".gitlab/ci/**/*" - ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}" - ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml" - ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,rubocop_manual_todo,scss-lint}.yml"
- "*_VERSION" - "*_VERSION"
- "Gemfile{,.lock}" - "Gemfile{,.lock}"
- "Rakefile" - "Rakefile"
@ -207,7 +210,7 @@
- "vendor/assets/**/*" - "vendor/assets/**/*"
- ".gitlab/ci/**/*" - ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}" - ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml" - ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,rubocop_manual_todo,scss-lint}.yml"
- "*_VERSION" - "*_VERSION"
- "Gemfile{,.lock}" - "Gemfile{,.lock}"
- "Rakefile" - "Rakefile"
@ -228,7 +231,7 @@
- "vendor/assets/**/*" - "vendor/assets/**/*"
- ".gitlab/ci/**/*" - ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}" - ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml" - ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,rubocop_manual_todo,scss-lint}.yml"
- "*_VERSION" - "*_VERSION"
- "Gemfile{,.lock}" - "Gemfile{,.lock}"
- "Rakefile" - "Rakefile"
@ -673,10 +676,14 @@
################## ##################
.releases:rules:canonical-dot-com-gitlab-stable-branch-only: .releases:rules:canonical-dot-com-gitlab-stable-branch-only:
rules: rules:
- if: '$CI_COMMIT_MESSAGE =~ /\[merge-train skip\]/'
when: never
- if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable-ee$/' - if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable-ee$/'
.releases:rules:canonical-dot-com-security-gitlab-stable-branch-only: .releases:rules:canonical-dot-com-security-gitlab-stable-branch-only:
rules: rules:
- if: '$CI_COMMIT_MESSAGE =~ /\[merge-train skip\]/'
when: never
- if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/security/gitlab" && $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable-ee$/' - if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/security/gitlab" && $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable-ee$/'
################# #################
@ -905,10 +912,10 @@
- <<: *if-dot-com-ee-schedule - <<: *if-dot-com-ee-schedule
changes: *code-backstage-patterns changes: *code-backstage-patterns
############## ###################
# YAML rules # # yaml-lint rules #
############## ###################
.yaml:rules: .yaml-lint:rules:
rules: rules:
- <<: *if-default-refs - <<: *if-default-refs
changes: *yaml-patterns changes: *yaml-lint-patterns

View file

@ -52,7 +52,7 @@ no_ee_check:
verify-tests-yml: verify-tests-yml:
extends: extends:
- .setup:rules:verify-tests-yml - .setup:rules:verify-tests-yml
image: ruby:2.6-alpine image: ruby:2.7-alpine
stage: test stage: test
needs: [] needs: []
script: script:
@ -61,7 +61,7 @@ verify-tests-yml:
- scripts/verify-tff-mapping - scripts/verify-tff-mapping
.detect-test-base: .detect-test-base:
image: ruby:2.6-alpine image: ruby:2.7-alpine
needs: [] needs: []
stage: prepare stage: prepare
script: script:

View file

@ -9,6 +9,7 @@
- knapsack/ - knapsack/
- rspec_flaky/ - rspec_flaky/
- rspec_profiling/ - rspec_profiling/
- crystalball/packed-mapping.json.gz
retrieve-tests-metadata: retrieve-tests-metadata:
extends: extends:
@ -27,6 +28,8 @@ update-tests-metadata:
dependencies: dependencies:
- setup-test-env - setup-test-env
- rspec migration pg11 - rspec migration pg11
- rspec frontend_fixture
- rspec-ee frontend_fixture
- rspec unit pg11 - rspec unit pg11
- rspec integration pg11 - rspec integration pg11
- rspec system pg11 - rspec system pg11
@ -41,3 +44,4 @@ update-tests-metadata:
- run_timed_command "retry gem install fog-aws mime-types activesupport rspec_profiling postgres-copy --no-document" - run_timed_command "retry gem install fog-aws mime-types activesupport rspec_profiling postgres-copy --no-document"
- source ./scripts/rspec_helpers.sh - source ./scripts/rspec_helpers.sh
- update_tests_metadata - update_tests_metadata
- update_tests_mapping

View file

@ -1,9 +1,9 @@
# Yamllint of *.yml for .gitlab-ci.yml. # Yamllint of CI-related yaml and changelogs.
# This uses rules from project root `.yamllint`. # This uses rules from project root `.yamllint`.
lint-ci-gitlab: lint-yaml:
extends: extends:
- .default-retry - .default-retry
- .yaml:rules - .yaml-lint:rules
image: pipelinecomponents/yamllint:latest image: pipelinecomponents/yamllint:latest
stage: test stage: test
needs: [] needs: []

View file

@ -0,0 +1,11 @@
<!-- This template is a great use for issues that are feature::additions or technical tasks for larger issues.-->
### Proposal
<!-- Use this section to explain the feature and how it will work. It can be helpful to add technical details, design proposals, and links to related epics or issues. -->
<!-- Consider adding related issues and epics to this issue. You can also reference the Feature Proposal Template (https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Feature%20proposal.md) for additional details to consider adding to this issue. Additionally, as a data oriented organization, when your feature exits planning breakdown, consider adding the `What does success look like, and how can we measure that?` section.
/label ~"group::" ~"section::" ~"Category::" ~"GitLab Core"/~"GitLab Starter"/~"GitLab Premium"/~"GitLab Ultimate"
-->

View file

@ -34,7 +34,9 @@ If applicable, any groups/projects that are happy to have this feature turned on
- [ ] Test on staging - [ ] Test on staging
- [ ] Ensure that documentation has been updated - [ ] 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`) - [ ] Enable on GitLab.com for individual groups/projects listed above and verify behaviour (`/chatops run feature set --project=gitlab-org/gitlab feature_name true`)
- [ ] Coordinate a time to enable the flag with `#production` and `#g_delivery` on slack. - [ ] Coordinate a time to enable the flag with the SRE oncall and release managers
- In `#production` by pinging `@sre-oncall`
- In `#g_delivery` by pinging `@release-managers`
- [ ] 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`) - [ ] 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 - [ ] 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
@ -42,4 +44,12 @@ If applicable, any groups/projects that are happy to have this feature turned on
- [ ] Remove feature flag and add changelog entry - [ ] Remove feature flag and add changelog entry
- [ ] 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
## Rollback Steps
- [ ] This feature can be disabled by running the following Chatops command:
```
/chatops run feature set --project=gitlab-org/gitlab feature_name false
```
/label ~"feature flag" /label ~"feature flag"

View file

@ -28,6 +28,7 @@ Personas are described at https://about.gitlab.com/handbook/marketing/product-ma
* [Allison (Application Ops)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#allison-application-ops) * [Allison (Application Ops)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#allison-application-ops)
* [Priyanka (Platform Engineer)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#priyanka-platform-engineer) * [Priyanka (Platform Engineer)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#priyanka-platform-engineer)
* [Dana (Data Analyst)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#dana-data-analyst) * [Dana (Data Analyst)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#dana-data-analyst)
* [Eddie (Content Editor)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#eddie-content-editor)
--> -->
### User experience goal ### User experience goal
@ -95,7 +96,8 @@ In which enterprise tier should this feature go? See https://about.gitlab.com/ha
### Links / references ### Links / references
<!-- Label reminders - you should have one of each of the following labels if you can figure out the correct ones --> <!-- Label reminders - you should have one of each of the following labels.
Read the descriptions on https://gitlab.com/gitlab-org/gitlab/-/labels to find the correct ones -->
/label ~devops:: ~group: ~Category: /label ~devops:: ~group: ~Category:
/label ~feature /label ~feature

View file

@ -0,0 +1,99 @@
<!-- This issue template can be used a great starting point for feature requests. The last section "Release notes" can be used as a summary of the feature and is also required if you want to have your release post blog MR auto generated using the release post item generator: https://about.gitlab.com/handbook/marketing/blog/release-posts/#release-post-item-generator. The remaining sections are the backbone for every feature in GitLab. -->
### Release notes
<!-- What is the problem and solution you're proposing? This content sets the overall vision for the feature and serves as the release notes that will populate in various places, including the [release post blog](https://about.gitlab.com/releases/categories/releases/) and [Gitlab project releases](https://gitlab.com/gitlab-org/gitlab/-/releases). " -->
### Problem to solve
<!-- What is the user problem you are trying to solve with this issue? -->
### Proposal
<!-- Use this section to explain the feature and how it will work. It can be helpful to add technical details, design proposals, and links to related epics or issues. -->
/label ~"feature" ~"group::" ~"section::" ~"Category::" ~"GitLab Core"/~"GitLab Starter"/~"GitLab Premium"/~"GitLab Ultimate"
<!-- Read the labels descriptions on https://gitlab.com/gitlab-org/gitlab/-/labels to find the appropriate labels. Consider adding related issues and epics to this issue. You can also reference the Feature Proposal Template (https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Feature%20proposal.md) for additional details to consider adding to this issue. Additionally, as a data oriented organization, when your feature exits planning breakdown, consider adding the `What does success look like, and how can we measure that?` section.
Other sections to consider adding:
### Intended users
Who will use this feature? If known, include any of the following: types of users (e.g. Developer), personas, or specific company roles (e.g. Release Manager). It's okay to write "Unknown" and fill this field in later.
Personas are described at https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/
* [Cameron (Compliance Manager)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#cameron-compliance-manager)
* [Parker (Product Manager)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#parker-product-manager)
* [Delaney (Development Team Lead)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#delaney-development-team-lead)
* [Presley (Product Designer)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#presley-product-designer)
* [Sasha (Software Developer)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#sasha-software-developer)
* [Devon (DevOps Engineer)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#devon-devops-engineer)
* [Sidney (Systems Administrator)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#sidney-systems-administrator)
* [Sam (Security Analyst)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#sam-security-analyst)
* [Rachel (Release Manager)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#rachel-release-manager)
* [Alex (Security Operations Engineer)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#alex-security-operations-engineer)
* [Simone (Software Engineer in Test)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#simone-software-engineer-in-test)
* [Allison (Application Ops)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#allison-application-ops)
* [Priyanka (Platform Engineer)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#priyanka-platform-engineer)
* [Dana (Data Analyst)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#dana-data-analyst)
### User experience goal
What is the single user experience workflow this problem addresses?
For example, "The user should be able to use the UI/API/.gitlab-ci.yml with GitLab to <perform a specific task>"
https://about.gitlab.com/handbook/engineering/ux/ux-research-training/user-story-mapping/
### Further details
Include use cases, benefits, goals, or any other details that will help us understand the problem better.
### Permissions and Security
<!-- What permissions are required to perform the described actions? Are they consistent with the existing permissions as documented for users, groups, and projects as appropriate? Is the proposed behavior consistent between the UI, API, and other access methods (e.g. email replies)?
Consider adding checkboxes and expectations of users with certain levels of membership https://docs.gitlab.com/ee/user/permissions.html
* [ ] Add expected impact to members with no access (0)
* [ ] Add expected impact to Guest (10) members
* [ ] Add expected impact to Reporter (20) members
* [ ] Add expected impact to Developer (30) members
* [ ] Add expected impact to Maintainer (40) members
* [ ] Add expected impact to Owner (50) members
### Documentation
See the Feature Change Documentation Workflow https://docs.gitlab.com/ee/development/documentation/workflow.html#for-a-product-change
* Add all known Documentation Requirements in this section. See https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html#documentation-requirements
* If this feature requires changing permissions, update the permissions document. See https://docs.gitlab.com/ee/user/permissions.html
### Availability & Testing
This section needs to be retained and filled in during the workflow planning breakdown phase of this feature proposal, if not earlier.
What risks does this change pose to our availability? How might it affect the quality of the product? What additional test coverage or changes to tests will be needed? Will it require cross-browser testing?
Please list the test areas (unit, integration and end-to-end) that needs to be added or updated to ensure that this feature will work as intended. Please use the list below as guidance.
* Unit test changes
* Integration test changes
* End-to-end test change
See the test engineering planning process and reach out to your counterpart Software Engineer in Test for assistance: https://about.gitlab.com/handbook/engineering/quality/test-engineering/#test-planning
### What does success look like, and how can we measure that?
Define both the success metrics and acceptance criteria. Note that success metrics indicate the desired business outcomes, while acceptance criteria indicate when the solution is working correctly. If there is no way to measure success, link to an issue that will implement a way to measure this.
### What is the type of buyer?
What is the buyer persona for this feature? See https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/buyer-persona/
In which enterprise tier should this feature go? See https://about.gitlab.com/handbook/product/pricing/#four-tiers
### 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 -->

View file

@ -55,6 +55,14 @@ All reviewers can help ensure accuracy, clarity, completeness, and adherence to
For more information about labels, see [Technical Writing workflows - Labels](https://about.gitlab.com/handbook/engineering/ux/technical-writing/workflow/#labels). For more information about labels, see [Technical Writing workflows - Labels](https://about.gitlab.com/handbook/engineering/ux/technical-writing/workflow/#labels).
For suggestions that you are confident don't need to be reviewed, change them locally
and push a commit directly to save others from unneeded reviews. For example:
- Clear typos, like `this is a typpo`.
- Minor issues, like single quotes instead of double quotes, Oxford commas, and periods.
For more information, see our documentation on [Merging a merge request](https://docs.gitlab.com/ee/development/code_review.html#merging-a-merge-request).
**3. Maintainer** **3. Maintainer**
1. [ ] Review by assigned maintainer, who can always request/require the above reviews. Maintainer's review can occur before or after a technical writer review. 1. [ ] Review by assigned maintainer, who can always request/require the above reviews. Maintainer's review can occur before or after a technical writer review.

View file

@ -7,15 +7,14 @@ tasks:
- init: | - init: |
echo "$(date) Copying GDK" | tee -a /workspace/startup.log echo "$(date) Copying GDK" | tee -a /workspace/startup.log
rm -r /workspace/.rvm
mv $HOME/.rvm-workspace /workspace/.rvm mv $HOME/.rvm-workspace /workspace/.rvm
cp -r $HOME/gitlab-development-kit /workspace/ cp -r $HOME/gitlab-development-kit /workspace/
( (
set -e set -e
cd /workspace/gitlab-development-kit cd /workspace/gitlab-development-kit
[[ ! -L /workspace/gitlab-development-kit/gitlab ]] && ln -fs /workspace/gitlab /workspace/gitlab-development-kit/gitlab [[ ! -L /workspace/gitlab-development-kit/gitlab ]] && ln -fs /workspace/gitlab /workspace/gitlab-development-kit/gitlab
# make webpack static, prevents that GitLab tries to connect to localhost webpack from browser outside the workspace mv /workspace/gitlab-development-kit/secrets.yml /workspace/gitlab-development-kit/gitlab/config
echo "webpack:" >> gdk.yml
echo " static: true" >> gdk.yml
# reconfigure GDK # reconfigure GDK
echo "$(date) Reconfiguring GDK" | tee -a /workspace/startup.log echo "$(date) Reconfiguring GDK" | tee -a /workspace/startup.log
gdk reconfigure gdk reconfigure
@ -41,20 +40,16 @@ tasks:
fi fi
# start GDK # start GDK
echo "$(date) Starting GDK" | tee -a /workspace/startup.log echo "$(date) Starting GDK" | tee -a /workspace/startup.log
export DEV_SERVER_PUBLIC_ADDR=$(gp url 3808)
export RAILS_HOSTS=$(gp url 3000 | sed -e 's+^http[s]*://++') export RAILS_HOSTS=$(gp url 3000 | sed -e 's+^http[s]*://++')
gdk start gdk start
# Run DB migrations # Run DB migrations
if [ "$GITLAB_RUN_DB_MIGRATIONS" == true ]; then if [ "$GITLAB_RUN_DB_MIGRATIONS" == true ]; then
make gitlab-db-migrate make gitlab-db-migrate
fi fi
# Fix DB key cd ../gitlab
if [ "$GITLAB_FIX_DB_KEY" = true ]; then git checkout db/structure.sql
echo "$(date) Fixing DB key" | tee -a /workspace/startup.log cd ../gitlab-development-kit
cd gitlab
# see https://gitlab.com/gitlab-org/gitlab-foss/-/issues/56403#note_132515069
printf 'ApplicationSetting.last.update_column(:runners_registration_token_encrypted, nil)\nexit\n' | bundle exec rails c
cd -
fi
# Waiting for GitLab ... # Waiting for GitLab ...
gp await-port 3000 gp await-port 3000
printf "Waiting for GitLab at $(gp url 3000) ..." printf "Waiting for GitLab at $(gp url 3000) ..."

View file

@ -119,7 +119,6 @@ linters:
- 'app/views/invites/show.html.haml' - 'app/views/invites/show.html.haml'
- 'app/views/jira_connect/subscriptions/index.html.haml' - 'app/views/jira_connect/subscriptions/index.html.haml'
- 'app/views/layouts/_mailer.html.haml' - 'app/views/layouts/_mailer.html.haml'
- 'app/views/layouts/experiment_mailer.html.haml'
- 'app/views/layouts/header/_default.html.haml' - 'app/views/layouts/header/_default.html.haml'
- 'app/views/layouts/header/_new_dropdown.haml' - 'app/views/layouts/header/_new_dropdown.haml'
- 'app/views/layouts/jira_connect.html.haml' - 'app/views/layouts/jira_connect.html.haml'
@ -330,7 +329,6 @@ linters:
- 'ee/app/views/errors/kerberos_denied.html.haml' - 'ee/app/views/errors/kerberos_denied.html.haml'
- 'ee/app/views/groups/ee/_settings_nav.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/group_members/_ldap_sync.html.haml'
- 'ee/app/views/groups/group_members/_sync_button.html.haml'
- 'ee/app/views/groups/hooks/edit.html.haml' - 'ee/app/views/groups/hooks/edit.html.haml'
- 'ee/app/views/groups/ldap_group_links/index.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/nav/ee/admin/_new_monitoring_sidebar.html.haml'
@ -363,7 +361,6 @@ linters:
- 'ee/app/views/projects/services/gitlab_slack_application/_help.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/services/gitlab_slack_application/_slack_integration_form.html.haml'
- 'ee/app/views/projects/settings/slacks/edit.html.haml' - 'ee/app/views/projects/settings/slacks/edit.html.haml'
- 'ee/app/views/shared/_mirror_update_button.html.haml'
- 'ee/app/views/shared/epic/_search_bar.html.haml' - 'ee/app/views/shared/epic/_search_bar.html.haml'
- 'ee/app/views/shared/issuable/_approvals.html.haml' - 'ee/app/views/shared/issuable/_approvals.html.haml'
- 'ee/app/views/shared/issuable/_board_create_list_dropdown.html.haml' - 'ee/app/views/shared/issuable/_board_create_list_dropdown.html.haml'

2
.nvmrc
View file

@ -1 +1 @@
12.10.0 12.18.4

View file

@ -7,16 +7,18 @@ require:
- rubocop-rspec - rubocop-rspec
inherit_from: inherit_from:
- .rubocop_manual_todo.yml
- .rubocop_todo.yml - .rubocop_todo.yml
- ./rubocop/rubocop-migrations.yml - ./rubocop/rubocop-migrations.yml
- ./rubocop/rubocop-usage-data.yml - ./rubocop/rubocop-usage-data.yml
- ./rubocop/rubocop-code_reuse.yml
inherit_mode: inherit_mode:
merge: merge:
- Include - Include
AllCops: AllCops:
TargetRubyVersion: 2.6 TargetRubyVersion: 2.7
TargetRailsVersion: 6.0 TargetRailsVersion: 6.0
Exclude: Exclude:
- 'vendor/**/*' - 'vendor/**/*'
@ -353,6 +355,12 @@ Cop/SidekiqOptionsQueue:
- 'spec/**/*.rb' - 'spec/**/*.rb'
- 'ee/spec/**/*.rb' - 'ee/spec/**/*.rb'
Graphql/ResolverType:
Enabled: true
Include:
- 'app/graphql/resolvers/**/*'
- 'ee/app/graphql/resolvers/**/*'
Graphql/AuthorizeTypes: Graphql/AuthorizeTypes:
Enabled: true Enabled: true
Include: Include:

759
.rubocop_manual_todo.yml Normal file
View file

@ -0,0 +1,759 @@
FactoryBot/InlineAssociation:
Exclude:
- 'ee/spec/factories/analytics/cycle_analytics/group_stages.rb'
- 'ee/spec/factories/geo/event_log.rb'
- 'ee/spec/factories/groups.rb'
- 'ee/spec/factories/merge_request_blocks.rb'
- 'ee/spec/factories/vulnerabilities/feedback.rb'
- 'spec/factories/atlassian_identities.rb'
- 'spec/factories/events.rb'
- 'spec/factories/git_wiki_commit_details.rb'
- 'spec/factories/gitaly/commit.rb'
- 'spec/factories/go_module_commits.rb'
- 'spec/factories/go_module_versions.rb'
- 'spec/factories/go_modules.rb'
- 'spec/factories/group_group_links.rb'
- 'spec/factories/import_export_uploads.rb'
- 'spec/factories/merge_requests.rb'
- 'spec/factories/notes.rb'
- 'spec/factories/sent_notifications.rb'
- 'spec/factories/uploads.rb'
- 'spec/factories/wiki_pages.rb'
Graphql/IDType:
Exclude:
- 'ee/app/graphql/ee/mutations/issues/update.rb'
- 'ee/app/graphql/mutations/iterations/update.rb'
- 'ee/app/graphql/resolvers/iterations_resolver.rb'
- 'app/graphql/mutations/boards/issues/issue_move_list.rb'
- 'app/graphql/mutations/issues/update.rb'
- 'app/graphql/mutations/metrics/dashboard/annotations/delete.rb'
- 'app/graphql/resolvers/design_management/design_at_version_resolver.rb'
- 'app/graphql/resolvers/design_management/design_resolver.rb'
- 'app/graphql/resolvers/design_management/designs_resolver.rb'
- 'app/graphql/resolvers/design_management/version/design_at_version_resolver.rb'
- 'app/graphql/resolvers/design_management/version_in_collection_resolver.rb'
- 'app/graphql/resolvers/design_management/version_resolver.rb'
- 'app/graphql/resolvers/design_management/versions_resolver.rb'
- 'app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb'
- 'app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb'
- 'app/graphql/resolvers/user_merge_requests_resolver.rb'
Graphql/ResolverType:
Exclude:
- 'app/graphql/resolvers/base_resolver.rb'
- 'app/graphql/resolvers/ci/jobs_resolver.rb'
- 'app/graphql/resolvers/ci/pipeline_stages_resolver.rb'
- 'app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb'
- 'app/graphql/resolvers/merge_requests_resolver.rb'
- 'app/graphql/resolvers/users/group_count_resolver.rb'
- 'ee/app/graphql/resolvers/geo/merge_request_diff_registries_resolver.rb'
- 'ee/app/graphql/resolvers/geo/package_file_registries_resolver.rb'
- 'ee/app/graphql/resolvers/geo/terraform_state_version_registries_resolver.rb'
- 'ee/app/graphql/resolvers/vulnerabilities_base_resolver.rb'
Rails/SaveBang:
Exclude:
- 'ee/spec/controllers/projects/merge_requests_controller_spec.rb'
- 'ee/spec/controllers/subscriptions_controller_spec.rb'
- 'ee/spec/frontend/fixtures/analytics.rb'
- 'ee/spec/graphql/resolvers/vulnerabilities_resolver_spec.rb'
- 'ee/spec/initializers/fog_google_https_private_urls_spec.rb'
- 'ee/spec/lib/analytics/merge_request_metrics_calculator_spec.rb'
- 'ee/spec/lib/ee/gitlab/auth/ldap/sync/group_spec.rb'
- 'ee/spec/lib/ee/gitlab/checks/push_rules/commit_check_spec.rb'
- 'ee/spec/lib/ee/gitlab/ci/pipeline/quota/activity_spec.rb'
- 'ee/spec/lib/gitlab/auth/ldap/access_spec.rb'
- 'ee/spec/lib/gitlab/auth/o_auth/user_spec.rb'
- 'ee/spec/lib/gitlab/auth/saml/user_spec.rb'
- 'ee/spec/lib/gitlab/background_migration/fix_orphan_promoted_issues_spec.rb'
- 'ee/spec/lib/gitlab/elastic/search_results_spec.rb'
- 'ee/spec/lib/gitlab/email/handler/ee/service_desk_handler_spec.rb'
- 'ee/spec/lib/gitlab/geo_spec.rb'
- 'ee/spec/lib/gitlab/git_access_spec.rb'
- 'ee/spec/lib/gitlab/import_export/group/relation_factory_spec.rb'
- 'ee/spec/lib/gitlab/mirror_spec.rb'
- 'ee/spec/mailers/notify_spec.rb'
- 'ee/spec/migrations/fix_any_approver_rule_for_projects_spec.rb'
- 'ee/spec/migrations/geo/migrate_ci_job_artifacts_to_separate_registry_spec.rb'
- 'ee/spec/migrations/geo/migrate_lfs_objects_to_separate_registry_spec.rb'
- 'ee/spec/migrations/schedule_merge_request_any_approval_rule_migration_spec.rb'
- 'ee/spec/migrations/schedule_project_any_approval_rule_migration_spec.rb'
- 'ee/spec/models/application_setting_spec.rb'
- 'ee/spec/models/approval_merge_request_rule_spec.rb'
- 'ee/spec/models/approval_project_rule_spec.rb'
- 'ee/spec/models/burndown_spec.rb'
- 'ee/spec/models/ci/pipeline_spec.rb'
- 'ee/spec/models/ci/subscriptions/project_spec.rb'
- 'ee/spec/models/ee/appearance_spec.rb'
- 'ee/spec/models/ee/ci/job_artifact_spec.rb'
- 'ee/spec/models/ee/protected_branch_spec.rb'
- 'ee/spec/models/ee/protected_ref_access_spec.rb'
- 'ee/spec/models/ee/protected_ref_spec.rb'
- 'ee/spec/models/elasticsearch_indexed_namespace_spec.rb'
- 'ee/spec/models/environment_spec.rb'
- 'ee/spec/models/epic_spec.rb'
- 'ee/spec/models/geo/project_registry_spec.rb'
- 'ee/spec/models/geo_node_spec.rb'
- 'ee/spec/models/geo_node_status_spec.rb'
- 'ee/spec/models/gitlab_subscription_spec.rb'
- 'ee/spec/models/group_spec.rb'
- 'ee/spec/models/issue_spec.rb'
- 'ee/spec/models/label_note_spec.rb'
- 'ee/spec/models/lfs_object_spec.rb'
- 'ee/spec/models/license_spec.rb'
- 'ee/spec/models/merge_request_spec.rb'
- 'ee/spec/models/merge_train_spec.rb'
- 'spec/models/packages/package_spec.rb'
- 'ee/spec/models/project_ci_cd_setting_spec.rb'
- 'ee/spec/models/project_spec.rb'
- 'ee/spec/models/protected_environment_spec.rb'
- 'ee/spec/models/repository_spec.rb'
- 'ee/spec/models/scim_identity_spec.rb'
- 'ee/spec/models/scim_oauth_access_token_spec.rb'
- 'ee/spec/models/upload_spec.rb'
- 'ee/spec/models/user_preference_spec.rb'
- 'ee/spec/models/user_spec.rb'
- 'ee/spec/models/visible_approvable_spec.rb'
- 'ee/spec/models/vulnerabilities/feedback_spec.rb'
- 'ee/spec/models/vulnerabilities/issue_link_spec.rb'
- 'ee/spec/presenters/audit_event_presenter_spec.rb'
- 'ee/spec/presenters/epic_presenter_spec.rb'
- 'ee/spec/requests/api/boards_spec.rb'
- 'ee/spec/requests/api/epic_issues_spec.rb'
- 'ee/spec/requests/api/epic_links_spec.rb'
- 'ee/spec/requests/api/epics_spec.rb'
- 'ee/spec/requests/api/geo_nodes_spec.rb'
- 'ee/spec/requests/api/geo_spec.rb'
- 'ee/spec/requests/api/graphql/group/epics_spec.rb'
- 'ee/spec/requests/api/graphql/mutations/epic_tree/reorder_spec.rb'
- 'ee/spec/requests/api/groups_spec.rb'
- 'ee/spec/requests/api/issues_spec.rb'
- 'ee/spec/requests/api/ldap_group_links_spec.rb'
- 'ee/spec/requests/api/merge_request_approval_rules_spec.rb'
- 'ee/spec/requests/api/merge_request_approvals_spec.rb'
- 'ee/spec/requests/api/merge_requests_spec.rb'
- 'ee/spec/requests/api/project_approvals_spec.rb'
- 'ee/spec/requests/api/projects_spec.rb'
- 'ee/spec/requests/api/protected_branches_spec.rb'
- 'ee/spec/requests/api/scim_spec.rb'
- 'ee/spec/requests/api/todos_spec.rb'
- 'ee/spec/requests/lfs_http_spec.rb'
- 'ee/spec/services/approval_rules/finalize_service_spec.rb'
- 'ee/spec/services/approval_rules/update_service_spec.rb'
- 'ee/spec/services/ee/boards/issues/create_service_spec.rb'
- 'ee/spec/services/ee/boards/issues/list_service_spec.rb'
- 'ee/spec/services/ee/boards/lists/list_service_spec.rb'
- 'ee/spec/services/ee/issuable/clone/attributes_rewriter_spec.rb'
- 'ee/spec/services/ee/issuable/common_system_notes_service_spec.rb'
- 'ee/spec/services/ee/issues/update_service_spec.rb'
- 'ee/spec/services/ee/merge_requests/refresh_service_spec.rb'
- 'ee/spec/services/ee/merge_requests/update_service_spec.rb'
- 'ee/spec/services/ee/notes/quick_actions_service_spec.rb'
- 'ee/spec/services/ee/notification_service_spec.rb'
- 'ee/spec/services/epic_links/create_service_spec.rb'
- 'ee/spec/services/epics/close_service_spec.rb'
- 'ee/spec/services/epics/issue_promote_service_spec.rb'
- 'ee/spec/services/epics/reopen_service_spec.rb'
- 'ee/spec/services/epics/tree_reorder_service_spec.rb'
- 'ee/spec/services/epics/update_dates_service_spec.rb'
- 'ee/spec/services/epics/update_service_spec.rb'
- 'ee/spec/services/geo/blob_verification_secondary_service_spec.rb'
- 'ee/spec/services/geo/files_expire_service_spec.rb'
- 'ee/spec/services/geo/metrics_update_service_spec.rb'
- 'ee/spec/services/geo/registry_consistency_service_spec.rb'
- 'ee/spec/services/geo/repository_verification_secondary_service_spec.rb'
- 'ee/spec/services/groups/autocomplete_service_spec.rb'
- 'ee/spec/services/ldap_group_reset_service_spec.rb'
- 'ee/spec/services/lfs/unlock_file_service_spec.rb'
- 'ee/spec/services/merge_trains/refresh_merge_request_service_spec.rb'
- 'ee/spec/services/quick_actions/interpret_service_spec.rb'
- 'ee/spec/services/slash_commands/global_slack_handler_spec.rb'
- 'ee/spec/services/start_pull_mirroring_service_spec.rb'
- 'ee/spec/services/status_page/trigger_publish_service_spec.rb'
- 'ee/spec/services/todo_service_spec.rb'
- 'ee/spec/services/update_build_minutes_service_spec.rb'
- 'ee/spec/services/vulnerability_feedback/create_service_spec.rb'
- 'ee/spec/support/protected_tags/access_control_shared_examples.rb'
- 'ee/spec/support/shared_examples/features/protected_branches_access_control_shared_examples.rb'
- 'ee/spec/support/shared_examples/finders/geo/framework_registry_finder_shared_examples.rb'
- 'ee/spec/support/shared_examples/graphql/geo/geo_registries_resolver_shared_examples.rb'
- 'ee/spec/support/shared_examples/lib/analytics/common_merge_request_metrics_refresh_shared_examples.rb'
- 'ee/spec/support/shared_examples/policies/protected_environments_shared_examples.rb'
- 'ee/spec/workers/adjourned_project_deletion_worker_spec.rb'
- 'ee/spec/workers/clear_shared_runners_minutes_worker_spec.rb'
- 'ee/spec/workers/create_github_webhook_worker_spec.rb'
- 'ee/spec/workers/elastic_namespace_rollout_worker_spec.rb'
- 'ee/spec/workers/geo/container_repository_sync_dispatch_worker_spec.rb'
- 'ee/spec/workers/geo/file_download_dispatch_worker_spec.rb'
- 'ee/spec/workers/geo/prune_event_log_worker_spec.rb'
- 'ee/spec/workers/geo/registry_sync_worker_spec.rb'
- 'ee/spec/workers/geo/repository_shard_sync_worker_spec.rb'
- 'ee/spec/workers/repository_import_worker_spec.rb'
- 'ee/spec/workers/update_all_mirrors_worker_spec.rb'
- 'qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb'
- 'qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb'
- 'qa/qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb'
- 'qa/qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb'
- 'spec/controllers/abuse_reports_controller_spec.rb'
- 'spec/controllers/admin/impersonations_controller_spec.rb'
- 'spec/controllers/admin/runners_controller_spec.rb'
- 'spec/controllers/admin/services_controller_spec.rb'
- 'spec/controllers/boards/issues_controller_spec.rb'
- 'spec/controllers/groups/milestones_controller_spec.rb'
- 'spec/controllers/groups/runners_controller_spec.rb'
- 'spec/controllers/groups/uploads_controller_spec.rb'
- 'spec/controllers/groups_controller_spec.rb'
- 'spec/controllers/oauth/authorizations_controller_spec.rb'
- 'spec/controllers/omniauth_callbacks_controller_spec.rb'
- 'spec/controllers/profiles/emails_controller_spec.rb'
- 'spec/controllers/profiles/notifications_controller_spec.rb'
- 'spec/controllers/projects/artifacts_controller_spec.rb'
- 'spec/controllers/projects/cycle_analytics/events_controller_spec.rb'
- 'spec/controllers/projects/cycle_analytics_controller_spec.rb'
- 'spec/controllers/projects/discussions_controller_spec.rb'
- 'spec/controllers/projects/forks_controller_spec.rb'
- 'spec/controllers/projects/group_links_controller_spec.rb'
- 'spec/controllers/projects/imports_controller_spec.rb'
- 'spec/controllers/projects/issues_controller_spec.rb'
- 'spec/controllers/projects/labels_controller_spec.rb'
- 'spec/controllers/projects/milestones_controller_spec.rb'
- 'spec/controllers/projects/notes_controller_spec.rb'
- 'spec/controllers/projects/pipelines_controller_spec.rb'
- 'spec/controllers/projects/releases/evidences_controller_spec.rb'
- 'spec/controllers/projects/runners_controller_spec.rb'
- 'spec/controllers/projects/starrers_controller_spec.rb'
- 'spec/controllers/projects/uploads_controller_spec.rb'
- 'spec/controllers/projects_controller_spec.rb'
- 'spec/controllers/sent_notifications_controller_spec.rb'
- 'spec/controllers/sessions_controller_spec.rb'
- 'spec/controllers/users_controller_spec.rb'
- 'spec/factories_spec.rb'
- 'spec/features/admin/admin_appearance_spec.rb'
- 'spec/features/admin/admin_labels_spec.rb'
- 'spec/features/admin/admin_mode/login_spec.rb'
- 'spec/features/admin/admin_runners_spec.rb'
- 'spec/features/admin/admin_sees_project_statistics_spec.rb'
- 'spec/features/admin/admin_sees_projects_statistics_spec.rb'
- 'spec/features/admin/admin_users_impersonation_tokens_spec.rb'
- 'spec/features/admin/admin_users_spec.rb'
- 'spec/features/boards/sidebar_spec.rb'
- 'spec/features/calendar_spec.rb'
- 'spec/features/commits_spec.rb'
- 'spec/features/dashboard/datetime_on_tooltips_spec.rb'
- 'spec/features/dashboard/issuables_counter_spec.rb'
- 'spec/features/dashboard/project_member_activity_index_spec.rb'
- 'spec/features/dashboard/projects_spec.rb'
- 'spec/features/error_tracking/user_sees_error_index_spec.rb'
- 'spec/features/groups/members/request_access_spec.rb'
- 'spec/features/issuables/close_reopen_report_toggle_spec.rb'
- 'spec/features/issues/bulk_assignment_labels_spec.rb'
- 'spec/features/issues/gfm_autocomplete_spec.rb'
- 'spec/features/issues/issue_sidebar_spec.rb'
- 'spec/features/issues/note_polling_spec.rb'
- 'spec/features/issues/user_creates_branch_and_merge_request_spec.rb'
- 'spec/features/issues/user_creates_confidential_merge_request_spec.rb'
- 'spec/features/issues/user_edits_issue_spec.rb'
- 'spec/features/issues/user_filters_issues_spec.rb'
- 'spec/features/issues/user_sees_live_update_spec.rb'
- 'spec/features/issues/user_sorts_issues_spec.rb'
- 'spec/features/profiles/emails_spec.rb'
- 'spec/features/profiles/password_spec.rb'
- 'spec/features/profiles/personal_access_tokens_spec.rb'
- 'spec/features/projects/features_visibility_spec.rb'
- 'spec/features/projects/fork_spec.rb'
- 'spec/features/projects/jobs/permissions_spec.rb'
- 'spec/features/projects/jobs_spec.rb'
- 'spec/features/projects/members/user_requests_access_spec.rb'
- 'spec/features/projects/pages_lets_encrypt_spec.rb'
- 'spec/features/projects/pages_spec.rb'
- 'spec/features/projects/pipelines/pipeline_spec.rb'
- 'spec/features/projects/pipelines/pipelines_spec.rb'
- 'spec/features/projects/remote_mirror_spec.rb'
- 'spec/features/projects/services/user_activates_slack_notifications_spec.rb'
- 'spec/features/projects/settings/access_tokens_spec.rb'
- 'spec/features/projects/show/user_sees_deletion_failure_message_spec.rb'
- 'spec/features/projects/user_sees_sidebar_spec.rb'
- 'spec/features/projects/wiki/user_updates_wiki_page_spec.rb'
- 'spec/features/projects/wiki/user_views_wiki_page_spec.rb'
- 'spec/features/projects/wiki/users_views_asciidoc_page_with_includes_spec.rb'
- 'spec/features/runners_spec.rb'
- 'spec/features/security/project/internal_access_spec.rb'
- 'spec/features/security/project/private_access_spec.rb'
- 'spec/features/security/project/public_access_spec.rb'
- 'spec/features/users/login_spec.rb'
- 'spec/features/users/show_spec.rb'
- 'spec/frontend/fixtures/issues.rb'
- 'spec/frontend/fixtures/merge_requests.rb'
- 'spec/graphql/mutations/merge_requests/set_locked_spec.rb'
- 'spec/graphql/mutations/merge_requests/set_wip_spec.rb'
- 'spec/graphql/resolvers/boards_resolver_spec.rb'
- 'spec/initializers/active_record_locking_spec.rb'
- 'spec/initializers/fog_google_https_private_urls_spec.rb'
- 'spec/lib/after_commit_queue_spec.rb'
- 'spec/lib/backup/manager_spec.rb'
- 'spec/lib/banzai/reference_parser/external_issue_parser_spec.rb'
- 'spec/lib/banzai/reference_redactor_spec.rb'
- 'spec/lib/gitlab/alerting/alert_spec.rb'
- 'spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb'
- 'spec/lib/gitlab/auth/ldap/user_spec.rb'
- 'spec/lib/gitlab/auth/o_auth/user_spec.rb'
- 'spec/lib/gitlab/auth/saml/user_spec.rb'
- 'spec/lib/gitlab/auth_spec.rb'
- 'spec/lib/gitlab/authorized_keys_spec.rb'
- 'spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb'
- 'spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb'
- 'spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb'
- 'spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb'
- 'spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb'
- 'spec/lib/gitlab/background_migration/digest_column_spec.rb'
- 'spec/lib/gitlab/background_migration/encrypt_columns_spec.rb'
- 'spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb'
- 'spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb'
- 'spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb'
- 'spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb'
- 'spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb'
- 'spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb'
- 'spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb'
- 'spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb'
- 'spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb'
- 'spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb'
- 'spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb'
- 'spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb'
- 'spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb'
- 'spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb'
- 'spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb'
- 'spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb'
- 'spec/lib/gitlab/background_migration/reset_merge_status_spec.rb'
- 'spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb'
- 'spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb'
- 'spec/lib/gitlab/bitbucket_server_import/importer_spec.rb'
- 'spec/lib/gitlab/ci/ansi2json/style_spec.rb'
- 'spec/lib/gitlab/ci/status/build/common_spec.rb'
- 'spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb'
- 'spec/lib/gitlab/database/custom_structure_spec.rb'
- 'spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb'
- 'spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb'
- 'spec/lib/gitlab/email/handler/create_note_handler_spec.rb'
- 'spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb'
- 'spec/lib/gitlab/gfm/reference_rewriter_spec.rb'
- 'spec/lib/gitlab/git_access_spec.rb'
- 'spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb'
- 'spec/lib/gitlab/gitaly_client/repository_service_spec.rb'
- 'spec/lib/gitlab/import_export/avatar_saver_spec.rb'
- 'spec/lib/gitlab/import_export/base/relation_factory_spec.rb'
- 'spec/lib/gitlab/import_export/design_repo_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb'
- 'spec/lib/gitlab/import_export/fork_spec.rb'
- 'spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb'
- 'spec/lib/gitlab/import_export/group/relation_factory_spec.rb'
- 'spec/lib/gitlab/import_export/group/tree_saver_spec.rb'
- 'spec/lib/gitlab/import_export/importer_spec.rb'
- 'spec/lib/gitlab/import_export/lfs_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/lfs_saver_spec.rb'
- 'spec/lib/gitlab/import_export/members_mapper_spec.rb'
- 'spec/lib/gitlab/import_export/project/relation_factory_spec.rb'
- 'spec/lib/gitlab/import_export/project/tree_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/project/tree_saver_spec.rb'
- 'spec/lib/gitlab/import_export/repo_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/saver_spec.rb'
- 'spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb'
- 'spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb'
- 'spec/lib/gitlab/import_export/uploads_manager_spec.rb'
- 'spec/lib/gitlab/import_export/uploads_saver_spec.rb'
- 'spec/lib/gitlab/import_export/wiki_restorer_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/importer_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb'
- 'spec/lib/gitlab/lets_encrypt/client_spec.rb'
- 'spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb'
- 'spec/lib/gitlab/markdown_cache/redis/store_spec.rb'
- 'spec/lib/gitlab/middleware/go_spec.rb'
- 'spec/lib/gitlab/shard_health_cache_spec.rb'
- 'spec/lib/mattermost/command_spec.rb'
- 'spec/lib/mattermost/session_spec.rb'
- 'spec/lib/mattermost/team_spec.rb'
- 'spec/mailers/notify_spec.rb'
- 'spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb'
- 'spec/migrations/20200122123016_backfill_project_settings_spec.rb'
- 'spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb'
- 'spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb'
- 'spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb'
- 'spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb'
- 'spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb'
- 'spec/migrations/20200526115436_dedup_mr_metrics_spec.rb'
- 'spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb'
- 'spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb'
- 'spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb'
- 'spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb'
- 'spec/migrations/backfill_imported_snippet_repositories_spec.rb'
- 'spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb'
- 'spec/migrations/backfill_snippet_repositories_spec.rb'
- 'spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb'
- 'spec/migrations/enqueue_reset_merge_status_second_run_spec.rb'
- 'spec/migrations/enqueue_reset_merge_status_spec.rb'
- 'spec/migrations/fill_file_store_lfs_objects_spec.rb'
- 'spec/migrations/fill_store_uploads_spec.rb'
- 'spec/migrations/fix_null_type_labels_spec.rb'
- 'spec/migrations/fix_pool_repository_source_project_id_spec.rb'
- 'spec/migrations/fix_projects_without_project_feature_spec.rb'
- 'spec/migrations/fix_projects_without_prometheus_services_spec.rb'
- 'spec/migrations/fix_wrong_pages_access_level_spec.rb'
- 'spec/migrations/insert_project_hooks_plan_limits_spec.rb'
- 'spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb'
- 'spec/migrations/move_limits_from_plans_spec.rb'
- 'spec/migrations/populate_project_statistics_packages_size_spec.rb'
- 'spec/migrations/schedule_link_lfs_objects_projects_spec.rb'
- 'spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb'
- 'spec/migrations/seed_repository_storages_weighted_spec.rb'
- 'spec/models/appearance_spec.rb'
- 'spec/models/application_record_spec.rb'
- 'spec/models/application_setting_spec.rb'
- 'spec/models/clusters/applications/helm_spec.rb'
- 'spec/models/commit_spec.rb'
- 'spec/models/commit_status_spec.rb'
- 'spec/models/container_repository_spec.rb'
- 'spec/models/deploy_keys_project_spec.rb'
- 'spec/models/deploy_token_spec.rb'
- 'spec/models/deployment_spec.rb'
- 'spec/models/design_management/version_spec.rb'
- 'spec/models/diff_discussion_spec.rb'
- 'spec/models/diff_note_spec.rb'
- 'spec/models/email_spec.rb'
- 'spec/models/environment_spec.rb'
- 'spec/models/event_spec.rb'
- 'spec/models/fork_network_spec.rb'
- 'spec/models/generic_commit_status_spec.rb'
- 'spec/models/grafana_integration_spec.rb'
- 'spec/models/group_spec.rb'
- 'spec/models/hooks/system_hook_spec.rb'
- 'spec/models/hooks/web_hook_spec.rb'
- 'spec/models/identity_spec.rb'
- 'spec/models/issue/metrics_spec.rb'
- 'spec/models/issue_spec.rb'
- 'spec/models/jira_import_state_spec.rb'
- 'spec/models/key_spec.rb'
- 'spec/models/lfs_objects_project_spec.rb'
- 'spec/models/member_spec.rb'
- 'spec/models/members/group_member_spec.rb'
- 'spec/models/members/project_member_spec.rb'
- 'spec/models/merge_request_spec.rb'
- 'spec/models/milestone_spec.rb'
- 'spec/models/namespace_spec.rb'
- 'spec/models/note_spec.rb'
- 'spec/models/notification_setting_spec.rb'
- 'spec/models/operations/feature_flag_scope_spec.rb'
- 'spec/models/operations/feature_flags/strategy_spec.rb'
- 'spec/models/operations/feature_flags/user_list_spec.rb'
- 'spec/models/pages_domain_spec.rb'
- 'spec/models/project_auto_devops_spec.rb'
- 'spec/models/project_feature_spec.rb'
- 'spec/models/project_spec.rb'
- 'spec/models/project_team_spec.rb'
- 'spec/models/protectable_dropdown_spec.rb'
- 'spec/models/redirect_route_spec.rb'
- 'spec/models/release_spec.rb'
- 'spec/models/remote_mirror_spec.rb'
- 'spec/models/resource_milestone_event_spec.rb'
- 'spec/models/route_spec.rb'
- 'spec/models/sentry_issue_spec.rb'
- 'spec/models/service_spec.rb'
- 'spec/models/snippet_spec.rb'
- 'spec/models/upload_spec.rb'
- 'spec/models/user_preference_spec.rb'
- 'spec/models/user_spec.rb'
- 'spec/models/user_status_spec.rb'
- 'spec/models/wiki_page/meta_spec.rb'
- 'spec/models/wiki_page_spec.rb'
- 'spec/presenters/ci/build_runner_presenter_spec.rb'
- 'spec/presenters/ci/trigger_presenter_spec.rb'
- 'spec/presenters/packages/conan/package_presenter_spec.rb'
- 'spec/requests/api/ci/runner_spec.rb'
- 'spec/requests/api/commit_statuses_spec.rb'
- 'spec/requests/api/conan_packages_spec.rb'
- 'spec/requests/api/deployments_spec.rb'
- 'spec/requests/api/environments_spec.rb'
- 'spec/requests/api/go_proxy_spec.rb'
- 'spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb'
- 'spec/requests/api/graphql/user_query_spec.rb'
- 'spec/requests/api/graphql_spec.rb'
- 'spec/requests/api/group_import_spec.rb'
- 'spec/requests/api/group_milestones_spec.rb'
- 'spec/requests/api/internal/base_spec.rb'
- 'spec/requests/api/issues/get_group_issues_spec.rb'
- 'spec/requests/api/issues/post_projects_issues_spec.rb'
- 'spec/requests/api/jobs_spec.rb'
- 'spec/requests/api/labels_spec.rb'
- 'spec/requests/api/project_import_spec.rb'
- 'spec/requests/projects/cycle_analytics_events_spec.rb'
Rails/TimeZone:
Enabled: true
Exclude:
- 'lib/gitlab/popen.rb'
- 'ee/lib/delay.rb'
- 'ee/lib/gitlab/elastic/helper.rb'
- 'ee/lib/gitlab/elastic/indexer.rb'
- 'ee/lib/gitlab/geo/base_request.rb'
- 'ee/lib/gitlab/geo/event_gap_tracking.rb'
- 'ee/lib/gitlab/geo/log_cursor/events/design_repository_updated_event.rb'
- 'ee/lib/gitlab/geo/log_cursor/events/repository_updated_event.rb'
- 'ee/lib/gitlab/geo/log_cursor/logger.rb'
- 'ee/lib/gitlab/geo/oauth/login_state.rb'
- 'ee/lib/gitlab/prometheus/queries/cluster_query.rb'
- 'ee/lib/gitlab/prometheus/queries/packet_flow_query.rb'
- 'ee/spec/lib/ee/gitlab/checks/push_rules/commit_check_spec.rb'
- 'ee/spec/lib/ee/gitlab/ci/pipeline/quota/job_activity_spec.rb'
- 'ee/spec/lib/gitlab/analytics/cycle_analytics/data_collector_spec.rb'
- 'ee/spec/lib/gitlab/analytics/cycle_analytics/summary/group/stage_summary_spec.rb'
- 'ee/spec/lib/gitlab/analytics/cycle_analytics/summary/group/stage_time_summary_spec.rb'
- 'ee/spec/lib/gitlab/auth/ldap/access_spec.rb'
- 'ee/spec/lib/gitlab/auth/smartcard/san_extension_spec.rb'
- 'ee/spec/lib/gitlab/auth/smartcard/session_spec.rb'
- 'ee/spec/lib/gitlab/background_migration/fix_orphan_promoted_issues_spec.rb'
- 'ee/spec/lib/gitlab/ci/pipeline/chain/limit/job_activity_spec.rb'
- 'ee/spec/lib/gitlab/elastic/client_spec.rb'
- 'ee/spec/lib/gitlab/geo/base_request_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/cache_invalidation_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/container_repository_updated_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/design_repository_updated_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/hashed_storage_attachments_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/hashed_storage_migrated_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/job_artifact_deleted_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/lfs_object_deleted_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/repositories_changed_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/repository_created_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/repository_deleted_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/repository_renamed_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/repository_updated_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/reset_checksum_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/upload_deleted_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/logger_spec.rb'
- 'ee/spec/lib/gitlab/git_access_spec.rb'
- 'ee/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb'
- 'ee/spec/lib/gitlab/prometheus/queries/cluster_query_spec.rb'
- 'ee/spec/lib/gitlab/prometheus/queries/packet_flow_query_spec.rb'
- 'lib/api/helpers.rb'
- 'lib/api/sidekiq_metrics.rb'
- 'lib/backup/manager.rb'
- 'lib/bitbucket_server/representation/base.rb'
- 'lib/gitlab/auth/current_user_mode.rb'
- 'lib/gitlab/auth/ldap/access.rb'
- 'lib/gitlab/chaos.rb'
- 'lib/gitlab/checks/timed_logger.rb'
- 'lib/gitlab/ci/ansi2json/line.rb'
- 'lib/gitlab/ci/pipeline/chain/sequence.rb'
- 'lib/gitlab/ci/pipeline/duration.rb'
- 'lib/gitlab/cycle_analytics/summary/deployment_frequency.rb'
- 'lib/gitlab/database.rb'
- 'lib/gitlab/external_authorization/access.rb'
- 'lib/gitlab/external_authorization/cache.rb'
- 'lib/gitlab/gitaly_client.rb'
- 'lib/gitlab/gitaly_client/ref_service.rb'
- 'lib/gitlab/github_import/representation.rb'
- 'lib/gitlab/grape_logging/loggers/queue_duration_logger.rb'
- 'lib/gitlab/health_checks/base_abstract_check.rb'
- 'lib/gitlab/import_export.rb'
- 'lib/gitlab/instrumentation/elasticsearch_transport.rb'
- 'lib/gitlab/instrumentation/redis_interceptor.rb'
- 'lib/gitlab/instrumentation_helper.rb'
- 'lib/gitlab/kubernetes/helm/certificate.rb'
- 'lib/gitlab/lfs_token.rb'
- 'lib/gitlab/loop_helpers.rb'
- 'lib/gitlab/phabricator_import/representation/task.rb'
- 'lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb'
- 'lib/gitlab/prometheus/queries/matched_metric_query.rb'
- 'lib/gitlab/prometheus_client.rb'
- 'lib/gitlab/sherlock/transaction.rb'
- 'lib/gitlab/task_helpers.rb'
- 'lib/gitlab/x509/tag.rb'
- 'lib/grafana/time_window.rb'
- 'lib/json_web_token/token.rb'
- 'lib/object_storage/direct_upload.rb'
- 'lib/quality/seeders/issues.rb'
- 'lib/rspec_flaky/flaky_example.rb'
- 'lib/rspec_flaky/report.rb'
- 'lib/tasks/gitlab/assets.rake'
- 'lib/tasks/gitlab/backup.rake'
- 'lib/tasks/gitlab/cleanup.rake'
- 'lib/tasks/gitlab/list_repos.rake'
- 'spec/lib/api/helpers_spec.rb'
- 'spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb'
- 'spec/lib/gitlab/app_json_logger_spec.rb'
- 'spec/lib/gitlab/app_text_logger_spec.rb'
- 'spec/lib/gitlab/auth/current_user_mode_spec.rb'
- 'spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb'
- 'spec/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer_spec.rb'
- 'spec/lib/gitlab/bitbucket_import/importer_spec.rb'
- 'spec/lib/gitlab/bitbucket_server_import/importer_spec.rb'
- 'spec/lib/gitlab/checks/timed_logger_spec.rb'
- 'spec/lib/gitlab/ci/cron_parser_spec.rb'
- 'spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb'
- 'spec/lib/gitlab/cycle_analytics/usage_data_spec.rb'
- 'spec/lib/gitlab/data_builder/note_spec.rb'
- 'spec/lib/gitlab/database/background_migration_job_spec.rb'
- 'spec/lib/gitlab/database_spec.rb'
- 'spec/lib/gitlab/discussions_diff/file_collection_spec.rb'
- 'spec/lib/gitlab/external_authorization/access_spec.rb'
- 'spec/lib/gitlab/external_authorization/cache_spec.rb'
- 'spec/lib/gitlab/external_authorization/logger_spec.rb'
- 'spec/lib/gitlab/fogbugz_import/importer_spec.rb'
- 'spec/lib/gitlab/git/branch_spec.rb'
- 'spec/lib/gitlab/git/commit_spec.rb'
- 'spec/lib/gitlab/git/repository_spec.rb'
- 'spec/lib/gitlab/git_access_spec.rb'
- 'spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb'
- 'spec/lib/gitlab/github_import/importer/issue_importer_spec.rb'
- 'spec/lib/gitlab/github_import/importer/issues_importer_spec.rb'
- 'spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb'
- 'spec/lib/gitlab/github_import/importer/note_importer_spec.rb'
- 'spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb'
- 'spec/lib/gitlab/github_import/importer/releases_importer_spec.rb'
- 'spec/lib/gitlab/github_import/representation/diff_note_spec.rb'
- 'spec/lib/gitlab/github_import/representation/issue_spec.rb'
- 'spec/lib/gitlab/github_import/representation/note_spec.rb'
- 'spec/lib/gitlab/github_import/representation/pull_request_spec.rb'
- 'spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb'
- 'spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb'
- 'spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb'
- 'spec/lib/gitlab/graphql_logger_spec.rb'
- 'spec/lib/gitlab/graphs/commits_spec.rb'
- 'spec/lib/gitlab/import_export/project/relation_factory_spec.rb'
- 'spec/lib/gitlab/instrumentation_helper_spec.rb'
- 'spec/lib/gitlab/json_logger_spec.rb'
- 'spec/lib/gitlab/lfs_token_spec.rb'
- 'spec/lib/gitlab/log_timestamp_formatter_spec.rb'
- 'spec/lib/gitlab/middleware/rails_queue_duration_spec.rb'
- 'spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb'
- 'spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb'
- 'spec/lib/gitlab/phabricator_import/representation/task_spec.rb'
- 'spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb'
- 'spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb'
- 'spec/lib/gitlab/prometheus/queries/validate_query_spec.rb'
- 'spec/lib/gitlab/sherlock/transaction_spec.rb'
- 'spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb'
- 'spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb'
- 'spec/lib/gitlab/updated_notes_paginator_spec.rb'
- 'spec/lib/gitlab/utils/json_size_estimator_spec.rb'
- 'spec/lib/gitlab/x509/signature_spec.rb'
- 'spec/lib/grafana/time_window_spec.rb'
- 'spec/lib/json_web_token/hmac_token_spec.rb'
- 'spec/lib/rspec_flaky/flaky_example_spec.rb'
- 'spec/lib/rspec_flaky/listener_spec.rb'
- 'spec/lib/rspec_flaky/report_spec.rb'
RSpec/TimecopFreeze:
Exclude:
- 'ee/spec/controllers/admin/application_settings_controller_spec.rb'
- 'ee/spec/controllers/projects/security/network_policies_controller_spec.rb'
- 'ee/spec/features/admin/admin_reset_pipeline_minutes_spec.rb'
- 'ee/spec/features/boards/sidebar_spec.rb'
- 'ee/spec/features/groups/analytics/cycle_analytics/filters_and_data_spec.rb'
- 'ee/spec/features/groups/iteration_spec.rb'
- 'ee/spec/features/projects/mirror_spec.rb'
- 'ee/spec/features/projects/services/prometheus_custom_metrics_spec.rb'
- 'ee/spec/finders/productivity_analytics_finder_spec.rb'
- 'ee/spec/frontend/fixtures/analytics.rb'
- 'ee/spec/helpers/vulnerabilities_helper_spec.rb'
- 'ee/spec/lib/analytics/merge_request_metrics_refresh_spec.rb'
- 'ee/spec/lib/analytics/productivity_analytics_request_params_spec.rb'
- 'ee/spec/lib/ee/gitlab/background_migration/populate_vulnerability_historical_statistics_spec.rb'
- 'ee/spec/lib/gitlab/analytics/cycle_analytics/data_collector_spec.rb'
- 'ee/spec/lib/gitlab/analytics/cycle_analytics/group_stage_time_summary_spec.rb'
- 'ee/spec/lib/gitlab/analytics/cycle_analytics/summary/group/stage_time_summary_spec.rb'
- 'ee/spec/lib/gitlab/analytics/type_of_work/tasks_by_type_spec.rb'
- 'ee/spec/lib/gitlab/auth/group_saml/sso_enforcer_spec.rb'
- 'ee/spec/lib/gitlab/database/load_balancing/host_spec.rb'
- 'ee/spec/lib/gitlab/geo/base_request_spec.rb'
- 'ee/spec/lib/gitlab/geo/event_gap_tracking_spec.rb'
- 'ee/spec/lib/gitlab/geo/git_push_http_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/daemon_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/repository_updated_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/oauth/login_state_spec.rb'
- 'ee/spec/lib/gitlab/insights/reducers/count_per_period_reducer_spec.rb'
- 'ee/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb'
- 'ee/spec/lib/gitlab/prometheus/queries/cluster_query_spec.rb'
- 'ee/spec/migrations/populate_vulnerability_historical_statistics_for_year_spec.rb'
- 'ee/spec/migrations/remove_duplicated_cs_findings_spec.rb'
- 'ee/spec/migrations/remove_duplicated_cs_findings_without_vulnerability_id_spec.rb'
- 'ee/spec/migrations/schedule_fix_orphan_promoted_issues_spec.rb'
- 'ee/spec/migrations/schedule_merge_request_any_approval_rule_migration_spec.rb'
- 'ee/spec/migrations/schedule_populate_resolved_on_default_branch_column_spec.rb'
- 'ee/spec/migrations/schedule_populate_vulnerability_historical_statistics_spec.rb'
- 'ee/spec/migrations/schedule_project_any_approval_rule_migration_spec.rb'
- 'ee/spec/migrations/set_resolved_state_on_vulnerabilities_spec.rb'
- 'ee/spec/migrations/20190926180443_schedule_epic_issues_after_epics_move_spec.rb'
- 'ee/spec/models/analytics/cycle_analytics/group_level_spec.rb'
- 'ee/spec/models/burndown_spec.rb'
- 'ee/spec/models/ee/namespace_spec.rb'
- 'ee/spec/models/geo/project_registry_spec.rb'
- 'ee/spec/models/merge_train_spec.rb'
- 'ee/spec/models/productivity_analytics_spec.rb'
- 'ee/spec/models/project_spec.rb'
- 'ee/spec/models/vulnerabilities/export_spec.rb'
- 'ee/spec/requests/api/vulnerabilities_spec.rb'
- 'ee/spec/services/geo/file_download_service_spec.rb'
- 'ee/spec/services/vulnerabilities/confirm_service_spec.rb'
- 'ee/spec/services/vulnerabilities/dismiss_service_spec.rb'
- 'ee/spec/services/vulnerabilities/resolve_service_spec.rb'
- 'ee/spec/services/vulnerabilities/revert_to_detected_service_spec.rb'
- 'ee/spec/services/vulnerability_exports/export_service_spec.rb'
- 'ee/spec/support/shared_contexts/lib/gitlab/insights/reducers/reducers_shared_contexts.rb'
- 'qa/spec/support/repeater_spec.rb'
- 'spec/features/profiles/active_sessions_spec.rb'
- 'spec/features/projects/environments/environment_metrics_spec.rb'
- 'spec/features/users/active_sessions_spec.rb'
- 'spec/lib/atlassian/jira_connect/client_spec.rb'
- 'spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb'
- 'spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb'
- 'spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb'
- 'spec/lib/gitlab/anonymous_session_spec.rb'
- 'spec/lib/gitlab/auth/unique_ips_limiter_spec.rb'
- 'spec/lib/gitlab/checks/timed_logger_spec.rb'
- 'spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb'
- 'spec/lib/gitlab/cycle_analytics/usage_data_spec.rb'
- 'spec/lib/gitlab/instrumentation_helper_spec.rb'
- 'spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb'
- 'spec/lib/gitlab/puma_logging/json_formatter_spec.rb'
- 'spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb'
- 'spec/lib/json_web_token/hmac_token_spec.rb'
- 'spec/lib/rspec_flaky/flaky_example_spec.rb'
- 'spec/lib/rspec_flaky/listener_spec.rb'
- 'spec/models/active_session_spec.rb'
- 'spec/models/container_repository_spec.rb'
- 'spec/models/pages/lookup_path_spec.rb'
- 'spec/models/project_feature_usage_spec.rb'
- 'spec/requests/api/v3/github_spec.rb'
- 'spec/serializers/entity_date_helper_spec.rb'
- 'spec/support/cycle_analytics_helpers/test_generation.rb'
- 'spec/support/helpers/cycle_analytics_helpers.rb'
- 'spec/support/helpers/javascript_fixtures_helpers.rb'
- 'spec/support/shared_contexts/rack_attack_shared_context.rb'
- 'spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb'
- 'spec/workers/concerns/reenqueuer_spec.rb'
- 'spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb'
RSpec/TimecopTravel:
Exclude:
- 'ee/spec/lib/gitlab/geo/event_gap_tracking_spec.rb'
- 'ee/spec/lib/gitlab/geo/git_push_http_spec.rb'
- 'ee/spec/lib/gitlab/geo/jwt_request_decoder_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/daemon_spec.rb'
- 'ee/spec/models/broadcast_message_spec.rb'
- 'ee/spec/models/burndown_spec.rb'
- 'qa/spec/support/repeater_spec.rb'
- 'spec/features/users/terms_spec.rb'
- 'spec/lib/feature_spec.rb'
- 'spec/models/broadcast_message_spec.rb'
- 'spec/models/concerns/issuable_spec.rb'
- 'spec/requests/api/ci/runner/jobs_trace_spec.rb'
- 'spec/requests/api/issues/put_projects_issues_spec.rb'
- 'spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb'
- 'spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb'
- 'spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb'
- 'spec/workers/concerns/reenqueuer_spec.rb'
- 'spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb'

File diff suppressed because it is too large Load diff

View file

@ -1 +1 @@
2.6.6 2.7.2

View file

@ -8,6 +8,7 @@ scss_files:
exclude: exclude:
- 'app/assets/stylesheets/pages/emojis.scss' - 'app/assets/stylesheets/pages/emojis.scss'
- 'app/assets/stylesheets/startup/startup-*.scss' - 'app/assets/stylesheets/startup/startup-*.scss'
- 'app/assets/stylesheets/lazy_bundles/select2.scss'
linters: linters:
# Reports when you use improper spacing around ! (the "bang") in !default, # Reports when you use improper spacing around ! (the "bang") in !default,

View file

@ -2,14 +2,14 @@
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.5.7 (2021-01-13) ## 13.6.5 (2021-01-13)
### Security (1 change) ### Security (1 change)
- Deny implicit flow for confidential apps. - Deny implicit flow for confidential apps.
## 13.5.6 (2021-01-07) ## 13.6.4 (2021-01-07)
### Security (7 changes) ### Security (7 changes)
@ -17,12 +17,27 @@ entry.
- Deny implicit flow for confidential apps. - Deny implicit flow for confidential apps.
- Update NuGet regular expression to protect against ReDoS. - Update NuGet regular expression to protect against ReDoS.
- Fix regular expression backtracking issue in package name validation. - Fix regular expression backtracking issue in package name validation.
- Upgrade GitLab Pages to 1.28.2. - Upgrade GitLab Pages to 1.30.2.
- Update trusted OAuth applications to set them as confidential. - Update trusted OAuth applications to set them as confidential.
- Upgrade Workhorse to 8.51.2. - Upgrade Workhorse to 8.54.2.
## 13.5.5 (2020-12-07) ## 13.6.3 (2020-12-10)
### Fixed (5 changes)
- Fix error 500s creating projects concurrently. !48571
- Fix container_registry url for relative urls. !48661
- Resolve Members page 500 error after Invitation sent via API. !48937
- Add different string encoding method in rack middleware. !49044
- Fix MR rendering issue when user is tool admin and not project member. !49258
### Changed (1 change)
- Update Rake check and docs to require Ruby 2.7. !48552
## 13.6.2 (2020-12-07)
### Security (10 changes) ### Security (10 changes)
@ -38,6 +53,533 @@ entry.
- Do not show starred & contributed projects of users with private profile. - Do not show starred & contributed projects of users with private profile.
## 13.6.1 (2020-11-23)
### Fixed (5 changes)
- Fix project transfer corrupting shared runners state. !48032
- Fix project select split button bug. !48065
- Fix tags pages erroring for projects with private pipelines. !48184
- Ensure Alerts list loads when only HTTP integrations are enabled. !48247
- Does not track package events on a read-only instance. !48257
### Changed (1 change)
- Re-name Instance Statistics as Usage Trends. !48183
## 13.6.0 (2020-11-22)
### Removed (3 changes)
- Removed ACE editor from the codebase. !46420
- Remove storage limit column from application settings. !46676
- Remove the ability to resole individual notes. !46775
### Fixed (140 changes, 11 of them are from the community)
- Fix rendering of markdown headings and floated images. !25442 (Gwen_)
- Fix release assets link redirection. !35381
- Fix chatbot replies not including job log. !42010
- Show tar warning message when file/folder changed during backup instead of failing whole backup operation. !42197
- Remove default EKS Region dropdown in cluster create form. !43017
- Remove all records from `security_findings` table. !44312
- Add `position` column into security_findings table. !44815
- Render script newlines in CI Lint view. !45087 (Nejc Habjan)
- Fix a race condition checking whether a project is read-only. !45160
- Limit number of times a background migration is rescheduled. !45298
- Improve project labels page card layout consistency. !45311
- Do not convert unicode versions of trademark, copyright, and registered trademark to emoji. !45457
- Gracefully recover from deleted LFS file. !45459
- Fix Bad Escape in Issue Board Empty State. !45465 (Kev @KevSlashNull)
- Update cluster applications CI template to 0.34.1. !45487
- Fixed multi line comment options in parallel mode. !45557
- Removed not equal filter option for drafts on merge requests. !45649
- Fixed target branch not filtering. !45652
- Fix Merge Request "Edit in Web IDE" dropdown link on MR diffs page. !45653
- Handle malformed strings in URL. !45701
- Reset the pagination cursor when a search result filter changes. !45708
- Fix aria label on IDE tab close button. !45709
- Fix danger-secondary button in the Web IDE dark theme. !45714
- Removes the hamburger icon in the Changes tab in Web IDE. !45717
- Fix exception when saving Jira integration info for an instance. !45718
- Make sure the http_requests_total and http_request_duration_seconds metrics are not empty on application start. !45755
- Configure CSP for displaying Youtube videos in the Static Site Editor. !45767
- Render correct URLs for uploads in service desk issues when custom template is used. !45772
- Upgrade Workhorse to v8.52.0. !45778
- Fix project callbacks when propagating integrations. !45781
- Fix project import search box and make it case insensitive. !45783
- Remove the native styles for modal-dialog - Currently off center. !45789
- Fix when Feature Flags link is shown in search bar results. !45803
- Reset search results filters whenever a user changes scope. !45808
- Project Access Tokens - Delete project bot after token expires. !45828
- Paginate project_runners in ci_cd settings. !45830
- Fix bug with robots and .git suffix. !45866
- Block LFS requests on snippets. !45874
- Fix an N+1 issue in Packages::GroupPackagesFinder. !45875
- Fix sticky header issue status not syncing. !45895
- Download LFS files when importing from Bitbucket Server. !45908
- Fix viewing GitHub-imported diff notes in discussions. !45920
- Boards - Fix Milestone icon alignment in header. !45965
- Reduce whitespace on MR page header. !45966
- Fix CSS for To-Do List on mobile. !45969 (Takuya Noguchi)
- Fix wide content overflow on Notebook output. !45971
- Fix auto-deploy-image fetches deprecated stable repository and causes an error. !45984
- Fixed long paths truncating in merge request sidebar incorrectly. !45994
- Remove positive tabindexes. !46003
- Remove "Report abuse" button from a merged Merge Request. !46031 (Takuya Noguchi)
- Fix single file editor patch branch name. !46044
- Updated list view MR icon. !46059
- Tolerate UTF8 BOM character during frontmatter rendering. !46062
- Fix dropzone paperclip and loading icons. !46093
- Copyedit Project Issue Boards API docs. !46110 (Takuya Noguchi)
- Fix typos when deleting a project repository. !46204 (Edstub207)
- Enable rendering avatars with full url. !46206
- Fix bug accessing import route with no user. !46215
- Fix transaction usage in ContainerExpirationPolicyWorker. !46217
- Remove page_title from single project and group pages. !46223
- Skip GMA and SSO validation when creating project access tokens for project bots. !46257
- Make loading icon on feature flag edit page larger. !46268
- Allow semver versions in composer packages. !46301
- Don't return target-specific broadcasts without a current path supplied. !46322
- Fix tracking of frequently visited projects / groups. !46348
- Do not query snippet infromation on the new snippet's creation. !46355
- Populate missing `dismissed_at` and `dismissed_by_id` attributes of vulnerabilities. !46370
- Add CI Status CSS to the Environments Page. !46382
- Allow project storage to be updated when no repositories exist. !46385
- Add licensed check for wip limits. !46387
- Fix problems with Groups API search query parameter. !46394
- Fix QuickActions not working if written before a codeblock. !46401
- Resolve User stuck in 2FA setup page even if group disable 2FA enforce. !46432
- Job dropdown: Hide tooltip explicitly on click. !46465
- Fix loading current directory when changing branches. !46479
- Allow to apply group labels with service desk templates. !46492
- Fix CI artifacts not uploading with tracing enabled and without NGINX. !46513
- Fix logging handling for API integer params. !46551
- Bugfix email notification recipients for comments on Designs. !46642
- Fix linebreak issue in last commit anchor. !46643
- Upgrade fog-google to v1.11.0. !46648
- Fix 'File name too long' error happening during Project Export when exporting project uploads. !46674
- container registry: show delete selected button on medium viewports. !46699
- Improve thread safety of Ci::BuildTraceChunk data stores. !46717
- Fix 404 error from Commit Signature API when using Rugged. !46736
- Fix example responses for Project Issue Board creation API in the docs. !46749 (Takuya Noguchi)
- Autofocus on search input within labels dropdown after labels are loaded. !46750
- Fix example responses for Group Issue Board creation API in the docs. !46760 (Takuya Noguchi)
- Make the Merge Train process flow more resilient by always refreshing merge requests from beginning. !46768
- Show "No user list selected" in feature flags. !46790
- Skip disabled features when importing a project from Gitea. !46800 (John Kristensen (@jerrykan))
- Fixed create merge request dropdown not re-opening after typing invalid source branch. !46802
- Fix broadcast notification close icon appearance. !46804
- Fix remove label inconsistency. !46805
- Assign new incoming diff lines for renamed files to the correct view type. !46823
- Display submodules in MR tree and file header. !46840
- Fix empty state message in explore projects page. !46860
- Better-behaved tooltips in pipeline dropdown. !46866
- Ensure security report is displayed correctly in merge requests with a lot of CI jobs. !46870
- Fix code lines being cut-off on failed job tab. !46885
- Populate values for `has_vulnerabilities` column of `project_settings` table. !46890
- Fix group destroy not working with Gitaly Cluster. !46934
- Fix setting Comment detail for Jira and modal for groups. !46945
- Fix retried builds icon sprite to use css_class. !46955
- Remove unnecessary expand sha button in pipelines page. !47012
- Fix operations settings when Pipelines are disabled. !47062
- Fix duplicate epic iids and add uniqueness constraint. !47081
- Fix relative path not found on production web server. !47090
- Moved template warning below type. !47103
- Fix top margin in new project page. !47109
- Make delete repo prompts consistent. !47117
- Make register_instructions optional for RunnerSetup. !47123
- Fix milestones param validation for releases API PUT method. !47169
- Fixed create branch button not hiding when issue is closed. !47187
- Fix config variables when having includes. !47189
- Handle nullbytes in auth headers. !47206
- Fix error when updating releases with milestone associations through the UI. !47222
- Fixed diff metadata endpoint being called twice. !47265
- Fix pipeline security tab filters not showing. !47294
- Fix unified component inline display. !47345
- Fix secure MR widget colors in dark mode. !47352
- Fix status emoji tooltip trigger. !47378
- Fix workflow:rules not accessing passed-upstream and trigger variables. !47399
- Fix internal lfs_authenticate API for non-project repositories. !47404
- Fix alerts integration list Snowplow tracking event. !47413
- Resolve Suggest Pipeline flow second step not loading. !47419
- Fix overly aggressive prevent call. !47455
- Fix syntax highlight issue with regular expressions. !47469
- Stop finding commit with empty ref. !47497
- Fix issues list when due date parameter is invalid. !47524
- Bump versions of secrets and klar in the Secure-Binaries template. !47531
- Fixed copy contents functionality for snippets. !47646
- Reject incomplete multibyte chars in UTF8 params. !47658
- Fix deploy token permissions for write_package_registry. !47675
- Fix comment cells not rendering in unified component inline view. !47693
- Replace poorly performing auth event providers query in usage ping. !47710
- Do not fail when cleaning up MR with no repository. !47744
- Clear cached merge_ref_sha on reopen. !47747
- Refactor and UI-polish around activity calendar on user profile. !47797 (Takuya Noguchi)
- Fix for missing user info for Terraform State. !47814
### Deprecated (1 change)
- Deprecate support for Elasticsearch 6.x. !45619
### Changed (143 changes, 5 of them are from the community)
- Match Jira users by email, username or name on jira issues import. !33883
- Use global IDs for GraphQL arguments accepting sentry IDs. !36098
- GraphQL Snippets: use Global-ID scalar. !36117
- Add Google Tag Manger to sign in/up and trial pages. !38395
- Prune loose objects during git garbage collection. !39592
- Throttle container cleanup policies execution by using a limited capacity worker. !40740
- Update leave group modal to gl-modal. !41817
- Split sign in and sign up pages. !42592
- Improve messaging for emails from alerts. !43054
- Replace fa-check icons with GitLab SVG check icon. !43353
- Manually collapsed diff files are now significantly shorter and less visually intrusive. !43911
- Update change username modal. !44325
- Add support for search and inclusion of project labels within Group Labels API. !44415
- Add usage ping for unique users importing issues via CSV. !44742
- Add default regexes and prevent blank regexes for container cleanup policies. !44757
- Enable Sidekiq argument logging by default. !44853
- Search Autocomplete add GFM support for issues. !44930
- New group and project invite mail design. !44940
- Make the repository read-only while running cleanup. !45058
- Use existing group label when promoting project label. !45122
- Update Rack to v2.2.3. !45183
- Remove feedback alert from on-demand scans form. !45217
- Expand scope of coverage badge query to all successful builds. !45321
- Forbid top-level route sitemap.xml. !45359
- Update GraphQL input ids for Board Lists and Issues to be more type specific. !45398
- Update copy branch keyboard shortcut to click sidebar button. !45436
- Rename "a whole number" to "an integer number" in feature flags strategies. !45444
- Expose humanTimeEstimate and humanTotalTimeSpent via graphql. !45508
- Add link to the note on the email sent after adding a comment on an issue. !45511
- Add usage ping for unique users importing groups and projects via the group migration tool. !45536
- Remove resolve comment functionality. !45549
- Render 404 to search engine crawlers instead of redirecting to login. !45552
- Use GitLab SVGs in audit_icon helper. !45562
- Remove temp index on job artifacts. !45565
- Move test report system output to modal. !45575
- Generate a longer Kuberntes Agent Token by default (was 20 characters, now 50). !45620
- Update system note when marking merge request as draft or ready. !45644
- Replaced GlDeprecatedBadge for GlBadge in requirements tabs. !45647
- Add EC2 to AutoDevOps template. !45651
- Replace external-link icons with GitLab SVG. !45685
- Update loading icon for buttons used in MR's set to merge automatically. !45693
- Add fuzzy-search on full path in Groups API. !45729
- Minor UI improvements to Wiki Delete Page button and modal. !45740
- Add canonical link for default explore route. !45746
- Replaced GlDeprecatedBadge for GlBadge in environment header. !45768
- Replace fa-refresh icon with GitLab SVG. !45777
- Allow user snippets to be indexed by search crawlers. !45793
- Add total count to Terraform state GraphQL API. !45798
- Adds feature flag to disable package events. !45802
- increase allowed dotenv variables from 10 to 20. !45815 (jrreid)
- Remove search_filter_by_confidential Feature Flag. !45819
- Replace fa-caret-down with chevron-down SVG in pipeline action buttons. !45881
- Add new search params to metadata. !45896
- Add suggest pipeline for viable merge requests without pipelines. !45926
- Change permanent routable redirect to 301. !45980
- Disallow realtime_changes route in robots.txt. !45986
- Limits the Deploy Boards data to 10 MB. This change is enabled by default behind a feature flag. !46043
- Migrate DeprecatedModal to GitLab UI Modal for promoted labels. !46047
- Remove admin_approval_for_new_user_signups feature flag. !46051
- Show error in pipeline when API Fuzzing not licensed. !46064
- Improving Container Registry Delete Tags Service to log number of successfully deleted tags even if deletion process was interrupted by a timeout. !46079 (Maksim Stankevic, @maksimstankevic)
- Remove scanned_resources_count column from security scan. !46108
- Resolve request IP address on audit event. !46114
- Disallow /autocomplete/projects route in robots.txt. !46115
- Disallow WebIDE route in robots.txt. !46117
- Replace fa-chevron-up with GitLab SVG icon. !46118
- Pre-fetched GraphQL queries for snippet view. !46130
- Make all Project Issue Boards API available even in CE. !46137 (Takuya Noguchi)
- Move Personal Access Token API to Core. !46145
- Update stop all jobs modal to latest modal. !46157
- Replaced GlDeprecated Badge in clusters.vue. !46165
- Update whitelist/blacklist to allowlist/denylist in Signup restrictions window. !46168
- Use allowlist/denylist in application settings backend. !46170
- Update detailed_metric.vue modal to match Pajamas guidelines. !46183
- Use toasts for wiki notifications. !46201
- Refresh design zooming buttons. !46205
- Replace fa-warning icons with GitLab SVG warning-solid icon. !46214
- Disallow some project routes in robots.txt. !46218
- Improve empty search results message for group and project scopes. !46237
- Add minimal access users to group members api endpoints. !46238
- Replace ACE with Editor Lite. !46250
- Use CodeQuality 0.85.18 in the CI template. !46253
- Add rate limit bypass. !46259
- Use Helm 3 by default for GitLab-managed apps in new clusters. !46267
- Update diff_max_patch_bytes from 100kb -> 200kb. !46276
- Expand Diff File collapsed UI to be significantly more obvious. !46286
- Use standard loading state for Design Upload button. !46292
- Allow for return of scoped broadcast messages on shell. !46333
- Add filtering by recorded date to instance statistics measurements GraphQL API. !46344
- Background migration for setting Jira tracker data deployment type. !46368
- Use updated base report for CodeQuality MergeRequest widdget. !46384
- Make files header responsive and remove truncate name. !46406
- Switch to admin clusterRole for GitLab created environment Kubernetes service account. !46417
- Require Git v2.29.0. !46433
- Generate canonical url and remove trailing slash. !46435
- Moves projects_with_error_tracking_enabled ping usage to Core. !46556
- Mark Sidekiq queue selector as no longer experimental. !46562
- Add new incident counts to usage ping. !46602
- Added code coverage regex for Scala Scoverage. !46638 (opensorceror)
- Show error in pipeline when Coverage Fuzzing not licensed. !46652
- Forbid top level route sitemap. !46677
- Package details: on mobile show all the tags. !46679
- Add message in CI linter that it was validated with all the includes. !46713
- Reschedule again background migration which convers 'blocked_by' issue links to 'block'. !46770
- Load CI lint checks without refreshing the page. !46801
- Show code quality severity rating in the merge request details page. !46829
- Move "New subgroup" and "New project" out of the dropdown into individual buttons. !46907
- Admin approval required on user registration by default. !46937
- Update merge request search results design. !46944
- Add emailsDisabled field for issue type. !46947
- Enable 'instance_statistics' feature flag by default. !46962
- Update image upload path (SSE). !46967
- Changes limit for lsif artifacts to 100MB. !46980
- Add user info to Terraform State List. !46984
- Improve the container registry client tags delete method. !46989
- Filter GitHub projects to import using GitHub Search API. !47002
- Add BulkImport::Tracker to store the pagination information of the Group Migration (BulkImport) requests. !47009
- Use new image details API in container registry details. !47054
- Hide apply suggestion button for anonymous users. !47071
- Change the mutation and permissions for image note reposition. !47161
- Extend GraphQL API to commit to a new branch in a single operation. !47203
- Add region field to AWS Role. !47209
- Cache repository raw endpoint. !47225
- Update the tag name field helper text on the Edit Release page. !47234
- Make Terraform/Base.latest.gitlab-ci.yml template safer to use in projects that have non-terraform jobs. !47254
- MR Analytics: Fix chart tooltip covering filter dropdown. !47274
- Replace fa-check icon in custom notifications. !47288
- Use dedicated signing key for CI_JOB_JWT by default. !47336
- Replace fa-check icon in importer status. !47373
- Add pagination to Terraform list view. !47412
- Add new text and tab name for DAG. !47415
- Enable `vue_group_members_list` feature flag by default. !47427
- Improve the look of wikis in search results. !47470
- Dependency proxy feature is moved to GitLab core. !47471
- Remove ci_trace_new_fog_store feature flag. !47522
- Make schema breadcrumb urls absolute. !47523
- Add type annotation for snippet resolvers. !47548
- Remove feature flag to enable tracking unique test cases parsed globally. !47662
- Replace font-awesome icons in prometheus config. !47713
- Replace fa-chevron-down in dropdown button. !47758
- Replace fa-exclamation-triangle in markdown field MERGE_REQUEST_ID. !47786
- Update rack-oauth2 to v1.16.0. !47839 (Vincent Fazio @vfazio)
- Replace fa-chevron-down in project level VSA. !47885
### Performance (14 changes, 2 of them are from the community)
- Don't refresh all discussions for a new diff note on a merge request. !43015
- Add default_branch to ci_daily_build_group_report_result. !45702
- Upgrade labkit to 0.13.1. !45788
- Add Caching to BitBucket Server Import for pull requests. !45790 (Simon Schrottner)
- Resolve admin/license timeout on large instances. !46336
- Gracefully degrade when counting takes too long for a filtered search. !46350
- Add Batch Support for Importing Pull Requests from Bitbucket. !46696 (Simon Schrottner)
- Schedule clean up of merge request refs efficiently. !46758
- Only set an ETag for the notes endpoint after all notes have been sent. !46810
- Parallelize the removal of expired job artifacts. !46971
- Fix pipelines chart query timeout. !47069
- Add NULLS LAST to index on merge request metrics. !47300
- Add missing expression indexes. !47424
- Enable HTTP caching of repository raw, archive, and avatar endpoints. !47430
### Added (119 changes, 6 of them are from the community)
- Show build status in tag list. !34307 (Lee Tickett)
- Enable the ability to upload images via the SSE. !36299
- Add webhooks for feature flag. !41863 (Sashi)
- Add until_executed deduplication strategy. !42223
- Add support for .md.erb files in Static Site Editor. !42353
- Add install GitLab runner popup. !42877
- Add Default Initial Branch Name for Repositories Group Setting. !43290
- Update the milestone dropdown combobox to display separated sections and badge counters. !43427
- Jira Connect automatically synchronizes up to 400 existing merge requests per project when a namespace is connected. !43880
- Add CI JWT signing key to application_setings. !43950
- Add GraphQL endpoints to lock, unlock and delete Terraform states. !43955
- Add ability to sort releases on Releases page. !43963
- Debian RFC822 and .deb metadata extractor. !44029 (Mathieu Parent)
- Add assignees multiselect wrapper. !44087
- Show the environment link on alert details page. !44130
- Associate multiple pipelines with packages and package files. !44348
- Add a stop review job for ECS. !44717
- Add assignee dropdown to group issue boards. !44830
- Add Total Duration to CI/CD Analytics Page. !44863 (Kev @KevSlashNull)
- Add webhooks for creating and updating a release. !44881 (David Barr @davebarr)
- GraphQL API for listing container repositories. !44926
- Add ability to sort search results for issues and merge requests. !45003
- Add db index for DastSiteValidation#state. !45019
- Store test failure data when build finishes. !45027
- Add GraphQL burnup endpoint under milestone and iteration reports. !45121
- Add hostname to GitHub import API. !45188
- GraphQL: Adds downstream, upstream, source job, path, and project to PipelineType. !45212
- Associate Terraform state versions with the CI job that created them. !45347
- Add sha256 and file count to pages_deployments. !45522
- Add basic top level keys license, settings, and counts_weekly for usage data payload. !45540
- Allow sorting of releases from GraphQL. !45577
- UsageData for issues added/removed/edited. !45609
- Enable dashboard security discover button and ignore feature_filter_type column cleanup. !45636
- Add Support for Canary-Weight ingress annotation via API. !45637
- Add default sitemap generator for gitlab-org group. !45645
- Add new Terraform state list page. !45700
- Add Search for User Lists in Strategy. !45820
- Add jobs field with secureReportTypes argument to Ci::PipelineType. !45837
- Add latest version field to Terraform state GraphQL type. !45848
- Add repository_read_only column to Snippets. !45868
- Add availability to user status. !45888
- Add secret detection token revocation columns. !45912
- Add `has_vulnerabilities` column into project_settings table. !45944
- Email user on admin account approval. !45947
- Add API post /invitations by email. !45950
- Add repositionImageDiffNote GraphQL mutation to specifically reposition DiffNotes on images. !45958
- Create snippet_repository_storage_moves database table. !45990
- Expose issue updated by on GraphQL. !46015
- Allow to create todo on GraphQL. !46029
- Add API get /invitations for project and group. !46046
- Migrate Configure Feature Flags Modal to GitLab UI. !46055
- Add User-Agent to web hook service. !46070
- Add links to GraphQL release object for searching related issues and merge requests. !46161
- Migrate Alert Service to HTTP Integrations model. !46188
- Determine image relative paths. !46208
- Add releaseCreate mutation to GraphQL endpoint. !46263
- Add migration to populate pipeline_id in Vulnerability Feedback. !46266
- Add a /draft alias to the /wip quick action. !46277
- Add usage ping for web users of geo secondaries. !46278
- Enable refactored union set operator. !46295
- Add userDiscussionsCount to issues and merge requests GraphQL. !46311
- Add a service for token revocation. !46356
- Forward deployment, add modal to warn users on Retry action. !46416
- Expose moved and movedTo attributes in Issues query. !46447
- Add merge request description templates to Static Site Editor. !46488
- Add container repositories API. !46495
- Track usage of CI Secrets Management (Vault secrets). !46515
- Added GraphQL mutation for creating project and group labels. !46534
- Add total projects imported usage ping. !46541
- Add structured markup for users. !46553
- Container repository details GraphQL API. !46560
- Add iid column to design_management_designs. !46596
- Add search param to Users GraphQL type. !46609
- Add metric count for projects with alerts created. !46636
- Add ability to sort to search API. !46646
- Enable MR CSV export. !46662
- Upgrade GitLab Pages to 1.29.0. !46665
- Add merge requests filters for filtering by environments and deployment dates. !46683
- Add projects_with_enabled_alert_integrations usage ping. !46693
- Remove graphql_lazy_authorization feature flag. !46819
- Allow setting the value of 'require_admin_approval_after_user_signup' via Settings API. !46851
- Add structured data for projects. !46858
- NPM project level API. !46867
- Send email notifications to admins about users pending approval. !46895
- Monokai theme for the Web IDE. !46901
- Enable issue and MR stat links on release progress review. !46910
- Default enable new_pipeline_form. !46915
- Add tooltips to design buttons. !46922 (Lee Tickett)
- Account for uploads as part of used repository storage. !46941
- Add SEO schema markup to breadcrumbs. !46991
- Add locked and confidential badge to issue sticky header. !46996
- Add Web IDE Solarized Light theme support. !46999
- Add POST project CI lint API endpoint. !47026
- Display Group SAML provider ID in admin. !47034
- Adds warnings to API response for /lint. !47037
- Filter jobs by security report type in GraphQL. !47095
- Update container_scanning to version 3 to support FIPS. !47099
- Adds rake task to generate package events file. !47118
- Add container repository destroy GraphQL mutation. !47175
- Add search assignees to group issue boards. !47241
- Add Redis version to admin page. !47242
- Add CI job to Terraform state version GraphQL type. !47339
- Add pipeline to CI job GraphQL type. !47347
- Add group-level integration management for external services. !47391
- Add cloud_license_auth_token column to application_settings. !47396
- Add user callout to alert admins that registration is open by default. !47425
- Include aggregated git-write usage counts. !47511
- Add cleanup status field to graphQL ContainerRepositoryType. !47544
- Global Search - Left Sidebar. !47561
- Add group name and link in admin identities. !47563
- Implement including multiple files from a project. !47609
- Expose GraphQL API for managing HTTP alerting intergations. !47687
- Convert issue header actions to an ellipsis dropdown menu. !47690
- Add alerting support for custom dashboards. !47704
- Support fuzzing HTTP headers with API Fuzzing. !47727
- Store pages content in zip format. !47763
- Upgrade GitLab Pages to 1.30.0. !47780
- Add variable expansion to rules:changes. !47783
- GraphQL: Expose uploads_size for project_statistics and root_storage_statistics. !47820
### Other (68 changes, 26 of them are from the community)
- Migrate .fa-spinner to .spinner for app/helpers. !25033 (nuwe1)
- Remove new_variables_ui feature flag. !41412
- Replace-GlDeprecatedDropdown-with-GlDropdown-in-app/assets/javascripts/error_tracking. !41420 (nuwe1)
- Replace-GlDeprecatedDropdown-with-GlDropdown-in-app/assets/javascripts/monitoring. !41422 (nuwe1)
- Replace Deprecated Dropdown in Container Registry Explorer Page. !41425 (nuwe1)
- Replace-GlDeprecatedDropdown-with-GlDropdown-in-app/assets/javascripts/snippets/components/snippet_header.vue. !41428 (nuwe1)
- Replace-GlDeprecatedDropdown-with-GlDropdown-in-app/assets/javascripts/vue_merge_request_widget. !41429 (nuwe1)
- Migrate-Bootstrap-dropdown-to-GitLab-UI-GlDropdown-in-app/assets/javascripts/jobs/components/stages_dropdown.vue. !41452 (nuwe1)
- Replace v-html with GlSprintf in notes/.../discussion_filter_note.vue. !41482 (Takuya Noguchi)
- Update to Ruby v2.7.2. !44223
- Update haml_lint from 0.34.0 to 0.36.0. !44914 (Takuya Noguchi)
- Update Web IDE pipelines panel to use our design system component. !45007 (matejlatin)
- Replace existing Image Resizing FFs with a single of `ops` type enabled by default. !45050
- Remove Cycle Analytics message from en i18n message. !45178 (Takuya Noguchi)
- Specify primary key for tables without. !45198
- Update Tooltip in Groups to use gl-tooltip. !45305
- Print Ruby version in console greeting. !45370
- Fix Rails/SaveBang offenses for spec/services/* and spec/sidekiq/*. !45391 (matthewbried)
- Migrate collapsed notification tooltip. !45453 (artychan)
- Add database view for partitioned tables. !45591
- Add database view for partitions. !45592
- Remove duplicated BS display properties from Issue. !45628 (Takuya Noguchi)
- Replace quick_submit BSTooltip with GlTooltip. !45638 (Kristin Brooks @kristinbrooks)
- Add migration to add a new configuration option for setting the new user signups count. !45643
- Remove unnecessary index on services for usage data. !45655
- Update GitLab Runner Helm Chart to 0.22.0. !45664
- Replace bootstrap classes for alerts in ping consent. !45723
- Add `analytics_devops_adoption_segment_selections` and `analytics_devops_adoption_segments` database tables. !45748
- Refactor secondary_navigation_elements.scss. !45763 (Takuya Noguchi)
- Migrate toggle replies widget from Bootstrap to GlButton. !45780
- Simplify CSS for Merge Requests (list). !45785 (Takuya Noguchi)
- Add auto_rollback_enabled column to project_ci_cd_settings table. !45816
- Add merge trains enabled setting to project ci cd settings. !45834
- Fix incorrect code in Load Performance Testing docs. !45877
- Migrate services specs to consider admin mode. !45988 (Diego Louzán)
- Migrate tooltip in app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue. !46034
- Migrate Bootstrap buttons to GitLab UI buttons for attach a file form actions. !46041
- Replace chevron-down fa-icon in board sidebar. !46075
- Replace down chevron on invite member/group. !46076
- Clarify that external users cannot access all internal projects, groups, and snippets. !46087 (Ben Bodenmiller (@bbodenmiller))
- Declare and assign variable separately in Shell Script. !46121 (Peter Dave Hello @PeterDaveHello)
- Execute `exit 1` when shell script `cd` fails. !46122 (Peter Dave Hello)
- Migrate tooltip in app/assets/javascripts/ide/components/commit_sidebar/list.vue. !46148
- Migrate tooltip in app/assets/javascripts/vue_merge_request_widget/components/mr_file_icon.vue. !46156 (46156)
- Migrate tooltip in app/assets/javascripts/vue_shared/components/awards_list.vue. !46171
- Replace close button in Scheduling Pipelines user notice with GlButton. !46264
- Add performance marks and measures to the MR Diffs app at critical moments. !46434
- Corrected grammar in Sign-in restrictions text. !46500
- Update access token fields to new input style. !46569
- Rename "cycle analytics" with "value stream analytics" under /spec. !46613 (Takuya Noguchi)
- Resolve Implement GraphQL Startup.js for Design Management app. !46660
- Bump workhorse to 8.53.0. !46666
- Remove columns no longer used for replicating terraform state. !46742
- Backfill cleanup schedules for old closed/merged MRs. !46782
- Bump gitlab-shell version to 13.12.0. !47084
- Remove duplicated BS display properties from Merge Request title. !47124 (Takuya Noguchi)
- Remove duplicated BS display properties from various Diffs. !47125 (Takuya Noguchi)
- Expand postgres_indexes view. !47304
- Update terminal empty state alert to gl component. !47340
- Guard against existence of project_features.requirement_access_level in migration. !47458 (Lee Tickett)
- Replace mirror chevron down icon with svg. !47459
- Update chevron-down icon on project branch page. !47460
- Update button styles in project tree header. !47562
- Update button styles in blob header. !47571
- Update icons to svg for issuable pages. !47596
- Update Workhorse version to 8.54.0. !47625
- Update GitLab Shell to v13.13.0. !47875
- Change wording on the project remove fork page. !47878
## 13.5.4 (2020-11-13) ## 13.5.4 (2020-11-13)
### Fixed (4 changes) ### Fixed (4 changes)
@ -657,6 +1199,32 @@ entry.
- Bump cluster applications CI template. !45472 - Bump cluster applications CI template. !45472
## 13.4.6 (2020-11-03)
### Fixed (1 change)
- Auto Deploy: fixes issues for fetching other charts from stable repo. !46531
### Other (1 change)
- GitLab-managed apps: Use GitLab's repo as replacement for the Helm stable repo. !44875
## 13.4.5 (2020-11-02)
### Security (9 changes)
- Add CSRF protection to runner pause and resume. !1021
- Do not expose Terraform state record in API.
- Path traversal to RCE via LFS upload.
- Update container_repository_name_regex to prevent catastrophic backtracking.
- Validate nuget package names.
- Prevent private repo from being accessed via internal Kubernetes API.
- Validate each upload param key in multipart.rb.
- Fix XSS vulnerability for job build dependencies.
- Fix unauthorized user is able to access schedule pipeline variables and values.
## 13.4.4 (2020-10-15) ## 13.4.4 (2020-10-15)
### Fixed (2 changes) ### Fixed (2 changes)
@ -1315,6 +1883,37 @@ entry.
- Expand the visible highlight for collapsed diffs (re: !41393). !42343 - Expand the visible highlight for collapsed diffs (re: !41393). !42343
## 13.3.9 (2020-11-02)
### Security (9 changes)
- Add CSRF protection to runner pause and resume. !1021
- Do not expose Terraform state record in API.
- Path traversal to RCE via LFS upload.
- Update container_repository_name_regex to prevent catastrophic backtracking.
- Validate nuget package names.
- Prevent private repo from being accessed via internal Kubernetes API.
- Validate each upload param key in multipart.rb.
- Fix XSS vulnerability for job build dependencies.
- Fix unauthorized user is able to access schedule pipeline variables and values.
## 13.3.8 (2020-10-21)
### Fixed (2 changes)
- Make SSH keys publicly accessible. !42288
- Revert required encryption on CI runner tokens. !42623
### Added (1 change)
- Add missing fontawesome file icon classes. !43091
### Other (1 change)
- GitLab-managed apps: Use GitLab's repo as replacement for the Helm stable repo. !44875
## 13.3.4 (2020-09-02) ## 13.3.4 (2020-09-02)
### Security (1 change) ### Security (1 change)

View file

@ -1 +1 @@
13.5.7 13.6.5

View file

@ -1 +1 @@
0.0.6 13.6.1

View file

@ -1 +1 @@
1.28.2 1.30.2

View file

@ -1 +1 @@
13.11.0 13.13.0

View file

@ -1 +1 @@
8.51.2 8.54.2

36
Gemfile
View file

@ -19,7 +19,7 @@ gem 'default_value_for', '~> 3.3.0'
gem 'pg', '~> 1.1' gem 'pg', '~> 1.1'
gem 'rugged', '~> 0.28' gem 'rugged', '~> 0.28'
gem 'grape-path-helpers', '~> 1.4' gem 'grape-path-helpers', '~> 1.5'
gem 'faraday', '~> 1.0' gem 'faraday', '~> 1.0'
gem 'marginalia', '~> 1.9.0' gem 'marginalia', '~> 1.9.0'
@ -48,7 +48,7 @@ gem 'omniauth-authentiq', '~> 0.3.3'
gem 'omniauth_openid_connect', '~> 0.3.5' gem 'omniauth_openid_connect', '~> 0.3.5'
gem 'omniauth-salesforce', '~> 1.0.5' gem 'omniauth-salesforce', '~> 1.0.5'
gem 'omniauth-atlassian-oauth2', '~> 0.2.0' gem 'omniauth-atlassian-oauth2', '~> 0.2.0'
gem 'rack-oauth2', '~> 1.9.3' gem 'rack-oauth2', '~> 1.16.0'
gem 'jwt', '~> 2.1.0' gem 'jwt', '~> 2.1.0'
# Kerberos authentication. EE-only # Kerberos authentication. EE-only
@ -98,6 +98,7 @@ gem 'graphql', '~> 1.11.4'
gem 'graphiql-rails', '~> 1.4.10' gem 'graphiql-rails', '~> 1.4.10'
gem 'apollo_upload_server', '~> 2.0.2' gem 'apollo_upload_server', '~> 2.0.2'
gem 'graphql-docs', '~> 1.6.0', group: [:development, :test] gem 'graphql-docs', '~> 1.6.0', group: [:development, :test]
gem 'graphlient', '~> 0.4.0' # Used by BulkImport feature (group::import)
gem 'hashie' gem 'hashie'
# Disable strong_params so that Mash does not respond to :permitted? # Disable strong_params so that Mash does not respond to :permitted?
@ -118,7 +119,7 @@ gem 'fog-aws', '~> 3.5'
# Locked until fog-google resolves https://github.com/fog/fog-google/issues/421. # Locked until fog-google resolves https://github.com/fog/fog-google/issues/421.
# Also see config/initializers/fog_core_patch.rb. # Also see config/initializers/fog_core_patch.rb.
gem 'fog-core', '= 2.1.0' gem 'fog-core', '= 2.1.0'
gem 'fog-google', '~> 1.10' gem 'fog-google', '~> 1.11'
gem 'fog-local', '~> 0.6' gem 'fog-local', '~> 0.6'
gem 'fog-openstack', '~> 1.0' gem 'fog-openstack', '~> 1.0'
gem 'fog-rackspace', '~> 0.1.1' gem 'fog-rackspace', '~> 0.1.1'
@ -158,7 +159,7 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 2.0.10' gem 'asciidoctor', '~> 2.0.10'
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
gem 'asciidoctor-plantuml', '~> 0.0.12' gem 'asciidoctor-plantuml', '~> 0.0.12'
gem 'rouge', '~> 3.24.0' gem 'rouge', '~> 3.25.0'
gem 'truncato', '~> 0.7.11' gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0' gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.10.9' gem 'nokogiri', '~> 1.10.9'
@ -172,7 +173,7 @@ gem 'diffy', '~> 3.3'
gem 'diff_match_patch', '~> 0.1.0' gem 'diff_match_patch', '~> 0.1.0'
# Application server # Application server
gem 'rack', '~> 2.1.4' gem 'rack', '~> 2.2.3'
# https://github.com/sharpstone/rack-timeout/blob/master/README.md#rails-apps-manually # https://github.com/sharpstone/rack-timeout/blob/master/README.md#rails-apps-manually
gem 'rack-timeout', '~> 0.5.1', require: 'rack/timeout/base' gem 'rack-timeout', '~> 0.5.1', require: 'rack/timeout/base'
@ -271,9 +272,6 @@ gem 'loofah', '~> 2.2'
# Working with license # Working with license
gem 'licensee', '~> 8.9' gem 'licensee', '~> 8.9'
# Ace editor
gem 'ace-rails-ap', '~> 4.1.0'
# Detect and convert string character encoding # Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.7' gem 'charlock_holmes', '~> 0.7.7'
@ -307,13 +305,16 @@ gem 'rack-attack', '~> 6.3.0'
# Sentry integration # Sentry integration
gem 'sentry-raven', '~> 3.0' gem 'sentry-raven', '~> 3.0'
# PostgreSQL query parsing
gem 'gitlab-pg_query', '~> 1.3', require: 'pg_query'
gem 'premailer-rails', '~> 1.10.3' gem 'premailer-rails', '~> 1.10.3'
# LabKit: Tracing and Correlation # LabKit: Tracing and Correlation
gem 'gitlab-labkit', '0.12.2' gem 'gitlab-labkit', '0.13.1'
# I18n # I18n
gem 'ruby_parser', '~> 3.8', require: false gem 'ruby_parser', '~> 3.15', require: false
gem 'rails-i18n', '~> 6.0' gem 'rails-i18n', '~> 6.0'
gem 'gettext_i18n_rails', '~> 1.8.0' gem 'gettext_i18n_rails', '~> 1.8.0'
gem 'gettext_i18n_rails_js', '~> 1.3' gem 'gettext_i18n_rails_js', '~> 1.3'
@ -366,22 +367,19 @@ group :development, :test do
# Generate Fake data # Generate Fake data
gem 'ffaker', '~> 2.10' gem 'ffaker', '~> 2.10'
gem 'spring', '~> 2.0.0' gem 'spring', '~> 2.1.0'
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'gitlab-styles', '~> 4.3.0', require: false gem 'gitlab-styles', '~> 5.1.0', require: false
# Pin these dependencies, otherwise a new rule could break the CI pipelines
gem 'rubocop', '~> 0.82.0'
gem 'rubocop-performance', '~> 1.5.2'
gem 'rubocop-rspec', '~> 1.37.0'
gem 'scss_lint', '~> 0.56.0', require: false gem 'scss_lint', '~> 0.59.0', require: false
gem 'haml_lint', '~> 0.34.0', require: false gem 'haml_lint', '~> 0.36.0', require: false
gem 'bundler-audit', '~> 0.6.1', require: false gem 'bundler-audit', '~> 0.6.1', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false gem 'benchmark-ips', '~> 2.3.0', require: false
gem 'knapsack', '~> 1.17' gem 'knapsack', '~> 1.17'
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
@ -407,7 +405,7 @@ end
group :test do group :test do
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.5' gem 'rspec_profiling', '~> 0.0.6'
gem 'rspec-parameterized', require: false gem 'rspec-parameterized', require: false
gem 'capybara', '~> 3.33.0' gem 'capybara', '~> 3.33.0'

View file

@ -3,7 +3,6 @@ GEM
specs: specs:
RedCloth (4.3.2) RedCloth (4.3.2)
abstract_type (0.0.7) abstract_type (0.0.7)
ace-rails-ap (4.1.2)
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.3) actioncable (6.0.3.3)
@ -71,7 +70,7 @@ GEM
memoizable (~> 0.4.0) memoizable (~> 0.4.0)
addressable (2.7.0) addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 5.0)
aes_key_wrap (1.0.1) aes_key_wrap (1.1.0)
akismet (3.0.0) akismet (3.0.0)
android_key_attestation (0.3.0) android_key_attestation (0.3.0)
apollo_upload_server (2.0.2) apollo_upload_server (2.0.2)
@ -87,7 +86,7 @@ GEM
asciidoctor (>= 1.5.6, < 3.0.0) asciidoctor (>= 1.5.6, < 3.0.0)
asciidoctor-plantuml (0.0.12) asciidoctor-plantuml (0.0.12)
asciidoctor (>= 1.5.6, < 3.0.0) asciidoctor (>= 1.5.6, < 3.0.0)
ast (2.4.0) ast (2.4.1)
atlassian-jwt (0.2.0) atlassian-jwt (0.2.0)
jwt (~> 2.1.0) jwt (~> 2.1.0)
attr_encrypted (3.1.0) attr_encrypted (3.1.0)
@ -133,7 +132,7 @@ GEM
coderay (>= 1.0.0) coderay (>= 1.0.0)
erubi (>= 1.0.0) erubi (>= 1.0.0)
rack (>= 0.9.0) rack (>= 0.9.0)
bindata (2.4.3) bindata (2.4.8)
binding_ninja (0.2.3) binding_ninja (0.2.3)
bootsnap (1.4.6) bootsnap (1.4.6)
msgpack (~> 1.0) msgpack (~> 1.0)
@ -199,6 +198,8 @@ GEM
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
crass (1.0.6) crass (1.0.6)
creole (0.5.0) creole (0.5.0)
crystalball (0.7.0)
git
css_parser (1.7.0) css_parser (1.7.0)
addressable addressable
daemons (1.2.6) daemons (1.2.6)
@ -368,11 +369,12 @@ GEM
excon (~> 0.58) excon (~> 0.58)
formatador (~> 0.2) formatador (~> 0.2)
mime-types mime-types
fog-google (1.10.0) fog-google (1.11.0)
fog-core (<= 2.1.0) fog-core (<= 2.1.0)
fog-json (~> 1.2) fog-json (~> 1.2)
fog-xml (~> 0.1.0) fog-xml (~> 0.1.0)
google-api-client (>= 0.32, < 0.34) google-api-client (>= 0.32, < 0.34)
google-cloud-env (~> 1.2)
fog-json (1.2.0) fog-json (1.2.0)
fog-core fog-core
multi_json (~> 1.10) multi_json (~> 1.10)
@ -428,17 +430,18 @@ GEM
fog-json (~> 1.2.0) fog-json (~> 1.2.0)
mime-types mime-types
ms_rest_azure (~> 0.12.0) ms_rest_azure (~> 0.12.0)
gitlab-labkit (0.12.2) gitlab-labkit (0.13.1)
actionpack (>= 5.0.0, < 6.1.0) actionpack (>= 5.0.0, < 6.1.0)
activesupport (>= 5.0.0, < 6.1.0) activesupport (>= 5.0.0, < 6.1.0)
grpc (~> 1.19) grpc (~> 1.19)
jaeger-client (~> 0.10) jaeger-client (~> 1.1)
opentracing (~> 0.4) opentracing (~> 0.4)
redis (> 3.0.0, < 5.0.0) redis (> 3.0.0, < 5.0.0)
gitlab-license (1.0.0) gitlab-license (1.0.0)
gitlab-mail_room (0.0.7) gitlab-mail_room (0.0.7)
gitlab-markup (1.7.1) gitlab-markup (1.7.1)
gitlab-net-dns (0.9.1) gitlab-net-dns (0.9.1)
gitlab-pg_query (1.3.0)
gitlab-puma (4.3.5.gitlab.3) gitlab-puma (4.3.5.gitlab.3)
nio4r (~> 2.0) nio4r (~> 2.0)
gitlab-puma_worker_killer (0.1.1.gitlab.1) gitlab-puma_worker_killer (0.1.1.gitlab.1)
@ -446,12 +449,12 @@ GEM
gitlab-puma (>= 2.7, < 5) gitlab-puma (>= 2.7, < 5)
gitlab-sidekiq-fetcher (0.5.2) gitlab-sidekiq-fetcher (0.5.2)
sidekiq (~> 5) sidekiq (~> 5)
gitlab-styles (4.3.0) gitlab-styles (5.1.0)
rubocop (~> 0.82.0) rubocop (~> 0.89.1)
rubocop-gitlab-security (~> 0.1.0) rubocop-gitlab-security (~> 0.1.0)
rubocop-performance (~> 1.5.2) rubocop-performance (~> 1.8.1)
rubocop-rails (~> 2.5) rubocop-rails (~> 2.8)
rubocop-rspec (~> 1.36) rubocop-rspec (~> 1.44)
gitlab_chronic_duration (0.10.6.2) gitlab_chronic_duration (0.10.6.2)
numerizer (~> 0.2) numerizer (~> 0.2)
gitlab_omniauth-ldap (2.1.1) gitlab_omniauth-ldap (2.1.1)
@ -473,6 +476,8 @@ GEM
representable (~> 3.0) representable (~> 3.0)
retriable (>= 2.0, < 4.0) retriable (>= 2.0, < 4.0)
signet (~> 0.12) signet (~> 0.12)
google-cloud-env (1.4.0)
faraday (>= 0.17.3, < 2.0)
google-protobuf (3.12.4) google-protobuf (3.12.4)
googleapis-common-protos-types (1.0.5) googleapis-common-protos-types (1.0.5)
google-protobuf (~> 3.11) google-protobuf (~> 3.11)
@ -495,17 +500,24 @@ GEM
grape-entity (0.7.1) grape-entity (0.7.1)
activesupport (>= 4.0) activesupport (>= 4.0)
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
grape-path-helpers (1.4.0) grape-path-helpers (1.5.0)
activesupport activesupport
grape (~> 1.3) grape (~> 1.3)
rake (~> 12) rake (> 12)
grape_logging (1.8.3) grape_logging (1.8.3)
grape grape
rack rack
graphiql-rails (1.4.10) graphiql-rails (1.4.10)
railties railties
sprockets-rails sprockets-rails
graphlient (0.4.0)
faraday (>= 1.0)
faraday_middleware
graphql-client
graphql (1.11.4) graphql (1.11.4)
graphql-client (0.16.0)
activesupport (>= 3.0)
graphql (~> 1.8)
graphql-docs (1.6.0) graphql-docs (1.6.0)
commonmarker (~> 0.16) commonmarker (~> 0.16)
escape_utils (~> 1.2) escape_utils (~> 1.2)
@ -536,8 +548,9 @@ GEM
haml (5.1.2) haml (5.1.2)
temple (>= 0.8.0) temple (>= 0.8.0)
tilt tilt
haml_lint (0.34.0) haml_lint (0.36.0)
haml (>= 4.0, < 5.2) haml (>= 4.0, < 5.3)
parallel (~> 1.10)
rainbow rainbow
rubocop (>= 0.50.0) rubocop (>= 0.50.0)
sysexits (~> 1.1) sysexits (~> 1.1)
@ -586,10 +599,9 @@ GEM
invisible_captcha (0.12.1) invisible_captcha (0.12.1)
rails (>= 3.2.0) rails (>= 3.2.0)
ipaddress (0.8.3) ipaddress (0.8.3)
jaeger-client (0.10.0) jaeger-client (1.1.0)
opentracing (~> 0.3) opentracing (~> 0.3)
thrift thrift
jaro_winkler (1.5.4)
jira-ruby (2.0.0) jira-ruby (2.0.0)
activesupport activesupport
atlassian-jwt atlassian-jwt
@ -601,7 +613,7 @@ GEM
regexp_parser (~> 1.5) regexp_parser (~> 1.5)
regexp_property_values (~> 0.3) regexp_property_values (~> 0.3)
json (2.3.0) json (2.3.0)
json-jwt (1.11.0) json-jwt (1.13.0)
activesupport (>= 4.2) activesupport (>= 4.2)
aes_key_wrap aes_key_wrap
bindata bindata
@ -821,9 +833,9 @@ GEM
rubypants (~> 0.2) rubypants (~> 0.2)
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (1.0.0) os (1.0.0)
parallel (1.19.1) parallel (1.19.2)
parser (2.7.1.2) parser (2.7.2.0)
ast (~> 2.4.0) ast (~> 2.4.1)
parslet (1.8.2) parslet (1.8.2)
peek (1.1.0) peek (1.1.0)
railties (>= 4.0.0) railties (>= 4.0.0)
@ -855,19 +867,19 @@ GEM
public_suffix (4.0.6) public_suffix (4.0.6)
pyu-ruby-sasl (0.0.3.3) pyu-ruby-sasl (0.0.3.3)
raabro (1.1.6) raabro (1.1.6)
rack (2.1.4) rack (2.2.3)
rack-accept (0.4.5) rack-accept (0.4.5)
rack (>= 0.4) rack (>= 0.4)
rack-attack (6.3.0) rack-attack (6.3.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rack-cors (1.0.6) rack-cors (1.0.6)
rack (>= 1.6.0) rack (>= 1.6.0)
rack-oauth2 (1.9.3) rack-oauth2 (1.16.0)
activesupport activesupport
attr_required attr_required
httpclient httpclient
json-jwt (>= 1.9.0) json-jwt (>= 1.11.0)
rack rack (>= 2.1.0)
rack-protection (2.0.5) rack-protection (2.0.5)
rack rack
rack-proxy (0.6.0) rack-proxy (0.6.0)
@ -910,7 +922,7 @@ GEM
thor (>= 0.20.3, < 2.0) thor (>= 0.20.3, < 2.0)
rainbow (3.0.0) rainbow (3.0.0)
raindrops (0.19.1) raindrops (0.19.1)
rake (12.3.3) rake (13.0.1)
rb-fsevent (0.10.4) rb-fsevent (0.10.4)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
@ -945,7 +957,7 @@ GEM
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-store (1.8.1) redis-store (1.8.1)
redis (>= 4, < 5) redis (>= 4, < 5)
regexp_parser (1.5.1) regexp_parser (1.8.2)
regexp_property_values (0.3.5) regexp_property_values (0.3.5)
representable (3.0.4) representable (3.0.4)
declarative (< 0.1.0) declarative (< 0.1.0)
@ -965,7 +977,7 @@ GEM
rexml (3.2.4) rexml (3.2.4)
rinku (2.0.0) rinku (2.0.0)
rotp (2.1.2) rotp (2.1.2)
rouge (3.24.0) rouge (3.25.0)
rqrcode (0.7.0) rqrcode (0.7.0)
chunky_png chunky_png
rqrcode-rails3 (0.1.7) rqrcode-rails3 (0.1.7)
@ -1001,29 +1013,34 @@ GEM
rspec-support (3.9.2) rspec-support (3.9.2)
rspec_junit_formatter (0.4.1) rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0) rspec-core (>= 2, < 4, != 2.12.0)
rspec_profiling (0.0.5) rspec_profiling (0.0.6)
activerecord activerecord
pg pg
rails rails
sqlite3 sqlite3
rubocop (0.82.0) rubocop (0.89.1)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.7.0.1) parser (>= 2.7.1.1)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.7)
rexml rexml
rubocop-ast (>= 0.3.0, < 1.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0) unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (0.8.0)
parser (>= 2.7.1.5)
rubocop-gitlab-security (0.1.1) rubocop-gitlab-security (0.1.1)
rubocop (>= 0.51) rubocop (>= 0.51)
rubocop-performance (1.5.2) rubocop-performance (1.8.1)
rubocop (>= 0.71.0) rubocop (>= 0.87.0)
rubocop-rails (2.5.2) rubocop-ast (>= 0.4.0)
activesupport rubocop-rails (2.8.1)
activesupport (>= 4.2.0)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 0.72.0) rubocop (>= 0.87.0)
rubocop-rspec (1.37.0) rubocop-rspec (1.44.1)
rubocop (>= 0.68.1) rubocop (~> 0.87)
rubocop-ast (>= 0.7.1)
ruby-enum (0.7.2) ruby-enum (0.7.2)
i18n i18n
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
@ -1034,7 +1051,7 @@ GEM
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
ruby-statistics (2.1.2) ruby-statistics (2.1.2)
ruby2_keywords (0.0.2) ruby2_keywords (0.0.2)
ruby_parser (3.13.1) ruby_parser (3.15.0)
sexp_processor (~> 4.9) sexp_processor (~> 4.9)
rubyntlm (0.6.2) rubyntlm (0.6.2)
rubypants (0.2.0) rubypants (0.2.0)
@ -1064,9 +1081,8 @@ GEM
sawyer (0.8.2) sawyer (0.8.2)
addressable (>= 2.3.5) addressable (>= 2.3.5)
faraday (> 0.8, < 2.0) faraday (> 0.8, < 2.0)
scss_lint (0.56.0) scss_lint (0.59.0)
rake (>= 0.9, < 13) sass (~> 3.5, >= 3.5.5)
sass (~> 3.5.3)
securecompare (1.0.0) securecompare (1.0.0)
seed-fu (2.3.7) seed-fu (2.3.7)
activerecord (>= 3.1) activerecord (>= 3.1)
@ -1077,7 +1093,7 @@ GEM
sentry-raven (3.0.4) sentry-raven (3.0.4)
faraday (>= 1.0) faraday (>= 1.0)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.12.0) sexp_processor (4.15.1)
shellany (0.0.1) shellany (0.0.1)
shoulda-matchers (4.0.1) shoulda-matchers (4.0.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
@ -1105,8 +1121,7 @@ GEM
slack-messenger (2.3.4) slack-messenger (2.3.4)
snowplow-tracker (0.6.1) snowplow-tracker (0.6.1)
contracts (~> 0.7, <= 0.11) contracts (~> 0.7, <= 0.11)
spring (2.0.2) spring (2.1.1)
activesupport (>= 4.2)
spring-commands-rspec (1.0.4) spring-commands-rspec (1.0.4)
spring (>= 0.9.1) spring (>= 0.9.1)
sprockets (3.7.2) sprockets (3.7.2)
@ -1146,7 +1161,7 @@ GEM
rack (>= 1, < 3) rack (>= 1, < 3)
thor (0.20.3) thor (0.20.3)
thread_safe (0.3.6) thread_safe (0.3.6)
thrift (0.11.0.0) thrift (0.13.0)
tilt (2.0.10) tilt (2.0.10)
timecop (0.9.1) timecop (0.9.1)
timeliness (0.3.10) timeliness (0.3.10)
@ -1162,7 +1177,7 @@ GEM
truncato (0.7.11) truncato (0.7.11)
htmlentities (~> 4.3.1) htmlentities (~> 4.3.1)
nokogiri (>= 1.7.0, <= 2.0) nokogiri (>= 1.7.0, <= 2.0)
tzinfo (1.2.7) tzinfo (1.2.8)
thread_safe (~> 0.1) thread_safe (~> 0.1)
u2f (0.2.1) u2f (0.2.1)
uber (0.1.0) uber (0.1.0)
@ -1236,14 +1251,13 @@ GEM
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
yajl-ruby (1.4.1) yajl-ruby (1.4.1)
zeitwerk (2.4.0) zeitwerk (2.4.1)
PLATFORMS PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
RedCloth (~> 4.3.2) RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0)
acme-client (~> 2.0, >= 2.0.6) acme-client (~> 2.0, >= 2.0.6)
activerecord-explain-analyze (~> 0.1) activerecord-explain-analyze (~> 0.1)
acts-as-taggable-on (~> 6.0) acts-as-taggable-on (~> 6.0)
@ -1283,6 +1297,7 @@ DEPENDENCIES
connection_pool (~> 2.0) connection_pool (~> 2.0)
countries (~> 3.0) countries (~> 3.0)
creole (~> 0.5.0) creole (~> 0.5.0)
crystalball (~> 0.7.0)
danger (~> 8.0.6) danger (~> 8.0.6)
database_cleaner (~> 1.7.0) database_cleaner (~> 1.7.0)
deckar01-task_list (= 2.3.1) deckar01-task_list (= 2.3.1)
@ -1316,7 +1331,7 @@ DEPENDENCIES
fog-aliyun (~> 0.3) fog-aliyun (~> 0.3)
fog-aws (~> 3.5) fog-aws (~> 3.5)
fog-core (= 2.1.0) fog-core (= 2.1.0)
fog-google (~> 1.10) fog-google (~> 1.11)
fog-local (~> 0.6) fog-local (~> 0.6)
fog-openstack (~> 1.0) fog-openstack (~> 1.0)
fog-rackspace (~> 0.1.1) fog-rackspace (~> 0.1.1)
@ -1331,15 +1346,16 @@ DEPENDENCIES
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5) gitlab-chronic (~> 0.10.5)
gitlab-fog-azure-rm (~> 1.0) gitlab-fog-azure-rm (~> 1.0)
gitlab-labkit (= 0.12.2) gitlab-labkit (= 0.13.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
gitlab-mail_room (~> 0.0.7) gitlab-mail_room (~> 0.0.7)
gitlab-markup (~> 1.7.1) gitlab-markup (~> 1.7.1)
gitlab-net-dns (~> 0.9.1) gitlab-net-dns (~> 0.9.1)
gitlab-pg_query (~> 1.3)
gitlab-puma (~> 4.3.3.gitlab.2) gitlab-puma (~> 4.3.3.gitlab.2)
gitlab-puma_worker_killer (~> 0.1.1.gitlab.1) gitlab-puma_worker_killer (~> 0.1.1.gitlab.1)
gitlab-sidekiq-fetcher (= 0.5.2) gitlab-sidekiq-fetcher (= 0.5.2)
gitlab-styles (~> 4.3.0) gitlab-styles (~> 5.1.0)
gitlab_chronic_duration (~> 0.10.6.2) gitlab_chronic_duration (~> 0.10.6.2)
gitlab_omniauth-ldap (~> 2.1.1) gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2) gon (~> 6.2)
@ -1348,15 +1364,16 @@ DEPENDENCIES
gpgme (~> 2.0.19) gpgme (~> 2.0.19)
grape (= 1.4.0) grape (= 1.4.0)
grape-entity (~> 0.7.1) grape-entity (~> 0.7.1)
grape-path-helpers (~> 1.4) grape-path-helpers (~> 1.5)
grape_logging (~> 1.7) grape_logging (~> 1.7)
graphiql-rails (~> 1.4.10) graphiql-rails (~> 1.4.10)
graphlient (~> 0.4.0)
graphql (~> 1.11.4) graphql (~> 1.11.4)
graphql-docs (~> 1.6.0) graphql-docs (~> 1.6.0)
grpc (~> 1.30.2) grpc (~> 1.30.2)
gssapi gssapi
guard-rspec guard-rspec
haml_lint (~> 0.34.0) haml_lint (~> 0.36.0)
hamlit (~> 2.11.0) hamlit (~> 2.11.0)
hangouts-chat (~> 0.0.5) hangouts-chat (~> 0.0.5)
hashie hashie
@ -1429,10 +1446,10 @@ DEPENDENCIES
prometheus-client-mmap (~> 0.12.0) prometheus-client-mmap (~> 0.12.0)
pry-byebug (~> 3.9.0) pry-byebug (~> 3.9.0)
pry-rails (~> 0.3.9) pry-rails (~> 0.3.9)
rack (~> 2.1.4) rack (~> 2.2.3)
rack-attack (~> 6.3.0) rack-attack (~> 6.3.0)
rack-cors (~> 1.0.6) rack-cors (~> 1.0.6)
rack-oauth2 (~> 1.9.3) 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.1)
@ -1451,25 +1468,22 @@ DEPENDENCIES
request_store (~> 1.5) request_store (~> 1.5)
responders (~> 3.0) responders (~> 3.0)
retriable (~> 3.1.2) retriable (~> 3.1.2)
rouge (~> 3.24.0) rouge (~> 3.25.0)
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-parameterized rspec-parameterized
rspec-rails (~> 4.0.0) rspec-rails (~> 4.0.0)
rspec-retry (~> 0.6.1) rspec-retry (~> 0.6.1)
rspec_junit_formatter rspec_junit_formatter
rspec_profiling (~> 0.0.5) rspec_profiling (~> 0.0.6)
rubocop (~> 0.82.0)
rubocop-performance (~> 1.5.2)
rubocop-rspec (~> 1.37.0)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 1.3.0) ruby-prof (~> 1.3.0)
ruby-progressbar ruby-progressbar
ruby_parser (~> 3.8) ruby_parser (~> 3.15)
rubyzip (~> 2.0.0) rubyzip (~> 2.0.0)
rugged (~> 0.28) rugged (~> 0.28)
sanitize (~> 5.2.1) sanitize (~> 5.2.1)
sassc-rails (~> 2.1.0) sassc-rails (~> 2.1.0)
scss_lint (~> 0.56.0) scss_lint (~> 0.59.0)
seed-fu (~> 2.3.7) seed-fu (~> 2.3.7)
selenium-webdriver (~> 3.142) selenium-webdriver (~> 3.142)
sentry-raven (~> 3.0) sentry-raven (~> 3.0)
@ -1482,7 +1496,7 @@ DEPENDENCIES
simplecov-cobertura (~> 1.3.1) simplecov-cobertura (~> 1.3.1)
slack-messenger (~> 2.3.4) slack-messenger (~> 2.3.4)
snowplow-tracker (~> 0.6.1) snowplow-tracker (~> 0.6.1)
spring (~> 2.0.0) spring (~> 2.1.0)
spring-commands-rspec (~> 1.0.4) spring-commands-rspec (~> 1.0.4)
sprockets (~> 3.7.0) sprockets (~> 3.7.0)
sshkey (~> 2.0) sshkey (~> 2.0)
@ -1510,4 +1524,4 @@ DEPENDENCIES
yajl-ruby (~> 1.4.1) yajl-ruby (~> 1.4.1)
BUNDLED WITH BUNDLED WITH
1.17.3 2.1.4

View file

@ -4,7 +4,7 @@
The canonical source of GitLab where all development takes place is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab). The canonical source of GitLab where all development takes place is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab).
If you wish to clone a copy of GitLab without proprietary code, you can use the read-only mirror of GitLab located at https://gitlab.com/gitlab-org/gitlab-foss/. Please do not submit any issues and/or merge requests to this project. If you wish to clone a copy of GitLab without proprietary code, you can use the read-only mirror of GitLab located at https://gitlab.com/gitlab-org/gitlab-foss/. However, please do not submit any issues and/or merge requests to that project.
## Free trial ## Free trial
@ -79,7 +79,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the
GitLab is a Ruby on Rails application that runs on the following software: 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.6.6 - Ruby (MRI) 2.7.2
- Git 2.24+ - Git 2.24+
- Redis 4.0+ - Redis 4.0+
- PostgreSQL 11+ - PostgreSQL 11+

View file

@ -1 +1 @@
13.5.7 13.6.5

View file

@ -1,14 +1,32 @@
<script> <script>
import { GlDatepicker } from '@gitlab/ui'; import { GlDatepicker, GlFormInput } from '@gitlab/ui';
export default { export default {
name: 'ExpiresAtField', name: 'ExpiresAtField',
components: { GlDatepicker }, components: { GlDatepicker, GlFormInput },
props: {
inputAttrs: {
type: Object,
required: false,
default: () => ({}),
},
},
data() {
return {
minDate: new Date(),
};
},
}; };
</script> </script>
<template> <template>
<gl-datepicker :target="null" :min-date="new Date()"> <gl-datepicker :target="null" :min-date="minDate">
<slot></slot> <gl-form-input
v-bind="inputAttrs"
class="datepicker gl-datepicker-input"
autocomplete="off"
inputmode="none"
data-qa-selector="expiry_date_field"
/>
</gl-datepicker> </gl-datepicker>
</template> </template>

View file

@ -1,11 +1,34 @@
import Vue from 'vue'; import Vue from 'vue';
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,
placeholder: input.placeholder,
};
};
const initExpiresAtField = () => { const initExpiresAtField = () => {
// eslint-disable-next-line no-new const el = document.querySelector('.js-access-tokens-expires-at');
new Vue({
el: document.querySelector('.js-access-tokens-expires-at'), if (!el) {
components: { ExpiresAtField }, return null;
}
const inputAttrs = getInputAttrs(el);
return new Vue({
el,
render(h) {
return h(ExpiresAtField, {
props: {
inputAttrs,
},
});
},
}); });
}; };

View file

@ -0,0 +1,2 @@
// EE-specific feature. Find the implementation in the `ee/`-folder
export default () => {};

View file

@ -0,0 +1,27 @@
import Vue from 'vue';
import UserCallout from '~/user_callout';
import UsagePingDisabled from './components/usage_ping_disabled.vue';
export default () => {
// eslint-disable-next-line no-new
new UserCallout();
const emptyStateContainer = document.getElementById('js-devops-empty-state');
if (!emptyStateContainer) return false;
const { emptyStateSvgPath, enableUsagePingLink, docsLink, isAdmin } = emptyStateContainer.dataset;
return new Vue({
el: emptyStateContainer,
provide: {
isAdmin: Boolean(isAdmin),
svgPath: emptyStateSvgPath,
primaryButtonPath: enableUsagePingLink,
docsLink,
},
render(h) {
return h(UsagePingDisabled);
},
});
};

View file

@ -30,7 +30,6 @@ import AlertSidebar from './alert_sidebar.vue';
import AlertMetrics from './alert_metrics.vue'; import AlertMetrics from './alert_metrics.vue';
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue'; import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
import AlertSummaryRow from './alert_summary_row.vue'; import AlertSummaryRow from './alert_summary_row.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
const containerEl = document.querySelector('.page-with-contextual-sidebar'); const containerEl = document.querySelector('.page-with-contextual-sidebar');
@ -77,7 +76,6 @@ export default {
SystemNote, SystemNote,
AlertMetrics, AlertMetrics,
}, },
mixins: [glFeatureFlagsMixin()],
inject: { inject: {
projectPath: { projectPath: {
default: '', default: '',
@ -150,13 +148,10 @@ export default {
}, },
}, },
environmentName() { environmentName() {
return this.shouldDisplayEnvironment && this.alert?.environment?.name; return this.alert?.environment?.name;
}, },
environmentPath() { environmentPath() {
return this.shouldDisplayEnvironment && this.alert?.environment?.path; return this.alert?.environment?.path;
},
shouldDisplayEnvironment() {
return this.glFeatures.exposeEnvironmentPathInAlertDetails;
}, },
}, },
mounted() { mounted() {

View file

@ -0,0 +1,221 @@
<script>
import Vue from 'vue';
import {
GlIcon,
GlFormInput,
GlDropdown,
GlDropdownItem,
GlSearchBoxByType,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
import { s__, __ } from '~/locale';
// Mocks will be removed when integrating with BE is ready
// data format is defined and will be the same as mocked (maybe with some minor changes)
// feature rollout plan - https://gitlab.com/gitlab-org/gitlab/-/issues/262707#note_442529171
import gitlabFieldsMock from './mocks/gitlabFields.json';
export const i18n = {
columns: {
gitlabKeyTitle: s__('AlertMappingBuilder|GitLab alert key'),
payloadKeyTitle: s__('AlertMappingBuilder|Payload alert key'),
fallbackKeyTitle: s__('AlertMappingBuilder|Define fallback'),
},
selectMappingKey: s__('AlertMappingBuilder|Select key'),
makeSelection: s__('AlertMappingBuilder|Make selection'),
fallbackTooltip: s__(
'AlertMappingBuilder|Title is a required field for alerts in GitLab. Should the payload field you specified not be available, specifiy which field we should use instead. ',
),
noResults: __('No matching results'),
};
export default {
i18n,
components: {
GlIcon,
GlFormInput,
GlDropdown,
GlDropdownItem,
GlSearchBoxByType,
},
directives: {
GlTooltip,
},
props: {
payloadFields: {
type: Array,
required: false,
default: () => [],
},
mapping: {
type: Array,
required: false,
default: () => [],
},
},
data() {
return {
gitlabFields: this.gitlabAlertFields,
};
},
inject: {
gitlabAlertFields: {
default: gitlabFieldsMock,
},
},
computed: {
mappingData() {
return this.gitlabFields.map(gitlabField => {
const mappingFields = this.payloadFields.filter(({ type }) =>
type.some(t => gitlabField.compatibleTypes.includes(t)),
);
const foundMapping = this.mapping.find(
({ alertFieldName }) => alertFieldName === gitlabField.name,
);
const { fallbackAlertPaths, payloadAlertPaths } = foundMapping || {};
return {
mapping: payloadAlertPaths,
fallback: fallbackAlertPaths,
searchTerm: '',
fallbackSearchTerm: '',
mappingFields,
...gitlabField,
};
});
},
},
methods: {
setMapping(gitlabKey, mappingKey, valueKey) {
const fieldIndex = this.gitlabFields.findIndex(field => field.name === gitlabKey);
const updatedField = { ...this.gitlabFields[fieldIndex], ...{ [valueKey]: mappingKey } };
Vue.set(this.gitlabFields, fieldIndex, updatedField);
},
setSearchTerm(search = '', searchFieldKey, gitlabKey) {
const fieldIndex = this.gitlabFields.findIndex(field => field.name === gitlabKey);
const updatedField = { ...this.gitlabFields[fieldIndex], ...{ [searchFieldKey]: search } };
Vue.set(this.gitlabFields, fieldIndex, updatedField);
},
filterFields(searchTerm = '', fields) {
const search = searchTerm.toLowerCase();
return fields.filter(field => field.label.toLowerCase().includes(search));
},
isSelected(fieldValue, mapping) {
return fieldValue === mapping;
},
selectedValue(name) {
return (
this.payloadFields.find(item => item.name === name)?.label ||
this.$options.i18n.makeSelection
);
},
getFieldValue({ label, type }) {
return `${label} (${type.join(__(' or '))})`;
},
noResults(searchTerm, fields) {
return !this.filterFields(searchTerm, fields).length;
},
},
};
</script>
<template>
<div class="gl-display-table gl-w-full gl-mt-5">
<div class="gl-display-table-row">
<h5 id="gitlabFieldsHeader" class="gl-display-table-cell gl-py-3 gl-pr-3">
{{ $options.i18n.columns.gitlabKeyTitle }}
</h5>
<h5 class="gl-display-table-cell gl-py-3 gl-pr-3">&nbsp;</h5>
<h5 id="parsedFieldsHeader" class="gl-display-table-cell gl-py-3 gl-pr-3">
{{ $options.i18n.columns.payloadKeyTitle }}
</h5>
<h5 id="fallbackFieldsHeader" class="gl-display-table-cell gl-py-3 gl-pr-3">
{{ $options.i18n.columns.fallbackKeyTitle }}
<gl-icon
v-gl-tooltip
name="question"
class="gl-text-gray-500"
:title="$options.i18n.fallbackTooltip"
/>
</h5>
</div>
<div
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 w-30p gl-vertical-align-middle">
<gl-form-input
aria-labelledby="gitlabFieldsHeader"
disabled
:value="getFieldValue(gitlabField)"
/>
</div>
<div class="gl-display-table-cell gl-py-3 gl-pr-3">
<div class="right-arrow" :class="{ 'gl-vertical-align-middle': index === 0 }">
<i class="right-arrow-head"></i>
</div>
</div>
<div class="gl-display-table-cell gl-py-3 gl-pr-3 w-30p gl-vertical-align-middle">
<gl-dropdown
:disabled="!gitlabField.mappingFields.length"
aria-labelledby="parsedFieldsHeader"
:text="selectedValue(gitlabField.mapping)"
class="gl-w-full"
:header-text="$options.i18n.selectMappingKey"
>
<gl-search-box-by-type @input="setSearchTerm($event, 'searchTerm', gitlabField.name)" />
<gl-dropdown-item
v-for="mappingField in filterFields(gitlabField.searchTerm, gitlabField.mappingFields)"
:key="`${mappingField.name}__mapping`"
:is-checked="isSelected(gitlabField.mapping, mappingField.name)"
is-check-item
@click="setMapping(gitlabField.name, mappingField.name, 'mapping')"
>
{{ mappingField.label }}
</gl-dropdown-item>
<gl-dropdown-item v-if="noResults(gitlabField.searchTerm, gitlabField.mappingFields)">
{{ $options.i18n.noResults }}
</gl-dropdown-item>
</gl-dropdown>
</div>
<div class="gl-display-table-cell gl-py-3 w-30p">
<gl-dropdown
v-if="Boolean(gitlabField.numberOfFallbacks)"
:disabled="!gitlabField.mappingFields.length"
aria-labelledby="fallbackFieldsHeader"
:text="selectedValue(gitlabField.fallback)"
class="gl-w-full"
:header-text="$options.i18n.selectMappingKey"
>
<gl-search-box-by-type
@input="setSearchTerm($event, 'fallbackSearchTerm', gitlabField.name)"
/>
<gl-dropdown-item
v-for="mappingField in filterFields(
gitlabField.fallbackSearchTerm,
gitlabField.mappingFields,
)"
:key="`${mappingField.name}__fallback`"
:is-checked="isSelected(gitlabField.fallback, mappingField.name)"
is-check-item
@click="setMapping(gitlabField.name, mappingField.name, 'fallback')"
>
{{ mappingField.label }}
</gl-dropdown-item>
<gl-dropdown-item
v-if="noResults(gitlabField.fallbackSearchTerm, gitlabField.mappingFields)"
>
{{ $options.i18n.noResults }}
</gl-dropdown-item>
</gl-dropdown>
</div>
</div>
</div>
</template>

View file

@ -0,0 +1,32 @@
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
export default {
components: {
GlLink,
GlSprintf,
},
props: {
message: {
type: String,
required: true,
},
link: {
type: String,
required: true,
},
},
};
</script>
<template>
<span>
<gl-sprintf :message="message">
<template #link="{ content }">
<gl-link class="gl-display-inline-block" :href="link" target="_blank">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</span>
</template>

View file

@ -1,8 +1,24 @@
<script> <script>
import { GlTable, GlIcon, GlTooltipDirective } from '@gitlab/ui'; import {
GlButtonGroup,
GlButton,
GlIcon,
GlLoadingIcon,
GlModal,
GlModalDirective,
GlTable,
GlTooltipDirective,
GlSprintf,
} from '@gitlab/ui';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import { trackAlertIntergrationsViewsOptions } from '../constants'; import {
trackAlertIntegrationsViewsOptions,
integrationToDeleteDefault,
typeSet,
} from '../constants';
import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
export const i18n = { export const i18n = {
title: s__('AlertsIntegrations|Current integrations'), title: s__('AlertsIntegrations|Current integrations'),
@ -24,23 +40,36 @@ const bodyTrClass =
export default { export default {
i18n, i18n,
typeSet,
components: { components: {
GlTable, GlButtonGroup,
GlButton,
GlIcon, GlIcon,
GlLoadingIcon,
GlModal,
GlTable,
GlSprintf,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective,
}, },
mixins: [glFeatureFlagsMixin()],
props: { props: {
integrations: { integrations: {
type: Array, type: Array,
required: false, required: false,
default: () => [], default: () => [],
}, },
loading: {
type: Boolean,
required: false,
default: false,
},
}, },
fields: [ fields: [
{ {
key: 'activated', key: 'active',
label: __('Status'), label: __('Status'),
}, },
{ {
@ -51,22 +80,56 @@ export default {
key: 'type', key: 'type',
label: __('Type'), label: __('Type'),
}, },
{
key: 'actions',
thClass: `gl-text-center`,
tdClass: `gl-text-center`,
label: __('Actions'),
},
], ],
computed: { apollo: {
tbodyTrClass() { currentIntegration: {
query: getCurrentIntegrationQuery,
},
},
data() {
return { return {
[bodyTrClass]: this.integrations.length, integrationToDelete: integrationToDeleteDefault,
currentIntegration: null,
}; };
}, },
},
mounted() { mounted() {
const callback = entries => {
const isVisible = entries.some(entry => entry.isIntersecting);
if (isVisible) {
this.trackPageViews(); this.trackPageViews();
this.observer.disconnect();
}
};
this.observer = new IntersectionObserver(callback);
this.observer.observe(this.$el);
}, },
methods: { methods: {
tbodyTrClass(item) {
return {
[bodyTrClass]: this.integrations.length,
'gl-bg-blue-50': (item !== null && item.id) === this.currentIntegration?.id,
};
},
trackPageViews() { trackPageViews() {
const { category, action } = trackAlertIntergrationsViewsOptions; const { category, action } = trackAlertIntegrationsViewsOptions;
Tracking.event(category, action); Tracking.event(category, action);
}, },
setIntegrationToDelete({ name, id }) {
this.integrationToDelete.id = id;
this.integrationToDelete.name = name;
},
deleteIntegration() {
this.$emit('delete-integration', { id: this.integrationToDelete.id });
this.integrationToDelete = { ...integrationToDeleteDefault };
},
}, },
}; };
</script> </script>
@ -75,15 +138,16 @@ export default {
<div class="incident-management-list"> <div class="incident-management-list">
<h5 class="gl-font-lg">{{ $options.i18n.title }}</h5> <h5 class="gl-font-lg">{{ $options.i18n.title }}</h5>
<gl-table <gl-table
:empty-text="$options.i18n.emptyState" class="integration-list"
:items="integrations" :items="integrations"
:fields="$options.fields" :fields="$options.fields"
:busy="loading"
stacked="md" stacked="md"
:tbody-tr-class="tbodyTrClass" :tbody-tr-class="tbodyTrClass"
show-empty show-empty
> >
<template #cell(activated)="{ item }"> <template #cell(active)="{ item }">
<span v-if="item.activated" data-testid="integration-activated-status"> <span v-if="item.active" data-testid="integration-activated-status">
<gl-icon <gl-icon
v-gl-tooltip v-gl-tooltip
name="check-circle-filled" name="check-circle-filled"
@ -104,6 +168,47 @@ export default {
{{ $options.i18n.status.disabled.name }} {{ $options.i18n.status.disabled.name }}
</span> </span>
</template> </template>
<template #cell(actions)="{ item }">
<gl-button-group v-if="glFeatures.httpIntegrationsList" class="gl-ml-3">
<gl-button icon="pencil" @click="$emit('edit-integration', { id: item.id })" />
<gl-button
v-gl-modal.deleteIntegration
:disabled="item.type === $options.typeSet.prometheus"
icon="remove"
@click="setIntegrationToDelete(item)"
/>
</gl-button-group>
</template>
<template #table-busy>
<gl-loading-icon size="lg" color="dark" class="mt-3" />
</template>
<template #empty>
<div
class="gl-border-t-solid gl-border-b-solid gl-border-1 gl-border gl-border-gray-100 mt-n3 gl-px-5"
>
<p class="gl-text-gray-400 gl-py-3 gl-my-3">{{ $options.i18n.emptyState }}</p>
</div>
</template>
</gl-table> </gl-table>
<gl-modal
modal-id="deleteIntegration"
:title="s__('AlertSettings|Delete integration')"
:ok-title="s__('AlertSettings|Delete integration')"
ok-variant="danger"
@ok="deleteIntegration"
>
<gl-sprintf
:message="
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.',
)
"
>
<template #integrationName>{{ integrationToDelete.name }}</template>
</gl-sprintf>
</gl-modal>
</div> </div>
</template> </template>

View file

@ -0,0 +1,661 @@
<script>
import {
GlButton,
GlCollapse,
GlForm,
GlFormGroup,
GlFormSelect,
GlFormInput,
GlFormInputGroup,
GlFormTextarea,
GlModal,
GlModalDirective,
GlToggle,
} from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import MappingBuilder from './alert_mapping_builder.vue';
import AlertSettingsFormHelpBlock from './alert_settings_form_help_block.vue';
import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
import service from '../services';
import {
integrationTypesNew,
JSON_VALIDATE_DELAY,
targetPrometheusUrlPlaceholder,
targetOpsgenieUrlPlaceholder,
typeSet,
sectionHash,
} from '../constants';
// Mocks will be removed when integrating with BE is ready
// data format is defined and will be the same as mocked (maybe with some minor changes)
// feature rollout plan - https://gitlab.com/gitlab-org/gitlab/-/issues/262707#note_442529171
import mockedCustomMapping from './mocks/parsedMapping.json';
export default {
placeholders: {
prometheus: targetPrometheusUrlPlaceholder,
opsgenie: targetOpsgenieUrlPlaceholder,
},
JSON_VALIDATE_DELAY,
typeSet,
i18n: {
integrationFormSteps: {
step1: {
label: s__('AlertSettings|1. Select integration type'),
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.',
),
},
step2: {
label: s__('AlertSettings|2. Name integration'),
placeholder: s__('AlertSettings|Enter integration name'),
},
step3: {
label: s__('AlertSettings|3. Set up webhook'),
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.",
),
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.',
),
info: s__('AlertSettings|Authorization key'),
reset: s__('AlertSettings|Reset Key'),
},
step4: {
label: s__('AlertSettings|4. Sample alert payload (optional)'),
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), or to test the integration (also optional).',
),
prometheusHelp: s__(
'AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to test the integration (optional).',
),
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'),
submitPayload: s__('AlertSettings|Submit payload'),
payloadParsedSucessMsg: s__(
'AlertSettings|Sample payload has been parsed. You can now map the fields.',
),
},
step5: {
label: s__('AlertSettings|5. Map fields (optional)'),
intro: s__(
"AlertSettings|If you've provided a sample alert payload, you can create a custom mapping for your endpoint. The default GitLab alert keys are listed below. Please define which payload key should map to the specified GitLab key.",
),
},
prometheusFormUrl: {
label: s__('AlertSettings|Prometheus API base URL'),
help: s__('AlertSettings|URL cannot be blank and must start with http or https'),
},
restKeyInfo: {
label: s__(
'AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.',
),
},
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
opsgenie: {
label: s__('AlertSettings|2. Add link to your Opsgenie alert list'),
info: s__(
'AlertSettings|Utilizing this option will link the GitLab Alerts navigation item to your existing Opsgenie instance. By selecting this option, you cannot receive alerts from any other source in GitLab; it will effectively be turning Alerts within GitLab off as a feature.',
),
},
},
},
components: {
ClipboardButton,
GlButton,
GlCollapse,
GlForm,
GlFormGroup,
GlFormInput,
GlFormInputGroup,
GlFormTextarea,
GlFormSelect,
GlModal,
GlToggle,
AlertSettingsFormHelpBlock,
MappingBuilder,
},
directives: {
GlModal: GlModalDirective,
},
inject: {
generic: {
default: {},
},
prometheus: {
default: {},
},
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
opsgenie: {
default: {},
},
},
mixins: [glFeatureFlagsMixin()],
props: {
loading: {
type: Boolean,
required: true,
},
canAddIntegration: {
type: Boolean,
required: true,
},
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
canManageOpsgenie: {
type: Boolean,
required: false,
default: false,
},
},
apollo: {
currentIntegration: {
query: getCurrentIntegrationQuery,
},
},
data() {
return {
selectedIntegration: integrationTypesNew[0].value,
active: false,
formVisible: false,
integrationTestPayload: {
json: null,
error: null,
},
resetSamplePayloadConfirmed: false,
customMapping: null,
parsingPayload: false,
currentIntegration: null,
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
isManagingOpsgenie: false,
};
},
computed: {
isPrometheus() {
return this.selectedIntegration === this.$options.typeSet.prometheus;
},
jsonIsValid() {
return this.integrationTestPayload.error === null;
},
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
disabledIntegrations() {
const options = [];
if (this.opsgenie.active) {
options.push(typeSet.http, typeSet.prometheus);
} else if (!this.canManageOpsgenie) {
options.push(typeSet.opsgenie);
}
return options;
},
options() {
return integrationTypesNew.map(el => ({
...el,
disabled: this.disabledIntegrations.includes(el.value),
}));
},
selectedIntegrationType() {
switch (this.selectedIntegration) {
case typeSet.http:
return this.generic;
case typeSet.prometheus:
return this.prometheus;
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
case typeSet.opsgenie:
return this.opsgenie;
default:
return {};
}
},
integrationForm() {
return {
name: this.currentIntegration?.name || '',
active: this.currentIntegration?.active || false,
token: this.currentIntegration?.token || this.selectedIntegrationType.token,
url: this.currentIntegration?.url || this.selectedIntegrationType.url,
apiUrl: this.currentIntegration?.apiUrl || '',
};
},
testAlertPayload() {
return {
data: this.integrationTestPayload.json,
endpoint: this.integrationForm.url,
token: this.integrationForm.token,
};
},
showMappingBuilder() {
return (
this.glFeatures.multipleHttpIntegrationsCustomMapping &&
this.selectedIntegration === typeSet.http
);
},
mappingBuilderFields() {
return this.customMapping?.samplePayload?.payloadAlerFields?.nodes;
},
mappingBuilderMapping() {
return this.customMapping?.storedMapping?.nodes;
},
hasSamplePayload() {
return Boolean(this.customMapping?.samplePayload);
},
canEditPayload() {
return this.hasSamplePayload && !this.resetSamplePayloadConfirmed;
},
isPayloadEditDisabled() {
return !this.active || this.canEditPayload;
},
},
watch: {
currentIntegration(val) {
if (val === null) {
return this.reset();
}
this.selectedIntegration = val.type;
this.active = val.active;
if (val.type === typeSet.http) this.getIntegrationMapping(val.id);
return this.integrationTypeSelect();
},
},
methods: {
integrationTypeSelect() {
if (this.selectedIntegration === integrationTypesNew[0].value) {
this.formVisible = false;
} else {
this.formVisible = true;
}
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
if (this.canManageOpsgenie && this.selectedIntegration === typeSet.opsgenie) {
this.isManagingOpsgenie = true;
this.active = this.opsgenie.active;
this.integrationForm.apiUrl = this.opsgenie.opsgenieMvcTargetUrl;
} else {
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
this.isManagingOpsgenie = false;
}
},
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
submitWithOpsgenie() {
return service
.updateGenericActive({
endpoint: this.opsgenie.formPath,
params: {
service: {
opsgenie_mvc_target_url: this.integrationForm.apiUrl,
opsgenie_mvc_enabled: this.active,
},
},
})
.then(() => {
window.location.hash = sectionHash;
window.location.reload();
});
},
submitWithTestPayload() {
return service
.updateTestAlert(this.testAlertPayload)
.then(() => {
this.submit();
})
.catch(() => {
this.$emit('test-payload-failure');
});
},
submit() {
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
if (this.isManagingOpsgenie) {
return this.submitWithOpsgenie();
}
const { name, apiUrl } = this.integrationForm;
const variables =
this.selectedIntegration === typeSet.http
? { name, active: this.active }
: { apiUrl, active: this.active };
const integrationPayload = { type: this.selectedIntegration, variables };
if (this.currentIntegration) {
return this.$emit('update-integration', integrationPayload);
}
return this.$emit('create-new-integration', integrationPayload);
},
reset() {
this.selectedIntegration = integrationTypesNew[0].value;
this.integrationTypeSelect();
if (this.currentIntegration) {
return this.$emit('clear-current-integration');
}
return this.resetFormValues();
},
resetFormValues() {
this.integrationForm.name = '';
this.integrationForm.apiUrl = '';
this.integrationTestPayload = {
json: null,
error: null,
};
this.active = false;
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
this.isManagingOpsgenie = false;
},
resetAuthKey() {
if (!this.currentIntegration) {
return;
}
this.$emit('reset-token', {
type: this.selectedIntegration,
variables: { id: this.currentIntegration.id },
});
},
validateJson() {
this.integrationTestPayload.error = null;
if (this.integrationTestPayload.json === '') {
return;
}
try {
JSON.parse(this.integrationTestPayload.json);
} catch (e) {
this.integrationTestPayload.error = JSON.stringify(e.message);
}
},
parseMapping() {
// TODO: replace with real BE mutation when ready;
this.parsingPayload = true;
return new Promise(resolve => {
setTimeout(() => resolve(mockedCustomMapping), 1000);
})
.then(res => {
const mapping = { ...res };
delete mapping.storedMapping;
this.customMapping = res;
this.integrationTestPayload.json = res?.samplePayload.body;
this.resetSamplePayloadConfirmed = false;
this.$toast.show(this.$options.i18n.integrationFormSteps.step4.payloadParsedSucessMsg);
})
.finally(() => {
this.parsingPayload = false;
});
},
getIntegrationMapping() {
// TODO: replace with real BE mutation when ready;
return Promise.resolve(mockedCustomMapping).then(res => {
this.customMapping = res;
this.integrationTestPayload.json = res?.samplePayload.body;
});
},
},
};
</script>
<template>
<gl-form class="gl-mt-6" @submit.prevent="submit" @reset.prevent="reset">
<h5 class="gl-font-lg gl-my-5">{{ s__('AlertSettings|Add new integrations') }}</h5>
<gl-form-group
id="integration-type"
:label="$options.i18n.integrationFormSteps.step1.label"
label-for="integration-type"
>
<gl-form-select
v-model="selectedIntegration"
:disabled="currentIntegration !== null || !canAddIntegration"
:options="options"
@change="integrationTypeSelect"
/>
<div v-if="!canAddIntegration" class="gl-my-4" data-testid="multi-integrations-not-supported">
<alert-settings-form-help-block
:message="$options.i18n.integrationFormSteps.step1.enterprise"
link="https://about.gitlab.com/pricing"
/>
</div>
</gl-form-group>
<gl-collapse v-model="formVisible" class="gl-mt-3">
<!-- TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657 -->
<div v-if="isManagingOpsgenie">
<gl-form-group
id="integration-webhook"
:label="$options.i18n.integrationFormSteps.opsgenie.label"
label-for="integration-webhook"
>
<span class="gl-my-4">
{{ $options.i18n.integrationFormSteps.opsgenie.info }}
</span>
<gl-toggle
v-model="active"
:is-loading="loading"
:label="__('Active')"
class="gl-my-4 gl-font-weight-normal"
/>
<gl-form-input
id="opsgenie-opsgenieMvcTargetUrl"
v-model="integrationForm.apiUrl"
type="text"
:placeholder="$options.placeholders.opsgenie"
/>
<span class="gl-text-gray-400 gl-my-1">
{{ $options.i18n.integrationFormSteps.prometheusFormUrl.help }}
</span>
</gl-form-group>
</div>
<div v-else>
<gl-form-group
id="name-integration"
:label="$options.i18n.integrationFormSteps.step2.label"
label-for="name-integration"
>
<gl-form-input
v-model="integrationForm.name"
type="text"
:placeholder="$options.i18n.integrationFormSteps.step2.placeholder"
/>
</gl-form-group>
<gl-form-group
id="integration-webhook"
:label="$options.i18n.integrationFormSteps.step3.label"
label-for="integration-webhook"
>
<alert-settings-form-help-block
:message="
isPrometheus
? $options.i18n.integrationFormSteps.step3.prometheusHelp
: $options.i18n.integrationFormSteps.step3.help
"
link="https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html"
/>
<gl-toggle
v-model="active"
:is-loading="loading"
:label="__('Active')"
class="gl-my-4 gl-font-weight-normal"
/>
<div v-if="isPrometheus" class="gl-my-4">
<span class="gl-font-weight-bold">
{{ $options.i18n.integrationFormSteps.prometheusFormUrl.label }}
</span>
<gl-form-input
id="integration-apiUrl"
v-model="integrationForm.apiUrl"
type="text"
:placeholder="$options.placeholders.prometheus"
/>
<span class="gl-text-gray-400">
{{ $options.i18n.integrationFormSteps.prometheusFormUrl.help }}
</span>
</div>
<div class="gl-my-4">
<span class="gl-font-weight-bold">
{{ s__('AlertSettings|Webhook URL') }}
</span>
<gl-form-input-group id="url" readonly :value="integrationForm.url">
<template #append>
<clipboard-button
:text="integrationForm.url || ''"
:title="__('Copy')"
class="gl-m-0!"
/>
</template>
</gl-form-input-group>
</div>
<div class="gl-my-4">
<span class="gl-font-weight-bold">
{{ $options.i18n.integrationFormSteps.step3.info }}
</span>
<gl-form-input-group
id="authorization-key"
class="gl-mb-3"
readonly
:value="integrationForm.token"
>
<template #append>
<clipboard-button
:text="integrationForm.token || ''"
:title="__('Copy')"
class="gl-m-0!"
/>
</template>
</gl-form-input-group>
<gl-button v-gl-modal.authKeyModal :disabled="!active">
{{ $options.i18n.integrationFormSteps.step3.reset }}
</gl-button>
<gl-modal
modal-id="authKeyModal"
:title="$options.i18n.integrationFormSteps.step3.reset"
:ok-title="$options.i18n.integrationFormSteps.step3.reset"
ok-variant="danger"
@ok="resetAuthKey"
>
{{ $options.i18n.integrationFormSteps.restKeyInfo.label }}
</gl-modal>
</div>
</gl-form-group>
<gl-form-group
id="test-integration"
:label="$options.i18n.integrationFormSteps.step4.label"
label-for="test-integration"
:class="{ 'gl-mb-0!': showMappingBuilder }"
:invalid-feedback="integrationTestPayload.error"
>
<alert-settings-form-help-block
:message="
isPrometheus || !showMappingBuilder
? $options.i18n.integrationFormSteps.step4.prometheusHelp
: $options.i18n.integrationFormSteps.step4.help
"
:link="generic.alertsUsageUrl"
/>
<gl-form-textarea
id="test-payload"
v-model.trim="integrationTestPayload.json"
:disabled="isPayloadEditDisabled"
:state="jsonIsValid"
:placeholder="$options.i18n.integrationFormSteps.step4.placeholder"
class="gl-my-3"
:debounce="$options.JSON_VALIDATE_DELAY"
rows="6"
max-rows="10"
@input="validateJson"
/>
</gl-form-group>
<template v-if="showMappingBuilder">
<gl-button
v-if="canEditPayload"
v-gl-modal.resetPayloadModal
data-testid="payload-action-btn"
:disabled="!active"
class="gl-mt-3"
>
{{ $options.i18n.integrationFormSteps.step4.editPayload }}
</gl-button>
<gl-button
v-else
data-testid="payload-action-btn"
:class="{ 'gl-mt-3': integrationTestPayload.error }"
:disabled="!active"
:loading="parsingPayload"
@click="parseMapping"
>
{{ $options.i18n.integrationFormSteps.step4.submitPayload }}
</gl-button>
<gl-modal
modal-id="resetPayloadModal"
:title="$options.i18n.integrationFormSteps.step4.resetHeader"
:ok-title="$options.i18n.integrationFormSteps.step4.resetOk"
ok-variant="danger"
@ok="resetSamplePayloadConfirmed = true"
>
{{ $options.i18n.integrationFormSteps.step4.resetBody }}
</gl-modal>
</template>
<gl-form-group
v-if="showMappingBuilder"
id="mapping-builder"
class="gl-mt-5"
:label="$options.i18n.integrationFormSteps.step5.label"
label-for="mapping-builder"
>
<span>{{ $options.i18n.integrationFormSteps.step5.intro }}</span>
<mapping-builder
:payload-fields="mappingBuilderFields"
:mapping="mappingBuilderMapping"
/>
</gl-form-group>
</div>
<div class="gl-display-flex gl-justify-content-start gl-py-3">
<gl-button
type="submit"
variant="success"
class="js-no-auto-disable"
data-testid="integration-form-submit"
>{{ s__('AlertSettings|Save integration') }}
</gl-button>
<!-- TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657 -->
<gl-button
v-if="!isManagingOpsgenie"
data-testid="integration-test-and-submit"
:disabled="Boolean(integrationTestPayload.error)"
category="secondary"
variant="success"
class="gl-mx-3 js-no-auto-disable"
@click="submitWithTestPayload"
>{{ s__('AlertSettings|Save and test payload') }}</gl-button
>
<gl-button
type="reset"
class="js-no-auto-disable"
:class="{ 'gl-ml-3': isManagingOpsgenie }"
>{{ __('Cancel') }}</gl-button
>
</div>
</gl-collapse>
</gl-form>
</template>

View file

@ -14,16 +14,14 @@ import {
GlFormSelect, GlFormSelect,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { s__ } from '~/locale';
import { doesHashExistInUrl } from '~/lib/utils/url_utility'; import { doesHashExistInUrl } from '~/lib/utils/url_utility';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ToggleButton from '~/vue_shared/components/toggle_button.vue'; import ToggleButton from '~/vue_shared/components/toggle_button.vue';
import IntegrationsList from './alerts_integrations_list.vue';
import csrf from '~/lib/utils/csrf'; import csrf from '~/lib/utils/csrf';
import service from '../services'; import service from '../services';
import { import {
i18n, i18n,
serviceOptions, integrationTypes,
JSON_VALIDATE_DELAY, JSON_VALIDATE_DELAY,
targetPrometheusUrlPlaceholder, targetPrometheusUrlPlaceholder,
targetOpsgenieUrlPlaceholder, targetOpsgenieUrlPlaceholder,
@ -50,7 +48,6 @@ export default {
GlSprintf, GlSprintf,
ClipboardButton, ClipboardButton,
ToggleButton, ToggleButton,
IntegrationsList,
}, },
directives: { directives: {
'gl-modal': GlModalDirective, 'gl-modal': GlModalDirective,
@ -59,10 +56,10 @@ export default {
data() { data() {
return { return {
loading: false, loading: false,
selectedEndpoint: serviceOptions[0].value, selectedIntegration: integrationTypes[0].value,
options: serviceOptions, options: integrationTypes,
active: false, active: false,
authKey: '', token: '',
targetUrl: '', targetUrl: '',
feedback: { feedback: {
variant: 'danger', variant: 'danger',
@ -91,34 +88,34 @@ export default {
]; ];
}, },
isPrometheus() { isPrometheus() {
return this.selectedEndpoint === 'prometheus'; return this.selectedIntegration === 'PROMETHEUS';
}, },
isOpsgenie() { isOpsgenie() {
return this.selectedEndpoint === 'opsgenie'; return this.selectedIntegration === 'OPSGENIE';
}, },
selectedService() { selectedIntegrationType() {
switch (this.selectedEndpoint) { switch (this.selectedIntegration) {
case 'generic': { case 'HTTP': {
return { return {
url: this.generic.url, url: this.generic.url,
authKey: this.generic.authorizationKey, token: this.generic.token,
activated: this.generic.activated, active: this.generic.active,
resetKey: this.resetKey.bind(this), resetKey: this.resetKey.bind(this),
}; };
} }
case 'prometheus': { case 'PROMETHEUS': {
return { return {
url: this.prometheus.prometheusUrl, url: this.prometheus.url,
authKey: this.prometheus.authorizationKey, token: this.prometheus.token,
activated: this.prometheus.activated, active: this.prometheus.active,
resetKey: this.resetKey.bind(this, 'prometheus'), resetKey: this.resetKey.bind(this, 'PROMETHEUS'),
targetUrl: this.prometheus.prometheusApiUrl, targetUrl: this.prometheus.prometheusApiUrl,
}; };
} }
case 'opsgenie': { case 'OPSGENIE': {
return { return {
targetUrl: this.opsgenie.opsgenieMvcTargetUrl, targetUrl: this.opsgenie.opsgenieMvcTargetUrl,
activated: this.opsgenie.activated, active: this.opsgenie.active,
}; };
} }
default: { default: {
@ -152,43 +149,25 @@ export default {
? this.$options.targetOpsgenieUrlPlaceholder ? this.$options.targetOpsgenieUrlPlaceholder
: this.$options.targetPrometheusUrlPlaceholder; : this.$options.targetPrometheusUrlPlaceholder;
}, },
integrations() {
return [
{
name: s__('AlertSettings|HTTP endpoint'),
type: s__('AlertsIntegrations|HTTP endpoint'),
activated: this.generic.activated,
},
{
name: s__('AlertSettings|External Prometheus'),
type: s__('AlertsIntegrations|Prometheus'),
activated: this.prometheus.activated,
},
];
},
}, },
watch: { watch: {
'testAlert.json': debounce(function debouncedJsonValidate() { 'testAlert.json': debounce(function debouncedJsonValidate() {
this.validateJson(); this.validateJson();
}, JSON_VALIDATE_DELAY), }, JSON_VALIDATE_DELAY),
targetUrl(oldVal, newVal) { targetUrl(oldVal, newVal) {
if (newVal && oldVal !== this.selectedService.targetUrl) { if (newVal && oldVal !== this.selectedIntegrationType.targetUrl) {
this.canSaveForm = true; this.canSaveForm = true;
} }
}, },
}, },
mounted() { mounted() {
if ( if (this.prometheus.active || this.generic.active || !this.opsgenie.opsgenieMvcIsAvailable) {
this.prometheus.activated ||
this.generic.activated ||
!this.opsgenie.opsgenieMvcIsAvailable
) {
this.removeOpsGenieOption(); this.removeOpsGenieOption();
} else if (this.opsgenie.activated) { } else if (this.opsgenie.active) {
this.setOpsgenieAsDefault(); this.setOpsgenieAsDefault();
} }
this.active = this.selectedService.activated; this.active = this.selectedIntegrationType.active;
this.authKey = this.selectedService.authKey ?? ''; this.token = this.selectedIntegrationType.token ?? '';
}, },
methods: { methods: {
createUserErrorMessage(errors = {}) { createUserErrorMessage(errors = {}) {
@ -200,19 +179,19 @@ export default {
}, },
setOpsgenieAsDefault() { setOpsgenieAsDefault() {
this.options = this.options.map(el => { this.options = this.options.map(el => {
if (el.value !== 'opsgenie') { if (el.value !== 'OPSGENIE') {
return { ...el, disabled: true }; return { ...el, disabled: true };
} }
return { ...el, disabled: false }; return { ...el, disabled: false };
}); });
this.selectedEndpoint = this.options.find(({ value }) => value === 'opsgenie').value; this.selectedIntegration = this.options.find(({ value }) => value === 'OPSGENIE').value;
if (this.targetUrl === null) { if (this.targetUrl === null) {
this.targetUrl = this.selectedService.targetUrl; this.targetUrl = this.selectedIntegrationType.targetUrl;
} }
}, },
removeOpsGenieOption() { removeOpsGenieOption() {
this.options = this.options.map(el => { this.options = this.options.map(el => {
if (el.value !== 'opsgenie') { if (el.value !== 'OPSGENIE') {
return { ...el, disabled: false }; return { ...el, disabled: false };
} }
return { ...el, disabled: true }; return { ...el, disabled: true };
@ -220,8 +199,8 @@ export default {
}, },
resetFormValues() { resetFormValues() {
this.testAlert.json = null; this.testAlert.json = null;
this.targetUrl = this.selectedService.targetUrl; this.targetUrl = this.selectedIntegrationType.targetUrl;
this.active = this.selectedService.activated; this.active = this.selectedIntegrationType.active;
}, },
dismissFeedback() { dismissFeedback() {
this.serverError = null; this.serverError = null;
@ -229,12 +208,12 @@ export default {
this.isFeedbackDismissed = false; this.isFeedbackDismissed = false;
}, },
resetKey(key) { resetKey(key) {
const fn = key === 'prometheus' ? this.resetPrometheusKey() : this.resetGenericKey(); const fn = key === 'PROMETHEUS' ? this.resetPrometheusKey() : this.resetGenericKey();
return fn return fn
.then(({ data: { token } }) => { .then(({ data: { token } }) => {
this.authKey = token; this.token = token;
this.setFeedback({ feedbackMessage: this.$options.i18n.authKeyRest, variant: 'success' }); this.setFeedback({ feedbackMessage: this.$options.i18n.tokenRest, variant: 'success' });
}) })
.catch(() => { .catch(() => {
this.setFeedback({ feedbackMessage: this.$options.i18n.errorKeyMsg, variant: 'danger' }); this.setFeedback({ feedbackMessage: this.$options.i18n.errorKeyMsg, variant: 'danger' });
@ -259,9 +238,10 @@ export default {
}, },
toggleActivated(value) { toggleActivated(value) {
this.loading = true; this.loading = true;
const path = this.isOpsgenie ? this.opsgenie.formPath : this.generic.formPath;
return service return service
.updateGenericActive({ .updateGenericActive({
endpoint: this[this.selectedEndpoint].formPath, endpoint: path,
params: this.isOpsgenie params: this.isOpsgenie
? { service: { opsgenie_mvc_target_url: this.targetUrl, opsgenie_mvc_enabled: value } } ? { service: { opsgenie_mvc_target_url: this.targetUrl, opsgenie_mvc_enabled: value } }
: { service: { active: value } }, : { service: { active: value } },
@ -331,9 +311,9 @@ export default {
this.validateJson(); this.validateJson();
return service return service
.updateTestAlert({ .updateTestAlert({
endpoint: this.selectedService.url, endpoint: this.selectedIntegrationType.url,
data: this.testAlert.json, data: this.testAlert.json,
authKey: this.selectedService.authKey, token: this.selectedIntegrationType.token,
}) })
.then(() => { .then(() => {
this.setFeedback({ this.setFeedback({
@ -358,11 +338,11 @@ export default {
onReset() { onReset() {
this.testAlert.json = null; this.testAlert.json = null;
this.dismissFeedback(); this.dismissFeedback();
this.targetUrl = this.selectedService.targetUrl; this.targetUrl = this.selectedIntegrationType.targetUrl;
if (this.canSaveForm) { if (this.canSaveForm) {
this.canSaveForm = false; this.canSaveForm = false;
this.active = this.selectedService.activated; this.active = this.selectedIntegrationType.active;
} }
}, },
}, },
@ -370,9 +350,6 @@ export default {
</script> </script>
<template> <template>
<div>
<integrations-list :integrations="integrations" />
<gl-form @submit.prevent="onSubmit" @reset.prevent="onReset"> <gl-form @submit.prevent="onSubmit" @reset.prevent="onReset">
<h5 class="gl-font-lg gl-my-5">{{ $options.i18n.integrationsLabel }}</h5> <h5 class="gl-font-lg gl-my-5">{{ $options.i18n.integrationsLabel }}</h5>
@ -404,7 +381,7 @@ export default {
<gl-form-group label-for="integration-type" :label="$options.i18n.integration"> <gl-form-group label-for="integration-type" :label="$options.i18n.integration">
<gl-form-select <gl-form-select
id="integration-type" id="integration-type"
v-model="selectedEndpoint" v-model="selectedIntegration"
:options="options" :options="options"
data-testid="alert-settings-select" data-testid="alert-settings-select"
@change="resetFormValues" @change="resetFormValues"
@ -422,9 +399,9 @@ export default {
</gl-sprintf> </gl-sprintf>
</span> </span>
</gl-form-group> </gl-form-group>
<gl-form-group :label="$options.i18n.activeLabel" label-for="activated"> <gl-form-group :label="$options.i18n.activeLabel" label-for="active">
<toggle-button <toggle-button
id="activated" id="active"
:disabled-input="loading" :disabled-input="loading"
:is-loading="loading" :is-loading="loading"
:value="active" :value="active"
@ -449,10 +426,10 @@ export default {
</gl-form-group> </gl-form-group>
<template v-if="!isOpsgenie"> <template v-if="!isOpsgenie">
<gl-form-group :label="$options.i18n.urlLabel" label-for="url"> <gl-form-group :label="$options.i18n.urlLabel" label-for="url">
<gl-form-input-group id="url" readonly :value="selectedService.url"> <gl-form-input-group id="url" readonly :value="selectedIntegrationType.url">
<template #append> <template #append>
<clipboard-button <clipboard-button
:text="selectedService.url" :text="selectedIntegrationType.url"
:title="$options.i18n.copyToClipboard" :title="$options.i18n.copyToClipboard"
class="gl-m-0!" class="gl-m-0!"
/> />
@ -462,25 +439,25 @@ export default {
{{ prometheusInfo }} {{ prometheusInfo }}
</span> </span>
</gl-form-group> </gl-form-group>
<gl-form-group :label="$options.i18n.authKeyLabel" label-for="authorization-key"> <gl-form-group :label="$options.i18n.tokenLabel" label-for="authorization-key">
<gl-form-input-group id="authorization-key" class="gl-mb-2" readonly :value="authKey"> <gl-form-input-group id="authorization-key" class="gl-mb-2" readonly :value="token">
<template #append> <template #append>
<clipboard-button <clipboard-button
:text="authKey" :text="token"
:title="$options.i18n.copyToClipboard" :title="$options.i18n.copyToClipboard"
class="gl-m-0!" class="gl-m-0!"
/> />
</template> </template>
</gl-form-input-group> </gl-form-input-group>
<gl-button v-gl-modal.authKeyModal :disabled="!active" class="gl-mt-3">{{ <gl-button v-gl-modal.tokenModal :disabled="!active" class="gl-mt-3">{{
$options.i18n.resetKey $options.i18n.resetKey
}}</gl-button> }}</gl-button>
<gl-modal <gl-modal
modal-id="authKeyModal" modal-id="tokenModal"
:title="$options.i18n.resetKey" :title="$options.i18n.resetKey"
:ok-title="$options.i18n.resetKey" :ok-title="$options.i18n.resetKey"
ok-variant="danger" ok-variant="danger"
@ok="selectedService.resetKey" @ok="selectedIntegrationType.resetKey"
> >
{{ $options.i18n.restKeyInfo }} {{ $options.i18n.restKeyInfo }}
</gl-modal> </gl-modal>
@ -500,17 +477,13 @@ export default {
max-rows="10" max-rows="10"
/> />
</gl-form-group> </gl-form-group>
<gl-button :disabled="!canTestAlert" @click="validateTestAlert">{{ <gl-button :disabled="!canTestAlert" @click="validateTestAlert">{{
$options.i18n.testAlertInfo $options.i18n.testAlertInfo
}}</gl-button> }}</gl-button>
</template> </template>
<div class="footer-block row-content-block gl-display-flex gl-justify-content-space-between"> <div class="footer-block row-content-block gl-display-flex gl-justify-content-space-between">
<gl-button <gl-button variant="success" category="primary" :disabled="!canSaveConfig" @click="onSubmit">
variant="success"
category="primary"
:disabled="!canSaveConfig"
@click="onSubmit"
>
{{ __('Save changes') }} {{ __('Save changes') }}
</gl-button> </gl-button>
<gl-button category="primary" :disabled="!canSaveConfig" @click="onReset"> <gl-button category="primary" :disabled="!canSaveConfig" @click="onReset">
@ -518,5 +491,4 @@ export default {
</gl-button> </gl-button>
</div> </div>
</gl-form> </gl-form>
</div>
</template> </template>

View file

@ -0,0 +1,331 @@
<script>
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { fetchPolicies } from '~/lib/graphql';
import createFlash, { FLASH_TYPES } from '~/flash';
import getIntegrationsQuery from '../graphql/queries/get_integrations.query.graphql';
import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
import createHttpIntegrationMutation from '../graphql/mutations/create_http_integration.mutation.graphql';
import createPrometheusIntegrationMutation from '../graphql/mutations/create_prometheus_integration.mutation.graphql';
import updateHttpIntegrationMutation from '../graphql/mutations/update_http_integration.mutation.graphql';
import updatePrometheusIntegrationMutation from '../graphql/mutations/update_prometheus_integration.mutation.graphql';
import destroyHttpIntegrationMutation from '../graphql/mutations/destroy_http_integration.mutation.graphql';
import resetHttpTokenMutation from '../graphql/mutations/reset_http_token.mutation.graphql';
import resetPrometheusTokenMutation from '../graphql/mutations/reset_prometheus_token.mutation.graphql';
import updateCurrentIntergrationMutation from '../graphql/mutations/update_current_intergration.mutation.graphql';
import IntegrationsList from './alerts_integrations_list.vue';
import SettingsFormOld from './alerts_settings_form_old.vue';
import SettingsFormNew from './alerts_settings_form_new.vue';
import { typeSet } from '../constants';
import {
updateStoreAfterIntegrationDelete,
updateStoreAfterIntegrationAdd,
} from '../utils/cache_updates';
import {
DELETE_INTEGRATION_ERROR,
ADD_INTEGRATION_ERROR,
RESET_INTEGRATION_TOKEN_ERROR,
UPDATE_INTEGRATION_ERROR,
INTEGRATION_PAYLOAD_TEST_ERROR,
} from '../utils/error_messages';
export default {
typeSet,
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.'),
},
components: {
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
GlAlert,
GlLink,
GlSprintf,
IntegrationsList,
SettingsFormOld,
SettingsFormNew,
},
mixins: [glFeatureFlagsMixin()],
inject: {
generic: {
default: {},
},
prometheus: {
default: {},
},
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
opsgenie: {
default: {},
},
projectPath: {
default: '',
},
multiIntegrations: {
default: false,
},
},
apollo: {
integrations: {
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
query: getIntegrationsQuery,
variables() {
return {
projectPath: this.projectPath,
};
},
update(data) {
const { alertManagementIntegrations: { nodes: list = [] } = {} } = data.project || {};
return {
list,
};
},
error(err) {
createFlash({ message: err });
},
},
currentIntegration: {
query: getCurrentIntegrationQuery,
},
},
data() {
return {
isUpdating: false,
integrations: {},
currentIntegration: null,
};
},
computed: {
loading() {
return this.$apollo.queries.integrations.loading;
},
integrationsOptionsOld() {
return [
{
name: s__('AlertSettings|HTTP endpoint'),
type: s__('AlertsIntegrations|HTTP endpoint'),
active: this.generic.active,
},
{
name: s__('AlertSettings|External Prometheus'),
type: s__('AlertsIntegrations|Prometheus'),
active: this.prometheus.active,
},
];
},
canAddIntegration() {
return this.multiIntegrations || this.integrations?.list?.length < 2;
},
canManageOpsgenie() {
return (
this.integrations?.list?.every(({ active }) => active === false) ||
this.integrations?.list?.length === 0
);
},
},
methods: {
createNewIntegration({ type, variables }) {
const { projectPath } = this;
this.isUpdating = true;
this.$apollo
.mutate({
mutation:
type === this.$options.typeSet.http
? createHttpIntegrationMutation
: createPrometheusIntegrationMutation,
variables: {
...variables,
projectPath,
},
update(store, { data }) {
updateStoreAfterIntegrationAdd(store, getIntegrationsQuery, data, { projectPath });
},
})
.then(({ data: { httpIntegrationCreate, prometheusIntegrationCreate } = {} } = {}) => {
const error = httpIntegrationCreate?.errors[0] || prometheusIntegrationCreate?.errors[0];
if (error) {
return createFlash({ message: error });
}
return createFlash({
message: this.$options.i18n.changesSaved,
type: FLASH_TYPES.SUCCESS,
});
})
.catch(() => {
createFlash({ message: ADD_INTEGRATION_ERROR });
})
.finally(() => {
this.isUpdating = false;
});
},
updateIntegration({ type, variables }) {
this.isUpdating = true;
this.$apollo
.mutate({
mutation:
type === this.$options.typeSet.http
? updateHttpIntegrationMutation
: updatePrometheusIntegrationMutation,
variables: {
...variables,
id: this.currentIntegration.id,
},
})
.then(({ data: { httpIntegrationUpdate, prometheusIntegrationUpdate } = {} } = {}) => {
const error = httpIntegrationUpdate?.errors[0] || prometheusIntegrationUpdate?.errors[0];
if (error) {
return createFlash({ message: error });
}
return createFlash({
message: this.$options.i18n.changesSaved,
type: FLASH_TYPES.SUCCESS,
});
})
.catch(() => {
createFlash({ message: UPDATE_INTEGRATION_ERROR });
})
.finally(() => {
this.isUpdating = false;
});
},
resetToken({ type, variables }) {
this.isUpdating = true;
this.$apollo
.mutate({
mutation:
type === this.$options.typeSet.http
? resetHttpTokenMutation
: resetPrometheusTokenMutation,
variables,
})
.then(
({ data: { httpIntegrationResetToken, prometheusIntegrationResetToken } = {} } = {}) => {
const error =
httpIntegrationResetToken?.errors[0] || prometheusIntegrationResetToken?.errors[0];
if (error) {
return createFlash({ message: error });
}
const integration =
httpIntegrationResetToken?.integration ||
prometheusIntegrationResetToken?.integration;
this.currentIntegration = integration;
return createFlash({
message: this.$options.i18n.changesSaved,
type: FLASH_TYPES.SUCCESS,
});
},
)
.catch(() => {
createFlash({ message: RESET_INTEGRATION_TOKEN_ERROR });
})
.finally(() => {
this.isUpdating = false;
});
},
editIntegration({ id }) {
const currentIntegration = this.integrations.list.find(integration => integration.id === id);
this.$apollo.mutate({
mutation: updateCurrentIntergrationMutation,
variables: {
id: currentIntegration.id,
name: currentIntegration.name,
active: currentIntegration.active,
token: currentIntegration.token,
type: currentIntegration.type,
url: currentIntegration.url,
apiUrl: currentIntegration.apiUrl,
},
});
},
deleteIntegration({ id }) {
const { projectPath } = this;
this.isUpdating = true;
this.$apollo
.mutate({
mutation: destroyHttpIntegrationMutation,
variables: {
id,
},
update(store, { data }) {
updateStoreAfterIntegrationDelete(store, getIntegrationsQuery, data, { projectPath });
},
})
.then(({ data: { httpIntegrationDestroy } = {} } = {}) => {
const error = httpIntegrationDestroy?.errors[0];
if (error) {
return createFlash({ message: error });
}
this.clearCurrentIntegration();
return createFlash({
message: this.$options.i18n.integrationRemoved,
type: FLASH_TYPES.SUCCESS,
});
})
.catch(() => {
createFlash({ message: DELETE_INTEGRATION_ERROR });
})
.finally(() => {
this.isUpdating = false;
});
},
clearCurrentIntegration() {
this.$apollo.mutate({
mutation: updateCurrentIntergrationMutation,
variables: {},
});
},
testPayloadFailure() {
createFlash({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
},
},
};
</script>
<template>
<div>
<!-- TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657 -->
<gl-alert v-if="opsgenie.active" :dismissible="false" variant="tip">
<gl-sprintf
:message="
s__(
'AlertSettings|We will soon be introducing the ability to create multiple unique HTTP endpoints. When this functionality is live, you will be able to configure an integration with Opsgenie to surface Opsgenie alerts in GitLab. This will replace the current Opsgenie integration which will be deprecated. %{linkStart}More Information%{linkEnd}',
)
"
>
<template #link="{ content }">
<gl-link
class="gl-display-inline-block"
href="https://gitlab.com/gitlab-org/gitlab/-/issues/273657"
target="_blank"
>{{ content }}</gl-link
>
</template>
</gl-sprintf>
</gl-alert>
<integrations-list
v-else
:integrations="glFeatures.httpIntegrationsList ? integrations.list : integrationsOptionsOld"
:loading="loading"
@edit-integration="editIntegration"
@delete-integration="deleteIntegration"
/>
<settings-form-new
v-if="glFeatures.httpIntegrationsList"
:loading="isUpdating"
:can-add-integration="canAddIntegration"
:can-manage-opsgenie="canManageOpsgenie"
@create-new-integration="createNewIntegration"
@update-integration="updateIntegration"
@reset-token="resetToken"
@clear-current-integration="clearCurrentIntegration"
@test-payload-failure="testPayloadFailure"
/>
<settings-form-old v-else />
</div>
</template>

View file

@ -0,0 +1,112 @@
[
{
"name": "title",
"label": "Title",
"type": [
"String"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
],
"numberOfFallbacks": 1
},
{
"name": "description",
"label": "Description",
"type": [
"String"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
]
},
{
"name": "startTime",
"label": "Start time",
"type": [
"DateTime"
],
"compatibleTypes": [
"Number",
"DateTime"
]
},
{
"name": "service",
"label": "Service",
"type": [
"String"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
]
},
{
"name": "monitoringTool",
"label": "Monitoring tool",
"type": [
"String"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
]
},
{
"name": "hosts",
"label": "Hosts",
"type": [
"String",
"Array"
],
"compatibleTypes": [
"String",
"Array",
"Number",
"DateTime"
]
},
{
"name": "severity",
"label": "Severity",
"type": [
"String"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
]
},
{
"name": "fingerprint",
"label": "Fingerprint",
"type": [
"String"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
]
},
{
"name": "environment",
"label": "Environment",
"type": [
"String"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
]
}
]

View file

@ -0,0 +1,121 @@
{
"samplePayload": {
"body": "{\n \"dashboardId\":1,\n \"evalMatches\":[\n {\n \"value\":1,\n \"metric\":\"Count\",\n \"tags\":{}\n }\n ],\n \"imageUrl\":\"https://grafana.com/static/assets/img/blog/mixed_styles.png\",\n \"message\":\"Notification Message\",\n \"orgId\":1,\n \"panelId\":2,\n \"ruleId\":1,\n \"ruleName\":\"Panel Title alert\",\n \"ruleUrl\":\"http://localhost:3000/d/hZ7BuVbWz/test-dashboard?fullscreen\\u0026edit\\u0026tab=alert\\u0026panelId=2\\u0026orgId=1\",\n \"state\":\"alerting\",\n \"tags\":{\n \"tag name\":\"tag value\"\n },\n \"title\":\"[Alerting] Panel Title alert\"\n}\n",
"payloadAlerFields": {
"nodes": [
{
"name": "dashboardId",
"label": "Dashboard Id",
"type": [
"Number"
]
},
{
"name": "evalMatches",
"label": "Eval Matches",
"type": [
"Array"
]
},
{
"name": "createdAt",
"label": "Created At",
"type": [
"DateTime"
]
},
{
"name": "imageUrl",
"label": "Image Url",
"type": [
"String"
]
},
{
"name": "message",
"label": "Message",
"type": [
"String"
]
},
{
"name": "orgId",
"label": "Org Id",
"type": [
"Number"
]
},
{
"name": "panelId",
"label": "Panel Id",
"type": [
"String"
]
},
{
"name": "ruleId",
"label": "Rule Id",
"type": [
"Number"
]
},
{
"name": "ruleName",
"label": "Rule Name",
"type": [
"String"
]
},
{
"name": "ruleUrl",
"label": "Rule Url",
"type": [
"String"
]
},
{
"name": "state",
"label": "State",
"type": [
"String"
]
},
{
"name": "title",
"label": "Title",
"type": [
"String"
]
},
{
"name": "tags",
"label": "Tags",
"type": [
"Object"
]
}
]
}
},
"storedMapping": {
"nodes": [
{
"alertFieldName": "title",
"payloadAlertPaths": "title",
"fallbackAlertPaths": "ruleUrl"
},
{
"alertFieldName": "description",
"payloadAlertPaths": "message"
},
{
"alertFieldName": "hosts",
"payloadAlertPaths": "evalMatches"
},
{
"alertFieldName": "startTime",
"payloadAlertPaths": "createdAt"
}
]
}
}

View file

@ -1,5 +1,6 @@
import { s__ } from '~/locale'; import { s__ } from '~/locale';
// TODO: Remove this as part of the form old removal
export const i18n = { export const i18n = {
usageSection: s__( usageSection: s__(
'AlertSettings|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.', 'AlertSettings|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.',
@ -17,11 +18,10 @@ export const i18n = {
changesSaved: s__('AlertSettings|Your integration was successfully updated.'), changesSaved: s__('AlertSettings|Your integration was successfully updated.'),
prometheusInfo: s__('AlertSettings|Add URL and auth key to your Prometheus config file'), prometheusInfo: s__('AlertSettings|Add URL and auth key to your Prometheus config file'),
integrationsInfo: s__( integrationsInfo: s__(
'AlertSettings|Learn more about our improvements for %{linkStart}integrations%{linkEnd}', 'AlertSettings|Learn more about our our upcoming %{linkStart}integrations%{linkEnd}',
), ),
resetKey: s__('AlertSettings|Reset key'), resetKey: s__('AlertSettings|Reset key'),
copyToClipboard: s__('AlertSettings|Copy'), copyToClipboard: s__('AlertSettings|Copy'),
integrationsLabel: s__('AlertSettings|Add new integrations'),
apiBaseUrlLabel: s__('AlertSettings|API URL'), apiBaseUrlLabel: s__('AlertSettings|API URL'),
authKeyLabel: s__('AlertSettings|Authorization key'), authKeyLabel: s__('AlertSettings|Authorization key'),
urlLabel: s__('AlertSettings|Webhook URL'), urlLabel: s__('AlertSettings|Webhook URL'),
@ -40,12 +40,26 @@ export const i18n = {
integration: s__('AlertSettings|Integration'), integration: s__('AlertSettings|Integration'),
}; };
export const serviceOptions = [ // TODO: Delete as part of old form removal in 13.6
{ value: 'generic', text: s__('AlertSettings|HTTP Endpoint') }, export const integrationTypes = [
{ value: 'prometheus', text: s__('AlertSettings|External Prometheus') }, { value: 'HTTP', text: s__('AlertSettings|HTTP Endpoint') },
{ value: 'opsgenie', text: s__('AlertSettings|Opsgenie') }, { value: 'PROMETHEUS', text: s__('AlertSettings|External Prometheus') },
{ value: 'OPSGENIE', text: s__('AlertSettings|Opsgenie') },
]; ];
export const integrationTypesNew = [
{ value: '', text: s__('AlertSettings|Select integration type') },
...integrationTypes,
];
export const typeSet = {
http: 'HTTP',
prometheus: 'PROMETHEUS',
opsgenie: 'OPSGENIE',
};
export const integrationToDeleteDefault = { id: null, name: '' };
export const JSON_VALIDATE_DELAY = 250; export const JSON_VALIDATE_DELAY = 250;
export const targetPrometheusUrlPlaceholder = 'http://prometheus.example.com/'; export const targetPrometheusUrlPlaceholder = 'http://prometheus.example.com/';
@ -56,9 +70,9 @@ export const sectionHash = 'js-alert-management-settings';
/* eslint-disable @gitlab/require-i18n-strings */ /* eslint-disable @gitlab/require-i18n-strings */
/** /**
* Tracks snowplow event when user views alerts intergration list * Tracks snowplow event when user views alerts integration list
*/ */
export const trackAlertIntergrationsViewsOptions = { export const trackAlertIntegrationsViewsOptions = {
category: 'Alert Intergrations', category: 'Alert Integrations',
action: 'view_alert_integrations_list', action: 'view_alert_integrations_list',
}; };

View file

@ -0,0 +1,44 @@
import Vue from 'vue';
import produce from 'immer';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import getCurrentIntegrationQuery from './graphql/queries/get_current_integration.query.graphql';
Vue.use(VueApollo);
const resolvers = {
Mutation: {
updateCurrentIntegration: (
_,
{ id = null, name, active, token, type, url, apiUrl },
{ cache },
) => {
const sourceData = cache.readQuery({ query: getCurrentIntegrationQuery });
const data = produce(sourceData, draftData => {
if (id === null) {
// eslint-disable-next-line no-param-reassign
draftData.currentIntegration = null;
} else {
// eslint-disable-next-line no-param-reassign
draftData.currentIntegration = {
id,
name,
active,
token,
type,
url,
apiUrl,
};
}
});
cache.writeQuery({ query: getCurrentIntegrationQuery, data });
},
},
};
export default new VueApollo({
defaultClient: createDefaultClient(resolvers, {
cacheConfig: {},
assumeImmutableResults: true,
}),
});

View file

@ -0,0 +1,9 @@
fragment IntegrationItem on AlertManagementIntegration {
id
type
active
name
url
token
apiUrl
}

View file

@ -0,0 +1,10 @@
#import "../fragments/integration_item.fragment.graphql"
mutation createHttpIntegration($projectPath: ID!, $name: String!, $active: Boolean!) {
httpIntegrationCreate(input: { projectPath: $projectPath, name: $name, active: $active }) {
errors
integration {
...IntegrationItem
}
}
}

View file

@ -0,0 +1,12 @@
#import "../fragments/integration_item.fragment.graphql"
mutation createPrometheusIntegration($projectPath: ID!, $apiUrl: String!, $active: Boolean!) {
prometheusIntegrationCreate(
input: { projectPath: $projectPath, apiUrl: $apiUrl, active: $active }
) {
errors
integration {
...IntegrationItem
}
}
}

View file

@ -0,0 +1,10 @@
#import "../fragments/integration_item.fragment.graphql"
mutation destroyHttpIntegration($id: ID!) {
httpIntegrationDestroy(input: { id: $id }) {
errors
integration {
...IntegrationItem
}
}
}

View file

@ -0,0 +1,10 @@
#import "../fragments/integration_item.fragment.graphql"
mutation resetHttpIntegrationToken($id: ID!) {
httpIntegrationResetToken(input: { id: $id }) {
errors
integration {
...IntegrationItem
}
}
}

View file

@ -0,0 +1,10 @@
#import "../fragments/integration_item.fragment.graphql"
mutation resetPrometheusIntegrationToken($id: ID!) {
prometheusIntegrationResetToken(input: { id: $id }) {
errors
integration {
...IntegrationItem
}
}
}

View file

@ -0,0 +1,19 @@
mutation updateCurrentIntegration(
$id: String
$name: String
$active: Boolean
$token: String
$type: String
$url: String
$apiUrl: String
) {
updateCurrentIntegration(
id: $id
name: $name
active: $active
token: $token
type: $type
url: $url
apiUrl: $apiUrl
) @client
}

View file

@ -0,0 +1,10 @@
#import "../fragments/integration_item.fragment.graphql"
mutation updateHttpIntegration($id: ID!, $name: String!, $active: Boolean!) {
httpIntegrationUpdate(input: { id: $id, name: $name, active: $active }) {
errors
integration {
...IntegrationItem
}
}
}

View file

@ -0,0 +1,10 @@
#import "../fragments/integration_item.fragment.graphql"
mutation updatePrometheusIntegration($id: ID!, $apiUrl: String!, $active: Boolean!) {
prometheusIntegrationUpdate(input: { id: $id, apiUrl: $apiUrl, active: $active }) {
errors
integration {
...IntegrationItem
}
}
}

View file

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

View file

@ -0,0 +1,11 @@
#import "../fragments/integration_item.fragment.graphql"
query getIntegrations($projectPath: ID!) {
project(fullPath: $projectPath) {
alertManagementIntegrations {
nodes {
...IntegrationItem
}
}
}
}

View file

@ -1,6 +1,15 @@
import Vue from 'vue'; import Vue from 'vue';
import { GlToast } from '@gitlab/ui';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import AlertSettingsForm from './components/alerts_settings_form.vue'; import AlertSettingsWrapper from './components/alerts_settings_wrapper.vue';
import apolloProvider from './graphql';
apolloProvider.clients.defaultClient.cache.writeData({
data: {
currentIntegration: null,
},
});
Vue.use(GlToast);
export default el => { export default el => {
if (!el) { if (!el) {
@ -24,20 +33,17 @@ export default el => {
opsgenieMvcFormPath, opsgenieMvcFormPath,
opsgenieMvcEnabled, opsgenieMvcEnabled,
opsgenieMvcTargetUrl, opsgenieMvcTargetUrl,
projectPath,
multiIntegrations,
} = el.dataset; } = el.dataset;
const genericActivated = parseBoolean(activatedStr);
const prometheusIsActivated = parseBoolean(prometheusActivated);
const opsgenieMvcActivated = parseBoolean(opsgenieMvcEnabled);
const opsgenieMvcIsAvailable = parseBoolean(opsgenieMvcAvailable);
return new Vue({ return new Vue({
el, el,
provide: { provide: {
prometheus: { prometheus: {
activated: prometheusIsActivated, active: parseBoolean(prometheusActivated),
prometheusUrl, url: prometheusUrl,
authorizationKey: prometheusAuthorizationKey, token: prometheusAuthorizationKey,
prometheusFormPath, prometheusFormPath,
prometheusResetKeyPath, prometheusResetKeyPath,
prometheusApiUrl, prometheusApiUrl,
@ -45,23 +51,26 @@ export default el => {
generic: { generic: {
alertsSetupUrl, alertsSetupUrl,
alertsUsageUrl, alertsUsageUrl,
activated: genericActivated, active: parseBoolean(activatedStr),
formPath, formPath,
authorizationKey, token: authorizationKey,
url, url,
}, },
opsgenie: { opsgenie: {
formPath: opsgenieMvcFormPath, formPath: opsgenieMvcFormPath,
activated: opsgenieMvcActivated, active: parseBoolean(opsgenieMvcEnabled),
opsgenieMvcTargetUrl, opsgenieMvcTargetUrl,
opsgenieMvcIsAvailable, opsgenieMvcIsAvailable: parseBoolean(opsgenieMvcAvailable),
}, },
projectPath,
multiIntegrations: parseBoolean(multiIntegrations),
}, },
apolloProvider,
components: { components: {
AlertSettingsForm, AlertSettingsWrapper,
}, },
render(createElement) { render(createElement) {
return createElement('alert-settings-form'); return createElement('alert-settings-wrapper');
}, },
}); });
}; };

View file

@ -2,6 +2,7 @@
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
export default { export default {
// TODO: All this code save updateTestAlert will be deleted as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/255501
updateGenericKey({ endpoint, params }) { updateGenericKey({ endpoint, params }) {
return axios.put(endpoint, params); return axios.put(endpoint, params);
}, },
@ -25,11 +26,11 @@ export default {
}, },
}); });
}, },
updateTestAlert({ endpoint, data, authKey }) { updateTestAlert({ endpoint, data, token }) {
return axios.post(endpoint, data, { return axios.post(endpoint, data, {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: `Bearer ${authKey}`, Authorization: `Bearer ${token}`,
}, },
}); });
}, },

View file

@ -0,0 +1,84 @@
import produce from 'immer';
import createFlash from '~/flash';
import { DELETE_INTEGRATION_ERROR, ADD_INTEGRATION_ERROR } from './error_messages';
const deleteIntegrationFromStore = (store, query, { httpIntegrationDestroy }, variables) => {
const integration = httpIntegrationDestroy?.integration;
if (!integration) {
return;
}
const sourceData = store.readQuery({
query,
variables,
});
const data = produce(sourceData, draftData => {
// eslint-disable-next-line no-param-reassign
draftData.project.alertManagementIntegrations.nodes = draftData.project.alertManagementIntegrations.nodes.filter(
({ id }) => id !== integration.id,
);
});
store.writeQuery({
query,
variables,
data,
});
};
const addIntegrationToStore = (
store,
query,
{ httpIntegrationCreate, prometheusIntegrationCreate },
variables,
) => {
const integration =
httpIntegrationCreate?.integration || prometheusIntegrationCreate?.integration;
if (!integration) {
return;
}
const sourceData = store.readQuery({
query,
variables,
});
const data = produce(sourceData, draftData => {
// eslint-disable-next-line no-param-reassign
draftData.project.alertManagementIntegrations.nodes = [
integration,
...draftData.project.alertManagementIntegrations.nodes,
];
});
store.writeQuery({
query,
variables,
data,
});
};
const onError = (data, message) => {
createFlash({ message });
throw new Error(data.errors);
};
export const hasErrors = ({ errors = [] }) => errors?.length;
export const updateStoreAfterIntegrationDelete = (store, query, data, variables) => {
if (hasErrors(data)) {
onError(data, DELETE_INTEGRATION_ERROR);
} else {
deleteIntegrationFromStore(store, query, data, variables);
}
};
export const updateStoreAfterIntegrationAdd = (store, query, data, variables) => {
if (hasErrors(data)) {
onError(data, ADD_INTEGRATION_ERROR);
} else {
addIntegrationToStore(store, query, data, variables);
}
};

View file

@ -0,0 +1,21 @@
import { s__ } from '~/locale';
export const DELETE_INTEGRATION_ERROR = s__(
'AlertsIntegrations|The integration could not be deleted. Please try again.',
);
export const ADD_INTEGRATION_ERROR = s__(
'AlertsIntegrations|The integration could not be added. Please try again.',
);
export const UPDATE_INTEGRATION_ERROR = s__(
'AlertsIntegrations|The current integration could not be updated. Please try again.',
);
export const RESET_INTEGRATION_TOKEN_ERROR = s__(
'AlertsIntegrations|The integration token could not be reset. Please try again.',
);
export const INTEGRATION_PAYLOAD_TEST_ERROR = s__(
'AlertsIntegrations|Integration payload is invalid. You can still save your changes.',
);

View file

@ -1,19 +1,23 @@
<script> <script>
import InstanceCounts from './instance_counts.vue'; import InstanceCounts from './instance_counts.vue';
import PipelinesChart from './pipelines_chart.vue'; import InstanceStatisticsCountChart from './instance_statistics_count_chart.vue';
import UsersChart from './users_chart.vue'; import UsersChart from './users_chart.vue';
import ProjectsAndGroupsChart from './projects_and_groups_chart.vue';
import ChartsConfig from './charts_config';
import { TODAY, TOTAL_DAYS_TO_SHOW, START_DATE } from '../constants'; import { TODAY, TOTAL_DAYS_TO_SHOW, START_DATE } from '../constants';
export default { export default {
name: 'InstanceStatisticsApp', name: 'InstanceStatisticsApp',
components: { components: {
InstanceCounts, InstanceCounts,
PipelinesChart, InstanceStatisticsCountChart,
UsersChart, UsersChart,
ProjectsAndGroupsChart,
}, },
TOTAL_DAYS_TO_SHOW, TOTAL_DAYS_TO_SHOW,
START_DATE, START_DATE,
TODAY, TODAY,
configs: ChartsConfig,
}; };
</script> </script>
@ -25,6 +29,20 @@ export default {
:end-date="$options.TODAY" :end-date="$options.TODAY"
:total-data-points="$options.TOTAL_DAYS_TO_SHOW" :total-data-points="$options.TOTAL_DAYS_TO_SHOW"
/> />
<pipelines-chart /> <projects-and-groups-chart
:start-date="$options.START_DATE"
:end-date="$options.TODAY"
:total-data-points="$options.TOTAL_DAYS_TO_SHOW"
/>
<instance-statistics-count-chart
v-for="chartOptions in $options.configs"
:key="chartOptions.chartTitle"
:queries="chartOptions.queries"
:x-axis-title="chartOptions.xAxisTitle"
:y-axis-title="chartOptions.yAxisTitle"
:load-chart-error-message="chartOptions.loadChartError"
:no-data-message="chartOptions.noDataMessage"
:chart-title="chartOptions.chartTitle"
/>
</div> </div>
</template> </template>

View file

@ -0,0 +1,87 @@
import { s__, __, sprintf } from '~/locale';
import query from '../graphql/queries/instance_count.query.graphql';
const noDataMessage = s__('InstanceStatistics|No data available.');
export default [
{
loadChartError: sprintf(
s__(
'InstanceStatistics|Could not load the pipelines chart. Please refresh the page to try again.',
),
),
noDataMessage,
chartTitle: s__('InstanceStatistics|Pipelines'),
yAxisTitle: s__('InstanceStatistics|Items'),
xAxisTitle: s__('InstanceStatistics|Month'),
queries: [
{
query,
title: s__('InstanceStatistics|Pipelines total'),
identifier: 'PIPELINES',
loadError: sprintf(
s__('InstanceStatistics|There was an error fetching the total pipelines'),
),
},
{
query,
title: s__('InstanceStatistics|Pipelines succeeded'),
identifier: 'PIPELINES_SUCCEEDED',
loadError: sprintf(
s__('InstanceStatistics|There was an error fetching the successful pipelines'),
),
},
{
query,
title: s__('InstanceStatistics|Pipelines failed'),
identifier: 'PIPELINES_FAILED',
loadError: sprintf(
s__('InstanceStatistics|There was an error fetching the failed pipelines'),
),
},
{
query,
title: s__('InstanceStatistics|Pipelines canceled'),
identifier: 'PIPELINES_CANCELED',
loadError: sprintf(
s__('InstanceStatistics|There was an error fetching the cancelled pipelines'),
),
},
{
query,
title: s__('InstanceStatistics|Pipelines skipped'),
identifier: 'PIPELINES_SKIPPED',
loadError: sprintf(
s__('InstanceStatistics|There was an error fetching the skipped pipelines'),
),
},
],
},
{
loadChartError: sprintf(
s__(
'InstanceStatistics|Could not load the issues and merge requests chart. Please refresh the page to try again.',
),
),
noDataMessage,
chartTitle: s__('InstanceStatistics|Issues & Merge Requests'),
yAxisTitle: s__('InstanceStatistics|Items'),
xAxisTitle: s__('InstanceStatistics|Month'),
queries: [
{
query,
title: __('Issues'),
identifier: 'ISSUES',
loadError: sprintf(s__('InstanceStatistics|There was an error fetching the issues')),
},
{
query,
title: __('Merge requests'),
identifier: 'MERGE_REQUESTS',
loadError: sprintf(
s__('InstanceStatistics|There was an error fetching the merge requests'),
),
},
],
},
];

View file

@ -56,7 +56,7 @@ export default {
<template> <template>
<metric-card <metric-card
:title="__('Instance Statistics')" :title="__('Usage Trends')"
:metrics="counts" :metrics="counts"
:is-loading="$apollo.queries.counts.loading" :is-loading="$apollo.queries.counts.loading"
class="gl-mt-4" class="gl-mt-4"

View file

@ -0,0 +1,206 @@
<script>
import { GlLineChart } from '@gitlab/ui/dist/charts';
import { GlAlert } from '@gitlab/ui';
import { some, every } from 'lodash';
import * as Sentry from '~/sentry/wrapper';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import {
differenceInMonths,
formatDateAsMonth,
getDayDifference,
} from '~/lib/utils/datetime_utility';
import { getAverageByMonth, getEarliestDate, generateDataKeys } from '../utils';
import { TODAY, START_DATE } from '../constants';
const QUERY_DATA_KEY = 'instanceStatisticsMeasurements';
export default {
name: 'InstanceStatisticsCountChart',
components: {
GlLineChart,
GlAlert,
ChartSkeletonLoader,
},
startDate: START_DATE,
endDate: TODAY,
props: {
chartTitle: {
type: String,
required: true,
},
loadChartErrorMessage: {
type: String,
required: true,
},
noDataMessage: {
type: String,
required: true,
},
xAxisTitle: {
type: String,
required: true,
},
yAxisTitle: {
type: String,
required: true,
},
queries: {
type: Array,
required: true,
},
},
data() {
return {
errors: { ...generateDataKeys(this.queries, '') },
...generateDataKeys(this.queries, []),
};
},
computed: {
errorMessages() {
return Object.values(this.errors);
},
isLoading() {
return some(this.$apollo.queries, query => query?.loading);
},
allQueriesFailed() {
return every(this.errorMessages, message => message.length);
},
hasLoadingErrors() {
return some(this.errorMessages, message => message.length);
},
errorMessage() {
// show the generic loading message if all requests fail
return this.allQueriesFailed ? this.loadChartErrorMessage : this.errorMessages.join('\n\n');
},
hasEmptyDataSet() {
return this.chartData.every(({ data }) => data.length === 0);
},
totalDaysToShow() {
return getDayDifference(this.$options.startDate, this.$options.endDate);
},
chartData() {
const options = { shouldRound: true };
return this.queries.map(({ identifier, title }) => ({
name: title,
data: getAverageByMonth(this[identifier]?.nodes, options),
}));
},
range() {
return {
min: this.$options.startDate,
max: this.$options.endDate,
};
},
chartOptions() {
const { endDate, startDate } = this.$options;
return {
xAxis: {
...this.range,
name: this.xAxisTitle,
type: 'time',
splitNumber: differenceInMonths(startDate, endDate) + 1,
axisLabel: {
interval: 0,
showMinLabel: false,
showMaxLabel: false,
align: 'right',
formatter: formatDateAsMonth,
},
},
yAxis: {
name: this.yAxisTitle,
},
};
},
},
created() {
this.queries.forEach(({ query, identifier, loadError }) => {
this.$apollo.addSmartQuery(identifier, {
query,
variables() {
return {
identifier,
first: this.totalDaysToShow,
after: null,
};
},
update(data) {
const { nodes = [], pageInfo } = data[QUERY_DATA_KEY] || {};
return {
nodes,
pageInfo,
};
},
result() {
const { pageInfo, nodes } = this[identifier];
if (pageInfo?.hasNextPage && this.calculateDaysToFetch(getEarliestDate(nodes)) > 0) {
this.fetchNextPage({
query: this.$apollo.queries[identifier],
errorMessage: loadError,
pageInfo,
identifier,
});
}
},
error(error) {
this.handleError({
message: loadError,
identifier,
error,
});
},
});
});
},
methods: {
calculateDaysToFetch(firstDataPointDate = null) {
return firstDataPointDate
? Math.max(0, getDayDifference(this.$options.startDate, new Date(firstDataPointDate)))
: 0;
},
handleError({ identifier, error, message }) {
this.loadingError = true;
this.errors = { ...this.errors, [identifier]: message };
Sentry.captureException(error);
},
fetchNextPage({ query, pageInfo, identifier, errorMessage }) {
query
.fetchMore({
variables: {
identifier,
first: this.calculateDaysToFetch(getEarliestDate(this[identifier].nodes)),
after: pageInfo.endCursor,
},
updateQuery: (previousResult, { fetchMoreResult }) => {
const { nodes, ...rest } = fetchMoreResult[QUERY_DATA_KEY];
const { nodes: previousNodes } = previousResult[QUERY_DATA_KEY];
return {
[QUERY_DATA_KEY]: { ...rest, nodes: [...previousNodes, ...nodes] },
};
},
})
.catch(error => this.handleError({ identifier, error, message: errorMessage }));
},
},
};
</script>
<template>
<div>
<h3>{{ chartTitle }}</h3>
<gl-alert v-if="hasLoadingErrors" variant="danger" :dismissible="false" class="gl-mt-3">
{{ errorMessage }}
</gl-alert>
<div v-if="!allQueriesFailed">
<chart-skeleton-loader v-if="isLoading" />
<gl-alert v-else-if="hasEmptyDataSet" variant="info" :dismissible="false" class="gl-mt-3">
{{ noDataMessage }}
</gl-alert>
<gl-line-chart
v-else
:option="chartOptions"
:include-legend-avg-max="true"
:data="chartData"
/>
</div>
</div>
</template>

View file

@ -1,215 +0,0 @@
<script>
import { GlLineChart } from '@gitlab/ui/dist/charts';
import { GlAlert } from '@gitlab/ui';
import { mapKeys, mapValues, pick, some, sum } from 'lodash';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import { s__ } from '~/locale';
import {
differenceInMonths,
formatDateAsMonth,
getDayDifference,
} from '~/lib/utils/datetime_utility';
import { getAverageByMonth, sortByDate, extractValues } from '../utils';
import pipelineStatsQuery from '../graphql/queries/pipeline_stats.query.graphql';
import { TODAY, START_DATE } from '../constants';
const DATA_KEYS = [
'pipelinesTotal',
'pipelinesSucceeded',
'pipelinesFailed',
'pipelinesCanceled',
'pipelinesSkipped',
];
const PREFIX = 'pipelines';
export default {
name: 'PipelinesChart',
components: {
GlLineChart,
GlAlert,
ChartSkeletonLoader,
},
startDate: START_DATE,
endDate: TODAY,
i18n: {
loadPipelineChartError: s__(
'InstanceAnalytics|Could not load the pipelines chart. Please refresh the page to try again.',
),
noDataMessage: s__('InstanceAnalytics|There is no data available.'),
total: s__('InstanceAnalytics|Total'),
succeeded: s__('InstanceAnalytics|Succeeded'),
failed: s__('InstanceAnalytics|Failed'),
canceled: s__('InstanceAnalytics|Canceled'),
skipped: s__('InstanceAnalytics|Skipped'),
chartTitle: s__('InstanceAnalytics|Pipelines'),
yAxisTitle: s__('InstanceAnalytics|Items'),
xAxisTitle: s__('InstanceAnalytics|Month'),
},
data() {
return {
loading: true,
loadingError: null,
};
},
apollo: {
pipelineStats: {
query: pipelineStatsQuery,
variables() {
return {
firstTotal: this.totalDaysToShow,
firstSucceeded: this.totalDaysToShow,
firstFailed: this.totalDaysToShow,
firstCanceled: this.totalDaysToShow,
firstSkipped: this.totalDaysToShow,
};
},
update(data) {
const allData = extractValues(data, DATA_KEYS, PREFIX, 'nodes');
const allPageInfo = extractValues(data, DATA_KEYS, PREFIX, 'pageInfo');
return {
...mapValues(allData, sortByDate),
...allPageInfo,
};
},
result() {
if (this.hasNextPage) {
this.fetchNextPage();
}
},
error() {
this.handleError();
},
},
},
computed: {
isLoading() {
return this.$apollo.queries.pipelineStats.loading;
},
totalDaysToShow() {
return getDayDifference(this.$options.startDate, this.$options.endDate);
},
firstVariables() {
const allData = pick(this.pipelineStats, [
'nodesTotal',
'nodesSucceeded',
'nodesFailed',
'nodesCanceled',
'nodesSkipped',
]);
const allDayDiffs = mapValues(allData, data => {
const firstdataPoint = data[0];
if (!firstdataPoint) {
return 0;
}
return Math.max(
0,
getDayDifference(this.$options.startDate, new Date(firstdataPoint.recordedAt)),
);
});
return mapKeys(allDayDiffs, (value, key) => key.replace('nodes', 'first'));
},
cursorVariables() {
const pageInfoKeys = [
'pageInfoTotal',
'pageInfoSucceeded',
'pageInfoFailed',
'pageInfoCanceled',
'pageInfoSkipped',
];
return extractValues(this.pipelineStats, pageInfoKeys, 'pageInfo', 'endCursor');
},
hasNextPage() {
return (
sum(Object.values(this.firstVariables)) > 0 &&
some(this.pipelineStats, ({ hasNextPage }) => hasNextPage)
);
},
hasEmptyDataSet() {
return this.chartData.every(({ data }) => data.length === 0);
},
chartData() {
const allData = pick(this.pipelineStats, [
'nodesTotal',
'nodesSucceeded',
'nodesFailed',
'nodesCanceled',
'nodesSkipped',
]);
const options = { shouldRound: true };
return Object.keys(allData).map(key => {
const i18nName = key.slice('nodes'.length).toLowerCase();
return {
name: this.$options.i18n[i18nName],
data: getAverageByMonth(allData[key], options),
};
});
},
range() {
return {
min: this.$options.startDate,
max: this.$options.endDate,
};
},
chartOptions() {
const { endDate, startDate, i18n } = this.$options;
return {
xAxis: {
...this.range,
name: i18n.xAxisTitle,
type: 'time',
splitNumber: differenceInMonths(startDate, endDate) + 1,
axisLabel: {
interval: 0,
showMinLabel: false,
showMaxLabel: false,
align: 'right',
formatter: formatDateAsMonth,
},
},
yAxis: {
name: i18n.yAxisTitle,
},
};
},
},
methods: {
handleError() {
this.loadingError = true;
},
fetchNextPage() {
this.$apollo.queries.pipelineStats
.fetchMore({
variables: {
...this.firstVariables,
...this.cursorVariables,
},
updateQuery: (previousResult, { fetchMoreResult }) => {
return Object.keys(fetchMoreResult).reduce((memo, key) => {
const { nodes, ...rest } = fetchMoreResult[key];
const previousNodes = previousResult[key].nodes;
return { ...memo, [key]: { ...rest, nodes: [...previousNodes, ...nodes] } };
}, {});
},
})
.catch(this.handleError);
},
},
};
</script>
<template>
<div>
<h3>{{ $options.i18n.chartTitle }}</h3>
<gl-alert v-if="loadingError" variant="danger" :dismissible="false" class="gl-mt-3">
{{ this.$options.i18n.loadPipelineChartError }}
</gl-alert>
<chart-skeleton-loader v-else-if="isLoading" />
<gl-alert v-else-if="hasEmptyDataSet" variant="info" :dismissible="false" class="gl-mt-3">
{{ $options.i18n.noDataMessage }}
</gl-alert>
<gl-line-chart v-else :option="chartOptions" :include-legend-avg-max="true" :data="chartData" />
</div>
</template>

View file

@ -0,0 +1,224 @@
<script>
import { GlAlert } from '@gitlab/ui';
import { GlLineChart } from '@gitlab/ui/dist/charts';
import produce from 'immer';
import { sortBy } from 'lodash';
import * as Sentry from '~/sentry/wrapper';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import { s__, __ } from '~/locale';
import { formatDateAsMonth } from '~/lib/utils/datetime_utility';
import latestGroupsQuery from '../graphql/queries/groups.query.graphql';
import latestProjectsQuery from '../graphql/queries/projects.query.graphql';
import { getAverageByMonth } from '../utils';
const sortByDate = data => sortBy(data, item => new Date(item[0]).getTime());
const averageAndSortData = (data = [], maxDataPoints) => {
const averaged = getAverageByMonth(
data.length > maxDataPoints ? data.slice(0, maxDataPoints) : data,
{ shouldRound: true },
);
return sortByDate(averaged);
};
export default {
name: 'ProjectsAndGroupsChart',
components: { GlAlert, GlLineChart, ChartSkeletonLoader },
props: {
startDate: {
type: Date,
required: true,
},
endDate: {
type: Date,
required: true,
},
totalDataPoints: {
type: Number,
required: true,
},
},
data() {
return {
loadingError: false,
errorMessage: '',
groups: [],
projects: [],
groupsPageInfo: null,
projectsPageInfo: null,
};
},
apollo: {
groups: {
query: latestGroupsQuery,
variables() {
return {
first: this.totalDataPoints,
after: null,
};
},
update(data) {
return data.groups?.nodes || [];
},
result({ data }) {
const {
groups: { pageInfo },
} = data;
this.groupsPageInfo = pageInfo;
this.fetchNextPage({
query: this.$apollo.queries.groups,
pageInfo: this.groupsPageInfo,
dataKey: 'groups',
errorMessage: this.$options.i18n.loadGroupsDataError,
});
},
error(error) {
this.handleError({
message: this.$options.i18n.loadGroupsDataError,
error,
dataKey: 'groups',
});
},
},
projects: {
query: latestProjectsQuery,
variables() {
return {
first: this.totalDataPoints,
after: null,
};
},
update(data) {
return data.projects?.nodes || [];
},
result({ data }) {
const {
projects: { pageInfo },
} = data;
this.projectsPageInfo = pageInfo;
this.fetchNextPage({
query: this.$apollo.queries.projects,
pageInfo: this.projectsPageInfo,
dataKey: 'projects',
errorMessage: this.$options.i18n.loadProjectsDataError,
});
},
error(error) {
this.handleError({
message: this.$options.i18n.loadProjectsDataError,
error,
dataKey: 'projects',
});
},
},
},
i18n: {
yAxisTitle: s__('InstanceStatistics|Total projects & groups'),
xAxisTitle: __('Month'),
loadChartError: s__(
'InstanceStatistics|Could not load the projects and groups chart. Please refresh the page to try again.',
),
loadProjectsDataError: s__('InstanceStatistics|There was an error while loading the projects'),
loadGroupsDataError: s__('InstanceStatistics|There was an error while loading the groups'),
noDataMessage: s__('InstanceStatistics|No data available.'),
},
computed: {
isLoadingGroups() {
return this.$apollo.queries.groups.loading || this.groupsPageInfo?.hasNextPage;
},
isLoadingProjects() {
return this.$apollo.queries.projects.loading || this.projectsPageInfo?.hasNextPage;
},
isLoading() {
return this.isLoadingProjects && this.isLoadingGroups;
},
groupChartData() {
return averageAndSortData(this.groups, this.totalDataPoints);
},
projectChartData() {
return averageAndSortData(this.projects, this.totalDataPoints);
},
hasNoData() {
const { projectChartData, groupChartData } = this;
return Boolean(!projectChartData.length && !groupChartData.length);
},
options() {
return {
xAxis: {
name: this.$options.i18n.xAxisTitle,
type: 'category',
axisLabel: {
formatter: value => {
return formatDateAsMonth(value);
},
},
},
yAxis: {
name: this.$options.i18n.yAxisTitle,
},
};
},
chartData() {
return [
{
name: s__('InstanceStatistics|Total projects'),
data: this.projectChartData,
},
{
name: s__('InstanceStatistics|Total groups'),
data: this.groupChartData,
},
];
},
},
methods: {
handleError({ error, message = this.$options.i18n.loadChartError, dataKey = null }) {
this.loadingError = true;
this.errorMessage = message;
if (!dataKey) {
this.projects = [];
this.groups = [];
} else {
this[dataKey] = [];
}
Sentry.captureException(error);
},
fetchNextPage({ pageInfo, query, dataKey, errorMessage }) {
if (pageInfo?.hasNextPage) {
query
.fetchMore({
variables: { first: this.totalDataPoints, after: pageInfo.endCursor },
updateQuery: (previousResult, { fetchMoreResult }) => {
const results = produce(fetchMoreResult, newData => {
// eslint-disable-next-line no-param-reassign
newData[dataKey].nodes = [
...previousResult[dataKey].nodes,
...newData[dataKey].nodes,
];
});
return results;
},
})
.catch(error => {
this.handleError({ error, message: errorMessage, dataKey });
});
}
},
},
};
</script>
<template>
<div>
<h3>{{ $options.i18n.yAxisTitle }}</h3>
<chart-skeleton-loader v-if="isLoading" />
<gl-alert v-else-if="hasNoData" variant="info" :dismissible="false" class="gl-mt-3">
{{ $options.i18n.noDataMessage }}
</gl-alert>
<div v-else>
<gl-alert v-if="loadingError" variant="danger" :dismissible="false" class="gl-mt-3">{{
errorMessage
}}</gl-alert>
<gl-line-chart :option="options" :include-legend-avg-max="true" :data="chartData" />
</div>
</div>
</template>

View file

@ -0,0 +1,13 @@
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
#import "../fragments/count.fragment.graphql"
query getGroupsCount($first: Int, $after: String) {
groups: instanceStatisticsMeasurements(identifier: GROUPS, first: $first, after: $after) {
nodes {
...Count
}
pageInfo {
...PageInfo
}
}
}

View file

@ -0,0 +1,13 @@
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
#import "../fragments/count.fragment.graphql"
query getCount($identifier: MeasurementIdentifier!, $first: Int, $after: String) {
instanceStatisticsMeasurements(identifier: $identifier, first: $first, after: $after) {
nodes {
...Count
}
pageInfo {
...PageInfo
}
}
}

View file

@ -1,76 +0,0 @@
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
#import "./count.fragment.graphql"
query pipelineStats(
$firstTotal: Int
$firstSucceeded: Int
$firstFailed: Int
$firstCanceled: Int
$firstSkipped: Int
$endCursorTotal: String
$endCursorSucceeded: String
$endCursorFailed: String
$endCursorCanceled: String
$endCursorSkipped: String
) {
pipelinesTotal: instanceStatisticsMeasurements(
identifier: PIPELINES
first: $firstTotal
after: $endCursorTotal
) {
nodes {
...Count
}
pageInfo {
...PageInfo
}
}
pipelinesSucceeded: instanceStatisticsMeasurements(
identifier: PIPELINES_SUCCEEDED
first: $firstSucceeded
after: $endCursorSucceeded
) {
nodes {
...Count
}
pageInfo {
...PageInfo
}
}
pipelinesFailed: instanceStatisticsMeasurements(
identifier: PIPELINES_FAILED
first: $firstFailed
after: $endCursorFailed
) {
nodes {
...Count
}
pageInfo {
...PageInfo
}
}
pipelinesCanceled: instanceStatisticsMeasurements(
identifier: PIPELINES_CANCELED
first: $firstCanceled
after: $endCursorCanceled
) {
nodes {
...Count
}
pageInfo {
...PageInfo
}
}
pipelinesSkipped: instanceStatisticsMeasurements(
identifier: PIPELINES_SKIPPED
first: $firstSkipped
after: $endCursorSkipped
) {
nodes {
...Count
}
pageInfo {
...PageInfo
}
}
}

View file

@ -0,0 +1,13 @@
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
#import "../fragments/count.fragment.graphql"
query getProjectsCount($first: Int, $after: String) {
projects: instanceStatisticsMeasurements(identifier: PROJECTS, first: $first, after: $after) {
nodes {
...Count
}
pageInfo {
...PageInfo
}
}
}

View file

@ -1,5 +1,5 @@
import { masks } from 'dateformat'; import { masks } from 'dateformat';
import { mapKeys, mapValues, pick, sortBy } from 'lodash'; import { get } from 'lodash';
import { formatDate } from '~/lib/utils/datetime_utility'; import { formatDate } from '~/lib/utils/datetime_utility';
const { isoDate } = masks; const { isoDate } = masks;
@ -41,29 +41,28 @@ export function getAverageByMonth(items = [], options = {}) {
} }
/** /**
* Extracts values given a data set and a set of keys * Takes an array of instance counts and returns the last item in the list
* @example * @param {Array} arr array of instance counts in the form { count: Number, recordedAt: date String }
* const data = { fooBar: { baz: 'quis' }, ignored: 'ignored' }; * @return {String} the 'recordedAt' value of the earliest item
* extractValues(data, ['fooBar'], 'foo', 'baz') => { bazBar: 'quis' }
* @param {Object} data set to extract values from
* @param {Array} dataKeys keys describing where to look for values in the data set
* @param {String} replaceKey name key to be replaced in the data set
* @param {String} nestedKey key nested in the data set to be extracted,
* this is also used to rename the newly created data set
* @return {Object} the newly created data set with the extracted values
*/ */
export function extractValues(data, dataKeys = [], replaceKey, nestedKey) { export const getEarliestDate = (arr = []) => {
return mapKeys(pick(mapValues(data, nestedKey), dataKeys), (value, key) => const len = arr.length;
key.replace(replaceKey, nestedKey), return get(arr, `[${len - 1}].recordedAt`, null);
); };
}
/** /**
* Creates a new array of items sorted by the date string of each item * Takes an array of queries and produces an object with the query identifier as key
* @param {Array} items [description] * and a supplied defaultValue as its value
* @param {String} items[0] date string * @param {Array} queries array of chart query configs,
* @return {Array} the new sorted array. * see ./analytics/instance_statistics/components/charts_config.js
* @param {any} defaultValue value to set each identifier to
* @return {Object} key value pair of the form { queryIdentifier: defaultValue }
*/ */
export function sortByDate(items = []) { export const generateDataKeys = (queries, defaultValue) =>
return sortBy(items, ({ recordedAt }) => new Date(recordedAt).getTime()); queries.reduce(
} (acc, { identifier }) => ({
...acc,
[identifier]: defaultValue,
}),
{},
);

View file

@ -17,10 +17,13 @@ export default {
}, },
}, },
computed: { computed: {
seriesData() { barSeriesData() {
return { return [
full: this.formattedData.keys.map((val, idx) => [val, this.formattedData.values[idx]]), {
}; name: 'full',
data: this.formattedData.keys.map((val, idx) => [val, this.formattedData.values[idx]]),
},
];
}, },
}, },
}; };
@ -30,7 +33,7 @@ export default {
<div class="gl-xs-w-full"> <div class="gl-xs-w-full">
<gl-column-chart <gl-column-chart
v-if="formattedData.keys" v-if="formattedData.keys"
:data="seriesData" :bars="barSeriesData"
:x-axis-title="__('Value')" :x-axis-title="__('Value')"
:y-axis-title="__('Number of events')" :y-axis-title="__('Number of events')"
:x-axis-type="'category'" :x-axis-type="'category'"

View file

@ -43,7 +43,7 @@ export default {
}; };
</script> </script>
<template> <template>
<gl-card> <gl-card class="gl-mb-5">
<template #header> <template #header>
<strong ref="title">{{ title }}</strong> <strong ref="title">{{ title }}</strong>
</template> </template>

View file

@ -6,6 +6,7 @@ import { __ } from '~/locale';
const DEFAULT_PER_PAGE = 20; const DEFAULT_PER_PAGE = 20;
const Api = { const Api = {
DEFAULT_PER_PAGE,
groupsPath: '/api/:version/groups.json', groupsPath: '/api/:version/groups.json',
groupPath: '/api/:version/groups/:id', groupPath: '/api/:version/groups/:id',
groupMembersPath: '/api/:version/groups/:id/members', groupMembersPath: '/api/:version/groups/:id/members',
@ -22,6 +23,7 @@ const Api = {
projectLabelsPath: '/:namespace_path/:project_path/-/labels', projectLabelsPath: '/:namespace_path/:project_path/-/labels',
projectFileSchemaPath: '/:namespace_path/:project_path/-/schema/:ref/:filename', projectFileSchemaPath: '/:namespace_path/:project_path/-/schema/:ref/:filename',
projectUsersPath: '/api/:version/projects/:id/users', projectUsersPath: '/api/:version/projects/:id/users',
projectMembersPath: '/api/:version/projects/:id/members',
projectMergeRequestsPath: '/api/:version/projects/:id/merge_requests', projectMergeRequestsPath: '/api/:version/projects/:id/merge_requests',
projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid', projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes', projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
@ -34,6 +36,7 @@ const Api = {
mergeRequestsPath: '/api/:version/merge_requests', mergeRequestsPath: '/api/:version/merge_requests',
groupLabelsPath: '/groups/:namespace_path/-/labels', groupLabelsPath: '/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',
projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key', projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key',
projectTemplatesPath: '/api/:version/projects/:id/templates/:type', projectTemplatesPath: '/api/:version/projects/:id/templates/:type',
userCountsPath: '/api/:version/user_counts', userCountsPath: '/api/:version/user_counts',
@ -70,6 +73,7 @@ const Api = {
featureFlagUserLists: '/api/:version/projects/:id/feature_flags_user_lists', featureFlagUserLists: '/api/:version/projects/:id/feature_flags_user_lists',
featureFlagUserList: '/api/:version/projects/:id/feature_flags_user_lists/:list_iid', featureFlagUserList: '/api/:version/projects/:id/feature_flags_user_lists/:list_iid',
billableGroupMembersPath: '/api/:version/groups/:id/billable_members', billableGroupMembersPath: '/api/:version/groups/:id/billable_members',
containerRegistryDetailsPath: '/api/:version/registry/repositories/:id/',
group(groupId, callback = () => {}) { group(groupId, callback = () => {}) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId); const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
@ -106,6 +110,11 @@ const Api = {
return axios.delete(url); return axios.delete(url);
}, },
containerRegistryDetails(registryId, options = {}) {
const url = Api.buildUrl(this.containerRegistryDetailsPath).replace(':id', registryId);
return axios.get(url, options);
},
groupMembers(id, options) { groupMembers(id, options) {
const url = Api.buildUrl(this.groupMembersPath).replace(':id', encodeURIComponent(id)); const url = Api.buildUrl(this.groupMembersPath).replace(':id', encodeURIComponent(id));
@ -207,6 +216,12 @@ const Api = {
.then(({ data }) => data); .then(({ data }) => data);
}, },
inviteProjectMembers(id, data) {
const url = Api.buildUrl(this.projectMembersPath).replace(':id', encodeURIComponent(id));
return axios.post(url, data);
},
// Return single project // Return single project
project(projectPath) { project(projectPath) {
const url = Api.buildUrl(Api.projectPath).replace(':id', encodeURIComponent(projectPath)); const url = Api.buildUrl(Api.projectPath).replace(':id', encodeURIComponent(projectPath));
@ -454,17 +469,38 @@ const Api = {
}, },
issueTemplate(namespacePath, projectPath, key, type, callback) { issueTemplate(namespacePath, projectPath, key, type, callback) {
const url = Api.buildUrl(Api.issuableTemplatePath) const url = this.buildIssueTemplateUrl(
.replace(':key', encodeURIComponent(key)) Api.issuableTemplatePath,
.replace(':type', type) type,
.replace(':project_path', projectPath) projectPath,
.replace(':namespace_path', namespacePath); namespacePath,
).replace(':key', encodeURIComponent(key));
return axios return axios
.get(url) .get(url)
.then(({ data }) => callback(null, data)) .then(({ data }) => callback(null, data))
.catch(callback); .catch(callback);
}, },
issueTemplates(namespacePath, projectPath, type, callback) {
const url = this.buildIssueTemplateUrl(
Api.issuableTemplatesPath,
type,
projectPath,
namespacePath,
);
return axios
.get(url)
.then(({ data }) => callback(null, data))
.catch(callback);
},
buildIssueTemplateUrl(path, type, projectPath, namespacePath) {
return Api.buildUrl(path)
.replace(':type', type)
.replace(':project_path', projectPath)
.replace(':namespace_path', namespacePath);
},
users(query, options) { users(query, options) {
const url = Api.buildUrl(this.usersPath); const url = Api.buildUrl(this.usersPath);
return axios.get(url, { return axios.get(url, {
@ -530,12 +566,13 @@ const Api = {
}); });
}, },
postUserStatus({ emoji, message }) { postUserStatus({ emoji, message, availability }) {
const url = Api.buildUrl(this.userPostStatusPath); const url = Api.buildUrl(this.userPostStatusPath);
return axios.put(url, { return axios.put(url, {
emoji, emoji,
message, message,
availability,
}); });
}, },
@ -610,12 +647,12 @@ const Api = {
return axios.get(url); return axios.get(url);
}, },
pipelineJobs(projectId, pipelineId) { pipelineJobs(projectId, pipelineId, params) {
const url = Api.buildUrl(this.pipelineJobsPath) const url = Api.buildUrl(this.pipelineJobsPath)
.replace(':id', encodeURIComponent(projectId)) .replace(':id', encodeURIComponent(projectId))
.replace(':pipeline_id', encodeURIComponent(pipelineId)); .replace(':pipeline_id', encodeURIComponent(pipelineId));
return axios.get(url); return axios.get(url, { params });
}, },
// Return all pipelines for a project or filter by query params // Return all pipelines for a project or filter by query params
@ -737,6 +774,12 @@ const Api = {
return axios.get(url, { params: { page } }); return axios.get(url, { params: { page } });
}, },
searchFeatureFlagUserLists(id, search) {
const url = Api.buildUrl(this.featureFlagUserLists).replace(':id', id);
return axios.get(url, { params: { search } });
},
createFeatureFlagUserList(id, list) { createFeatureFlagUserList(id, list) {
const url = Api.buildUrl(this.featureFlagUserLists).replace(':id', id); const url = Api.buildUrl(this.featureFlagUserLists).replace(':id', id);

View file

@ -5,11 +5,11 @@ import { uniq } from 'lodash';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { __ } from './locale'; import { __ } from './locale';
import { updateTooltipTitle } from './lib/utils/common_utils';
import { isInVueNoteablePage } from './lib/utils/dom_utils'; import { isInVueNoteablePage } from './lib/utils/dom_utils';
import { deprecatedCreateFlash as flash } from './flash'; import { deprecatedCreateFlash as flash } from './flash';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import * as Emoji from '~/emoji'; import * as Emoji from '~/emoji';
import { dispose, fixTitle } from '~/tooltips';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
@ -374,7 +374,7 @@ export class AwardsHandler {
counter.text(counterNumber - 1); counter.text(counterNumber - 1);
this.removeYouFromUserList($emojiButton); this.removeYouFromUserList($emojiButton);
} else if (emoji === 'thumbsup' || emoji === 'thumbsdown') { } else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
$emojiButton.tooltip('dispose'); dispose($emojiButton);
counter.text('0'); counter.text('0');
this.removeYouFromUserList($emojiButton); this.removeYouFromUserList($emojiButton);
if ($emojiButton.parents('.note').length) { if ($emojiButton.parents('.note').length) {
@ -387,7 +387,8 @@ export class AwardsHandler {
} }
removeEmoji($emojiButton) { removeEmoji($emojiButton) {
$emojiButton.tooltip('dispose'); dispose($emojiButton);
$emojiButton.remove(); $emojiButton.remove();
const $votesBlock = this.getVotesBlock(); const $votesBlock = this.getVotesBlock();
if ($votesBlock.find('.js-emoji-btn').length === 0) { if ($votesBlock.find('.js-emoji-btn').length === 0) {
@ -415,13 +416,17 @@ export class AwardsHandler {
const originalTitle = this.getAwardTooltip(awardBlock); const originalTitle = this.getAwardTooltip(awardBlock);
const authors = originalTitle.split(FROM_SENTENCE_REGEX); const authors = originalTitle.split(FROM_SENTENCE_REGEX);
authors.splice(authors.indexOf('You'), 1); authors.splice(authors.indexOf('You'), 1);
return awardBlock
awardBlock
.closest('.js-emoji-btn') .closest('.js-emoji-btn')
.removeData('title') .removeData('title')
.removeAttr('data-title') .removeAttr('data-title')
.removeAttr('data-original-title') .removeAttr('data-original-title')
.attr('title', this.toSentence(authors)) .attr('title', this.toSentence(authors));
.tooltip('_fixTitle');
fixTitle(awardBlock);
return awardBlock;
} }
addYouToUserList(votesBlock, emoji) { addYouToUserList(votesBlock, emoji) {
@ -432,7 +437,12 @@ export class AwardsHandler {
users = origTitle.trim().split(FROM_SENTENCE_REGEX); users = origTitle.trim().split(FROM_SENTENCE_REGEX);
} }
users.unshift('You'); users.unshift('You');
return awardBlock.attr('title', this.toSentence(users)).tooltip('_fixTitle');
awardBlock.attr('title', this.toSentence(users));
fixTitle(awardBlock);
return awardBlock;
} }
createAwardButtonForVotesBlock(votesBlock, emojiName) { createAwardButtonForVotesBlock(votesBlock, emojiName) {
@ -448,7 +458,7 @@ export class AwardsHandler {
.find('.emoji-icon') .find('.emoji-icon')
.data('name', emojiName); .data('name', emojiName);
this.animateEmoji($emojiButton); this.animateEmoji($emojiButton);
$('.award-control').tooltip();
votesBlock.removeClass('current'); votesBlock.removeClass('current');
} }
@ -487,17 +497,6 @@ export class AwardsHandler {
return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`); return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`);
} }
userAuthored($emojiButton) {
const oldTitle = this.getAwardTooltip($emojiButton);
const newTitle = 'You cannot vote on your own issue, MR and note';
updateTooltipTitle($emojiButton, newTitle).tooltip('show');
// Restore tooltip back to award list
return setTimeout(() => {
$emojiButton.tooltip('hide');
updateTooltipTitle($emojiButton, oldTitle);
}, 2800);
}
scrollToAwards() { scrollToAwards() {
const options = { const options = {
scrollTop: $('.awards').offset().top - 110, scrollTop: $('.awards').offset().top - 110,

View file

@ -1,5 +1,5 @@
<script> <script>
import { GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui'; import { GlLoadingIcon, GlTooltipDirective, GlIcon, GlButton } from '@gitlab/ui';
export default { export default {
// 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
@ -8,6 +8,7 @@ export default {
components: { components: {
GlIcon, GlIcon,
GlLoadingIcon, GlLoadingIcon,
GlButton,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
@ -90,15 +91,16 @@ export default {
</div> </div>
</div> </div>
<button <gl-button
v-show="hasError" v-show="hasError"
v-gl-tooltip.hover v-gl-tooltip.hover
:title="s__('Badges|Reload badge image')" :title="s__('Badges|Reload badge image')"
class="btn btn-transparent btn-sm text-primary" category="tertiary"
variant="success"
type="button" type="button"
icon="retry"
size="small"
@click="reloadImage" @click="reloadImage"
> />
<gl-icon :size="16" name="retry" />
</button>
</div> </div>
</template> </template>

View file

@ -1,32 +0,0 @@
<script>
import DraftNote from './draft_note.vue';
export default {
components: {
DraftNote,
},
props: {
draft: {
type: Object,
required: true,
},
diffFile: {
type: Object,
required: true,
},
line: {
type: Object,
required: false,
default: null,
},
},
};
</script>
<template>
<tr class="notes_holder js-temp-notes-holder">
<td class="notes-content" colspan="4">
<div class="content"><draft-note :draft="draft" :diff-file="diffFile" :line="line" /></div>
</td>
</tr>
</template>

View file

@ -1,49 +0,0 @@
<script>
import { mapGetters } from 'vuex';
import DraftNote from './draft_note.vue';
export default {
components: {
DraftNote,
},
props: {
line: {
type: Object,
required: true,
},
diffFileContentSha: {
type: String,
required: true,
},
},
computed: {
...mapGetters('batchComments', ['draftForLine']),
className() {
return this.leftDraft > 0 || this.rightDraft > 0 ? '' : 'js-temp-notes-holder';
},
leftDraft() {
return this.draftForLine(this.diffFileContentSha, this.line, 'left');
},
rightDraft() {
return this.draftForLine(this.diffFileContentSha, this.line, 'right');
},
},
};
</script>
<template>
<tr :class="className" class="notes_holder">
<td class="notes_line old"></td>
<td class="notes-content parallel old" colspan="2">
<div v-if="leftDraft.isDraft" class="content">
<draft-note :draft="leftDraft" :line="line.left" />
</div>
</td>
<td class="notes_line new"></td>
<td class="notes-content parallel new" colspan="2">
<div v-if="rightDraft.isDraft" class="content">
<draft-note :draft="rightDraft" :line="line.right" />
</div>
</td>
</tr>
</template>

View file

@ -1,7 +1,9 @@
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,
@ -54,6 +56,10 @@ export default {
let title = __('Mark as resolved'); let title = __('Mark as resolved');
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 });
} }

View file

@ -79,3 +79,33 @@ export default function initCopyToClipboard() {
clipboardData.setData('text/x-gfm', json.gfm); clipboardData.setData('text/x-gfm', json.gfm);
}); });
} }
/**
* Programmatically triggers a click event on a
* "copy to clipboard" button, causing its
* contents to be copied. Handles some of the messiniess
* around managing the button's tooltip.
* @param {HTMLElement} btnElement
*/
export function clickCopyToClipboardButton(btnElement) {
const $btnElement = $(btnElement);
// Ensure the button has already been tooltip'd.
// If the use hasn't yet interacted (i.e. hovered or clicked)
// with the button, Bootstrap hasn't yet initialized
// the tooltip, and its `data-original-title` will be `undefined`.
// This value is used in the functions above.
$btnElement.tooltip();
btnElement.dispatchEvent(new MouseEvent('mouseover'));
btnElement.click();
// Manually trigger the necessary events to hide the
// button's tooltip and allow the button to perform its
// tooltip cleanup (updating the title from "Copied" back
// to its original title, "Copy branch name").
setTimeout(() => {
btnElement.dispatchEvent(new MouseEvent('mouseout'));
$btnElement.tooltip('hide');
}, 2000);
}

View file

@ -1,28 +0,0 @@
import $ from 'jquery';
$(() => {
$('body').on('click', '.js-details-target', function target() {
$(this)
.closest('.js-details-container')
.toggleClass('open');
});
// Show details content. Hides link after click.
//
// %div
// %a.js-details-expand
// %div.js-details-content
//
$('body').on('click', '.js-details-expand', function expand(e) {
e.preventDefault();
$(this)
.next('.js-details-content')
.removeClass('hide');
$(this).hide();
const truncatedItem = $(this).siblings('.js-details-short');
if (truncatedItem.length) {
truncatedItem.addClass('hide');
}
});
});

View file

@ -4,7 +4,6 @@ import './bind_in_out';
import './markdown/render_gfm'; import './markdown/render_gfm';
import initCopyAsGFM from './markdown/copy_as_gfm'; import initCopyAsGFM from './markdown/copy_as_gfm';
import initCopyToClipboard from './copy_to_clipboard'; import initCopyToClipboard from './copy_to_clipboard';
import './details_behavior';
import installGlEmojiElement from './gl_emoji'; import installGlEmojiElement from './gl_emoji';
import './quick_submit'; import './quick_submit';
import './requires_input'; import './requires_input';

View file

@ -14,6 +14,7 @@ export default function initGFMInput($els) {
milestones: enableGFM, milestones: enableGFM,
mergeRequests: enableGFM, mergeRequests: enableGFM,
labels: enableGFM, labels: enableGFM,
vulnerabilities: enableGFM,
}); });
}); });
} }

View file

@ -31,7 +31,7 @@ function importMermaidModule() {
return import(/* webpackChunkName: 'mermaid' */ 'mermaid') return import(/* webpackChunkName: 'mermaid' */ 'mermaid')
.then(mermaid => { .then(mermaid => {
let theme = 'neutral'; let theme = 'neutral';
const ideDarkThemes = ['dark', 'solarized-dark']; const ideDarkThemes = ['dark', 'solarized-dark', 'monokai'];
if ( if (
ideDarkThemes.includes(window.gon?.user_color_scheme) && ideDarkThemes.includes(window.gon?.user_color_scheme) &&

View file

@ -2,6 +2,7 @@ import $ from 'jquery';
import '../commons/bootstrap'; import '../commons/bootstrap';
import { isInIssuePage } from '../lib/utils/common_utils'; import { isInIssuePage } from '../lib/utils/common_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { add, show, hide } from '~/tooltips';
// Quick Submit behavior // Quick Submit behavior
// //
@ -65,18 +66,17 @@ $(document).on(
return; return;
} }
const $this = $(this); const $el = $(this);
const title = isMac() const title = isMac()
? __('You can also press &#8984;-Enter') ? __('You can also press \u{2318}-Enter')
: __('You can also press Ctrl-Enter'); : __('You can also press Ctrl-Enter');
$this.tooltip({ add($el, {
container: 'body', triggers: 'manual',
html: true, show: true,
placement: 'top',
title, title,
trigger: 'manual',
}); });
$this.tooltip('show').one('blur click', () => $this.tooltip('hide')); $el.one('blur click', () => hide($el));
show($el);
}, },
); );

View file

@ -4,6 +4,8 @@ import Sidebar from '../../right_sidebar';
import Shortcuts from './shortcuts'; import Shortcuts from './shortcuts';
import { CopyAsGFM } from '../markdown/copy_as_gfm'; import { CopyAsGFM } from '../markdown/copy_as_gfm';
import { getSelectedFragment } from '~/lib/utils/common_utils'; import { getSelectedFragment } from '~/lib/utils/common_utils';
import { isElementVisible } from '~/lib/utils/dom_utils';
import { clickCopyToClipboardButton } from '~/behaviors/copy_to_clipboard';
export default class ShortcutsIssuable extends Shortcuts { export default class ShortcutsIssuable extends Shortcuts {
constructor() { constructor() {
@ -14,6 +16,7 @@ export default class ShortcutsIssuable extends Shortcuts {
Mousetrap.bind('l', () => ShortcutsIssuable.openSidebarDropdown('labels')); Mousetrap.bind('l', () => ShortcutsIssuable.openSidebarDropdown('labels'));
Mousetrap.bind('r', ShortcutsIssuable.replyWithSelectedText); Mousetrap.bind('r', ShortcutsIssuable.replyWithSelectedText);
Mousetrap.bind('e', ShortcutsIssuable.editIssue); Mousetrap.bind('e', ShortcutsIssuable.editIssue);
Mousetrap.bind('b', ShortcutsIssuable.copyBranchName);
} }
static replyWithSelectedText() { static replyWithSelectedText() {
@ -98,4 +101,18 @@ export default class ShortcutsIssuable extends Shortcuts {
Sidebar.instance.openDropdown(name); Sidebar.instance.openDropdown(name);
return false; return false;
} }
static copyBranchName() {
// There are two buttons - one that is shown when the sidebar
// is expanded, and one that is shown when it's collapsed.
const allCopyBtns = Array.from(document.querySelectorAll('.sidebar-source-branch button'));
// Select whichever button is currently visible so that
// the "Copied" tooltip is shown when a click is simulated.
const visibleBtn = allCopyBtns.find(isElementVisible);
if (visibleBtn) {
clickCopyToClipboardButton(visibleBtn);
}
}
} }

View file

@ -12,11 +12,19 @@ import { getLocationHash } from '../lib/utils/url_utility';
$(() => { $(() => {
function toggleContainer(container, toggleState) { function toggleContainer(container, toggleState) {
const $container = $(container); const $container = $(container);
const isExpanded = $container.data('is-expanded');
const $collapseIcon = $container.find('.js-sidebar-collapse');
const $expandIcon = $container.find('.js-sidebar-expand');
$container if (isExpanded && !toggleState) {
.find('.js-toggle-button .fa-chevron-up, .js-toggle-button .fa-chevron-down') $container.data('is-expanded', false);
.toggleClass('fa-chevron-up', toggleState) $collapseIcon.addClass('hidden');
.toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined); $expandIcon.removeClass('hidden');
} else {
$container.data('is-expanded', true);
$expandIcon.addClass('hidden');
$collapseIcon.removeClass('hidden');
}
$container.find('.js-toggle-content').toggle(toggleState); $container.find('.js-toggle-content').toggle(toggleState);
} }

View file

@ -1,7 +1,7 @@
<script> <script>
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { initEditorLite } from '~/blob/utils'; import { initEditorLite } from '~/blob/utils';
import { SNIPPET_MEASURE_BLOBS_CONTENT } from '~/performance_constants'; import { SNIPPET_MEASURE_BLOBS_CONTENT } from '~/performance/constants';
import eventHub from './eventhub'; import eventHub from './eventhub';

View file

@ -50,6 +50,7 @@ export default {
variant="danger" variant="danger"
category="secondary" category="secondary"
:disabled="!canDelete" :disabled="!canDelete"
data-qa-selector="delete_file_button"
@click="$emit('delete')" @click="$emit('delete')"
>{{ s__('Snippets|Delete file') }}</gl-button >{{ s__('Snippets|Delete file') }}</gl-button
> >

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