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}]
indent_style = space
charset = utf-8
[*.{md,markdown}]
trim_trailing_whitespace = false

View File

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

4
.gitignore vendored
View File

@ -35,7 +35,6 @@ eslint-report.html
/config/gitlab.yml
/config/gitlab_ci.yml
/config/Gitlab.gitlab-license
/config/initializers/rack_attack.rb
/config/initializers/smtp_settings.rb
/config/initializers/relative_url.rb
/config/resque.yml
@ -92,3 +91,6 @@ jsdoc/
webpack-dev-server.json
/.nvimrc
.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
# definition must be extended with `.use-docker-in-docker`
default:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.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:
- gitlab-org
# All jobs are interruptible by default

View File

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

View File

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

View File

@ -1,157 +1,128 @@
.assets-compile-cache:
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:
.frontend-base:
extends:
- .default-retry
- .default-before_script
- .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
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:
NODE_ENV: "production"
RAILS_ENV: "production"
SETUP_DB: "false"
SKIP_STORAGE_VALIDATION: "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:
name: webpack-report
expire_in: 31d
paths:
- webpack-report/
- assets-compile.log
# These assets are used in multiple locations:
# - 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
- public/assets
- public/assets/
- webpack-report/
when: always
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
after_script:
- 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:
- .gitlab:assets:compile-metadata
- .frontend:rules:gitlab-assets-compile-pull-push-cache
cache:
policy: pull-push
- .compile-assets-base
- .frontend:rules:compile-test-assets
artifacts:
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:
- .gitlab:assets:compile-metadata
- .frontend:rules:gitlab-assets-compile-pull-cache
- compile-test-assets
- .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:
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:
extends:
- .use-kaniko
- .frontend:rules:gitlab-assets-compile-pull-cache
- .frontend:rules:compile-production-assets
stage: build-images
needs: ["gitlab:assets:compile pull-cache"]
needs: ["compile-production-assets"]
variables:
GIT_DEPTH: "1"
script:
# TODO: Change the image tag to be the MD5 of assets files and skip image building if the image exists
# We'll also need to pass GITLAB_ASSETS_TAG to the trigerred omnibus-gitlab pipeline similarly to how we do it for trigerred CNG pipelines
# https://gitlab.com/gitlab-org/gitlab/issues/208389
- scripts/build_assets_image
.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"
- run_timed_command "scripts/build_assets_image"
retry: 2
.frontend-fixtures-base:
extends:
- .default-retry
- .frontend-base
- .rails-cache
- .default-before_script
- .use-pg11
stage: fixtures
needs: ["setup-test-env", "compile-assets pull-cache"]
needs: ["setup-test-env", "compile-test-assets"]
variables:
SETUP_DB: "true"
script:
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
- run_timed_command "bundle exec rake frontend:fixtures"
- run_timed_command "bin/rake frontend:fixtures"
artifacts:
name: frontend-fixtures
expire_in: 31d
when: always
paths:
- node_modules
- public/assets
- tmp/tests/frontend/
frontend-fixtures:
@ -165,25 +136,27 @@ frontend-fixtures-as-if-foss:
- .frontend:rules:default-frontend-jobs-as-if-foss
- .as-if-foss
.frontend-job-base:
.frontend-test-base:
extends:
- .default-retry
- .default-cache
- .default-before_script
- .yarn-cache
variables:
USE_BUNDLE_INSTALL: "false"
SETUP_DB: "false"
stage: test
before_script:
- source scripts/utils.sh
.karma-base:
extends: .frontend-job-base
extends: .frontend-test-base
variables:
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
script:
- source scripts/utils.sh
- export BABEL_ENV=coverage CHROME_LOG_FILE=chrome_debug.log
- date
- yarn karma
- run_timed_command "retry yarn install --frozen-lockfile"
- run_timed_command "yarn karma"
karma:
extends:
@ -210,15 +183,11 @@ karma-as-if-foss:
needs: ["frontend-fixtures-as-if-foss"]
.jest-base:
extends: .frontend-job-base
extends: .frontend-test-base
script:
- date
- yarn jest --ci --coverage --testSequencer ./scripts/frontend/parallel_ci_sequencer.js
cache:
key: jest
paths:
- tmp/cache/jest/
policy: pull-push
- source scripts/utils.sh
- run_timed_command "retry yarn install --frozen-lockfile"
- run_timed_command "yarn jest --ci --coverage --testSequencer ./scripts/frontend/parallel_ci_sequencer.js"
jest:
extends:
@ -235,21 +204,17 @@ jest:
- tmp/tests/frontend/
reports:
junit: junit_jest.xml
parallel: 2
parallel: 4
jest-integration:
extends:
- .frontend-job-base
- .frontend-test-base
- .frontend:rules:default-frontend-jobs
script:
- date
- yarn jest:integration --ci
- source scripts/utils.sh
- run_timed_command "retry yarn install --frozen-lockfile"
- run_timed_command "yarn jest:integration --ci"
needs: ["frontend-fixtures"]
cache:
key: jest-integration
paths:
- tmp/cache/jest/
policy: pull-push
jest-as-if-foss:
extends:
@ -257,8 +222,7 @@ jest-as-if-foss:
- .frontend:rules:default-frontend-jobs-as-if-foss
- .as-if-foss
needs: ["frontend-fixtures-as-if-foss"]
cache:
policy: pull
parallel: 2
coverage-frontend:
extends:
@ -269,33 +233,26 @@ coverage-frontend:
stage: post-test
before_script:
- source scripts/utils.sh
- retry yarn install --frozen-lockfile
- run_timed_command "retry yarn install --frozen-lockfile"
script:
- yarn node scripts/frontend/merge_coverage_frontend.js
- run_timed_command "yarn node scripts/frontend/merge_coverage_frontend.js"
artifacts:
name: coverage-frontend
expire_in: 31d
paths:
- coverage-frontend/
cache:
policy: pull
.qa-frontend-node:
extends:
- .default-retry
- .yarn-cache
- .frontend:rules:qa-frontend-node
stage: test
dependencies: []
cache:
key: "$CI_JOB_NAME"
paths:
- .yarn-cache/
policy: pull-push
script:
- date
- yarn install --frozen-lockfile --cache-folder .yarn-cache --prefer-offline
- date
- yarn run webpack-prod
- source scripts/utils.sh
- run_timed_command "yarn install --frozen-lockfile"
- run_timed_command "yarn run webpack-prod"
qa-frontend-node:10:
extends: .qa-frontend-node
@ -310,27 +267,39 @@ qa-frontend-node:latest:
webpack-dev-server:
extends:
- .default-retry
- .yarn-cache
- .frontend:rules:default-frontend-jobs
stage: test
needs: []
variables:
WEBPACK_MEMORY_TEST: "true"
WEBPACK_VENDOR_DLL: "true"
cache:
key:
files:
- yarn.lock
prefix: "v1"
paths:
- node_modules/
- tmp/cache/webpack-dlls/
script:
- source scripts/utils.sh
- retry yarn install --frozen-lockfile
- retry yarn webpack-vendor
- node --expose-gc node_modules/.bin/webpack-dev-server --config config/webpack.config.js
- run_timed_command "retry yarn install --frozen-lockfile"
- run_timed_command "retry yarn webpack-vendor"
- run_timed_command "node --expose-gc node_modules/.bin/webpack-dev-server --config config/webpack.config.js"
artifacts:
name: webpack-dev-server
expire_in: 31d
paths:
- 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:
before_script:
- date
- '[ "$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
- mkdir -p $GOPATH
- source scripts/utils.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:
cache:
key:
files:
- Gemfile.lock
- GITALY_SERVER_VERSION
prefix: "ruby-go-cache-v1"
key: "rails-v1"
paths:
- vendor/ruby
- vendor/gitaly-ruby
- .go/pkg/mod
- vendor/ruby/
- vendor/gitaly-ruby/
- .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
.yarn-cache:
cache:
key:
files:
- yarn.lock
prefix: "v1"
key: "yarn-v1"
paths:
- 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:
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:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@ -61,7 +73,7 @@
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg11-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.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:
- name: postgres:11.6
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
entrypoint: [""]
before_script:
- source scripts/utils.sh
- mkdir -p /kaniko/.docker
- 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:
extends:
- .default-retry
- .default-cache
- .rails-cache
- .default-before_script
- .memory:rules
@ -39,12 +39,11 @@ memory-on-boot:
- .only-code-memory-job-base
- .use-pg11
stage: test
needs: ["setup-test-env", "compile-assets pull-cache"]
needs: ["setup-test-env", "compile-test-assets"]
variables:
NODE_ENV: "production"
RAILS_ENV: "production"
SETUP_DB: "true"
SKIP_STORAGE_VALIDATION: "true"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
script:

View File

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

View File

@ -1,12 +1,9 @@
.qa-job-base:
extends:
- .default-retry
- .qa-cache
stage: test
needs: []
cache:
key: "qa-framework-jobs:v1"
paths:
- vendor/ruby
before_script:
- '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/ qa/spec/ee/ qa/qa/specs/features/ee/ qa/qa/ee/ qa/qa/ee.rb'
- cd qa/
@ -22,11 +19,9 @@ qa:internal:
qa:internal-as-if-foss:
extends:
- .qa-job-base
- qa:internal
- .qa:rules:as-if-foss
- .as-if-foss
script:
- bundle exec rspec
qa:selectors:
extends:
@ -41,6 +36,16 @@ qa:selectors-as-if-foss:
- .qa:rules: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:
image: ruby:2.6-alpine
stage: qa

View File

@ -1,6 +1,3 @@
.rails:needs:setup-and-assets:
needs: ["setup-test-env", "compile-assets pull-cache"]
.rails-job-base:
extends:
- .default-retry
@ -35,32 +32,54 @@ setup-test-env:
- tmp/tests/repositories
- tmp/tests/second_storage
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:
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
static-analysis:
extends:
- .rails-job-base
- .static-analysis-base
- .rails:rules:default-refs-code-backstage-qa
- .rails:needs:setup-and-assets
stage: test
variables:
SETUP_DB: "false"
parallel: 2
parallel: 4
script:
- run_timed_command "retry yarn install --frozen-lockfile"
- scripts/static-analysis
cache:
key: "ruby-2.6.6-pg11-rubocop"
paths:
- vendor/ruby
- tmp/rubocop_cache
policy: pull-push
downtime_check:
extends:
- .rails-job-base
- .rails:rules:downtime_check
needs: ["setup-test-env"]
needs: []
stage: test
variables:
SETUP_DB: "false"
@ -70,7 +89,7 @@ downtime_check:
.rspec-base:
extends: .rails-job-base
stage: test
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-assets pull-cache"]
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
script:
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
@ -173,7 +192,7 @@ db:migrate-from-v12.10.0:
db:rollback:
extends: .db-job-base
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
gitlab:setup:
@ -218,8 +237,6 @@ rspec:coverage:
- memory-on-boot
variables:
SETUP_DB: "false"
cache:
policy: pull
script:
- bundle exec scripts/merge-simplecov
- bundle exec scripts/gather-test-memory-data
@ -247,7 +264,7 @@ rspec:coverage:
- .rails:rules:as-if-foss
- .as-if-foss
- .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:
extends:
@ -323,3 +340,26 @@ db:rollback geo:
- bundle exec rake geo:db:migrate
# 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
needs: []
variables:
DS_MAJOR_VERSION: 2
DS_EXCLUDED_PATHS: "qa/qa/ee/fixtures/secure_premade_reports,spec,ee/spec" # GitLab-specific
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 [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
@ -138,7 +138,7 @@ dependency_scanning:
) \
--volume "$PWD:/code" \
--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:
paths:
- gl-dependency-scanning-report.json # GitLab-specific
@ -146,37 +146,38 @@ dependency_scanning:
dependency_scanning: gl-dependency-scanning-report.json
expire_in: 1 week # GitLab-specific
# We need to duplicate this job's definition because it seems it's impossible to
# override an included `only.refs`.
# See https://gitlab.com/gitlab-org/gitlab/issues/31371.
dast:
extends:
- .default-retry
- .reports:rules:dast
# This is needed so that manual jobs with needs don't block the pipeline.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
dependencies: ["review-deploy"]
stage: qa # GitLab-specific
image:
name: "registry.gitlab.com/gitlab-org/security-products/dast:$DAST_VERSION"
variables:
# To be done in a later iteration
# DAST_USERNAME: "root"
# DAST_USERNAME_FIELD: "user[login]"
# DAST_PASSWORD_FIELD: "user[passowrd]"
DAST_VERSION: 1
script:
- 'export DAST_WEBSITE="${DAST_WEBSITE:-$(cat environment_url.txt)}"'
# To be done in a later iteration
# - 'export DAST_AUTH_URL="${DAST_WEBSITE}/users/sign_in"'
# - 'export DAST_PASSWORD="${REVIEW_APPS_ROOT_PASSWORD}"'
- /analyze -t $DAST_WEBSITE
artifacts:
paths:
- gl-dast-report.json # GitLab-specific
reports:
dast: gl-dast-report.json
expire_in: 1 week # GitLab-specific
# Temporarily disabling review apps
## We need to duplicate this job's definition because it seems it's impossible to
## override an included `only.refs`.
## See https://gitlab.com/gitlab-org/gitlab/issues/31371.
#dast:
# extends:
# - .default-retry
# - .reports:rules:dast
# # This is needed so that manual jobs with needs don't block the pipeline.
# # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
# dependencies: ["review-deploy"]
# stage: qa # GitLab-specific
# image:
# name: "registry.gitlab.com/gitlab-org/security-products/dast:$DAST_VERSION"
# variables:
# # To be done in a later iteration
# # DAST_USERNAME: "root"
# # DAST_USERNAME_FIELD: "user[login]"
# # DAST_PASSWORD_FIELD: "user[passowrd]"
# DAST_VERSION: 1
# script:
# - 'export DAST_WEBSITE="${DAST_WEBSITE:-$(cat environment_url.txt)}"'
# # To be done in a later iteration
# # - 'export DAST_AUTH_URL="${DAST_WEBSITE}/users/sign_in"'
# # - 'export DAST_PASSWORD="${REVIEW_APPS_ROOT_PASSWORD}"'
# - /analyze -t $DAST_WEBSITE
# artifacts:
# paths:
# - gl-dast-report.json # GitLab-specific
# reports:
# 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
# schedule:dast:

View File

@ -1,13 +1,13 @@
build-qa-image:
extends:
- .use-kaniko
- .default-retry
- .review:rules:build-qa-image
stage: build-images
needs: []
script:
- export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
- /kaniko/executor --context=${CI_PROJECT_DIR} --dockerfile=${CI_PROJECT_DIR}/qa/Dockerfile --destination=${QA_IMAGE} --cache=true
retry: 2
review-cleanup:
extends:
@ -27,24 +27,25 @@ review-cleanup:
- ruby -rrubygems scripts/review_apps/automated_cleanup.rb
- gcp_cleanup
review-build-cng:
extends:
- .default-retry
- .review:rules:review-build-cng
image: ruby:2.6-alpine
stage: review-prepare
before_script:
- source scripts/utils.sh
- install_api_client_dependencies_with_apk
- install_gitlab_gem
needs:
- job: gitlab:assets:compile pull-cache
artifacts: false
script:
- BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
# When the job is manual, review-deploy is also manual and we don't want people
# to have to manually start the jobs in sequence, so we do it for them.
- '[ -z $CI_JOB_MANUAL ] || play_job "review-deploy"'
# Temporarily disabling review apps
#review-build-cng:
# extends:
# - .default-retry
# - .review:rules:review-build-cng
# image: ruby:2.6-alpine
# stage: review-prepare
# before_script:
# - source scripts/utils.sh
# - install_api_client_dependencies_with_apk
# - install_gitlab_gem
# needs:
# - job: compile-production-assets
# artifacts: false
# script:
# - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
# # When the job is manual, review-deploy is also manual and we don't want people
# # to have to manually start the jobs in sequence, so we do it for them.
# - '[ -z $CI_JOB_MANUAL ] || play_job "review-deploy"'
.review-workflow-base:
extends:
@ -53,43 +54,44 @@ review-build-cng:
variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
GITLAB_HELM_CHART_REF: "v3.3.3"
GITLAB_HELM_CHART_REF: "master"
environment:
name: review/${CI_COMMIT_REF_NAME}
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
on_stop: review-stop
auto_stop_in: 48 hours
review-deploy:
extends:
- .review-workflow-base
- .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
stage: review
dependencies: []
resource_group: "review/${CI_COMMIT_REF_NAME}"
before_script:
- export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
- export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
- export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
- echo "${CI_ENVIRONMENT_URL}" > environment_url.txt
- source ./scripts/utils.sh
- install_api_client_dependencies_with_apk
- source scripts/review_apps/review-apps.sh
script:
- check_kube_domain
- ensure_namespace
- install_external_dns
- download_chart
- date
- deploy || (display_deployment_debug && exit 1)
# When the job is manual, review-qa-smoke is also manual and we don't want people
# to have to manually start the jobs in sequence, so we do it for them.
- '[ -z $CI_JOB_MANUAL ] || play_job "review-qa-smoke"'
- '[ -z $CI_JOB_MANUAL ] || play_job "review-performance"'
artifacts:
paths: [environment_url.txt]
expire_in: 2 days
when: always
# Temporarily disabling review apps
#review-deploy:
# extends:
# - .review-workflow-base
# - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
# stage: review
# dependencies: []
# resource_group: "review/${CI_COMMIT_REF_NAME}"
# before_script:
# - export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
# - export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
# - export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
# - echo "${CI_ENVIRONMENT_URL}" > environment_url.txt
# - source ./scripts/utils.sh
# - install_api_client_dependencies_with_apk
# - source scripts/review_apps/review-apps.sh
# script:
# - check_kube_domain
# - ensure_namespace
# - install_external_dns
# - download_chart
# - date
# - deploy || (display_deployment_debug && exit 1)
# # When the job is manual, review-qa-smoke is also manual and we don't want people
# # to have to manually start the jobs in sequence, so we do it for them.
# - '[ -z $CI_JOB_MANUAL ] || play_job "review-qa-smoke"'
# - '[ -z $CI_JOB_MANUAL ] || play_job "review-performance"'
# artifacts:
# paths: [environment_url.txt]
# expire_in: 2 days
# when: always
.review-stop-base:
extends: .review-workflow-base
@ -122,109 +124,110 @@ review-stop:
script:
- delete_release
.review-qa-base:
extends:
- .default-retry
- .use-docker-in-docker
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6
stage: qa
# This is needed so that manual jobs with needs don't block the pipeline.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
dependencies: ["review-deploy"]
variables:
QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
QA_DEBUG: "true"
GITLAB_USERNAME: "root"
GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
GITLAB_ADMIN_USERNAME: "root"
GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
before_script:
- export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
- export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
- echo "${CI_ENVIRONMENT_URL}"
- echo "${QA_IMAGE}"
- source scripts/utils.sh
- install_api_client_dependencies_with_apk
- gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}}
artifacts:
paths:
- ./qa/gitlab-qa-run-*
expire_in: 7 days
when: always
review-qa-smoke:
extends:
- .review-qa-base
- .review:rules:review-qa-smoke
script:
- gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
review-qa-all:
extends:
- .review-qa-base
- .review:rules:mr-only-manual
parallel: 5
script:
- export KNAPSACK_REPORT_PATH=knapsack/master_report.json
- export KNAPSACK_TEST_FILE_PATTERN=qa/specs/features/**/*_spec.rb
- gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}" -- --format RspecJunitFormatter --out tmp/rspec-${CI_JOB_ID}.xml --format html --out tmp/rspec.htm --color --format documentation
review-performance:
extends:
- .default-retry
- .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
image:
name: sitespeedio/sitespeed.io:6.3.1
entrypoint: [""]
stage: qa
# This is needed so that manual jobs with needs don't block the pipeline.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
dependencies: ["review-deploy"]
before_script:
- export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
- echo "${CI_ENVIRONMENT_URL}"
- mkdir -p gitlab-exporter
- wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
- mkdir -p sitespeed-results
script:
- /start.sh --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "${CI_ENVIRONMENT_URL}"
after_script:
- mv sitespeed-results/data/performance.json performance.json
artifacts:
paths:
- sitespeed-results/
reports:
performance: performance.json
expire_in: 31d
parallel-spec-reports:
extends:
- .review:rules:mr-only-manual
image: ruby:2.6-alpine
stage: post-qa
dependencies: ["review-qa-all"]
variables:
NEW_PARALLEL_SPECS_REPORT: qa/report-new.html
BASE_ARTIFACT_URL: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/qa/"
script:
- apk add --update build-base libxml2-dev libxslt-dev && rm -rf /var/cache/apk/*
- gem install nokogiri --no-document
- cd qa/gitlab-qa-run-*/gitlab-*
- ARTIFACT_DIRS=$(pwd |rev| awk -F / '{print $1,$2}' | rev | sed s_\ _/_)
- cd -
- '[[ -f $NEW_PARALLEL_SPECS_REPORT ]] || echo "{}" > ${NEW_PARALLEL_SPECS_REPORT}'
- scripts/merge-html-reports ${NEW_PARALLEL_SPECS_REPORT} ${BASE_ARTIFACT_URL}${ARTIFACT_DIRS} qa/gitlab-qa-run-*/**/rspec.htm
artifacts:
when: always
paths:
- qa/report-new.html
- qa/gitlab-qa-run-*
reports:
junit: qa/gitlab-qa-run-*/**/rspec-*.xml
expire_in: 31d
# Temporarily disabling review apps
#.review-qa-base:
# extends:
# - .default-retry
# - .use-docker-in-docker
# image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6
# stage: qa
# # This is needed so that manual jobs with needs don't block the pipeline.
# # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
# dependencies: ["review-deploy"]
# variables:
# QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
# QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
# QA_DEBUG: "true"
# GITLAB_USERNAME: "root"
# GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
# GITLAB_ADMIN_USERNAME: "root"
# GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
# GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
# EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
# before_script:
# - export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
# - export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
# - echo "${CI_ENVIRONMENT_URL}"
# - echo "${QA_IMAGE}"
# - source scripts/utils.sh
# - install_api_client_dependencies_with_apk
# - gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}}
# artifacts:
# paths:
# - ./qa/gitlab-qa-run-*
# expire_in: 7 days
# when: always
#
#review-qa-smoke:
# extends:
# - .review-qa-base
# - .review:rules:review-qa-smoke
# script:
# - gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
#
#review-qa-all:
# extends:
# - .review-qa-base
# - .review:rules:mr-only-manual
# parallel: 5
# script:
# - export KNAPSACK_REPORT_PATH=knapsack/master_report.json
# - export KNAPSACK_TEST_FILE_PATTERN=qa/specs/features/**/*_spec.rb
# - gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}" -- --format RspecJunitFormatter --out tmp/rspec-${CI_JOB_ID}.xml --format html --out tmp/rspec.htm --color --format documentation
#
#review-performance:
# extends:
# - .default-retry
# - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
# image:
# name: sitespeedio/sitespeed.io:6.3.1
# entrypoint: [""]
# stage: qa
# # This is needed so that manual jobs with needs don't block the pipeline.
# # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
# dependencies: ["review-deploy"]
# before_script:
# - export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
# - echo "${CI_ENVIRONMENT_URL}"
# - mkdir -p gitlab-exporter
# - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
# - mkdir -p sitespeed-results
# script:
# - /start.sh --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "${CI_ENVIRONMENT_URL}"
# after_script:
# - mv sitespeed-results/data/performance.json performance.json
# artifacts:
# paths:
# - sitespeed-results/
# reports:
# performance: performance.json
# expire_in: 31d
#
#parallel-spec-reports:
# extends:
# - .review:rules:mr-only-manual
# image: ruby:2.6-alpine
# stage: post-qa
# dependencies: ["review-qa-all"]
# variables:
# NEW_PARALLEL_SPECS_REPORT: qa/report-new.html
# BASE_ARTIFACT_URL: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/qa/"
# script:
# - apk add --update build-base libxml2-dev libxslt-dev && rm -rf /var/cache/apk/*
# - gem install nokogiri --no-document
# - cd qa/gitlab-qa-run-*/gitlab-*
# - ARTIFACT_DIRS=$(pwd |rev| awk -F / '{print $1,$2}' | rev | sed s_\ _/_)
# - cd -
# - '[[ -f $NEW_PARALLEL_SPECS_REPORT ]] || echo "{}" > ${NEW_PARALLEL_SPECS_REPORT}'
# - scripts/merge-html-reports ${NEW_PARALLEL_SPECS_REPORT} ${BASE_ARTIFACT_URL}${ARTIFACT_DIRS} qa/gitlab-qa-run-*/**/rspec.htm
# artifacts:
# when: always
# paths:
# - qa/report-new.html
# - qa/gitlab-qa-run-*
# reports:
# junit: qa/gitlab-qa-run-*/**/rspec-*.xml
# expire_in: 31d
danger-review:
extends:
@ -238,5 +241,3 @@ danger-review:
- source scripts/utils.sh
- retry yarn install --frozen-lockfile
- 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: '$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: '$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: '$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: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE =~ /^gitlab-org($|\/security$)/ && $CI_COMMIT_TAG'
@ -78,9 +84,11 @@
.frontend-patterns: &frontend-patterns
- "{package.json,yarn.lock}"
- "{babel.config,jest.config}.js"
- "babel.config.js"
- "jest.config.{base,integration,unit}.js"
- ".csscomb.json"
- "Dockerfile.assets"
- "config/**/*.js"
- "vendor/assets/**/*"
- "{,ee/}{app/assets,app/helpers,app/presenters,app/views,locale,public,symbol}/**/*"
@ -93,7 +101,8 @@
.code-patterns: &code-patterns
- "{package.json,yarn.lock}"
- "{babel.config,jest.config}.js"
- "babel.config.js"
- "jest.config.{base,integration,unit}.js"
- ".csscomb.json"
- "Dockerfile.assets"
- "vendor/assets/**/*"
@ -113,7 +122,8 @@
.code-backstage-patterns: &code-backstage-patterns
- "{package.json,yarn.lock}"
- "{babel.config,jest.config}.js"
- "babel.config.js"
- "jest.config.{base,integration,unit}.js"
- ".csscomb.json"
- "Dockerfile.assets"
- "vendor/assets/**/*"
@ -135,7 +145,8 @@
.code-qa-patterns: &code-qa-patterns
- "{package.json,yarn.lock}"
- "{babel.config,jest.config}.js"
- "babel.config.js"
- "jest.config.{base,integration,unit}.js"
- ".csscomb.json"
- "Dockerfile.assets"
- "vendor/assets/**/*"
@ -154,7 +165,8 @@
.code-backstage-qa-patterns: &code-backstage-qa-patterns
- "{package.json,yarn.lock}"
- "{babel.config,jest.config}.js"
- "babel.config.js"
- "jest.config.{base,integration,unit}.js"
- ".csscomb.json"
- "Dockerfile.assets"
- "vendor/assets/**/*"
@ -177,6 +189,14 @@
- ".dockerignore"
- "qa/**/*"
################
# Shared rules #
################
.shared:rules:update-cache:
rules:
- <<: *if-master-schedule-2-hourly
- <<: *if-merge-request-title-update-caches
####################
# Cache repo rules #
####################
@ -238,51 +258,21 @@
##################
# Frontend rules #
##################
# This job only runs on `master` since it pushes to the cache.
.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:
.frontend:rules:compile-production-assets:
rules:
- <<: *if-not-canonical-namespace
when: never
- <<: *if-default-refs
changes: *code-backstage-qa-patterns
when: on_success
.frontend:rules:compile-assets-pull-push-cache:
.frontend:rules:compile-test-assets:
rules:
- <<: *if-master-refs
changes: *code-backstage-qa-patterns
when: on_success
- changes: *code-backstage-qa-patterns
# This job only runs on `master` since it pushes to the cache.
.frontend:rules:compile-assets-pull-push-cache-as-if-foss:
.frontend:rules:compile-test-assets-as-if-foss:
rules:
- <<: *if-not-ee
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
changes: *code-backstage-qa-patterns
- <<: *if-merge-request-title-as-if-foss
@ -293,15 +283,11 @@
rules:
- <<: *if-default-refs
changes: *code-backstage-patterns
when: on_success
.frontend:rules:default-frontend-jobs-as-if-foss:
rules:
- <<: *if-not-ee
when: never
- <<: *if-master-push
changes: *code-backstage-patterns
- <<: *if-master-schedule-2-hourly
- <<: *if-security-merge-request
changes: *code-backstage-patterns
- <<: *if-merge-request-title-as-if-foss
@ -321,10 +307,8 @@
rules:
- <<: *if-master-refs
changes: *frontend-dependency-patterns
when: on_success
- <<: *if-merge-request
changes: *frontend-dependency-patterns
when: on_success
.frontend:rules:qa-frontend-node-latest:
rules:
@ -335,6 +319,12 @@
changes: *frontend-dependency-patterns
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 #
################
@ -368,9 +358,6 @@
rules:
- <<: *if-not-ee
when: never
- <<: *if-master-push
changes: *code-qa-patterns
- <<: *if-master-schedule-2-hourly
- <<: *if-security-merge-request
changes: *code-qa-patterns
- <<: *if-merge-request-title-as-if-foss
@ -379,13 +366,13 @@
.qa:rules:package-and-qa:
rules:
- <<: *if-dot-com-gitlab-org-merge-request
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *ci-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *qa-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *code-patterns
when: manual
allow_failure: true
@ -416,9 +403,6 @@
rules:
- <<: *if-not-ee
when: never
- <<: *if-master-push
changes: *code-backstage-patterns
- <<: *if-master-schedule-2-hourly
- <<: *if-security-merge-request
changes: *code-backstage-patterns
- <<: *if-merge-request-title-as-if-foss
@ -434,6 +418,17 @@
- <<: *if-master-refs
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:
rules:
- <<: *if-merge-request
@ -505,7 +500,7 @@
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *code-qa-patterns
- <<: *if-dot-com-gitlab-org-schedule

View File

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

View File

@ -3,11 +3,6 @@
TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache"
before_script:
- source scripts/utils.sh
cache:
key: tests_metadata
paths:
- knapsack/
- rspec_flaky/
artifacts:
expire_in: 31d
paths:
@ -20,8 +15,6 @@ retrieve-tests-metadata:
- .tests-metadata-state
- .test-metadata:rules:retrieve-tests-metadata
stage: prepare
cache:
policy: pull
script:
- source scripts/rspec_helpers.sh
- retrieve_tests_metadata
@ -44,8 +37,6 @@ update-tests-metadata:
- rspec-ee unit pg11 geo
- rspec-ee integration pg11 geo
- rspec-ee system pg11 geo
cache:
policy: push
script:
- retry gem install fog-aws mime-types activesupport rspec_profiling postgres-copy --no-document
- 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.
- [ ] 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:
- [ ] Update the [permissions table](https://docs.gitlab.com/ee/user/permissions.html).

View File

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

View File

@ -1,6 +1,6 @@
# This configuration was generated by
# `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
# one by one as the lints are removed from the code base.
# 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/_import_project_pane.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/artifacts/_artifact.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/_trigger.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/_note.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/_group_form.html.haml"
- "app/views/shared/_group_tips.html.haml"
- "app/views/shared/_md_preview.html.haml"
- "app/views/shared/_milestone_expired.html.haml"
- "app/views/shared/_no_password.html.haml"
- "app/views/shared/_ping_consent.html.haml"
@ -313,6 +312,7 @@ linters:
- "app/views/shared/snippets/_snippet.html.haml"
- "app/views/shared/web_hooks/_form.html.haml"
- "app/views/shared/web_hooks/_hook.html.haml"
- "app/views/shared/wikis/_pages_wiki_page.html.haml"
- "app/views/u2f/_authenticate.html.haml"
- "app/views/u2f/_register.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/licenses/_upload_trial_license.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/projects/_shared_runner_status.html.haml"
- "ee/app/views/admin/users/_auditor_access_level_radio.html.haml"

View File

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

View File

@ -2,18 +2,21 @@ inherit_gem:
gitlab-styles:
- rubocop-default.yml
inherit_from: .rubocop_todo.yml
require:
- ./rubocop/rubocop
- rubocop-rspec
inherit_from:
- .rubocop_todo.yml
- ./rubocop/rubocop-migrations.yml
inherit_mode:
merge:
- Include
AllCops:
TargetRubyVersion: 2.6
TargetRailsVersion: 5.0
TargetRailsVersion: 6.0
Exclude:
- 'vendor/**/*'
- 'node_modules/**/*'
@ -27,6 +30,7 @@ AllCops:
- 'plugins/**/*'
- 'file_hooks/**/*'
CacheRootDirectory: tmp
MaxFilesInCache: 18000
Cop/AvoidKeywordArgumentsInSidekiqWorkers:
Enabled: true
@ -178,6 +182,9 @@ Rails/ApplicationRecord:
- ee/db/**/*.rb
- ee/spec/**/*.rb
Cop/DefaultScope:
Enabled: true
Rails/FindBy:
Enabled: true
Include:
@ -186,6 +193,14 @@ Rails/FindBy:
- '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/ModuleWithInstanceVariables:
@ -225,6 +240,7 @@ Gitlab/Json:
- 'scripts/**/*'
- 'lib/rspec_flaky/**/*'
- 'lib/quality/**/*'
- 'lib/gitlab/danger/**/*'
GitlabSecurity/PublicSend:
Enabled: true
@ -240,10 +256,7 @@ GitlabSecurity/PublicSend:
- 'ee/spec/**/*'
Gitlab/DuplicateSpecLocation:
Exclude:
- 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
Enabled: true
Cop/InjectEnterpriseEditionModule:
Enabled: true
@ -259,12 +272,15 @@ Style/ReturnNil:
Performance/RegexpMatch:
Enabled: false
ActiveRecordAssociationReload:
Cop/ActiveRecordAssociationReload:
Enabled: true
Exclude:
- 'spec/**/*'
- 'ee/spec/**/*'
Gitlab/AvoidFeatureGet:
Enabled: true
Naming/PredicateName:
Enabled: true
Exclude:
@ -349,39 +365,17 @@ RSpec/LeakyConstantDeclaration:
Exclude:
- 'spec/db/schema_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/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/sidekiq_middleware/client_metrics_spec.rb'
- 'spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb'
- 'spec/lib/marginalia_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/bulk_insert_safe_spec.rb'
- 'spec/models/concerns/bulk_insertable_associations_spec.rb'
- 'spec/models/concerns/triggerable_hooks_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_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_specs/matchers/exceed_query_limit_helpers_spec.rb'
RSpec/EmptyLineAfterHook:
Enabled: false
@ -452,4 +446,16 @@ Rails/TimeZone:
- 'ee/app/services/**/*'
- 'ee/spec/controllers/**/*'
- '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.
## 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)
@ -12,10 +15,6 @@ Please view this file on the master branch, on stable branches it's out of date.
- No changes.
## 13.0.2 (2020-05-28)
- No changes.
## 13.0.1 (2020-05-27)
### 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)
## 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)
### 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
## 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)
- No changes.

View File

@ -2,24 +2,498 @@
documentation](doc/development/changelog.md) for instructions on adding your own
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)
- 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)
### Security (1 change)
@ -41,10 +515,6 @@ entry.
- Fix close issue when user created the issue. !33294
## 13.0.2 (2020-05-28)
- No changes.
## 13.0.1 (2020-05-27)
### Security (12 changes)
@ -632,6 +1102,49 @@ entry.
- 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)
### Added (1 change)
@ -1121,6 +1634,29 @@ entry.
- 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)
### 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/changelog.rb')
unless helper.release_automation?
GitlabDanger.new(helper.gitlab_helper).rule_names.each do |file|
danger.import_dangerfile(path: File.join('danger', file))
end
return if helper.release_automation?
gitlab_danger = GitlabDanger.new(helper.gitlab_helper)
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

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'
gem 'rails', '~> 6.0.3'
gem 'rails', '~> 6.0.3.1'
gem 'bootsnap', '~> 1.4.6'
@ -43,8 +43,7 @@ gem 'omniauth-shibboleth', '~> 1.3.0'
gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.3.3'
gem 'omniauth_openid_connect', '~> 0.3.3'
gem "omniauth-ultraauth", '~> 0.0.2'
gem 'omniauth_openid_connect', '~> 0.3.5'
gem 'omniauth-salesforce', '~> 1.0.5'
gem 'rack-oauth2', '~> 1.9.3'
gem 'jwt', '~> 2.1.0'
@ -64,7 +63,7 @@ gem 'attr_encrypted', '~> 3.1.0'
gem 'u2f', '~> 0.2.1'
# GitLab Pages
gem 'validates_hostname', '~> 1.0.6'
gem 'validates_hostname', '~> 1.0.10'
gem 'rubyzip', '~> 2.0.0', require: 'zip'
# GitLab Pages letsencrypt support
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.
# Also see config/initializers/fog_core_patch.rb.
gem 'fog-core', '= 2.1.0'
gem 'fog-google', '~> 1.9'
gem 'fog-google', '~> 1.10'
gem 'fog-local', '~> 0.6'
gem 'fog-openstack', '~> 1.0'
gem 'fog-rackspace', '~> 0.1.1'
gem 'fog-aliyun', '~> 0.3'
# for Google storage
gem 'google-api-client', '~> 0.23'
gem 'google-api-client', '~> 0.33'
# for aws storage
gem 'unf', '~> 0.1.4'
@ -343,7 +342,7 @@ group :development do
end
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-rails', '~> 0.3.9'
@ -362,10 +361,10 @@ group :development, :test do
gem 'spring', '~> 2.0.0'
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
gem 'rubocop', '~> 0.74.0'
gem 'rubocop-performance', '~> 1.4.1'
gem 'rubocop', '~> 0.82.0'
gem 'rubocop-performance', '~> 1.5.2'
gem 'rubocop-rspec', '~> 1.37.0'
gem 'scss_lint', '~> 0.56.0', require: false
@ -403,7 +402,6 @@ group :test do
gem 'shoulda-matchers', '~> 4.0.1', require: false
gem 'email_spec', '~> 2.2.0'
gem 'json-schema', '~> 2.8.0'
gem 'webmock', '~> 3.5.1'
gem 'rails-controller-testing'
gem 'concurrent-ruby', '~> 1.1'
@ -454,7 +452,7 @@ group :ed25519 do
end
# Gitaly GRPC protocol definitions
gem 'gitaly', '~> 13.0.0.pre.rc1'
gem 'gitaly', '~> 13.1.0.pre.rc1'
gem 'grpc', '~> 1.24.0'
@ -498,3 +496,4 @@ gem 'valid_email', '~> 0.1'
# JSON
gem 'json', '~> 2.3.0'
gem 'json-schema', '~> 2.8.0'

View File

@ -6,59 +6,59 @@ GEM
ace-rails-ap (4.1.2)
acme-client (2.0.5)
faraday (~> 0.9, >= 0.9.1)
actioncable (6.0.3)
actionpack (= 6.0.3)
actioncable (6.0.3.1)
actionpack (= 6.0.3.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.0.3)
actionpack (= 6.0.3)
activejob (= 6.0.3)
activerecord (= 6.0.3)
activestorage (= 6.0.3)
activesupport (= 6.0.3)
actionmailbox (6.0.3.1)
actionpack (= 6.0.3.1)
activejob (= 6.0.3.1)
activerecord (= 6.0.3.1)
activestorage (= 6.0.3.1)
activesupport (= 6.0.3.1)
mail (>= 2.7.1)
actionmailer (6.0.3)
actionpack (= 6.0.3)
actionview (= 6.0.3)
activejob (= 6.0.3)
actionmailer (6.0.3.1)
actionpack (= 6.0.3.1)
actionview (= 6.0.3.1)
activejob (= 6.0.3.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.0.3)
actionview (= 6.0.3)
activesupport (= 6.0.3)
actionpack (6.0.3.1)
actionview (= 6.0.3.1)
activesupport (= 6.0.3.1)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.0.3)
actionpack (= 6.0.3)
activerecord (= 6.0.3)
activestorage (= 6.0.3)
activesupport (= 6.0.3)
actiontext (6.0.3.1)
actionpack (= 6.0.3.1)
activerecord (= 6.0.3.1)
activestorage (= 6.0.3.1)
activesupport (= 6.0.3.1)
nokogiri (>= 1.8.5)
actionview (6.0.3)
activesupport (= 6.0.3)
actionview (6.0.3.1)
activesupport (= 6.0.3.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.0.3)
activesupport (= 6.0.3)
activejob (6.0.3.1)
activesupport (= 6.0.3.1)
globalid (>= 0.3.6)
activemodel (6.0.3)
activesupport (= 6.0.3)
activerecord (6.0.3)
activemodel (= 6.0.3)
activesupport (= 6.0.3)
activemodel (6.0.3.1)
activesupport (= 6.0.3.1)
activerecord (6.0.3.1)
activemodel (= 6.0.3.1)
activesupport (= 6.0.3.1)
activerecord-explain-analyze (0.1.0)
activerecord (>= 4)
pg
activestorage (6.0.3)
actionpack (= 6.0.3)
activejob (= 6.0.3)
activerecord (= 6.0.3)
activestorage (6.0.3.1)
actionpack (= 6.0.3.1)
activejob (= 6.0.3.1)
activerecord (= 6.0.3.1)
marcel (~> 0.3.1)
activesupport (6.0.3)
activesupport (6.0.3.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@ -286,7 +286,7 @@ GEM
factory_bot_rails (5.1.0)
factory_bot (~> 5.1.0)
railties (>= 4.2.0)
faraday (0.15.4)
faraday (0.17.3)
multipart-post (>= 1.2, < 3)
faraday-http-cache (2.0.0)
faraday (~> 0.8)
@ -330,11 +330,11 @@ GEM
excon (~> 0.58)
formatador (~> 0.2)
mime-types
fog-google (1.9.1)
fog-google (1.10.0)
fog-core (<= 2.1.0)
fog-json (~> 1.2)
fog-xml (~> 0.1.0)
google-api-client (~> 0.23.0)
google-api-client (>= 0.32, < 0.34)
fog-json (1.2.0)
fog-core
multi_json (~> 1.10)
@ -377,7 +377,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
git (1.5.0)
gitaly (13.0.0.pre.rc1)
gitaly (13.1.0.pre.rc1)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-chronic (0.10.5)
@ -400,11 +400,11 @@ GEM
gitlab-puma (>= 2.7, < 5)
gitlab-sidekiq-fetcher (0.5.2)
sidekiq (~> 5)
gitlab-styles (3.2.0)
rubocop (~> 0.74.0)
gitlab-styles (4.2.0)
rubocop (~> 0.82.0)
rubocop-gitlab-security (~> 0.1.0)
rubocop-performance (~> 1.4.1)
rubocop-rails (~> 2.0)
rubocop-performance (~> 1.5.2)
rubocop-rails (~> 2.5)
rubocop-rspec (~> 1.36)
gitlab_chronic_duration (0.10.6.2)
numerizer (~> 0.2)
@ -419,23 +419,24 @@ GEM
actionpack (>= 3.0)
multi_json
request_store (>= 1.0)
google-api-client (0.23.4)
google-api-client (0.33.2)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.5, < 0.7.0)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
google-protobuf (3.8.0)
googleapis-common-protos-types (1.0.4)
google-protobuf (~> 3.0)
googleauth (0.6.6)
faraday (~> 0.12)
googleauth (0.12.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.12)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.7)
signet (~> 0.14)
gpgme (2.0.20)
mini_portile2 (~> 2.3)
grape (1.1.0)
@ -529,7 +530,7 @@ GEM
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
i18n (1.8.2)
i18n (1.8.3)
concurrent-ruby (~> 1.0)
i18n_data (0.8.0)
icalendar (2.4.1)
@ -604,7 +605,7 @@ GEM
ruby_dep (~> 1.2)
locale (2.1.2)
lockbox (0.3.3)
lograge (0.10.0)
lograge (0.11.2)
actionpack (>= 4)
activesupport (>= 4)
railties (>= 4)
@ -661,8 +662,8 @@ GEM
shellany (~> 0.0)
numerizer (0.2.0)
oauth (0.5.4)
oauth2 (1.4.1)
faraday (>= 0.8, < 0.16.0)
oauth2 (1.4.4)
faraday (>= 0.8, < 2.0)
jwt (>= 1.0, < 3.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
@ -722,13 +723,11 @@ GEM
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
omniauth-ultraauth (0.0.2)
omniauth_openid_connect (~> 0.3.0)
omniauth_crowd (2.2.3)
activesupport
nokogiri (>= 1.4.4)
omniauth (~> 1.0)
omniauth_openid_connect (0.3.3)
omniauth_openid_connect (0.3.5)
addressable (~> 2.5)
omniauth (~> 1.9)
openid_connect (~> 1.1)
@ -750,7 +749,7 @@ GEM
orm_adapter (0.5.0)
os (1.0.0)
parallel (1.19.1)
parser (2.7.0.4)
parser (2.7.1.2)
ast (~> 2.4.0)
parslet (1.8.2)
peek (1.1.0)
@ -803,20 +802,20 @@ GEM
rack-test (1.1.0)
rack (>= 1.0, < 3)
rack-timeout (0.5.1)
rails (6.0.3)
actioncable (= 6.0.3)
actionmailbox (= 6.0.3)
actionmailer (= 6.0.3)
actionpack (= 6.0.3)
actiontext (= 6.0.3)
actionview (= 6.0.3)
activejob (= 6.0.3)
activemodel (= 6.0.3)
activerecord (= 6.0.3)
activestorage (= 6.0.3)
activesupport (= 6.0.3)
rails (6.0.3.1)
actioncable (= 6.0.3.1)
actionmailbox (= 6.0.3.1)
actionmailer (= 6.0.3.1)
actionpack (= 6.0.3.1)
actiontext (= 6.0.3.1)
actionview (= 6.0.3.1)
activejob (= 6.0.3.1)
activemodel (= 6.0.3.1)
activerecord (= 6.0.3.1)
activestorage (= 6.0.3.1)
activesupport (= 6.0.3.1)
bundler (>= 1.3.0)
railties (= 6.0.3)
railties (= 6.0.3.1)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.4)
actionpack (>= 5.0.1.x)
@ -830,9 +829,9 @@ GEM
rails-i18n (6.0.0)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 7)
railties (6.0.3)
actionpack (= 6.0.3)
activesupport (= 6.0.3)
railties (6.0.3.1)
actionpack (= 6.0.3.1)
activesupport (= 6.0.3.1)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
@ -888,6 +887,7 @@ GEM
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
retriable (3.1.2)
rexml (3.2.4)
rinku (2.0.0)
rotp (2.1.2)
rouge (3.19.0)
@ -931,18 +931,20 @@ GEM
pg
rails
sqlite3
rubocop (0.74.0)
rubocop (0.82.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.6)
parser (>= 2.7.0.1)
rainbow (>= 2.2.2, < 4.0)
rexml
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 (>= 0.51)
rubocop-performance (1.4.1)
rubocop-performance (1.5.2)
rubocop (>= 0.71.0)
rubocop-rails (2.4.0)
rubocop-rails (2.5.2)
activesupport
rack (>= 1.1)
rubocop (>= 0.72.0)
rubocop-rspec (1.37.0)
@ -1009,9 +1011,9 @@ GEM
sidekiq-cron (1.0.4)
fugit (~> 1.1)
sidekiq (>= 4.2.1)
signet (0.11.0)
signet (0.14.0)
addressable (~> 2.3)
faraday (~> 0.9)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simple_po_parser (1.1.2)
@ -1083,7 +1085,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.5)
unicode-display_width (1.6.0)
unicode-display_width (1.7.0)
unicode_plot (0.0.4)
enumerable-statistics (>= 2.0.1)
unicode_utils (1.4.0)
@ -1113,7 +1115,7 @@ GEM
validate_url (1.0.8)
activemodel (>= 3.0.0)
public_suffix
validates_hostname (1.0.6)
validates_hostname (1.0.10)
activerecord (>= 3.0)
activesupport (>= 3.0)
version_sorter (2.2.4)
@ -1223,7 +1225,7 @@ DEPENDENCIES
fog-aliyun (~> 0.3)
fog-aws (~> 3.5)
fog-core (= 2.1.0)
fog-google (~> 1.9)
fog-google (~> 1.10)
fog-local (~> 0.6)
fog-openstack (~> 1.0)
fog-rackspace (~> 0.1.1)
@ -1234,7 +1236,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly (~> 13.0.0.pre.rc1)
gitaly (~> 13.1.0.pre.rc1)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-labkit (= 0.12.0)
@ -1245,11 +1247,11 @@ DEPENDENCIES
gitlab-puma (~> 4.3.3.gitlab.2)
gitlab-puma_worker_killer (~> 0.1.1.gitlab.1)
gitlab-sidekiq-fetcher (= 0.5.2)
gitlab-styles (~> 3.2.0)
gitlab-styles (~> 4.2.0)
gitlab_chronic_duration (~> 0.10.6.2)
gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2)
google-api-client (~> 0.23)
google-api-client (~> 0.33)
google-protobuf (~> 3.8.0)
gpgme (~> 2.0.19)
grape (~> 1.1.0)
@ -1317,9 +1319,8 @@ DEPENDENCIES
omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.3.0)
omniauth-twitter (~> 1.4)
omniauth-ultraauth (~> 0.0.2)
omniauth_crowd (~> 2.2.0)
omniauth_openid_connect (~> 0.3.3)
omniauth_openid_connect (~> 0.3.5)
org-ruby (~> 0.9.12)
parallel (~> 1.19)
peek (~> 1.1)
@ -1335,7 +1336,7 @@ DEPENDENCIES
rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0)
rack-timeout
rails (~> 6.0.3)
rails (~> 6.0.3.1)
rails-controller-testing
rails-i18n (~> 6.0)
rainbow (~> 3.0)
@ -1358,8 +1359,8 @@ DEPENDENCIES
rspec-retry (~> 0.6.1)
rspec_junit_formatter
rspec_profiling (~> 0.0.5)
rubocop (~> 0.74.0)
rubocop-performance (~> 1.4.1)
rubocop (~> 0.82.0)
rubocop-performance (~> 1.5.2)
rubocop-rspec (~> 1.37.0)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 1.3.0)
@ -1400,7 +1401,7 @@ DEPENDENCIES
unicorn-worker-killer (~> 0.4.4)
unleash (~> 0.1.5)
valid_email (~> 0.1)
validates_hostname (~> 1.0.6)
validates_hostname (~> 1.0.10)
version_sorter (~> 2.2.4)
vmstat (~> 2.3.0)
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 {
GlAlert,
GlBadge,
GlIcon,
GlLoadingIcon,
GlDropdown,
GlDropdownItem,
GlSprintf,
GlTabs,
GlTab,
GlButton,
GlTable,
} from '@gitlab/ui';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import query from '../graphql/queries/details.query.graphql';
import { fetchPolicies } from '~/lib/graphql';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { ALERTS_SEVERITY_LABELS } from '../constants';
import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql';
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
import initUserPopovers from '~/user_popovers';
import { ALERTS_SEVERITY_LABELS, trackAlertsDetailsViewsOptions } from '../constants';
import createIssueQuery from '../graphql/mutations/create_issue_from_alert.graphql';
import { 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 {
statuses: {
TRIGGERED: s__('AlertManagement|Triggered'),
ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
RESOLVED: s__('AlertManagement|Resolved'),
},
i18n: {
errorMsg: s__(
'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,
components: {
GlBadge,
GlAlert,
GlIcon,
GlLoadingIcon,
GlSprintf,
GlDropdown,
GlDropdownItem,
GlTab,
GlTabs,
GlButton,
GlTable,
TimeAgoTooltip,
AlertSidebar,
SystemNote,
},
mixins: [glFeatureFlagsMixin()],
props: {
alertId: {
type: String,
@ -60,7 +61,7 @@ export default {
type: String,
required: true,
},
newIssuePath: {
projectIssuesPath: {
type: String,
required: true,
},
@ -85,7 +86,15 @@ export default {
},
},
data() {
return { alert: null, errored: false, isErrorDismissed: false };
return {
alert: null,
errored: false,
isErrorDismissed: false,
createIssueError: '',
issueCreationInProgress: false,
sidebarCollapsed: false,
sidebarErrorMessage: '',
};
},
computed: {
loading() {
@ -100,38 +109,92 @@ export default {
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: {
dismissError() {
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
.mutate({
mutation: updateAlertStatus,
mutation: createIssueQuery,
variables: {
iid: this.alertId,
status: status.toUpperCase(),
iid: this.alert.iid,
projectPath: this.projectPath,
},
})
.catch(() => {
createFlash(
s__(
'AlertManagement|There was an error while updating the status of the alert. Please try again.',
),
);
.then(({ data: { createAlertIssue: { errors, issue } } }) => {
if (errors?.length) {
[this.createIssueError] = errors;
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>
<template>
<div>
<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>
<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
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
class="gl-display-inline-flex gl-align-items-center gl-justify-content-space-between"
>
<gl-icon
class="gl-mr-3 align-middle"
:size="12"
:name="`severity-${alert.severity.toLowerCase()}`"
:class="`icon-${alert.severity.toLowerCase()}`"
/>
<strong>{{ $options.severityLabels[alert.severity] }}</strong>
<gl-badge class="gl-mr-3">
<strong>{{ s__('AlertManagement|Alert') }}</strong>
</gl-badge>
</div>
<span class="mx-2">&bull;</span>
<gl-sprintf :message="reportedAtMessage">
<template #when>
<time-ago-tooltip :time="alert.createdAt" class="gl-ml-3" />
</template>
<template #tool>{{ alert.monitoringTool }}</template>
</gl-sprintf>
<span>
<gl-sprintf :message="reportedAtMessage">
<template #when>
<time-ago-tooltip :time="alert.createdAt" />
</template>
<template #tool>{{ alert.monitoringTool }}</template>
</gl-sprintf>
</span>
</div>
<gl-button
v-if="glFeatures.createIssueFromAlertEnabled"
class="gl-mt-3 mt-sm-0 align-self-center align-self-sm-baseline alert-details-create-issue-button"
data-testid="createIssueBtn"
:href="newIssuePath"
v-if="alert.issueIid"
class="gl-mt-3 mt-sm-0 align-self-center align-self-sm-baseline alert-details-issue-button"
data-testid="viewIssueBtn"
:href="issuePath(alert.issueIid)"
category="primary"
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') }}
</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
v-if="alert"
@ -175,44 +256,57 @@ export default {
>
<h2 data-testid="title">{{ alert.title }}</h2>
</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-tab data-testid="overviewTab" :title="$options.i18n.overviewTitle">
<ul class="pl-4 mb-n1">
<li v-if="alert.startedAt" class="my-2">
<strong class="bold">{{ s__('AlertManagement|Start time') }}:</strong>
<div v-if="alert.severity" class="gl-mt-3 gl-mb-5 gl-display-flex">
<div class="gl-font-weight-bold gl-w-13 gl-text-right gl-pr-3">
{{ 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" />
</li>
<li v-if="alert.eventCount" class="my-2">
<strong class="bold">{{ s__('AlertManagement|Events') }}:</strong>
<span data-testid="eventCount">{{ alert.eventCount }}</span>
</li>
<li v-if="alert.monitoringTool" class="my-2">
<strong class="bold">{{ s__('AlertManagement|Tool') }}:</strong>
<span data-testid="monitoringTool">{{ alert.monitoringTool }}</span>
</li>
<li v-if="alert.service" class="my-2">
<strong class="bold">{{ s__('AlertManagement|Service') }}:</strong>
<span data-testid="service">{{ alert.service }}</span>
</li>
</ul>
</div>
</div>
<div v-if="alert.eventCount" class="gl-my-5 gl-display-flex">
<div class="gl-font-weight-bold gl-w-13 gl-text-right gl-pr-3">
{{ s__('AlertManagement|Events') }}:
</div>
<div class="gl-pl-2" data-testid="eventCount">{{ alert.eventCount }}</div>
</div>
<div v-if="alert.monitoringTool" class="gl-my-5 gl-display-flex">
<div class="gl-font-weight-bold gl-w-13 gl-text-right gl-pr-3">
{{ s__('AlertManagement|Tool') }}:
</div>
<div class="gl-pl-2" data-testid="monitoringTool">{{ alert.monitoringTool }}</div>
</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 data-testid="fullDetailsTab" :title="$options.i18n.fullAlertDetailsTitle">
<gl-table
@ -231,6 +325,14 @@ export default {
</gl-table>
</gl-tab>
</gl-tabs>
<alert-sidebar
:project-path="projectPath"
:alert="alert"
:sidebar-collapsed="sidebarCollapsed"
@alert-refresh="alertRefresh"
@toggle-sidebar="toggleSidebar"
@alert-sidebar-error="handleAlertSidebarError"
/>
</div>
</div>
</template>

View File

@ -10,23 +10,41 @@ import {
GlDropdownItem,
GlTabs,
GlTab,
GlBadge,
GlPagination,
} from '@gitlab/ui';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
import { fetchPolicies } from '~/lib/graphql';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import getAlerts from '../graphql/queries/getAlerts.query.graphql';
import { ALERTS_STATUS, ALERTS_STATUS_TABS, ALERTS_SEVERITY_LABELS } from '../constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import getAlerts from '../graphql/queries/get_alerts.query.graphql';
import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql';
import {
ALERTS_STATUS_TABS,
ALERTS_SEVERITY_LABELS,
DEFAULT_PAGE_SIZE,
trackAlertListViewsOptions,
trackAlertStatusUpdateOptions,
} from '../constants';
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 =
'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 {
bodyTrClass,
i18n: {
noAlertsMsg: s__(
"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',
label: s__('AlertManagement|Severity'),
tdClass: `${tdClass} rounded-top text-capitalize`,
thClass,
sortable: true,
},
{
key: 'startedAt',
label: s__('AlertManagement|Start time'),
thClass: `${thClass} js-started-at`,
tdClass,
sortable: true,
},
{
key: 'endedAt',
label: s__('AlertManagement|End time'),
thClass,
tdClass,
sortable: true,
},
{
key: 'title',
label: s__('AlertManagement|Alert'),
thClass: 'w-30p',
thClass: `${thClass} w-30p gl-pointer-events-none`,
tdClass,
sortable: false,
},
{
key: 'eventCount',
label: s__('AlertManagement|Events'),
thClass: 'text-right event-count',
tdClass: `${tdClass} text-md-right event-count`,
thClass: `${thClass} text-right gl-pr-9 w-3rem`,
tdClass: `${tdClass} text-md-right`,
sortable: true,
},
{
key: 'assignees',
label: s__('AlertManagement|Assignees'),
tdClass,
},
{
key: 'status',
thClass: 'w-15p',
thClass: `${thClass} w-15p`,
label: s__('AlertManagement|Status'),
tdClass: `${tdClass} rounded-bottom`,
sortable: true,
},
],
statuses: {
[ALERTS_STATUS.TRIGGERED]: s__('AlertManagement|Triggered'),
[ALERTS_STATUS.ACKNOWLEDGED]: s__('AlertManagement|Acknowledged'),
[ALERTS_STATUS.RESOLVED]: s__('AlertManagement|Resolved'),
TRIGGERED: s__('AlertManagement|Triggered'),
ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
RESOLVED: s__('AlertManagement|Resolved'),
},
severityLabels: ALERTS_SEVERITY_LABELS,
statusTabs: ALERTS_STATUS_TABS,
@ -89,8 +121,9 @@ export default {
GlIcon,
GlTabs,
GlTab,
GlBadge,
GlPagination,
},
mixins: [glFeatureFlagsMixin()],
props: {
projectPath: {
type: String,
@ -115,33 +148,63 @@ export default {
},
apollo: {
alerts: {
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
query: getAlerts,
variables() {
return {
projectPath: this.projectPath,
statuses: this.statusFilter,
sort: this.sort,
firstPageSize: this.pagination.firstPageSize,
lastPageSize: this.pagination.lastPageSize,
prevPageCursor: this.pagination.prevPageCursor,
nextPageCursor: this.pagination.nextPageCursor,
};
},
update(data) {
return data.project.alertManagementAlerts.nodes;
const { alertManagementAlerts: { nodes: list = [], pageInfo = {} } = {} } =
data.project || {};
return {
list,
pageInfo,
};
},
error() {
this.errored = true;
},
},
alertsCount: {
query: getAlertsCountByStatus,
variables() {
return {
projectPath: this.projectPath,
};
},
update(data) {
return data.project?.alertManagementAlertStatusCounts;
},
},
},
data() {
return {
alerts: null,
errored: false,
isAlertDismissed: false,
isErrorAlertDismissed: false,
statusFilter: this.$options.statusTabs[4].filters,
sort: 'STARTED_AT_DESC',
statusFilter: [],
filteredByStatus: '',
pagination: initialPaginationState,
sortBy: 'startedAt',
sortDesc: true,
sortDirection: 'desc',
};
},
computed: {
showNoAlertsMsg() {
return !this.errored && !this.loading && !this.alerts?.length && !this.isAlertDismissed;
return (
!this.errored && !this.loading && this.alertsCount?.all === 0 && !this.isAlertDismissed
);
},
showErrorMsg() {
return this.errored && !this.isErrorAlertDismissed;
@ -149,12 +212,43 @@ export default {
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: {
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) {
this.$apollo
.mutate({
@ -166,7 +260,10 @@ export default {
},
})
.then(() => {
this.trackStatusUpdate(status);
this.$apollo.queries.alerts.refetch();
this.$apollo.queries.alertsCount.refetch();
this.resetPagination();
})
.catch(() => {
createFlash(
@ -179,6 +276,42 @@ export default {
navigateToAlertDetails({ iid }) {
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>
@ -192,10 +325,13 @@ export default {
{{ $options.i18n.errorMsg }}
</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">
<template slot="title">
<span>{{ tab.title }}</span>
<gl-badge v-if="alertsCount" pill size="sm" class="gl-tab-counter-badge">
{{ alertsCount[tab.status.toLowerCase()] }}
</gl-badge>
</template>
</gl-tab>
</gl-tabs>
@ -205,13 +341,19 @@ export default {
</h4>
<gl-table
class="alert-management-table mt-3"
:items="alerts"
:items="alerts ? alerts.list : []"
:fields="$options.fields"
:show-empty="true"
:busy="loading"
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"
@sort-changed="fetchSortedData"
>
<template #cell(severity)="{ item }">
<div
@ -236,16 +378,22 @@ export default {
<time-ago v-if="item.endedAt" :time="item.endedAt" />
</template>
<template #cell(eventCount)="{ item }">
{{ item.eventCount }}
</template>
<template #cell(title)="{ item }">
<div class="gl-max-w-full text-truncate">{{ item.title }}</div>
</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 }">
<gl-dropdown
:text="capitalizeFirstCharacter(item.status.toLowerCase())"
class="w-100"
right
>
<gl-dropdown :text="$options.statuses[item.status]" class="w-100" right>
<gl-dropdown-item
v-for="(label, field) in $options.statuses"
:key="field"
@ -271,6 +419,16 @@ export default {
<gl-loading-icon size="lg" color="dark" class="mt-3" />
</template>
</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>
<gl-empty-state
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'),
};
export const ALERTS_STATUS = {
OPEN: 'OPEN',
TRIGGERED: 'TRIGGERED',
ACKNOWLEDGED: 'ACKNOWLEDGED',
RESOLVED: 'RESOLVED',
ALL: 'ALL',
};
export const ALERTS_STATUS_TABS = [
{
title: s__('AlertManagement|Open'),
status: ALERTS_STATUS.OPEN,
filters: [ALERTS_STATUS.TRIGGERED, ALERTS_STATUS.ACKNOWLEDGED],
status: 'OPEN',
filters: ['TRIGGERED', 'ACKNOWLEDGED'],
},
{
title: s__('AlertManagement|Triggered'),
status: ALERTS_STATUS.TRIGGERED,
filters: [ALERTS_STATUS.TRIGGERED],
status: 'TRIGGERED',
filters: 'TRIGGERED',
},
{
title: s__('AlertManagement|Acknowledged'),
status: ALERTS_STATUS.ACKNOWLEDGED,
filters: [ALERTS_STATUS.ACKNOWLEDGED],
status: 'ACKNOWLEDGED',
filters: 'ACKNOWLEDGED',
},
{
title: s__('AlertManagement|Resolved'),
status: ALERTS_STATUS.RESOLVED,
filters: [ALERTS_STATUS.RESOLVED],
status: 'RESOLVED',
filters: 'RESOLVED',
},
{
title: s__('AlertManagement|All alerts'),
status: ALERTS_STATUS.ALL,
filters: [ALERTS_STATUS.TRIGGERED, ALERTS_STATUS.ACKNOWLEDGED, ALERTS_STATUS.RESOLVED],
status: 'ALL',
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 => {
const domEl = document.querySelector(selector);
const { alertId, projectPath, newIssuePath } = domEl.dataset;
const { alertId, projectPath, projectIssuesPath } = domEl.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
@ -39,7 +39,7 @@ export default selector => {
props: {
alertId,
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 {
...AlertListItem
@ -8,4 +9,9 @@ fragment AlertDetailItem on AlertManagementAlert {
description
updatedAt
details
notes {
nodes {
...AlertNote
}
}
}

View File

@ -6,4 +6,10 @@ fragment AlertListItem on AlertManagementAlert {
startedAt
endedAt
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 {
iid,
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) {
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>
import {
GlDeprecatedButton,
GlButton,
GlFormGroup,
GlFormInput,
GlLink,
GlModal,
GlModalDirective,
GlSprintf,
} from '@gitlab/ui';
import { escape } from 'lodash';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ToggleButton from '~/vue_shared/components/toggle_button.vue';
import axios from '~/lib/utils/axios_utils';
import { s__, __, sprintf } from '~/locale';
import { s__, __ } from '~/locale';
import createFlash from '~/flash';
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'),
RESET_KEY: __('Reset key'),
components: {
GlDeprecatedButton,
GlButton,
GlFormGroup,
GlFormInput,
GlLink,
GlModal,
GlSprintf,
ClipboardButton,
ToggleButton,
},
@ -28,6 +39,14 @@ export default {
'gl-modal': GlModalDirective,
},
props: {
alertsSetupUrl: {
type: String,
required: true,
},
alertsUsageUrl: {
type: String,
required: true,
},
initialAuthorizationKey: {
type: String,
required: false,
@ -41,11 +60,6 @@ export default {
type: String,
required: true,
},
learnMoreUrl: {
type: String,
required: false,
default: '',
},
initialActivated: {
type: Boolean,
required: true,
@ -59,27 +73,17 @@ export default {
};
},
computed: {
learnMoreDescription() {
return sprintf(
s__(
'AlertService|%{linkStart}Learn more%{linkEnd} about configuring this endpoint to receive alerts.',
),
sections() {
return [
{
linkStart: `<a href="${escape(
this.learnMoreUrl,
)}" target="_blank" rel="noopener noreferrer">`,
linkEnd: '</a>',
text: this.$options.i18n.usageSection,
url: this.alertsUsageUrl,
},
false,
);
},
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}`;
{
text: this.$options.i18n.setupSection,
url: this.alertsSetupUrl,
},
];
},
},
watch: {
@ -126,7 +130,15 @@ export default {
<template>
<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">
<toggle-button
id="activated"
@ -155,9 +167,7 @@ export default {
<clipboard-button :text="authorizationKey" :title="$options.COPY_TO_CLIPBOARD" />
</span>
</div>
<gl-deprecated-button v-gl-modal.authKeyModal class="mt-2">{{
$options.RESET_KEY
}}</gl-deprecated-button>
<gl-button v-gl-modal.authKeyModal class="mt-2">{{ $options.RESET_KEY }}</gl-button>
<gl-modal
modal-id="authKeyModal"
:title="$options.RESET_KEY"

View File

@ -7,7 +7,14 @@ export default el => {
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);
return new Vue({
@ -15,9 +22,10 @@ export default el => {
render(createElement) {
return createElement(AlertsServiceForm, {
props: {
alertsSetupUrl,
alertsUsageUrl,
initialActivated: activated,
formPath,
learnMoreUrl,
initialAuthorizationKey: authorizationKey,
url,
},

View File

@ -38,6 +38,7 @@ const Api = {
userPostStatusPath: '/api/:version/user/status',
commitPath: '/api/:version/projects/:id/repository/commits',
applySuggestionPath: '/api/:version/suggestions/:id/apply',
applySuggestionBatchPath: '/api/:version/suggestions/batch_apply',
commitPipelinesPath: '/:project_id/commit/:sha/pipelines',
branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch',
createBranchPath: '/api/:version/projects/:id/repository/branches',
@ -51,8 +52,10 @@ const Api = {
pipelinesPath: '/api/:version/projects/:id/pipelines/',
environmentsPath: '/api/:version/projects/:id/environments',
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);
return axios.get(url).then(({ data }) => {
callback(data);
@ -321,6 +324,12 @@ const Api = {
return axios.put(url);
},
applySuggestionBatch(ids) {
const url = Api.buildUrl(Api.applySuggestionBatchPath);
return axios.put(url, { ids });
},
commitPipelines(projectId, sha) {
const encodedProjectId = projectId
.split('/')
@ -540,6 +549,34 @@ const Api = {
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) {
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.templates = {
setup: '#js-authenticate-u2f-setup',
inProgress: '#js-authenticate-u2f-in-progress',
error: '#js-authenticate-u2f-error',
authenticated: '#js-authenticate-u2f-authenticated',
setup: '#js-authenticate-token-2fa-setup',
inProgress: '#js-authenticate-token-2fa-in-progress',
error: '#js-authenticate-token-2fa-error',
authenticated: '#js-authenticate-token-2fa-authenticated',
};
}
@ -88,7 +88,7 @@ export default class U2FAuthenticate {
error_message: error.message(),
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) {

View File

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

View File

@ -78,7 +78,7 @@ export default class U2FRegister {
error_message: error.message(),
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) {

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 class="btn btn-default btn-sm disabled">
<icon
:size="16"
class="prepend-left-8 append-right-8"
name="doc-image"
aria-hidden="true"
/>
<icon :size="16" class="gl-ml-3 gl-mr-3" name="doc-image" aria-hidden="true" />
</div>
<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>

View File

@ -54,7 +54,7 @@ export default {
<div v-if="canEditBadge" class="table-action-buttons">
<button
:disabled="badge.isDeleting"
class="btn btn-default append-right-8"
class="btn btn-default gl-mr-3"
type="button"
@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 {
props: {
discussionId: {
type: String,
required: false,
default: null,
},
resolveDiscussion: {
type: Boolean,
required: false,
default: false,
},
isDraft: {
type: Boolean,
required: false,
default: false,
},
},
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() {
let title = __('Mark comment as resolved');
if (this.isDraft || this.discussionId) return this.resolvedStatusMessage;
let title = __('Mark as resolved');
if (this.resolvedBy) {
title = sprintf(__('Resolved by %{name}'), { name: this.resolvedBy.name });
@ -12,4 +61,5 @@ export default {
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')
.then(mermaid => {
let theme = 'neutral';
const ideDarkThemes = ['dark', 'solarized-dark'];
if (
window.gon?.user_color_scheme === 'dark' &&
ideDarkThemes.includes(window.gon?.user_color_scheme) &&
// if on the Web IDE page
document.querySelector('.ide')
) {

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