New upstream version 13.1.0

This commit is contained in:
Pirate Praveen 2020-06-23 00:09:42 +05:30
parent 5f558ee76f
commit 6158a767e6
5513 changed files with 296367 additions and 78792 deletions

View file

@ -13,3 +13,6 @@ indent_size = 2
[*.{js,json,vue,scss,rb,haml,yml,md}] [*.{js,json,vue,scss,rb,haml,yml,md}]
indent_style = space indent_style = space
charset = utf-8 charset = utf-8
[*.{md,markdown}]
trim_trailing_whitespace = false

View file

@ -9,6 +9,7 @@
/scripts/ /scripts/
/tmp/ /tmp/
/vendor/ /vendor/
jest.config.js
jest.config.*.js jest.config.*.js
karma.config.js karma.config.js
webpack.config.js webpack.config.js

4
.gitignore vendored
View file

@ -35,7 +35,6 @@ eslint-report.html
/config/gitlab.yml /config/gitlab.yml
/config/gitlab_ci.yml /config/gitlab_ci.yml
/config/Gitlab.gitlab-license /config/Gitlab.gitlab-license
/config/initializers/rack_attack.rb
/config/initializers/smtp_settings.rb /config/initializers/smtp_settings.rb
/config/initializers/relative_url.rb /config/initializers/relative_url.rb
/config/resque.yml /config/resque.yml
@ -92,3 +91,6 @@ jsdoc/
webpack-dev-server.json webpack-dev-server.json
/.nvimrc /.nvimrc
.solargraph.yml .solargraph.yml
apollo.config.js
/tmp/matching_foss_tests.txt
ee/changelogs/unreleased-ee

View file

@ -15,7 +15,7 @@ stages:
# in cases where jobs require Docker-in-Docker, the job # in cases where jobs require Docker-in-Docker, the job
# definition must be extended with `.use-docker-in-docker` # definition must be extended with `.use-docker-in-docker`
default: default:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-9.6-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.27-lfs-2.9-chrome-83-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
tags: tags:
- gitlab-org - gitlab-org
# All jobs are interruptible by default # All jobs are interruptible by default

View file

@ -8,11 +8,15 @@
# Technical writing team are the default reviewers for all markdown docs # Technical writing team are the default reviewers for all markdown docs
/doc/ @gl-docsteam /doc/ @gl-docsteam
# Dev and Doc guidelines # Doc subpaths
/doc/administration/monitoring/ @aqualls
/doc/development/ @marcia @mjang1 /doc/development/ @marcia @mjang1
/doc/development/documentation/ @mikelewis /doc/development/documentation/ @mikelewis
/doc/ci @marcel.amirault @sselhorn /doc/ci @marcel.amirault @sselhorn
/doc/.linting @marcel.amirault @eread @aqualls @mikelewis /doc/user/clusters @aqualls
/doc/user/infrastructure @aqualls
/doc/user/project/clusters @aqualls
/doc/.vale/ @marcel.amirault @eread @aqualls @mikelewis
# Frontend maintainers should see everything in `app/assets/` # Frontend maintainers should see everything in `app/assets/`
*.scss @annabeldunstone @gitlab-org/maintainers/frontend *.scss @annabeldunstone @gitlab-org/maintainers/frontend
@ -37,7 +41,6 @@
/ee/app/finders/ @gitlab-org/maintainers/database /ee/app/finders/ @gitlab-org/maintainers/database
# Feature specific owners # Feature specific owners
/ee/lib/gitlab/code_owners/ @reprazent @kerrizor
/ee/lib/ee/gitlab/auth/ldap/ @dblessing @mkozono /ee/lib/ee/gitlab/auth/ldap/ @dblessing @mkozono
/lib/gitlab/auth/ldap/ @dblessing @mkozono /lib/gitlab/auth/ldap/ @dblessing @mkozono
/lib/gitlab/ci/templates/ @nolith @zj /lib/gitlab/ci/templates/ @nolith @zj
@ -46,6 +49,11 @@
/ee/app/models/project_alias.rb @patrickbajao /ee/app/models/project_alias.rb @patrickbajao
/ee/lib/api/project_aliases.rb @patrickbajao /ee/lib/api/project_aliases.rb @patrickbajao
# Code Owners
#
/ee/lib/gitlab/code_owners/ @reprazent @kerrizor @garyh
/doc/user/project/code_owners.md @reprazent @kerrizor @garyh
# Quality owned files # Quality owned files
/qa/ @gl-quality /qa/ @gl-quality

View file

@ -62,7 +62,7 @@ docs lint:
graphql-reference-verify: graphql-reference-verify:
extends: extends:
- .default-retry - .default-retry
- .default-cache - .rails-cache
- .default-before_script - .default-before_script
- .docs:rules:graphql-reference-verify - .docs:rules:graphql-reference-verify
- .use-pg11 - .use-pg11

View file

@ -1,157 +1,128 @@
.assets-compile-cache: .frontend-base:
cache:
paths:
- vendor/ruby/
- public/assets/webpack/
- assets-hash.txt
- .yarn-cache/
- tmp/cache/assets/sprockets
- tmp/cache/babel-loader
- tmp/cache/vue-loader
- tmp/cache/webpack-dlls
.gitlab:assets:compile-metadata:
extends: extends:
- .default-retry - .default-retry
- .default-before_script - .default-before_script
- .assets-compile-cache - .assets-compile-cache
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-graphicsmagick-1.3.34-docker-19.03.1 variables:
SETUP_DB: "false"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
WEBPACK_VENDOR_DLL: "true"
.compile-assets-base:
extends: .frontend-base
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-git-2.27-lfs-2.9-node-12.x-yarn-1.21-graphicsmagick-1.3.34
stage: prepare stage: prepare
script:
- node --version
- run_timed_command "retry yarn install --frozen-lockfile"
- free -m
- run_timed_command "bin/rake gitlab:assets:compile > assets-compile.log 2>&1"
- run_timed_command "scripts/clean-old-cached-assets"
compile-production-assets:
extends:
- .compile-assets-base
- .frontend:rules:compile-production-assets
variables: variables:
NODE_ENV: "production" NODE_ENV: "production"
RAILS_ENV: "production" RAILS_ENV: "production"
SETUP_DB: "false"
SKIP_STORAGE_VALIDATION: "true"
WEBPACK_REPORT: "true" WEBPACK_REPORT: "true"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
cache:
key: "assets-compile:production:v1"
artifacts: artifacts:
name: webpack-report name: webpack-report
expire_in: 31d expire_in: 31d
paths: paths:
- webpack-report/
- assets-compile.log - assets-compile.log
# These assets are used in multiple locations: # These assets are used in multiple locations:
# - in `build-assets-image` job to create assets image for packaging systems # - in `build-assets-image` job to create assets image for packaging systems
# - GitLab UI for integration tests: https://gitlab.com/gitlab-org/gitlab-ui/-/blob/e88493b3c855aea30bf60baee692a64606b0eb1e/.storybook/preview-head.pug#L1 # - GitLab UI for integration tests: https://gitlab.com/gitlab-org/gitlab-ui/-/blob/e88493b3c855aea30bf60baee692a64606b0eb1e/.storybook/preview-head.pug#L1
- public/assets - public/assets/
- webpack-report/
when: always when: always
script: after_script:
- node --version
- retry yarn install --frozen-lockfile --production --cache-folder .yarn-cache --prefer-offline
- free -m
- time bin/rake gitlab:assets:compile > assets-compile.log 2>&1
- scripts/clean-old-cached-assets
- rm -f /etc/apt/sources.list.d/google*.list # We don't need to update Chrome here - rm -f /etc/apt/sources.list.d/google*.list # We don't need to update Chrome here
gitlab:assets:compile pull-push-cache: compile-test-assets:
extends: extends:
- .gitlab:assets:compile-metadata - .compile-assets-base
- .frontend:rules:gitlab-assets-compile-pull-push-cache - .frontend:rules:compile-test-assets
cache: artifacts:
policy: pull-push expire_in: 7d
paths:
- assets-compile.log
- public/assets/
- node_modules/@gitlab/svgs/dist/icons.json # app/helpers/icons_helper.rb uses this file
when: always
gitlab:assets:compile pull-cache: compile-test-assets as-if-foss:
extends: extends:
- .gitlab:assets:compile-metadata - compile-test-assets
- .frontend:rules:gitlab-assets-compile-pull-cache - .frontend:rules:compile-test-assets-as-if-foss
- .as-if-foss
update-assets-compile-production-cache:
extends:
- compile-production-assets
- .shared:rules:update-cache
stage: prepare
artifacts: {} # This job's purpose is only to update the cache.
cache: cache:
policy: pull policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
update-assets-compile-test-cache:
extends:
- compile-test-assets
- .shared:rules:update-cache
stage: prepare
artifacts: {} # This job's purpose is only to update the cache.
cache:
policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
update-yarn-cache:
extends:
- .default-retry
- .yarn-cache
- .shared:rules:update-cache
stage: prepare
script:
- source scripts/utils.sh
- run_timed_command "retry yarn install --frozen-lockfile"
cache:
policy: push
build-assets-image: build-assets-image:
extends: extends:
- .use-kaniko - .use-kaniko
- .frontend:rules:gitlab-assets-compile-pull-cache - .frontend:rules:compile-production-assets
stage: build-images stage: build-images
needs: ["gitlab:assets:compile pull-cache"] needs: ["compile-production-assets"]
variables: variables:
GIT_DEPTH: "1" GIT_DEPTH: "1"
script: script:
# TODO: Change the image tag to be the MD5 of assets files and skip image building if the image exists # TODO: Change the image tag to be the MD5 of assets files and skip image building if the image exists
# We'll also need to pass GITLAB_ASSETS_TAG to the trigerred omnibus-gitlab pipeline similarly to how we do it for trigerred CNG pipelines # We'll also need to pass GITLAB_ASSETS_TAG to the trigerred omnibus-gitlab pipeline similarly to how we do it for trigerred CNG pipelines
# https://gitlab.com/gitlab-org/gitlab/issues/208389 # https://gitlab.com/gitlab-org/gitlab/issues/208389
- scripts/build_assets_image - run_timed_command "scripts/build_assets_image"
retry: 2
.compile-assets-metadata:
extends:
- .default-retry
- .default-before_script
- .assets-compile-cache
stage: prepare
script:
- node --version
- retry yarn install --frozen-lockfile --cache-folder .yarn-cache --prefer-offline
- free -m
- time bin/rake gitlab:assets:compile > assets-compile.log 2>&1
- scripts/clean-old-cached-assets
variables:
SETUP_DB: "false"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
WEBPACK_VENDOR_DLL: "true"
cache:
key: "assets-compile:test:v1"
artifacts:
expire_in: 7d
paths:
- node_modules
- public/assets
- assets-compile.log
when: always
compile-assets pull-push-cache:
extends:
- .compile-assets-metadata
- .frontend:rules:compile-assets-pull-push-cache
cache:
policy: pull-push
compile-assets pull-push-cache as-if-foss:
extends:
- .compile-assets-metadata
- .frontend:rules:compile-assets-pull-push-cache-as-if-foss
- .as-if-foss
cache:
policy: pull-push
key: "assets-compile:test:as-if-foss:v1"
compile-assets pull-cache:
extends:
- .compile-assets-metadata
- .frontend:rules:compile-assets-pull-cache
cache:
policy: pull
compile-assets pull-cache as-if-foss:
extends:
- .compile-assets-metadata
- .frontend:rules:compile-assets-pull-cache-as-if-foss
- .as-if-foss
cache:
policy: pull
key: "assets-compile:test:as-if-foss:v1"
.frontend-fixtures-base: .frontend-fixtures-base:
extends: extends:
- .default-retry - .frontend-base
- .rails-cache - .rails-cache
- .default-before_script
- .use-pg11 - .use-pg11
stage: fixtures stage: fixtures
needs: ["setup-test-env", "compile-assets pull-cache"] needs: ["setup-test-env", "compile-test-assets"]
variables:
SETUP_DB: "true"
script: script:
- run_timed_command "scripts/gitaly-test-build" - run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn" - run_timed_command "scripts/gitaly-test-spawn"
- run_timed_command "bundle exec rake frontend:fixtures" - run_timed_command "bin/rake frontend:fixtures"
artifacts: artifacts:
name: frontend-fixtures name: frontend-fixtures
expire_in: 31d expire_in: 31d
when: always when: always
paths: paths:
- node_modules
- public/assets
- tmp/tests/frontend/ - tmp/tests/frontend/
frontend-fixtures: frontend-fixtures:
@ -165,25 +136,27 @@ frontend-fixtures-as-if-foss:
- .frontend:rules:default-frontend-jobs-as-if-foss - .frontend:rules:default-frontend-jobs-as-if-foss
- .as-if-foss - .as-if-foss
.frontend-job-base: .frontend-test-base:
extends: extends:
- .default-retry - .default-retry
- .default-cache - .yarn-cache
- .default-before_script
variables: variables:
USE_BUNDLE_INSTALL: "false" USE_BUNDLE_INSTALL: "false"
SETUP_DB: "false" SETUP_DB: "false"
stage: test stage: test
before_script:
- source scripts/utils.sh
.karma-base: .karma-base:
extends: .frontend-job-base extends: .frontend-test-base
variables: variables:
# we override the max_old_space_size to prevent OOM errors # we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584 NODE_OPTIONS: --max_old_space_size=3584
script: script:
- source scripts/utils.sh
- export BABEL_ENV=coverage CHROME_LOG_FILE=chrome_debug.log - export BABEL_ENV=coverage CHROME_LOG_FILE=chrome_debug.log
- date - run_timed_command "retry yarn install --frozen-lockfile"
- yarn karma - run_timed_command "yarn karma"
karma: karma:
extends: extends:
@ -210,15 +183,11 @@ karma-as-if-foss:
needs: ["frontend-fixtures-as-if-foss"] needs: ["frontend-fixtures-as-if-foss"]
.jest-base: .jest-base:
extends: .frontend-job-base extends: .frontend-test-base
script: script:
- date - source scripts/utils.sh
- yarn jest --ci --coverage --testSequencer ./scripts/frontend/parallel_ci_sequencer.js - run_timed_command "retry yarn install --frozen-lockfile"
cache: - run_timed_command "yarn jest --ci --coverage --testSequencer ./scripts/frontend/parallel_ci_sequencer.js"
key: jest
paths:
- tmp/cache/jest/
policy: pull-push
jest: jest:
extends: extends:
@ -235,21 +204,17 @@ jest:
- tmp/tests/frontend/ - tmp/tests/frontend/
reports: reports:
junit: junit_jest.xml junit: junit_jest.xml
parallel: 2 parallel: 4
jest-integration: jest-integration:
extends: extends:
- .frontend-job-base - .frontend-test-base
- .frontend:rules:default-frontend-jobs - .frontend:rules:default-frontend-jobs
script: script:
- date - source scripts/utils.sh
- yarn jest:integration --ci - run_timed_command "retry yarn install --frozen-lockfile"
- run_timed_command "yarn jest:integration --ci"
needs: ["frontend-fixtures"] needs: ["frontend-fixtures"]
cache:
key: jest-integration
paths:
- tmp/cache/jest/
policy: pull-push
jest-as-if-foss: jest-as-if-foss:
extends: extends:
@ -257,8 +222,7 @@ jest-as-if-foss:
- .frontend:rules:default-frontend-jobs-as-if-foss - .frontend:rules:default-frontend-jobs-as-if-foss
- .as-if-foss - .as-if-foss
needs: ["frontend-fixtures-as-if-foss"] needs: ["frontend-fixtures-as-if-foss"]
cache: parallel: 2
policy: pull
coverage-frontend: coverage-frontend:
extends: extends:
@ -269,33 +233,26 @@ coverage-frontend:
stage: post-test stage: post-test
before_script: before_script:
- source scripts/utils.sh - source scripts/utils.sh
- retry yarn install --frozen-lockfile - run_timed_command "retry yarn install --frozen-lockfile"
script: script:
- yarn node scripts/frontend/merge_coverage_frontend.js - run_timed_command "yarn node scripts/frontend/merge_coverage_frontend.js"
artifacts: artifacts:
name: coverage-frontend name: coverage-frontend
expire_in: 31d expire_in: 31d
paths: paths:
- coverage-frontend/ - coverage-frontend/
cache:
policy: pull
.qa-frontend-node: .qa-frontend-node:
extends: extends:
- .default-retry - .default-retry
- .yarn-cache
- .frontend:rules:qa-frontend-node - .frontend:rules:qa-frontend-node
stage: test stage: test
dependencies: [] dependencies: []
cache:
key: "$CI_JOB_NAME"
paths:
- .yarn-cache/
policy: pull-push
script: script:
- date - source scripts/utils.sh
- yarn install --frozen-lockfile --cache-folder .yarn-cache --prefer-offline - run_timed_command "yarn install --frozen-lockfile"
- date - run_timed_command "yarn run webpack-prod"
- yarn run webpack-prod
qa-frontend-node:10: qa-frontend-node:10:
extends: .qa-frontend-node extends: .qa-frontend-node
@ -310,27 +267,39 @@ qa-frontend-node:latest:
webpack-dev-server: webpack-dev-server:
extends: extends:
- .default-retry - .default-retry
- .yarn-cache
- .frontend:rules:default-frontend-jobs - .frontend:rules:default-frontend-jobs
stage: test stage: test
needs: [] needs: []
variables: variables:
WEBPACK_MEMORY_TEST: "true" WEBPACK_MEMORY_TEST: "true"
WEBPACK_VENDOR_DLL: "true" WEBPACK_VENDOR_DLL: "true"
cache:
key:
files:
- yarn.lock
prefix: "v1"
paths:
- node_modules/
- tmp/cache/webpack-dlls/
script: script:
- source scripts/utils.sh - source scripts/utils.sh
- retry yarn install --frozen-lockfile - run_timed_command "retry yarn install --frozen-lockfile"
- retry yarn webpack-vendor - run_timed_command "retry yarn webpack-vendor"
- node --expose-gc node_modules/.bin/webpack-dev-server --config config/webpack.config.js - run_timed_command "node --expose-gc node_modules/.bin/webpack-dev-server --config config/webpack.config.js"
artifacts: artifacts:
name: webpack-dev-server name: webpack-dev-server
expire_in: 31d expire_in: 31d
paths: paths:
- webpack-dev-server.json - webpack-dev-server.json
bundle-size-review:
extends:
- .default-retry
- .frontend:rules:bundle-size-review
image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger
stage: test
needs: ["compile-production-assets"]
script:
- mkdir -p bundle-size-review
- cp webpack-report/index.html bundle-size-review/bundle-report.html
- yarn global add https://gitlab.com/gitlab-org/frontend/playground/webpack-memory-metrics.git
- danger --dangerfile=danger/bundle_size/Dangerfile --fail-on-errors=true --verbose --danger_id=bundle-size-review
artifacts:
when: always
name: bundle-size-review
expire_in: 31d
paths:
- bundle-size-review

View file

@ -10,49 +10,61 @@
.default-before_script: .default-before_script:
before_script: before_script:
- date
- '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/ qa/spec/ee/ qa/qa/specs/features/ee/ qa/qa/ee/ qa/qa/ee.rb' - '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/ qa/spec/ee/ qa/qa/specs/features/ee/ qa/qa/ee/ qa/qa/ee.rb'
- export GOPATH=$CI_PROJECT_DIR/.go - export GOPATH=$CI_PROJECT_DIR/.go
- mkdir -p $GOPATH - mkdir -p $GOPATH
- source scripts/utils.sh - source scripts/utils.sh
- source scripts/prepare_build.sh - source scripts/prepare_build.sh
- date
# Jobs that only need to pull cache
.default-cache:
cache:
key: "debian-stretch-ruby-2.6.6-pg11-node-12.x"
paths:
- .go/pkg/mod
- vendor/ruby
- .yarn-cache/
- vendor/gitaly-ruby
policy: pull
.rails-cache: .rails-cache:
cache: cache:
key: key: "rails-v1"
files:
- Gemfile.lock
- GITALY_SERVER_VERSION
prefix: "ruby-go-cache-v1"
paths: paths:
- vendor/ruby - vendor/ruby/
- vendor/gitaly-ruby - vendor/gitaly-ruby/
- .go/pkg/mod - .go/pkg/mod/
policy: pull
.static-analysis-cache:
cache:
key: "static-analysis-v1"
paths:
- vendor/ruby/
- node_modules/
- tmp/rubocop_cache/
policy: pull
.qa-cache:
cache:
key: "qa-v1"
paths:
- qa/vendor/ruby/
policy: pull policy: pull
.yarn-cache: .yarn-cache:
cache: cache:
key: key: "yarn-v1"
files:
- yarn.lock
prefix: "v1"
paths: paths:
- node_modules/ - node_modules/
- tmp/cache/webpack-dlls/
policy: pull
.assets-compile-cache:
cache:
key: "assets-compile-${NODE_ENV}-v1"
paths:
- vendor/ruby/
- node_modules/
- assets-hash.txt
- public/assets/webpack/
- tmp/cache/assets/sprockets/
- tmp/cache/babel-loader/
- tmp/cache/vue-loader/
- tmp/cache/webpack-dlls/
policy: pull
.use-pg11: .use-pg11:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.27-lfs-2.9-chrome-83-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
services: services:
- name: postgres:11.6 - name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@ -61,7 +73,7 @@
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
.use-pg11-ee: .use-pg11-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.27-lfs-2.9-chrome-83-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
services: services:
- name: postgres:11.6 - name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@ -75,6 +87,7 @@
name: gcr.io/kaniko-project/executor:debug-v0.20.0 name: gcr.io/kaniko-project/executor:debug-v0.20.0
entrypoint: [""] entrypoint: [""]
before_script: before_script:
- source scripts/utils.sh
- mkdir -p /kaniko/.docker - mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json

View file

@ -1,7 +1,7 @@
.only-code-memory-job-base: .only-code-memory-job-base:
extends: extends:
- .default-retry - .default-retry
- .default-cache - .rails-cache
- .default-before_script - .default-before_script
- .memory:rules - .memory:rules
@ -39,12 +39,11 @@ memory-on-boot:
- .only-code-memory-job-base - .only-code-memory-job-base
- .use-pg11 - .use-pg11
stage: test stage: test
needs: ["setup-test-env", "compile-assets pull-cache"] needs: ["setup-test-env", "compile-test-assets"]
variables: variables:
NODE_ENV: "production" NODE_ENV: "production"
RAILS_ENV: "production" RAILS_ENV: "production"
SETUP_DB: "true" SETUP_DB: "true"
SKIP_STORAGE_VALIDATION: "true"
# we override the max_old_space_size to prevent OOM errors # we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584 NODE_OPTIONS: --max_old_space_size=3584
script: script:

View file

@ -3,11 +3,16 @@ pages:
- .default-retry - .default-retry
- .pages:rules - .pages:rules
stage: pages stage: pages
dependencies: ["rspec:coverage", "karma", "gitlab:assets:compile pull-cache"] dependencies:
- rspec:coverage
- coverage-frontend
- karma
- compile-production-assets
script: script:
- mv public/ .public/ - mv public/ .public/
- mkdir public/ - mkdir public/
- mv coverage/ public/coverage-ruby/ || true - mv coverage/ public/coverage-ruby/ || true
- mv coverage-frontend/ public/coverage-frontend/ || true
- mv coverage-javascript/ public/coverage-javascript/ || true - mv coverage-javascript/ public/coverage-javascript/ || true
- mv webpack-report/ public/webpack-report/ || true - mv webpack-report/ public/webpack-report/ || true
- cp .public/assets/application-*.css public/application.css || true - cp .public/assets/application-*.css public/application.css || true

View file

@ -1,12 +1,9 @@
.qa-job-base: .qa-job-base:
extends: extends:
- .default-retry - .default-retry
- .qa-cache
stage: test stage: test
needs: [] needs: []
cache:
key: "qa-framework-jobs:v1"
paths:
- vendor/ruby
before_script: before_script:
- '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/ qa/spec/ee/ qa/qa/specs/features/ee/ qa/qa/ee/ qa/qa/ee.rb' - '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/ qa/spec/ee/ qa/qa/specs/features/ee/ qa/qa/ee/ qa/qa/ee.rb'
- cd qa/ - cd qa/
@ -22,11 +19,9 @@ qa:internal:
qa:internal-as-if-foss: qa:internal-as-if-foss:
extends: extends:
- .qa-job-base - qa:internal
- .qa:rules:as-if-foss - .qa:rules:as-if-foss
- .as-if-foss - .as-if-foss
script:
- bundle exec rspec
qa:selectors: qa:selectors:
extends: extends:
@ -41,6 +36,16 @@ qa:selectors-as-if-foss:
- .qa:rules:as-if-foss - .qa:rules:as-if-foss
- .as-if-foss - .as-if-foss
update-qa-cache:
extends:
- .qa-job-base
- .shared:rules:update-cache
stage: prepare
script:
- echo "Cache has been updated and ready to be uploaded."
cache:
policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
.package-and-qa-base: .package-and-qa-base:
image: ruby:2.6-alpine image: ruby:2.6-alpine
stage: qa stage: qa

View file

@ -1,6 +1,3 @@
.rails:needs:setup-and-assets:
needs: ["setup-test-env", "compile-assets pull-cache"]
.rails-job-base: .rails-job-base:
extends: extends:
- .default-retry - .default-retry
@ -35,32 +32,54 @@ setup-test-env:
- tmp/tests/repositories - tmp/tests/repositories
- tmp/tests/second_storage - tmp/tests/second_storage
when: always when: always
update-rails-cache:
extends:
- setup-test-env
- .shared:rules:update-cache
artifacts: {} # This job's purpose is only to update the cache.
cache: cache:
policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
.static-analysis-base:
extends:
- .default-retry
- .default-before_script
- .static-analysis-cache
needs: []
variables:
SETUP_DB: "false"
ENABLE_SPRING: "1"
update-static-analysis-cache:
extends:
- .static-analysis-base
- .shared:rules:update-cache
stage: prepare
script:
- rm -rf ./node_modules # We remove node_modules because there's no mechanism to remove stall entries.
- run_timed_command "retry yarn install --frozen-lockfile"
- bundle exec rubocop --parallel # For the moment we only cache `vendor/ruby/`, `node_modules/`, and `tmp/rubocop_cache` so we don't need to run all the tasks,
cache:
# We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up but RuboCop has a mechanism
# for keeping only the N latest cache files, so we take advantage of it with `pull-push` and removing `node_modules` at the start of the job.
policy: pull-push policy: pull-push
static-analysis: static-analysis:
extends: extends:
- .rails-job-base - .static-analysis-base
- .rails:rules:default-refs-code-backstage-qa - .rails:rules:default-refs-code-backstage-qa
- .rails:needs:setup-and-assets
stage: test stage: test
variables: parallel: 4
SETUP_DB: "false"
parallel: 2
script: script:
- run_timed_command "retry yarn install --frozen-lockfile"
- scripts/static-analysis - scripts/static-analysis
cache:
key: "ruby-2.6.6-pg11-rubocop"
paths:
- vendor/ruby
- tmp/rubocop_cache
policy: pull-push
downtime_check: downtime_check:
extends: extends:
- .rails-job-base - .rails-job-base
- .rails:rules:downtime_check - .rails:rules:downtime_check
needs: ["setup-test-env"] needs: []
stage: test stage: test
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
@ -70,7 +89,7 @@ downtime_check:
.rspec-base: .rspec-base:
extends: .rails-job-base extends: .rails-job-base
stage: test stage: test
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-assets pull-cache"] needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
script: script:
- run_timed_command "scripts/gitaly-test-build" - run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn" - run_timed_command "scripts/gitaly-test-spawn"
@ -173,7 +192,7 @@ db:migrate-from-v12.10.0:
db:rollback: db:rollback:
extends: .db-job-base extends: .db-job-base
script: script:
- bundle exec rake db:migrate VERSION=20180101160629 - bundle exec rake db:migrate VERSION=20181228175414
- bundle exec rake db:migrate SKIP_SCHEMA_VERSION_CHECK=true - bundle exec rake db:migrate SKIP_SCHEMA_VERSION_CHECK=true
gitlab:setup: gitlab:setup:
@ -218,8 +237,6 @@ rspec:coverage:
- memory-on-boot - memory-on-boot
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
cache:
policy: pull
script: script:
- bundle exec scripts/merge-simplecov - bundle exec scripts/merge-simplecov
- bundle exec scripts/gather-test-memory-data - bundle exec scripts/gather-test-memory-data
@ -247,7 +264,7 @@ rspec:coverage:
- .rails:rules:as-if-foss - .rails:rules:as-if-foss
- .as-if-foss - .as-if-foss
- .use-pg11 - .use-pg11
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-assets pull-cache as-if-foss"] needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets as-if-foss"]
.rspec-ee-base-pg11: .rspec-ee-base-pg11:
extends: extends:
@ -323,3 +340,26 @@ db:rollback geo:
- bundle exec rake geo:db:migrate - bundle exec rake geo:db:migrate
# EE: default refs (MRs, master, schedules) jobs # # EE: default refs (MRs, master, schedules) jobs #
################################################## ##################################################
##################################################
# EE: Canonical MR pipelines
rspec foss-impact:
extends:
- .rspec-base
- .as-if-foss
- .rails:rules:ee-mr-only
- .use-pg11
script:
- install_gitlab_gem
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
- source scripts/rspec_helpers.sh
- tooling/bin/find_foss_tests tmp/matching_foss_tests.txt
- rspec_matched_tests tmp/matching_foss_tests.txt "--tag ~quarantine --tag ~geo --tag ~level:migration"
artifacts:
expire_in: 7d
paths:
- tmp/matching_foss_tests.txt
- tmp/capybara/
# EE: Merge Request pipelines
##################################################

View file

@ -94,9 +94,9 @@ dependency_scanning:
stage: test stage: test
needs: [] needs: []
variables: variables:
DS_MAJOR_VERSION: 2
DS_EXCLUDED_PATHS: "qa/qa/ee/fixtures/secure_premade_reports,spec,ee/spec" # GitLab-specific DS_EXCLUDED_PATHS: "qa/qa/ee/fixtures/secure_premade_reports,spec,ee/spec" # GitLab-specific
script: script:
- export DS_VERSION=${SP_VERSION:-$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')}
- | - |
if ! docker info &>/dev/null; then if ! docker info &>/dev/null; then
if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
@ -138,7 +138,7 @@ dependency_scanning:
) \ ) \
--volume "$PWD:/code" \ --volume "$PWD:/code" \
--volume /var/run/docker.sock:/var/run/docker.sock \ --volume /var/run/docker.sock:/var/run/docker.sock \
"registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$DS_VERSION" /code "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$DS_MAJOR_VERSION" /code
artifacts: artifacts:
paths: paths:
- gl-dependency-scanning-report.json # GitLab-specific - gl-dependency-scanning-report.json # GitLab-specific
@ -146,37 +146,38 @@ dependency_scanning:
dependency_scanning: gl-dependency-scanning-report.json dependency_scanning: gl-dependency-scanning-report.json
expire_in: 1 week # GitLab-specific expire_in: 1 week # GitLab-specific
# We need to duplicate this job's definition because it seems it's impossible to # Temporarily disabling review apps
# override an included `only.refs`. ## We need to duplicate this job's definition because it seems it's impossible to
# See https://gitlab.com/gitlab-org/gitlab/issues/31371. ## override an included `only.refs`.
dast: ## See https://gitlab.com/gitlab-org/gitlab/issues/31371.
extends: #dast:
- .default-retry # extends:
- .reports:rules:dast # - .default-retry
# This is needed so that manual jobs with needs don't block the pipeline. # - .reports:rules:dast
# See https://gitlab.com/gitlab-org/gitlab/-/issues/199979. # # This is needed so that manual jobs with needs don't block the pipeline.
dependencies: ["review-deploy"] # # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
stage: qa # GitLab-specific # dependencies: ["review-deploy"]
image: # stage: qa # GitLab-specific
name: "registry.gitlab.com/gitlab-org/security-products/dast:$DAST_VERSION" # image:
variables: # name: "registry.gitlab.com/gitlab-org/security-products/dast:$DAST_VERSION"
# To be done in a later iteration # variables:
# DAST_USERNAME: "root" # # To be done in a later iteration
# DAST_USERNAME_FIELD: "user[login]" # # DAST_USERNAME: "root"
# DAST_PASSWORD_FIELD: "user[passowrd]" # # DAST_USERNAME_FIELD: "user[login]"
DAST_VERSION: 1 # # DAST_PASSWORD_FIELD: "user[passowrd]"
script: # DAST_VERSION: 1
- 'export DAST_WEBSITE="${DAST_WEBSITE:-$(cat environment_url.txt)}"' # script:
# To be done in a later iteration # - 'export DAST_WEBSITE="${DAST_WEBSITE:-$(cat environment_url.txt)}"'
# - 'export DAST_AUTH_URL="${DAST_WEBSITE}/users/sign_in"' # # To be done in a later iteration
# - 'export DAST_PASSWORD="${REVIEW_APPS_ROOT_PASSWORD}"' # # - 'export DAST_AUTH_URL="${DAST_WEBSITE}/users/sign_in"'
- /analyze -t $DAST_WEBSITE # # - 'export DAST_PASSWORD="${REVIEW_APPS_ROOT_PASSWORD}"'
artifacts: # - /analyze -t $DAST_WEBSITE
paths: # artifacts:
- gl-dast-report.json # GitLab-specific # paths:
reports: # - gl-dast-report.json # GitLab-specific
dast: gl-dast-report.json # reports:
expire_in: 1 week # GitLab-specific # dast: gl-dast-report.json
# expire_in: 1 week # GitLab-specific
# To be done in a later iteration: https://gitlab.com/gitlab-org/gitlab/issues/31160#note_278188255 # To be done in a later iteration: https://gitlab.com/gitlab-org/gitlab/issues/31160#note_278188255
# schedule:dast: # schedule:dast:

View file

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

View file

@ -37,6 +37,9 @@
.if-merge-request-title-as-if-foss: &if-merge-request-title-as-if-foss .if-merge-request-title-as-if-foss: &if-merge-request-title-as-if-foss
if: '$CI_MERGE_REQUEST_TITLE =~ /RUN AS-IF-FOSS/' if: '$CI_MERGE_REQUEST_TITLE =~ /RUN AS-IF-FOSS/'
.if-merge-request-title-update-caches: &if-merge-request-title-update-caches
if: '$CI_MERGE_REQUEST_TITLE =~ /UPDATE CACHE/'
.if-security-merge-request: &if-security-merge-request .if-security-merge-request: &if-security-merge-request
if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_MERGE_REQUEST_IID' if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_MERGE_REQUEST_IID'
@ -49,6 +52,9 @@
.if-dot-com-gitlab-org-merge-request: &if-dot-com-gitlab-org-merge-request .if-dot-com-gitlab-org-merge-request: &if-dot-com-gitlab-org-merge-request
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_MERGE_REQUEST_IID' if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_MERGE_REQUEST_IID'
.if-dot-com-gitlab-org-and-security-merge-request: &if-dot-com-gitlab-org-and-security-merge-request
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE =~ /^gitlab-org($|\/security$)/ && $CI_MERGE_REQUEST_IID'
.if-dot-com-gitlab-org-and-security-tag: &if-dot-com-gitlab-org-and-security-tag .if-dot-com-gitlab-org-and-security-tag: &if-dot-com-gitlab-org-and-security-tag
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE =~ /^gitlab-org($|\/security$)/ && $CI_COMMIT_TAG' if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE =~ /^gitlab-org($|\/security$)/ && $CI_COMMIT_TAG'
@ -78,9 +84,11 @@
.frontend-patterns: &frontend-patterns .frontend-patterns: &frontend-patterns
- "{package.json,yarn.lock}" - "{package.json,yarn.lock}"
- "{babel.config,jest.config}.js" - "babel.config.js"
- "jest.config.{base,integration,unit}.js"
- ".csscomb.json" - ".csscomb.json"
- "Dockerfile.assets" - "Dockerfile.assets"
- "config/**/*.js"
- "vendor/assets/**/*" - "vendor/assets/**/*"
- "{,ee/}{app/assets,app/helpers,app/presenters,app/views,locale,public,symbol}/**/*" - "{,ee/}{app/assets,app/helpers,app/presenters,app/views,locale,public,symbol}/**/*"
@ -93,7 +101,8 @@
.code-patterns: &code-patterns .code-patterns: &code-patterns
- "{package.json,yarn.lock}" - "{package.json,yarn.lock}"
- "{babel.config,jest.config}.js" - "babel.config.js"
- "jest.config.{base,integration,unit}.js"
- ".csscomb.json" - ".csscomb.json"
- "Dockerfile.assets" - "Dockerfile.assets"
- "vendor/assets/**/*" - "vendor/assets/**/*"
@ -113,7 +122,8 @@
.code-backstage-patterns: &code-backstage-patterns .code-backstage-patterns: &code-backstage-patterns
- "{package.json,yarn.lock}" - "{package.json,yarn.lock}"
- "{babel.config,jest.config}.js" - "babel.config.js"
- "jest.config.{base,integration,unit}.js"
- ".csscomb.json" - ".csscomb.json"
- "Dockerfile.assets" - "Dockerfile.assets"
- "vendor/assets/**/*" - "vendor/assets/**/*"
@ -135,7 +145,8 @@
.code-qa-patterns: &code-qa-patterns .code-qa-patterns: &code-qa-patterns
- "{package.json,yarn.lock}" - "{package.json,yarn.lock}"
- "{babel.config,jest.config}.js" - "babel.config.js"
- "jest.config.{base,integration,unit}.js"
- ".csscomb.json" - ".csscomb.json"
- "Dockerfile.assets" - "Dockerfile.assets"
- "vendor/assets/**/*" - "vendor/assets/**/*"
@ -154,7 +165,8 @@
.code-backstage-qa-patterns: &code-backstage-qa-patterns .code-backstage-qa-patterns: &code-backstage-qa-patterns
- "{package.json,yarn.lock}" - "{package.json,yarn.lock}"
- "{babel.config,jest.config}.js" - "babel.config.js"
- "jest.config.{base,integration,unit}.js"
- ".csscomb.json" - ".csscomb.json"
- "Dockerfile.assets" - "Dockerfile.assets"
- "vendor/assets/**/*" - "vendor/assets/**/*"
@ -177,6 +189,14 @@
- ".dockerignore" - ".dockerignore"
- "qa/**/*" - "qa/**/*"
################
# Shared rules #
################
.shared:rules:update-cache:
rules:
- <<: *if-master-schedule-2-hourly
- <<: *if-merge-request-title-update-caches
#################### ####################
# Cache repo rules # # Cache repo rules #
#################### ####################
@ -238,51 +258,21 @@
################## ##################
# Frontend rules # # Frontend rules #
################## ##################
# This job only runs on `master` since it pushes to the cache. .frontend:rules:compile-production-assets:
.frontend:rules:gitlab-assets-compile-pull-push-cache:
rules:
- <<: *if-not-canonical-namespace
when: never
- <<: *if-master-refs
changes: *code-backstage-qa-patterns
when: on_success
.frontend:rules:gitlab-assets-compile-pull-cache:
rules: rules:
- <<: *if-not-canonical-namespace - <<: *if-not-canonical-namespace
when: never when: never
- <<: *if-default-refs - <<: *if-default-refs
changes: *code-backstage-qa-patterns changes: *code-backstage-qa-patterns
when: on_success
.frontend:rules:compile-assets-pull-push-cache: .frontend:rules:compile-test-assets:
rules: rules:
- <<: *if-master-refs - changes: *code-backstage-qa-patterns
changes: *code-backstage-qa-patterns
when: on_success
# This job only runs on `master` since it pushes to the cache. .frontend:rules:compile-test-assets-as-if-foss:
.frontend:rules:compile-assets-pull-push-cache-as-if-foss:
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-master-push
changes: *code-backstage-qa-patterns
- <<: *if-master-schedule-2-hourly
.frontend:rules:compile-assets-pull-cache:
rules:
- <<: *if-default-refs
changes: *code-backstage-qa-patterns
when: on_success
.frontend:rules:compile-assets-pull-cache-as-if-foss:
rules:
- <<: *if-not-ee
when: never
- <<: *if-master-push
changes: *code-backstage-qa-patterns
- <<: *if-master-schedule-2-hourly
- <<: *if-security-merge-request - <<: *if-security-merge-request
changes: *code-backstage-qa-patterns changes: *code-backstage-qa-patterns
- <<: *if-merge-request-title-as-if-foss - <<: *if-merge-request-title-as-if-foss
@ -293,15 +283,11 @@
rules: rules:
- <<: *if-default-refs - <<: *if-default-refs
changes: *code-backstage-patterns changes: *code-backstage-patterns
when: on_success
.frontend:rules:default-frontend-jobs-as-if-foss: .frontend:rules:default-frontend-jobs-as-if-foss:
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-master-push
changes: *code-backstage-patterns
- <<: *if-master-schedule-2-hourly
- <<: *if-security-merge-request - <<: *if-security-merge-request
changes: *code-backstage-patterns changes: *code-backstage-patterns
- <<: *if-merge-request-title-as-if-foss - <<: *if-merge-request-title-as-if-foss
@ -321,10 +307,8 @@
rules: rules:
- <<: *if-master-refs - <<: *if-master-refs
changes: *frontend-dependency-patterns changes: *frontend-dependency-patterns
when: on_success
- <<: *if-merge-request - <<: *if-merge-request
changes: *frontend-dependency-patterns changes: *frontend-dependency-patterns
when: on_success
.frontend:rules:qa-frontend-node-latest: .frontend:rules:qa-frontend-node-latest:
rules: rules:
@ -335,6 +319,12 @@
changes: *frontend-dependency-patterns changes: *frontend-dependency-patterns
allow_failure: true allow_failure: true
.frontend:rules:bundle-size-review:
rules:
- if: '$DANGER_GITLAB_API_TOKEN && $CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"'
changes: *frontend-patterns
allow_failure: true
################ ################
# Memory rules # # Memory rules #
################ ################
@ -368,9 +358,6 @@
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-master-push
changes: *code-qa-patterns
- <<: *if-master-schedule-2-hourly
- <<: *if-security-merge-request - <<: *if-security-merge-request
changes: *code-qa-patterns changes: *code-qa-patterns
- <<: *if-merge-request-title-as-if-foss - <<: *if-merge-request-title-as-if-foss
@ -379,13 +366,13 @@
.qa:rules:package-and-qa: .qa:rules:package-and-qa:
rules: rules:
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *ci-patterns changes: *ci-patterns
allow_failure: true allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *qa-patterns changes: *qa-patterns
allow_failure: true allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *code-patterns changes: *code-patterns
when: manual when: manual
allow_failure: true allow_failure: true
@ -416,9 +403,6 @@
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-master-push
changes: *code-backstage-patterns
- <<: *if-master-schedule-2-hourly
- <<: *if-security-merge-request - <<: *if-security-merge-request
changes: *code-backstage-patterns changes: *code-backstage-patterns
- <<: *if-merge-request-title-as-if-foss - <<: *if-merge-request-title-as-if-foss
@ -434,6 +418,17 @@
- <<: *if-master-refs - <<: *if-master-refs
changes: *code-backstage-patterns changes: *code-backstage-patterns
.rails:rules:ee-mr-only:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-title-as-if-foss
when: never
- <<: *if-security-merge-request
changes: *code-backstage-patterns
- <<: *if-dot-com-gitlab-org-merge-request
changes: *code-backstage-patterns
.rails:rules:downtime_check: .rails:rules:downtime_check:
rules: rules:
- <<: *if-merge-request - <<: *if-merge-request
@ -505,7 +500,7 @@
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *code-qa-patterns changes: *code-qa-patterns
- <<: *if-dot-com-gitlab-org-schedule - <<: *if-dot-com-gitlab-org-schedule

View file

@ -3,7 +3,7 @@
cache gems: cache gems:
extends: extends:
- .default-retry - .default-retry
- .default-cache - .rails-cache
- .default-before_script - .default-before_script
- .setup:rules:cache-gems - .setup:rules:cache-gems
stage: test stage: test

View file

@ -3,11 +3,6 @@
TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache" TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache"
before_script: before_script:
- source scripts/utils.sh - source scripts/utils.sh
cache:
key: tests_metadata
paths:
- knapsack/
- rspec_flaky/
artifacts: artifacts:
expire_in: 31d expire_in: 31d
paths: paths:
@ -20,8 +15,6 @@ retrieve-tests-metadata:
- .tests-metadata-state - .tests-metadata-state
- .test-metadata:rules:retrieve-tests-metadata - .test-metadata:rules:retrieve-tests-metadata
stage: prepare stage: prepare
cache:
policy: pull
script: script:
- source scripts/rspec_helpers.sh - source scripts/rspec_helpers.sh
- retrieve_tests_metadata - retrieve_tests_metadata
@ -44,8 +37,6 @@ update-tests-metadata:
- rspec-ee unit pg11 geo - rspec-ee unit pg11 geo
- rspec-ee integration pg11 geo - rspec-ee integration pg11 geo
- rspec-ee system pg11 geo - rspec-ee system pg11 geo
cache:
policy: push
script: script:
- retry gem install fog-aws mime-types activesupport rspec_profiling postgres-copy --no-document - retry gem install fog-aws mime-types activesupport rspec_profiling postgres-copy --no-document
- source scripts/rspec_helpers.sh - source scripts/rspec_helpers.sh

View file

@ -24,6 +24,8 @@
- ~"development guidelines" and ~"Description templates (.gitlab/\*)" when creating/updating issue and MR description templates. - ~"development guidelines" and ~"Description templates (.gitlab/\*)" when creating/updating issue and MR description templates.
- [ ] Assign the [designated Technical Writer](https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments). - [ ] Assign the [designated Technical Writer](https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments).
Do not add the ~"feature", ~"frontend", ~"backend", ~"bug", or ~"database" labels if you are only updating documentation. These labels will cause the MR to be added to code verification QA issues.
When applicable: When applicable:
- [ ] Update the [permissions table](https://docs.gitlab.com/ee/user/permissions.html). - [ ] Update the [permissions table](https://docs.gitlab.com/ee/user/permissions.html).

View file

@ -80,8 +80,8 @@ linters:
ignored_cops: ignored_cops:
- Layout/BlockAlignment - Layout/BlockAlignment
- Layout/EndAlignment - Layout/EndAlignment
- Layout/LineLength
- Lint/Void - Lint/Void
- Metrics/LineLength
- Naming/FileName - Naming/FileName
- Style/AlignParameters - Style/AlignParameters
- Style/BlockNesting - Style/BlockNesting
@ -92,7 +92,6 @@ linters:
- Style/IfUnlessModifier - Style/IfUnlessModifier
- Style/IndentationWidth - Style/IndentationWidth
- Style/Next - Style/Next
- Style/TrailingBlankLines
- Style/TrailingWhitespace - Style/TrailingWhitespace
- Style/WhileUntilModifier - Style/WhileUntilModifier
@ -112,7 +111,7 @@ linters:
- Layout/SpaceInsideArrayLiteralBrackets - Layout/SpaceInsideArrayLiteralBrackets
- Layout/SpaceInsideHashLiteralBraces - Layout/SpaceInsideHashLiteralBraces
- Layout/SpaceInsideStringInterpolation - Layout/SpaceInsideStringInterpolation
- Layout/TrailingBlankLines - Layout/TrailingEmptyLines
- Lint/BooleanSymbol - Lint/BooleanSymbol
- Lint/LiteralInInterpolation - Lint/LiteralInInterpolation
- Lint/ParenthesesAsGroupedExpression - Lint/ParenthesesAsGroupedExpression

View file

@ -1,6 +1,6 @@
# This configuration was generated by # This configuration was generated by
# `haml-lint --auto-gen-config` # `haml-lint --auto-gen-config`
# on 2020-04-20 07:11:26 +0000 using Haml-Lint version 0.34.0. # on 2020-05-21 10:58:59 -0400 using Haml-Lint version 0.34.0.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the lints are removed from the code base. # one by one as the lints are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
@ -162,7 +162,6 @@ linters:
- "app/views/projects/_home_panel.html.haml" - "app/views/projects/_home_panel.html.haml"
- "app/views/projects/_import_project_pane.html.haml" - "app/views/projects/_import_project_pane.html.haml"
- "app/views/projects/_issuable_by_email.html.haml" - "app/views/projects/_issuable_by_email.html.haml"
- "app/views/projects/_md_preview.html.haml"
- "app/views/projects/_readme.html.haml" - "app/views/projects/_readme.html.haml"
- "app/views/projects/artifacts/_artifact.html.haml" - "app/views/projects/artifacts/_artifact.html.haml"
- "app/views/projects/artifacts/_tree_file.html.haml" - "app/views/projects/artifacts/_tree_file.html.haml"
@ -267,7 +266,6 @@ linters:
- "app/views/projects/triggers/_index.html.haml" - "app/views/projects/triggers/_index.html.haml"
- "app/views/projects/triggers/_trigger.html.haml" - "app/views/projects/triggers/_trigger.html.haml"
- "app/views/projects/triggers/edit.html.haml" - "app/views/projects/triggers/edit.html.haml"
- "app/views/projects/wikis/_pages_wiki_page.html.haml"
- "app/views/search/results/_issue.html.haml" - "app/views/search/results/_issue.html.haml"
- "app/views/search/results/_note.html.haml" - "app/views/search/results/_note.html.haml"
- "app/views/search/results/_snippet_blob.html.haml" - "app/views/search/results/_snippet_blob.html.haml"
@ -277,6 +275,7 @@ linters:
- "app/views/shared/_delete_label_modal.html.haml" - "app/views/shared/_delete_label_modal.html.haml"
- "app/views/shared/_group_form.html.haml" - "app/views/shared/_group_form.html.haml"
- "app/views/shared/_group_tips.html.haml" - "app/views/shared/_group_tips.html.haml"
- "app/views/shared/_md_preview.html.haml"
- "app/views/shared/_milestone_expired.html.haml" - "app/views/shared/_milestone_expired.html.haml"
- "app/views/shared/_no_password.html.haml" - "app/views/shared/_no_password.html.haml"
- "app/views/shared/_ping_consent.html.haml" - "app/views/shared/_ping_consent.html.haml"
@ -313,6 +312,7 @@ linters:
- "app/views/shared/snippets/_snippet.html.haml" - "app/views/shared/snippets/_snippet.html.haml"
- "app/views/shared/web_hooks/_form.html.haml" - "app/views/shared/web_hooks/_form.html.haml"
- "app/views/shared/web_hooks/_hook.html.haml" - "app/views/shared/web_hooks/_hook.html.haml"
- "app/views/shared/wikis/_pages_wiki_page.html.haml"
- "app/views/u2f/_authenticate.html.haml" - "app/views/u2f/_authenticate.html.haml"
- "app/views/u2f/_register.html.haml" - "app/views/u2f/_register.html.haml"
- "app/views/users/_deletion_guidance.html.haml" - "app/views/users/_deletion_guidance.html.haml"
@ -324,7 +324,6 @@ linters:
- "ee/app/views/admin/geo/projects/_registry_never.html.haml" - "ee/app/views/admin/geo/projects/_registry_never.html.haml"
- "ee/app/views/admin/licenses/_upload_trial_license.html.haml" - "ee/app/views/admin/licenses/_upload_trial_license.html.haml"
- "ee/app/views/admin/licenses/new.html.haml" - "ee/app/views/admin/licenses/new.html.haml"
- "ee/app/views/admin/licenses/show.html.haml"
- "ee/app/views/admin/monitoring/ee/_nav.html.haml" - "ee/app/views/admin/monitoring/ee/_nav.html.haml"
- "ee/app/views/admin/projects/_shared_runner_status.html.haml" - "ee/app/views/admin/projects/_shared_runner_status.html.haml"
- "ee/app/views/admin/users/_auditor_access_level_radio.html.haml" - "ee/app/views/admin/users/_auditor_access_level_radio.html.haml"

View file

@ -43,8 +43,10 @@
"Consul", "Consul",
"Debian", "Debian",
"DevOps", "DevOps",
"Docker",
"Elasticsearch", "Elasticsearch",
"Facebook", "Facebook",
"fastlane",
"GDK", "GDK",
"Geo", "Geo",
"Git LFS", "Git LFS",
@ -101,6 +103,7 @@
"OpenShift", "OpenShift",
"PgBouncer", "PgBouncer",
"PostgreSQL", "PostgreSQL",
"Praefect",
"Prometheus", "Prometheus",
"Puma", "Puma",
"puma-worker-killer", "puma-worker-killer",

View file

@ -2,18 +2,21 @@ inherit_gem:
gitlab-styles: gitlab-styles:
- rubocop-default.yml - rubocop-default.yml
inherit_from: .rubocop_todo.yml
require: require:
- ./rubocop/rubocop - ./rubocop/rubocop
- rubocop-rspec - rubocop-rspec
inherit_from:
- .rubocop_todo.yml
- ./rubocop/rubocop-migrations.yml
inherit_mode: inherit_mode:
merge: merge:
- Include - Include
AllCops: AllCops:
TargetRubyVersion: 2.6 TargetRubyVersion: 2.6
TargetRailsVersion: 5.0 TargetRailsVersion: 6.0
Exclude: Exclude:
- 'vendor/**/*' - 'vendor/**/*'
- 'node_modules/**/*' - 'node_modules/**/*'
@ -27,6 +30,7 @@ AllCops:
- 'plugins/**/*' - 'plugins/**/*'
- 'file_hooks/**/*' - 'file_hooks/**/*'
CacheRootDirectory: tmp CacheRootDirectory: tmp
MaxFilesInCache: 18000
Cop/AvoidKeywordArgumentsInSidekiqWorkers: Cop/AvoidKeywordArgumentsInSidekiqWorkers:
Enabled: true Enabled: true
@ -178,6 +182,9 @@ Rails/ApplicationRecord:
- ee/db/**/*.rb - ee/db/**/*.rb
- ee/spec/**/*.rb - ee/spec/**/*.rb
Cop/DefaultScope:
Enabled: true
Rails/FindBy: Rails/FindBy:
Enabled: true Enabled: true
Include: Include:
@ -186,6 +193,14 @@ Rails/FindBy:
- 'spec/**/*.rb' - 'spec/**/*.rb'
- 'ee/spec/**/*.rb' - 'ee/spec/**/*.rb'
# This is currently exiting with a rubocop exception error and should be
# resolved hopefully a future update
# An error occurred while Rails/UniqueValidationWithoutIndex cop was inspecting
# app/models/abuse_report.rb:15:2.
# To see the complete backtrace run rubocop -d.
Rails/UniqueValidationWithoutIndex:
Enabled: false
# GitLab ################################################################### # GitLab ###################################################################
Gitlab/ModuleWithInstanceVariables: Gitlab/ModuleWithInstanceVariables:
@ -225,6 +240,7 @@ Gitlab/Json:
- 'scripts/**/*' - 'scripts/**/*'
- 'lib/rspec_flaky/**/*' - 'lib/rspec_flaky/**/*'
- 'lib/quality/**/*' - 'lib/quality/**/*'
- 'lib/gitlab/danger/**/*'
GitlabSecurity/PublicSend: GitlabSecurity/PublicSend:
Enabled: true Enabled: true
@ -240,10 +256,7 @@ GitlabSecurity/PublicSend:
- 'ee/spec/**/*' - 'ee/spec/**/*'
Gitlab/DuplicateSpecLocation: Gitlab/DuplicateSpecLocation:
Exclude: Enabled: true
- ee/spec/lib/gitlab/gl_repository_spec.rb
- ee/spec/services/merge_requests/refresh_service_spec.rb
- ee/spec/services/ee/merge_requests/refresh_service_spec.rb
Cop/InjectEnterpriseEditionModule: Cop/InjectEnterpriseEditionModule:
Enabled: true Enabled: true
@ -259,12 +272,15 @@ Style/ReturnNil:
Performance/RegexpMatch: Performance/RegexpMatch:
Enabled: false Enabled: false
ActiveRecordAssociationReload: Cop/ActiveRecordAssociationReload:
Enabled: true Enabled: true
Exclude: Exclude:
- 'spec/**/*' - 'spec/**/*'
- 'ee/spec/**/*' - 'ee/spec/**/*'
Gitlab/AvoidFeatureGet:
Enabled: true
Naming/PredicateName: Naming/PredicateName:
Enabled: true Enabled: true
Exclude: Exclude:
@ -349,39 +365,17 @@ RSpec/LeakyConstantDeclaration:
Exclude: Exclude:
- 'spec/db/schema_spec.rb' - 'spec/db/schema_spec.rb'
- 'spec/lib/feature_spec.rb' - 'spec/lib/feature_spec.rb'
- 'spec/lib/gitlab/ci/build/credentials/factory_spec.rb'
- 'spec/lib/gitlab/ci/config/entry/retry_spec.rb'
- 'spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb'
- 'spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb'
- 'spec/lib/gitlab/config/entry/factory_spec.rb'
- 'spec/lib/gitlab/config/entry/simplifiable_spec.rb' - 'spec/lib/gitlab/config/entry/simplifiable_spec.rb'
- 'spec/lib/gitlab/database/migration_helpers_spec.rb'
- 'spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb'
- 'spec/lib/gitlab/database/with_lock_retries_spec.rb'
- 'spec/lib/gitlab/git/diff_collection_spec.rb'
- 'spec/lib/gitlab/import_export/import_test_coverage_spec.rb'
- 'spec/lib/gitlab/import_export/project/relation_factory_spec.rb'
- 'spec/lib/gitlab/jira_import/issues_importer_spec.rb'
- 'spec/lib/gitlab/no_cache_headers_spec.rb'
- 'spec/lib/gitlab/path_regex_spec.rb'
- 'spec/lib/gitlab/quick_actions/dsl_spec.rb' - 'spec/lib/gitlab/quick_actions/dsl_spec.rb'
- 'spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb'
- 'spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb'
- 'spec/lib/marginalia_spec.rb' - 'spec/lib/marginalia_spec.rb'
- 'spec/mailers/notify_spec.rb' - 'spec/mailers/notify_spec.rb'
- 'spec/migrations/20191125114345_add_admin_mode_protected_path_spec.rb'
- 'spec/models/concerns/batch_destroy_dependent_associations_spec.rb' - 'spec/models/concerns/batch_destroy_dependent_associations_spec.rb'
- 'spec/models/concerns/bulk_insert_safe_spec.rb' - 'spec/models/concerns/bulk_insert_safe_spec.rb'
- 'spec/models/concerns/bulk_insertable_associations_spec.rb' - 'spec/models/concerns/bulk_insertable_associations_spec.rb'
- 'spec/models/concerns/triggerable_hooks_spec.rb' - 'spec/models/concerns/triggerable_hooks_spec.rb'
- 'spec/models/repository_spec.rb' - 'spec/models/repository_spec.rb'
- 'spec/requests/api/graphql/tasks/task_completion_status_spec.rb'
- 'spec/serializers/commit_entity_spec.rb'
- 'spec/services/clusters/applications/check_installation_progress_service_spec.rb' - 'spec/services/clusters/applications/check_installation_progress_service_spec.rb'
- 'spec/services/clusters/applications/check_uninstall_progress_service_spec.rb'
- 'spec/support/shared_contexts/spam_constants.rb'
- 'spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb' - 'spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb'
- 'spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb'
RSpec/EmptyLineAfterHook: RSpec/EmptyLineAfterHook:
Enabled: false Enabled: false
@ -452,4 +446,16 @@ Rails/TimeZone:
- 'ee/app/services/**/*' - 'ee/app/services/**/*'
- 'ee/spec/controllers/**/*' - 'ee/spec/controllers/**/*'
- 'ee/spec/services/**/*' - 'ee/spec/services/**/*'
- 'app/models/**/*'
- 'spec/models/**/*'
- 'ee/app/models/**/*'
- 'ee/spec/models/**/*'
# WIP: See https://gitlab.com/gitlab-org/gitlab/-/issues/220040
Rails/SaveBang:
Enabled: true
Include:
- 'spec/**/*.rb'
- 'ee/spec/**/*.rb'
- 'qa/spec/**/*.rb'
- 'qa/qa/specs/**/*.rb'

File diff suppressed because it is too large Load diff

View file

@ -1 +1 @@
2.6.5 2.6.6

View file

@ -1,8 +1,11 @@
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 13.0.5 (2020-06-04) ## 13.0.6 (2020-06-10)
### Security (1 change)
- Do not set fallback mirror user.
- No changes.
## 13.0.4 (2020-06-03) ## 13.0.4 (2020-06-03)
@ -12,10 +15,6 @@ Please view this file on the master branch, on stable branches it's out of date.
- No changes. - No changes.
## 13.0.2 (2020-05-28)
- No changes.
## 13.0.1 (2020-05-27) ## 13.0.1 (2020-05-27)
### Security (3 changes) ### Security (3 changes)
@ -351,6 +350,33 @@ Please view this file on the master branch, on stable branches it's out of date.
- Translate unauthenticated user string for Audit Event. !31856 (Sashi Kumar) - Translate unauthenticated user string for Audit Event. !31856 (Sashi Kumar)
## 12.10.11 (2020-06-10)
### Security (1 change)
- Do not set fallback mirror user.
## 12.10.8 (2020-05-28)
### Fixed (1 change)
- Geo: Fix empty synchronisation status when nothing is synchronised. !30710
## 12.10.7 (2020-05-27)
### Security (3 changes)
- Change the mirror user along with pull mirror settings.
- Allow only users with a verified email to be member of a group when the group has restricted membership based on email domain.
- Do not auto-confirm email in Trial registration.
## 12.10.6 (2020-05-15)
- No changes.
## 12.10.5 (2020-05-13) ## 12.10.5 (2020-05-13)
### Fixed (1 change) ### Fixed (1 change)
@ -421,6 +447,22 @@ Please view this file on the master branch, on stable branches it's out of date.
- Add health status counts to usage data. !28964 - Add health status counts to usage data. !28964
## 12.9.10 (2020-06-10)
### Security (1 change)
- Do not set fallback mirror user.
## 12.9.8 (2020-05-27)
### Security (3 changes)
- Change the mirror user along with pull mirror settings.
- Allow only users with a verified email to be member of a group when the group has restricted membership based on email domain.
- Do not auto-confirm email in Trial registration.
## 12.9.6 (2020-05-05) ## 12.9.6 (2020-05-05)
- No changes. - No changes.

View file

@ -2,24 +2,498 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 13.1.0 (2020-06-22)
### Removed (4 changes, 2 of them are from the community)
- Remove deprecated dashboard & group milestone pages. !13237
- Removed UltraAuth integration for OmniAuth. !29330 (Kartikey Tanna)
- Remove all search autocomplete for groups/projects/other. !31187
- Remove temporary datepicker position fix as it is no longer required. !31836 (Arun Kumar Mohan)
### Fixed (154 changes, 57 of them are from the community)
- Fix 'Active' checkbox text in Pipeline Schedule form to be a label. !27054 (Jonston Chan)
- Fix back button when switching MR tabs. !29862 (Lee Tickett)
- Remove ability to scroll Issue while in Design View. !29881
- Fix merge request note label URLs. !30428 (Lee Tickett)
- Fix default path when creating project from group template. !30597 (Lee Tickett)
- Group authorization refresh to consider shared groups. !31204
- Fix group transfer service to deny moving group to its subgroup. !31495 (Abhisek Datta)
- Fix issuable listings with any label filter. !31729
- Move prepend to last in ee-app-services. !31838 (Rajendra Kadam)
- Fallback to lowest visibility level in snippet visibility radio. !31847 (Jacopo Beschi @jacopo-beschi)
- Add class stubs and fix leaky constant alert in query limit helper spec. !31949 (Rajendra Kadam)
- Remove usage of spam constants in spec. !31959 (Rajendra Kadam)
- Fix leaky constant issue in uninstall progress service check. !32036 (Rajendra Kadam)
- Fix leaky constant issue in commit entity spec. !32039 (Rajendra Kadam)
- Fix leaky constant issue in task completion status spec. !32043 (Rajendra Kadam)
- Fix leaky constant issue in admin mode migration spec. !32074 (Rajendra Kadam)
- Fix leaky constant issue in sidekiq middleware server metric spec. !32104 (Rajendra Kadam)
- Fix leaky constant issue in sidekiq middleware client metric spec. !32108 (Rajendra Kadam)
- Fix leaky constant issue in path regex spec. !32115 (Rajendra Kadam)
- Fix leaky constant issue importer and cache headers spec. !32122 (Rajendra Kadam)
- Fix leaky constant issue in relation factory spec. !32129 (Rajendra Kadam)
- Fix leaky constant issue in test coverage spec. !32134 (Rajendra Kadam)
- Prevent emails to user on expiry of impersonation token. !32140
- Fix leaky constant issue in diff collection spec. !32163 (Rajendra Kadam)
- Fix leaky constant issue in migration helpers, with lock retries and ignored cols spec. !32170 (Rajendra Kadam)
- Fix leaky constant issue in factory spec. !32174 (Rajendra Kadam)
- Fix leaky constant issue in creds factory spec. !32176 (Rajendra Kadam)
- Use applogger in project import state file. !32182 (Rajendra Kadam)
- Use applogger in project.rb. !32183 (Rajendra Kadam)
- Use applogger in chat_team.rb. !32184 (Rajendra Kadam)
- Use applogger in repository model. !32185 (Rajendra Kadam)
- Use applogger in build and ssh host key. !32187 (Rajendra Kadam)
- Use applogger in cache attrs and highest role ruby files. !32189 (Rajendra Kadam)
- Use applogger in legacy project and namespace. !32190 (Rajendra Kadam)
- Use applogger in base.rb. !32191 (Rajendra Kadam)
- Use applogger in usage ping and webhook service. !32192 (Rajendra Kadam)
- Use applogger in exclusive_lease_guard. !32194 (Rajendra Kadam)
- Use applogger in groups destroy service and label create service. !32195 (Rajendra Kadam)
- Use applogger in merge_service.rb. !32196 (Rajendra Kadam)
- Use applogger in project create service and after import service. !32198 (Rajendra Kadam)
- Use applogger in update stats service. !32200 (Rajendra Kadam)
- Use applogger in base attachment service. !32201 (Rajendra Kadam)
- Use applogger in export service. !32203 (Rajendra Kadam)
- Use applogger in akismet service. !32205 (Rajendra Kadam)
- Use applogger in file mover file. !32206 (Rajendra Kadam)
- Use applogger in commit signature worker. !32207 (Rajendra Kadam)
- Use applogger in delete user worker. !32209 (Rajendra Kadam)
- Use applogger in email receiver worker. !32211 (Rajendra Kadam)
- Use applogger in artifact worker. !32212 (Rajendra Kadam)
- Use applogger in new note worker. !32213 (Rajendra Kadam)
- Fix duplicate filename displayed in design todos. !32274 (Arun Kumar Mohan)
- Add value length validations for instance level variable. !32303
- Resolve image overflow at releases list panel. !32307
- Clean up shared/tmp folder after Import/Export. !32326
- Fix creating release evidence if release is created via UI. !32441
- GraphQL hasNextPage and hasPreviousPage return correct values. !32476
- Fix loading and empty state styling for alerts list. !32531
- Resolve incorrect x-axis padding on the Environments Dashboard. !32533
- Fix time_tracking help link. !32552
- Don't display confidential note icon on confidential issue public notes. !32571
- Update container expiration policy database defaults. !32600
- Fix rendering of emojis in status tooltips. !32604
- Hid copy contents button when blob has rendering error. !32632
- Avoid refresh to show endedAt after mutation. !32636
- Fix for metrics creation when saving MR. !32668
- Skip the individual JIRA issues if failed to import vs failing the whole batch. !32673
- Hide "Import from Jira" option from non-entitled users. !32685
- Fix broken help link on operations settings page. !32722
- Allow different in bulk editing issues. !32734
- Fix whitespace changes overgrowing the diff container. !32774
- Improve spacing and wrapping of group actions buttons and stats in group list view. !32786
- Fix "Broadcast Messages" table overflow and button alignment. !32801
- Fix 404 when downloading a non-archive artifact. !32811
- Make commits author button confirm to Pajamas specs. !32821
- Fix filename duplication in design notes in activity feeds. !32823 (Arun Kumar Mohan)
- Prevent multiple Auto DevOps deployment jobs running concurrently when using manual rollout. !32824
- Implement displaying downstream pipeline error details. !32844
- Fix Runner heartbeats that results in considering them offline. !32851
- Conan package registry support for the conan_export.tgz file. !32866
- Fix plural message in account deletion section. !32868
- Fix atomic processing bumping a lock_version. !32914
- AsciiDoc: Add support for built-in alignment roles. !32928 (mnrvwl)
- Fix a bug where some Vue apps would be unable to load when DAG tab is disabled. !32966
- Fix undefined error in Gitlab::Git::Diff. !32967
- Fix spelling error on Ci::RunnersFinder. !32985 (Arthur de Lapertosa Lisboa)
- Fix polling for resource events. !33025
- Fix broken CSS classes inside alert management list. !33038
- Fix bug in snippet create mutation with non ActiveRecord errors. !33085
- Fix overflow issue in MR and Issue comments. !33100
- Fix alignment of button text on the Edit Release page. !33104
- Deduplicate URL parameters when requesting merge request diffs which causes diffs load to fail. !33117
- Fix tabbing through form fields in projects/new flow. !33209
- Fix incorrect commit search results returned when searching with ref. !33216
- Fix NoMethodError by using the correct method to report exceptions to Sentry. !33260
- Fix KaTeX font paths. !33338
- Resolve Fix Incomplete Kubernetes Cluster Status List. !33344
- Fix auto-merge not running after discussions resolved. !33371
- Fix bug in snippets updating only file_name or content. !33375
- Fix invisible emoji modal on Set Status form when clicked the second time. !33398
- vertically center action icon in the CI pipeline. !33427 (Nathanael Weber)
- Wrap auto merge parameters update in database transaction. !33471
- Return 404 response when redirecting request with invalid url. !33492
- Fix ambiguous string concatenation on CleanupProjectsWithMissingNamespace. !33497
- Fix snippet repository import edge cases. !33506
- Rust CI template: Replace --all with --workspace on cargo test. !33517 (Markus Becker)
- Make markdown textarea links tab-accessible. !33518
- Pass hard delete option to snippets bulk destroy. !33520
- Fix CI rules for ECS related jobs. !33527
- Update GitLab Workhorse to v8.34.0. !33543
- Fix snippet repository import fail with older export files. !33584
- Web IDE: Create template files in the folder from which new file request was made. !33585 (Ashesh Vidyut)
- Improve header acccessibility. !33603
- Remove non migrated snippets from failed imports. !33621
- Prevent duplicate issues when importing from CSV. !33626
- Fix sidebar spacing for alert details. !33630
- Fix linking alerts to created issues for the Generic alerts intergration. !33647
- Resolve spacing ux debt on Release assets form field. !33684
- Fix pagination link header. !33714 (Max Wittig)
- Fix Value Stream Analytics summary when using non-english locale. !33717
- Fix bug with variable substitution in alerts. !33772
- Allow wiki pages with +<> characters in their title to be saved. !33803
- Fix force_remove_source_branch not working in API. !33804
- Fix prometheus alerts not being automatically created. !33806
- Fix pagination for resource label events. !33821
- Fix relative URL root in wiki_base_path. !33841
- Return code navigation path for nil diff_refs. !33850
- Record audit event when an admin creates a new SSH Key for a user via the API. !33859 (Rajendra Kadam)
- Do not create duplicate issues for exising Alert Management alerts. !33860
- Add link text to collapsed left sidebar links for screen readers. !33866
- Update text in error tracking list error message. !33872
- Adjust wrong column reference for ResetMergeStatus (background job). !33899
- Fixed dashboard YAML file validaiton for files which do not contain object as root element. !33935
- Fix design note scrolling. !33939
- Update validates_hostname gem with support for more TLDs. !34010
- Update wording of addMultipleToDiscussionWarning. !34088
- Show all storages in settings. !34093
- Set author as nullable in snippet GraphQL Type. !34135
- Fix rendering of very long paths in merge request file tree. !34153
- Remove not null constraint from events tables. !34190
- Ensure we always generate a valid wiki event URL. !34191
- Send information about attached files to the GraphQL mutation. !34221
- Update issue limits template to use minutes. !34254
- Add route for the lost-and-found group and update the route of orphaned projects. !34285
- GraphQL - properly handle pagination of millisecond-precision timestamps. !34352
- Fix 500 error in BlobController#delete. !34367
- Updated Auto DevOps with a fix to delete PostgreSQL PVC on environment cleanup, a fix for multiline K8S_SECRET variables, updated Helm to 2.16.7 and glibc to 2.31. !34399 (verenion)
- Fix issues with scroll on iOS / iPad OS. !34486
- Fix order of integrations to be sorted alphabetically. !34501
- Fix undefined method error. !34522
- Use Keys::DestroyService for deleting an SSH key when an admin deletes a key via the API. !34535 (Rajendra Kadam)
- Removed default artifact name for Terraform template. !34557
- Footer system message fix.
- Set experiementation cookie for GitLab domain only.
- Add DS detection of build.gradle.kts.
### Changed (76 changes, 5 of them are from the community)
- Add a GraphQL endpoint to fetch Jira projects through its REST API. !28190
- Change legends in monitor dashboards to tabular layout. !30131
- Move pipelines routing under /-/ scope. !30730
- Set markdown toolbar to use hyphens for lists. !31426
- Use sprites for comment icons on Commits. !31696
- Rate limit project export by user. !31719
- Reorder diffs compare versions dropdowns. !31770 (Gilang Gumilar)
- Enable the `in this group` action in the Search dropdown. !31939
- Externalize i18n strings from ./app/views/shared/_promo.html.haml. !32109 (Gilang Gumilar)
- Add Usage Ping count for all searches. !32111
- Add tags_count to container registry api and controller. !32141
- Externalize i18n strings from ./app/views/shared/milestones/_sidebar.html.haml. !32150 (Gilang Gumilar)
- Externalize i18n strings from ./app/views/shared/milestones/_form_dates.html.haml. !32162 (Gilang Gumilar)
- Improve Container Registry UI header. !32424
- Added node size to cluster index. !32435
- Update operations metrics settings title and description to make them general. !32494
- Track merge_requests_users usage data. !32562
- Adds cluster CPU and Memory to cluster index. !32601
- Allow the snippet create service to accept an array of files. !32649
- Move review related controllers/workers outside EE. !32663
- Move the Members section from settings to the side nav for projects. !32667
- Show more context in unresolved jump button. !32737
- Exclude extra.server fields from exceptions_json.log. !32770
- Improve new/unknown sign-in email styling. !32808
- Allow the snippet update service to accept an array of files. !32832
- Add new issue link to email notification header. !32833
- Bump cluster-applications to 0.17.0, which updates Runner to 0.17.0 and Cilium to 1.7.4. !32931
- Update artifacts section to show when an artifact is locked. !32992
- Include tag count in the image repository list. !33027
- Clean up gitlab-shell install-from-source path. !33057
- Increase LFS token default time to 2 hours. !33140
- Add explicit mention of Merge request in Slack message. !33152
- Expose `release_links.type` via API. !33154
- Add link_type column to release_links table. !33156
- Move broadcast notification dismiss button to the top. !33174
- Remove null constraint for JID in GroupImportState. !33181
- Added provider type icon to cluster list. !33196
- Remove search icon from Project find file button. !33198
- Refine SAST language detection by frameworks. !33226
- Render Merge request reference as link. !33248
- Upgrade to Gitaly v13.1.0-rc1. !33302
- Show disabled suggestion button with tooltip message. !33357
- Add update validations to SnippetInputAction. !33379
- Add snippet DB visibility check in spec. !33388 (Jacopo Beschi @jacopo-beschi)
- Add Hugo logo to project templates. !33402
- Add GitBook logo to project templates. !33403
- Add GoMicro logo to project templates. !33404
- Add Jekyll logo to project templates. !33405
- Add Hexo logo to project templates. !33406
- Rename Add Designs button. !33491
- Add CPU, memory usage charts to self monitoring default dashboard. !33532
- Add database migrations to design_management_designs.filename to enforce a 255 character limit, and modify any filenames that exceed that limit. !33565
- Track Sentry error status updates with dedicated actions. !33623
- Alert Managament: Change sorting order to have newest alerts first. !33642
- Add blobs field to SnippetType in GraphQL. !33657
- Format metrics column chart x axis dates. !33681
- Style ToastUI contextual menus. !33719
- Update Auto deploy image to v0.16.1, introducing support for AUTO_DEVOPS_DEPLOY_DEBUG. !33799
- Add whether instance has Auto DevOps enabled to usage ping. !33811
- Update local IP address and domain name allow list input label. !33812
- Add date time format to the monitor stacked-column chart. !33814
- Allow Tf Plan to genrate multiple reports. !33867
- Remove async_merge_request_check_mergeability feature flag. !33917
- Filter potentially-sensitive Sidekiq arguments from logs and Sentry. !33967
- Update Static Site Editor toolbar to group inline-code and code-block buttons together. !34006
- Set default values for SAST_EXCLUDED_PATHS and DS_EXCLUDED_PATHS. !34076
- Add ability to filter self monitoring resource usage charts by instance name. !34084
- Pick repository storage based on weight. !34095
- Display error for YAML files that are too large. !34199
- Change copy of webhooks / integration help text. !34301
- Update board header icons. !34366
- Show Redis instance in performance bar. !34377
- Add secret detection template to Auto DevOps. !34467
- Add allowed actions to snippet input action. !34499
- Change from vendor specific to Gitlab. !34576
- Assign alerts sidebar base.
### Performance (19 changes, 1 of them is from the community)
- Improve performance of commit search by limiting the number of results requested. !32260
- Add GraphQL lookahead support. !32373
- Update index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial index for secret_detection. !32584
- Add index on id and type for Snippets. !32885
- Use build_stubbed to avoid interacting with the DB in todos helper specs. !32906 (Arun Kumar Mohan)
- Optimize SQL queries on Milestone index page. !32953
- Add build report results data model. !32991
- Adjust condition for partial indexes on services table. !33044
- Add index to issues and epics on last_edited_by_id. !33075
- Fix preconnect typo in rel link. !33255
- Add project_id, user_id, status, ref index to ci_pipelines. !33290
- Move migration related to ci_builds to post_deployment. !33416
- Reduce redundant queries for Search API users scope. !33795
- Speed up boot time in production. !33929
- Harden CI pipelines usage data queries with an index. !34045
- Add partial index on locked merge requets. !34127
- Lazy load commit_date and authored_date on Commit. !34181
- Optimize container repository for groups query. !34364
- Enable CI Atomic Processing by default.
### Added (149 changes, 14 of them are from the community)
- Add rake task to verify encrypted data through secrets. !21851
- User can apply multiple suggestions at the same time. !22439 (Jesse Hall)
- Resolve Add a button to assign users who have commented on an issue. !23883
- Resolve Graph code coverage changes over time for a project. !26174
- Add doc for custom validators in api styleguide. !26734 (Rajendra Kadam)
- Add Scheduled Job for Monitoring Monitor Group Demo Environments. !27360
- Add setting to allow merge on skipped pipeline. !27490 (Mathieu Parent)
- Add dark theme (alpha). !28252
- Show estimate on issues list. !28271 (Lee Tickett)
- Make Fixed Email Notification Generally Available. !28338 (jacopo-beschi)
- Add a link to the `renamed` viewer to fully expand the renamed file (if it's text). !28448
- Focus and toggle metrics dashboard panels via keyboard. !28603
- Remove `scoped_approval_rules` feature flag. !28864 (Lee Tickett)
- Create Group import UI for creating new Groups. !29271
- Add finder for group-level runners. !29283 (Arthur de Lapertosa Lisboa)
- Allow customization of badge key_text and key_width. !29381 (Fabian Schneider @fabsrc)
- Support Workhorse directly uploading files to S3. !29389
- Add frontend support for multiline comments. !29516
- Support first_name and last_name attributes in LDAP user sync. !29542
- Add link to status page detail view for status page published issues. !30249
- Add metrics dashboard name to document title. !30392
- Backfill StatusPage::Published incidents and enable a publish quick action for EE. !30906
- Add missing Merge Request fields. !30935
- Show build status on branch list. !30948 (Lee Tickett)
- Add mutation to create commits in GraphQL. !31102
- Add GraphQL support for authored and assigned Merge Requests. !31227
- Add usage data metrics for terraform states. !31280
- Add usage data metrics for terraform reports. !31281
- Add API endpoint for listing bridge jobs. !31370 (Abhijith Sivarajan)
- SpamVerdictService can call external spam check endpoint. !31449
- Move Admin note feature to GitLab Core. !31457 (Rajendra)
- Add DAG serializer for pipelines controller. !31583
- Save repository storages in application settings with weights. !31645
- Add API endpoint for resource milestone events. !31720
- Show import in progress screen for group imports. !31731
- Add Verify/FailFast CI template. !31812
- Improve Add/Remove Issue Labels API. !31864 (Lee Tickett)
- Add mutation to create a merge request in GraphQL. !31867
- Add warning popup for Elastic Stack update. !31972
- Add API support for sharing groups with groups. !32008
- Add the container expiration policy attribute to the project GraphQL type. !32100
- Add GraphQL support for project and group labels. !32113
- Add number of database calls to Prometheus metrics and logs for sidekiq and request. !32131
- Filter pipelines by status. !32151
- Filter pipelines based on url query params. !32230
- Add metrics for Redis usage during Sidekiq job execution. !32265
- Add filters to merge request fields. !32328
- Support reading .editorconfig files inside of the Web IDE. !32378
- [Frontend] Resolvable design discussions. !32399
- Table index added to `metrics_dashboard_annotations` for future pruning of stale metrics Annotations for metrics dashboards are now checked for valid start and end dates. !32433
- Enable GitLab-Flavored Markdown processing for design links. !32446
- Filter Pipelines by Tag Name. !32470
- Adds sorting by column to alert management list. !32478
- Add project specific repository storage API. !32493
- Adapt Limitable for system-wide features. !32574
- Add application limits to instance level CI/CD variables. !32575
- Add model for project level security auto-fix settings. !32577
- Expose Jira imported issues count in GraphQL. !32580
- Organize alerts by status tabs. !32582
- Add note to ECS CI template. !32597
- Add metrics for Redis usage during web requests. !32605
- Add database and GraphQL support for alert assignees. !32609
- Set fingerprints and increment events count for Alert Management alerts. !32613
- Process stuck jira import jobs. !32643
- Allow user to add custom links to their metrics dashboard panels. !32646
- Add tags to experimental queue selector attributes. !32651
- Allow generic endpoint to receive alerts from external Prometheus. !32676
- Customize the Cloud Native Buildpack builder used with Auto Build. !32691
- Add timezone display to alert based issue start time. !32702
- Display dates on metrics dashboards in UTC time zone. !32746
- Store Todo resolution method. !32753
- Add experience_level to user_preferences. !32784
- Remove metrics dashboard annotations attached to time periods older than two weeks. !32838
- Monitor:Health metrics instrumenation. !32846
- Adds PostHog as a CI/CD Managed Application. !32856
- Groups API has top_level_only option to exclude subgroups. !32870
- Create operations_feature_flags_issues table. !32876
- Add api.js methods to update issues and merge requests. !32893
- Render user-defined links in dashboard yml file on metrics dashboard. !32895
- Add accessibility report MR widget. !32902
- Add a GraphQL mutation for toggling the resolved state of a Discussion. !32934
- Add container expiration policy objects to the GraphQL API. !32944
- Don't hide Commit tab in Web IDE when there are no changes yet. !32979
- Add column for alert slack notifications. !33017
- Add ability to insert an image via SSE. !33029
- Add user root query to GraphQL API. !33041
- Adds groupMembership and projectMembership to GraphQL API. !33049
- Alerts list pagination. !33073
- Add ApplicationSetting ui changes for repository_storages_weighted. !33096
- Display confirmation modal when user exits SSE and there are unsaved changes. !33103
- Add column dashboard_timezone to project_metrics_setting. !33120
- Allow the assignment of alerts to users from the alert detail view. !33122
- Add solarized dark for Web IDE. !33148
- Add support for artifacts/exclude configuration. !33170
- Add root users query to GraphQL API. !33195
- Added validation for YAML files with metrics dashboard definitions. !33202
- Create issue from alert. !33213
- Add max import file size option. !33215 (Roger Meier)
- Add system note when assigning user to alert. !33217
- Add count of alerts from all sources to usage ping. !33220
- Add button to create an issue from an alert management alert. !33221
- Add more detail to alert integration settings description. !33244
- Add Evidence to Releases GraphQL endpoint. !33254
- Add support for pasting images in the Web IDE. !33256
- Add ProjectAccessToken table. !33272
- Automatically resolve alert when associated issue closes. !33278
- Add `link_type` to `ReleaseLink` GraphQL type. !33386
- Add members to project graphQL endpoint. !33418
- Update Static Site Editor WYSIWYG mode to hide front matter. !33441
- Added delete action for Dashboard Annotations in GraphQL. !33468
- Create graphQL endpoint for Jira users import. !33501
- Support IAP protected prometheus installations. !33508
- New instance-level variables UI. !33510
- Provide `__range` variable for Prometheus queries. !33521
- Add support for `git filter-repo` to repository cleanup. !33576
- Close open reply input fields in the design view sidebar when leaving a new comment. !33587
- Add dashboard schema validation warnings as metrics dashboard GraphQL field. !33592
- Add time range to user-defined links in metrics dashboard. !33663
- Increase events count for Prometheus alerts. !33706
- Track pod logs refresh action. !33802
- Add secret detection template. !33869
- Add DAG visualization MVC. !33958
- Introduce a feature flag for Vue-based UI for all import providers. !33980
- Add sticky title on Issue pages. !33983
- Allow Release asset links to be associated with a type. !33998
- Support user-defined Grafana links in metrics dashboard. !34003
- Adds AWS guidance to CI/CD > Add Variable modal. !34009
- Show custom attributes within Admin Pages. !34017 (Roger Meier)
- Enable Slack notifications for alerts. !34038
- Container expiration policy regular expressions are now validated. !34063
- Add todo when alert is assigned to a user. !34104
- Track merge requests submitted by Static Site Editor. !34105
- Turn off alert issue creation by default. !34107
- Add detailed logs of each Redis instance usage during job execution and web requests. !34110
- Add API to schedule project repository storage moves. !34119
- Add validation step on backend for metrics dashboard links. !34204
- Track when Static Site Editor is initialized. !34215
- Bring SAST to Core - brakeman. !34217
- Mask key comments when exposing SSH/Deploy Keys via the API. !34255
- Convert `:release` yaml to `release-cli` commands. !34261
- Validate regex before sending them to CleanupContainerRepositoryWorker. !34282
- Add secret_detection to DOWNLOADABLE_TYPES. !34313
- Enable ability to assign alerts to users with corresponding system notes and todos. !34360
- Enable CI Inheriting Env Variables feature. !34495
- Show tooltip on error detail page when hovering over dates. !34506
- Add native code intelligence. !34542
- Bump cluster-applications version to v0.20.0. !34569
- Add search argument for AlertStatusCountsResolver. !34596
- Allow CI_JOB_TOKEN for authenticating to the Terraform state API. !34618
### Other (65 changes, 36 of them are from the community)
- Improve fast-forward merge is not possible message. !22834 (Ben Bodenmiller)
- Remove unused WAF indexes from CI variables. !30021
- Update the visual design of badges in some areas. !31646
- Extract featurable concern from ProjectFeature. !31700 (Alexander Randa)
- Remove update function logic from list model. !31900 (nuwe1)
- Remove nextpage function logic from list model. !31904 (nuwe1)
- Squash database migrations prior to 2019 into one. !31936
- Update deprecated slot syntax in app/assets/javascripts/reports/components/grouped_test_reports_app.vue. !31975 (Gilang Gumilar)
- Replace slot syntax for Vue 3 migration. !31987 (gaslan)
- Update deprecated slot syntax in ./app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue. !31994 (Gilang Gumilar)
- Update deprecated slot syntax in ./app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue. !31995 (Gilang Gumilar)
- Update deprecated slot syntax in ./app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue. !32010 (Gilang Gumilar)
- Update deprecated slot syntax in ./app/assets/javascripts/environments/components/environments_app.vue. !32011 (Gilang Gumilar)
- Remove setLoadingState logic from issue model. !32226 (nuwe1)
- Remove addAssignee logic from issue model. !32231 (nuwe1)
- Remove addLabel Logic from issue models. !32233 (nuwe1)
- Remove addMilestone logic from issue model. !32235 (nuwe1)
- Remove destroy function logic from list model. !32237 (nuwe1)
- Remove findAssignee logic from issue model. !32238 (nuwe1)
- Remove findLabel logic from issue model. !32239 (nuwe1)
- Remove findIssue logic from list model. !32241 (nuwe1)
- Remove moveIssue logic from list model. !32242 (nuwe1)
- Remove newIssue logic from list model. !32244 (nuwe1)
- Remove removeAllAssignees logic from issue model. !32247 (nuwe1)
- Remove removeAssignee logic from issue model. !32248 (nuwe1)
- Clarify verbiage for stuck job messages. !32250
- Remove removeLabel logic from issue model. !32251 (nuwe1)
- Remove removeLabels logic from issue model. !32252 (nuwe1)
- Remove removeMilestone logic from issue model. !32253 (nuwe1)
- Remove removeMultipleIssues logic from list model. !32254 (nuwe1)
- Remove setFetchingState logic from issue model. !32255 (nuwe1)
- Remove updateData logic from issue model. !32256 (nuwe1)
- Update U2F docs for Firefox 67+. !32289 (Takuya Noguchi)
- Update alert management mobile table alignment. !32295
- Include available instance memory in usage ping. !32315
- Moves merge request reviews into Core. !32558
- Update GitLab Runner Helm Chart to 0.17.0. !32634
- Add snowplow tracking for logs page. !32704
- Extend "Remember me" token after each login. !32730
- Assign alerts sidebar container fix. !32743
- Add anchor for creating a branch. !32745
- Tidy. !32759 (Lee Tickett)
- Less verbose JiraService error logs. !32847
- Reduced padding and increased emphasis of titles within the epic tree. !32873
- Remove obsolete users.ghost column. !32957
- Move NoPrimary table def to last context in spec. !33015 (Rajendra Kadam)
- Document github rate limit behavior. !33090
- Added build_id column to requirements_management_test_reports table. !33184
- Add version history information on U2F support. !33229 (Takuya Noguchi)
- Convert IP spoofing errors into client errors. !33280
- Update docs to reflect move web IDE Terminal and file sync to Core. !33419
- Add hovering icon for sorting columns on alert management list. !33429
- Avoid javascript for omniauth logins. !33459 (Diego Louzán)
- Add opacity transition to active design discussion pins. !33493
- Update GitLab Runner Helm Chart to 0.17.1. !33504
- Make project selector in various dashboard more translatable. !33771
- Update Workhorse to v8.35.0. !33817
- Remove FF hide_token_from_runners_api. !33947
- Bump omniauth_openid_connect to 0.3.5. !34030 (Roger Meier)
- Specify tiers for SAML SSO at self-hosted plans. !34040 (Takuya Noguchi)
- Backfill failed imported snippet repositories. !34052
- Use GitLab SVG icon for file attacher action. !34196
- Add GraphQL snippet FileInputType. !34442
- Update red hex values to match GitLab UI. !34544
- Remove removeIssue logic from list model. (nuwe1)
## 13.0.6 (2020-06-10) ## 13.0.6 (2020-06-10)
- No changes. - No changes.
## 13.0.5 (2020-06-04)
### Fixed (4 changes)
- Fix NoMethodError by using the correct method to report exceptions to Sentry. !33260
- Fix bug in snippets updating only file_name or content. !33375
- Fix ambiguous string concatenation on CleanupProjectsWithMissingNamespace. !33497
- Fix linking alerts to created issues for the Generic alerts intergration. !33647
### Other (1 change)
- Update GitLab Workhorse to v8.31.2. !33818
## 13.0.4 (2020-06-03) ## 13.0.4 (2020-06-03)
### Security (1 change) ### Security (1 change)
@ -41,10 +515,6 @@ entry.
- Fix close issue when user created the issue. !33294 - Fix close issue when user created the issue. !33294
## 13.0.2 (2020-05-28)
- No changes.
## 13.0.1 (2020-05-27) ## 13.0.1 (2020-05-27)
### Security (12 changes) ### Security (12 changes)
@ -632,6 +1102,49 @@ entry.
- Use visitUrl in Alert management. !32414 - Use visitUrl in Alert management. !32414
## 12.10.11 (2020-06-10)
- No changes.
## 12.10.8 (2020-05-28)
### Fixed (2 changes)
- Fix Geo replication for design thumbnails. !32703
- Fix 404s downloading build artifacts. !32741
## 12.10.7 (2020-05-27)
### Security (14 changes)
- Add an extra validation to Static Site Editor payload.
- Hide EKS secret key in admin integrations settings.
- Added data integrity check before updating a deploy key.
- Display only verified emails on notifications and profile page.
- Disable caching on repo/blobs/[sha]/raw endpoint.
- Require confirmed email address for GitLab OAuth authentication.
- Kubernetes cluster details page no longer exposes Service Token.
- Fix confirming unverified emails with soft email confirmation flow enabled.
- Disallow user to control PUT request using mermaid markdown in issue description.
- Check forked project permissions before allowing fork.
- Limit memory footprint of a command that generates ZIP artifacts metadata.
- Fix file enuming using Group Import.
- Prevent XSS in the monitoring dashboard.
- Use `gsub` instead of the Ruby `%` operator to perform variable substitution in Prometheus proxy API.
## 12.10.6 (2020-05-15)
### Fixed (5 changes)
- Fix duplicate index removal on ci_pipelines.project_id. !31043
- Fix 500 on creating an invalid domains and verification. !31190
- Fix incorrect number of errors returned when querying sentry errors. !31252
- Add instance column to services table if it's missing. !31631
- Fix incorrect regex used in FileUploader#extract_dynamic_path. !32271
## 12.10.5 (2020-05-13) ## 12.10.5 (2020-05-13)
### Added (1 change) ### Added (1 change)
@ -1121,6 +1634,29 @@ entry.
- Remove store_mentions! in Snippets::CreateService. !29581 (Sashi Kumar) - Remove store_mentions! in Snippets::CreateService. !29581 (Sashi Kumar)
## 12.9.10 (2020-06-10)
- No changes.
## 12.9.8 (2020-05-27)
### Security (13 changes)
- Hide EKS secret key in admin integrations settings.
- Added data integrity check before updating a deploy key.
- Display only verified emails on notifications and profile page.
- Disable caching on repo/blobs/[sha]/raw endpoint.
- Require confirmed email address for GitLab OAuth authentication.
- Kubernetes cluster details page no longer exposes Service Token.
- Fix confirming unverified emails with soft email confirmation flow enabled.
- Disallow user to control PUT request using mermaid markdown in issue description.
- Check forked project permissions before allowing fork.
- Limit memory footprint of a command that generates ZIP artifacts metadata.
- Fix file enuming using Group Import.
- Prevent XSS in the monitoring dashboard.
- Use `gsub` instead of the Ruby `%` operator to perform variable substitution in Prometheus proxy API.
## 12.9.6 (2020-05-05) ## 12.9.6 (2020-05-05)
### Fixed (1 change) ### Fixed (1 change)

View file

@ -7,8 +7,16 @@ danger.import_plugin('danger/plugins/helper.rb')
danger.import_plugin('danger/plugins/roulette.rb') danger.import_plugin('danger/plugins/roulette.rb')
danger.import_plugin('danger/plugins/changelog.rb') danger.import_plugin('danger/plugins/changelog.rb')
unless helper.release_automation? return if helper.release_automation?
GitlabDanger.new(helper.gitlab_helper).rule_names.each do |file|
danger.import_dangerfile(path: File.join('danger', file)) gitlab_danger = GitlabDanger.new(helper.gitlab_helper)
end
gitlab_danger.rule_names.each do |file|
danger.import_dangerfile(path: File.join('danger', file))
end
anything_to_post = status_report.values.any? { |data| data.any? }
if gitlab_danger.ci? && anything_to_post
markdown("**If needed, you can retry the [`danger-review` job](#{ENV['CI_JOB_URL']}) that generated this comment.**")
end end

View file

@ -1 +1 @@
13.0.6 203182ffe94da165d4ff81332b1b3fff9771e631

View file

@ -1 +1 @@
13.2.0 13.3.0

View file

@ -1 +1 @@
8.31.2 8.35.0

23
Gemfile
View file

@ -1,6 +1,6 @@
source 'https://rubygems.org' source 'https://rubygems.org'
gem 'rails', '~> 6.0.3' gem 'rails', '~> 6.0.3.1'
gem 'bootsnap', '~> 1.4.6' gem 'bootsnap', '~> 1.4.6'
@ -43,8 +43,7 @@ gem 'omniauth-shibboleth', '~> 1.3.0'
gem 'omniauth-twitter', '~> 1.4' gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.3.3' gem 'omniauth-authentiq', '~> 0.3.3'
gem 'omniauth_openid_connect', '~> 0.3.3' gem 'omniauth_openid_connect', '~> 0.3.5'
gem "omniauth-ultraauth", '~> 0.0.2'
gem 'omniauth-salesforce', '~> 1.0.5' gem 'omniauth-salesforce', '~> 1.0.5'
gem 'rack-oauth2', '~> 1.9.3' gem 'rack-oauth2', '~> 1.9.3'
gem 'jwt', '~> 2.1.0' gem 'jwt', '~> 2.1.0'
@ -64,7 +63,7 @@ gem 'attr_encrypted', '~> 3.1.0'
gem 'u2f', '~> 0.2.1' gem 'u2f', '~> 0.2.1'
# GitLab Pages # GitLab Pages
gem 'validates_hostname', '~> 1.0.6' gem 'validates_hostname', '~> 1.0.10'
gem 'rubyzip', '~> 2.0.0', require: 'zip' gem 'rubyzip', '~> 2.0.0', require: 'zip'
# GitLab Pages letsencrypt support # GitLab Pages letsencrypt support
gem 'acme-client', '~> 2.0.5' gem 'acme-client', '~> 2.0.5'
@ -113,14 +112,14 @@ gem 'fog-aws', '~> 3.5'
# Locked until fog-google resolves https://github.com/fog/fog-google/issues/421. # Locked until fog-google resolves https://github.com/fog/fog-google/issues/421.
# Also see config/initializers/fog_core_patch.rb. # Also see config/initializers/fog_core_patch.rb.
gem 'fog-core', '= 2.1.0' gem 'fog-core', '= 2.1.0'
gem 'fog-google', '~> 1.9' gem 'fog-google', '~> 1.10'
gem 'fog-local', '~> 0.6' gem 'fog-local', '~> 0.6'
gem 'fog-openstack', '~> 1.0' gem 'fog-openstack', '~> 1.0'
gem 'fog-rackspace', '~> 0.1.1' gem 'fog-rackspace', '~> 0.1.1'
gem 'fog-aliyun', '~> 0.3' gem 'fog-aliyun', '~> 0.3'
# for Google storage # for Google storage
gem 'google-api-client', '~> 0.23' gem 'google-api-client', '~> 0.33'
# for aws storage # for aws storage
gem 'unf', '~> 0.1.4' gem 'unf', '~> 0.1.4'
@ -343,7 +342,7 @@ group :development do
end end
group :development, :test do group :development, :test do
gem 'bullet', '~> 6.0.2', require: !!ENV['ENABLE_BULLET'] gem 'bullet', '~> 6.0.2'
gem 'pry-byebug', '~> 3.5.1', platform: :mri gem 'pry-byebug', '~> 3.5.1', platform: :mri
gem 'pry-rails', '~> 0.3.9' gem 'pry-rails', '~> 0.3.9'
@ -362,10 +361,10 @@ group :development, :test do
gem 'spring', '~> 2.0.0' gem 'spring', '~> 2.0.0'
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'gitlab-styles', '~> 3.2.0', require: false gem 'gitlab-styles', '~> 4.2.0', require: false
# Pin these dependencies, otherwise a new rule could break the CI pipelines # Pin these dependencies, otherwise a new rule could break the CI pipelines
gem 'rubocop', '~> 0.74.0' gem 'rubocop', '~> 0.82.0'
gem 'rubocop-performance', '~> 1.4.1' gem 'rubocop-performance', '~> 1.5.2'
gem 'rubocop-rspec', '~> 1.37.0' gem 'rubocop-rspec', '~> 1.37.0'
gem 'scss_lint', '~> 0.56.0', require: false gem 'scss_lint', '~> 0.56.0', require: false
@ -403,7 +402,6 @@ group :test do
gem 'shoulda-matchers', '~> 4.0.1', require: false gem 'shoulda-matchers', '~> 4.0.1', require: false
gem 'email_spec', '~> 2.2.0' gem 'email_spec', '~> 2.2.0'
gem 'json-schema', '~> 2.8.0'
gem 'webmock', '~> 3.5.1' gem 'webmock', '~> 3.5.1'
gem 'rails-controller-testing' gem 'rails-controller-testing'
gem 'concurrent-ruby', '~> 1.1' gem 'concurrent-ruby', '~> 1.1'
@ -454,7 +452,7 @@ group :ed25519 do
end end
# Gitaly GRPC protocol definitions # Gitaly GRPC protocol definitions
gem 'gitaly', '~> 13.0.0.pre.rc1' gem 'gitaly', '~> 13.1.0.pre.rc1'
gem 'grpc', '~> 1.24.0' gem 'grpc', '~> 1.24.0'
@ -498,3 +496,4 @@ gem 'valid_email', '~> 0.1'
# JSON # JSON
gem 'json', '~> 2.3.0' gem 'json', '~> 2.3.0'
gem 'json-schema', '~> 2.8.0'

View file

@ -6,59 +6,59 @@ GEM
ace-rails-ap (4.1.2) ace-rails-ap (4.1.2)
acme-client (2.0.5) acme-client (2.0.5)
faraday (~> 0.9, >= 0.9.1) faraday (~> 0.9, >= 0.9.1)
actioncable (6.0.3) actioncable (6.0.3.1)
actionpack (= 6.0.3) actionpack (= 6.0.3.1)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (6.0.3) actionmailbox (6.0.3.1)
actionpack (= 6.0.3) actionpack (= 6.0.3.1)
activejob (= 6.0.3) activejob (= 6.0.3.1)
activerecord (= 6.0.3) activerecord (= 6.0.3.1)
activestorage (= 6.0.3) activestorage (= 6.0.3.1)
activesupport (= 6.0.3) activesupport (= 6.0.3.1)
mail (>= 2.7.1) mail (>= 2.7.1)
actionmailer (6.0.3) actionmailer (6.0.3.1)
actionpack (= 6.0.3) actionpack (= 6.0.3.1)
actionview (= 6.0.3) actionview (= 6.0.3.1)
activejob (= 6.0.3) activejob (= 6.0.3.1)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (6.0.3) actionpack (6.0.3.1)
actionview (= 6.0.3) actionview (= 6.0.3.1)
activesupport (= 6.0.3) activesupport (= 6.0.3.1)
rack (~> 2.0, >= 2.0.8) rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.0.3) actiontext (6.0.3.1)
actionpack (= 6.0.3) actionpack (= 6.0.3.1)
activerecord (= 6.0.3) activerecord (= 6.0.3.1)
activestorage (= 6.0.3) activestorage (= 6.0.3.1)
activesupport (= 6.0.3) activesupport (= 6.0.3.1)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (6.0.3) actionview (6.0.3.1)
activesupport (= 6.0.3) activesupport (= 6.0.3.1)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.0.3) activejob (6.0.3.1)
activesupport (= 6.0.3) activesupport (= 6.0.3.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (6.0.3) activemodel (6.0.3.1)
activesupport (= 6.0.3) activesupport (= 6.0.3.1)
activerecord (6.0.3) activerecord (6.0.3.1)
activemodel (= 6.0.3) activemodel (= 6.0.3.1)
activesupport (= 6.0.3) activesupport (= 6.0.3.1)
activerecord-explain-analyze (0.1.0) activerecord-explain-analyze (0.1.0)
activerecord (>= 4) activerecord (>= 4)
pg pg
activestorage (6.0.3) activestorage (6.0.3.1)
actionpack (= 6.0.3) actionpack (= 6.0.3.1)
activejob (= 6.0.3) activejob (= 6.0.3.1)
activerecord (= 6.0.3) activerecord (= 6.0.3.1)
marcel (~> 0.3.1) marcel (~> 0.3.1)
activesupport (6.0.3) activesupport (6.0.3.1)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
@ -286,7 +286,7 @@ GEM
factory_bot_rails (5.1.0) factory_bot_rails (5.1.0)
factory_bot (~> 5.1.0) factory_bot (~> 5.1.0)
railties (>= 4.2.0) railties (>= 4.2.0)
faraday (0.15.4) faraday (0.17.3)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
faraday-http-cache (2.0.0) faraday-http-cache (2.0.0)
faraday (~> 0.8) faraday (~> 0.8)
@ -330,11 +330,11 @@ GEM
excon (~> 0.58) excon (~> 0.58)
formatador (~> 0.2) formatador (~> 0.2)
mime-types mime-types
fog-google (1.9.1) fog-google (1.10.0)
fog-core (<= 2.1.0) fog-core (<= 2.1.0)
fog-json (~> 1.2) fog-json (~> 1.2)
fog-xml (~> 0.1.0) fog-xml (~> 0.1.0)
google-api-client (~> 0.23.0) google-api-client (>= 0.32, < 0.34)
fog-json (1.2.0) fog-json (1.2.0)
fog-core fog-core
multi_json (~> 1.10) multi_json (~> 1.10)
@ -377,7 +377,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
git (1.5.0) git (1.5.0)
gitaly (13.0.0.pre.rc1) gitaly (13.1.0.pre.rc1)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-chronic (0.10.5) gitlab-chronic (0.10.5)
@ -400,11 +400,11 @@ GEM
gitlab-puma (>= 2.7, < 5) gitlab-puma (>= 2.7, < 5)
gitlab-sidekiq-fetcher (0.5.2) gitlab-sidekiq-fetcher (0.5.2)
sidekiq (~> 5) sidekiq (~> 5)
gitlab-styles (3.2.0) gitlab-styles (4.2.0)
rubocop (~> 0.74.0) rubocop (~> 0.82.0)
rubocop-gitlab-security (~> 0.1.0) rubocop-gitlab-security (~> 0.1.0)
rubocop-performance (~> 1.4.1) rubocop-performance (~> 1.5.2)
rubocop-rails (~> 2.0) rubocop-rails (~> 2.5)
rubocop-rspec (~> 1.36) rubocop-rspec (~> 1.36)
gitlab_chronic_duration (0.10.6.2) gitlab_chronic_duration (0.10.6.2)
numerizer (~> 0.2) numerizer (~> 0.2)
@ -419,23 +419,24 @@ GEM
actionpack (>= 3.0) actionpack (>= 3.0)
multi_json multi_json
request_store (>= 1.0) request_store (>= 1.0)
google-api-client (0.23.4) google-api-client (0.33.2)
addressable (~> 2.5, >= 2.5.1) addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.5, < 0.7.0) googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0) httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0) mini_mime (~> 1.0)
representable (~> 3.0) representable (~> 3.0)
retriable (>= 2.0, < 4.0) retriable (>= 2.0, < 4.0)
signet (~> 0.12)
google-protobuf (3.8.0) google-protobuf (3.8.0)
googleapis-common-protos-types (1.0.4) googleapis-common-protos-types (1.0.4)
google-protobuf (~> 3.0) google-protobuf (~> 3.0)
googleauth (0.6.6) googleauth (0.12.0)
faraday (~> 0.12) faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0) jwt (>= 1.4, < 3.0)
memoist (~> 0.12) memoist (~> 0.16)
multi_json (~> 1.11) multi_json (~> 1.11)
os (>= 0.9, < 2.0) os (>= 0.9, < 2.0)
signet (~> 0.7) signet (~> 0.14)
gpgme (2.0.20) gpgme (2.0.20)
mini_portile2 (~> 2.3) mini_portile2 (~> 2.3)
grape (1.1.0) grape (1.1.0)
@ -529,7 +530,7 @@ GEM
mime-types (~> 3.0) mime-types (~> 3.0)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpclient (2.8.3) httpclient (2.8.3)
i18n (1.8.2) i18n (1.8.3)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
i18n_data (0.8.0) i18n_data (0.8.0)
icalendar (2.4.1) icalendar (2.4.1)
@ -604,7 +605,7 @@ GEM
ruby_dep (~> 1.2) ruby_dep (~> 1.2)
locale (2.1.2) locale (2.1.2)
lockbox (0.3.3) lockbox (0.3.3)
lograge (0.10.0) lograge (0.11.2)
actionpack (>= 4) actionpack (>= 4)
activesupport (>= 4) activesupport (>= 4)
railties (>= 4) railties (>= 4)
@ -661,8 +662,8 @@ GEM
shellany (~> 0.0) shellany (~> 0.0)
numerizer (0.2.0) numerizer (0.2.0)
oauth (0.5.4) oauth (0.5.4)
oauth2 (1.4.1) oauth2 (1.4.4)
faraday (>= 0.8, < 0.16.0) faraday (>= 0.8, < 2.0)
jwt (>= 1.0, < 3.0) jwt (>= 1.0, < 3.0)
multi_json (~> 1.3) multi_json (~> 1.3)
multi_xml (~> 0.5) multi_xml (~> 0.5)
@ -722,13 +723,11 @@ GEM
omniauth-twitter (1.4.0) omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1) omniauth-oauth (~> 1.1)
rack rack
omniauth-ultraauth (0.0.2)
omniauth_openid_connect (~> 0.3.0)
omniauth_crowd (2.2.3) omniauth_crowd (2.2.3)
activesupport activesupport
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth_openid_connect (0.3.3) omniauth_openid_connect (0.3.5)
addressable (~> 2.5) addressable (~> 2.5)
omniauth (~> 1.9) omniauth (~> 1.9)
openid_connect (~> 1.1) openid_connect (~> 1.1)
@ -750,7 +749,7 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (1.0.0) os (1.0.0)
parallel (1.19.1) parallel (1.19.1)
parser (2.7.0.4) parser (2.7.1.2)
ast (~> 2.4.0) ast (~> 2.4.0)
parslet (1.8.2) parslet (1.8.2)
peek (1.1.0) peek (1.1.0)
@ -803,20 +802,20 @@ GEM
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rack-timeout (0.5.1) rack-timeout (0.5.1)
rails (6.0.3) rails (6.0.3.1)
actioncable (= 6.0.3) actioncable (= 6.0.3.1)
actionmailbox (= 6.0.3) actionmailbox (= 6.0.3.1)
actionmailer (= 6.0.3) actionmailer (= 6.0.3.1)
actionpack (= 6.0.3) actionpack (= 6.0.3.1)
actiontext (= 6.0.3) actiontext (= 6.0.3.1)
actionview (= 6.0.3) actionview (= 6.0.3.1)
activejob (= 6.0.3) activejob (= 6.0.3.1)
activemodel (= 6.0.3) activemodel (= 6.0.3.1)
activerecord (= 6.0.3) activerecord (= 6.0.3.1)
activestorage (= 6.0.3) activestorage (= 6.0.3.1)
activesupport (= 6.0.3) activesupport (= 6.0.3.1)
bundler (>= 1.3.0) bundler (>= 1.3.0)
railties (= 6.0.3) railties (= 6.0.3.1)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.4) rails-controller-testing (1.0.4)
actionpack (>= 5.0.1.x) actionpack (>= 5.0.1.x)
@ -830,9 +829,9 @@ GEM
rails-i18n (6.0.0) rails-i18n (6.0.0)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 7) railties (>= 6.0.0, < 7)
railties (6.0.3) railties (6.0.3.1)
actionpack (= 6.0.3) actionpack (= 6.0.3.1)
activesupport (= 6.0.3) activesupport (= 6.0.3.1)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0) thor (>= 0.20.3, < 2.0)
@ -888,6 +887,7 @@ GEM
mime-types (>= 1.16, < 4.0) mime-types (>= 1.16, < 4.0)
netrc (~> 0.8) netrc (~> 0.8)
retriable (3.1.2) retriable (3.1.2)
rexml (3.2.4)
rinku (2.0.0) rinku (2.0.0)
rotp (2.1.2) rotp (2.1.2)
rouge (3.19.0) rouge (3.19.0)
@ -931,18 +931,20 @@ GEM
pg pg
rails rails
sqlite3 sqlite3
rubocop (0.74.0) rubocop (0.82.0)
jaro_winkler (~> 1.5.1) jaro_winkler (~> 1.5.1)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.6) parser (>= 2.7.0.1)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
rexml
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.7) unicode-display_width (>= 1.4.0, < 2.0)
rubocop-gitlab-security (0.1.1) rubocop-gitlab-security (0.1.1)
rubocop (>= 0.51) rubocop (>= 0.51)
rubocop-performance (1.4.1) rubocop-performance (1.5.2)
rubocop (>= 0.71.0) rubocop (>= 0.71.0)
rubocop-rails (2.4.0) rubocop-rails (2.5.2)
activesupport
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 0.72.0) rubocop (>= 0.72.0)
rubocop-rspec (1.37.0) rubocop-rspec (1.37.0)
@ -1009,9 +1011,9 @@ GEM
sidekiq-cron (1.0.4) sidekiq-cron (1.0.4)
fugit (~> 1.1) fugit (~> 1.1)
sidekiq (>= 4.2.1) sidekiq (>= 4.2.1)
signet (0.11.0) signet (0.14.0)
addressable (~> 2.3) addressable (~> 2.3)
faraday (~> 0.9) faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0) jwt (>= 1.5, < 3.0)
multi_json (~> 1.10) multi_json (~> 1.10)
simple_po_parser (1.1.2) simple_po_parser (1.1.2)
@ -1083,7 +1085,7 @@ GEM
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.5) unf_ext (0.0.7.5)
unicode-display_width (1.6.0) unicode-display_width (1.7.0)
unicode_plot (0.0.4) unicode_plot (0.0.4)
enumerable-statistics (>= 2.0.1) enumerable-statistics (>= 2.0.1)
unicode_utils (1.4.0) unicode_utils (1.4.0)
@ -1113,7 +1115,7 @@ GEM
validate_url (1.0.8) validate_url (1.0.8)
activemodel (>= 3.0.0) activemodel (>= 3.0.0)
public_suffix public_suffix
validates_hostname (1.0.6) validates_hostname (1.0.10)
activerecord (>= 3.0) activerecord (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
version_sorter (2.2.4) version_sorter (2.2.4)
@ -1223,7 +1225,7 @@ DEPENDENCIES
fog-aliyun (~> 0.3) fog-aliyun (~> 0.3)
fog-aws (~> 3.5) fog-aws (~> 3.5)
fog-core (= 2.1.0) fog-core (= 2.1.0)
fog-google (~> 1.9) fog-google (~> 1.10)
fog-local (~> 0.6) fog-local (~> 0.6)
fog-openstack (~> 1.0) fog-openstack (~> 1.0)
fog-rackspace (~> 0.1.1) fog-rackspace (~> 0.1.1)
@ -1234,7 +1236,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly (~> 13.0.0.pre.rc1) gitaly (~> 13.1.0.pre.rc1)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5) gitlab-chronic (~> 0.10.5)
gitlab-labkit (= 0.12.0) gitlab-labkit (= 0.12.0)
@ -1245,11 +1247,11 @@ DEPENDENCIES
gitlab-puma (~> 4.3.3.gitlab.2) gitlab-puma (~> 4.3.3.gitlab.2)
gitlab-puma_worker_killer (~> 0.1.1.gitlab.1) gitlab-puma_worker_killer (~> 0.1.1.gitlab.1)
gitlab-sidekiq-fetcher (= 0.5.2) gitlab-sidekiq-fetcher (= 0.5.2)
gitlab-styles (~> 3.2.0) gitlab-styles (~> 4.2.0)
gitlab_chronic_duration (~> 0.10.6.2) gitlab_chronic_duration (~> 0.10.6.2)
gitlab_omniauth-ldap (~> 2.1.1) gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2) gon (~> 6.2)
google-api-client (~> 0.23) google-api-client (~> 0.33)
google-protobuf (~> 3.8.0) google-protobuf (~> 3.8.0)
gpgme (~> 2.0.19) gpgme (~> 2.0.19)
grape (~> 1.1.0) grape (~> 1.1.0)
@ -1317,9 +1319,8 @@ DEPENDENCIES
omniauth-saml (~> 1.10) omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.3.0) omniauth-shibboleth (~> 1.3.0)
omniauth-twitter (~> 1.4) omniauth-twitter (~> 1.4)
omniauth-ultraauth (~> 0.0.2)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
omniauth_openid_connect (~> 0.3.3) omniauth_openid_connect (~> 0.3.5)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
parallel (~> 1.19) parallel (~> 1.19)
peek (~> 1.1) peek (~> 1.1)
@ -1335,7 +1336,7 @@ DEPENDENCIES
rack-oauth2 (~> 1.9.3) rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0) rack-proxy (~> 0.6.0)
rack-timeout rack-timeout
rails (~> 6.0.3) rails (~> 6.0.3.1)
rails-controller-testing rails-controller-testing
rails-i18n (~> 6.0) rails-i18n (~> 6.0)
rainbow (~> 3.0) rainbow (~> 3.0)
@ -1358,8 +1359,8 @@ DEPENDENCIES
rspec-retry (~> 0.6.1) rspec-retry (~> 0.6.1)
rspec_junit_formatter rspec_junit_formatter
rspec_profiling (~> 0.0.5) rspec_profiling (~> 0.0.5)
rubocop (~> 0.74.0) rubocop (~> 0.82.0)
rubocop-performance (~> 1.4.1) rubocop-performance (~> 1.5.2)
rubocop-rspec (~> 1.37.0) rubocop-rspec (~> 1.37.0)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 1.3.0) ruby-prof (~> 1.3.0)
@ -1400,7 +1401,7 @@ DEPENDENCIES
unicorn-worker-killer (~> 0.4.4) unicorn-worker-killer (~> 0.4.4)
unleash (~> 0.1.5) unleash (~> 0.1.5)
valid_email (~> 0.1) valid_email (~> 0.1)
validates_hostname (~> 1.0.6) validates_hostname (~> 1.0.10)
version_sorter (~> 2.2.4) version_sorter (~> 2.2.4)
vmstat (~> 2.3.0) vmstat (~> 2.3.0)
webmock (~> 3.5.1) webmock (~> 3.5.1)

View file

@ -1 +1 @@
13.0.6 13.1.0

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 304 182" style="enable-background:new 0 0 304 182;" xml:space="preserve">
<style type="text/css">
.st0{fill:#252F3E;}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#FF9900;}
</style>
<g>
<path class="st0" d="M86.4,66.4c0,3.7,0.4,6.7,1.1,8.9c0.8,2.2,1.8,4.6,3.2,7.2c0.5,0.8,0.7,1.6,0.7,2.3c0,1-0.6,2-1.9,3l-6.3,4.2
c-0.9,0.6-1.8,0.9-2.6,0.9c-1,0-2-0.5-3-1.4C76.2,90,75,88.4,74,86.8c-1-1.7-2-3.6-3.1-5.9c-7.8,9.2-17.6,13.8-29.4,13.8
c-8.4,0-15.1-2.4-20-7.2c-4.9-4.8-7.4-11.2-7.4-19.2c0-8.5,3-15.4,9.1-20.6c6.1-5.2,14.2-7.8,24.5-7.8c3.4,0,6.9,0.3,10.6,0.8
c3.7,0.5,7.5,1.3,11.5,2.2v-7.3c0-7.6-1.6-12.9-4.7-16c-3.2-3.1-8.6-4.6-16.3-4.6c-3.5,0-7.1,0.4-10.8,1.3c-3.7,0.9-7.3,2-10.8,3.4
c-1.6,0.7-2.8,1.1-3.5,1.3c-0.7,0.2-1.2,0.3-1.6,0.3c-1.4,0-2.1-1-2.1-3.1v-4.9c0-1.6,0.2-2.8,0.7-3.5c0.5-0.7,1.4-1.4,2.8-2.1
c3.5-1.8,7.7-3.3,12.6-4.5c4.9-1.3,10.1-1.9,15.6-1.9c11.9,0,20.6,2.7,26.2,8.1c5.5,5.4,8.3,13.6,8.3,24.6V66.4z M45.8,81.6
c3.3,0,6.7-0.6,10.3-1.8c3.6-1.2,6.8-3.4,9.5-6.4c1.6-1.9,2.8-4,3.4-6.4c0.6-2.4,1-5.3,1-8.7v-4.2c-2.9-0.7-6-1.3-9.2-1.7
c-3.2-0.4-6.3-0.6-9.4-0.6c-6.7,0-11.6,1.3-14.9,4c-3.3,2.7-4.9,6.5-4.9,11.5c0,4.7,1.2,8.2,3.7,10.6
C37.7,80.4,41.2,81.6,45.8,81.6z M126.1,92.4c-1.8,0-3-0.3-3.8-1c-0.8-0.6-1.5-2-2.1-3.9L96.7,10.2c-0.6-2-0.9-3.3-0.9-4
c0-1.6,0.8-2.5,2.4-2.5h9.8c1.9,0,3.2,0.3,3.9,1c0.8,0.6,1.4,2,2,3.9l16.8,66.2l15.6-66.2c0.5-2,1.1-3.3,1.9-3.9c0.8-0.6,2.2-1,4-1
h8c1.9,0,3.2,0.3,4,1c0.8,0.6,1.5,2,1.9,3.9l15.8,67l17.3-67c0.6-2,1.3-3.3,2-3.9c0.8-0.6,2.1-1,3.9-1h9.3c1.6,0,2.5,0.8,2.5,2.5
c0,0.5-0.1,1-0.2,1.6c-0.1,0.6-0.3,1.4-0.7,2.5l-24.1,77.3c-0.6,2-1.3,3.3-2.1,3.9c-0.8,0.6-2.1,1-3.8,1h-8.6c-1.9,0-3.2-0.3-4-1
c-0.8-0.7-1.5-2-1.9-4L156,23l-15.4,64.4c-0.5,2-1.1,3.3-1.9,4c-0.8,0.7-2.2,1-4,1H126.1z M254.6,95.1c-5.2,0-10.4-0.6-15.4-1.8
c-5-1.2-8.9-2.5-11.5-4c-1.6-0.9-2.7-1.9-3.1-2.8c-0.4-0.9-0.6-1.9-0.6-2.8v-5.1c0-2.1,0.8-3.1,2.3-3.1c0.6,0,1.2,0.1,1.8,0.3
c0.6,0.2,1.5,0.6,2.5,1c3.4,1.5,7.1,2.7,11,3.5c4,0.8,7.9,1.2,11.9,1.2c6.3,0,11.2-1.1,14.6-3.3c3.4-2.2,5.2-5.4,5.2-9.5
c0-2.8-0.9-5.1-2.7-7c-1.8-1.9-5.2-3.6-10.1-5.2L246,52c-7.3-2.3-12.7-5.7-16-10.2c-3.3-4.4-5-9.3-5-14.5c0-4.2,0.9-7.9,2.7-11.1
c1.8-3.2,4.2-6,7.2-8.2c3-2.3,6.4-4,10.4-5.2c4-1.2,8.2-1.7,12.6-1.7c2.2,0,4.5,0.1,6.7,0.4c2.3,0.3,4.4,0.7,6.5,1.1
c2,0.5,3.9,1,5.7,1.6c1.8,0.6,3.2,1.2,4.2,1.8c1.4,0.8,2.4,1.6,3,2.5c0.6,0.8,0.9,1.9,0.9,3.3v4.7c0,2.1-0.8,3.2-2.3,3.2
c-0.8,0-2.1-0.4-3.8-1.2c-5.7-2.6-12.1-3.9-19.2-3.9c-5.7,0-10.2,0.9-13.3,2.8c-3.1,1.9-4.7,4.8-4.7,8.9c0,2.8,1,5.2,3,7.1
c2,1.9,5.7,3.8,11,5.5l14.2,4.5c7.2,2.3,12.4,5.5,15.5,9.6c3.1,4.1,4.6,8.8,4.6,14c0,4.3-0.9,8.2-2.6,11.6
c-1.8,3.4-4.2,6.4-7.3,8.8c-3.1,2.5-6.8,4.3-11.1,5.6C264.4,94.4,259.7,95.1,254.6,95.1z"/>
<g>
<path class="st1" d="M273.5,143.7c-32.9,24.3-80.7,37.2-121.8,37.2c-57.6,0-109.5-21.3-148.7-56.7c-3.1-2.8-0.3-6.6,3.4-4.4
c42.4,24.6,94.7,39.5,148.8,39.5c36.5,0,76.6-7.6,113.5-23.2C274.2,133.6,278.9,139.7,273.5,143.7z"/>
<path class="st1" d="M287.2,128.1c-4.2-5.4-27.8-2.6-38.5-1.3c-3.2,0.4-3.7-2.4-0.8-4.5c18.8-13.2,49.7-9.4,53.3-5
c3.6,4.5-1,35.4-18.6,50.2c-2.7,2.3-5.3,1.1-4.1-1.9C282.5,155.7,291.4,133.4,287.2,128.1z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1 @@
<svg height="82" viewBox="0 0 78 82" width="78" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><g fill-rule="nonzero"><path d="m2.12 42c-.08 1-.12 2-.12 3 0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3-1.53 19.03-17.46 34-36.88 34s-35.35-14.97-36.88-34z" fill="#000" fill-opacity=".03"/><path d="m39 78c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35s-15.67-35-35-35-35 15.67-35 35 15.67 35 35 35z" fill="#eee"/><path d="m44 31-2.5-3-2.5 3-2.5-3-2.5 3-2.5-3-2.5 3h-2.72c2.65-4.2 7.36-7 12.72-7s10.07 2.8 12.72 7h-2.72l-2.5-3z" fill="#e1dbf2"/><path d="m39 57c-9.4 0-17-7.6-17-17s7.6-17 17-17 17 7.6 17 17-7.6 17-17 17zm0-4c7.18 0 13-5.82 13-13s-5.82-13-13-13-13 5.82-13 13 5.82 13 13 13z" fill="#e1dbf2"/></g><g fill="#e2ba3e" stroke="#fff" transform="translate(14 18)"><path d="m8.5 12.75-4.99617464 2.6266445.95418445-5.56332227-4.0419902-3.93996668 5.58589307-.81167778 2.49808732-5.06167777 2.4980873 5.06167777 5.5858931.81167778-4.0419902 3.93996668.9541844 5.56332227z"/><path d="m24.5 12.75-4.9961746 2.6266445.9541844-5.56332227-4.0419902-3.93996668 5.5858931-.81167778 2.4980873-5.06167777 2.4980873 5.06167777 5.5858931.81167778-4.0419902 3.93996668.9541844 5.56332227z"/><path d="m40.5 12.75-4.9961746 2.6266445.9541844-5.56332227-4.0419902-3.93996668 5.5858931-.81167778 2.4980873-5.06167777 2.4980873 5.06167777 5.5858931.81167778-4.0419902 3.93996668.9541844 5.56332227z"/></g><path d="m35 45h8c0 2.2-1.8 4-4 4s-4-1.8-4-4zm-1.5-2c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" fill="#6b4fbb" fill-rule="nonzero"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -0,0 +1 @@
<svg width="78" height="82" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z" fill-opacity=".03" fill="#000" fill-rule="nonzero"/><path d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z" fill="#EEE" fill-rule="nonzero"/><path d="M44 31l-2.5-3-2.5 3-2.5-3-2.5 3-2.5-3-2.5 3h-2.72c2.65-4.2 7.36-7 12.72-7s10.07 2.8 12.72 7H49l-2.5-3-2.5 3z" fill="#E1DBF2" fill-rule="nonzero"/><path d="M39 57c-9.4 0-17-7.6-17-17s7.6-17 17-17 17 7.6 17 17-7.6 17-17 17zm0-4c7.18 0 13-5.82 13-13s-5.82-13-13-13-13 5.82-13 13 5.82 13 13 13z" fill="#E1DBF2" fill-rule="nonzero"/><g transform="translate(20 13)"><path d="M5.731 15.24V8.736H33.75v6.504c0 4.851-6.272 8.784-14.01 8.784-7.737 0-14.009-3.933-14.009-8.784z" fill="#0B2630"/><path d="M.75 7.662L18.824.182a2.4 2.4 0 0 1 1.835 0l18.072 7.48a1.2 1.2 0 0 1 0 2.218l-18.072 7.48a2.4 2.4 0 0 1-1.835 0L.75 9.88a1.2 1.2 0 0 1 0-2.218z" fill="#29424E"/><path d="M19.295 9.771a1.194 1.194 0 0 1-.66-1.557c.248-.612.948-.907 1.562-.659l11.516 4.657v11.766c0 .66-.538 1.195-1.2 1.195-.663 0-1.2-.535-1.2-1.195V13.822l-10.018-4.05z" fill="#FFCF00" fill-rule="nonzero"/><path d="M32.613 23.373v3.807h-4.2v-3.807c0-.711.353-1.34.894-1.72h2.411c.541.38.895 1.009.895 1.72z" fill="#FCA326"/><path fill="#FFCF00" d="M28.413 23.592H32.613V27.18H28.413z"/></g><path d="M35 45h8c0 2.2-1.8 4-4 4s-4-1.8-4-4zm-1.5-2c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" fill="#6B4FBB" fill-rule="nonzero"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -2,31 +2,32 @@
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { import {
GlAlert, GlAlert,
GlBadge,
GlIcon, GlIcon,
GlLoadingIcon, GlLoadingIcon,
GlDropdown,
GlDropdownItem,
GlSprintf, GlSprintf,
GlTabs, GlTabs,
GlTab, GlTab,
GlButton, GlButton,
GlTable, GlTable,
} from '@gitlab/ui'; } from '@gitlab/ui';
import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import query from '../graphql/queries/details.query.graphql'; import query from '../graphql/queries/details.query.graphql';
import { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
import { ALERTS_SEVERITY_LABELS } from '../constants'; import initUserPopovers from '~/user_popovers';
import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql'; import { ALERTS_SEVERITY_LABELS, trackAlertsDetailsViewsOptions } from '../constants';
import createIssueQuery from '../graphql/mutations/create_issue_from_alert.graphql';
import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
import Tracking from '~/tracking';
import { toggleContainerClasses } from '~/lib/utils/dom_utils';
import SystemNote from './system_notes/system_note.vue';
import AlertSidebar from './alert_sidebar.vue';
const containerEl = document.querySelector('.page-with-contextual-sidebar');
export default { export default {
statuses: {
TRIGGERED: s__('AlertManagement|Triggered'),
ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
RESOLVED: s__('AlertManagement|Resolved'),
},
i18n: { i18n: {
errorMsg: s__( errorMsg: s__(
'AlertManagement|There was an error displaying the alert. Please refresh the page to try again.', 'AlertManagement|There was an error displaying the alert. Please refresh the page to try again.',
@ -38,19 +39,19 @@ export default {
}, },
severityLabels: ALERTS_SEVERITY_LABELS, severityLabels: ALERTS_SEVERITY_LABELS,
components: { components: {
GlBadge,
GlAlert, GlAlert,
GlIcon, GlIcon,
GlLoadingIcon, GlLoadingIcon,
GlSprintf, GlSprintf,
GlDropdown,
GlDropdownItem,
GlTab, GlTab,
GlTabs, GlTabs,
GlButton, GlButton,
GlTable, GlTable,
TimeAgoTooltip, TimeAgoTooltip,
AlertSidebar,
SystemNote,
}, },
mixins: [glFeatureFlagsMixin()],
props: { props: {
alertId: { alertId: {
type: String, type: String,
@ -60,7 +61,7 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
newIssuePath: { projectIssuesPath: {
type: String, type: String,
required: true, required: true,
}, },
@ -85,7 +86,15 @@ export default {
}, },
}, },
data() { data() {
return { alert: null, errored: false, isErrorDismissed: false }; return {
alert: null,
errored: false,
isErrorDismissed: false,
createIssueError: '',
issueCreationInProgress: false,
sidebarCollapsed: false,
sidebarErrorMessage: '',
};
}, },
computed: { computed: {
loading() { loading() {
@ -100,38 +109,92 @@ export default {
return this.errored && !this.isErrorDismissed; return this.errored && !this.isErrorDismissed;
}, },
}, },
mounted() {
this.trackPageViews();
toggleContainerClasses(containerEl, {
'issuable-bulk-update-sidebar': true,
'right-sidebar-expanded': true,
});
},
updated() {
this.$nextTick(() => {
highlightCurrentUser(this.$el.querySelectorAll('.gfm-project_member'));
initUserPopovers(this.$el.querySelectorAll('.js-user-link'));
});
},
methods: { methods: {
dismissError() { dismissError() {
this.isErrorDismissed = true; this.isErrorDismissed = true;
this.sidebarErrorMessage = '';
}, },
updateAlertStatus(status) { toggleSidebar() {
this.sidebarCollapsed = !this.sidebarCollapsed;
toggleContainerClasses(containerEl, {
'right-sidebar-collapsed': this.sidebarCollapsed,
'right-sidebar-expanded': !this.sidebarCollapsed,
});
},
handleAlertSidebarError(errorMessage) {
this.errored = true;
this.sidebarErrorMessage = errorMessage;
},
createIssue() {
this.issueCreationInProgress = true;
this.$apollo this.$apollo
.mutate({ .mutate({
mutation: updateAlertStatus, mutation: createIssueQuery,
variables: { variables: {
iid: this.alertId, iid: this.alert.iid,
status: status.toUpperCase(),
projectPath: this.projectPath, projectPath: this.projectPath,
}, },
}) })
.catch(() => { .then(({ data: { createAlertIssue: { errors, issue } } }) => {
createFlash( if (errors?.length) {
s__( [this.createIssueError] = errors;
'AlertManagement|There was an error while updating the status of the alert. Please try again.', this.issueCreationInProgress = false;
), } else if (issue) {
); visitUrl(this.issuePath(issue.iid));
}
})
.catch(error => {
this.createIssueError = error;
this.issueCreationInProgress = false;
}); });
}, },
issuePath(issueId) {
return joinPaths(this.projectIssuesPath, issueId);
},
trackPageViews() {
const { category, action } = trackAlertsDetailsViewsOptions;
Tracking.event(category, action);
},
alertRefresh() {
this.$apollo.queries.alert.refetch();
},
}, },
}; };
</script> </script>
<template> <template>
<div> <div>
<gl-alert v-if="showErrorMsg" variant="danger" @dismiss="dismissError"> <gl-alert v-if="showErrorMsg" variant="danger" @dismiss="dismissError">
{{ $options.i18n.errorMsg }} {{ sidebarErrorMessage || $options.i18n.errorMsg }}
</gl-alert>
<gl-alert
v-if="createIssueError"
variant="danger"
data-testid="issueCreationError"
@dismiss="createIssueError = null"
>
{{ createIssueError }}
</gl-alert> </gl-alert>
<div v-if="loading"><gl-loading-icon size="lg" class="gl-mt-5" /></div> <div v-if="loading"><gl-loading-icon size="lg" class="gl-mt-5" /></div>
<div v-if="alert" class="alert-management-details gl-relative"> <div
v-if="alert"
class="alert-management-details gl-relative"
:class="{ 'pr-sm-8': sidebarCollapsed }"
>
<div <div
class="gl-display-flex gl-justify-content-space-between gl-align-items-baseline gl-px-1 py-3 py-md-4 gl-border-b-1 gl-border-b-gray-200 gl-border-b-solid flex-column flex-sm-row" class="gl-display-flex gl-justify-content-space-between gl-align-items-baseline gl-px-1 py-3 py-md-4 gl-border-b-1 gl-border-b-gray-200 gl-border-b-solid flex-column flex-sm-row"
> >
@ -142,32 +205,50 @@ export default {
<div <div
class="gl-display-inline-flex gl-align-items-center gl-justify-content-space-between" class="gl-display-inline-flex gl-align-items-center gl-justify-content-space-between"
> >
<gl-icon <gl-badge class="gl-mr-3">
class="gl-mr-3 align-middle" <strong>{{ s__('AlertManagement|Alert') }}</strong>
:size="12" </gl-badge>
:name="`severity-${alert.severity.toLowerCase()}`"
:class="`icon-${alert.severity.toLowerCase()}`"
/>
<strong>{{ $options.severityLabels[alert.severity] }}</strong>
</div> </div>
<span class="mx-2">&bull;</span> <span>
<gl-sprintf :message="reportedAtMessage"> <gl-sprintf :message="reportedAtMessage">
<template #when> <template #when>
<time-ago-tooltip :time="alert.createdAt" class="gl-ml-3" /> <time-ago-tooltip :time="alert.createdAt" />
</template> </template>
<template #tool>{{ alert.monitoringTool }}</template> <template #tool>{{ alert.monitoringTool }}</template>
</gl-sprintf> </gl-sprintf>
</span>
</div> </div>
<gl-button <gl-button
v-if="glFeatures.createIssueFromAlertEnabled" v-if="alert.issueIid"
class="gl-mt-3 mt-sm-0 align-self-center align-self-sm-baseline alert-details-create-issue-button" class="gl-mt-3 mt-sm-0 align-self-center align-self-sm-baseline alert-details-issue-button"
data-testid="createIssueBtn" data-testid="viewIssueBtn"
:href="newIssuePath" :href="issuePath(alert.issueIid)"
category="primary" category="primary"
variant="success" variant="success"
>
{{ s__('AlertManagement|View issue') }}
</gl-button>
<gl-button
v-else
class="gl-mt-3 mt-sm-0 align-self-center align-self-sm-baseline alert-details-issue-button"
data-testid="createIssueBtn"
:loading="issueCreationInProgress"
category="primary"
variant="success"
@click="createIssue()"
> >
{{ s__('AlertManagement|Create issue') }} {{ s__('AlertManagement|Create issue') }}
</gl-button> </gl-button>
<gl-button
:aria-label="__('Toggle sidebar')"
category="primary"
variant="default"
class="d-sm-none gl-absolute toggle-sidebar-mobile-button"
type="button"
@click="toggleSidebar"
>
<i class="fa fa-angle-double-left"></i>
</gl-button>
</div> </div>
<div <div
v-if="alert" v-if="alert"
@ -175,44 +256,57 @@ export default {
> >
<h2 data-testid="title">{{ alert.title }}</h2> <h2 data-testid="title">{{ alert.title }}</h2>
</div> </div>
<gl-dropdown :text="$options.statuses[alert.status]" class="gl-absolute gl-right-0" right>
<gl-dropdown-item
v-for="(label, field) in $options.statuses"
:key="field"
data-testid="statusDropdownItem"
class="gl-vertical-align-middle"
@click="updateAlertStatus(label)"
>
<span class="d-flex">
<gl-icon
class="flex-shrink-0 append-right-4"
:class="{ invisible: label.toUpperCase() !== alert.status }"
name="mobile-issue-close"
/>
{{ label }}
</span>
</gl-dropdown-item>
</gl-dropdown>
<gl-tabs v-if="alert" data-testid="alertDetailsTabs"> <gl-tabs v-if="alert" data-testid="alertDetailsTabs">
<gl-tab data-testid="overviewTab" :title="$options.i18n.overviewTitle"> <gl-tab data-testid="overviewTab" :title="$options.i18n.overviewTitle">
<ul class="pl-4 mb-n1"> <div v-if="alert.severity" class="gl-mt-3 gl-mb-5 gl-display-flex">
<li v-if="alert.startedAt" class="my-2"> <div class="gl-font-weight-bold gl-w-13 gl-text-right gl-pr-3">
<strong class="bold">{{ s__('AlertManagement|Start time') }}:</strong> {{ s__('AlertManagement|Severity') }}:
</div>
<div class="gl-pl-2" data-testid="severity">
<span>
<gl-icon
class="gl-vertical-align-middle"
:size="12"
:name="`severity-${alert.severity.toLowerCase()}`"
:class="`icon-${alert.severity.toLowerCase()}`"
/>
</span>
{{ $options.severityLabels[alert.severity] }}
</div>
</div>
<div v-if="alert.startedAt" class="gl-my-5 gl-display-flex">
<div class="gl-font-weight-bold gl-w-13 gl-text-right gl-pr-3">
{{ s__('AlertManagement|Start time') }}:
</div>
<div class="gl-pl-2">
<time-ago-tooltip data-testid="startTimeItem" :time="alert.startedAt" /> <time-ago-tooltip data-testid="startTimeItem" :time="alert.startedAt" />
</li> </div>
<li v-if="alert.eventCount" class="my-2"> </div>
<strong class="bold">{{ s__('AlertManagement|Events') }}:</strong> <div v-if="alert.eventCount" class="gl-my-5 gl-display-flex">
<span data-testid="eventCount">{{ alert.eventCount }}</span> <div class="gl-font-weight-bold gl-w-13 gl-text-right gl-pr-3">
</li> {{ s__('AlertManagement|Events') }}:
<li v-if="alert.monitoringTool" class="my-2"> </div>
<strong class="bold">{{ s__('AlertManagement|Tool') }}:</strong> <div class="gl-pl-2" data-testid="eventCount">{{ alert.eventCount }}</div>
<span data-testid="monitoringTool">{{ alert.monitoringTool }}</span> </div>
</li> <div v-if="alert.monitoringTool" class="gl-my-5 gl-display-flex">
<li v-if="alert.service" class="my-2"> <div class="gl-font-weight-bold gl-w-13 gl-text-right gl-pr-3">
<strong class="bold">{{ s__('AlertManagement|Service') }}:</strong> {{ s__('AlertManagement|Tool') }}:
<span data-testid="service">{{ alert.service }}</span> </div>
</li> <div class="gl-pl-2" data-testid="monitoringTool">{{ alert.monitoringTool }}</div>
</ul> </div>
<div v-if="alert.service" class="gl-my-5 gl-display-flex">
<div class="bold gl-w-13 gl-text-right gl-pr-3">
{{ s__('AlertManagement|Service') }}:
</div>
<div class="gl-pl-2" data-testid="service">{{ alert.service }}</div>
</div>
<template>
<div v-if="alert.notes.nodes" class="issuable-discussion py-5">
<ul class="notes main-notes-list timeline">
<system-note v-for="note in alert.notes.nodes" :key="note.id" :note="note" />
</ul>
</div>
</template>
</gl-tab> </gl-tab>
<gl-tab data-testid="fullDetailsTab" :title="$options.i18n.fullAlertDetailsTitle"> <gl-tab data-testid="fullDetailsTab" :title="$options.i18n.fullAlertDetailsTitle">
<gl-table <gl-table
@ -231,6 +325,14 @@ export default {
</gl-table> </gl-table>
</gl-tab> </gl-tab>
</gl-tabs> </gl-tabs>
<alert-sidebar
:project-path="projectPath"
:alert="alert"
:sidebar-collapsed="sidebarCollapsed"
@alert-refresh="alertRefresh"
@toggle-sidebar="toggleSidebar"
@alert-sidebar-error="handleAlertSidebarError"
/>
</div> </div>
</div> </div>
</template> </template>

View file

@ -10,23 +10,41 @@ import {
GlDropdownItem, GlDropdownItem,
GlTabs, GlTabs,
GlTab, GlTab,
GlBadge,
GlPagination,
} from '@gitlab/ui'; } from '@gitlab/ui';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { joinPaths, visitUrl } from '~/lib/utils/url_utility'; import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
import { fetchPolicies } from '~/lib/graphql';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import getAlerts from '../graphql/queries/getAlerts.query.graphql'; import getAlerts from '../graphql/queries/get_alerts.query.graphql';
import { ALERTS_STATUS, ALERTS_STATUS_TABS, ALERTS_SEVERITY_LABELS } from '../constants'; import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import {
ALERTS_STATUS_TABS,
ALERTS_SEVERITY_LABELS,
DEFAULT_PAGE_SIZE,
trackAlertListViewsOptions,
trackAlertStatusUpdateOptions,
} from '../constants';
import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql'; import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; import { convertToSnakeCase } from '~/lib/utils/text_utility';
import Tracking from '~/tracking';
const tdClass = 'table-col d-flex d-md-table-cell align-items-center'; const tdClass = 'table-col gl-display-flex d-md-table-cell gl-align-items-center';
const thClass = 'gl-hover-bg-blue-50';
const bodyTrClass = const bodyTrClass =
'gl-border-1 gl-border-t-solid gl-border-gray-100 hover-bg-blue-50 hover-gl-cursor-pointer hover-gl-border-b-solid hover-gl-border-blue-200'; 'gl-border-1 gl-border-t-solid gl-border-gray-100 gl-hover-bg-blue-50 gl-hover-cursor-pointer gl-hover-border-b-solid gl-hover-border-blue-200';
const initialPaginationState = {
currentPage: 1,
prevPageCursor: '',
nextPageCursor: '',
firstPageSize: DEFAULT_PAGE_SIZE,
lastPageSize: null,
};
export default { export default {
bodyTrClass,
i18n: { i18n: {
noAlertsMsg: s__( noAlertsMsg: s__(
"AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page.", "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page.",
@ -40,40 +58,54 @@ export default {
key: 'severity', key: 'severity',
label: s__('AlertManagement|Severity'), label: s__('AlertManagement|Severity'),
tdClass: `${tdClass} rounded-top text-capitalize`, tdClass: `${tdClass} rounded-top text-capitalize`,
thClass,
sortable: true,
}, },
{ {
key: 'startedAt', key: 'startedAt',
label: s__('AlertManagement|Start time'), label: s__('AlertManagement|Start time'),
thClass: `${thClass} js-started-at`,
tdClass, tdClass,
sortable: true,
}, },
{ {
key: 'endedAt', key: 'endedAt',
label: s__('AlertManagement|End time'), label: s__('AlertManagement|End time'),
thClass,
tdClass, tdClass,
sortable: true,
}, },
{ {
key: 'title', key: 'title',
label: s__('AlertManagement|Alert'), label: s__('AlertManagement|Alert'),
thClass: 'w-30p', thClass: `${thClass} w-30p gl-pointer-events-none`,
tdClass, tdClass,
sortable: false,
}, },
{ {
key: 'eventCount', key: 'eventCount',
label: s__('AlertManagement|Events'), label: s__('AlertManagement|Events'),
thClass: 'text-right event-count', thClass: `${thClass} text-right gl-pr-9 w-3rem`,
tdClass: `${tdClass} text-md-right event-count`, tdClass: `${tdClass} text-md-right`,
sortable: true,
},
{
key: 'assignees',
label: s__('AlertManagement|Assignees'),
tdClass,
}, },
{ {
key: 'status', key: 'status',
thClass: 'w-15p', thClass: `${thClass} w-15p`,
label: s__('AlertManagement|Status'), label: s__('AlertManagement|Status'),
tdClass: `${tdClass} rounded-bottom`, tdClass: `${tdClass} rounded-bottom`,
sortable: true,
}, },
], ],
statuses: { statuses: {
[ALERTS_STATUS.TRIGGERED]: s__('AlertManagement|Triggered'), TRIGGERED: s__('AlertManagement|Triggered'),
[ALERTS_STATUS.ACKNOWLEDGED]: s__('AlertManagement|Acknowledged'), ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
[ALERTS_STATUS.RESOLVED]: s__('AlertManagement|Resolved'), RESOLVED: s__('AlertManagement|Resolved'),
}, },
severityLabels: ALERTS_SEVERITY_LABELS, severityLabels: ALERTS_SEVERITY_LABELS,
statusTabs: ALERTS_STATUS_TABS, statusTabs: ALERTS_STATUS_TABS,
@ -89,8 +121,9 @@ export default {
GlIcon, GlIcon,
GlTabs, GlTabs,
GlTab, GlTab,
GlBadge,
GlPagination,
}, },
mixins: [glFeatureFlagsMixin()],
props: { props: {
projectPath: { projectPath: {
type: String, type: String,
@ -115,33 +148,63 @@ export default {
}, },
apollo: { apollo: {
alerts: { alerts: {
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
query: getAlerts, query: getAlerts,
variables() { variables() {
return { return {
projectPath: this.projectPath, projectPath: this.projectPath,
statuses: this.statusFilter, statuses: this.statusFilter,
sort: this.sort,
firstPageSize: this.pagination.firstPageSize,
lastPageSize: this.pagination.lastPageSize,
prevPageCursor: this.pagination.prevPageCursor,
nextPageCursor: this.pagination.nextPageCursor,
}; };
}, },
update(data) { update(data) {
return data.project.alertManagementAlerts.nodes; const { alertManagementAlerts: { nodes: list = [], pageInfo = {} } = {} } =
data.project || {};
return {
list,
pageInfo,
};
}, },
error() { error() {
this.errored = true; this.errored = true;
}, },
}, },
alertsCount: {
query: getAlertsCountByStatus,
variables() {
return {
projectPath: this.projectPath,
};
},
update(data) {
return data.project?.alertManagementAlertStatusCounts;
},
},
}, },
data() { data() {
return { return {
alerts: null,
errored: false, errored: false,
isAlertDismissed: false, isAlertDismissed: false,
isErrorAlertDismissed: false, isErrorAlertDismissed: false,
statusFilter: this.$options.statusTabs[4].filters, sort: 'STARTED_AT_DESC',
statusFilter: [],
filteredByStatus: '',
pagination: initialPaginationState,
sortBy: 'startedAt',
sortDesc: true,
sortDirection: 'desc',
}; };
}, },
computed: { computed: {
showNoAlertsMsg() { showNoAlertsMsg() {
return !this.errored && !this.loading && !this.alerts?.length && !this.isAlertDismissed; return (
!this.errored && !this.loading && this.alertsCount?.all === 0 && !this.isAlertDismissed
);
}, },
showErrorMsg() { showErrorMsg() {
return this.errored && !this.isErrorAlertDismissed; return this.errored && !this.isErrorAlertDismissed;
@ -149,12 +212,43 @@ export default {
loading() { loading() {
return this.$apollo.queries.alerts.loading; return this.$apollo.queries.alerts.loading;
}, },
hasAlerts() {
return this.alerts?.list?.length;
},
tbodyTrClass() {
return !this.loading && this.hasAlerts ? bodyTrClass : '';
},
showPaginationControls() {
return Boolean(this.prevPage || this.nextPage);
},
alertsForCurrentTab() {
return this.alertsCount ? this.alertsCount[this.filteredByStatus.toLowerCase()] : 0;
},
prevPage() {
return Math.max(this.pagination.currentPage - 1, 0);
},
nextPage() {
const nextPage = this.pagination.currentPage + 1;
return nextPage > Math.ceil(this.alertsForCurrentTab / DEFAULT_PAGE_SIZE) ? null : nextPage;
},
},
mounted() {
this.trackPageViews();
}, },
methods: { methods: {
filterAlertsByStatus(tabIndex) { filterAlertsByStatus(tabIndex) {
this.statusFilter = this.$options.statusTabs[tabIndex].filters; this.resetPagination();
const { filters, status } = this.$options.statusTabs[tabIndex];
this.statusFilter = filters;
this.filteredByStatus = status;
},
fetchSortedData({ sortBy, sortDesc }) {
const sortingDirection = sortDesc ? 'DESC' : 'ASC';
const sortingColumn = convertToSnakeCase(sortBy).toUpperCase();
this.resetPagination();
this.sort = `${sortingColumn}_${sortingDirection}`;
}, },
capitalizeFirstCharacter,
updateAlertStatus(status, iid) { updateAlertStatus(status, iid) {
this.$apollo this.$apollo
.mutate({ .mutate({
@ -166,7 +260,10 @@ export default {
}, },
}) })
.then(() => { .then(() => {
this.trackStatusUpdate(status);
this.$apollo.queries.alerts.refetch(); this.$apollo.queries.alerts.refetch();
this.$apollo.queries.alertsCount.refetch();
this.resetPagination();
}) })
.catch(() => { .catch(() => {
createFlash( createFlash(
@ -179,6 +276,42 @@ export default {
navigateToAlertDetails({ iid }) { navigateToAlertDetails({ iid }) {
return visitUrl(joinPaths(window.location.pathname, iid, 'details')); return visitUrl(joinPaths(window.location.pathname, iid, 'details'));
}, },
trackPageViews() {
const { category, action } = trackAlertListViewsOptions;
Tracking.event(category, action);
},
trackStatusUpdate(status) {
const { category, action, label } = trackAlertStatusUpdateOptions;
Tracking.event(category, action, { label, property: status });
},
getAssignees(assignees) {
// TODO: Update to show list of assignee(s) after https://gitlab.com/gitlab-org/gitlab/-/issues/218405
return assignees.nodes?.length > 0
? assignees.nodes[0]?.username
: s__('AlertManagement|Unassigned');
},
handlePageChange(page) {
const { startCursor, endCursor } = this.alerts.pageInfo;
if (page > this.pagination.currentPage) {
this.pagination = {
...initialPaginationState,
nextPageCursor: endCursor,
currentPage: page,
};
} else {
this.pagination = {
lastPageSize: DEFAULT_PAGE_SIZE,
firstPageSize: null,
prevPageCursor: startCursor,
nextPageCursor: '',
currentPage: page,
};
}
},
resetPagination() {
this.pagination = initialPaginationState;
},
}, },
}; };
</script> </script>
@ -192,10 +325,13 @@ export default {
{{ $options.i18n.errorMsg }} {{ $options.i18n.errorMsg }}
</gl-alert> </gl-alert>
<gl-tabs v-if="glFeatures.alertListStatusFilteringEnabled" @input="filterAlertsByStatus"> <gl-tabs @input="filterAlertsByStatus">
<gl-tab v-for="tab in $options.statusTabs" :key="tab.status"> <gl-tab v-for="tab in $options.statusTabs" :key="tab.status">
<template slot="title"> <template slot="title">
<span>{{ tab.title }}</span> <span>{{ tab.title }}</span>
<gl-badge v-if="alertsCount" pill size="sm" class="gl-tab-counter-badge">
{{ alertsCount[tab.status.toLowerCase()] }}
</gl-badge>
</template> </template>
</gl-tab> </gl-tab>
</gl-tabs> </gl-tabs>
@ -205,13 +341,19 @@ export default {
</h4> </h4>
<gl-table <gl-table
class="alert-management-table mt-3" class="alert-management-table mt-3"
:items="alerts" :items="alerts ? alerts.list : []"
:fields="$options.fields" :fields="$options.fields"
:show-empty="true" :show-empty="true"
:busy="loading" :busy="loading"
stacked="md" stacked="md"
:tbody-tr-class="$options.bodyTrClass" :tbody-tr-class="tbodyTrClass"
:no-local-sorting="true"
:sort-direction="sortDirection"
:sort-desc.sync="sortDesc"
:sort-by.sync="sortBy"
sort-icon-left
@row-clicked="navigateToAlertDetails" @row-clicked="navigateToAlertDetails"
@sort-changed="fetchSortedData"
> >
<template #cell(severity)="{ item }"> <template #cell(severity)="{ item }">
<div <div
@ -236,16 +378,22 @@ export default {
<time-ago v-if="item.endedAt" :time="item.endedAt" /> <time-ago v-if="item.endedAt" :time="item.endedAt" />
</template> </template>
<template #cell(eventCount)="{ item }">
{{ item.eventCount }}
</template>
<template #cell(title)="{ item }"> <template #cell(title)="{ item }">
<div class="gl-max-w-full text-truncate">{{ item.title }}</div> <div class="gl-max-w-full text-truncate">{{ item.title }}</div>
</template> </template>
<template #cell(assignees)="{ item }">
<div class="gl-max-w-full text-truncate" data-testid="assigneesField">
{{ getAssignees(item.assignees) }}
</div>
</template>
<template #cell(status)="{ item }"> <template #cell(status)="{ item }">
<gl-dropdown <gl-dropdown :text="$options.statuses[item.status]" class="w-100" right>
:text="capitalizeFirstCharacter(item.status.toLowerCase())"
class="w-100"
right
>
<gl-dropdown-item <gl-dropdown-item
v-for="(label, field) in $options.statuses" v-for="(label, field) in $options.statuses"
:key="field" :key="field"
@ -271,6 +419,16 @@ export default {
<gl-loading-icon size="lg" color="dark" class="mt-3" /> <gl-loading-icon size="lg" color="dark" class="mt-3" />
</template> </template>
</gl-table> </gl-table>
<gl-pagination
v-if="showPaginationControls"
:value="pagination.currentPage"
:prev-page="prevPage"
:next-page="nextPage"
align="center"
class="gl-pagination prepend-top-default"
@input="handlePageChange"
/>
</div> </div>
<gl-empty-state <gl-empty-state
v-else v-else

View file

@ -0,0 +1,61 @@
<script>
import SidebarHeader from './sidebar/sidebar_header.vue';
import SidebarTodo from './sidebar/sidebar_todo.vue';
import SidebarStatus from './sidebar/sidebar_status.vue';
import SidebarAssignees from './sidebar/sidebar_assignees.vue';
export default {
components: {
SidebarAssignees,
SidebarHeader,
SidebarTodo,
SidebarStatus,
},
props: {
sidebarCollapsed: {
type: Boolean,
required: true,
},
projectPath: {
type: String,
required: true,
},
alert: {
type: Object,
required: true,
},
},
computed: {
sidebarCollapsedClass() {
return this.sidebarCollapsed ? 'right-sidebar-collapsed' : 'right-sidebar-expanded';
},
},
};
</script>
<template>
<aside :class="sidebarCollapsedClass" class="right-sidebar alert-sidebar">
<div class="issuable-sidebar js-issuable-update">
<sidebar-header
:sidebar-collapsed="sidebarCollapsed"
@toggle-sidebar="$emit('toggle-sidebar')"
/>
<sidebar-todo v-if="sidebarCollapsed" :sidebar-collapsed="sidebarCollapsed" />
<sidebar-status
:project-path="projectPath"
:alert="alert"
@toggle-sidebar="$emit('toggle-sidebar')"
@alert-sidebar-error="$emit('alert-sidebar-error', $event)"
/>
<sidebar-assignees
:project-path="projectPath"
:alert="alert"
:sidebar-collapsed="sidebarCollapsed"
@alert-refresh="$emit('alert-refresh')"
@toggle-sidebar="$emit('toggle-sidebar')"
@alert-sidebar-error="$emit('alert-sidebar-error', $event)"
/>
<div class="block"></div>
</div>
</aside>
</template>

View file

@ -0,0 +1,51 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
export default {
components: {
GlDropdownItem,
},
props: {
user: {
type: Object,
required: true,
},
active: {
type: Boolean,
required: true,
},
},
methods: {
isActive(name) {
return this.alert.assignees.nodes.some(({ username }) => username === name);
},
},
};
</script>
<template>
<gl-dropdown-item
:key="user.username"
data-testid="assigneeDropdownItem"
class="assignee-dropdown-item gl-vertical-align-middle"
:active="active"
active-class="is-active"
@click="$emit('update-alert-assignees', user.username)"
>
<span class="gl-relative mr-2">
<img
:alt="user.username"
:src="user.avatar_url"
:width="32"
class="avatar avatar-inline gl-m-0 s32"
data-qa-selector="avatar_image"
/>
</span>
<span class="d-flex gl-flex-direction-column gl-overflow-hidden">
<strong class="dropdown-menu-user-full-name">
{{ user.name }}
</strong>
<span class="dropdown-menu-user-username"> {{ user.username }}</span>
</span>
</gl-dropdown-item>
</template>

View file

@ -0,0 +1,278 @@
<script>
import {
GlIcon,
GlDropdown,
GlDropdownDivider,
GlDropdownHeader,
GlDropdownItem,
GlLoadingIcon,
GlTooltip,
GlButton,
GlSprintf,
} from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
import alertSetAssignees from '../../graphql/mutations/alert_set_assignees.graphql';
import SidebarAssignee from './sidebar_assignee.vue';
import { debounce } from 'lodash';
const DATA_REFETCH_DELAY = 250;
export default {
FETCH_USERS_ERROR: s__(
'AlertManagement|There was an error while updating the assignee(s) list. Please try again.',
),
UPDATE_ALERT_ASSIGNEES_ERROR: s__(
'AlertManagement|There was an error while updating the assignee(s) of the alert. Please try again.',
),
components: {
GlIcon,
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
GlDropdownHeader,
GlLoadingIcon,
GlTooltip,
GlButton,
GlSprintf,
SidebarAssignee,
},
props: {
projectPath: {
type: String,
required: true,
},
alert: {
type: Object,
required: true,
},
isEditable: {
type: Boolean,
required: false,
default: true,
},
sidebarCollapsed: {
type: Boolean,
required: false,
},
},
data() {
return {
isDropdownShowing: false,
isDropdownSearching: false,
isUpdating: false,
search: '',
users: [],
};
},
computed: {
currentUser() {
return gon?.current_username;
},
userName() {
return this.alert?.assignees?.nodes[0]?.username;
},
assignedUser() {
return this.userName || s__('AlertManagement|None');
},
sortedUsers() {
return this.users
.map(user => ({ ...user, active: this.isActive(user.username) }))
.sort((a, b) => (a.active === b.active ? 0 : a.active ? -1 : 1)); // eslint-disable-line no-nested-ternary
},
dropdownClass() {
return this.isDropdownShowing ? 'show' : 'gl-display-none';
},
userListValid() {
return !this.isDropdownSearching && this.users.length > 0;
},
userListEmpty() {
return !this.isDropdownSearching && this.users.length === 0;
},
},
watch: {
search: debounce(function debouncedUserSearch() {
this.updateAssigneesDropdown();
}, DATA_REFETCH_DELAY),
},
mounted() {
this.updateAssigneesDropdown();
},
methods: {
hideDropdown() {
this.isDropdownShowing = false;
},
toggleFormDropdown() {
this.isDropdownShowing = !this.isDropdownShowing;
const { dropdown } = this.$refs.dropdown.$refs;
if (dropdown && this.isDropdownShowing) {
dropdown.show();
}
},
isActive(name) {
return this.alert.assignees.nodes.some(({ username }) => username === name);
},
buildUrl(urlRoot, url) {
let newUrl;
if (urlRoot != null) {
newUrl = urlRoot.replace(/\/$/, '') + url;
}
return newUrl;
},
updateAssigneesDropdown() {
this.isDropdownSearching = true;
return axios
.get(this.buildUrl(gon.relative_url_root, '/autocomplete/users.json'), {
params: {
search: this.search,
per_page: 20,
active: true,
current_user: true,
project_id: gon?.current_project_id,
},
})
.then(({ data }) => {
this.users = data;
})
.catch(() => {
this.$emit('alert-sidebar-error', this.$options.FETCH_USERS_ERROR);
})
.finally(() => {
this.isDropdownSearching = false;
});
},
updateAlertAssignees(assignees) {
this.isUpdating = true;
this.$apollo
.mutate({
mutation: alertSetAssignees,
variables: {
iid: this.alert.iid,
assigneeUsernames: [this.isActive(assignees) ? '' : assignees],
projectPath: this.projectPath,
},
})
.then(() => {
this.hideDropdown();
this.$emit('alert-refresh');
})
.catch(() => {
this.$emit('alert-sidebar-error', this.$options.UPDATE_ALERT_ASSIGNEES_ERROR);
})
.finally(() => {
this.isUpdating = false;
});
},
},
};
</script>
<template>
<div class="block alert-status">
<div ref="status" class="sidebar-collapsed-icon" @click="$emit('toggle-sidebar')">
<gl-icon name="user" :size="14" />
<gl-loading-icon v-if="isUpdating" />
</div>
<gl-tooltip :target="() => $refs.status" boundary="viewport" placement="left">
<gl-sprintf :message="s__('AlertManagement|Alert assignee(s): %{assignees}')">
<template #assignees>
{{ assignedUser }}
</template>
</gl-sprintf>
</gl-tooltip>
<div class="hide-collapsed">
<p class="title gl-display-flex gl-justify-content-space-between">
{{ s__('AlertManagement|Assignee') }}
<a
v-if="isEditable"
ref="editButton"
class="btn-link"
href="#"
@click="toggleFormDropdown"
@keydown.esc="hideDropdown"
>
{{ s__('AlertManagement|Edit') }}
</a>
</p>
<div class="dropdown dropdown-menu-selectable" :class="dropdownClass">
<gl-dropdown
ref="dropdown"
:text="assignedUser"
class="w-100"
toggle-class="dropdown-menu-toggle"
variant="outline-default"
@keydown.esc.native="hideDropdown"
@hide="hideDropdown"
>
<div class="dropdown-title">
<span class="alert-title">{{ s__('AlertManagement|Assign To') }}</span>
<gl-button
:aria-label="__('Close')"
variant="link"
class="dropdown-title-button dropdown-menu-close"
icon="close"
@click="hideDropdown"
/>
</div>
<div class="dropdown-input">
<input
v-model.trim="search"
class="dropdown-input-field"
type="search"
:placeholder="__('Search users')"
/>
<gl-icon name="search" class="dropdown-input-search ic-search" data-hidden="true" />
</div>
<div class="dropdown-content dropdown-body">
<template v-if="userListValid">
<gl-dropdown-item
:active="!userName"
active-class="is-active"
@click="updateAlertAssignees('')"
>
{{ s__('AlertManagement|Unassigned') }}
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-dropdown-header class="mt-0">
{{ s__('AlertManagement|Assignee') }}
</gl-dropdown-header>
<sidebar-assignee
v-for="user in sortedUsers"
:key="user.username"
:user="user"
:active="user.active"
@update-alert-assignees="updateAlertAssignees"
/>
</template>
<gl-dropdown-item v-else-if="userListEmpty">
{{ s__('AlertManagement|No Matching Results') }}
</gl-dropdown-item>
<gl-loading-icon v-else />
</div>
</gl-dropdown>
</div>
<gl-loading-icon v-if="isUpdating" :inline="true" />
<p v-else-if="!isDropdownShowing" class="value gl-m-0" :class="{ 'no-value': !userName }">
<span v-if="userName" class="gl-text-gray-700" data-testid="assigned-users">{{
assignedUser
}}</span>
<span v-else class="gl-display-flex gl-align-items-center">
{{ s__('AlertManagement|None -') }}
<gl-button
class="gl-pl-2"
href="#"
variant="link"
data-testid="unassigned-users"
@click="updateAlertAssignees(currentUser)"
>
{{ s__('AlertManagement| assign yourself') }}
</gl-button>
</span>
</p>
</div>
</div>
</template>

View file

@ -0,0 +1,34 @@
<script>
import ToggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
import SidebarTodo from './sidebar_todo.vue';
export default {
components: {
ToggleSidebar,
SidebarTodo,
},
props: {
sidebarCollapsed: {
type: Boolean,
required: true,
},
},
};
</script>
<template>
<div class="block d-flex justify-content-between">
<span class="issuable-header-text hide-collapsed">
{{ __('Quick actions') }}
</span>
<toggle-sidebar
:collapsed="sidebarCollapsed"
css-classes="ml-auto"
@toggle="$emit('toggle-sidebar')"
/>
<!-- TODO: Implement after or as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/215946 -->
<template v-if="false">
<sidebar-todo v-if="!sidebarCollapsed" :sidebar-collapsed="sidebarCollapsed" />
</template>
</div>
</template>

View file

@ -0,0 +1,189 @@
<script>
import {
GlIcon,
GlDropdown,
GlDropdownItem,
GlLoadingIcon,
GlTooltip,
GlButton,
GlSprintf,
} from '@gitlab/ui';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
import { trackAlertStatusUpdateOptions } from '../../constants';
import updateAlertStatus from '../../graphql/mutations/update_alert_status.graphql';
export default {
statuses: {
TRIGGERED: s__('AlertManagement|Triggered'),
ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
RESOLVED: s__('AlertManagement|Resolved'),
},
components: {
GlIcon,
GlDropdown,
GlDropdownItem,
GlLoadingIcon,
GlTooltip,
GlButton,
GlSprintf,
},
props: {
projectPath: {
type: String,
required: true,
},
alert: {
type: Object,
required: true,
},
isEditable: {
type: Boolean,
required: false,
default: true,
},
},
data() {
return {
isDropdownShowing: false,
isUpdating: false,
};
},
computed: {
dropdownClass() {
return this.isDropdownShowing ? 'show' : 'gl-display-none';
},
},
methods: {
hideDropdown() {
this.isDropdownShowing = false;
},
toggleFormDropdown() {
this.isDropdownShowing = !this.isDropdownShowing;
const { dropdown } = this.$refs.dropdown.$refs;
if (dropdown && this.isDropdownShowing) {
dropdown.show();
}
},
isSelected(status) {
return this.alert.status === status;
},
updateAlertStatus(status) {
this.isUpdating = true;
this.$apollo
.mutate({
mutation: updateAlertStatus,
variables: {
iid: this.alert.iid,
status: status.toUpperCase(),
projectPath: this.projectPath,
},
})
.then(() => {
this.trackStatusUpdate(status);
this.hideDropdown();
})
.catch(() => {
this.$emit(
'alert-sidebar-error',
s__(
'AlertManagement|There was an error while updating the status of the alert. Please try again.',
),
);
})
.finally(() => {
this.isUpdating = false;
});
},
trackStatusUpdate(status) {
const { category, action, label } = trackAlertStatusUpdateOptions;
Tracking.event(category, action, { label, property: status });
},
},
};
</script>
<template>
<div class="block alert-status">
<div ref="status" class="sidebar-collapsed-icon" @click="$emit('toggle-sidebar')">
<gl-icon name="status" :size="14" />
<gl-loading-icon v-if="isUpdating" />
</div>
<gl-tooltip :target="() => $refs.status" boundary="viewport" placement="left">
<gl-sprintf :message="s__('AlertManagement|Alert status: %{status}')">
<template #status>
{{ alert.status.toLowerCase() }}
</template>
</gl-sprintf>
</gl-tooltip>
<div class="hide-collapsed">
<p class="title gl-display-flex justify-content-between">
{{ s__('AlertManagement|Status') }}
<a
v-if="isEditable"
ref="editButton"
class="btn-link"
href="#"
@click="toggleFormDropdown"
@keydown.esc="hideDropdown"
>
{{ s__('AlertManagement|Edit') }}
</a>
</p>
<div class="dropdown dropdown-menu-selectable" :class="dropdownClass">
<gl-dropdown
ref="dropdown"
:text="$options.statuses[alert.status]"
class="w-100"
toggle-class="dropdown-menu-toggle"
variant="outline-default"
@keydown.esc.native="hideDropdown"
@hide="hideDropdown"
>
<div class="dropdown-title">
<span class="alert-title">{{ s__('AlertManagement|Assign status') }}</span>
<gl-button
:aria-label="__('Close')"
variant="link"
class="dropdown-title-button dropdown-menu-close"
icon="close"
@click="hideDropdown"
/>
</div>
<div class="dropdown-content dropdown-body">
<gl-dropdown-item
v-for="(label, field) in $options.statuses"
:key="field"
data-testid="statusDropdownItem"
class="gl-vertical-align-middle"
:active="label.toUpperCase() === alert.status"
:active-class="'is-active'"
@click="updateAlertStatus(label)"
>
{{ label }}
</gl-dropdown-item>
</div>
</gl-dropdown>
</div>
<gl-loading-icon v-if="isUpdating" :inline="true" />
<p
v-else-if="!isDropdownShowing"
class="value gl-m-0"
:class="{ 'no-value': !$options.statuses[alert.status] }"
>
<span
v-if="$options.statuses[alert.status]"
class="gl-text-gray-700"
data-testid="status"
>{{ $options.statuses[alert.status] }}</span
>
<span v-else>
{{ s__('AlertManagement|None') }}
</span>
</p>
</div>
</div>
</template>

View file

@ -0,0 +1,29 @@
<script>
import Todo from '~/sidebar/components/todo_toggle/todo.vue';
export default {
components: {
Todo,
},
props: {
sidebarCollapsed: {
type: Boolean,
required: true,
},
},
};
</script>
<!-- TODO: Implement after or as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/215946 -->
<template>
<div v-if="false" :class="{ 'block todo': sidebarCollapsed }">
<todo
:collapsed="sidebarCollapsed"
:issuable-id="1"
:is-todo="false"
:is-action-active="false"
issuable-type="alert"
@toggleTodo="() => {}"
/>
</div>
</template>

View file

@ -0,0 +1,46 @@
<script>
import NoteHeader from '~/notes/components/note_header.vue';
import { spriteIcon } from '~/lib/utils/common_utils';
export default {
components: {
NoteHeader,
},
props: {
note: {
type: Object,
required: true,
},
},
computed: {
noteAnchorId() {
return `note_${this.note?.id?.split('/').pop()}`;
},
noteAuthor() {
const {
author,
author: { id },
} = this.note;
return { ...author, id: id?.split('/').pop() };
},
iconHtml() {
return spriteIcon('user');
},
},
};
</script>
<template>
<li :id="noteAnchorId" class="timeline-entry note system-note note-wrapper">
<div class="timeline-entry-inner">
<div class="timeline-icon" v-html="iconHtml"></div>
<div class="timeline-content">
<div class="note-header">
<note-header :author="noteAuthor" :created-at="note.createdAt" :note-id="note.id">
<span v-html="note.bodyHtml"></span>
</note-header>
</div>
</div>
</div>
</li>
</template>

View file

@ -9,38 +9,59 @@ export const ALERTS_SEVERITY_LABELS = {
UNKNOWN: s__('AlertManagement|Unknown'), UNKNOWN: s__('AlertManagement|Unknown'),
}; };
export const ALERTS_STATUS = {
OPEN: 'OPEN',
TRIGGERED: 'TRIGGERED',
ACKNOWLEDGED: 'ACKNOWLEDGED',
RESOLVED: 'RESOLVED',
ALL: 'ALL',
};
export const ALERTS_STATUS_TABS = [ export const ALERTS_STATUS_TABS = [
{ {
title: s__('AlertManagement|Open'), title: s__('AlertManagement|Open'),
status: ALERTS_STATUS.OPEN, status: 'OPEN',
filters: [ALERTS_STATUS.TRIGGERED, ALERTS_STATUS.ACKNOWLEDGED], filters: ['TRIGGERED', 'ACKNOWLEDGED'],
}, },
{ {
title: s__('AlertManagement|Triggered'), title: s__('AlertManagement|Triggered'),
status: ALERTS_STATUS.TRIGGERED, status: 'TRIGGERED',
filters: [ALERTS_STATUS.TRIGGERED], filters: 'TRIGGERED',
}, },
{ {
title: s__('AlertManagement|Acknowledged'), title: s__('AlertManagement|Acknowledged'),
status: ALERTS_STATUS.ACKNOWLEDGED, status: 'ACKNOWLEDGED',
filters: [ALERTS_STATUS.ACKNOWLEDGED], filters: 'ACKNOWLEDGED',
}, },
{ {
title: s__('AlertManagement|Resolved'), title: s__('AlertManagement|Resolved'),
status: ALERTS_STATUS.RESOLVED, status: 'RESOLVED',
filters: [ALERTS_STATUS.RESOLVED], filters: 'RESOLVED',
}, },
{ {
title: s__('AlertManagement|All alerts'), title: s__('AlertManagement|All alerts'),
status: ALERTS_STATUS.ALL, status: 'ALL',
filters: [ALERTS_STATUS.TRIGGERED, ALERTS_STATUS.ACKNOWLEDGED, ALERTS_STATUS.RESOLVED], filters: ['TRIGGERED', 'ACKNOWLEDGED', 'RESOLVED'],
}, },
]; ];
/* eslint-disable @gitlab/require-i18n-strings */
/**
* Tracks snowplow event when user views alerts list
*/
export const trackAlertListViewsOptions = {
category: 'Alert Management',
action: 'view_alerts_list',
};
/**
* Tracks snowplow event when user views alert details
*/
export const trackAlertsDetailsViewsOptions = {
category: 'Alert Management',
action: 'view_alert_details',
};
/**
* Tracks snowplow event when alert status is updated
*/
export const trackAlertStatusUpdateOptions = {
category: 'Alert Management',
action: 'update_alert_status',
label: 'Status',
};
export const DEFAULT_PAGE_SIZE = 10;

View file

@ -8,7 +8,7 @@ Vue.use(VueApollo);
export default selector => { export default selector => {
const domEl = document.querySelector(selector); const domEl = document.querySelector(selector);
const { alertId, projectPath, newIssuePath } = domEl.dataset; const { alertId, projectPath, projectIssuesPath } = domEl.dataset;
const apolloProvider = new VueApollo({ const apolloProvider = new VueApollo({
defaultClient: createDefaultClient( defaultClient: createDefaultClient(
@ -39,7 +39,7 @@ export default selector => {
props: { props: {
alertId, alertId,
projectPath, projectPath,
newIssuePath, projectIssuesPath,
}, },
}); });
}, },

View file

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

View file

@ -1,4 +1,5 @@
#import "./listItem.fragment.graphql" #import "./list_item.fragment.graphql"
#import "./alert_note.fragment.graphql"
fragment AlertDetailItem on AlertManagementAlert { fragment AlertDetailItem on AlertManagementAlert {
...AlertListItem ...AlertListItem
@ -8,4 +9,9 @@ fragment AlertDetailItem on AlertManagementAlert {
description description
updatedAt updatedAt
details details
notes {
nodes {
...AlertNote
}
}
} }

View file

@ -6,4 +6,10 @@ fragment AlertListItem on AlertManagementAlert {
startedAt startedAt
endedAt endedAt
eventCount eventCount
issueIid
assignees {
nodes {
username
}
}
} }

View file

@ -0,0 +1,15 @@
mutation($projectPath: ID!, $assigneeUsernames: [String!]!, $iid: String!) {
alertSetAssignees(
input: { iid: $iid, assigneeUsernames: $assigneeUsernames, projectPath: $projectPath }
) {
errors
alert {
iid
assignees {
nodes {
username
}
}
}
}
}

View file

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

View file

@ -4,6 +4,7 @@ mutation ($projectPath: ID!, $status: AlertManagementStatus!, $iid: String!) {
alert { alert {
iid, iid,
status, status,
endedAt
} }
} }
} }

View file

@ -1,4 +1,4 @@
#import "../fragments/detailItem.fragment.graphql" #import "../fragments/detail_item.fragment.graphql"
query alertDetails($fullPath: ID!, $alertId: String) { query alertDetails($fullPath: ID!, $alertId: String) {
project(fullPath: $fullPath) { project(fullPath: $fullPath) {

View file

@ -1,11 +0,0 @@
#import "../fragments/listItem.fragment.graphql"
query getAlerts($projectPath: ID!, $statuses: [AlertManagementStatus!]) {
project(fullPath: $projectPath) {
alertManagementAlerts(statuses: $statuses) {
nodes {
...AlertListItem
}
}
}
}

View file

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

View file

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

View file

@ -1,7 +0,0 @@
import axios from '~/lib/utils/axios_utils';
export default {
getAlertManagementList({ endpoint }) {
return axios.get(endpoint);
},
};

View file

@ -1,26 +1,37 @@
<script> <script>
import { import {
GlDeprecatedButton, GlButton,
GlFormGroup, GlFormGroup,
GlFormInput, GlFormInput,
GlLink,
GlModal, GlModal,
GlModalDirective, GlModalDirective,
GlSprintf,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { escape } from 'lodash';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ToggleButton from '~/vue_shared/components/toggle_button.vue'; import ToggleButton from '~/vue_shared/components/toggle_button.vue';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { s__, __, sprintf } from '~/locale'; import { s__, __ } from '~/locale';
import createFlash from '~/flash'; import createFlash from '~/flash';
export default { export default {
i18n: {
usageSection: s__(
'AlertService|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.',
),
setupSection: s__(
"AlertService|Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.",
),
},
COPY_TO_CLIPBOARD: __('Copy'), COPY_TO_CLIPBOARD: __('Copy'),
RESET_KEY: __('Reset key'), RESET_KEY: __('Reset key'),
components: { components: {
GlDeprecatedButton, GlButton,
GlFormGroup, GlFormGroup,
GlFormInput, GlFormInput,
GlLink,
GlModal, GlModal,
GlSprintf,
ClipboardButton, ClipboardButton,
ToggleButton, ToggleButton,
}, },
@ -28,6 +39,14 @@ export default {
'gl-modal': GlModalDirective, 'gl-modal': GlModalDirective,
}, },
props: { props: {
alertsSetupUrl: {
type: String,
required: true,
},
alertsUsageUrl: {
type: String,
required: true,
},
initialAuthorizationKey: { initialAuthorizationKey: {
type: String, type: String,
required: false, required: false,
@ -41,11 +60,6 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
learnMoreUrl: {
type: String,
required: false,
default: '',
},
initialActivated: { initialActivated: {
type: Boolean, type: Boolean,
required: true, required: true,
@ -59,27 +73,17 @@ export default {
}; };
}, },
computed: { computed: {
learnMoreDescription() { sections() {
return sprintf( return [
s__(
'AlertService|%{linkStart}Learn more%{linkEnd} about configuring this endpoint to receive alerts.',
),
{ {
linkStart: `<a href="${escape( text: this.$options.i18n.usageSection,
this.learnMoreUrl, url: this.alertsUsageUrl,
)}" target="_blank" rel="noopener noreferrer">`,
linkEnd: '</a>',
}, },
false, {
); text: this.$options.i18n.setupSection,
}, url: this.alertsSetupUrl,
sectionDescription() { },
const desc = s__( ];
'AlertService|Each alert source must be authorized using the following URL and authorization key.',
);
const learnMoreDesc = this.learnMoreDescription ? ` ${this.learnMoreDescription}` : '';
return `${desc}${learnMoreDesc}`;
}, },
}, },
watch: { watch: {
@ -126,7 +130,15 @@ export default {
<template> <template>
<div> <div>
<p v-html="sectionDescription"></p> <div data-testid="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="__('Active')" label-for="activated" label-class="label-bold"> <gl-form-group :label="__('Active')" label-for="activated" label-class="label-bold">
<toggle-button <toggle-button
id="activated" id="activated"
@ -155,9 +167,7 @@ export default {
<clipboard-button :text="authorizationKey" :title="$options.COPY_TO_CLIPBOARD" /> <clipboard-button :text="authorizationKey" :title="$options.COPY_TO_CLIPBOARD" />
</span> </span>
</div> </div>
<gl-deprecated-button v-gl-modal.authKeyModal class="mt-2">{{ <gl-button v-gl-modal.authKeyModal class="mt-2">{{ $options.RESET_KEY }}</gl-button>
$options.RESET_KEY
}}</gl-deprecated-button>
<gl-modal <gl-modal
modal-id="authKeyModal" modal-id="authKeyModal"
:title="$options.RESET_KEY" :title="$options.RESET_KEY"

View file

@ -7,7 +7,14 @@ export default el => {
return null; return null;
} }
const { activated: activatedStr, formPath, authorizationKey, url, learnMoreUrl } = el.dataset; const {
activated: activatedStr,
alertsSetupUrl,
alertsUsageUrl,
formPath,
authorizationKey,
url,
} = el.dataset;
const activated = parseBoolean(activatedStr); const activated = parseBoolean(activatedStr);
return new Vue({ return new Vue({
@ -15,9 +22,10 @@ export default el => {
render(createElement) { render(createElement) {
return createElement(AlertsServiceForm, { return createElement(AlertsServiceForm, {
props: { props: {
alertsSetupUrl,
alertsUsageUrl,
initialActivated: activated, initialActivated: activated,
formPath, formPath,
learnMoreUrl,
initialAuthorizationKey: authorizationKey, initialAuthorizationKey: authorizationKey,
url, url,
}, },

View file

@ -38,6 +38,7 @@ const Api = {
userPostStatusPath: '/api/:version/user/status', userPostStatusPath: '/api/:version/user/status',
commitPath: '/api/:version/projects/:id/repository/commits', commitPath: '/api/:version/projects/:id/repository/commits',
applySuggestionPath: '/api/:version/suggestions/:id/apply', applySuggestionPath: '/api/:version/suggestions/:id/apply',
applySuggestionBatchPath: '/api/:version/suggestions/batch_apply',
commitPipelinesPath: '/:project_id/commit/:sha/pipelines', commitPipelinesPath: '/:project_id/commit/:sha/pipelines',
branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch', branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch',
createBranchPath: '/api/:version/projects/:id/repository/branches', createBranchPath: '/api/:version/projects/:id/repository/branches',
@ -51,8 +52,10 @@ const Api = {
pipelinesPath: '/api/:version/projects/:id/pipelines/', pipelinesPath: '/api/:version/projects/:id/pipelines/',
environmentsPath: '/api/:version/projects/:id/environments', environmentsPath: '/api/:version/projects/:id/environments',
rawFilePath: '/api/:version/projects/:id/repository/files/:path/raw', rawFilePath: '/api/:version/projects/:id/repository/files/:path/raw',
issuePath: '/api/:version/projects/:id/issues/:issue_iid',
tagsPath: '/api/:version/projects/:id/repository/tags',
group(groupId, callback) { group(groupId, callback = () => {}) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId); const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
return axios.get(url).then(({ data }) => { return axios.get(url).then(({ data }) => {
callback(data); callback(data);
@ -321,6 +324,12 @@ const Api = {
return axios.put(url); return axios.put(url);
}, },
applySuggestionBatch(ids) {
const url = Api.buildUrl(Api.applySuggestionBatchPath);
return axios.put(url, { ids });
},
commitPipelines(projectId, sha) { commitPipelines(projectId, sha) {
const encodedProjectId = projectId const encodedProjectId = projectId
.split('/') .split('/')
@ -540,6 +549,34 @@ const Api = {
return axios.get(url, { params }); return axios.get(url, { params });
}, },
updateIssue(project, issue, data = {}) {
const url = Api.buildUrl(Api.issuePath)
.replace(':id', encodeURIComponent(project))
.replace(':issue_iid', encodeURIComponent(issue));
return axios.put(url, data);
},
updateMergeRequest(project, mergeRequest, data = {}) {
const url = Api.buildUrl(Api.projectMergeRequestPath)
.replace(':id', encodeURIComponent(project))
.replace(':mrid', encodeURIComponent(mergeRequest));
return axios.put(url, data);
},
tags(id, query = '', options = {}) {
const url = Api.buildUrl(this.tagsPath).replace(':id', encodeURIComponent(id));
return axios.get(url, {
params: {
search: query,
per_page: DEFAULT_PER_PAGE,
...options,
},
});
},
buildUrl(url) { buildUrl(url) {
return joinPaths(gon.relative_url_root || '', url.replace(':version', gon.api_version)); return joinPaths(gon.relative_url_root || '', url.replace(':version', gon.api_version));
}, },

View file

@ -0,0 +1,14 @@
import $ from 'jquery';
import initU2F from './u2f';
import U2FRegister from './u2f/register';
export const mount2faAuthentication = () => {
// Soon this will conditionally mount a webauthn app (see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/26692)
initU2F();
};
export const mount2faRegistration = () => {
// Soon this will conditionally mount a webauthn app (see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/26692)
const u2fRegister = new U2FRegister($('#js-register-u2f'), gon.u2f);
u2fRegister.start();
};

View file

@ -40,10 +40,10 @@ export default class U2FAuthenticate {
this.signRequests = u2fParams.sign_requests.map(request => omit(request, 'challenge')); this.signRequests = u2fParams.sign_requests.map(request => omit(request, 'challenge'));
this.templates = { this.templates = {
setup: '#js-authenticate-u2f-setup', setup: '#js-authenticate-token-2fa-setup',
inProgress: '#js-authenticate-u2f-in-progress', inProgress: '#js-authenticate-token-2fa-in-progress',
error: '#js-authenticate-u2f-error', error: '#js-authenticate-token-2fa-error',
authenticated: '#js-authenticate-u2f-authenticated', authenticated: '#js-authenticate-token-2fa-authenticated',
}; };
} }
@ -88,7 +88,7 @@ export default class U2FAuthenticate {
error_message: error.message(), error_message: error.message(),
error_code: error.errorCode, error_code: error.errorCode,
}); });
return this.container.find('#js-u2f-try-again').on('click', this.renderInProgress); return this.container.find('#js-token-2fa-try-again').on('click', this.renderInProgress);
} }
renderAuthenticated(deviceResponse) { renderAuthenticated(deviceResponse) {

View file

@ -1,17 +1,17 @@
import $ from 'jquery'; import $ from 'jquery';
import U2FAuthenticate from '../../u2f/authenticate'; import U2FAuthenticate from './authenticate';
export default () => { export default () => {
if (!gon.u2f) return; if (!gon.u2f) return;
const u2fAuthenticate = new U2FAuthenticate( const u2fAuthenticate = new U2FAuthenticate(
$('#js-authenticate-u2f'), $('#js-authenticate-token-2fa'),
'#js-login-u2f-form', '#js-login-token-2fa-form',
gon.u2f, gon.u2f,
document.querySelector('#js-login-2fa-device'), document.querySelector('#js-login-2fa-device'),
document.querySelector('.js-2fa-form'), document.querySelector('.js-2fa-form'),
); );
u2fAuthenticate.start(); u2fAuthenticate.start();
// needed in rspec // needed in rspec (FakeU2fDevice)
gl.u2fAuthenticate = u2fAuthenticate; gl.u2fAuthenticate = u2fAuthenticate;
}; };

View file

@ -78,7 +78,7 @@ export default class U2FRegister {
error_message: error.message(), error_message: error.message(),
error_code: error.errorCode, error_code: error.errorCode,
}); });
return this.container.find('#js-u2f-try-again').on('click', this.renderSetup); return this.container.find('#js-token-2fa-try-again').on('click', this.renderSetup);
} }
renderRegistered(deviceResponse) { renderRegistered(deviceResponse) {

View file

@ -1,16 +0,0 @@
import $ from 'jquery';
export default function initAvatarPicker() {
$('.js-choose-avatar-button').on('click', function onClickAvatar() {
const form = $(this).closest('form');
return form.find('.js-avatar-input').click();
});
$('.js-avatar-input').on('change', function onChangeAvatarInput() {
const form = $(this).closest('form');
const filename = $(this)
.val()
.replace(/^.*[\\\/]/, ''); // eslint-disable-line no-useless-escape
return form.find('.js-avatar-filename').text(filename);
});
}

View file

@ -84,15 +84,10 @@ export default {
<div v-show="hasError" class="btn-group"> <div v-show="hasError" class="btn-group">
<div class="btn btn-default btn-sm disabled"> <div class="btn btn-default btn-sm disabled">
<icon <icon :size="16" class="gl-ml-3 gl-mr-3" name="doc-image" aria-hidden="true" />
:size="16"
class="prepend-left-8 append-right-8"
name="doc-image"
aria-hidden="true"
/>
</div> </div>
<div class="btn btn-default btn-sm disabled"> <div class="btn btn-default btn-sm disabled">
<span class="prepend-left-8 append-right-8">{{ s__('Badges|No badge image') }}</span> <span class="gl-ml-3 gl-mr-3">{{ s__('Badges|No badge image') }}</span>
</div> </div>
</div> </div>

View file

@ -54,7 +54,7 @@ export default {
<div v-if="canEditBadge" class="table-action-buttons"> <div v-if="canEditBadge" class="table-action-buttons">
<button <button
:disabled="badge.isDeleting" :disabled="badge.isDeleting"
class="btn btn-default append-right-8" class="btn btn-default gl-mr-3"
type="button" type="button"
@click="editBadge(badge)" @click="editBadge(badge)"
> >

View file

@ -0,0 +1,41 @@
<script>
import { mapGetters } from 'vuex';
import imageDiff from '~/diffs/mixins/image_diff';
import DraftNote from './draft_note.vue';
export default {
components: {
DraftNote,
},
mixins: [imageDiff],
props: {
fileHash: {
type: String,
required: true,
},
},
computed: {
...mapGetters('batchComments', ['draftsForFile']),
drafts() {
return this.draftsForFile(this.fileHash);
},
},
};
</script>
<template>
<div>
<div
v-for="(draft, index) in drafts"
:key="draft.id"
class="discussion-notes diff-discussions position-relative"
>
<div class="notes">
<span class="d-block btn-transparent badge badge-pill is-draft js-diff-notes-index">
{{ toggleText(draft, index) }}
</span>
<draft-note :draft="draft" />
</div>
</div>
</div>
</template>

View file

@ -0,0 +1,113 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import NoteableNote from '~/notes/components/noteable_note.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import PublishButton from './publish_button.vue';
export default {
components: {
NoteableNote,
PublishButton,
LoadingButton,
},
props: {
draft: {
type: Object,
required: true,
},
diffFile: {
type: Object,
required: false,
default: () => ({}),
},
line: {
type: Object,
required: false,
default: null,
},
},
data() {
return {
isEditingDraft: false,
};
},
computed: {
...mapState('batchComments', ['isPublishing']),
...mapGetters('batchComments', ['isPublishingDraft']),
draftCommands() {
return this.draft.references.commands;
},
},
mounted() {
if (window.location.hash && window.location.hash === `#note_${this.draft.id}`) {
this.scrollToDraft(this.draft);
}
},
methods: {
...mapActions('batchComments', [
'deleteDraft',
'updateDraft',
'publishSingleDraft',
'scrollToDraft',
'toggleResolveDiscussion',
]),
update(data) {
this.updateDraft(data);
},
publishNow() {
this.publishSingleDraft(this.draft.id);
},
handleEditing() {
this.isEditingDraft = true;
},
handleNotEditing() {
this.isEditingDraft = false;
},
},
};
</script>
<template>
<article class="draft-note-component note-wrapper">
<ul class="notes draft-notes">
<noteable-note
:note="draft"
:diff-lines="diffFile.highlighted_diff_lines"
:line="line"
class="draft-note"
@handleEdit="handleEditing"
@cancelForm="handleNotEditing"
@updateSuccess="handleNotEditing"
@handleDeleteNote="deleteDraft"
@handleUpdateNote="update"
@toggleResolveStatus="toggleResolveDiscussion(draft.id)"
>
<strong slot="note-header-info" class="badge draft-pending-label append-right-4">
{{ __('Pending') }}
</strong>
</noteable-note>
</ul>
<template v-if="!isEditingDraft">
<div
v-if="draftCommands"
class="referenced-commands draft-note-commands"
v-html="draftCommands"
></div>
<p class="draft-note-actions d-flex">
<publish-button
:show-count="true"
:should-publish="false"
class="btn btn-success btn-inverted gl-mr-3"
/>
<loading-button
ref="publishNowButton"
:loading="isPublishingDraft(draft.id) || isPublishing"
:label="__('Add comment now')"
container-class="btn btn-inverted"
@click="publishNow"
/>
</p>
</template>
</article>
</template>

View file

@ -0,0 +1,15 @@
<script>
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters('batchComments', ['draftsCount']),
},
};
</script>
<template>
<span class="drafts-count-component">
<span class="drafts-count-number">{{ draftsCount }}</span>
<span class="sr-only"> {{ n__('draft', 'drafts', draftsCount) }} </span>
</span>
</template>

View file

@ -0,0 +1,32 @@
<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

@ -0,0 +1,45 @@
<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" /></div>
</td>
<td class="notes_line new"></td>
<td class="notes-content parallel new" colspan="2">
<div v-if="rightDraft.isDraft" class="content"><draft-note :draft="rightDraft" /></div>
</td>
</tr>
</template>

View file

@ -0,0 +1,111 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import { sprintf, n__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import DraftsCount from './drafts_count.vue';
import PublishButton from './publish_button.vue';
import PreviewItem from './preview_item.vue';
export default {
components: {
GlLoadingIcon,
Icon,
DraftsCount,
PublishButton,
PreviewItem,
},
computed: {
...mapGetters(['isNotesFetched']),
...mapGetters('batchComments', ['draftsCount', 'sortedDrafts']),
...mapState('batchComments', ['showPreviewDropdown']),
dropdownTitle() {
return sprintf(
n__('%{count} pending comment', '%{count} pending comments', this.draftsCount),
{ count: this.draftsCount },
);
},
},
watch: {
showPreviewDropdown() {
if (this.showPreviewDropdown && this.$refs.dropdown) {
this.$nextTick(() => this.$refs.dropdown.focus());
}
},
},
mounted() {
document.addEventListener('click', this.onClickDocument);
},
beforeDestroy() {
document.removeEventListener('click', this.onClickDocument);
},
methods: {
...mapActions('batchComments', ['toggleReviewDropdown']),
isLast(index) {
return index === this.sortedDrafts.length - 1;
},
onClickDocument({ target }) {
if (
this.showPreviewDropdown &&
!target.closest('.review-preview-dropdown, .js-publish-draft-button')
) {
this.toggleReviewDropdown();
}
},
},
};
</script>
<template>
<div
class="dropdown float-right review-preview-dropdown"
:class="{
show: showPreviewDropdown,
}"
>
<button
ref="dropdown"
type="button"
class="btn btn-success review-preview-dropdown-toggle qa-review-preview-toggle"
@click="toggleReviewDropdown"
>
{{ __('Finish review') }}
<drafts-count />
<icon name="angle-up" />
</button>
<div
class="dropdown-menu dropdown-menu-large dropdown-menu-right dropdown-open-top"
:class="{
show: showPreviewDropdown,
}"
>
<div class="dropdown-title">
{{ dropdownTitle }}
<button
:aria-label="__('Close')"
type="button"
class="dropdown-title-button dropdown-menu-close"
@click="toggleReviewDropdown"
>
<icon name="close" />
</button>
</div>
<div class="dropdown-content">
<ul v-if="isNotesFetched">
<li v-for="(draft, index) in sortedDrafts" :key="draft.id">
<preview-item :draft="draft" :is-last="isLast(index)" />
</li>
</ul>
<gl-loading-icon v-else size="lg" class="prepend-top-default append-bottom-default" />
</div>
<div class="dropdown-footer">
<publish-button
:show-count="false"
:should-publish="true"
:label="__('Submit review')"
class="float-right gl-mr-3"
/>
</div>
</div>
</div>
</template>

View file

@ -0,0 +1,143 @@
<script>
import { mapActions, mapGetters } from 'vuex';
import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
import { sprintf, __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import resolvedStatusMixin from '../mixins/resolved_status';
import { GlSprintf } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
getStartLineNumber,
getEndLineNumber,
getLineClasses,
} from '~/notes/components/multiline_comment_utils';
export default {
components: {
Icon,
GlSprintf,
},
mixins: [resolvedStatusMixin, glFeatureFlagsMixin()],
props: {
draft: {
type: Object,
required: true,
},
isLast: {
type: Boolean,
required: true,
},
},
computed: {
...mapGetters('diffs', ['getDiffFileByHash']),
...mapGetters(['getDiscussion']),
iconName() {
return this.isDiffDiscussion || this.draft.line_code ? 'doc-text' : 'comment';
},
discussion() {
return this.getDiscussion(this.draft.discussion_id);
},
isDiffDiscussion() {
return this.discussion && this.discussion.diff_discussion;
},
titleText() {
const file = this.discussion ? this.discussion.diff_file : this.draft;
if (file) {
return file.file_path;
}
return sprintf(__("%{authorsName}'s thread"), {
authorsName: this.discussion.notes.find(note => !note.system).author.name,
});
},
linePosition() {
if (this.draft.position && this.draft.position.position_type === IMAGE_DIFF_POSITION_TYPE) {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `${this.draft.position.x}x ${this.draft.position.y}y`;
}
const position = this.discussion ? this.discussion.position : this.draft.position;
return position?.new_line || position?.old_line;
},
content() {
const el = document.createElement('div');
el.innerHTML = this.draft.note_html;
return el.textContent;
},
showLinePosition() {
return this.draft.file_hash || this.isDiffDiscussion;
},
startLineNumber() {
return getStartLineNumber(this.draft.position?.line_range);
},
endLineNumber() {
return getEndLineNumber(this.draft.position?.line_range);
},
},
methods: {
...mapActions('batchComments', ['scrollToDraft']),
getLineClasses(lineNumber) {
return getLineClasses(lineNumber);
},
},
showStaysResolved: false,
};
</script>
<template>
<button
type="button"
class="review-preview-item menu-item"
:class="[
componentClasses,
{
'is-last': isLast,
},
]"
@click="scrollToDraft(draft)"
>
<span class="review-preview-item-header">
<icon class="flex-shrink-0" :name="iconName" />
<span
class="bold text-nowrap"
:class="{ 'gl-align-items-center': glFeatures.multilineComments }"
>
<span class="review-preview-item-header-text block-truncated">
{{ titleText }}
</span>
<template v-if="showLinePosition">
<template v-if="!glFeatures.multilineComments"
>:{{ linePosition }}</template
>
<template v-else-if="startLineNumber === endLineNumber">
:<span :class="getLineClasses(startLineNumber)">{{ startLineNumber }}</span>
</template>
<gl-sprintf v-else :message="__(':%{startLine} to %{endLine}')">
<template #startLine>
<span class="gl-mr-2" :class="getLineClasses(startLineNumber)">{{
startLineNumber
}}</span>
</template>
<template #endLine>
<span class="gl-ml-2" :class="getLineClasses(endLineNumber)">{{
endLineNumber
}}</span>
</template>
</gl-sprintf>
</template>
</span>
</span>
<span class="review-preview-item-content">
<p>{{ content }}</p>
</span>
<span
v-if="draft.discussion_id && resolvedStatusMessage"
class="review-preview-item-footer draft-note-resolution p-0"
>
<icon class="gl-mr-3" name="status_success" /> {{ resolvedStatusMessage }}
</span>
</button>
</template>

View file

@ -0,0 +1,55 @@
<script>
import { mapActions, mapState } from 'vuex';
import { __ } from '~/locale';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import DraftsCount from './drafts_count.vue';
export default {
components: {
LoadingButton,
DraftsCount,
},
props: {
showCount: {
type: Boolean,
required: false,
default: false,
},
label: {
type: String,
required: false,
default: __('Finish review'),
},
shouldPublish: {
type: Boolean,
required: true,
},
},
computed: {
...mapState('batchComments', ['isPublishing']),
},
methods: {
...mapActions('batchComments', ['publishReview', 'toggleReviewDropdown']),
onClick() {
if (this.shouldPublish) {
this.publishReview();
} else {
this.toggleReviewDropdown();
}
},
},
};
</script>
<template>
<loading-button
:loading="isPublishing"
container-class="btn btn-success js-publish-draft-button qa-submit-review"
@click="onClick"
>
<span>
{{ label }}
<drafts-count v-if="showCount" />
</span>
</loading-button>
</template>

View file

@ -0,0 +1,70 @@
<script>
import { mapActions, mapState, mapGetters } from 'vuex';
import { GlModal, GlModalDirective } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import PreviewDropdown from './preview_dropdown.vue';
export default {
components: {
LoadingButton,
GlModal,
PreviewDropdown,
},
directives: {
'gl-modal': GlModalDirective,
},
computed: {
...mapGetters(['isNotesFetched']),
...mapState('batchComments', ['isDiscarding']),
...mapGetters('batchComments', ['draftsCount']),
},
watch: {
isNotesFetched() {
if (this.isNotesFetched) {
this.expandAllDiscussions();
}
},
},
methods: {
...mapActions('batchComments', ['discardReview', 'expandAllDiscussions']),
},
modalId: 'discard-draft-review',
text: sprintf(
s__(
`BatchComments|You're about to discard your review which will delete all of your pending comments.
The deleted comments %{strong_start}cannot%{strong_end} be restored.`,
),
{
strong_start: '<strong>',
strong_end: '</strong>',
},
false,
),
};
</script>
<template>
<div v-show="draftsCount > 0">
<nav class="review-bar-component">
<div class="review-bar-content qa-review-bar">
<preview-dropdown />
<loading-button
v-gl-modal="$options.modalId"
:loading="isDiscarding"
:label="__('Discard review')"
class="qa-discard-review float-right"
/>
</div>
</nav>
<gl-modal
:title="s__('BatchComments|Discard review?')"
:ok-title="s__('BatchComments|Delete all pending comments')"
:modal-id="$options.modalId"
title-tag="h4"
ok-variant="danger qa-modal-delete-pending-comments"
@ok="discardReview"
>
<p v-html="$options.text"></p>
</gl-modal>
</div>
</template>

View file

@ -0,0 +1,3 @@
export const CHANGES_TAB = 'diffs';
export const DISCUSSION_TAB = 'notes';
export const SHOW_TAB = 'show';

View file

@ -0,0 +1,24 @@
import Vue from 'vue';
import { mapActions } from 'vuex';
import store from '~/mr_notes/stores';
import ReviewBar from './components/review_bar.vue';
// eslint-disable-next-line import/prefer-default-export
export const initReviewBar = () => {
const el = document.getElementById('js-review-bar');
// eslint-disable-next-line no-new
new Vue({
el,
store,
mounted() {
this.fetchDrafts();
},
methods: {
...mapActions('batchComments', ['fetchDrafts']),
},
render(createElement) {
return createElement(ReviewBar);
},
});
};

View file

@ -1,9 +1,58 @@
import { sprintf, __ } from '~/locale'; import { mapGetters } from 'vuex';
import { sprintf, s__, __ } from '~/locale';
export default { export default {
props: {
discussionId: {
type: String,
required: false,
default: null,
},
resolveDiscussion: {
type: Boolean,
required: false,
default: false,
},
isDraft: {
type: Boolean,
required: false,
default: false,
},
},
computed: { computed: {
...mapGetters(['isDiscussionResolved']),
resolvedStatusMessage() {
let message;
const discussionResolved = this.isDiscussionResolved(
this.draft ? this.draft.discussion_id : this.discussionId,
);
const discussionToBeResolved = this.draft
? this.draft.resolve_discussion
: this.resolveDiscussion;
if (discussionToBeResolved && discussionResolved && !this.$options.showStaysResolved) {
return undefined;
}
if (discussionToBeResolved) {
message = discussionResolved
? s__('MergeRequests|Thread stays resolved')
: s__('MergeRequests|Thread will be resolved');
} else if (discussionResolved) {
message = s__('MergeRequests|Thread will be unresolved');
} else if (this.$options.showStaysResolved) {
message = s__('MergeRequests|Thread stays unresolved');
}
return message;
},
componentClasses() {
return this.resolveDiscussion ? 'is-resolving-discussion' : 'is-unresolving-discussion';
},
resolveButtonTitle() { resolveButtonTitle() {
let title = __('Mark comment as resolved'); if (this.isDraft || this.discussionId) return this.resolvedStatusMessage;
let title = __('Mark as resolved');
if (this.resolvedBy) { if (this.resolvedBy) {
title = sprintf(__('Resolved by %{name}'), { name: this.resolvedBy.name }); title = sprintf(__('Resolved by %{name}'), { name: this.resolvedBy.name });
@ -12,4 +61,5 @@ export default {
return title; return title;
}, },
}, },
showStaysResolved: true,
}; };

View file

@ -0,0 +1,33 @@
import axios from '~/lib/utils/axios_utils';
export default {
createNewDraft(endpoint, data) {
const postData = { ...data, draft_note: data.note };
delete postData.note;
return axios.post(endpoint, postData);
},
deleteDraft(endpoint, draftId) {
return axios.delete(`${endpoint}/${draftId}`);
},
publishDraft(endpoint, draftId) {
return axios.post(endpoint, { id: draftId });
},
addDraftToDiscussion(endpoint, data) {
return axios.post(endpoint, data);
},
fetchDrafts(endpoint) {
return axios.get(endpoint);
},
publish(endpoint) {
return axios.post(endpoint);
},
discard(endpoint) {
return axios.delete(endpoint);
},
update(endpoint, { draftId, note, resolveDiscussion, position }) {
return axios.put(`${endpoint}/${draftId}`, {
draft_note: { note, resolve_discussion: resolveDiscussion, position },
});
},
};

View file

@ -0,0 +1,14 @@
import Vue from 'vue';
import Vuex from 'vuex';
import batchComments from './modules/batch_comments';
Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
modules: {
batchComments: batchComments(),
},
});
export default createStore();

View file

@ -0,0 +1,151 @@
import flash from '~/flash';
import { __ } from '~/locale';
import { scrollToElement } from '~/lib/utils/common_utils';
import service from '../../../services/drafts_service';
import * as types from './mutation_types';
import { CHANGES_TAB, DISCUSSION_TAB, SHOW_TAB } from '../../../constants';
export const saveDraft = ({ dispatch }, draft) =>
dispatch('saveNote', { ...draft, isDraft: true }, { root: true });
export const addDraftToDiscussion = ({ commit }, { endpoint, data }) =>
service
.addDraftToDiscussion(endpoint, data)
.then(res => res.data)
.then(res => {
commit(types.ADD_NEW_DRAFT, res);
return res;
})
.catch(() => {
flash(__('An error occurred adding a draft to the thread.'));
});
export const createNewDraft = ({ commit }, { endpoint, data }) =>
service
.createNewDraft(endpoint, data)
.then(res => res.data)
.then(res => {
commit(types.ADD_NEW_DRAFT, res);
return res;
})
.catch(() => {
flash(__('An error occurred adding a new draft.'));
});
export const deleteDraft = ({ commit, getters }, draft) =>
service
.deleteDraft(getters.getNotesData.draftsPath, draft.id)
.then(() => {
commit(types.DELETE_DRAFT, draft.id);
})
.catch(() => flash(__('An error occurred while deleting the comment')));
export const fetchDrafts = ({ commit, getters }) =>
service
.fetchDrafts(getters.getNotesData.draftsPath)
.then(res => res.data)
.then(data => commit(types.SET_BATCH_COMMENTS_DRAFTS, data))
.catch(() => flash(__('An error occurred while fetching pending comments')));
export const publishSingleDraft = ({ commit, dispatch, getters }, draftId) => {
commit(types.REQUEST_PUBLISH_DRAFT, draftId);
service
.publishDraft(getters.getNotesData.draftsPublishPath, draftId)
.then(() => dispatch('updateDiscussionsAfterPublish'))
.then(() => commit(types.RECEIVE_PUBLISH_DRAFT_SUCCESS, draftId))
.catch(() => commit(types.RECEIVE_PUBLISH_DRAFT_ERROR, draftId));
};
export const publishReview = ({ commit, dispatch, getters }) => {
commit(types.REQUEST_PUBLISH_REVIEW);
return service
.publish(getters.getNotesData.draftsPublishPath)
.then(() => dispatch('updateDiscussionsAfterPublish'))
.then(() => commit(types.RECEIVE_PUBLISH_REVIEW_SUCCESS))
.catch(() => commit(types.RECEIVE_PUBLISH_REVIEW_ERROR));
};
export const updateDiscussionsAfterPublish = ({ dispatch, getters, rootGetters }) =>
dispatch('fetchDiscussions', { path: getters.getNotesData.discussionsPath }, { root: true }).then(
() =>
dispatch('diffs/assignDiscussionsToDiff', rootGetters.discussionsStructuredByLineCode, {
root: true,
}),
);
export const discardReview = ({ commit, getters }) => {
commit(types.REQUEST_DISCARD_REVIEW);
return service
.discard(getters.getNotesData.draftsDiscardPath)
.then(() => commit(types.RECEIVE_DISCARD_REVIEW_SUCCESS))
.catch(() => commit(types.RECEIVE_DISCARD_REVIEW_ERROR));
};
export const updateDraft = (
{ commit, getters },
{ note, noteText, resolveDiscussion, position, callback },
) =>
service
.update(getters.getNotesData.draftsPath, {
draftId: note.id,
note: noteText,
resolveDiscussion,
position: JSON.stringify(position),
})
.then(res => res.data)
.then(data => commit(types.RECEIVE_DRAFT_UPDATE_SUCCESS, data))
.then(callback)
.catch(() => flash(__('An error occurred while updating the comment')));
export const scrollToDraft = ({ dispatch, rootGetters }, draft) => {
const discussion = draft.discussion_id && rootGetters.getDiscussion(draft.discussion_id);
const tab =
draft.file_hash || (discussion && discussion.diff_discussion) ? CHANGES_TAB : SHOW_TAB;
const tabEl = tab === CHANGES_TAB ? CHANGES_TAB : DISCUSSION_TAB;
const draftID = `note_${draft.id}`;
const el = document.querySelector(`#${tabEl} #${draftID}`);
dispatch('closeReviewDropdown');
window.location.hash = draftID;
if (window.mrTabs.currentAction !== tab) {
window.mrTabs.tabShown(tab);
}
if (discussion) {
dispatch('expandDiscussion', { discussionId: discussion.id }, { root: true });
}
if (el) {
setTimeout(() => scrollToElement(el.closest('.draft-note-component')));
}
};
export const toggleReviewDropdown = ({ dispatch, state }) => {
if (state.showPreviewDropdown) {
dispatch('closeReviewDropdown');
} else {
dispatch('openReviewDropdown');
}
};
export const openReviewDropdown = ({ commit }) => commit(types.OPEN_REVIEW_DROPDOWN);
export const closeReviewDropdown = ({ commit }) => commit(types.CLOSE_REVIEW_DROPDOWN);
export const expandAllDiscussions = ({ dispatch, state }) =>
state.drafts
.filter(draft => draft.discussion_id)
.forEach(draft => {
dispatch('expandDiscussion', { discussionId: draft.discussion_id }, { root: true });
});
export const toggleResolveDiscussion = ({ commit }, draftId) => {
commit(types.TOGGLE_RESOLVE_DISCUSSION, draftId);
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};

View file

@ -0,0 +1,87 @@
import { parallelLineKey, showDraftOnSide } from '../../../utils';
export const draftsCount = state => state.drafts.length;
export const getNotesData = (state, getters, rootState, rootGetters) => rootGetters.getNotesData;
export const hasDrafts = state => state.drafts.length > 0;
export const draftsPerDiscussionId = state =>
state.drafts.reduce((acc, draft) => {
if (draft.discussion_id) {
acc[draft.discussion_id] = draft;
}
return acc;
}, {});
export const draftsPerFileHashAndLine = state =>
state.drafts.reduce((acc, draft) => {
if (draft.file_hash) {
if (!acc[draft.file_hash]) {
acc[draft.file_hash] = {};
}
acc[draft.file_hash][draft.line_code] = draft;
}
return acc;
}, {});
export const shouldRenderDraftRow = (state, getters) => (diffFileSha, line) =>
Boolean(
diffFileSha in getters.draftsPerFileHashAndLine &&
getters.draftsPerFileHashAndLine[diffFileSha][line.line_code],
);
export const shouldRenderParallelDraftRow = (state, getters) => (diffFileSha, line) => {
const draftsForFile = getters.draftsPerFileHashAndLine[diffFileSha];
const [lkey, rkey] = [parallelLineKey(line, 'left'), parallelLineKey(line, 'right')];
return draftsForFile ? Boolean(draftsForFile[lkey] || draftsForFile[rkey]) : false;
};
export const hasParallelDraftLeft = (state, getters) => (diffFileSha, line) => {
const draftsForFile = getters.draftsPerFileHashAndLine[diffFileSha];
const lkey = parallelLineKey(line, 'left');
return draftsForFile ? Boolean(draftsForFile[lkey]) : false;
};
export const hasParallelDraftRight = (state, getters) => (diffFileSha, line) => {
const draftsForFile = getters.draftsPerFileHashAndLine[diffFileSha];
const rkey = parallelLineKey(line, 'left');
return draftsForFile ? Boolean(draftsForFile[rkey]) : false;
};
export const shouldRenderDraftRowInDiscussion = (state, getters) => discussionId =>
typeof getters.draftsPerDiscussionId[discussionId] !== 'undefined';
export const draftForDiscussion = (state, getters) => discussionId =>
getters.draftsPerDiscussionId[discussionId] || {};
export const draftForLine = (state, getters) => (diffFileSha, line, side = null) => {
const draftsForFile = getters.draftsPerFileHashAndLine[diffFileSha];
const key = side !== null ? parallelLineKey(line, side) : line.line_code;
if (draftsForFile) {
const draft = draftsForFile[key];
if (draft && showDraftOnSide(line, side)) {
return draft;
}
}
return {};
};
export const draftsForFile = state => diffFileSha =>
state.drafts.filter(draft => draft.file_hash === diffFileSha);
export const isPublishingDraft = state => draftId =>
state.currentlyPublishingDrafts.indexOf(draftId) !== -1;
export const sortedDrafts = state => [...state.drafts].sort((a, b) => a.id > b.id);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};

View file

@ -0,0 +1,12 @@
import state from './state';
import mutations from './mutations';
import * as actions from './actions';
import * as getters from './getters';
export default () => ({
namespaced: true,
state: state(),
mutations,
actions,
getters,
});

View file

@ -0,0 +1,23 @@
export const ENABLE_BATCH_COMMENTS = 'ENABLE_BATCH_COMMENTS';
export const ADD_NEW_DRAFT = 'ADD_NEW_DRAFT';
export const DELETE_DRAFT = 'DELETE_DRAFT';
export const SET_BATCH_COMMENTS_DRAFTS = 'SET_BATCH_COMMENTS_DRAFTS';
export const REQUEST_PUBLISH_DRAFT = 'REQUEST_PUBLISH_DRAFT';
export const RECEIVE_PUBLISH_DRAFT_SUCCESS = 'RECEIVE_PUBLISH_DRAFT_SUCCESS';
export const RECEIVE_PUBLISH_DRAFT_ERROR = 'RECEIVE_PUBLISH_DRAFT_ERROR';
export const REQUEST_PUBLISH_REVIEW = 'REQUEST_PUBLISH_REVIEW';
export const RECEIVE_PUBLISH_REVIEW_SUCCESS = 'RECEIVE_PUBLISH_REVIEW_SUCCESS';
export const RECEIVE_PUBLISH_REVIEW_ERROR = 'RECEIVE_PUBLISH_REVIEW_ERROR';
export const REQUEST_DISCARD_REVIEW = 'REQUEST_DISCARD_REVIEW';
export const RECEIVE_DISCARD_REVIEW_SUCCESS = 'RECEIVE_DISCARD_REVIEW_SUCCESS';
export const RECEIVE_DISCARD_REVIEW_ERROR = 'RECEIVE_DISCARD_REVIEW_ERROR';
export const RECEIVE_DRAFT_UPDATE_SUCCESS = 'RECEIVE_DRAFT_UPDATE_SUCCESS';
export const OPEN_REVIEW_DROPDOWN = 'OPEN_REVIEW_DROPDOWN';
export const CLOSE_REVIEW_DROPDOWN = 'CLOSE_REVIEW_DROPDOWN';
export const TOGGLE_RESOLVE_DISCUSSION = 'TOGGLE_RESOLVE_DISCUSSION';

View file

@ -0,0 +1,81 @@
import * as types from './mutation_types';
const processDraft = draft => ({
...draft,
isDraft: true,
});
export default {
[types.ADD_NEW_DRAFT](state, draft) {
state.drafts.push(processDraft(draft));
},
[types.DELETE_DRAFT](state, draftId) {
state.drafts = state.drafts.filter(draft => draft.id !== draftId);
},
[types.SET_BATCH_COMMENTS_DRAFTS](state, drafts) {
state.drafts = drafts.map(processDraft);
},
[types.REQUEST_PUBLISH_DRAFT](state, draftId) {
state.currentlyPublishingDrafts.push(draftId);
},
[types.RECEIVE_PUBLISH_DRAFT_SUCCESS](state, draftId) {
state.currentlyPublishingDrafts = state.currentlyPublishingDrafts.filter(
publishingDraftId => publishingDraftId !== draftId,
);
state.drafts = state.drafts.filter(d => d.id !== draftId);
},
[types.RECEIVE_PUBLISH_DRAFT_ERROR](state, draftId) {
state.currentlyPublishingDrafts = state.currentlyPublishingDrafts.filter(
publishingDraftId => publishingDraftId !== draftId,
);
},
[types.REQUEST_PUBLISH_REVIEW](state) {
state.isPublishing = true;
},
[types.RECEIVE_PUBLISH_REVIEW_SUCCESS](state) {
state.isPublishing = false;
state.drafts = [];
},
[types.RECEIVE_PUBLISH_REVIEW_ERROR](state) {
state.isPublishing = false;
},
[types.REQUEST_DISCARD_REVIEW](state) {
state.isDiscarding = true;
},
[types.RECEIVE_DISCARD_REVIEW_SUCCESS](state) {
state.isDiscarding = false;
state.drafts = [];
},
[types.RECEIVE_DISCARD_REVIEW_ERROR](state) {
state.isDiscarding = false;
},
[types.RECEIVE_DRAFT_UPDATE_SUCCESS](state, data) {
const index = state.drafts.findIndex(draft => draft.id === data.id);
if (index >= 0) {
state.drafts.splice(index, 1, processDraft(data));
}
},
[types.OPEN_REVIEW_DROPDOWN](state) {
state.showPreviewDropdown = true;
},
[types.CLOSE_REVIEW_DROPDOWN](state) {
state.showPreviewDropdown = false;
},
[types.TOGGLE_RESOLVE_DISCUSSION](state, draftId) {
state.drafts = state.drafts.map(draft => {
if (draft.id === draftId) {
return {
...draft,
resolve_discussion: !draft.resolve_discussion,
};
}
return draft;
});
},
};

View file

@ -0,0 +1,9 @@
export default () => ({
withBatchComments: true,
isDraftsFetched: false,
drafts: [],
isPublishing: false,
currentlyPublishingDrafts: [],
isDiscarding: false,
showPreviewDropdown: false,
});

View file

@ -0,0 +1,35 @@
import { getFormData } from '~/diffs/store/utils';
export const getDraftReplyFormData = data => ({
endpoint: data.notesData.draftsPath,
data,
});
export const getDraftFormData = params => ({
endpoint: params.notesData.draftsPath,
data: getFormData(params),
});
export const parallelLineKey = (line, side) => (line[side] ? line[side].line_code : '');
export const showDraftOnSide = (line, side) => {
// inline mode
if (side === null) {
return true;
}
// parallel
if (side === 'left' || side === 'right') {
const otherSide = side === 'left' ? 'right' : 'left';
const thisCode = (line[side] && line[side].line_code) || '';
const otherCode = (line[otherSide] && line[otherSide].line_code) || '';
// either the lineCodes are different
// or if they're the same, only show on the left side
if (thisCode !== otherCode || (side === 'left' && thisCode === otherCode)) {
return true;
}
}
return false;
};

View file

@ -25,9 +25,10 @@ function importMermaidModule() {
return import(/* webpackChunkName: 'mermaid' */ 'mermaid') return import(/* webpackChunkName: 'mermaid' */ 'mermaid')
.then(mermaid => { .then(mermaid => {
let theme = 'neutral'; let theme = 'neutral';
const ideDarkThemes = ['dark', 'solarized-dark'];
if ( if (
window.gon?.user_color_scheme === 'dark' && ideDarkThemes.includes(window.gon?.user_color_scheme) &&
// if on the Web IDE page // if on the Web IDE page
document.querySelector('.ide') document.querySelector('.ide')
) { ) {

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