New upstream version 13.6.5

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

View File

@ -3,6 +3,7 @@ extends:
- plugin:@gitlab/i18n
- plugin:no-jquery/slim
- plugin:no-jquery/deprecated-3.4
- ./tooling/eslint-config/conditionally_ignore_ee.js
globals:
__webpack_public_path__: true
gl: false
@ -25,9 +26,6 @@ rules:
- _links
import/no-unresolved:
- 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
no-else-return:
- error

1
.gitattributes vendored
View File

@ -1,3 +1,4 @@
VERSION merge=ours
Dangerfile gitlab-language=ruby
*.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/*
/dump.rdb
/jsconfig.json
/lefthook-local.yml
/log/*.log*
/node_modules
/nohup.out
/public/assets/
/public/uploads.*
/public/uploads/
/public/sitemap.xml
/public/sitemap.xml.gz
/shared/artifacts/
/spec/examples.txt
/rails_best_practices_output.html
@ -73,6 +76,7 @@ eslint-report.html
/.gitlab_pages_secret
/.gitlab_kas_secret
/webpack-report/
/crystalball/
/knapsack/
/rspec_flaky/
/locale/**/LC_MESSAGES
@ -97,3 +101,5 @@ apollo.config.js
/tmp/matching_tests.txt
ee/changelogs/unreleased-ee
/sitespeed-result
tags.lock
tags.temp

View File

@ -17,7 +17,7 @@ stages:
# in cases where jobs require Docker-in-Docker, the job
# definition must be extended with `.use-docker-in-docker`
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:
- gitlab-org
# All jobs are interruptible by default
@ -59,10 +59,13 @@ variables:
GET_SOURCES_ATTEMPTS: "3"
KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/report-master.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"
ES_JAVA_OPTS: "-Xms256m -Xmx256m"
ELASTIC_URL: "http://elastic:changeme@elasticsearch:9200"
DOCKER_VERSION: "19.03.0"
CACHE_CLASSES: "true"
# Preparing custom clone path to reduce space used by all random forks
# 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/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]
/ee/lib/gitlab/code_owners.rb @reprazent @kerrizor @garyh
/ee/lib/gitlab/code_owners/ @reprazent @kerrizor @garyh

View File

@ -23,14 +23,36 @@ cache-repo:
stage: sync
variables:
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:
- cd ..
- rm -rf $CI_PROJECT_NAME
- git clone --progress $CI_REPOSITORY_URL $CI_PROJECT_NAME
- cd $CI_PROJECT_NAME
- gcloud auth activate-service-account --key-file=$CI_REPO_CACHE_CREDENTIALS
- git remote rm origin
- tar cf $TAR_FILENAME .
- gzip $TAR_FILENAME
- gsutil cp $TAR_FILENAME.gz gs://gitlab-ci-git-repo-cache/project-$CI_PROJECT_ID/gitlab-master.tar.gz
# Enable shallow repo caching only if the $ENABLE_SHALLOW_REPO_CACHING variable exists
- if [ -n "$ENABLE_SHALLOW_REPO_CACHING" ]; then
cd .. && rm -rf $CI_PROJECT_NAME;
today=$(date +%Y-%m-%d);
year=$(date +%Y);
last_year=`expr $year - 1`;
one_year_ago=$(echo $today | sed "s/$year/$last_year/");
echo "Cloning $CI_REPOSITORY_URL into $CI_PROJECT_NAME with commits from $one_year_ago.";
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:
extends: .cng:rules
image: ruby:2.6-alpine
image: ruby:2.7-alpine
dependencies: []
stage: post-test
variables:

View File

@ -2,7 +2,7 @@
extends:
- .default-retry
- .docs:rules:review-docs
image: ruby:2.6-alpine
image: ruby:2.7-alpine
stage: review
needs: []
variables:
@ -38,7 +38,18 @@ review-docs-cleanup:
script:
- ./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:
- .default-retry
- .docs:rules:docs-lint
@ -46,7 +57,6 @@ docs lint:
stage: test
needs: []
script:
- scripts/lint-doc.sh
# Prepare docs for build
# 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

View File

@ -15,7 +15,7 @@
extends:
- .frontend-base
- .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:
WEBPACK_VENDOR_DLL: "true"
stage: prepare
@ -97,32 +97,41 @@ update-yarn-cache:
- .rails-cache
- .use-pg11
stage: fixtures
needs: ["setup-test-env", "compile-test-assets"]
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
variables:
SETUP_DB: "true"
WEBPACK_VENDOR_DLL: "true"
script:
- run_timed_command "gem install knapsack --no-document"
- run_timed_command "scripts/gitaly-test-build"
- 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:
name: frontend-fixtures
expire_in: 31d
when: always
paths:
- tmp/tests/frontend/
- knapsack/
frontend-fixtures:
rspec frontend_fixture:
extends:
- .frontend-fixtures-base
- .frontend:rules:default-frontend-jobs
frontend-fixtures-as-if-foss:
rspec frontend_fixture as-if-foss:
extends:
- .frontend-fixtures-base
- .frontend:rules:default-frontend-jobs-as-if-foss
- .as-if-foss
rspec-ee frontend_fixture:
extends:
- .frontend-fixtures-base
- .frontend:rules:default-frontend-jobs
parallel: 2
.frontend-test-base:
extends:
- .frontend-base
@ -152,7 +161,8 @@ karma:
extends:
- .karma-base
- .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+%)/'
artifacts:
name: coverage-javascript
@ -171,7 +181,7 @@ karma-as-if-foss:
- .karma-base
- .frontend:rules:default-frontend-jobs-as-if-foss
- .as-if-foss
needs: ["frontend-fixtures-as-if-foss"]
needs: ["rspec frontend_fixture as-if-foss"]
.jest-base:
extends: .frontend-test-base
@ -183,7 +193,8 @@ jest:
extends:
- .jest-base
- .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:
name: coverage-frontend
expire_in: 31d
@ -203,14 +214,15 @@ jest-integration:
script:
- *yarn-install
- 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:
extends:
- .jest-base
- .frontend:rules:default-frontend-jobs-as-if-foss
- .as-if-foss
needs: ["frontend-fixtures-as-if-foss"]
needs: ["rspec frontend_fixture as-if-foss"]
parallel: 2
coverage-frontend:

View File

@ -18,7 +18,7 @@
.rails-cache:
cache:
key: "rails-v2"
key: "rails-v3"
paths:
- vendor/ruby/
- vendor/gitaly-ruby/
@ -27,7 +27,7 @@
.static-analysis-cache:
cache:
key: "static-analysis-v1"
key: "static-analysis-v2"
paths:
- vendor/ruby/
- node_modules/
@ -43,7 +43,7 @@
.qa-cache:
cache:
key: "qa-v1"
key: "qa-v2"
paths:
- qa/vendor/ruby/
policy: pull
@ -71,7 +71,7 @@
policy: pull
.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:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@ -80,7 +80,7 @@
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg12:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.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:
- name: postgres:12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@ -89,7 +89,7 @@
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg11-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.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:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@ -100,7 +100,7 @@
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg12-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.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:
- name: postgres:12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]

View File

@ -7,7 +7,7 @@
before_script:
- '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/ qa/spec/ee/ qa/qa/specs/features/ee/ qa/qa/ee/ qa/qa/ee.rb'
- 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
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.
.package-and-qa-base:
image: ruby:2.6-alpine
image: ruby:2.7-alpine
stage: qa
retry: 0
script:

View File

@ -1,4 +1,4 @@
######################
#######################
# rspec job base specs
.rails-job-base:
extends:
@ -20,6 +20,7 @@
variables:
RUBY_GC_MALLOC_LIMIT: 67108864
RUBY_GC_MALLOC_LIMIT_MAX: 134217728
CRYSTALBALL: "true"
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
script:
- *base-script
@ -29,6 +30,7 @@
when: always
paths:
- coverage/
- crystalball/
- knapsack/
- rspec_flaky/
- rspec_profiling/
@ -284,6 +286,9 @@ db:migrate-from-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 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 install $BUNDLE_INSTALL_FLAGS
- date
@ -556,7 +561,7 @@ rspec-ee system pg12 geo:
# EE: Canonical MR pipelines
rspec fail-fast:
extends:
- .rspec-ee-base-pg11 # This job also runs EE spec which needs elasticsearch
- .rspec-ee-base-pg11 # This job also runs EE spec which needs elasticsearch
- .rails:rules:rspec fail-fast
stage: test
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets", "detect-tests"]

View File

@ -15,7 +15,7 @@ code_quality:
stage: test
needs: []
variables:
CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.10-gitlab.1"
CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.18"
script:
- |
if ! docker info &>/dev/null; then
@ -152,6 +152,26 @@ dependency_scanning:
dependency_scanning: gl-dependency-scanning-report.json
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:
extends:
- .default-retry

View File

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

View File

@ -103,8 +103,11 @@
- ".gitlab/ci/build-images.gitlab-ci.yml"
- ".gitlab/ci/qa.gitlab-ci.yml"
.yaml-patterns: &yaml-patterns
- "**/*.yml"
.yaml-lint-patterns: &yaml-lint-patterns
- ".gitlab-ci.yml"
- ".gitlab/ci/**/*.yml"
- "lib/gitlab/ci/templates/**/*.yml"
- "{,ee/}changelogs/**/*.yml"
.docs-patterns: &docs-patterns
- ".gitlab/route-map.yml"
@ -142,8 +145,8 @@
- "{,ee/}{,spec/}lib/{,ee/}gitlab/database{,_spec}.rb"
- "{,ee/}{,spec/}lib/{,ee/}gitlab/background_migration/**/*"
- "{,ee/}{,spec/}lib/{,ee/}gitlab/background_migration{,_spec}.rb"
- "config/prometheus/common_metrics.yml" # Used by Gitlab::DatabaseImporters::CommonMetrics::Importer
- "{,ee/}app/models/project_statistics.rb" # Used to calculate sizes in migration specs
- "config/prometheus/common_metrics.yml" # Used by Gitlab::DatabaseImporters::CommonMetrics::Importer
- "{,ee/}app/models/project_statistics.rb" # Used to calculate sizes in migration specs
.backstage-patterns: &backstage-patterns
- "Dangerfile"
@ -161,7 +164,7 @@
- "vendor/assets/**/*"
- ".gitlab/ci/**/*"
- ".{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"
- "Gemfile{,.lock}"
- "Rakefile"
@ -183,7 +186,7 @@
- "vendor/assets/**/*"
- ".gitlab/ci/**/*"
- ".{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"
- "Gemfile{,.lock}"
- "Rakefile"
@ -207,7 +210,7 @@
- "vendor/assets/**/*"
- ".gitlab/ci/**/*"
- ".{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"
- "Gemfile{,.lock}"
- "Rakefile"
@ -228,7 +231,7 @@
- "vendor/assets/**/*"
- ".gitlab/ci/**/*"
- ".{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"
- "Gemfile{,.lock}"
- "Rakefile"
@ -673,10 +676,14 @@
##################
.releases:rules:canonical-dot-com-gitlab-stable-branch-only:
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$/'
.releases:rules:canonical-dot-com-security-gitlab-stable-branch-only:
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$/'
#################
@ -771,7 +778,7 @@
.review:rules:review-performance:
rules:
- if: '$DAST_RUN == "true"' # Skip this job when DAST is run
- if: '$DAST_RUN == "true"' # Skip this job when DAST is run
when: never
- <<: *if-not-ee
when: never
@ -905,10 +912,10 @@
- <<: *if-dot-com-ee-schedule
changes: *code-backstage-patterns
##############
# YAML rules #
##############
.yaml:rules:
###################
# yaml-lint rules #
###################
.yaml-lint:rules:
rules:
- <<: *if-default-refs
changes: *yaml-patterns
changes: *yaml-lint-patterns

View File

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

View File

@ -9,6 +9,7 @@
- knapsack/
- rspec_flaky/
- rspec_profiling/
- crystalball/packed-mapping.json.gz
retrieve-tests-metadata:
extends:
@ -27,6 +28,8 @@ update-tests-metadata:
dependencies:
- setup-test-env
- rspec migration pg11
- rspec frontend_fixture
- rspec-ee frontend_fixture
- rspec unit pg11
- rspec integration 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"
- source ./scripts/rspec_helpers.sh
- 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`.
lint-ci-gitlab:
lint-yaml:
extends:
- .default-retry
- .yaml:rules
- .yaml-lint:rules
image: pipelinecomponents/yamllint:latest
stage: test
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
- [ ] 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`)
- [ ] 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
- [ ] 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
@ -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
- [ ] 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"

View File

@ -4,7 +4,7 @@
<!-- 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
### Problem to solve
<!-- What problem do we solve? Try to define the who/what/why of the opportunity as a user story. For example, "As a (who), I want (what), so I can (why/value)." -->
@ -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)
* [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)
* [Eddie (Content Editor)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#eddie-content-editor)
-->
### User experience goal
@ -95,7 +96,8 @@ In which enterprise tier should this feature go? See https://about.gitlab.com/ha
### 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 ~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 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**
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: |
echo "$(date) Copying GDK" | tee -a /workspace/startup.log
rm -r /workspace/.rvm
mv $HOME/.rvm-workspace /workspace/.rvm
cp -r $HOME/gitlab-development-kit /workspace/
(
set -e
cd /workspace/gitlab-development-kit
[[ ! -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
echo "webpack:" >> gdk.yml
echo " static: true" >> gdk.yml
mv /workspace/gitlab-development-kit/secrets.yml /workspace/gitlab-development-kit/gitlab/config
# reconfigure GDK
echo "$(date) Reconfiguring GDK" | tee -a /workspace/startup.log
gdk reconfigure
@ -41,20 +40,16 @@ tasks:
fi
# start GDK
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]*://++')
gdk start
# Run DB migrations
if [ "$GITLAB_RUN_DB_MIGRATIONS" == true ]; then
make gitlab-db-migrate
fi
# Fix DB key
if [ "$GITLAB_FIX_DB_KEY" = true ]; then
echo "$(date) Fixing DB key" | tee -a /workspace/startup.log
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
cd ../gitlab
git checkout db/structure.sql
cd ../gitlab-development-kit
# Waiting for GitLab ...
gp await-port 3000
printf "Waiting for GitLab at $(gp url 3000) ..."

View File

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

2
.nvmrc
View File

@ -1 +1 @@
12.10.0
12.18.4

View File

@ -7,16 +7,18 @@ require:
- rubocop-rspec
inherit_from:
- .rubocop_manual_todo.yml
- .rubocop_todo.yml
- ./rubocop/rubocop-migrations.yml
- ./rubocop/rubocop-usage-data.yml
- ./rubocop/rubocop-code_reuse.yml
inherit_mode:
merge:
- Include
AllCops:
TargetRubyVersion: 2.6
TargetRubyVersion: 2.7
TargetRailsVersion: 6.0
Exclude:
- 'vendor/**/*'
@ -353,6 +355,12 @@ Cop/SidekiqOptionsQueue:
- 'spec/**/*.rb'
- 'ee/spec/**/*.rb'
Graphql/ResolverType:
Enabled: true
Include:
- 'app/graphql/resolvers/**/*'
- 'ee/app/graphql/resolvers/**/*'
Graphql/AuthorizeTypes:
Enabled: true
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:
- 'app/assets/stylesheets/pages/emojis.scss'
- 'app/assets/stylesheets/startup/startup-*.scss'
- 'app/assets/stylesheets/lazy_bundles/select2.scss'
linters:
# 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
entry.
## 13.5.7 (2021-01-13)
## 13.6.5 (2021-01-13)
### Security (1 change)
- Deny implicit flow for confidential apps.
## 13.5.6 (2021-01-07)
## 13.6.4 (2021-01-07)
### Security (7 changes)
@ -17,12 +17,27 @@ entry.
- Deny implicit flow for confidential apps.
- Update NuGet regular expression to protect against ReDoS.
- 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.
- 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)
@ -38,6 +53,533 @@ entry.
- 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)
### Fixed (4 changes)
@ -657,6 +1199,32 @@ entry.
- 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)
### Fixed (2 changes)
@ -1315,6 +1883,37 @@ entry.
- 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)
### 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 'rugged', '~> 0.28'
gem 'grape-path-helpers', '~> 1.4'
gem 'grape-path-helpers', '~> 1.5'
gem 'faraday', '~> 1.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-salesforce', '~> 1.0.5'
gem 'omniauth-atlassian-oauth2', '~> 0.2.0'
gem 'rack-oauth2', '~> 1.9.3'
gem 'rack-oauth2', '~> 1.16.0'
gem 'jwt', '~> 2.1.0'
# Kerberos authentication. EE-only
@ -98,6 +98,7 @@ gem 'graphql', '~> 1.11.4'
gem 'graphiql-rails', '~> 1.4.10'
gem 'apollo_upload_server', '~> 2.0.2'
gem 'graphql-docs', '~> 1.6.0', group: [:development, :test]
gem 'graphlient', '~> 0.4.0' # Used by BulkImport feature (group::import)
gem 'hashie'
# 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.
# Also see config/initializers/fog_core_patch.rb.
gem 'fog-core', '= 2.1.0'
gem 'fog-google', '~> 1.10'
gem 'fog-google', '~> 1.11'
gem 'fog-local', '~> 0.6'
gem 'fog-openstack', '~> 1.0'
gem 'fog-rackspace', '~> 0.1.1'
@ -158,7 +159,7 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 2.0.10'
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
gem 'asciidoctor-plantuml', '~> 0.0.12'
gem 'rouge', '~> 3.24.0'
gem 'rouge', '~> 3.25.0'
gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.10.9'
@ -172,7 +173,7 @@ gem 'diffy', '~> 3.3'
gem 'diff_match_patch', '~> 0.1.0'
# 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
gem 'rack-timeout', '~> 0.5.1', require: 'rack/timeout/base'
@ -271,9 +272,6 @@ gem 'loofah', '~> 2.2'
# Working with license
gem 'licensee', '~> 8.9'
# Ace editor
gem 'ace-rails-ap', '~> 4.1.0'
# Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.7'
@ -307,13 +305,16 @@ gem 'rack-attack', '~> 6.3.0'
# Sentry integration
gem 'sentry-raven', '~> 3.0'
# PostgreSQL query parsing
gem 'gitlab-pg_query', '~> 1.3', require: 'pg_query'
gem 'premailer-rails', '~> 1.10.3'
# LabKit: Tracing and Correlation
gem 'gitlab-labkit', '0.12.2'
gem 'gitlab-labkit', '0.13.1'
# I18n
gem 'ruby_parser', '~> 3.8', require: false
gem 'ruby_parser', '~> 3.15', require: false
gem 'rails-i18n', '~> 6.0'
gem 'gettext_i18n_rails', '~> 1.8.0'
gem 'gettext_i18n_rails_js', '~> 1.3'
@ -366,22 +367,19 @@ group :development, :test do
# Generate Fake data
gem 'ffaker', '~> 2.10'
gem 'spring', '~> 2.0.0'
gem 'spring', '~> 2.1.0'
gem 'spring-commands-rspec', '~> 1.0.4'
gem 'gitlab-styles', '~> 4.3.0', require: false
# Pin these dependencies, otherwise a new rule could break the CI pipelines
gem 'rubocop', '~> 0.82.0'
gem 'rubocop-performance', '~> 1.5.2'
gem 'rubocop-rspec', '~> 1.37.0'
gem 'gitlab-styles', '~> 5.1.0', require: false
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.34.0', require: false
gem 'scss_lint', '~> 0.59.0', require: false
gem 'haml_lint', '~> 0.36.0', require: false
gem 'bundler-audit', '~> 0.6.1', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false
gem 'knapsack', '~> 1.17'
gem 'crystalball', '~> 0.7.0', require: false
gem 'simple_po_parser', '~> 1.1.2', require: false
@ -407,7 +405,7 @@ end
group :test do
gem 'fuubar', '~> 2.2.0'
gem 'rspec-retry', '~> 0.6.1'
gem 'rspec_profiling', '~> 0.0.5'
gem 'rspec_profiling', '~> 0.0.6'
gem 'rspec-parameterized', require: false
gem 'capybara', '~> 3.33.0'

View File

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

View File

@ -1 +1 @@
13.5.7
13.6.5

View File

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

View File

@ -1,11 +1,34 @@
import Vue from '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 = () => {
// eslint-disable-next-line no-new
new Vue({
el: document.querySelector('.js-access-tokens-expires-at'),
components: { ExpiresAtField },
const el = document.querySelector('.js-access-tokens-expires-at');
if (!el) {
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 AlertDetailsTable from '~/vue_shared/components/alert_details_table.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');
@ -77,7 +76,6 @@ export default {
SystemNote,
AlertMetrics,
},
mixins: [glFeatureFlagsMixin()],
inject: {
projectPath: {
default: '',
@ -150,13 +148,10 @@ export default {
},
},
environmentName() {
return this.shouldDisplayEnvironment && this.alert?.environment?.name;
return this.alert?.environment?.name;
},
environmentPath() {
return this.shouldDisplayEnvironment && this.alert?.environment?.path;
},
shouldDisplayEnvironment() {
return this.glFeatures.exposeEnvironmentPathInAlertDetails;
return this.alert?.environment?.path;
},
},
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>
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 glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
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 = {
title: s__('AlertsIntegrations|Current integrations'),
@ -24,23 +40,36 @@ const bodyTrClass =
export default {
i18n,
typeSet,
components: {
GlTable,
GlButtonGroup,
GlButton,
GlIcon,
GlLoadingIcon,
GlModal,
GlTable,
GlSprintf,
},
directives: {
GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective,
},
mixins: [glFeatureFlagsMixin()],
props: {
integrations: {
type: Array,
required: false,
default: () => [],
},
loading: {
type: Boolean,
required: false,
default: false,
},
},
fields: [
{
key: 'activated',
key: 'active',
label: __('Status'),
},
{
@ -51,22 +80,56 @@ export default {
key: 'type',
label: __('Type'),
},
{
key: 'actions',
thClass: `gl-text-center`,
tdClass: `gl-text-center`,
label: __('Actions'),
},
],
computed: {
tbodyTrClass() {
return {
[bodyTrClass]: this.integrations.length,
};
apollo: {
currentIntegration: {
query: getCurrentIntegrationQuery,
},
},
data() {
return {
integrationToDelete: integrationToDeleteDefault,
currentIntegration: null,
};
},
mounted() {
this.trackPageViews();
const callback = entries => {
const isVisible = entries.some(entry => entry.isIntersecting);
if (isVisible) {
this.trackPageViews();
this.observer.disconnect();
}
};
this.observer = new IntersectionObserver(callback);
this.observer.observe(this.$el);
},
methods: {
tbodyTrClass(item) {
return {
[bodyTrClass]: this.integrations.length,
'gl-bg-blue-50': (item !== null && item.id) === this.currentIntegration?.id,
};
},
trackPageViews() {
const { category, action } = trackAlertIntergrationsViewsOptions;
const { category, action } = trackAlertIntegrationsViewsOptions;
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>
@ -75,15 +138,16 @@ export default {
<div class="incident-management-list">
<h5 class="gl-font-lg">{{ $options.i18n.title }}</h5>
<gl-table
:empty-text="$options.i18n.emptyState"
class="integration-list"
:items="integrations"
:fields="$options.fields"
:busy="loading"
stacked="md"
:tbody-tr-class="tbodyTrClass"
show-empty
>
<template #cell(activated)="{ item }">
<span v-if="item.activated" data-testid="integration-activated-status">
<template #cell(active)="{ item }">
<span v-if="item.active" data-testid="integration-activated-status">
<gl-icon
v-gl-tooltip
name="check-circle-filled"
@ -104,6 +168,47 @@ export default {
{{ $options.i18n.status.disabled.name }}
</span>
</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-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>
</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,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import { s__ } from '~/locale';
import { doesHashExistInUrl } from '~/lib/utils/url_utility';
import ClipboardButton from '~/vue_shared/components/clipboard_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 service from '../services';
import {
i18n,
serviceOptions,
integrationTypes,
JSON_VALIDATE_DELAY,
targetPrometheusUrlPlaceholder,
targetOpsgenieUrlPlaceholder,
@ -50,7 +48,6 @@ export default {
GlSprintf,
ClipboardButton,
ToggleButton,
IntegrationsList,
},
directives: {
'gl-modal': GlModalDirective,
@ -59,10 +56,10 @@ export default {
data() {
return {
loading: false,
selectedEndpoint: serviceOptions[0].value,
options: serviceOptions,
selectedIntegration: integrationTypes[0].value,
options: integrationTypes,
active: false,
authKey: '',
token: '',
targetUrl: '',
feedback: {
variant: 'danger',
@ -91,34 +88,34 @@ export default {
];
},
isPrometheus() {
return this.selectedEndpoint === 'prometheus';
return this.selectedIntegration === 'PROMETHEUS';
},
isOpsgenie() {
return this.selectedEndpoint === 'opsgenie';
return this.selectedIntegration === 'OPSGENIE';
},
selectedService() {
switch (this.selectedEndpoint) {
case 'generic': {
selectedIntegrationType() {
switch (this.selectedIntegration) {
case 'HTTP': {
return {
url: this.generic.url,
authKey: this.generic.authorizationKey,
activated: this.generic.activated,
token: this.generic.token,
active: this.generic.active,
resetKey: this.resetKey.bind(this),
};
}
case 'prometheus': {
case 'PROMETHEUS': {
return {
url: this.prometheus.prometheusUrl,
authKey: this.prometheus.authorizationKey,
activated: this.prometheus.activated,
resetKey: this.resetKey.bind(this, 'prometheus'),
url: this.prometheus.url,
token: this.prometheus.token,
active: this.prometheus.active,
resetKey: this.resetKey.bind(this, 'PROMETHEUS'),
targetUrl: this.prometheus.prometheusApiUrl,
};
}
case 'opsgenie': {
case 'OPSGENIE': {
return {
targetUrl: this.opsgenie.opsgenieMvcTargetUrl,
activated: this.opsgenie.activated,
active: this.opsgenie.active,
};
}
default: {
@ -152,43 +149,25 @@ export default {
? this.$options.targetOpsgenieUrlPlaceholder
: 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: {
'testAlert.json': debounce(function debouncedJsonValidate() {
this.validateJson();
}, JSON_VALIDATE_DELAY),
targetUrl(oldVal, newVal) {
if (newVal && oldVal !== this.selectedService.targetUrl) {
if (newVal && oldVal !== this.selectedIntegrationType.targetUrl) {
this.canSaveForm = true;
}
},
},
mounted() {
if (
this.prometheus.activated ||
this.generic.activated ||
!this.opsgenie.opsgenieMvcIsAvailable
) {
if (this.prometheus.active || this.generic.active || !this.opsgenie.opsgenieMvcIsAvailable) {
this.removeOpsGenieOption();
} else if (this.opsgenie.activated) {
} else if (this.opsgenie.active) {
this.setOpsgenieAsDefault();
}
this.active = this.selectedService.activated;
this.authKey = this.selectedService.authKey ?? '';
this.active = this.selectedIntegrationType.active;
this.token = this.selectedIntegrationType.token ?? '';
},
methods: {
createUserErrorMessage(errors = {}) {
@ -200,19 +179,19 @@ export default {
},
setOpsgenieAsDefault() {
this.options = this.options.map(el => {
if (el.value !== 'opsgenie') {
if (el.value !== 'OPSGENIE') {
return { ...el, disabled: true };
}
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) {
this.targetUrl = this.selectedService.targetUrl;
this.targetUrl = this.selectedIntegrationType.targetUrl;
}
},
removeOpsGenieOption() {
this.options = this.options.map(el => {
if (el.value !== 'opsgenie') {
if (el.value !== 'OPSGENIE') {
return { ...el, disabled: false };
}
return { ...el, disabled: true };
@ -220,8 +199,8 @@ export default {
},
resetFormValues() {
this.testAlert.json = null;
this.targetUrl = this.selectedService.targetUrl;
this.active = this.selectedService.activated;
this.targetUrl = this.selectedIntegrationType.targetUrl;
this.active = this.selectedIntegrationType.active;
},
dismissFeedback() {
this.serverError = null;
@ -229,12 +208,12 @@ export default {
this.isFeedbackDismissed = false;
},
resetKey(key) {
const fn = key === 'prometheus' ? this.resetPrometheusKey() : this.resetGenericKey();
const fn = key === 'PROMETHEUS' ? this.resetPrometheusKey() : this.resetGenericKey();
return fn
.then(({ data: { token } }) => {
this.authKey = token;
this.setFeedback({ feedbackMessage: this.$options.i18n.authKeyRest, variant: 'success' });
this.token = token;
this.setFeedback({ feedbackMessage: this.$options.i18n.tokenRest, variant: 'success' });
})
.catch(() => {
this.setFeedback({ feedbackMessage: this.$options.i18n.errorKeyMsg, variant: 'danger' });
@ -259,9 +238,10 @@ export default {
},
toggleActivated(value) {
this.loading = true;
const path = this.isOpsgenie ? this.opsgenie.formPath : this.generic.formPath;
return service
.updateGenericActive({
endpoint: this[this.selectedEndpoint].formPath,
endpoint: path,
params: this.isOpsgenie
? { service: { opsgenie_mvc_target_url: this.targetUrl, opsgenie_mvc_enabled: value } }
: { service: { active: value } },
@ -331,9 +311,9 @@ export default {
this.validateJson();
return service
.updateTestAlert({
endpoint: this.selectedService.url,
endpoint: this.selectedIntegrationType.url,
data: this.testAlert.json,
authKey: this.selectedService.authKey,
token: this.selectedIntegrationType.token,
})
.then(() => {
this.setFeedback({
@ -358,11 +338,11 @@ export default {
onReset() {
this.testAlert.json = null;
this.dismissFeedback();
this.targetUrl = this.selectedService.targetUrl;
this.targetUrl = this.selectedIntegrationType.targetUrl;
if (this.canSaveForm) {
this.canSaveForm = false;
this.active = this.selectedService.activated;
this.active = this.selectedIntegrationType.active;
}
},
},
@ -370,153 +350,145 @@ export default {
</script>
<template>
<div>
<integrations-list :integrations="integrations" />
<gl-form @submit.prevent="onSubmit" @reset.prevent="onReset">
<h5 class="gl-font-lg gl-my-5">{{ $options.i18n.integrationsLabel }}</h5>
<gl-form @submit.prevent="onSubmit" @reset.prevent="onReset">
<h5 class="gl-font-lg gl-my-5">{{ $options.i18n.integrationsLabel }}</h5>
<gl-alert v-if="showFeedbackMsg" :variant="feedback.variant" @dismiss="dismissFeedback">
{{ feedback.feedbackMessage }}
<br />
<i v-if="serverError">{{ __('Error message:') }} {{ serverError }}</i>
<gl-button
v-if="showAlertSave"
variant="danger"
category="primary"
class="gl-display-block gl-mt-3"
@click="toggle(active)"
>
{{ __('Save anyway') }}
</gl-button>
</gl-alert>
<gl-alert v-if="showFeedbackMsg" :variant="feedback.variant" @dismiss="dismissFeedback">
{{ feedback.feedbackMessage }}
<br />
<i v-if="serverError">{{ __('Error message:') }} {{ serverError }}</i>
<gl-button
v-if="showAlertSave"
variant="danger"
category="primary"
class="gl-display-block gl-mt-3"
@click="toggle(active)"
>
{{ __('Save anyway') }}
</gl-button>
</gl-alert>
<div data-testid="alert-settings-description">
<p v-for="section in sections" :key="section.text">
<gl-sprintf :message="section.text">
<template #link="{ content }">
<gl-link :href="section.url" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</div>
<div data-testid="alert-settings-description">
<p v-for="section in sections" :key="section.text">
<gl-sprintf :message="section.text">
<template #link="{ content }">
<gl-link :href="section.url" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</div>
<gl-form-group label-for="integration-type" :label="$options.i18n.integration">
<gl-form-select
id="integration-type"
v-model="selectedEndpoint"
:options="options"
data-testid="alert-settings-select"
@change="resetFormValues"
/>
<gl-form-group label-for="integration-type" :label="$options.i18n.integration">
<gl-form-select
id="integration-type"
v-model="selectedIntegration"
:options="options"
data-testid="alert-settings-select"
@change="resetFormValues"
/>
<span class="gl-text-gray-500">
<gl-sprintf :message="$options.i18n.integrationsInfo">
<template #link="{ content }">
<gl-link
class="gl-display-inline-block"
href="https://gitlab.com/groups/gitlab-org/-/epics/4390"
target="_blank"
>{{ content }}</gl-link
>
</template>
</gl-sprintf>
</span>
</gl-form-group>
<gl-form-group :label="$options.i18n.activeLabel" label-for="active">
<toggle-button
id="active"
:disabled-input="loading"
:is-loading="loading"
:value="active"
@change="toggleService"
/>
</gl-form-group>
<gl-form-group
v-if="isOpsgenie || isPrometheus"
:label="$options.i18n.apiBaseUrlLabel"
label-for="api-url"
>
<gl-form-input
id="api-url"
v-model="targetUrl"
type="url"
:placeholder="baseUrlPlaceholder"
:disabled="!active"
/>
<span class="gl-text-gray-500">
{{ $options.i18n.apiBaseUrlHelpText }}
</span>
</gl-form-group>
<template v-if="!isOpsgenie">
<gl-form-group :label="$options.i18n.urlLabel" label-for="url">
<gl-form-input-group id="url" readonly :value="selectedIntegrationType.url">
<template #append>
<clipboard-button
:text="selectedIntegrationType.url"
:title="$options.i18n.copyToClipboard"
class="gl-m-0!"
/>
</template>
</gl-form-input-group>
<span class="gl-text-gray-500">
<gl-sprintf :message="$options.i18n.integrationsInfo">
<template #link="{ content }">
<gl-link
class="gl-display-inline-block"
href="https://gitlab.com/groups/gitlab-org/-/epics/4390"
target="_blank"
>{{ content }}</gl-link
>
</template>
</gl-sprintf>
{{ prometheusInfo }}
</span>
</gl-form-group>
<gl-form-group :label="$options.i18n.activeLabel" label-for="activated">
<toggle-button
id="activated"
:disabled-input="loading"
:is-loading="loading"
:value="active"
@change="toggleService"
/>
<gl-form-group :label="$options.i18n.tokenLabel" label-for="authorization-key">
<gl-form-input-group id="authorization-key" class="gl-mb-2" readonly :value="token">
<template #append>
<clipboard-button
:text="token"
:title="$options.i18n.copyToClipboard"
class="gl-m-0!"
/>
</template>
</gl-form-input-group>
<gl-button v-gl-modal.tokenModal :disabled="!active" class="gl-mt-3">{{
$options.i18n.resetKey
}}</gl-button>
<gl-modal
modal-id="tokenModal"
:title="$options.i18n.resetKey"
:ok-title="$options.i18n.resetKey"
ok-variant="danger"
@ok="selectedIntegrationType.resetKey"
>
{{ $options.i18n.restKeyInfo }}
</gl-modal>
</gl-form-group>
<gl-form-group
v-if="isOpsgenie || isPrometheus"
:label="$options.i18n.apiBaseUrlLabel"
label-for="api-url"
:label="$options.i18n.alertJson"
label-for="alert-json"
:invalid-feedback="testAlert.error"
>
<gl-form-input
id="api-url"
v-model="targetUrl"
type="url"
:placeholder="baseUrlPlaceholder"
<gl-form-textarea
id="alert-json"
v-model.trim="testAlert.json"
:disabled="!active"
:state="jsonIsValid"
:placeholder="$options.i18n.alertJsonPlaceholder"
rows="6"
max-rows="10"
/>
<span class="gl-text-gray-500">
{{ $options.i18n.apiBaseUrlHelpText }}
</span>
</gl-form-group>
<template v-if="!isOpsgenie">
<gl-form-group :label="$options.i18n.urlLabel" label-for="url">
<gl-form-input-group id="url" readonly :value="selectedService.url">
<template #append>
<clipboard-button
:text="selectedService.url"
:title="$options.i18n.copyToClipboard"
class="gl-m-0!"
/>
</template>
</gl-form-input-group>
<span class="gl-text-gray-500">
{{ prometheusInfo }}
</span>
</gl-form-group>
<gl-form-group :label="$options.i18n.authKeyLabel" label-for="authorization-key">
<gl-form-input-group id="authorization-key" class="gl-mb-2" readonly :value="authKey">
<template #append>
<clipboard-button
:text="authKey"
:title="$options.i18n.copyToClipboard"
class="gl-m-0!"
/>
</template>
</gl-form-input-group>
<gl-button v-gl-modal.authKeyModal :disabled="!active" class="gl-mt-3">{{
$options.i18n.resetKey
}}</gl-button>
<gl-modal
modal-id="authKeyModal"
:title="$options.i18n.resetKey"
:ok-title="$options.i18n.resetKey"
ok-variant="danger"
@ok="selectedService.resetKey"
>
{{ $options.i18n.restKeyInfo }}
</gl-modal>
</gl-form-group>
<gl-form-group
:label="$options.i18n.alertJson"
label-for="alert-json"
:invalid-feedback="testAlert.error"
>
<gl-form-textarea
id="alert-json"
v-model.trim="testAlert.json"
:disabled="!active"
:state="jsonIsValid"
:placeholder="$options.i18n.alertJsonPlaceholder"
rows="6"
max-rows="10"
/>
</gl-form-group>
<gl-button :disabled="!canTestAlert" @click="validateTestAlert">{{
$options.i18n.testAlertInfo
}}</gl-button>
</template>
<div class="footer-block row-content-block gl-display-flex gl-justify-content-space-between">
<gl-button
variant="success"
category="primary"
:disabled="!canSaveConfig"
@click="onSubmit"
>
{{ __('Save changes') }}
</gl-button>
<gl-button category="primary" :disabled="!canSaveConfig" @click="onReset">
{{ __('Cancel') }}
</gl-button>
</div>
</gl-form>
</div>
<gl-button :disabled="!canTestAlert" @click="validateTestAlert">{{
$options.i18n.testAlertInfo
}}</gl-button>
</template>
<div class="footer-block row-content-block gl-display-flex gl-justify-content-space-between">
<gl-button variant="success" category="primary" :disabled="!canSaveConfig" @click="onSubmit">
{{ __('Save changes') }}
</gl-button>
<gl-button category="primary" :disabled="!canSaveConfig" @click="onReset">
{{ __('Cancel') }}
</gl-button>
</div>
</gl-form>
</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';
// TODO: Remove this as part of the form old removal
export const i18n = {
usageSection: s__(
'AlertSettings|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.',
@ -17,11 +18,10 @@ export const i18n = {
changesSaved: s__('AlertSettings|Your integration was successfully updated.'),
prometheusInfo: s__('AlertSettings|Add URL and auth key to your Prometheus config file'),
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'),
copyToClipboard: s__('AlertSettings|Copy'),
integrationsLabel: s__('AlertSettings|Add new integrations'),
apiBaseUrlLabel: s__('AlertSettings|API URL'),
authKeyLabel: s__('AlertSettings|Authorization key'),
urlLabel: s__('AlertSettings|Webhook URL'),
@ -40,12 +40,26 @@ export const i18n = {
integration: s__('AlertSettings|Integration'),
};
export const serviceOptions = [
{ value: 'generic', text: s__('AlertSettings|HTTP Endpoint') },
{ value: 'prometheus', text: s__('AlertSettings|External Prometheus') },
{ value: 'opsgenie', text: s__('AlertSettings|Opsgenie') },
// TODO: Delete as part of old form removal in 13.6
export const integrationTypes = [
{ value: 'HTTP', text: s__('AlertSettings|HTTP Endpoint') },
{ 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 targetPrometheusUrlPlaceholder = 'http://prometheus.example.com/';
@ -56,9 +70,9 @@ export const sectionHash = 'js-alert-management-settings';
/* 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 = {
category: 'Alert Intergrations',
export const trackAlertIntegrationsViewsOptions = {
category: 'Alert Integrations',
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 { GlToast } from '@gitlab/ui';
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 => {
if (!el) {
@ -24,20 +33,17 @@ export default el => {
opsgenieMvcFormPath,
opsgenieMvcEnabled,
opsgenieMvcTargetUrl,
projectPath,
multiIntegrations,
} = el.dataset;
const genericActivated = parseBoolean(activatedStr);
const prometheusIsActivated = parseBoolean(prometheusActivated);
const opsgenieMvcActivated = parseBoolean(opsgenieMvcEnabled);
const opsgenieMvcIsAvailable = parseBoolean(opsgenieMvcAvailable);
return new Vue({
el,
provide: {
prometheus: {
activated: prometheusIsActivated,
prometheusUrl,
authorizationKey: prometheusAuthorizationKey,
active: parseBoolean(prometheusActivated),
url: prometheusUrl,
token: prometheusAuthorizationKey,
prometheusFormPath,
prometheusResetKeyPath,
prometheusApiUrl,
@ -45,23 +51,26 @@ export default el => {
generic: {
alertsSetupUrl,
alertsUsageUrl,
activated: genericActivated,
active: parseBoolean(activatedStr),
formPath,
authorizationKey,
token: authorizationKey,
url,
},
opsgenie: {
formPath: opsgenieMvcFormPath,
activated: opsgenieMvcActivated,
active: parseBoolean(opsgenieMvcEnabled),
opsgenieMvcTargetUrl,
opsgenieMvcIsAvailable,
opsgenieMvcIsAvailable: parseBoolean(opsgenieMvcAvailable),
},
projectPath,
multiIntegrations: parseBoolean(multiIntegrations),
},
apolloProvider,
components: {
AlertSettingsForm,
AlertSettingsWrapper,
},
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';
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 }) {
return axios.put(endpoint, params);
},
@ -25,11 +26,11 @@ export default {
},
});
},
updateTestAlert({ endpoint, data, authKey }) {
updateTestAlert({ endpoint, data, token }) {
return axios.post(endpoint, data, {
headers: {
'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>
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 ProjectsAndGroupsChart from './projects_and_groups_chart.vue';
import ChartsConfig from './charts_config';
import { TODAY, TOTAL_DAYS_TO_SHOW, START_DATE } from '../constants';
export default {
name: 'InstanceStatisticsApp',
components: {
InstanceCounts,
PipelinesChart,
InstanceStatisticsCountChart,
UsersChart,
ProjectsAndGroupsChart,
},
TOTAL_DAYS_TO_SHOW,
START_DATE,
TODAY,
configs: ChartsConfig,
};
</script>
@ -25,6 +29,20 @@ export default {
:end-date="$options.TODAY"
: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>
</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>
<metric-card
:title="__('Instance Statistics')"
:title="__('Usage Trends')"
:metrics="counts"
:is-loading="$apollo.queries.counts.loading"
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 { mapKeys, mapValues, pick, sortBy } from 'lodash';
import { get } from 'lodash';
import { formatDate } from '~/lib/utils/datetime_utility';
const { isoDate } = masks;
@ -41,29 +41,28 @@ export function getAverageByMonth(items = [], options = {}) {
}
/**
* Extracts values given a data set and a set of keys
* @example
* const data = { fooBar: { baz: 'quis' }, ignored: 'ignored' };
* 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
* Takes an array of instance counts and returns the last item in the list
* @param {Array} arr array of instance counts in the form { count: Number, recordedAt: date String }
* @return {String} the 'recordedAt' value of the earliest item
*/
export function extractValues(data, dataKeys = [], replaceKey, nestedKey) {
return mapKeys(pick(mapValues(data, nestedKey), dataKeys), (value, key) =>
key.replace(replaceKey, nestedKey),
);
}
export const getEarliestDate = (arr = []) => {
const len = arr.length;
return get(arr, `[${len - 1}].recordedAt`, null);
};
/**
* Creates a new array of items sorted by the date string of each item
* @param {Array} items [description]
* @param {String} items[0] date string
* @return {Array} the new sorted array.
* Takes an array of queries and produces an object with the query identifier as key
* and a supplied defaultValue as its value
* @param {Array} queries array of chart query configs,
* 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 = []) {
return sortBy(items, ({ recordedAt }) => new Date(recordedAt).getTime());
}
export const generateDataKeys = (queries, defaultValue) =>
queries.reduce(
(acc, { identifier }) => ({
...acc,
[identifier]: defaultValue,
}),
{},
);

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import { __ } from '~/locale';
const DEFAULT_PER_PAGE = 20;
const Api = {
DEFAULT_PER_PAGE,
groupsPath: '/api/:version/groups.json',
groupPath: '/api/:version/groups/:id',
groupMembersPath: '/api/:version/groups/:id/members',
@ -22,6 +23,7 @@ const Api = {
projectLabelsPath: '/:namespace_path/:project_path/-/labels',
projectFileSchemaPath: '/:namespace_path/:project_path/-/schema/:ref/:filename',
projectUsersPath: '/api/:version/projects/:id/users',
projectMembersPath: '/api/:version/projects/:id/members',
projectMergeRequestsPath: '/api/:version/projects/:id/merge_requests',
projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
@ -34,6 +36,7 @@ const Api = {
mergeRequestsPath: '/api/:version/merge_requests',
groupLabelsPath: '/groups/:namespace_path/-/labels',
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
issuableTemplatesPath: '/:namespace_path/:project_path/templates/:type',
projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key',
projectTemplatesPath: '/api/:version/projects/:id/templates/:type',
userCountsPath: '/api/:version/user_counts',
@ -70,6 +73,7 @@ const Api = {
featureFlagUserLists: '/api/:version/projects/:id/feature_flags_user_lists',
featureFlagUserList: '/api/:version/projects/:id/feature_flags_user_lists/:list_iid',
billableGroupMembersPath: '/api/:version/groups/:id/billable_members',
containerRegistryDetailsPath: '/api/:version/registry/repositories/:id/',
group(groupId, callback = () => {}) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
@ -106,6 +110,11 @@ const Api = {
return axios.delete(url);
},
containerRegistryDetails(registryId, options = {}) {
const url = Api.buildUrl(this.containerRegistryDetailsPath).replace(':id', registryId);
return axios.get(url, options);
},
groupMembers(id, options) {
const url = Api.buildUrl(this.groupMembersPath).replace(':id', encodeURIComponent(id));
@ -207,6 +216,12 @@ const Api = {
.then(({ data }) => data);
},
inviteProjectMembers(id, data) {
const url = Api.buildUrl(this.projectMembersPath).replace(':id', encodeURIComponent(id));
return axios.post(url, data);
},
// Return single project
project(projectPath) {
const url = Api.buildUrl(Api.projectPath).replace(':id', encodeURIComponent(projectPath));
@ -454,17 +469,38 @@ const Api = {
},
issueTemplate(namespacePath, projectPath, key, type, callback) {
const url = Api.buildUrl(Api.issuableTemplatePath)
.replace(':key', encodeURIComponent(key))
.replace(':type', type)
.replace(':project_path', projectPath)
.replace(':namespace_path', namespacePath);
const url = this.buildIssueTemplateUrl(
Api.issuableTemplatePath,
type,
projectPath,
namespacePath,
).replace(':key', encodeURIComponent(key));
return axios
.get(url)
.then(({ data }) => callback(null, data))
.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) {
const url = Api.buildUrl(this.usersPath);
return axios.get(url, {
@ -530,12 +566,13 @@ const Api = {
});
},
postUserStatus({ emoji, message }) {
postUserStatus({ emoji, message, availability }) {
const url = Api.buildUrl(this.userPostStatusPath);
return axios.put(url, {
emoji,
message,
availability,
});
},
@ -610,12 +647,12 @@ const Api = {
return axios.get(url);
},
pipelineJobs(projectId, pipelineId) {
pipelineJobs(projectId, pipelineId, params) {
const url = Api.buildUrl(this.pipelineJobsPath)
.replace(':id', encodeURIComponent(projectId))
.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
@ -737,6 +774,12 @@ const Api = {
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) {
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 Cookies from 'js-cookie';
import { __ } from './locale';
import { updateTooltipTitle } from './lib/utils/common_utils';
import { isInVueNoteablePage } from './lib/utils/dom_utils';
import { deprecatedCreateFlash as flash } from './flash';
import axios from './lib/utils/axios_utils';
import * as Emoji from '~/emoji';
import { dispose, fixTitle } from '~/tooltips';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
@ -374,7 +374,7 @@ export class AwardsHandler {
counter.text(counterNumber - 1);
this.removeYouFromUserList($emojiButton);
} else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
$emojiButton.tooltip('dispose');
dispose($emojiButton);
counter.text('0');
this.removeYouFromUserList($emojiButton);
if ($emojiButton.parents('.note').length) {
@ -387,7 +387,8 @@ export class AwardsHandler {
}
removeEmoji($emojiButton) {
$emojiButton.tooltip('dispose');
dispose($emojiButton);
$emojiButton.remove();
const $votesBlock = this.getVotesBlock();
if ($votesBlock.find('.js-emoji-btn').length === 0) {
@ -415,13 +416,17 @@ export class AwardsHandler {
const originalTitle = this.getAwardTooltip(awardBlock);
const authors = originalTitle.split(FROM_SENTENCE_REGEX);
authors.splice(authors.indexOf('You'), 1);
return awardBlock
awardBlock
.closest('.js-emoji-btn')
.removeData('title')
.removeAttr('data-title')
.removeAttr('data-original-title')
.attr('title', this.toSentence(authors))
.tooltip('_fixTitle');
.attr('title', this.toSentence(authors));
fixTitle(awardBlock);
return awardBlock;
}
addYouToUserList(votesBlock, emoji) {
@ -432,7 +437,12 @@ export class AwardsHandler {
users = origTitle.trim().split(FROM_SENTENCE_REGEX);
}
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) {
@ -448,7 +458,7 @@ export class AwardsHandler {
.find('.emoji-icon')
.data('name', emojiName);
this.animateEmoji($emojiButton);
$('.award-control').tooltip();
votesBlock.removeClass('current');
}
@ -487,17 +497,6 @@ export class AwardsHandler {
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() {
const options = {
scrollTop: $('.awards').offset().top - 110,

View File

@ -1,5 +1,5 @@
<script>
import { GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { GlLoadingIcon, GlTooltipDirective, GlIcon, GlButton } from '@gitlab/ui';
export default {
// name: 'Badge' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
@ -8,6 +8,7 @@ export default {
components: {
GlIcon,
GlLoadingIcon,
GlButton,
},
directives: {
GlTooltip: GlTooltipDirective,
@ -90,15 +91,16 @@ export default {
</div>
</div>
<button
<gl-button
v-show="hasError"
v-gl-tooltip.hover
:title="s__('Badges|Reload badge image')"
class="btn btn-transparent btn-sm text-primary"
category="tertiary"
variant="success"
type="button"
icon="retry"
size="small"
@click="reloadImage"
>
<gl-icon :size="16" name="retry" />
</button>
/>
</div>
</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 { sprintf, s__, __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
mixins: [glFeatureFlagsMixin()],
props: {
discussionId: {
type: String,
@ -54,6 +56,10 @@ export default {
let title = __('Mark as resolved');
if (this.glFeatures.removeResolveNote) {
title = __('Resolve thread');
}
if (this.resolvedBy) {
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);
});
}
/**
* 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 initCopyAsGFM from './markdown/copy_as_gfm';
import initCopyToClipboard from './copy_to_clipboard';
import './details_behavior';
import installGlEmojiElement from './gl_emoji';
import './quick_submit';
import './requires_input';

View File

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

View File

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

View File

@ -2,6 +2,7 @@ import $ from 'jquery';
import '../commons/bootstrap';
import { isInIssuePage } from '../lib/utils/common_utils';
import { __ } from '~/locale';
import { add, show, hide } from '~/tooltips';
// Quick Submit behavior
//
@ -65,18 +66,17 @@ $(document).on(
return;
}
const $this = $(this);
const $el = $(this);
const title = isMac()
? __('You can also press &#8984;-Enter')
? __('You can also press \u{2318}-Enter')
: __('You can also press Ctrl-Enter');
$this.tooltip({
container: 'body',
html: true,
placement: 'top',
add($el, {
triggers: 'manual',
show: true,
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 { CopyAsGFM } from '../markdown/copy_as_gfm';
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 {
constructor() {
@ -14,6 +16,7 @@ export default class ShortcutsIssuable extends Shortcuts {
Mousetrap.bind('l', () => ShortcutsIssuable.openSidebarDropdown('labels'));
Mousetrap.bind('r', ShortcutsIssuable.replyWithSelectedText);
Mousetrap.bind('e', ShortcutsIssuable.editIssue);
Mousetrap.bind('b', ShortcutsIssuable.copyBranchName);
}
static replyWithSelectedText() {
@ -98,4 +101,18 @@ export default class ShortcutsIssuable extends Shortcuts {
Sidebar.instance.openDropdown(name);
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) {
const $container = $(container);
const isExpanded = $container.data('is-expanded');
const $collapseIcon = $container.find('.js-sidebar-collapse');
const $expandIcon = $container.find('.js-sidebar-expand');
$container
.find('.js-toggle-button .fa-chevron-up, .js-toggle-button .fa-chevron-down')
.toggleClass('fa-chevron-up', toggleState)
.toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
if (isExpanded && !toggleState) {
$container.data('is-expanded', false);
$collapseIcon.addClass('hidden');
$expandIcon.removeClass('hidden');
} else {
$container.data('is-expanded', true);
$expandIcon.addClass('hidden');
$collapseIcon.removeClass('hidden');
}
$container.find('.js-toggle-content').toggle(toggleState);
}

View File

@ -1,7 +1,7 @@
<script>
import { debounce } from 'lodash';
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';

View File

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

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