Merge tag 'debian/12.1.13-2' into buster-fasttrack
gitlab Debian release 12.1.13-2
This commit is contained in:
commit
8563fafbe4
6809 changed files with 98427 additions and 198855 deletions
|
@ -5,6 +5,7 @@ globals:
|
|||
gl: false
|
||||
gon: false
|
||||
localStorage: false
|
||||
IS_EE: false
|
||||
plugins:
|
||||
- import
|
||||
- html
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.21-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.29"
|
||||
|
||||
variables:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
|
||||
RAILS_ENV: "test"
|
||||
NODE_ENV: "test"
|
||||
SIMPLECOV: "true"
|
||||
|
@ -37,6 +36,7 @@ include:
|
|||
- local: .gitlab/ci/cng.gitlab-ci.yml
|
||||
- local: .gitlab/ci/docs.gitlab-ci.yml
|
||||
- local: .gitlab/ci/frontend.gitlab-ci.yml
|
||||
- local: .gitlab/ci/memory.gitlab-ci.yml
|
||||
- local: .gitlab/ci/pages.gitlab-ci.yml
|
||||
- local: .gitlab/ci/qa.gitlab-ci.yml
|
||||
- local: .gitlab/ci/reports.gitlab-ci.yml
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
/doc/ @axil @marcia @eread @mikelewis
|
||||
|
||||
# Frontend maintainers should see everything in `app/assets/`
|
||||
app/assets/ @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya
|
||||
*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya
|
||||
app/assets/ @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya @pslaughter
|
||||
*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya @pslaughter
|
||||
|
||||
# Someone from the database team should review changes in `db/`
|
||||
db/ @abrandl @NikolayS
|
||||
|
@ -19,3 +19,5 @@ db/ @abrandl @NikolayS
|
|||
/lib/gitlab/ci/templates/ @nolith @zj
|
||||
/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @DylanGriffith @mayra-cabrera @tkuah
|
||||
/lib/gitlab/ci/templates/Security/ @plafoucriere @gonzoyumo @twoodham
|
||||
/ee/app/models/project_alias.rb @patrickbajao
|
||||
/ee/lib/api/project_aliases.rb @patrickbajao
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
# Trigger a manual docs build in gitlab-docs only on non docs-only branches.
|
||||
# Useful to preview the docs changes live.
|
||||
review-docs-deploy-manual:
|
||||
<<: *review-docs
|
||||
extends:
|
||||
- .review-docs
|
||||
- .no-docs-and-no-qa
|
||||
stage: build
|
||||
script:
|
||||
- gem install gitlab --no-document
|
||||
|
@ -21,9 +23,6 @@ review-docs-deploy-manual:
|
|||
only:
|
||||
- branches@gitlab-org/gitlab-ce
|
||||
- branches@gitlab-org/gitlab-ee
|
||||
except:
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
- /(^qa[\/-].*|.*-qa$)/
|
||||
|
||||
# Always trigger a docs build in gitlab-docs only on docs-only branches.
|
||||
# Useful to preview the docs changes live.
|
||||
|
@ -66,6 +65,8 @@ docs lint:
|
|||
- scripts/lint-changelog-yaml
|
||||
- mv doc/ /tmp/gitlab-docs/content/$DOCS_GITLAB_REPO_SUFFIX
|
||||
- cd /tmp/gitlab-docs
|
||||
# Lint Markdown
|
||||
- bundle exec mdl content/$DOCS_GITLAB_REPO_SUFFIX -c $CI_PROJECT_DIR/.mdlrc
|
||||
# Build HTML from Markdown
|
||||
- bundle exec nanoc
|
||||
# Check the internal links
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
.assets-compile-cache: &assets-compile-cache
|
||||
cache:
|
||||
key: "assets-compile:vendor_ruby:.yarn-cache:tmp_cache_assets_sprockets:v5"
|
||||
key: "assets-compile:vendor_ruby:.yarn-cache:tmp_cache_assets_sprockets:v6"
|
||||
paths:
|
||||
- vendor/ruby/
|
||||
- .yarn-cache/
|
||||
- tmp/cache/assets/sprockets
|
||||
policy: pull-push
|
||||
|
||||
.use-pg: &use-pg
|
||||
services:
|
||||
- name: postgres:9.6
|
||||
- name: postgres:9.6.11
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
- name: redis:alpine
|
||||
|
||||
gitlab:assets:compile:
|
||||
.gitlab:assets:compile-metadata:
|
||||
<<: *assets-compile-cache
|
||||
extends: .dedicated-no-docs-pull-cache-job
|
||||
image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-git-2.21-chrome-73.0-node-12.x-yarn-1.16-graphicsmagick-1.3.29-docker-18.06.1
|
||||
dependencies:
|
||||
- setup-test-env
|
||||
services:
|
||||
- docker:stable-dind
|
||||
- docker:19.03.0-dind
|
||||
variables:
|
||||
NODE_ENV: "production"
|
||||
RAILS_ENV: "production"
|
||||
|
@ -33,7 +32,7 @@ gitlab:assets:compile:
|
|||
DOCKER_HOST: tcp://docker:2375
|
||||
script:
|
||||
- node --version
|
||||
- retry yarn install --frozen-lockfile --production --cache-folder .yarn-cache
|
||||
- retry yarn install --frozen-lockfile --production --cache-folder .yarn-cache --prefer-offline
|
||||
- free -m
|
||||
- retry bundle exec rake gitlab:assets:compile
|
||||
- time scripts/build_assets_image
|
||||
|
@ -58,14 +57,32 @@ gitlab:assets:compile:
|
|||
- docker
|
||||
- gitlab-org
|
||||
|
||||
compile-assets:
|
||||
gitlab:assets:compile:
|
||||
extends: .gitlab:assets:compile-metadata
|
||||
cache:
|
||||
policy: pull-push
|
||||
only:
|
||||
- master@gitlab-org/gitlab-ce
|
||||
- master@gitlab-org/gitlab-ee
|
||||
|
||||
gitlab:assets:compile pull-cache:
|
||||
extends: .gitlab:assets:compile-metadata
|
||||
cache:
|
||||
policy: pull
|
||||
except:
|
||||
refs:
|
||||
- master@gitlab-org/gitlab-ce
|
||||
- master@gitlab-org/gitlab-ee
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
|
||||
.compile-assets-metadata:
|
||||
extends: .dedicated-runner
|
||||
<<: *use-pg
|
||||
<<: *assets-compile-cache
|
||||
stage: prepare
|
||||
script:
|
||||
- node --version
|
||||
- retry yarn install --frozen-lockfile --cache-folder .yarn-cache
|
||||
- retry yarn install --frozen-lockfile --cache-folder .yarn-cache --prefer-offline
|
||||
- free -m
|
||||
- retry bundle exec rake gitlab:assets:compile
|
||||
- scripts/clean-old-cached-assets
|
||||
|
@ -77,8 +94,23 @@ compile-assets:
|
|||
paths:
|
||||
- node_modules
|
||||
- public/assets
|
||||
|
||||
compile-assets:
|
||||
extends: .compile-assets-metadata
|
||||
cache:
|
||||
policy: pull-push
|
||||
only:
|
||||
- master@gitlab-org/gitlab-ce
|
||||
- master@gitlab-org/gitlab-ee
|
||||
|
||||
compile-assets pull-cache:
|
||||
extends: .compile-assets-metadata
|
||||
cache:
|
||||
policy: pull
|
||||
except:
|
||||
refs:
|
||||
- master@gitlab-org/gitlab-ce
|
||||
- master@gitlab-org/gitlab-ee
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
|
||||
gitlab:ui:visual:
|
||||
|
@ -87,6 +119,7 @@ gitlab:ui:visual:
|
|||
allow_failure: true
|
||||
dependencies:
|
||||
- compile-assets
|
||||
- compile-assets pull-cache
|
||||
script:
|
||||
# Remove node modules from GitLab that may conflict with gitlab-ui
|
||||
- rm -r node_modules
|
||||
|
@ -116,6 +149,7 @@ karma:
|
|||
<<: *use-pg
|
||||
dependencies:
|
||||
- compile-assets
|
||||
- compile-assets pull-cache
|
||||
- setup-test-env
|
||||
variables:
|
||||
# we override the max_old_space_size to prevent OOM errors
|
||||
|
@ -134,14 +168,15 @@ karma:
|
|||
paths:
|
||||
- chrome_debug.log
|
||||
- coverage-javascript/
|
||||
reports:
|
||||
junit: junit_karma.xml
|
||||
# reports:
|
||||
# junit: junit_karma.xml
|
||||
|
||||
jest:
|
||||
extends: .dedicated-no-docs-and-no-qa-pull-cache-job
|
||||
<<: *use-pg
|
||||
dependencies:
|
||||
- compile-assets
|
||||
- compile-assets pull-cache
|
||||
- setup-test-env
|
||||
script:
|
||||
- scripts/gitaly-test-spawn
|
||||
|
@ -156,8 +191,8 @@ jest:
|
|||
paths:
|
||||
- coverage-frontend/
|
||||
- junit_jest.xml
|
||||
reports:
|
||||
junit: junit_jest.xml
|
||||
# reports:
|
||||
# junit: junit_jest.xml
|
||||
cache:
|
||||
key: jest
|
||||
paths:
|
||||
|
@ -196,7 +231,7 @@ qa:selectors:
|
|||
before_script: []
|
||||
script:
|
||||
- date
|
||||
- yarn install --frozen-lockfile --cache-folder .yarn-cache
|
||||
- yarn install --frozen-lockfile --cache-folder .yarn-cache --prefer-offline
|
||||
- date
|
||||
- yarn run webpack-prod
|
||||
|
||||
|
@ -232,6 +267,7 @@ jsdoc:
|
|||
stage: post-test
|
||||
dependencies:
|
||||
- compile-assets
|
||||
- compile-assets pull-cache
|
||||
before_script: []
|
||||
script:
|
||||
- date
|
||||
|
|
|
@ -28,16 +28,26 @@
|
|||
policy: pull
|
||||
stage: test
|
||||
|
||||
.dedicated-no-docs-pull-cache-job:
|
||||
extends: .dedicated-pull-cache-job
|
||||
.no-docs:
|
||||
except:
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
refs:
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
|
||||
.no-docs-and-no-qa:
|
||||
except:
|
||||
refs:
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
- /(^qa[\/-].*|.*-qa$)/
|
||||
|
||||
.dedicated-no-docs-pull-cache-job:
|
||||
extends:
|
||||
- .dedicated-pull-cache-job
|
||||
- .no-docs
|
||||
|
||||
.dedicated-no-docs-and-no-qa-pull-cache-job:
|
||||
extends: .dedicated-pull-cache-job
|
||||
except:
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
- /(^qa[\/-].*|.*-qa$)/
|
||||
extends:
|
||||
- .dedicated-pull-cache-job
|
||||
- .no-docs-and-no-qa
|
||||
|
||||
# Jobs that do not need a DB
|
||||
.dedicated-no-docs-no-db-pull-cache-job:
|
||||
|
@ -45,6 +55,12 @@
|
|||
variables:
|
||||
SETUP_DB: "false"
|
||||
|
||||
# Jobs that need a dedicated runner, with no cache
|
||||
.dedicated-no-docs:
|
||||
extends:
|
||||
- .dedicated-runner
|
||||
- .no-docs
|
||||
|
||||
.single-script-job-dedicated-runner:
|
||||
extends: .dedicated-runner
|
||||
image: ruby:2.6-alpine
|
||||
|
|
42
.gitlab/ci/memory.gitlab-ci.yml
Normal file
42
.gitlab/ci/memory.gitlab-ci.yml
Normal file
|
@ -0,0 +1,42 @@
|
|||
memory-static:
|
||||
extends: .dedicated-no-docs-no-db-pull-cache-job
|
||||
script:
|
||||
# Uses two different reports from the 'derailed_benchmars' gem.
|
||||
|
||||
# Loads each of gems in the Gemfile and checks how much memory they consume when they are required.
|
||||
# 'derailed_benchmarks' internally uses 'get_process_mem'
|
||||
- bundle exec derailed bundle:mem > tmp/memory_bundle_mem.txt
|
||||
- scripts/generate-gems-size-metrics-static tmp/memory_bundle_mem.txt >> 'tmp/memory_metrics.txt'
|
||||
|
||||
# Outputs detailed information about objects created while gems are loaded.
|
||||
# 'derailed_benchmarks' internally uses 'memory_profiler'
|
||||
- bundle exec derailed bundle:objects > tmp/memory_bundle_objects.txt
|
||||
- scripts/generate-gems-memory-metrics-static tmp/memory_bundle_objects.txt >> 'tmp/memory_metrics.txt'
|
||||
artifacts:
|
||||
paths:
|
||||
- tmp/memory_*.txt
|
||||
reports:
|
||||
metrics: tmp/memory_metrics.txt
|
||||
|
||||
# Show memory usage caused by invoking require per gem.
|
||||
# Unlike `memory-static`, it hits the app with one request to ensure that any last minute require-s have been called.
|
||||
# The application is booted in `production` environment.
|
||||
# All tests are run without a webserver (directly using Rack::Mock by default).
|
||||
memory-on-boot:
|
||||
extends: .rspec-metadata-pg-10
|
||||
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:
|
||||
# Both bootsnap and derailed monkey-patch Kernel#require, which leads to circular dependency
|
||||
- DISABLE_BOOTSNAP=true PATH_TO_HIT="/users/sign_in" CUT_OFF=0.3 bundle exec derailed exec perf:mem >> 'tmp/memory_on_boot.txt'
|
||||
- scripts/generate-memory-metrics-on-boot tmp/memory_on_boot.txt >> 'tmp/memory_on_boot_metrics.txt'
|
||||
artifacts:
|
||||
paths:
|
||||
- tmp/memory_*.txt
|
||||
reports:
|
||||
metrics: tmp/memory_on_boot_metrics.txt
|
|
@ -1,6 +1,6 @@
|
|||
.use-pg: &use-pg
|
||||
services:
|
||||
- name: postgres:9.6
|
||||
- name: postgres:9.6.11
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
- name: redis:alpine
|
||||
|
||||
|
@ -10,11 +10,6 @@
|
|||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
- name: redis:alpine
|
||||
|
||||
.use-mysql: &use-mysql
|
||||
services:
|
||||
- mysql:5.7
|
||||
- redis:alpine
|
||||
|
||||
.only-schedules-master: &only-schedules-master
|
||||
only:
|
||||
- schedules@gitlab-org/gitlab-ce
|
||||
|
@ -25,8 +20,9 @@
|
|||
- master@gitlab/gitlab-ee
|
||||
|
||||
.gitlab-setup: &gitlab-setup
|
||||
extends: .dedicated-no-docs-and-no-qa-pull-cache-job
|
||||
<<: *use-pg
|
||||
extends:
|
||||
- .dedicated-no-docs-and-no-qa-pull-cache-job
|
||||
- .use-pg
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
script:
|
||||
|
@ -48,7 +44,9 @@
|
|||
- bundle exec rake $CI_JOB_NAME
|
||||
|
||||
.rspec-metadata: &rspec-metadata
|
||||
extends: .dedicated-pull-cache-job
|
||||
extends:
|
||||
- .dedicated-pull-cache-job
|
||||
- .no-docs-and-no-qa
|
||||
stage: test
|
||||
script:
|
||||
- JOB_NAME=( $CI_JOB_NAME )
|
||||
|
@ -68,6 +66,8 @@
|
|||
- scripts/gitaly-test-spawn
|
||||
- date
|
||||
- 'export KNAPSACK_TEST_FILE_PATTERN=$(ruby -r./lib/quality/test_level.rb -e "puts Quality::TestLevel.new.pattern(:${TEST_LEVEL})")'
|
||||
- mkdir -p tmp/memory_test
|
||||
- export MEMORY_TEST_PATH="tmp/memory_test/${TEST_TOOL}_${TEST_LEVEL}_${DATABASE}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_memory.csv"
|
||||
- knapsack rspec "--color --format documentation --format RspecJunitFormatter --out junit_rspec.xml --tag level:${TEST_LEVEL} --tag ~geo"
|
||||
- date
|
||||
artifacts:
|
||||
|
@ -79,11 +79,9 @@
|
|||
- rspec_flaky/
|
||||
- rspec_profiling/
|
||||
- tmp/capybara/
|
||||
reports:
|
||||
junit: junit_rspec.xml
|
||||
except:
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
- /(^qa[\/-].*|.*-qa$)/
|
||||
- tmp/memory_test/
|
||||
# reports:
|
||||
# junit: junit_rspec.xml
|
||||
|
||||
.rspec-metadata-pg: &rspec-metadata-pg
|
||||
<<: *rspec-metadata
|
||||
|
@ -94,10 +92,6 @@
|
|||
<<: *use-pg-10
|
||||
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.21-chrome-73.0-node-12.x-yarn-1.16-postgresql-10-graphicsmagick-1.3.29"
|
||||
|
||||
.rspec-metadata-mysql: &rspec-metadata-mysql
|
||||
<<: *rspec-metadata
|
||||
<<: *use-mysql
|
||||
|
||||
# DB migration, rollback, and seed jobs
|
||||
.db-migrate-reset: &db-migrate-reset
|
||||
extends: .dedicated-no-docs-and-no-qa-pull-cache-job
|
||||
|
@ -131,8 +125,10 @@
|
|||
- setup-test-env
|
||||
|
||||
setup-test-env:
|
||||
extends: .dedicated-runner-default-cache
|
||||
<<: *use-pg
|
||||
extends:
|
||||
- .dedicated-runner-default-cache
|
||||
- .no-docs
|
||||
- .use-pg
|
||||
stage: prepare
|
||||
script:
|
||||
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
|
||||
|
@ -143,8 +139,6 @@ setup-test-env:
|
|||
- tmp/tests
|
||||
- config/secrets.yml
|
||||
- vendor/gitaly-ruby
|
||||
except:
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
|
||||
rspec unit pg:
|
||||
<<: *rspec-metadata-pg
|
||||
|
@ -173,42 +167,6 @@ rspec system pg-10:
|
|||
<<: *only-schedules-master
|
||||
parallel: 24
|
||||
|
||||
rspec unit mysql:
|
||||
<<: *rspec-metadata-mysql
|
||||
<<: *only-schedules-master
|
||||
parallel: 20
|
||||
|
||||
rspec integration mysql:
|
||||
<<: *rspec-metadata-mysql
|
||||
<<: *only-schedules-master
|
||||
parallel: 6
|
||||
|
||||
rspec system mysql:
|
||||
<<: *rspec-metadata-mysql
|
||||
<<: *only-schedules-master
|
||||
parallel: 24
|
||||
|
||||
.rspec-mysql-on-demand: &rspec-mysql-on-demand
|
||||
only:
|
||||
variables:
|
||||
- $CI_COMMIT_MESSAGE =~ /\[run mysql\]/i
|
||||
- $CI_COMMIT_REF_NAME =~ /mysql/
|
||||
|
||||
rspec unit mysql on-demand:
|
||||
<<: *rspec-metadata-mysql
|
||||
<<: *rspec-mysql-on-demand
|
||||
parallel: 20
|
||||
|
||||
rspec integration mysql on-demand:
|
||||
<<: *rspec-metadata-mysql
|
||||
<<: *rspec-mysql-on-demand
|
||||
parallel: 6
|
||||
|
||||
rspec system mysql on-demand:
|
||||
<<: *rspec-metadata-mysql
|
||||
<<: *rspec-mysql-on-demand
|
||||
parallel: 24
|
||||
|
||||
rspec-fast-spec-helper:
|
||||
<<: *rspec-metadata-pg
|
||||
script:
|
||||
|
@ -226,16 +184,11 @@ rspec quarantine pg:
|
|||
<<: *rspec-quarantine
|
||||
allow_failure: true
|
||||
|
||||
rspec quarantine mysql:
|
||||
<<: *rspec-metadata-mysql
|
||||
<<: *rspec-quarantine
|
||||
<<: *only-schedules-master
|
||||
allow_failure: true
|
||||
|
||||
static-analysis:
|
||||
extends: .dedicated-no-docs-no-db-pull-cache-job
|
||||
dependencies:
|
||||
- compile-assets
|
||||
- compile-assets pull-cache
|
||||
- setup-test-env
|
||||
script:
|
||||
- scripts/static-analysis
|
||||
|
@ -250,11 +203,12 @@ static-analysis:
|
|||
downtime_check:
|
||||
<<: *rake-exec
|
||||
except:
|
||||
- master
|
||||
- tags
|
||||
- /^[\d-]+-stable(-ee)?$/
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
- /(^qa[\/-].*|.*-qa$)/
|
||||
refs:
|
||||
- master
|
||||
- tags
|
||||
- /^[\d-]+-stable(-ee)?$/
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
- /(^qa[\/-].*|.*-qa$)/
|
||||
dependencies:
|
||||
- setup-test-env
|
||||
|
||||
|
@ -262,12 +216,13 @@ ee_compat_check:
|
|||
<<: *rake-exec
|
||||
dependencies: []
|
||||
except:
|
||||
- master
|
||||
- tags
|
||||
- /[\d-]+-stable(-ee)?/
|
||||
- /^security-/
|
||||
- branches@gitlab-org/gitlab-ee
|
||||
- branches@gitlab/gitlab-ee
|
||||
refs:
|
||||
- master
|
||||
- tags
|
||||
- /[\d-]+-stable(-ee)?/
|
||||
- /^security-/
|
||||
- branches@gitlab-org/gitlab-ee
|
||||
- branches@gitlab/gitlab-ee
|
||||
retry: 0
|
||||
artifacts:
|
||||
name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}"
|
||||
|
@ -280,10 +235,6 @@ db:migrate:reset-pg:
|
|||
<<: *db-migrate-reset
|
||||
<<: *use-pg
|
||||
|
||||
db:migrate:reset-mysql:
|
||||
<<: *db-migrate-reset
|
||||
<<: *use-mysql
|
||||
|
||||
db:check-schema-pg:
|
||||
<<: *db-migrate-reset
|
||||
<<: *use-pg
|
||||
|
@ -294,15 +245,11 @@ migration:path-pg:
|
|||
<<: *migration-paths
|
||||
<<: *use-pg
|
||||
|
||||
migration:path-mysql:
|
||||
<<: *migration-paths
|
||||
<<: *use-mysql
|
||||
|
||||
.db-rollback: &db-rollback
|
||||
extends: .dedicated-no-docs-and-no-qa-pull-cache-job
|
||||
script:
|
||||
- bundle exec rake db:migrate VERSION=20170523121229
|
||||
- bundle exec rake db:migrate
|
||||
- bundle exec rake db:migrate VERSION=20180101160629
|
||||
- bundle exec rake db:migrate SKIP_SCHEMA_VERSION_CHECK=true
|
||||
dependencies:
|
||||
- setup-test-env
|
||||
|
||||
|
@ -310,26 +257,18 @@ db:rollback-pg:
|
|||
<<: *db-rollback
|
||||
<<: *use-pg
|
||||
|
||||
db:rollback-mysql:
|
||||
<<: *db-rollback
|
||||
<<: *use-mysql
|
||||
|
||||
gitlab:setup-pg:
|
||||
<<: *gitlab-setup
|
||||
<<: *use-pg
|
||||
dependencies:
|
||||
- setup-test-env
|
||||
|
||||
gitlab:setup-mysql:
|
||||
<<: *gitlab-setup
|
||||
<<: *use-mysql
|
||||
dependencies:
|
||||
- setup-test-env
|
||||
|
||||
coverage:
|
||||
# Don't include dedicated-no-docs-no-db-pull-cache-job here since we need to
|
||||
# download artifacts from all the rspec jobs instead of from setup-test-env only
|
||||
extends: .dedicated-runner-default-cache
|
||||
extends:
|
||||
- .dedicated-runner-default-cache
|
||||
- .no-docs-and-no-qa
|
||||
cache:
|
||||
policy: pull
|
||||
variables:
|
||||
|
@ -337,6 +276,7 @@ coverage:
|
|||
stage: post-test
|
||||
script:
|
||||
- bundle exec scripts/merge-simplecov
|
||||
- bundle exec scripts/gather-test-memory-data
|
||||
coverage: '/LOC \((\d+\.\d+%)\) covered.$/'
|
||||
artifacts:
|
||||
name: coverage
|
||||
|
@ -344,6 +284,4 @@ coverage:
|
|||
paths:
|
||||
- coverage/index.html
|
||||
- coverage/assets/
|
||||
except:
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
- /(^qa[\/-].*|.*-qa$)/
|
||||
- tmp/memory_test/
|
||||
|
|
|
@ -1,98 +1,26 @@
|
|||
include:
|
||||
- template: Code-Quality.gitlab-ci.yml
|
||||
- template: Security/SAST.gitlab-ci.yml
|
||||
- template: Security/Dependency-Scanning.gitlab-ci.yml
|
||||
|
||||
code_quality:
|
||||
extends: .dedicated-no-docs-no-db-pull-cache-job
|
||||
extends: .dedicated-no-docs
|
||||
# gitlab-org runners set `privileged: false` but we need to have it set to true
|
||||
# since we're using Docker in Docker
|
||||
tags: []
|
||||
before_script: []
|
||||
cache: {}
|
||||
dependencies: []
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
|
||||
sast:
|
||||
extends: .dedicated-no-docs-no-db-pull-cache-job
|
||||
image: docker:stable
|
||||
variables:
|
||||
SAST_CONFIDENCE_LEVEL: 2
|
||||
DOCKER_DRIVER: overlay2
|
||||
allow_failure: true
|
||||
extends: .dedicated-no-docs
|
||||
tags: []
|
||||
before_script: []
|
||||
cache: {}
|
||||
dependencies: []
|
||||
services:
|
||||
- docker:stable-dind
|
||||
script:
|
||||
- | # this is required to avoid undesirable reset of Docker image ENV variables being set on build stage
|
||||
function propagate_env_vars() {
|
||||
CURRENT_ENV=$(printenv)
|
||||
|
||||
for VAR_NAME; do
|
||||
echo $CURRENT_ENV | grep "${VAR_NAME}=" > /dev/null && echo "--env $VAR_NAME "
|
||||
done
|
||||
}
|
||||
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
|
||||
- |
|
||||
docker run \
|
||||
$(propagate_env_vars \
|
||||
SAST_ANALYZER_IMAGES \
|
||||
SAST_ANALYZER_IMAGE_PREFIX \
|
||||
SAST_ANALYZER_IMAGE_TAG \
|
||||
SAST_DEFAULT_ANALYZERS \
|
||||
SAST_BRAKEMAN_LEVEL \
|
||||
SAST_GOSEC_LEVEL \
|
||||
SAST_FLAWFINDER_LEVEL \
|
||||
SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
|
||||
SAST_PULL_ANALYZER_IMAGE_TIMEOUT \
|
||||
SAST_RUN_ANALYZER_TIMEOUT \
|
||||
) \
|
||||
--volume "$PWD:/code" \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
"registry.gitlab.com/gitlab-org/security-products/sast:$SP_VERSION" /app/bin/run /code
|
||||
artifacts:
|
||||
reports:
|
||||
sast: gl-sast-report.json
|
||||
variables:
|
||||
SAST_BRAKEMAN_LEVEL: 2
|
||||
|
||||
dependency_scanning:
|
||||
extends: .dedicated-no-docs-no-db-pull-cache-job
|
||||
image: docker:stable
|
||||
variables:
|
||||
DOCKER_DRIVER: overlay2
|
||||
allow_failure: true
|
||||
extends: .dedicated-no-docs
|
||||
tags: []
|
||||
before_script: []
|
||||
cache: {}
|
||||
dependencies: []
|
||||
services:
|
||||
- docker:stable-dind
|
||||
script:
|
||||
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
|
||||
- | # this is required to avoid undesirable reset of Docker image ENV variables being set on build stage
|
||||
function propagate_env_vars() {
|
||||
CURRENT_ENV=$(printenv)
|
||||
|
||||
for VAR_NAME; do
|
||||
echo $CURRENT_ENV | grep "${VAR_NAME}=" > /dev/null && echo "--env $VAR_NAME "
|
||||
done
|
||||
}
|
||||
- |
|
||||
docker run \
|
||||
$(propagate_env_vars \
|
||||
DS_ANALYZER_IMAGES \
|
||||
DS_ANALYZER_IMAGE_PREFIX \
|
||||
DS_ANALYZER_IMAGE_TAG \
|
||||
DS_DEFAULT_ANALYZERS \
|
||||
DEP_SCAN_DISABLE_REMOTE_CHECKS \
|
||||
DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
|
||||
DS_PULL_ANALYZER_IMAGE_TIMEOUT \
|
||||
DS_RUN_ANALYZER_TIMEOUT \
|
||||
) \
|
||||
--volume "$PWD:/code" \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
"registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$SP_VERSION" /code
|
||||
artifacts:
|
||||
reports:
|
||||
dependency_scanning: gl-dependency-scanning-report.json
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
<<: *review-base
|
||||
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine
|
||||
services:
|
||||
- docker:stable-dind
|
||||
- docker:19.03.0-dind
|
||||
tags:
|
||||
- gitlab-org
|
||||
- docker
|
||||
|
@ -77,6 +77,7 @@ schedule:review-build-cng:
|
|||
.review-deploy-base: &review-deploy-base
|
||||
<<: *review-base
|
||||
allow_failure: true
|
||||
retry: 1
|
||||
stage: review
|
||||
variables:
|
||||
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
|
||||
|
@ -95,10 +96,16 @@ schedule:review-build-cng:
|
|||
- install_api_client_dependencies_with_apk
|
||||
- source scripts/review_apps/review-apps.sh
|
||||
script:
|
||||
- perform_review_app_deployment
|
||||
- check_kube_domain
|
||||
- ensure_namespace
|
||||
- install_tiller
|
||||
- install_external_dns
|
||||
- download_chart
|
||||
- deploy || display_deployment_debug
|
||||
- wait_for_review_app_to_be_accessible
|
||||
- add_license
|
||||
artifacts:
|
||||
paths:
|
||||
- review_app_url.txt
|
||||
paths: [review_app_url.txt]
|
||||
expire_in: 2 days
|
||||
when: always
|
||||
|
||||
|
@ -108,8 +115,6 @@ review-deploy:
|
|||
schedule:review-deploy:
|
||||
<<: *review-deploy-base
|
||||
<<: *review-schedules-only
|
||||
script:
|
||||
- perform_review_app_deployment
|
||||
|
||||
review-stop:
|
||||
<<: *review-base
|
||||
|
@ -124,11 +129,11 @@ review-stop:
|
|||
script:
|
||||
- source scripts/review_apps/review-apps.sh
|
||||
- delete
|
||||
- cleanup
|
||||
|
||||
.review-qa-base: &review-qa-base
|
||||
<<: *review-docker
|
||||
allow_failure: true
|
||||
retry: 2
|
||||
stage: qa
|
||||
variables:
|
||||
<<: *review-docker-variables
|
||||
|
@ -169,7 +174,38 @@ review-qa-all:
|
|||
script:
|
||||
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/review-qa-all_master_report.json
|
||||
- export KNAPSACK_TEST_FILE_PATTERN=qa/specs/features/**/*_spec.rb
|
||||
- gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
|
||||
- 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
|
||||
|
||||
parallel-spec-reports:
|
||||
extends: .dedicated-runner
|
||||
dependencies:
|
||||
- review-qa-all
|
||||
image: ruby:2.6-alpine
|
||||
services: []
|
||||
before_script: []
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
NEW_PARALLEL_SPECS_REPORT: qa/report-new.html
|
||||
BASE_ARTIFACT_URL: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/qa/"
|
||||
stage: post-test
|
||||
allow_failure: true
|
||||
when: manual
|
||||
retry: 0
|
||||
artifacts:
|
||||
when: always
|
||||
paths:
|
||||
- qa/report-new.html
|
||||
- qa/gitlab-qa-run-*
|
||||
reports:
|
||||
junit: qa/gitlab-qa-run-*/**/rspec-*.xml
|
||||
script:
|
||||
- apk add --update build-base libxml2-dev libxslt-dev && rm -rf /var/cache/apk/*
|
||||
- gem install nokogiri
|
||||
- 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
|
||||
|
||||
.review-performance-base: &review-performance-base
|
||||
<<: *review-qa-base
|
||||
|
@ -225,11 +261,12 @@ danger-review:
|
|||
except:
|
||||
refs:
|
||||
- master
|
||||
- /^[\d-]+-stable(-ee)?$/
|
||||
variables:
|
||||
- $CI_COMMIT_REF_NAME =~ /^ce-to-ee-.*/
|
||||
- $CI_COMMIT_REF_NAME =~ /.*-stable(-ee)?-prepare-.*/
|
||||
script:
|
||||
- git version
|
||||
- node --version
|
||||
- yarn install --frozen-lockfile --cache-folder .yarn-cache
|
||||
- yarn install --frozen-lockfile --cache-folder .yarn-cache --prefer-offline
|
||||
- danger --fail-on-errors=true
|
||||
|
|
|
@ -15,7 +15,9 @@ cache gems:
|
|||
- setup-test-env
|
||||
|
||||
gitlab_git_test:
|
||||
extends: .dedicated-runner
|
||||
extends:
|
||||
- .dedicated-runner
|
||||
- .no-docs-and-no-qa
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
before_script: []
|
||||
|
@ -23,12 +25,11 @@ gitlab_git_test:
|
|||
cache: {}
|
||||
script:
|
||||
- spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
|
||||
except:
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
- /(^qa[\/-].*|.*-qa$)/
|
||||
|
||||
no_ee_check:
|
||||
extends: .dedicated-runner
|
||||
extends:
|
||||
- .dedicated-runner
|
||||
- .no-docs-and-no-qa
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
before_script: []
|
||||
|
@ -38,6 +39,3 @@ no_ee_check:
|
|||
- scripts/no-ee-check
|
||||
only:
|
||||
- /.+/@gitlab-org/gitlab-ce
|
||||
except:
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
- /(^qa[\/-].*|.*-qa$)/
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
- rspec_profiling/
|
||||
|
||||
retrieve-tests-metadata:
|
||||
<<: *tests-metadata-state
|
||||
extends:
|
||||
- .tests-metadata-state
|
||||
- .no-docs-and-no-qa
|
||||
stage: prepare
|
||||
cache:
|
||||
key: tests_metadata
|
||||
|
@ -25,9 +27,6 @@ retrieve-tests-metadata:
|
|||
- mkdir -p rspec_profiling/
|
||||
- wget -O $FLAKY_RSPEC_SUITE_REPORT_PATH http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/$FLAKY_RSPEC_SUITE_REPORT_PATH || rm $FLAKY_RSPEC_SUITE_REPORT_PATH
|
||||
- '[[ -f $FLAKY_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_SUITE_REPORT_PATH}'
|
||||
except:
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
- /(^qa[\/-].*|.*-qa$)/
|
||||
|
||||
update-tests-metadata:
|
||||
<<: *tests-metadata-state
|
||||
|
@ -69,9 +68,10 @@ flaky-examples-check:
|
|||
only:
|
||||
- branches
|
||||
except:
|
||||
- master
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
- /(^qa[\/-].*|.*-qa$)/
|
||||
refs:
|
||||
- master
|
||||
- /(^docs[\/-].*|.*-docs$)/
|
||||
- /(^qa[\/-].*|.*-qa$)/
|
||||
artifacts:
|
||||
expire_in: 30d
|
||||
paths:
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
#### Database Reviewer Checklist
|
||||
|
||||
Thank you for becoming a ~database reviewer! Please work on the list
|
||||
below to complete your setup. For any question, reach out to #database
|
||||
an mention `@abrandl`.
|
||||
|
||||
- [ ] Change issue title to include your name: `Database Reviewer Checklist: Your Name`
|
||||
- [ ] Review general [code review guide](https://docs.gitlab.com/ee/development/code_review.html)
|
||||
- [ ] Review [database review documentation](https://about.gitlab.com/handbook/engineering/workflow/code-review/database.html)
|
||||
- [ ] Familiarize with [migration helpers](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/database/migration_helpers.rb) and review usage in existing migrations
|
||||
- [ ] Read [database migration style guide](https://docs.gitlab.com/ee/development/migration_style_guide.html)
|
||||
- [ ] Familiarize with best practices in [database guides](https://docs.gitlab.com/ee/development/#database-guides)
|
||||
- [ ] Watch [Optimising Rails Database Queries: Episode 1](https://www.youtube.com/watch?v=79GurlaxhsI)
|
||||
- [ ] Read [Understanding EXPLAIN plans](https://docs.gitlab.com/ee/development/understanding_explain_plans.html)
|
||||
- [ ] Review [database best practices](https://docs.gitlab.com/ee/development/#best-practices)
|
||||
- [ ] Review how we use [database instances restored from a backup](https://ops.gitlab.net/gitlab-com/gl-infra/gitlab-restore/postgres-gprd) for testing and make sure you're set up to execute pipelines (check [README.md](https://ops.gitlab.net/gitlab-com/gl-infra/gitlab-restore/postgres-gprd/blob/master/README.md) and reach out to @abrandl since this is currently subject to being changed)
|
||||
- [ ] Get yourself added to [`@gl-database`](https://gitlab.com/groups/gl-database/-/group_members) group and respond to @-mentions to the group (reach out to any maintainer on the group to get added). You will get TODOs on gitlab.com for group mentions.
|
||||
- [ ] Make sure you have proper access to at least a read-only replica in staging and production
|
||||
- [ ] Indicate in `data/team.yml` your role as a database reviewer ([example MR](https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/19600/diffs)). Assign MR to your manager for merge.
|
||||
- [ ] Send one MR to improve the [review documentation](https://about.gitlab.com/handbook/engineering/workflow/code-review/database.html) or the [issue template](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/issue_templates/Database%20Reviewer.md)
|
||||
|
||||
Note that *approving and accepting* merge requests is *restricted* to
|
||||
Database Maintainers only. As a reviewer, pass the MR to a maintainer
|
||||
for approval.
|
||||
|
||||
You're all set! Watch out for TODOs on GitLab.com.
|
||||
|
||||
###### Where to go for questions?
|
||||
|
||||
Reach out to `#database` on Slack and mention `@abrandl` for any questions.
|
||||
|
||||
cc @abrandl
|
||||
|
||||
/label ~meta ~database
|
|
@ -4,7 +4,7 @@
|
|||
Note: Doc work as part of feature development is covered in the Feature Request template.
|
||||
|
||||
* For issues related to features of the docs.gitlab.com site, see
|
||||
https://gitlab.com/gitlab-com/gitlab-docs/issues/
|
||||
https://gitlab.com/gitlab-org/gitlab-docs/issues/
|
||||
|
||||
* For information about documentation content and process, see
|
||||
https://docs.gitlab.com/ee/development/documentation/ -->
|
||||
|
|
|
@ -22,7 +22,7 @@ https://docs.gitlab.com/ce/development/documentation/index.html#changing-documen
|
|||
- [ ] Make sure internal links pointing to the document in question are not broken.
|
||||
- [ ] Search and replace any links referring to old docs in GitLab Rails app,
|
||||
specifically under the `app/views/` and `ee/app/views` (for GitLab EE) directories.
|
||||
- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ce/development/writing_documentation.html#redirections-for-pages-with-disqus-comments)
|
||||
- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ce/development/documentation/index.html#redirections-for-pages-with-disqus-comments)
|
||||
to the new document if there are any Disqus comments on the old document thread.
|
||||
- [ ] Update the link in `features.yml` (if applicable)
|
||||
- [ ] If working on CE and the `ee-compat-check` jobs fails, submit an MR to EE
|
||||
|
|
7
.mdlrc
Normal file
7
.mdlrc
Normal file
|
@ -0,0 +1,7 @@
|
|||
# This is the options file for mdl, configured in .gitlab/ci/docs.gitlab-ci.yml,
|
||||
# and related to the style file ./mdlrc.style
|
||||
|
||||
# See https://github.com/markdownlint/markdownlint/blob/master/docs/configuration.md
|
||||
|
||||
ignore_front_matter true
|
||||
style File.expand_path('.mdlrc.style', __dir__)
|
21
.mdlrc.style
Normal file
21
.mdlrc.style
Normal file
|
@ -0,0 +1,21 @@
|
|||
# This is the style file for mdl, configured in .gitlab/ci/docs.gitlab-ci.yml,
|
||||
# and related to the options file ./mdlrc
|
||||
|
||||
# See https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md
|
||||
# for more detailed information on the rules and styles.
|
||||
|
||||
rule "MD001"
|
||||
rule "MD003", :style => :atx
|
||||
rule "MD011"
|
||||
rule "MD023"
|
||||
rule "MD032"
|
||||
rule "MD034"
|
||||
rule "MD037"
|
||||
|
||||
# Should not be used currently:
|
||||
|
||||
# rule "MD004", :style => :dash # unordered list style - dash
|
||||
# False positives, see https://github.com/markdownlint/markdownlint/issues/261
|
||||
|
||||
# rule "MD039" # Spaces inside link text
|
||||
# Crashes when link text has certain punctuation
|
|
@ -466,12 +466,10 @@ Rails/LinkToBlank:
|
|||
Rails/Presence:
|
||||
Exclude:
|
||||
- 'app/models/ci/pipeline.rb'
|
||||
- 'app/models/clusters/platforms/kubernetes.rb'
|
||||
- 'app/models/concerns/mentionable.rb'
|
||||
- 'app/models/project_services/hipchat_service.rb'
|
||||
- 'app/models/project_services/irker_service.rb'
|
||||
- 'app/models/project_services/jira_service.rb'
|
||||
- 'app/models/project_services/kubernetes_service.rb'
|
||||
- 'app/models/project_services/packagist_service.rb'
|
||||
- 'app/models/wiki_page.rb'
|
||||
- 'lib/gitlab/github_import/importer/releases_importer.rb'
|
||||
|
@ -514,7 +512,6 @@ Security/YAMLLoad:
|
|||
- 'spec/config/mail_room_spec.rb'
|
||||
- 'spec/initializers/secret_token_spec.rb'
|
||||
- 'spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb'
|
||||
- 'spec/models/project_services/kubernetes_service_spec.rb'
|
||||
|
||||
# Offense count: 34
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
|
|
398
CHANGELOG.md
398
CHANGELOG.md
|
@ -2,20 +2,51 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 12.0.9
|
||||
## 12.1.13
|
||||
|
||||
### Security (1 change)
|
||||
|
||||
- Upgrade pages to 1.6.3.
|
||||
- Fix private feature Elasticsearch leak.
|
||||
|
||||
|
||||
## 12.0.8
|
||||
## 12.1.12
|
||||
|
||||
### Security (22 changes)
|
||||
### Security (11 changes)
|
||||
|
||||
- Add a policy check for system notes that may not be visible due to cross references to private items.
|
||||
- Display only participants that user has permission to see on milestone page.
|
||||
- Do not disclose project milestones on group milestones page when project milestones access is disabled in project settings.
|
||||
- Fix new project path being disclosed through unsubscribe link of issue/merge requests.
|
||||
- Prevent bypassing email verification using Salesforce.
|
||||
- Do not show resource label events referencing not accessible labels.
|
||||
- Cancel all running CI jobs triggered by the user who is just blocked.
|
||||
- Fix Gitaly SearchBlobs flag RPC injection.
|
||||
- Only render fixed number of mermaid blocks.
|
||||
- Prevent GitLab accounts takeover if SAML is configured.
|
||||
- Upgrade mermaid to prevent XSS.
|
||||
|
||||
|
||||
## 12.1.11
|
||||
|
||||
- No changes.
|
||||
|
||||
## 12.1.10
|
||||
|
||||
- No changes.
|
||||
|
||||
## 12.1.9
|
||||
|
||||
### Security (1 change)
|
||||
|
||||
- Upgrade pages to 1.7.2.
|
||||
|
||||
|
||||
## 12.1.8
|
||||
|
||||
### Security (21 changes)
|
||||
|
||||
- Ensure only authorised users can create notes on Merge Requests and Issues.
|
||||
- Add :login_recaptcha_protection_enabled setting to prevent bots from brute-force attacks.
|
||||
- Queries for Upload should be scoped by model.
|
||||
- Speed up regexp in namespace format by failing fast after reaching maximum namespace depth.
|
||||
- Limit the size of issuable description and comments.
|
||||
- Send TODOs for comments on commits correctly.
|
||||
|
@ -37,36 +68,349 @@ entry.
|
|||
- Fix SSRF via DNS rebinding in Kubernetes Integration.
|
||||
|
||||
|
||||
## 12.0.7
|
||||
## 12.1.7
|
||||
|
||||
- Unreleased due to QA failure.
|
||||
|
||||
## 12.0.6
|
||||
## 12.1.6
|
||||
|
||||
### Security (2 changes)
|
||||
|
||||
- Upgrade Gitaly to 1.47.2 to prevent revision flag injection exploits.
|
||||
- Upgrade pages to 1.6.2 to prevent gitlab api token recovery from cookie.
|
||||
- Upgrade Gitaly to 1.53.2 to prevent revision flag injection exploits.
|
||||
- Upgrade pages to 1.7.1 to prevent gitlab api token recovery from cookie.
|
||||
|
||||
## 12.0.5
|
||||
## 12.1.5
|
||||
|
||||
- No changes.
|
||||
|
||||
## 12.0.4
|
||||
## 12.1.4
|
||||
|
||||
### Fixed (3 changes, 1 of them is from the community)
|
||||
|
||||
- Properly translate term in projects list. !30958
|
||||
- Add exclusive lease to mergeability check process. !31082
|
||||
- Fix Docker in Docker (DIND) listen port behavior change by adding DOCKER_TLS_CERTDIR in CI job templates. !31201 (Cameron Boulton)
|
||||
|
||||
### Performance (1 change)
|
||||
|
||||
- Improve job log rendering performance. !31262
|
||||
|
||||
|
||||
## 12.1.3
|
||||
|
||||
### Fixed (11 changes)
|
||||
|
||||
- Prevent multiple confirmation modals from opening when deleting a repository. !30532
|
||||
- Fix the project auto devops API. !30946
|
||||
- Fix "Certificate misses intermediates" UI error when enabling Let's Encrypt integration for pages domain. !30995
|
||||
- Fix xterm css not loading for environment terminal. !31023
|
||||
- Set DOCKER_TLS_CERTDIR in Auto Dev-Ops CI template to fix jobs using Docker-in-Docker. !31078
|
||||
- Set DOCKER_TLS_CERTDIR in CI job templates to fix Docker-in-Docker service. !31080
|
||||
- Support Docker OCI images. !31127
|
||||
- Fix error rendering submodules in MR diffs when there is no .gitmodules. !31162
|
||||
- Fix pdf.js rendering pages in the wrong order. !31222
|
||||
- Fix exception handling in Gitaly autodetection. !31285
|
||||
- Fix bug that caused diffs not to show on MRs with changes to submodules.
|
||||
|
||||
### Performance (1 change)
|
||||
|
||||
- Optimise import performance. !31045
|
||||
|
||||
|
||||
## 12.1.2
|
||||
|
||||
### Security (1 change)
|
||||
|
||||
- Use source project as permissions reference for MergeRequestsController#pipelines.
|
||||
|
||||
### Security (9 changes)
|
||||
|
||||
- Restrict slash commands to users who can log in.
|
||||
- Patch XSS issue in wiki links.
|
||||
- Queries for Upload should be scoped by model.
|
||||
- Filter merge request params on the new merge request page.
|
||||
- Fix Server Side Request Forgery mitigation bypass.
|
||||
- Show badges if pipelines are public otherwise default to project permissions.
|
||||
- Do not allow localhost url redirection in GitHub Integration.
|
||||
- Do not show moved issue id for users that cannot read issue.
|
||||
- Use source project as permissions reference for MergeRequestsController#pipelines.
|
||||
- Drop feature to take ownership of trigger token.
|
||||
|
||||
|
||||
## 12.1.1
|
||||
|
||||
- No changes.
|
||||
|
||||
## 12.1.0
|
||||
|
||||
### Security (11 changes, 2 of them are from the community)
|
||||
|
||||
- Update tar to 2.2.2. !29949 (Takuya Noguchi)
|
||||
- Update lodash to 4.7.14 and lodash.mergewith to 4.6.2. !30602 (Takuya Noguchi)
|
||||
- Correctly check permissions when creating snippet notes.
|
||||
- Gate MR head_pipeline behind read_pipeline ability.
|
||||
- Prevent Billion Laughs attack.
|
||||
- Add missing authorizations in GraphQL.
|
||||
- Fix Denial of Service for comments when rendering issues/MR comments.
|
||||
- Expose merge requests count based on user access.
|
||||
- Fix DoS vulnerability in color validation regex.
|
||||
- Prevent the detection of merge request templates by unauthorized users.
|
||||
- Persist tmp snippet uploads at users.
|
||||
|
||||
### Removed (7 changes)
|
||||
|
||||
- Disable Kubernetes credential passthrough for managed project-level clusters. !29262
|
||||
- Remove deprecated group routes. !29351
|
||||
- Remove support for creating non-RBAC kubernetes clusters. !29614
|
||||
- Remove Kubernetes service integration and Kubernetes service template from available deployment platforms. !29786
|
||||
- Remove MySQL support. !29790
|
||||
- Remove depreated /u/:username routing. !30044
|
||||
- Remove support for legacy pipeline triggers. !30133
|
||||
|
||||
### Fixed (84 changes, 14 of them are from the community)
|
||||
|
||||
- Update a user's routes after updating their name. !23272
|
||||
- Show poper panel when validation error occurs in admin settings panels. !25434
|
||||
- Expect bytes from Gitaly RPC GetRawChanges. !28164
|
||||
- Sanitize LDAP output in Rake tasks. !28427
|
||||
- Left align mr widget icons and text. !28561
|
||||
- Keep the empty folders in the tree. !29196
|
||||
- Fix incorrect emoji placement in commit diff discussion. !29445
|
||||
- Fix favicon path with uploads of object store. !29482 (Roger Meier)
|
||||
- Remove duplicate trailing +/- char in merge request discussions. !29518
|
||||
- Fix the signup form's username validation messages not displaying. !29678 (Jiaan Louw)
|
||||
- Fix broken environment selector and always display it on monitoring dashboard. !29705
|
||||
- Fix Container Scanning job timeout when using the kubernetes executor. !29706
|
||||
- Look for new branches more carefully. !29761
|
||||
- Fix nested lists unnecessary margin. !29775 (Kuba Kopeć)
|
||||
- Fix reports jobs timing out because of cache. !29780
|
||||
- Fix Double Border in Profile Page. !29784 (Yoginth <@yo>)
|
||||
- Remove minimum character limits for fuzzy searches when using a CTE. !29810
|
||||
- Set default sort method for dashboard projects list. !29830 (David Palubin)
|
||||
- Protect TeamCity builds from triggering when a branch has been deleted. And a MR-option. !29836 (Nikolay Novikov, Raphael Tweitmann)
|
||||
- Fix pipeline schedule does not run correctly when it's scheduled at the same time with the cron worker. !29848
|
||||
- Always shows author of created issue/started discussion/comment in HTML body and text of email. !29886 (Frank van Rest)
|
||||
- Build correct basenames for title search results. !29898
|
||||
- Resolve "500 error when forking via the web IDE button". !29909
|
||||
- Turn commit sha in monitor charts popover to link. !29914
|
||||
- Fix broken URLs for uploads with a plus in the filename. !29915
|
||||
- Retry fetching Kubernetes Secret#token (#63507). !29922
|
||||
- Enforce presence of pipeline when "Pipeline must succeed" project setting is enabled. !29926
|
||||
- Fix unresponsive reply button in discussions. !29936
|
||||
- Allow asynchronous rebase operations to be monitored. !29940
|
||||
- Resolve Avatar in Please sign in pattern too large. !29944
|
||||
- Persist the cluster a deployment was deployed to. !29960
|
||||
- Fix runner tags search dropdown being empty when there are tags. !29985
|
||||
- Display the correct amount of projects being migrated/rolled-back to Hashed Storage when specifying ranges. !29996
|
||||
- Resolve Environment details header border misaligned. !30011
|
||||
- Correct link to docs for External Dashboard. !30019
|
||||
- Fix Jupyter-Git integration. !30020 (Amit Rathi)
|
||||
- Update Mermaid to 8.1.0. !30036
|
||||
- Fix background migrations failing with unused replication slot. !30042
|
||||
- Disable Rails SQL query cache when applying service templates. !30060
|
||||
- Set higher TTL for write lock of trace to prevent concurrent archiving. !30064
|
||||
- Fix charts on Cluster health page. !30073
|
||||
- Display boards filter bar on mobile. !30120
|
||||
- Fix IDE editor not showing when switching back from preview. !30135
|
||||
- Support note position tracing on an image. !30158
|
||||
- Replace slugifyWithHyphens with improved slugify function. !30172 (Luke Ward)
|
||||
- 'Open' and 'Closed' issue board lists no longer display a redundant tooltip. !30187
|
||||
- Fix pipelines table to update without refreshing after action. !30190
|
||||
- Change ruby_process_start_time_seconds metric to unix timestamp instead of seconds from boot. !30195
|
||||
- Fix attachments using the wrong URLs in e-mails. !30197
|
||||
- Make sure UnicornSampler is started only in master process. !30215
|
||||
- Don't show image diff note on text file. !30221
|
||||
- Fix median counting for cycle analytics. !30229
|
||||
- In WebIDE allow adding new entries of the same name as deleted entry. !30239
|
||||
- Don't let logged out user do manual order. !30264
|
||||
- Skip spam check for task list updates. !30279
|
||||
- Make Housekeeping button do a full garbage collection. !30289
|
||||
- Removing an image should not output binary data. !30314
|
||||
- Fix spacing issues for toasts. !30345
|
||||
- Fix race in forbid_sidekiq_in_transactions.rb. !30359
|
||||
- Fixed back navigation for projects filter. !30373
|
||||
- Fix environments broken terminal. !30401
|
||||
- Fix invalid SSL certificate errors on Drone CI service. !30422
|
||||
- Fix subgroup url in search drop down. !30457
|
||||
- Make unicorn_workers to return meaningful results. !30506
|
||||
- Fix wrong URL when creating milestones from instance milestones dashboard. !30512
|
||||
- Fixed incorrect line wrap for assignee label in issues. !30523 (Marc Schwede)
|
||||
- Improves section header whitespace on the CI/CD Charts page. !30531
|
||||
- Prevent multiple confirmation modals from opening when deleting a repository. !30532
|
||||
- Aligns CI icon in Merge Request dashboard. !30558
|
||||
- Add text-secondary to controls in project list. !30567
|
||||
- Review Tools: Add large z-index to toolbar. !30583
|
||||
- Hide restricted and disallowed visibility radios. !30590
|
||||
- Resolve Label picker: Line break on long label titles. !30610
|
||||
- Fix a bug that prevented projects containing merge request diff comments from being imported. !30630
|
||||
- I fixed z index bug in diff page. !30657 (Faruk Can)
|
||||
- Allow client authentication method to be configured for OpenID Connect. !30683 (Vincent Fazio)
|
||||
- Fix commenting before discussions are loaded. !30724
|
||||
- Fix linebreak rendering in Mermaid flowcharts. !30730
|
||||
- Make httpclient respect system SSL configuration. !30749
|
||||
- Bump fog-aws to v3.5.2. !30803
|
||||
- API: Allow changing only ci_default_git_depth. !30888 (Mathieu Parent)
|
||||
- Search issuables by iids. (Riccardo Padovani)
|
||||
- Fix broken warnings while Editing Issues and Edit File on MR.
|
||||
- Make sure we are receiving the proper information on the MR Popover by updating the IID in the graphql query.
|
||||
|
||||
### Changed (39 changes, 8 of them are from the community)
|
||||
|
||||
- Improve group list UI. !26542
|
||||
- Backport and Docs for Paginate license management and add license search. !27602
|
||||
- Update merge requests section description text on project settings page. !27838
|
||||
- Knative version bump 0.5 -> 0.6. !28798 (Chris Baumbauer)
|
||||
- Add salesforce logo for salesforce SSO. !28857
|
||||
- Enforced requirements for UltraAuth users. !28941 (Kartikey Tanna)
|
||||
- Return 400 when deleting tags more often than once per hour. !29448
|
||||
- Add identity information to external authorization requests. !29461
|
||||
- Enable just-in-time Kubernetes resource creation for project-level clusters. !29515
|
||||
- renamed discussion to thread in merge-request and issue timeline. !29553 (Michel Engelen)
|
||||
- Changed HTTP Status Code for disabled repository on /branches and /commits to 404. !29585 (Sam Battalio)
|
||||
- Enable Git object pools. !29595 (jramsay)
|
||||
- Updated container registry to display error message when special characters in path. Documentation has also been updated. !29616
|
||||
- Allow developers to delete tags. !29668
|
||||
- Will not update issue timestamps when changing positions in a list. !29677
|
||||
- Include a link back to the MR for Visual Review feedback form. !29719
|
||||
- Improve discussion reply buttons layout and how jump to next discussion button appears. !29779
|
||||
- Renders a pre-release tag for releases. !29797
|
||||
- Migrate NULL values for users.private_profile column and update users API to reject null value for private_profile. !29888
|
||||
- Re-name files in Web IDE in a more natural way. !29948
|
||||
- Include events from subgroups in group's activity. !29953 (Fabian Schneider @fabsrc)
|
||||
- Upgrade to Gitaly v1.49.0. !29990
|
||||
- Remove group and instance clusters feature flag. !30124
|
||||
- Add support for creating random passwords in user creation API. !30138
|
||||
- Support CIDR notation in IP rate limiter. !30146
|
||||
- Add Redis call details in Peek performance bar. !30191
|
||||
- Create Knative role and binding with service account. !30235
|
||||
- Add cleanup migration for MR's multiple assignees. !30261
|
||||
- Updates PHP template to php:latest to ensure always targeting latest stable. !30319 (Paul Giberson)
|
||||
- Format `from` and `to` fields in JSON audit log. !30333
|
||||
- Upgrade to Gitaly v1.51.0. !30353
|
||||
- Modify cycle analytics on project level. !30356
|
||||
- Extract clair version as CLAIR_EXECUTABLE_VERSION variable and update clair executable from v8 to v11. !30396
|
||||
- Upgrade Rouge to 3.5.1. !30431
|
||||
- Move multiple issue boards to core. !30503
|
||||
- Upgrade to Gitaly v1.52.0. !30568
|
||||
- Upgrade to Gitaly v1.53.0. !30614
|
||||
- Open WebIDE in fork when user doesn't have access. !30642
|
||||
- Propagate python version variable. (Can Eldem)
|
||||
|
||||
### Performance (25 changes, 1 of them is from the community)
|
||||
|
||||
- Remove tooltip directive on project avatar image component. !29631 (George Tsiolis)
|
||||
- Use Rugged if we detect storage is NFS and we can access the disk. !29725
|
||||
- Add endpoint for fetching diverging commit counts. !29802
|
||||
- Cache feature flag names in Redis for a minute. !29816
|
||||
- Avoid storing backtraces from Bitbucket Cloud imports in the database. !29862
|
||||
- Remove import columns from projects table. !29863
|
||||
- Enable Gitaly ref name caching for discussions.json. !29951
|
||||
- Allow caching of negative FindCommit matches. !29952
|
||||
- Eliminate N+1 queries in Dashboard::TodosController. !29954
|
||||
- Memoize non-existent custom appearances. !29957
|
||||
- Add a separate endpoint for fetching MRs serialized as widgets. !29979
|
||||
- Use CTE to fetch clusters hierarchy in single query. !30063
|
||||
- Enable Gitaly ref caching for SearchController. !30105
|
||||
- Avoid loading pipeline status in search results. !30111
|
||||
- Improve performance of MergeRequestsController#ci_environment_status endpoint. !30224
|
||||
- Add a memory cache local to the thread to reduce Redis load. !30233
|
||||
- Cache Flipper persisted names directly to local memory storage. !30265
|
||||
- Limit amount of JUnit tests returned. !30274
|
||||
- Cache Flipper feature flags in L1 and L2 caches. !30276
|
||||
- Prevent amplification of ReactiveCachingWorker jobs upon failures. !30432
|
||||
- Allow ReactiveCaching to support nil value. !30456
|
||||
- Improve performance of fetching environments statuses. !30560
|
||||
- Do Redis lookup in batches in ActiveSession.sessions_from_ids. !30561
|
||||
- Remove catfile cache feature flag. !30750
|
||||
- Fix Gitaly auto-detection caching. !30954
|
||||
|
||||
### Added (46 changes, 12 of them are from the community)
|
||||
|
||||
- Document the negative commit message push rule for the API. !14004 (Maikel Vlasman)
|
||||
- Expose saml_provider_id in the users API. !14045
|
||||
- Improve Project API. !28327 (Mathieu Parent)
|
||||
- Remove Sentry from application settings. !28447 (Roger Meier)
|
||||
- Implement borderless discussion design with new reply field. !28580
|
||||
- Enable terminals for instance and group clusters. !28613
|
||||
- Resolve Multiple discussions per line in merge request diffs. !28748
|
||||
- Adds link to Grafana in Admin > Monitoring settings when grafana is enabled in config. !28937 (Romain Maneschi)
|
||||
- Bring Manual Ordering on Issue List. !29410
|
||||
- Added commit type to tree GraphQL response. !29412
|
||||
- New API for User Counts, updates on success of an MR the count on top and in other tabs. !29441
|
||||
- Add option to limit time tracking units to hours. !29469 (Jon Kolb)
|
||||
- Add confirmation for registry image deletion. !29505
|
||||
- Sync merge ref upon mergeability check. !29569
|
||||
- Show an Upcoming Status for Releases. !29577
|
||||
- Add order_by and sort params to list runner jobs api. !29629 (Sujay Patel)
|
||||
- Allow custom username for deploy tokens. !29639
|
||||
- Add a verified pill next to email addresses under the admin users section. !29669
|
||||
- Add rake task to clean orphan artifact files. !29681
|
||||
- Render GFM in GraphQL. !29700
|
||||
- Upgrade asciidoctor version to 2.0.10. !29741 (Rajendra Kadam)
|
||||
- Allow auto-completing scoped labels. !29749
|
||||
- Enable syntax highlighting for AsciiDoc. !29835 (Guillaume Grossetie)
|
||||
- Expose placeholder element for metrics charts in GFM. !29861
|
||||
- Added a min schema version check to db:migrate. !29882
|
||||
- Extract zoom link from issue and pass to frontend. !29910 (raju249)
|
||||
- GraphQL mutations for add, remove and toggle emoji. !29919
|
||||
- Labeled issue boards can now collapse. !29955
|
||||
- Allow Ingress to be uninstalled from the UI. !29977
|
||||
- Add permission check to metrics dashboards endpoint. !30017
|
||||
- Allow JupyterHub to be uninstalled from the UI. !30097
|
||||
- Allow GitLab Runner to be uninstalled from the UI. !30176
|
||||
- GraphQL mutations for managing Notes. !30210
|
||||
- Add API for CRUD group clusters. !30213
|
||||
- Add endpoint to move multiple issues in boards. !30216
|
||||
- Enable terminals button for group clusters. !30255
|
||||
- Prevent excessive sanitization of AsciiDoc ouptut. !30290 (Guillaume Grossetie)
|
||||
- Extend `MergeToRefService` to create merge ref from an arbitrary ref. !30361
|
||||
- Add CI variable to provide GitLab HOST. !30417
|
||||
- Add migration for adding rule_type to approval_project_rules. !30575
|
||||
- Enable section anchors in Asciidoctor. !30666 (Guillaume Grossetie)
|
||||
- Preserve footnote link ids in Asciidoctor. !30790 (Guillaume Grossetie)
|
||||
- Add support for generating SSL certificates for custon pages domains through Let's Encrypt.
|
||||
- Introduce default: for gitlab-ci.yml.
|
||||
- Move Multiple Issue Boards for Projects to Core.
|
||||
- Add Gitaly data to the usage ping.
|
||||
|
||||
### Other (35 changes, 15 of them are from the community)
|
||||
|
||||
- Remove unresolved class and fixed height in discussion header. !28440 (David Palubin)
|
||||
- Moved EE/CE code differences for file `app/views/search/_category.html.haml` into CE. !28755 (Michel Engelen)
|
||||
- Changes "Todo" to "To Do" in the UI for clarity. !28844
|
||||
- Migrate GitLab managed project-level clusters to unmanaged if a Kubernetes namespace was unable to be created. !29251
|
||||
- Migrate GitLab managed project-level clusters to unmanaged if they are missing a Kubernetes service account token. !29648
|
||||
- Add strategies column to operations_feature_flag_scopes table. !29808
|
||||
- Disallow `NULL` values for `geo_nodes.primary` column. !29818 (Arun Kumar Mohan)
|
||||
- Replace 'JIRA' with 'Jira'. !29849 (Takuya Noguchi)
|
||||
- Support jsonb default in add_column_with_default migration helper. !29871
|
||||
- Update pagination prev and next texts. !29911
|
||||
- Adds metrics to measure cost of expensive operations. !29928
|
||||
- Always allow access to health endpoints from localhost in dev. !29930
|
||||
- Update GitLab Runner Helm Chart to 0.6.0. !29982
|
||||
- Use darker gray color for system note metadata and edited text. !30054
|
||||
- Fix typo in docs about Elasticsearch. !30162 (Takuya Noguchi)
|
||||
- Fix typo in code comments about Elasticsearch. !30163 (Takuya Noguchi)
|
||||
- Update mixin-deep to 1.3.2. !30223 (Takuya Noguchi)
|
||||
- Migrate markdown header_spec.js to Jest. !30228 (Martin Hobert)
|
||||
- Remove istanbul JavaScript package. !30232 (Takuya Noguchi)
|
||||
- Centralize markdownlint configuration. !30263
|
||||
- Use PostgreSQL 9.6.11 in CI tests. !30270 (Takuya Noguchi)
|
||||
- Fix typo in updateResolvableDiscussionsCounts action. !30278 (Frank van Rest)
|
||||
- Change color for namespace in commit search. !30312
|
||||
- Remove applySuggestion from notes service. !30399 (Frank van Rest)
|
||||
- Improved readability of storage statistics in group / project admin area. !30406
|
||||
- Alignign empty container registry message with design guidelines. !30502
|
||||
- Remove toggleAward from notes service. !30536 (Frank van Rest)
|
||||
- Remove deleteNote from notes service. !30537 (Frank van Rest)
|
||||
- change the use of boardService in favor of boardsStore on footer for the board component. !30616 (eduarmreyes)
|
||||
- Update example Prometheus scrape config. !30739
|
||||
- Update GitLab Pages to v1.7.0.
|
||||
- Add token_encrypted column to operations_feature_flags_clients table.
|
||||
- Removes EE diff for app/views/profiles/preferences/show.html.haml.
|
||||
- Removes EE differences for app/views/layouts/fullscreen.html.haml.
|
||||
- Removes EE differences for app/views/admin/users/show.html.haml.
|
||||
|
||||
|
||||
## 12.0.3 (2019-06-27)
|
||||
|
||||
- No changes.
|
||||
|
@ -415,6 +759,15 @@ entry.
|
|||
- Moves snowplow to CE repo.
|
||||
|
||||
|
||||
## 11.11.4 (2019-06-26)
|
||||
|
||||
### Fixed (3 changes)
|
||||
|
||||
- Fix Fogbugz Importer not working. !29383
|
||||
- Fix scrolling to top on assignee change. !29500
|
||||
- Fix IDE commit using latest ref in branch and overriding contents. !29769
|
||||
|
||||
|
||||
## 11.11.3 (2019-06-10)
|
||||
|
||||
### Fixed (5 changes)
|
||||
|
@ -628,6 +981,27 @@ entry.
|
|||
- Add some frozen string to spec/**/*.rb. (gfyoung)
|
||||
|
||||
|
||||
## 11.10.8 (2019-06-27)
|
||||
|
||||
- No changes.
|
||||
### Security (10 changes)
|
||||
|
||||
- Fix Denial of Service for comments when rendering issues/MR comments.
|
||||
- Gate MR head_pipeline behind read_pipeline ability.
|
||||
- Fix DoS vulnerability in color validation regex.
|
||||
- Expose merge requests count based on user access.
|
||||
- Persist tmp snippet uploads at users.
|
||||
- Add missing authorizations in GraphQL.
|
||||
- Disable Rails SQL query cache when applying service templates.
|
||||
- Prevent Billion Laughs attack.
|
||||
- Correctly check permissions when creating snippet notes.
|
||||
- Prevent the detection of merge request templates by unauthorized users.
|
||||
|
||||
### Performance (1 change)
|
||||
|
||||
- Add improvements to global search of issues and merge requests. !27817
|
||||
|
||||
|
||||
## 11.10.6 (2019-06-04)
|
||||
|
||||
### Fixed (7 changes, 1 of them is from the community)
|
||||
|
|
|
@ -19,4 +19,5 @@ unless helper.release_automation?
|
|||
danger.import_dangerfile(path: 'danger/single_codebase')
|
||||
danger.import_dangerfile(path: 'danger/gitlab_ui_wg')
|
||||
danger.import_dangerfile(path: 'danger/ce_ee_vue_templates')
|
||||
danger.import_dangerfile(path: 'danger/only_documentation')
|
||||
end
|
||||
|
|
|
@ -1 +1 @@
|
|||
1.47.3
|
||||
1.53.4
|
||||
|
|
|
@ -1 +1 @@
|
|||
1.6.3
|
||||
1.7.2
|
||||
|
|
28
Gemfile
28
Gemfile
|
@ -1,6 +1,6 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'rails', '5.1.7'
|
||||
gem 'rails', '5.2.3'
|
||||
|
||||
# Improves copy-on-write performance for MRI
|
||||
gem 'nakayoshi_fork', '~> 0.0.4'
|
||||
|
@ -11,7 +11,7 @@ gem 'responders', '~> 2.0'
|
|||
gem 'sprockets', '~> 3.7.0'
|
||||
|
||||
# Default values for AR models
|
||||
gem 'gitlab-default_value_for', '~> 3.1.1', require: 'default_value_for'
|
||||
gem 'default_value_for', '~> 3.2.0'
|
||||
|
||||
# Supported DBs
|
||||
gem 'mysql2', '~> 0.4.10', group: :mysql
|
||||
|
@ -84,6 +84,7 @@ gem 'rack-cors', '~> 1.0.0', require: 'rack/cors'
|
|||
gem 'graphql', '~> 1.8.0'
|
||||
gem 'graphiql-rails', '~> 1.4.10'
|
||||
gem 'apollo_upload_server', '~> 2.0.0.beta3'
|
||||
gem 'graphql-docs', '~> 1.6.0', group: [:development, :test]
|
||||
|
||||
# Disable strong_params so that Mash does not respond to :permitted?
|
||||
gem 'hashie-forbidden_attributes'
|
||||
|
@ -99,7 +100,7 @@ gem 'carrierwave', '~> 1.3'
|
|||
gem 'mini_magick'
|
||||
|
||||
# for backups
|
||||
gem 'fog-aws', '~> 3.3'
|
||||
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'
|
||||
|
@ -129,10 +130,10 @@ gem 'rdoc', '~> 6.0'
|
|||
gem 'org-ruby', '~> 0.9.12'
|
||||
gem 'creole', '~> 0.5.0'
|
||||
gem 'wikicloth', '0.8.1'
|
||||
gem 'asciidoctor', '~> 1.5.8'
|
||||
gem 'asciidoctor', '~> 2.0.10'
|
||||
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
|
||||
gem 'asciidoctor-plantuml', '0.0.8'
|
||||
gem 'rouge', '~> 3.1'
|
||||
gem 'asciidoctor-plantuml', '0.0.9'
|
||||
gem 'rouge', '~> 3.5'
|
||||
gem 'truncato', '~> 0.7.11'
|
||||
gem 'bootstrap_form', '~> 4.2.0'
|
||||
gem 'nokogiri', '~> 1.10.3'
|
||||
|
@ -211,7 +212,7 @@ gem 'discordrb-webhooks-blackst0ne', '~> 3.3', require: false
|
|||
# HipChat integration
|
||||
gem 'hipchat', '~> 1.5.0'
|
||||
|
||||
# JIRA integration
|
||||
# Jira integration
|
||||
gem 'jira-ruby', '~> 1.4'
|
||||
|
||||
# Flowdock integration
|
||||
|
@ -300,13 +301,16 @@ gem 'peek-pg', '~> 1.3.0', group: :postgres
|
|||
gem 'peek-rblineprof', '~> 0.2.0'
|
||||
gem 'peek-redis', '~> 1.2.0'
|
||||
|
||||
# Memory benchmarks
|
||||
gem 'derailed_benchmarks', require: false
|
||||
|
||||
# Metrics
|
||||
group :metrics do
|
||||
gem 'method_source', '~> 0.8', require: false
|
||||
gem 'influxdb', '~> 0.2', require: false
|
||||
|
||||
# Prometheus
|
||||
gem 'prometheus-client-mmap', '~> 0.9.4'
|
||||
gem 'prometheus-client-mmap', '~> 0.9.8'
|
||||
gem 'raindrops', '~> 0.18'
|
||||
end
|
||||
|
||||
|
@ -336,7 +340,7 @@ group :development, :test do
|
|||
|
||||
gem 'database_cleaner', '~> 1.7.0'
|
||||
gem 'factory_bot_rails', '~> 4.8.2'
|
||||
gem 'rspec-rails', '~> 3.7.0'
|
||||
gem 'rspec-rails', '~> 3.8.0'
|
||||
gem 'rspec-retry', '~> 0.6.1'
|
||||
gem 'rspec_profiling', '~> 0.0.5'
|
||||
gem 'rspec-set', '~> 0.1.3'
|
||||
|
@ -365,6 +369,7 @@ group :development, :test do
|
|||
gem 'haml_lint', '~> 0.31.0', require: false
|
||||
gem 'simplecov', '~> 0.16.1', require: false
|
||||
gem 'bundler-audit', '~> 0.5.0', require: false
|
||||
gem 'mdl', '~> 0.5.0', require: false
|
||||
|
||||
gem 'benchmark-ips', '~> 2.3.0', require: false
|
||||
|
||||
|
@ -374,7 +379,6 @@ group :development, :test do
|
|||
gem 'activerecord_sane_schema_dumper', '1.0'
|
||||
|
||||
gem 'stackprof', '~> 0.2.10', require: false
|
||||
gem 'derailed_benchmarks', require: false
|
||||
|
||||
gem 'simple_po_parser', '~> 1.1.2', require: false
|
||||
|
||||
|
@ -417,7 +421,7 @@ gem 'vmstat', '~> 2.3.0'
|
|||
gem 'sys-filesystem', '~> 1.1.6'
|
||||
|
||||
# SSH host key support
|
||||
gem 'net-ssh', '~> 5.0'
|
||||
gem 'net-ssh', '~> 5.2'
|
||||
gem 'sshkey', '~> 2.0'
|
||||
|
||||
# Required for ED25519 SSH host key support
|
||||
|
@ -427,7 +431,7 @@ group :ed25519 do
|
|||
end
|
||||
|
||||
# Gitaly GRPC client
|
||||
gem 'gitaly-proto', '~> 1.32.0', require: 'gitaly'
|
||||
gem 'gitaly-proto', '~> 1.37.0', require: 'gitaly'
|
||||
|
||||
gem 'grpc', '~> 1.19.0'
|
||||
|
||||
|
|
175
Gemfile.lock
175
Gemfile.lock
|
@ -6,44 +6,48 @@ GEM
|
|||
ace-rails-ap (4.1.2)
|
||||
acme-client (2.0.2)
|
||||
faraday (~> 0.9, >= 0.9.1)
|
||||
actioncable (5.1.7)
|
||||
actionpack (= 5.1.7)
|
||||
actioncable (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (~> 0.6.1)
|
||||
actionmailer (5.1.7)
|
||||
actionpack (= 5.1.7)
|
||||
actionview (= 5.1.7)
|
||||
activejob (= 5.1.7)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailer (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activejob (= 5.2.3)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.1.7)
|
||||
actionview (= 5.1.7)
|
||||
activesupport (= 5.1.7)
|
||||
actionpack (5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
rack (~> 2.0)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.1.7)
|
||||
activesupport (= 5.1.7)
|
||||
actionview (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
activejob (5.1.7)
|
||||
activesupport (= 5.1.7)
|
||||
activejob (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.1.7)
|
||||
activesupport (= 5.1.7)
|
||||
activerecord (5.1.7)
|
||||
activemodel (= 5.1.7)
|
||||
activesupport (= 5.1.7)
|
||||
arel (~> 8.0)
|
||||
activemodel (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
activerecord (5.2.3)
|
||||
activemodel (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
arel (>= 9.0)
|
||||
activerecord-explain-analyze (0.1.0)
|
||||
activerecord (>= 4)
|
||||
pg
|
||||
activerecord_sane_schema_dumper (1.0)
|
||||
rails (>= 5, < 6)
|
||||
activesupport (5.1.7)
|
||||
activestorage (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
activerecord (= 5.2.3)
|
||||
marcel (~> 0.3.1)
|
||||
activesupport (5.2.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
|
@ -60,17 +64,17 @@ GEM
|
|||
apollo_upload_server (2.0.0.beta.3)
|
||||
graphql (>= 1.8)
|
||||
rails (>= 4.2)
|
||||
arel (8.0.0)
|
||||
arel (9.0.0)
|
||||
asana (0.8.1)
|
||||
faraday (~> 0.9)
|
||||
faraday_middleware (~> 0.9)
|
||||
faraday_middleware-multi_json (~> 0.0)
|
||||
oauth2 (~> 1.0)
|
||||
asciidoctor (1.5.8)
|
||||
asciidoctor (2.0.10)
|
||||
asciidoctor-include-ext (0.3.1)
|
||||
asciidoctor (>= 1.5.6, < 3.0.0)
|
||||
asciidoctor-plantuml (0.0.8)
|
||||
asciidoctor (~> 1.5)
|
||||
asciidoctor-plantuml (0.0.9)
|
||||
asciidoctor (>= 1.5.6, < 3.0.0)
|
||||
ast (2.4.0)
|
||||
atomic (1.1.99)
|
||||
attr_encrypted (3.1.0)
|
||||
|
@ -163,6 +167,8 @@ GEM
|
|||
html-pipeline
|
||||
declarative (0.0.10)
|
||||
declarative-option (0.1.0)
|
||||
default_value_for (3.2.0)
|
||||
activerecord (>= 3.2.0, < 6.0)
|
||||
derailed_benchmarks (1.3.5)
|
||||
benchmark-ips (~> 2)
|
||||
get_process_mem (~> 0)
|
||||
|
@ -214,6 +220,8 @@ GEM
|
|||
excon (0.62.0)
|
||||
execjs (2.6.0)
|
||||
expression_parser (0.9.0)
|
||||
extended-markdown-filter (0.6.0)
|
||||
html-pipeline (~> 2.0)
|
||||
factory_bot (4.8.2)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_bot_rails (4.8.2)
|
||||
|
@ -245,7 +253,7 @@ GEM
|
|||
fog-json
|
||||
ipaddress (~> 0.8)
|
||||
xml-simple (~> 1.1)
|
||||
fog-aws (3.3.0)
|
||||
fog-aws (3.5.2)
|
||||
fog-core (~> 2.1)
|
||||
fog-json (~> 1.1)
|
||||
fog-xml (~> 0.1)
|
||||
|
@ -288,6 +296,7 @@ GEM
|
|||
fuubar (2.2.0)
|
||||
rspec-core (~> 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
gemoji (3.0.1)
|
||||
gemojione (3.3.0)
|
||||
json
|
||||
get_process_mem (0.2.3)
|
||||
|
@ -301,11 +310,9 @@ GEM
|
|||
gettext_i18n_rails (>= 0.7.1)
|
||||
po_to_json (>= 1.0.0)
|
||||
rails (>= 3.2.0)
|
||||
gitaly-proto (1.32.0)
|
||||
gitaly-proto (1.37.0)
|
||||
grpc (~> 1.0)
|
||||
github-markup (1.7.0)
|
||||
gitlab-default_value_for (3.1.1)
|
||||
activerecord (>= 3.2.0, < 6.0)
|
||||
gitlab-labkit (0.3.0)
|
||||
actionpack (~> 5)
|
||||
activesupport (~> 5)
|
||||
|
@ -370,6 +377,14 @@ GEM
|
|||
railties
|
||||
sprockets-rails
|
||||
graphql (1.8.1)
|
||||
graphql-docs (1.6.0)
|
||||
commonmarker (~> 0.16)
|
||||
escape_utils (~> 1.2)
|
||||
extended-markdown-filter (~> 0.4)
|
||||
gemoji (~> 3.0)
|
||||
graphql (~> 1.6)
|
||||
html-pipeline (~> 2.8)
|
||||
sass (~> 3.4)
|
||||
grpc (1.19.0)
|
||||
google-protobuf (~> 3.1)
|
||||
googleapis-common-protos-types (~> 1.0.0)
|
||||
|
@ -459,6 +474,7 @@ GEM
|
|||
kgio (2.11.2)
|
||||
knapsack (1.17.0)
|
||||
rake
|
||||
kramdown (1.17.0)
|
||||
kubeclient (4.2.2)
|
||||
http (~> 3.0)
|
||||
recursive-open-struct (~> 1.0, >= 1.0.4)
|
||||
|
@ -492,6 +508,12 @@ GEM
|
|||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
mail_room (0.9.1)
|
||||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
mdl (0.5.0)
|
||||
kramdown (~> 1.12, >= 1.12.0)
|
||||
mixlib-cli (~> 1.7, >= 1.7.0)
|
||||
mixlib-config (~> 2.2, >= 2.2.1)
|
||||
memoist (0.16.0)
|
||||
memoizable (0.4.2)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
|
@ -505,6 +527,9 @@ GEM
|
|||
mini_mime (1.0.1)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.11.3)
|
||||
mixlib-cli (1.7.0)
|
||||
mixlib-config (2.2.18)
|
||||
tomlrb
|
||||
msgpack (1.2.10)
|
||||
multi_json (1.13.1)
|
||||
multi_xml (0.6.0)
|
||||
|
@ -515,7 +540,7 @@ GEM
|
|||
mysql2 (0.4.10)
|
||||
nakayoshi_fork (0.0.4)
|
||||
net-ldap (0.16.0)
|
||||
net-ssh (5.0.1)
|
||||
net-ssh (5.2.0)
|
||||
netrc (0.11.0)
|
||||
nio4r (2.3.1)
|
||||
nokogiri (1.10.3)
|
||||
|
@ -652,7 +677,7 @@ GEM
|
|||
parser
|
||||
unparser
|
||||
procto (0.0.3)
|
||||
prometheus-client-mmap (0.9.4)
|
||||
prometheus-client-mmap (0.9.8)
|
||||
pry (0.11.3)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
|
@ -687,17 +712,18 @@ GEM
|
|||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-timeout (0.5.1)
|
||||
rails (5.1.7)
|
||||
actioncable (= 5.1.7)
|
||||
actionmailer (= 5.1.7)
|
||||
actionpack (= 5.1.7)
|
||||
actionview (= 5.1.7)
|
||||
activejob (= 5.1.7)
|
||||
activemodel (= 5.1.7)
|
||||
activerecord (= 5.1.7)
|
||||
activesupport (= 5.1.7)
|
||||
rails (5.2.3)
|
||||
actioncable (= 5.2.3)
|
||||
actionmailer (= 5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activejob (= 5.2.3)
|
||||
activemodel (= 5.2.3)
|
||||
activerecord (= 5.2.3)
|
||||
activestorage (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 5.1.7)
|
||||
railties (= 5.2.3)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.2)
|
||||
actionpack (~> 5.x, >= 5.0.1)
|
||||
|
@ -711,12 +737,12 @@ GEM
|
|||
rails-i18n (5.1.1)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 5.0, < 6)
|
||||
railties (5.1.7)
|
||||
actionpack (= 5.1.7)
|
||||
activesupport (= 5.1.7)
|
||||
railties (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
thor (>= 0.19.0, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
raindrops (0.19.0)
|
||||
rake (12.3.2)
|
||||
|
@ -770,41 +796,41 @@ GEM
|
|||
retriable (3.1.2)
|
||||
rinku (2.0.0)
|
||||
rotp (2.1.2)
|
||||
rouge (3.3.0)
|
||||
rouge (3.5.1)
|
||||
rqrcode (0.7.0)
|
||||
chunky_png
|
||||
rqrcode-rails3 (0.1.7)
|
||||
rqrcode (>= 0.4.2)
|
||||
rspec (3.7.0)
|
||||
rspec-core (~> 3.7.0)
|
||||
rspec-expectations (~> 3.7.0)
|
||||
rspec-mocks (~> 3.7.0)
|
||||
rspec-core (3.7.1)
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-expectations (3.7.0)
|
||||
rspec (3.8.0)
|
||||
rspec-core (~> 3.8.0)
|
||||
rspec-expectations (~> 3.8.0)
|
||||
rspec-mocks (~> 3.8.0)
|
||||
rspec-core (3.8.2)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-expectations (3.8.4)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-mocks (3.7.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-mocks (3.8.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-parameterized (0.4.2)
|
||||
binding_ninja (>= 0.2.3)
|
||||
parser
|
||||
proc_to_ast
|
||||
rspec (>= 2.13, < 4)
|
||||
unparser
|
||||
rspec-rails (3.7.2)
|
||||
rspec-rails (3.8.2)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 3.7.0)
|
||||
rspec-expectations (~> 3.7.0)
|
||||
rspec-mocks (~> 3.7.0)
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-core (~> 3.8.0)
|
||||
rspec-expectations (~> 3.8.0)
|
||||
rspec-mocks (~> 3.8.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-retry (0.6.1)
|
||||
rspec-core (> 3.3)
|
||||
rspec-set (0.1.3)
|
||||
rspec-support (3.7.1)
|
||||
rspec-support (3.8.2)
|
||||
rspec_junit_formatter (0.4.1)
|
||||
rspec-core (>= 2, < 4, != 2.12.0)
|
||||
rspec_profiling (0.0.5)
|
||||
|
@ -943,6 +969,7 @@ GEM
|
|||
parslet (~> 1.8.0)
|
||||
toml-rb (1.0.0)
|
||||
citrus (~> 3.0, > 3.0)
|
||||
tomlrb (1.2.8)
|
||||
truncato (0.7.11)
|
||||
htmlentities (~> 4.3.1)
|
||||
nokogiri (>= 1.7.0, <= 2.0)
|
||||
|
@ -999,7 +1026,7 @@ GEM
|
|||
hashdiff
|
||||
webpack-rails (0.9.11)
|
||||
railties (>= 3.2.0)
|
||||
websocket-driver (0.6.5)
|
||||
websocket-driver (0.7.0)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.3)
|
||||
wikicloth (0.8.1)
|
||||
|
@ -1025,9 +1052,9 @@ DEPENDENCIES
|
|||
akismet (~> 2.0)
|
||||
apollo_upload_server (~> 2.0.0.beta3)
|
||||
asana (~> 0.8.1)
|
||||
asciidoctor (~> 1.5.8)
|
||||
asciidoctor (~> 2.0.10)
|
||||
asciidoctor-include-ext (~> 0.3.1)
|
||||
asciidoctor-plantuml (= 0.0.8)
|
||||
asciidoctor-plantuml (= 0.0.9)
|
||||
attr_encrypted (~> 3.1.0)
|
||||
awesome_print
|
||||
babosa (~> 1.0.2)
|
||||
|
@ -1056,6 +1083,7 @@ DEPENDENCIES
|
|||
creole (~> 0.5.0)
|
||||
database_cleaner (~> 1.7.0)
|
||||
deckar01-task_list (= 2.2.0)
|
||||
default_value_for (~> 3.2.0)
|
||||
derailed_benchmarks
|
||||
device_detector
|
||||
devise (~> 4.6)
|
||||
|
@ -1077,7 +1105,7 @@ DEPENDENCIES
|
|||
flipper-active_support_cache_store (~> 0.13.0)
|
||||
flowdock (~> 0.7)
|
||||
fog-aliyun (~> 0.3)
|
||||
fog-aws (~> 3.3)
|
||||
fog-aws (~> 3.5)
|
||||
fog-core (= 2.1.0)
|
||||
fog-google (~> 1.8)
|
||||
fog-local (~> 0.6)
|
||||
|
@ -1091,9 +1119,8 @@ DEPENDENCIES
|
|||
gettext (~> 3.2.2)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.3)
|
||||
gitaly-proto (~> 1.32.0)
|
||||
gitaly-proto (~> 1.37.0)
|
||||
github-markup (~> 1.7.0)
|
||||
gitlab-default_value_for (~> 3.1.1)
|
||||
gitlab-labkit (~> 0.3.0)
|
||||
gitlab-markup (~> 1.7.0)
|
||||
gitlab-sidekiq-fetcher (~> 0.4.0)
|
||||
|
@ -1109,6 +1136,7 @@ DEPENDENCIES
|
|||
grape_logging (~> 1.7)
|
||||
graphiql-rails (~> 1.4.10)
|
||||
graphql (~> 1.8.0)
|
||||
graphql-docs (~> 1.6.0)
|
||||
grpc (~> 1.19.0)
|
||||
haml_lint (~> 0.31.0)
|
||||
hamlit (~> 2.8.8)
|
||||
|
@ -1134,6 +1162,7 @@ DEPENDENCIES
|
|||
lograge (~> 0.5)
|
||||
loofah (~> 2.2)
|
||||
mail_room (~> 0.9.1)
|
||||
mdl (~> 0.5.0)
|
||||
memory_profiler (~> 0.9)
|
||||
method_source (~> 0.8)
|
||||
mimemagic (~> 0.3.2)
|
||||
|
@ -1142,7 +1171,7 @@ DEPENDENCIES
|
|||
mysql2 (~> 0.4.10)
|
||||
nakayoshi_fork (~> 0.0.4)
|
||||
net-ldap
|
||||
net-ssh (~> 5.0)
|
||||
net-ssh (~> 5.2)
|
||||
nokogiri (~> 1.10.3)
|
||||
oauth2 (~> 1.4)
|
||||
octokit (~> 4.9)
|
||||
|
@ -1173,7 +1202,7 @@ DEPENDENCIES
|
|||
peek-redis (~> 1.2.0)
|
||||
pg (~> 1.1)
|
||||
premailer-rails (~> 1.9.7)
|
||||
prometheus-client-mmap (~> 0.9.4)
|
||||
prometheus-client-mmap (~> 0.9.8)
|
||||
pry-byebug (~> 3.5.1)
|
||||
pry-rails (~> 0.3.4)
|
||||
puma (~> 3.12)
|
||||
|
@ -1184,7 +1213,7 @@ DEPENDENCIES
|
|||
rack-oauth2 (~> 1.9.3)
|
||||
rack-proxy (~> 0.6.0)
|
||||
rack-timeout
|
||||
rails (= 5.1.7)
|
||||
rails (= 5.2.3)
|
||||
rails-controller-testing
|
||||
rails-i18n (~> 5.1)
|
||||
rainbow (~> 3.0)
|
||||
|
@ -1199,10 +1228,10 @@ DEPENDENCIES
|
|||
redis-rails (~> 5.0.2)
|
||||
request_store (~> 1.3)
|
||||
responders (~> 2.0)
|
||||
rouge (~> 3.1)
|
||||
rouge (~> 3.5)
|
||||
rqrcode-rails3 (~> 0.1.7)
|
||||
rspec-parameterized
|
||||
rspec-rails (~> 3.7.0)
|
||||
rspec-rails (~> 3.8.0)
|
||||
rspec-retry (~> 0.6.1)
|
||||
rspec-set (~> 0.1.3)
|
||||
rspec_junit_formatter
|
||||
|
|
49
PROCESS.md
49
PROCESS.md
|
@ -84,44 +84,29 @@ star, smile, etc.). Some good tips about code reviews can be found in our
|
|||
|
||||
[Code Review Guidelines]: https://docs.gitlab.com/ce/development/code_review.html
|
||||
|
||||
## Feature flags
|
||||
|
||||
Overview and details of feature flag processes in development of GitLab itself is described in [feature flags process documentation](https://docs.gitlab.com/ee/development/feature_flags/process.html).
|
||||
|
||||
Guides on how to include feature flags in your backend/frontend code while developing GitLab are described in [developing with feature flags documentation](https://docs.gitlab.com/ee/development/feature_flags/developing.html).
|
||||
|
||||
Getting access and how to expose the feature to users is detailed in [controlling feature flags documentation](https://docs.gitlab.com/ee/development/feature_flags/controls.html).
|
||||
|
||||
## Feature proposals from the 22nd to the 1st
|
||||
|
||||
To allow the Product and Engineering teams time to discuss issues that will be placed into an upcoming milestone,
|
||||
Product Managers must have their proposal for that milestone ready by the 22nd of each month.
|
||||
|
||||
This proposal will be shared with Engineering for discussion, feedback, and planning.
|
||||
The plan for the upcoming milestone must be finalized by the 1st of the month, one week before kickoff on the 8th.
|
||||
|
||||
## Feature freeze on the 7th for the release on the 22nd
|
||||
|
||||
The feature freeze on the 7th has been discontinued. The [transition period overview](https://gitlab.com/gitlab-org/release/docs/blob/21cbd409dd5f157fe252f254f3e897f01908abe2/general/deploy/auto-deploy-transition.md#transition)
|
||||
The feature freeze on the 7th has been discontinued. [Transition period overview](https://gitlab.com/gitlab-org/release/docs/blob/21cbd409dd5f157fe252f254f3e897f01908abe2/general/deploy/auto-deploy-transition.md#transition)
|
||||
describes the change to this process. During the transition period, the only guarantee that
|
||||
a change will be included in the release on the 22nd is if the change has been
|
||||
deployed to GitLab.com prior to this date.
|
||||
|
||||
### Feature flags
|
||||
|
||||
Merge requests that make changes hidden behind a feature flag, or remove an
|
||||
existing feature flag because a feature is deemed stable, may be merged (and
|
||||
picked into the stable branches) up to the 19th of the month. Such merge
|
||||
requests should have the ~"feature flag" label assigned, and don't require a
|
||||
corresponding exception request to be created.
|
||||
|
||||
A level of common sense should be applied when deciding whether to have a feature
|
||||
behind a feature flag off or on by default.
|
||||
|
||||
The following guidelines can be applied to help make this decision:
|
||||
|
||||
* If the feature is not fully ready or functioning, the feature flag should be disabled by default.
|
||||
* If the feature is ready but there are concerns about performance or impact, the feature flag should be enabled by default, but
|
||||
disabled via chatops before deployment on GitLab.com environments. If the performance concern is confirmed, the final release should have the feature flag disabled by default.
|
||||
* In most other cases, the feature flag can be enabled by default.
|
||||
|
||||
For more information on rolling out changes using feature flags, read [through the documentation](https://docs.gitlab.com/ee/development/rolling_out_changes_using_feature_flags.html).
|
||||
|
||||
In order to build the final package and present the feature for self-hosted
|
||||
customers, the feature flag should be removed. This should happen before the
|
||||
22nd, ideally _at least_ 2 days before. That means MRs with feature
|
||||
flags being picked at the 19th would have quite a tight schedule, so picking
|
||||
these _earlier_ is preferable.
|
||||
|
||||
While rare, release managers may decide to reject picking a change into a stable
|
||||
branch, even when feature flags are used. This might be necessary if the changes
|
||||
are deemed problematic, too invasive, or there simply isn't enough time to
|
||||
properly test how the changes behave on GitLab.com.
|
||||
|
||||
### Between the 1st and the 7th
|
||||
|
||||
These types of merge requests for the upcoming release need special consideration:
|
||||
|
|
|
@ -15,7 +15,7 @@ To see how GitLab looks please see the [features page on our website](https://ab
|
|||
|
||||
- Manage Git repositories with fine grained access controls that keep your code secure
|
||||
- Perform code reviews and enhance collaboration with merge requests
|
||||
- Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications
|
||||
- Complete continuous integration (CI) and continuous deployment/delivery (CD) pipelines to build, test, and deploy your applications
|
||||
- Each project can also have an issue tracker, issue board, and a wiki
|
||||
- Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises
|
||||
- Completely free and open source (MIT Expat license)
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
12.0.9
|
||||
12.1.13
|
||||
|
|
BIN
app/assets/images/auth_buttons/salesforce_64.png
Normal file
BIN
app/assets/images/auth_buttons/salesforce_64.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
|
@ -12,6 +12,7 @@ const Api = {
|
|||
groupProjectsPath: '/api/:version/groups/:id/projects.json',
|
||||
projectsPath: '/api/:version/projects.json',
|
||||
projectPath: '/api/:version/projects/:id',
|
||||
forkedProjectsPath: '/api/:version/projects/:id/forks',
|
||||
projectLabelsPath: '/:namespace_path/:project_path/-/labels',
|
||||
projectMergeRequestsPath: '/api/:version/projects/:id/merge_requests',
|
||||
projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
|
||||
|
@ -23,6 +24,7 @@ const Api = {
|
|||
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
|
||||
projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key',
|
||||
projectTemplatesPath: '/api/:version/projects/:id/templates/:type',
|
||||
userCountsPath: '/api/:version/user_counts',
|
||||
usersPath: '/api/:version/users.json',
|
||||
userPath: '/api/:version/users/:id',
|
||||
userStatusPath: '/api/:version/users/:id/status',
|
||||
|
@ -113,6 +115,21 @@ const Api = {
|
|||
return axios.get(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all projects for a forked relationship to a specified project
|
||||
* @param {string} projectPath - Path or ID of a project
|
||||
* @param {Object} params - Get request parameters
|
||||
* @returns {Promise} - Request promise
|
||||
*/
|
||||
projectForks(projectPath, params) {
|
||||
const url = Api.buildUrl(Api.forkedProjectsPath).replace(
|
||||
':id',
|
||||
encodeURIComponent(projectPath),
|
||||
);
|
||||
|
||||
return axios.get(url, { params });
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all Merge Requests for a project, eventually filtering based on
|
||||
* supplied parameters
|
||||
|
@ -296,6 +313,11 @@ const Api = {
|
|||
});
|
||||
},
|
||||
|
||||
userCounts() {
|
||||
const url = Api.buildUrl(this.userCountsPath);
|
||||
return axios.get(url);
|
||||
},
|
||||
|
||||
userStatus(id, options) {
|
||||
const url = Api.buildUrl(this.userStatusPath).replace(':id', encodeURIComponent(id));
|
||||
return axios.get(url, {
|
||||
|
|
|
@ -102,7 +102,7 @@ class SafeMathRenderer {
|
|||
maxSize: 20,
|
||||
maxExpand: 20,
|
||||
});
|
||||
} catch {
|
||||
} catch (e) {
|
||||
// Don't show a flash for now because it would override an existing flash message
|
||||
el.textContent = s__('math|There was an error rendering this math block');
|
||||
// el.style.color = '#d00';
|
||||
|
|
|
@ -33,17 +33,21 @@ export default function renderMermaid($els) {
|
|||
flowchart: {
|
||||
htmlLabels: false,
|
||||
},
|
||||
securityLevel: 'strict',
|
||||
});
|
||||
|
||||
let renderedChars = 0;
|
||||
|
||||
$els.each((i, el) => {
|
||||
const source = el.textContent;
|
||||
// Mermaid doesn't like `<br />` tags, so collapse all like tags into `<br>`, which is parsed correctly.
|
||||
const source = el.textContent.replace(/<br\s*\/>/g, '<br>');
|
||||
|
||||
/**
|
||||
* Restrict the rendering to a certain amount of character to
|
||||
* prevent mermaidjs from hanging up the entire thread and
|
||||
* causing a DoS.
|
||||
*/
|
||||
if (source && source.length > MAX_CHAR_LIMIT) {
|
||||
if ((source && source.length > MAX_CHAR_LIMIT) || renderedChars > MAX_CHAR_LIMIT) {
|
||||
el.textContent = sprintf(
|
||||
__(
|
||||
'Cannot render the image. Maximum character count (%{charLimit}) has been exceeded.',
|
||||
|
@ -53,12 +57,21 @@ export default function renderMermaid($els) {
|
|||
return;
|
||||
}
|
||||
|
||||
renderedChars += source.length;
|
||||
// Remove any extra spans added by the backend syntax highlighting.
|
||||
Object.assign(el, { textContent: source });
|
||||
|
||||
mermaid.init(undefined, el, id => {
|
||||
const svg = document.getElementById(id);
|
||||
|
||||
// As of https://github.com/knsv/mermaid/commit/57b780a0d,
|
||||
// Mermaid will make two init callbacks:one to initialize the
|
||||
// flow charts, and another to initialize the Gannt charts.
|
||||
// Guard against an error caused by double initialization.
|
||||
if (svg.classList.contains('mermaid')) {
|
||||
return;
|
||||
}
|
||||
|
||||
svg.classList.add('mermaid');
|
||||
|
||||
// pre > code > svg
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
import Sortable from 'sortablejs';
|
||||
import Vue from 'vue';
|
||||
import { n__ } from '~/locale';
|
||||
import { n__, s__ } from '~/locale';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import Tooltip from '~/vue_shared/directives/tooltip';
|
||||
import AccessorUtilities from '../../lib/utils/accessor';
|
||||
|
@ -53,12 +54,19 @@ export default Vue.extend({
|
|||
const { issuesSize } = this.list;
|
||||
return `${n__('%d issue', '%d issues', issuesSize)}`;
|
||||
},
|
||||
caretTooltip() {
|
||||
return this.list.isExpanded ? s__('Boards|Collapse') : s__('Boards|Expand');
|
||||
},
|
||||
isNewIssueShown() {
|
||||
return (
|
||||
this.list.type === 'backlog' ||
|
||||
(!this.disabled && this.list.type !== 'closed' && this.list.type !== 'blank')
|
||||
);
|
||||
},
|
||||
uniqueKey() {
|
||||
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
|
||||
return `boards.${this.boardId}.${this.list.type}.${this.list.id}`;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
filter: {
|
||||
|
@ -72,31 +80,34 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
mounted() {
|
||||
this.sortableOptions = getBoardSortableDefaultOptions({
|
||||
const instance = this;
|
||||
|
||||
const sortableOptions = getBoardSortableDefaultOptions({
|
||||
disabled: this.disabled,
|
||||
group: 'boards',
|
||||
draggable: '.is-draggable',
|
||||
handle: '.js-board-handle',
|
||||
onEnd: e => {
|
||||
onEnd(e) {
|
||||
sortableEnd();
|
||||
|
||||
const sortable = this;
|
||||
|
||||
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
|
||||
const order = this.sortable.toArray();
|
||||
const order = sortable.toArray();
|
||||
const list = boardsStore.findList('id', parseInt(e.item.dataset.id, 10));
|
||||
|
||||
this.$nextTick(() => {
|
||||
instance.$nextTick(() => {
|
||||
boardsStore.moveList(list, order);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions);
|
||||
Sortable.create(this.$el.parentNode, sortableOptions);
|
||||
},
|
||||
created() {
|
||||
if (this.list.isExpandable && AccessorUtilities.isLocalStorageAccessSafe()) {
|
||||
const isCollapsed =
|
||||
localStorage.getItem(`boards.${this.boardId}.${this.list.type}.expanded`) === 'false';
|
||||
const isCollapsed = localStorage.getItem(`${this.uniqueKey}.expanded`) === 'false';
|
||||
|
||||
this.list.isExpanded = !isCollapsed;
|
||||
}
|
||||
|
@ -105,16 +116,17 @@ export default Vue.extend({
|
|||
showNewIssueForm() {
|
||||
this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
|
||||
},
|
||||
toggleExpanded(e) {
|
||||
if (this.list.isExpandable && !e.target.classList.contains('js-no-trigger-collapse')) {
|
||||
toggleExpanded() {
|
||||
if (this.list.isExpandable) {
|
||||
this.list.isExpanded = !this.list.isExpanded;
|
||||
|
||||
if (AccessorUtilities.isLocalStorageAccessSafe()) {
|
||||
localStorage.setItem(
|
||||
`boards.${this.boardId}.${this.list.type}.expanded`,
|
||||
this.list.isExpanded,
|
||||
);
|
||||
localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded);
|
||||
}
|
||||
|
||||
// When expanding/collapsing, the tooltip on the caret button sometimes stays open.
|
||||
// Close all tooltips manually to prevent dangling tooltips.
|
||||
$('.tooltip').tooltip('hide');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { __ } from '~/locale';
|
||||
/* global ListLabel */
|
||||
import Cookies from 'js-cookie';
|
||||
import boardsStore from '../stores/boards_store';
|
||||
|
@ -7,8 +8,8 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
predefinedLabels: [
|
||||
new ListLabel({ title: 'To Do', color: '#F0AD4E' }),
|
||||
new ListLabel({ title: 'Doing', color: '#5CB85C' }),
|
||||
new ListLabel({ title: __('To Do'), color: '#F0AD4E' }),
|
||||
new ListLabel({ title: __('Doing'), color: '#5CB85C' }),
|
||||
],
|
||||
};
|
||||
},
|
||||
|
@ -58,7 +59,11 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="board-blank-state p-3">
|
||||
<p>Add the following default lists to your Issue Board with one click:</p>
|
||||
<p>
|
||||
{{
|
||||
s__('BoardBlankState|Add the following default lists to your Issue Board with one click:')
|
||||
}}
|
||||
</p>
|
||||
<ul class="list-unstyled board-blank-state-list">
|
||||
<li v-for="(label, index) in predefinedLabels" :key="index">
|
||||
<span
|
||||
|
@ -70,18 +75,21 @@ export default {
|
|||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Starting out with the default set of lists will get you right on the way to making the most of
|
||||
your board.
|
||||
{{
|
||||
s__(
|
||||
'BoardBlankState|Starting out with the default set of lists will get you right on the way to making the most of your board.',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<button
|
||||
class="btn btn-success btn-inverted btn-block"
|
||||
type="button"
|
||||
@click.stop="addDefaultLists"
|
||||
>
|
||||
Add default lists
|
||||
{{ s__('BoardBlankState|Add default lists') }}
|
||||
</button>
|
||||
<button class="btn btn-default btn-block" type="button" @click.stop="clearBlankState">
|
||||
Nevermind, I'll use my own
|
||||
{{ s__("BoardBlankState|Nevermind, I'll use my own") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
216
app/assets/javascripts/boards/components/board_form.vue
Normal file
216
app/assets/javascripts/boards/components/board_form.vue
Normal file
|
@ -0,0 +1,216 @@
|
|||
<script>
|
||||
import Flash from '~/flash';
|
||||
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
import boardsStore from '~/boards/stores/boards_store';
|
||||
|
||||
const boardDefaults = {
|
||||
id: false,
|
||||
name: '',
|
||||
labels: [],
|
||||
milestone_id: undefined,
|
||||
assignee: {},
|
||||
assignee_id: undefined,
|
||||
weight: null,
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BoardScope: () => import('ee_component/boards/components/board_scope.vue'),
|
||||
DeprecatedModal,
|
||||
},
|
||||
props: {
|
||||
canAdminBoard: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
milestonePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labelsPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
scopedIssueBoardFeatureEnabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
projectId: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
groupId: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
weights: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
enableScopedLabels: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
scopedLabelsDocumentationLink: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '#',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
board: { ...boardDefaults, ...this.currentBoard },
|
||||
currentBoard: boardsStore.state.currentBoard,
|
||||
currentPage: boardsStore.state.currentPage,
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isNewForm() {
|
||||
return this.currentPage === 'new';
|
||||
},
|
||||
isDeleteForm() {
|
||||
return this.currentPage === 'delete';
|
||||
},
|
||||
isEditForm() {
|
||||
return this.currentPage === 'edit';
|
||||
},
|
||||
isVisible() {
|
||||
return this.currentPage !== '';
|
||||
},
|
||||
buttonText() {
|
||||
if (this.isNewForm) {
|
||||
return 'Create board';
|
||||
}
|
||||
if (this.isDeleteForm) {
|
||||
return 'Delete';
|
||||
}
|
||||
return 'Save changes';
|
||||
},
|
||||
buttonKind() {
|
||||
if (this.isNewForm) {
|
||||
return 'success';
|
||||
}
|
||||
if (this.isDeleteForm) {
|
||||
return 'danger';
|
||||
}
|
||||
return 'info';
|
||||
},
|
||||
title() {
|
||||
if (this.isNewForm) {
|
||||
return 'Create new board';
|
||||
}
|
||||
if (this.isDeleteForm) {
|
||||
return 'Delete board';
|
||||
}
|
||||
if (this.readonly) {
|
||||
return 'Board scope';
|
||||
}
|
||||
return 'Edit board';
|
||||
},
|
||||
readonly() {
|
||||
return !this.canAdminBoard;
|
||||
},
|
||||
submitDisabled() {
|
||||
return this.isLoading || this.board.name.length === 0;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.resetFormState();
|
||||
if (this.$refs.name) {
|
||||
this.$refs.name.focus();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
if (this.board.name.length === 0) return;
|
||||
this.isLoading = true;
|
||||
if (this.isDeleteForm) {
|
||||
gl.boardService
|
||||
.deleteBoard(this.currentBoard)
|
||||
.then(() => {
|
||||
visitUrl(boardsStore.rootPath);
|
||||
})
|
||||
.catch(() => {
|
||||
Flash('Failed to delete board. Please try again.');
|
||||
this.isLoading = false;
|
||||
});
|
||||
} else {
|
||||
gl.boardService
|
||||
.createBoard(this.board)
|
||||
.then(resp => resp.data)
|
||||
.then(data => {
|
||||
visitUrl(data.board_path);
|
||||
})
|
||||
.catch(() => {
|
||||
Flash('Unable to save your changes. Please try again.');
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
boardsStore.showPage('');
|
||||
},
|
||||
resetFormState() {
|
||||
if (this.isNewForm) {
|
||||
// Clear the form when we open the "New board" modal
|
||||
this.board = { ...boardDefaults };
|
||||
} else if (this.currentBoard && Object.keys(this.currentBoard).length) {
|
||||
this.board = { ...boardDefaults, ...this.currentBoard };
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<deprecated-modal
|
||||
v-show="isVisible"
|
||||
:hide-footer="readonly"
|
||||
:title="title"
|
||||
:primary-button-label="buttonText"
|
||||
:kind="buttonKind"
|
||||
:submit-disabled="submitDisabled"
|
||||
modal-dialog-class="board-config-modal"
|
||||
@cancel="cancel"
|
||||
@submit="submit"
|
||||
>
|
||||
<template slot="body">
|
||||
<p v-if="isDeleteForm">Are you sure you want to delete this board?</p>
|
||||
<form v-else class="js-board-config-modal" @submit.prevent>
|
||||
<div v-if="!readonly" class="append-bottom-20">
|
||||
<label class="form-section-title label-bold" for="board-new-name"> Board name </label>
|
||||
<input
|
||||
id="board-new-name"
|
||||
ref="name"
|
||||
v-model="board.name"
|
||||
class="form-control"
|
||||
type="text"
|
||||
placeholder="Enter board name"
|
||||
@keyup.enter="submit"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<board-scope
|
||||
v-if="scopedIssueBoardFeatureEnabled"
|
||||
:collapse-scope="isNewForm"
|
||||
:board="board"
|
||||
:can-admin-board="canAdminBoard"
|
||||
:milestone-path="milestonePath"
|
||||
:labels-path="labelsPath"
|
||||
:scoped-labels-documentation-link="scopedLabelsDocumentationLink"
|
||||
:enable-scoped-labels="enableScopedLabels"
|
||||
:project-id="projectId"
|
||||
:group-id="groupId"
|
||||
:weights="weights"
|
||||
/>
|
||||
</form>
|
||||
</template>
|
||||
</deprecated-modal>
|
||||
</template>
|
|
@ -227,7 +227,7 @@ export default {
|
|||
:class="{ 'd-none': !list.isExpanded, 'd-flex flex-column': list.isExpanded }"
|
||||
class="board-list-component position-relative h-100"
|
||||
>
|
||||
<div v-if="loading" class="board-list-loading text-center" aria-label="Loading issues">
|
||||
<div v-if="loading" class="board-list-loading text-center" :aria-label="__('Loading issues')">
|
||||
<gl-loading-icon />
|
||||
</div>
|
||||
<board-new-issue
|
||||
|
@ -257,7 +257,7 @@ export default {
|
|||
/>
|
||||
<li v-if="showCount" class="board-list-count text-center" data-issue-id="-1">
|
||||
<gl-loading-icon v-show="list.loadingMore" label="Loading more issues" />
|
||||
<span v-if="list.issues.length === list.issuesSize"> Showing all issues </span>
|
||||
<span v-if="list.issues.length === list.issuesSize">{{ __('Showing all issues') }}</span>
|
||||
<span v-else> Showing {{ list.issues.length }} of {{ list.issuesSize }} issues </span>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -102,9 +102,9 @@ export default {
|
|||
<div class="board-card position-relative p-3 rounded">
|
||||
<form @submit="submit($event)">
|
||||
<div v-if="error" class="flash-container">
|
||||
<div class="flash-alert">An error occurred. Please try again.</div>
|
||||
<div class="flash-alert">{{ __('An error occurred. Please try again.') }}</div>
|
||||
</div>
|
||||
<label :for="list.id + '-title'" class="label-bold"> Title </label>
|
||||
<label :for="list.id + '-title'" class="label-bold">{{ __('Title') }}</label>
|
||||
<input
|
||||
:id="list.id + '-title'"
|
||||
ref="input"
|
||||
|
@ -122,12 +122,11 @@ export default {
|
|||
class="float-left"
|
||||
variant="success"
|
||||
type="submit"
|
||||
>{{ __('Submit issue') }}</gl-button
|
||||
>
|
||||
Submit issue
|
||||
</gl-button>
|
||||
<gl-button class="float-right" type="button" variant="default" @click="cancel">
|
||||
Cancel
|
||||
</gl-button>
|
||||
<gl-button class="float-right" type="button" variant="default" @click="cancel">{{
|
||||
__('Cancel')
|
||||
}}</gl-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -38,6 +38,7 @@ export default Vue.extend({
|
|||
issue: {},
|
||||
list: {},
|
||||
loadingAssignees: false,
|
||||
timeTrackingLimitToHours: boardsStore.timeTracking.limitToHours,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
334
app/assets/javascripts/boards/components/boards_selector.vue
Normal file
334
app/assets/javascripts/boards/components/boards_selector.vue
Normal file
|
@ -0,0 +1,334 @@
|
|||
<script>
|
||||
import { throttle } from 'underscore';
|
||||
import {
|
||||
GlLoadingIcon,
|
||||
GlSearchBoxByType,
|
||||
GlDropdown,
|
||||
GlDropdownDivider,
|
||||
GlDropdownHeader,
|
||||
GlDropdownItem,
|
||||
} from '@gitlab/ui';
|
||||
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import httpStatusCodes from '~/lib/utils/http_status';
|
||||
import boardsStore from '../stores/boards_store';
|
||||
import BoardForm from './board_form.vue';
|
||||
|
||||
const MIN_BOARDS_TO_VIEW_RECENT = 10;
|
||||
|
||||
export default {
|
||||
name: 'BoardsSelector',
|
||||
components: {
|
||||
Icon,
|
||||
BoardForm,
|
||||
GlLoadingIcon,
|
||||
GlSearchBoxByType,
|
||||
GlDropdown,
|
||||
GlDropdownDivider,
|
||||
GlDropdownHeader,
|
||||
GlDropdownItem,
|
||||
},
|
||||
props: {
|
||||
currentBoard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
milestonePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
throttleDuration: {
|
||||
type: Number,
|
||||
default: 200,
|
||||
},
|
||||
boardBaseUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
hasMissingBoards: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canAdminBoard: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
multipleIssueBoardsAvailable: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
labelsPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
groupId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
scopedIssueBoardFeatureEnabled: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
weights: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
enabledScopedLabels: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
scopedLabelsDocumentationLink: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '#',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
hasScrollFade: false,
|
||||
scrollFadeInitialized: false,
|
||||
boards: [],
|
||||
recentBoards: [],
|
||||
state: boardsStore.state,
|
||||
throttledSetScrollFade: throttle(this.setScrollFade, this.throttleDuration),
|
||||
contentClientHeight: 0,
|
||||
maxPosition: 0,
|
||||
store: boardsStore,
|
||||
filterTerm: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentPage() {
|
||||
return this.state.currentPage;
|
||||
},
|
||||
filteredBoards() {
|
||||
return this.boards.filter(board =>
|
||||
board.name.toLowerCase().includes(this.filterTerm.toLowerCase()),
|
||||
);
|
||||
},
|
||||
reload: {
|
||||
get() {
|
||||
return this.state.reload;
|
||||
},
|
||||
set(newValue) {
|
||||
this.state.reload = newValue;
|
||||
},
|
||||
},
|
||||
board() {
|
||||
return this.state.currentBoard;
|
||||
},
|
||||
showDelete() {
|
||||
return this.boards.length > 1;
|
||||
},
|
||||
scrollFadeClass() {
|
||||
return {
|
||||
'fade-out': !this.hasScrollFade,
|
||||
};
|
||||
},
|
||||
showRecentSection() {
|
||||
return (
|
||||
this.recentBoards.length &&
|
||||
this.boards.length > MIN_BOARDS_TO_VIEW_RECENT &&
|
||||
!this.filterTerm.length
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
filteredBoards() {
|
||||
this.scrollFadeInitialized = false;
|
||||
this.$nextTick(this.setScrollFade);
|
||||
},
|
||||
reload() {
|
||||
if (this.reload) {
|
||||
this.boards = [];
|
||||
this.recentBoards = [];
|
||||
this.loading = true;
|
||||
this.reload = false;
|
||||
|
||||
this.loadBoards(false);
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
boardsStore.setCurrentBoard(this.currentBoard);
|
||||
},
|
||||
methods: {
|
||||
showPage(page) {
|
||||
boardsStore.showPage(page);
|
||||
},
|
||||
loadBoards(toggleDropdown = true) {
|
||||
if (toggleDropdown && this.boards.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const recentBoardsPromise = new Promise((resolve, reject) =>
|
||||
gl.boardService
|
||||
.recentBoards()
|
||||
.then(resolve)
|
||||
.catch(err => {
|
||||
/**
|
||||
* If user is unauthorized we'd still want to resolve the
|
||||
* request to display all boards.
|
||||
*/
|
||||
if (err.response.status === httpStatusCodes.UNAUTHORIZED) {
|
||||
resolve({ data: [] }); // recent boards are empty
|
||||
return;
|
||||
}
|
||||
reject(err);
|
||||
}),
|
||||
);
|
||||
|
||||
Promise.all([gl.boardService.allBoards(), recentBoardsPromise])
|
||||
.then(([allBoards, recentBoards]) => [allBoards.data, recentBoards.data])
|
||||
.then(([allBoardsJson, recentBoardsJson]) => {
|
||||
this.loading = false;
|
||||
this.boards = allBoardsJson;
|
||||
this.recentBoards = recentBoardsJson;
|
||||
})
|
||||
.then(() => this.$nextTick()) // Wait for boards list in DOM
|
||||
.then(() => {
|
||||
this.setScrollFade();
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
isScrolledUp() {
|
||||
const { content } = this.$refs;
|
||||
const currentPosition = this.contentClientHeight + content.scrollTop;
|
||||
|
||||
return content && currentPosition < this.maxPosition;
|
||||
},
|
||||
initScrollFade() {
|
||||
this.scrollFadeInitialized = true;
|
||||
|
||||
const { content } = this.$refs;
|
||||
|
||||
this.contentClientHeight = content.clientHeight;
|
||||
this.maxPosition = content.scrollHeight;
|
||||
},
|
||||
setScrollFade() {
|
||||
if (!this.scrollFadeInitialized) this.initScrollFade();
|
||||
|
||||
this.hasScrollFade = this.isScrolledUp();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="boards-switcher js-boards-selector append-right-10">
|
||||
<span class="boards-selector-wrapper js-boards-selector-wrapper">
|
||||
<gl-dropdown
|
||||
toggle-class="dropdown-menu-toggle js-dropdown-toggle"
|
||||
menu-class="flex-column dropdown-extended-height"
|
||||
:text="board.name"
|
||||
@show="loadBoards"
|
||||
>
|
||||
<div>
|
||||
<div class="dropdown-title mb-0" @mousedown.prevent>
|
||||
{{ s__('IssueBoards|Switch board') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<gl-dropdown-header class="mt-0">
|
||||
<gl-search-box-by-type ref="searchBox" v-model="filterTerm" />
|
||||
</gl-dropdown-header>
|
||||
|
||||
<div
|
||||
v-if="!loading"
|
||||
ref="content"
|
||||
class="dropdown-content flex-fill"
|
||||
@scroll.passive="throttledSetScrollFade"
|
||||
>
|
||||
<gl-dropdown-item
|
||||
v-show="filteredBoards.length === 0"
|
||||
class="no-pointer-events text-secondary"
|
||||
>
|
||||
{{ s__('IssueBoards|No matching boards found') }}
|
||||
</gl-dropdown-item>
|
||||
|
||||
<h6 v-if="showRecentSection" class="dropdown-bold-header my-0">
|
||||
{{ __('Recent') }}
|
||||
</h6>
|
||||
|
||||
<template v-if="showRecentSection">
|
||||
<gl-dropdown-item
|
||||
v-for="recentBoard in recentBoards"
|
||||
:key="`recent-${recentBoard.id}`"
|
||||
class="js-dropdown-item"
|
||||
:href="`${boardBaseUrl}/${recentBoard.id}`"
|
||||
>
|
||||
{{ recentBoard.name }}
|
||||
</gl-dropdown-item>
|
||||
</template>
|
||||
|
||||
<hr v-if="showRecentSection" class="my-1" />
|
||||
|
||||
<h6 v-if="showRecentSection" class="dropdown-bold-header my-0">
|
||||
{{ __('All') }}
|
||||
</h6>
|
||||
|
||||
<gl-dropdown-item
|
||||
v-for="otherBoard in filteredBoards"
|
||||
:key="otherBoard.id"
|
||||
class="js-dropdown-item"
|
||||
:href="`${boardBaseUrl}/${otherBoard.id}`"
|
||||
>
|
||||
{{ otherBoard.name }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item v-if="hasMissingBoards" class="small unclickable">
|
||||
{{
|
||||
s__(
|
||||
'IssueBoards|Some of your boards are hidden, activate a license to see them again.',
|
||||
)
|
||||
}}
|
||||
</gl-dropdown-item>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-show="filteredBoards.length > 0"
|
||||
class="dropdown-content-faded-mask"
|
||||
:class="scrollFadeClass"
|
||||
></div>
|
||||
|
||||
<gl-loading-icon v-if="loading" />
|
||||
|
||||
<div v-if="canAdminBoard">
|
||||
<gl-dropdown-divider />
|
||||
|
||||
<gl-dropdown-item v-if="multipleIssueBoardsAvailable" @click.prevent="showPage('new')">
|
||||
{{ s__('IssueBoards|Create new board') }}
|
||||
</gl-dropdown-item>
|
||||
|
||||
<gl-dropdown-item
|
||||
v-if="showDelete"
|
||||
class="text-danger"
|
||||
@click.prevent="showPage('delete')"
|
||||
>
|
||||
{{ s__('IssueBoards|Delete board') }}
|
||||
</gl-dropdown-item>
|
||||
</div>
|
||||
</gl-dropdown>
|
||||
|
||||
<board-form
|
||||
v-if="currentPage"
|
||||
:milestone-path="milestonePath"
|
||||
:labels-path="labelsPath"
|
||||
:project-id="projectId"
|
||||
:group-id="groupId"
|
||||
:can-admin-board="canAdminBoard"
|
||||
:scoped-issue-board-feature-enabled="scopedIssueBoardFeatureEnabled"
|
||||
:weights="weights"
|
||||
:enable-scoped-labels="enabledScopedLabels"
|
||||
:scoped-labels-documentation-link="scopedLabelsDocumentationLink"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
|
@ -124,7 +124,7 @@ export default {
|
|||
return `${this.rootPath}${assignee.username}`;
|
||||
},
|
||||
avatarUrlTitle(assignee) {
|
||||
return `Avatar for ${assignee.name}`;
|
||||
return sprintf(__(`Avatar for %{assigneeName}`), { assigneeName: assignee.name });
|
||||
},
|
||||
showLabel(label) {
|
||||
if (!label.id) return false;
|
||||
|
@ -160,9 +160,10 @@ export default {
|
|||
:title="__('Confidential')"
|
||||
class="confidential-icon append-right-4"
|
||||
:aria-label="__('Confidential')"
|
||||
/><a :href="issue.path" :title="issue.title" class="js-no-trigger" @mousemove.stop>{{
|
||||
issue.title
|
||||
}}</a>
|
||||
/>
|
||||
<a :href="issue.path" :title="issue.title" class="js-no-trigger" @mousemove.stop>
|
||||
{{ issue.title }}
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div v-if="showLabelFooter" class="board-card-labels prepend-top-4 d-flex flex-wrap">
|
||||
|
@ -204,13 +205,13 @@ export default {
|
|||
placement="bottom"
|
||||
class="board-issue-path block-truncated bold"
|
||||
>{{ issueReferencePath }}</tooltip-on-truncate
|
||||
>#{{ issue.iid }}
|
||||
>
|
||||
#{{ issue.iid }}
|
||||
</span>
|
||||
<span class="board-info-items prepend-top-8 d-inline-block">
|
||||
<issue-due-date v-if="issue.dueDate" :date="issue.dueDate" /><issue-time-estimate
|
||||
v-if="issue.timeEstimate"
|
||||
:estimate="issue.timeEstimate"
|
||||
/><issue-card-weight
|
||||
<issue-due-date v-if="issue.dueDate" :date="issue.dueDate" />
|
||||
<issue-time-estimate v-if="issue.timeEstimate" :estimate="issue.timeEstimate" />
|
||||
<issue-card-weight
|
||||
v-if="issue.weight"
|
||||
:weight="issue.weight"
|
||||
@click="filterByWeight(issue.weight)"
|
||||
|
@ -230,7 +231,8 @@ export default {
|
|||
tooltip-placement="bottom"
|
||||
>
|
||||
<span class="js-assignee-tooltip">
|
||||
<span class="bold d-block">Assignee</span> {{ assignee.name }}
|
||||
<span class="bold d-block">{{ __('Assignee') }}</span>
|
||||
{{ assignee.name }}
|
||||
<span class="text-white-50">@{{ assignee.username }}</span>
|
||||
</span>
|
||||
</user-avatar-link>
|
||||
|
@ -240,9 +242,8 @@ export default {
|
|||
:title="assigneeCounterTooltip"
|
||||
class="avatar-counter"
|
||||
data-placement="bottom"
|
||||
>{{ assigneeCounterLabel }}</span
|
||||
>
|
||||
{{ assigneeCounterLabel }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { GlTooltip } from '@gitlab/ui';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
|
||||
import boardsStore from '../stores/boards_store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -14,12 +15,17 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
limitToHours: boardsStore.timeTracking.limitToHours,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return stringifyTime(parseSeconds(this.estimate), true);
|
||||
return stringifyTime(parseSeconds(this.estimate, { limitToHours: this.limitToHours }), true);
|
||||
},
|
||||
timeEstimate() {
|
||||
return stringifyTime(parseSeconds(this.estimate));
|
||||
return stringifyTime(parseSeconds(this.estimate, { limitToHours: this.limitToHours }));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { __, sprintf } from '~/locale';
|
||||
import ModalStore from '../../stores/modal_store';
|
||||
import modalMixin from '../../mixins/modal_mixins';
|
||||
|
||||
|
@ -20,19 +21,20 @@ export default {
|
|||
computed: {
|
||||
contents() {
|
||||
const obj = {
|
||||
title: "You haven't added any issues to your project yet",
|
||||
content: `
|
||||
An issue can be a bug, a todo or a feature request that needs to be
|
||||
discussed in a project. Besides, issues are searchable and filterable.
|
||||
`,
|
||||
title: __("You haven't added any issues to your project yet"),
|
||||
content: __(
|
||||
'An issue can be a bug, a todo or a feature request that needs to be discussed in a project. Besides, issues are searchable and filterable.',
|
||||
),
|
||||
};
|
||||
|
||||
if (this.activeTab === 'selected') {
|
||||
obj.title = "You haven't selected any issues yet";
|
||||
obj.content = `
|
||||
Go back to <strong>Open issues</strong> and select some issues
|
||||
to add to your board.
|
||||
`;
|
||||
obj.title = __("You haven't selected any issues yet");
|
||||
obj.content = sprintf(
|
||||
__(
|
||||
'Go back to %{startTag}Open issues%{endTag} and select some issues to add to your board.',
|
||||
),
|
||||
{ startTag: '<strong>', endTag: '</strong>' },
|
||||
);
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
@ -51,16 +53,16 @@ export default {
|
|||
<div class="text-content">
|
||||
<h4>{{ contents.title }}</h4>
|
||||
<p v-html="contents.content"></p>
|
||||
<a v-if="activeTab === 'all'" :href="newIssuePath" class="btn btn-success btn-inverted">
|
||||
New issue
|
||||
</a>
|
||||
<a v-if="activeTab === 'all'" :href="newIssuePath" class="btn btn-success btn-inverted">{{
|
||||
__('New issue')
|
||||
}}</a>
|
||||
<button
|
||||
v-if="activeTab === 'selected'"
|
||||
class="btn btn-default"
|
||||
type="button"
|
||||
@click="changeTab('all')"
|
||||
>
|
||||
Open issues
|
||||
{{ __('Open issues') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script>
|
||||
import footerEEMixin from 'ee_else_ce/boards/mixins/modal_footer';
|
||||
import Flash from '../../../flash';
|
||||
import { __ } from '../../../locale';
|
||||
import { __, n__ } from '../../../locale';
|
||||
import ListsDropdown from './lists_dropdown.vue';
|
||||
import { pluralize } from '../../../lib/utils/text_utility';
|
||||
import ModalStore from '../../stores/modal_store';
|
||||
import modalMixin from '../../mixins/modal_mixins';
|
||||
import boardsStore from '../../stores/boards_store';
|
||||
|
@ -11,7 +11,7 @@ export default {
|
|||
components: {
|
||||
ListsDropdown,
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
mixins: [modalMixin, footerEEMixin],
|
||||
data() {
|
||||
return {
|
||||
modal: ModalStore.store,
|
||||
|
@ -24,8 +24,8 @@ export default {
|
|||
},
|
||||
submitText() {
|
||||
const count = ModalStore.selectedCount();
|
||||
|
||||
return `Add ${count > 0 ? count : ''} ${pluralize('issue', count)}`;
|
||||
if (!count) return __('Add issues');
|
||||
return n__(`Add %d issue`, `Add %d issues`, count);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -42,7 +42,7 @@ export default {
|
|||
const req = this.buildUpdateRequest(list);
|
||||
|
||||
// Post the data to the backend
|
||||
gl.boardService.bulkUpdate(issueIds, req).catch(() => {
|
||||
boardsStore.bulkUpdate(issueIds, req).catch(() => {
|
||||
Flash(__('Failed to update issues, please try again.'));
|
||||
|
||||
selectedIssues.forEach(issue => {
|
||||
|
@ -68,11 +68,11 @@ export default {
|
|||
<button :disabled="submitDisabled" class="btn btn-success" type="button" @click="addIssues">
|
||||
{{ submitText }}
|
||||
</button>
|
||||
<span class="inline add-issues-footer-to-list"> to list </span>
|
||||
<span class="inline add-issues-footer-to-list">{{ __('to list') }}</span>
|
||||
<lists-dropdown />
|
||||
</div>
|
||||
<button class="btn btn-default float-right" type="button" @click="toggleModal(false)">
|
||||
Cancel
|
||||
{{ __('Cancel') }}
|
||||
</button>
|
||||
</footer>
|
||||
</template>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { __ } from '~/locale';
|
||||
import ModalFilters from './filters';
|
||||
import ModalTabs from './tabs.vue';
|
||||
import ModalStore from '../../stores/modal_store';
|
||||
|
@ -30,10 +31,10 @@ export default {
|
|||
computed: {
|
||||
selectAllText() {
|
||||
if (ModalStore.selectedCount() !== this.issues.length || this.issues.length === 0) {
|
||||
return 'Select all';
|
||||
return __('Select all');
|
||||
}
|
||||
|
||||
return 'Deselect all';
|
||||
return __('Deselect all');
|
||||
},
|
||||
showSearch() {
|
||||
return this.activeTab === 'all' && !this.loading && this.issuesCount > 0;
|
||||
|
@ -57,7 +58,7 @@ export default {
|
|||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
:aria-label="__('Close')"
|
||||
@click="toggleModal(false)"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
|
|
|
@ -123,7 +123,9 @@ export default {
|
|||
class="empty-state add-issues-empty-state-filter text-center"
|
||||
>
|
||||
<div class="svg-content"><img :src="emptyStateSvg" /></div>
|
||||
<div class="text-content"><h4>There are no issues to show.</h4></div>
|
||||
<div class="text-content">
|
||||
<h4>{{ __('There are no issues to show.') }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(group, index) in groupedIssues" :key="index" class="add-issues-list-column">
|
||||
<div v-for="issue in group" v-if="showIssue(issue)" :key="issue.id" class="board-card-parent">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { __ } from '~/locale';
|
||||
import $ from 'jquery';
|
||||
import _ from 'underscore';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
@ -27,7 +28,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
selectedProjectName() {
|
||||
return this.selectedProject.name || 'Select a project';
|
||||
return this.selectedProject.name || __('Select a project');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
@ -81,7 +82,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<label class="label-bold prepend-top-10"> Project </label>
|
||||
<label class="label-bold prepend-top-10">{{ __('Project') }}</label>
|
||||
<div ref="projectsDropdown" class="dropdown dropdown-projects">
|
||||
<button
|
||||
class="dropdown-menu-toggle wide"
|
||||
|
@ -92,9 +93,9 @@ export default {
|
|||
{{ selectedProjectName }} <icon name="chevron-down" />
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-full-width">
|
||||
<div class="dropdown-title">Projects</div>
|
||||
<div class="dropdown-title">{{ __('Projects') }}</div>
|
||||
<div class="dropdown-input">
|
||||
<input class="dropdown-input-field" type="search" placeholder="Search projects" />
|
||||
<input class="dropdown-input-field" type="search" :placeholder="__('Search projects')" />
|
||||
<icon name="search" class="dropdown-input-search" data-hidden="true" />
|
||||
</div>
|
||||
<div class="dropdown-content"></div>
|
||||
|
|
|
@ -76,7 +76,7 @@ export default Vue.extend({
|
|||
<template>
|
||||
<div class="block list">
|
||||
<button class="btn btn-default btn-block" type="button" @click="removeIssue">
|
||||
Remove from board
|
||||
{{ __('Remove from board') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
1
app/assets/javascripts/boards/config_toggle.js
Normal file
1
app/assets/javascripts/boards/config_toggle.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default () => {};
|
|
@ -2,7 +2,6 @@ import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable
|
|||
import FilteredSearchContainer from '../filtered_search/container';
|
||||
import FilteredSearchManager from '../filtered_search/filtered_search_manager';
|
||||
import boardsStore from './stores/boards_store';
|
||||
import { isEE } from '~/lib/utils/common_utils';
|
||||
|
||||
export default class FilteredSearchBoards extends FilteredSearchManager {
|
||||
constructor(store, updateUrl = false, cantEdit = []) {
|
||||
|
@ -10,7 +9,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
|
|||
page: 'boards',
|
||||
isGroupDecendent: true,
|
||||
stateFiltersSelector: '.issues-state-filters',
|
||||
isGroup: isEE(),
|
||||
isGroup: IS_EE,
|
||||
filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
|
||||
});
|
||||
|
||||
|
|
|
@ -6,28 +6,31 @@ import { __ } from '~/locale';
|
|||
import './models/label';
|
||||
import './models/assignee';
|
||||
|
||||
import FilteredSearchBoards from './filtered_search_boards';
|
||||
import eventHub from './eventhub';
|
||||
import FilteredSearchBoards from '~/boards/filtered_search_boards';
|
||||
import eventHub from '~/boards/eventhub';
|
||||
import sidebarEventHub from '~/sidebar/event_hub';
|
||||
import './models/issue';
|
||||
import './models/list';
|
||||
import './models/milestone';
|
||||
import './models/project';
|
||||
import boardsStore from './stores/boards_store';
|
||||
import ModalStore from './stores/modal_store';
|
||||
import BoardService from './services/board_service';
|
||||
import modalMixin from './mixins/modal_mixins';
|
||||
import './filters/due_date_filters';
|
||||
import Board from './components/board';
|
||||
import BoardSidebar from './components/board_sidebar';
|
||||
import initNewListDropdown from './components/new_list_dropdown';
|
||||
import BoardAddIssuesModal from './components/modal/index.vue';
|
||||
import 'ee_else_ce/boards/models/issue';
|
||||
import 'ee_else_ce/boards/models/list';
|
||||
import '~/boards/models/milestone';
|
||||
import '~/boards/models/project';
|
||||
import boardsStore from '~/boards/stores/boards_store';
|
||||
import ModalStore from '~/boards/stores/modal_store';
|
||||
import BoardService from 'ee_else_ce/boards/services/board_service';
|
||||
import modalMixin from '~/boards/mixins/modal_mixins';
|
||||
import '~/boards/filters/due_date_filters';
|
||||
import Board from 'ee_else_ce/boards/components/board';
|
||||
import BoardSidebar from 'ee_else_ce/boards/components/board_sidebar';
|
||||
import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown';
|
||||
import BoardAddIssuesModal from '~/boards/components/modal/index.vue';
|
||||
import '~/vue_shared/vue_resource_interceptor';
|
||||
import {
|
||||
NavigationType,
|
||||
convertObjectPropsToCamelCase,
|
||||
parseBoolean,
|
||||
} from '~/lib/utils/common_utils';
|
||||
import boardConfigToggle from 'ee_else_ce/boards/config_toggle';
|
||||
import toggleFocusMode from 'ee_else_ce/boards/toggle_focus';
|
||||
import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher';
|
||||
|
||||
let issueBoardsApp;
|
||||
|
||||
|
@ -49,6 +52,7 @@ export default () => {
|
|||
}
|
||||
|
||||
boardsStore.create();
|
||||
boardsStore.setTimeTrackingLimitToHours($boardApp.dataset.timeTrackingLimitToHours);
|
||||
|
||||
issueBoardsApp = new Vue({
|
||||
el: $boardApp,
|
||||
|
@ -77,13 +81,14 @@ export default () => {
|
|||
},
|
||||
},
|
||||
created() {
|
||||
gl.boardService = new BoardService({
|
||||
boardsStore.setEndpoints({
|
||||
boardsEndpoint: this.boardsEndpoint,
|
||||
recentBoardsEndpoint: this.recentBoardsEndpoint,
|
||||
listsEndpoint: this.listsEndpoint,
|
||||
bulkUpdatePath: this.bulkUpdatePath,
|
||||
boardId: this.boardId,
|
||||
});
|
||||
gl.boardService = new BoardService();
|
||||
boardsStore.rootPath = this.boardsEndpoint;
|
||||
|
||||
eventHub.$on('updateTokens', this.updateTokens);
|
||||
|
@ -204,6 +209,8 @@ export default () => {
|
|||
},
|
||||
});
|
||||
|
||||
boardConfigToggle(boardsStore);
|
||||
|
||||
const issueBoardsModal = document.getElementById('js-add-issues-btn');
|
||||
|
||||
if (issueBoardsModal) {
|
||||
|
@ -277,4 +284,7 @@ export default () => {
|
|||
`,
|
||||
});
|
||||
}
|
||||
|
||||
toggleFocusMode(ModalStore, boardsStore);
|
||||
mountMultipleBoardsSwitcher();
|
||||
};
|
||||
|
|
1
app/assets/javascripts/boards/mixins/modal_footer.js
Normal file
1
app/assets/javascripts/boards/mixins/modal_footer.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default {};
|
|
@ -1,7 +1,7 @@
|
|||
/* global DocumentTouch */
|
||||
|
||||
import $ from 'jquery';
|
||||
import sortableConfig from '../../sortable/sortable_config';
|
||||
import sortableConfig from 'ee_else_ce/sortable/sortable_config';
|
||||
|
||||
export function sortableStart() {
|
||||
$('.has-tooltip')
|
||||
|
@ -20,7 +20,7 @@ export function getBoardSortableDefaultOptions(obj) {
|
|||
'ontouchstart' in window || (window.DocumentTouch && document instanceof DocumentTouch);
|
||||
|
||||
const defaultSortOptions = Object.assign({}, sortableConfig, {
|
||||
filter: '.board-delete, .btn',
|
||||
filter: '.no-drag',
|
||||
delay: touchEnabled ? 100 : 0,
|
||||
scrollSensitivity: touchEnabled ? 60 : 100,
|
||||
scrollSpeed: 20,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import Vue from 'vue';
|
||||
import './label';
|
||||
import { isEE, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import IssueProject from './project';
|
||||
import boardsStore from '../stores/boards_store';
|
||||
|
||||
|
@ -91,13 +91,13 @@ class ListIssue {
|
|||
|
||||
addMilestone(milestone) {
|
||||
const miletoneId = this.milestone ? this.milestone.id : null;
|
||||
if (isEE && milestone.id !== miletoneId) {
|
||||
if (IS_EE && milestone.id !== miletoneId) {
|
||||
this.milestone = new ListMilestone(milestone);
|
||||
}
|
||||
}
|
||||
|
||||
removeMilestone(removeMilestone) {
|
||||
if (isEE && removeMilestone && removeMilestone.id === this.milestone.id) {
|
||||
if (IS_EE && removeMilestone && removeMilestone.id === this.milestone.id) {
|
||||
this.milestone = {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import { __ } from '~/locale';
|
||||
import ListLabel from './label';
|
||||
import ListAssignee from './assignee';
|
||||
import { isEE, urlParamsToObject } from '~/lib/utils/common_utils';
|
||||
import { urlParamsToObject } from '~/lib/utils/common_utils';
|
||||
import boardsStore from '../stores/boards_store';
|
||||
import ListMilestone from './milestone';
|
||||
|
||||
|
@ -26,6 +26,12 @@ const TYPES = {
|
|||
isExpandable: false,
|
||||
isBlank: true,
|
||||
},
|
||||
default: {
|
||||
// includes label, assignee, and milestone lists
|
||||
isPreset: false,
|
||||
isExpandable: true,
|
||||
isBlank: false,
|
||||
},
|
||||
};
|
||||
|
||||
class List {
|
||||
|
@ -52,7 +58,7 @@ class List {
|
|||
} else if (obj.user) {
|
||||
this.assignee = new ListAssignee(obj.user);
|
||||
this.title = this.assignee.name;
|
||||
} else if (isEE && obj.milestone) {
|
||||
} else if (IS_EE && obj.milestone) {
|
||||
this.milestone = new ListMilestone(obj.milestone);
|
||||
this.title = this.milestone.title;
|
||||
}
|
||||
|
@ -79,7 +85,7 @@ class List {
|
|||
entityType = 'label_id';
|
||||
} else if (this.assignee) {
|
||||
entityType = 'assignee_id';
|
||||
} else if (isEE && this.milestone) {
|
||||
} else if (IS_EE && this.milestone) {
|
||||
entityType = 'milestone_id';
|
||||
}
|
||||
|
||||
|
@ -199,7 +205,7 @@ class List {
|
|||
issue.addAssignee(this.assignee);
|
||||
}
|
||||
|
||||
if (isEE && this.milestone) {
|
||||
if (IS_EE && this.milestone) {
|
||||
if (listFrom && listFrom.type === 'milestone') {
|
||||
issue.removeMilestone(listFrom.milestone);
|
||||
}
|
||||
|
@ -249,7 +255,7 @@ class List {
|
|||
}
|
||||
|
||||
getTypeInfo(type) {
|
||||
return TYPES[type] || {};
|
||||
return TYPES[type] || TYPES.default;
|
||||
}
|
||||
|
||||
onNewIssueResponse(issue, data) {
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { isEE } from '~/lib/utils/common_utils';
|
||||
|
||||
export default class ListMilestone {
|
||||
constructor(obj) {
|
||||
this.id = obj.id;
|
||||
this.title = obj.title;
|
||||
|
||||
if (isEE) {
|
||||
if (IS_EE) {
|
||||
this.path = obj.path;
|
||||
this.state = obj.state;
|
||||
this.webUrl = obj.web_url || obj.webUrl;
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import Vue from 'vue';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import BoardsSelector from '~/boards/components/boards_selector.vue';
|
||||
|
||||
export default () => {
|
||||
const boardsSwitcherElement = document.getElementById('js-multiple-boards-switcher');
|
||||
return new Vue({
|
||||
el: boardsSwitcherElement,
|
||||
components: {
|
||||
BoardsSelector,
|
||||
},
|
||||
data() {
|
||||
const { dataset } = boardsSwitcherElement;
|
||||
|
||||
const boardsSelectorProps = {
|
||||
...dataset,
|
||||
currentBoard: JSON.parse(dataset.currentBoard),
|
||||
hasMissingBoards: parseBoolean(dataset.hasMissingBoards),
|
||||
canAdminBoard: parseBoolean(dataset.canAdminBoard),
|
||||
multipleIssueBoardsAvailable: parseBoolean(dataset.multipleIssueBoardsAvailable),
|
||||
projectId: Number(dataset.projectId),
|
||||
groupId: Number(dataset.groupId),
|
||||
scopedIssueBoardFeatureEnabled: parseBoolean(dataset.scopedIssueBoardFeatureEnabled),
|
||||
weights: JSON.parse(dataset.weights),
|
||||
};
|
||||
|
||||
return { boardsSelectorProps };
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(BoardsSelector, {
|
||||
props: this.boardsSelectorProps,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,106 +1,82 @@
|
|||
import axios from '../../lib/utils/axios_utils';
|
||||
import { mergeUrlParams } from '../../lib/utils/url_utility';
|
||||
/* eslint-disable class-methods-use-this */
|
||||
|
||||
import boardsStore from '~/boards/stores/boards_store';
|
||||
|
||||
export default class BoardService {
|
||||
constructor({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId, recentBoardsEndpoint }) {
|
||||
this.boardsEndpoint = boardsEndpoint;
|
||||
this.boardId = boardId;
|
||||
this.listsEndpoint = listsEndpoint;
|
||||
this.listsEndpointGenerate = `${listsEndpoint}/generate.json`;
|
||||
this.bulkUpdatePath = bulkUpdatePath;
|
||||
this.recentBoardsEndpoint = `${recentBoardsEndpoint}.json`;
|
||||
}
|
||||
|
||||
generateBoardsPath(id) {
|
||||
return `${this.boardsEndpoint}${id ? `/${id}` : ''}.json`;
|
||||
return boardsStore.generateBoardsPath(id);
|
||||
}
|
||||
|
||||
generateIssuesPath(id) {
|
||||
return `${this.listsEndpoint}${id ? `/${id}` : ''}/issues`;
|
||||
return boardsStore.generateIssuesPath(id);
|
||||
}
|
||||
|
||||
static generateIssuePath(boardId, id) {
|
||||
return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${
|
||||
id ? `/${id}` : ''
|
||||
}`;
|
||||
return boardsStore.generateIssuePath(boardId, id);
|
||||
}
|
||||
|
||||
all() {
|
||||
return axios.get(this.listsEndpoint);
|
||||
return boardsStore.all();
|
||||
}
|
||||
|
||||
generateDefaultLists() {
|
||||
return axios.post(this.listsEndpointGenerate, {});
|
||||
return boardsStore.generateDefaultLists();
|
||||
}
|
||||
|
||||
createList(entityId, entityType) {
|
||||
const list = {
|
||||
[entityType]: entityId,
|
||||
};
|
||||
|
||||
return axios.post(this.listsEndpoint, {
|
||||
list,
|
||||
});
|
||||
return boardsStore.createList(entityId, entityType);
|
||||
}
|
||||
|
||||
updateList(id, position) {
|
||||
return axios.put(`${this.listsEndpoint}/${id}`, {
|
||||
list: {
|
||||
position,
|
||||
},
|
||||
});
|
||||
return boardsStore.updateList(id, position);
|
||||
}
|
||||
|
||||
destroyList(id) {
|
||||
return axios.delete(`${this.listsEndpoint}/${id}`);
|
||||
return boardsStore.destroyList(id);
|
||||
}
|
||||
|
||||
getIssuesForList(id, filter = {}) {
|
||||
const data = { id };
|
||||
Object.keys(filter).forEach(key => {
|
||||
data[key] = filter[key];
|
||||
});
|
||||
|
||||
return axios.get(mergeUrlParams(data, this.generateIssuesPath(id)));
|
||||
return boardsStore.getIssuesForList(id, filter);
|
||||
}
|
||||
|
||||
moveIssue(id, fromListId = null, toListId = null, moveBeforeId = null, moveAfterId = null) {
|
||||
return axios.put(BoardService.generateIssuePath(this.boardId, id), {
|
||||
from_list_id: fromListId,
|
||||
to_list_id: toListId,
|
||||
move_before_id: moveBeforeId,
|
||||
move_after_id: moveAfterId,
|
||||
});
|
||||
return boardsStore.moveIssue(id, fromListId, toListId, moveBeforeId, moveAfterId);
|
||||
}
|
||||
|
||||
newIssue(id, issue) {
|
||||
return axios.post(this.generateIssuesPath(id), {
|
||||
issue,
|
||||
});
|
||||
return boardsStore.newIssue(id, issue);
|
||||
}
|
||||
|
||||
getBacklog(data) {
|
||||
return axios.get(
|
||||
mergeUrlParams(data, `${gon.relative_url_root}/-/boards/${this.boardId}/issues.json`),
|
||||
);
|
||||
return boardsStore.getBacklog(data);
|
||||
}
|
||||
|
||||
bulkUpdate(issueIds, extraData = {}) {
|
||||
const data = {
|
||||
update: Object.assign(extraData, {
|
||||
issuable_ids: issueIds.join(','),
|
||||
}),
|
||||
};
|
||||
|
||||
return axios.post(this.bulkUpdatePath, data);
|
||||
return boardsStore.bulkUpdate(issueIds, extraData);
|
||||
}
|
||||
|
||||
static getIssueInfo(endpoint) {
|
||||
return axios.get(endpoint);
|
||||
return boardsStore.getIssueInfo(endpoint);
|
||||
}
|
||||
|
||||
static toggleIssueSubscription(endpoint) {
|
||||
return axios.post(endpoint);
|
||||
return boardsStore.toggleIssueSubscription(endpoint);
|
||||
}
|
||||
|
||||
allBoards() {
|
||||
return boardsStore.allBoards();
|
||||
}
|
||||
|
||||
recentBoards() {
|
||||
return boardsStore.recentBoards();
|
||||
}
|
||||
|
||||
createBoard(board) {
|
||||
return boardsStore.createBoard(board);
|
||||
}
|
||||
|
||||
deleteBoard({ id }) {
|
||||
return boardsStore.deleteBoard({ id });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,10 +8,15 @@ import Cookies from 'js-cookie';
|
|||
import BoardsStoreEE from 'ee_else_ce/boards/stores/boards_store_ee';
|
||||
import { getUrlParamsArray, parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { __ } from '~/locale';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||
import eventHub from '../eventhub';
|
||||
|
||||
const boardsStore = {
|
||||
disabled: false,
|
||||
timeTracking: {
|
||||
limitToHours: false,
|
||||
},
|
||||
scopedLabels: {
|
||||
helpLink: '',
|
||||
enabled: false,
|
||||
|
@ -25,6 +30,7 @@ const boardsStore = {
|
|||
},
|
||||
currentPage: '',
|
||||
reload: false,
|
||||
endpoints: {},
|
||||
},
|
||||
detail: {
|
||||
issue: {},
|
||||
|
@ -33,6 +39,19 @@ const boardsStore = {
|
|||
issue: {},
|
||||
list: {},
|
||||
},
|
||||
|
||||
setEndpoints({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId, recentBoardsEndpoint }) {
|
||||
const listsEndpointGenerate = `${listsEndpoint}/generate.json`;
|
||||
this.state.endpoints = {
|
||||
boardsEndpoint,
|
||||
boardId,
|
||||
listsEndpoint,
|
||||
listsEndpointGenerate,
|
||||
bulkUpdatePath,
|
||||
recentBoardsEndpoint: `${recentBoardsEndpoint}.json`,
|
||||
};
|
||||
},
|
||||
|
||||
create() {
|
||||
this.state.lists = [];
|
||||
this.filter.path = getUrlParamsArray().join('&');
|
||||
|
@ -222,6 +241,143 @@ const boardsStore = {
|
|||
setIssueDetail(issueDetail) {
|
||||
this.detail.issue = issueDetail;
|
||||
},
|
||||
|
||||
setTimeTrackingLimitToHours(limitToHours) {
|
||||
this.timeTracking.limitToHours = parseBoolean(limitToHours);
|
||||
},
|
||||
|
||||
generateBoardsPath(id) {
|
||||
return `${this.state.endpoints.boardsEndpoint}${id ? `/${id}` : ''}.json`;
|
||||
},
|
||||
|
||||
generateIssuesPath(id) {
|
||||
return `${this.state.endpoints.listsEndpoint}${id ? `/${id}` : ''}/issues`;
|
||||
},
|
||||
|
||||
generateIssuePath(boardId, id) {
|
||||
return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${
|
||||
id ? `/${id}` : ''
|
||||
}`;
|
||||
},
|
||||
|
||||
all() {
|
||||
return axios.get(this.state.endpoints.listsEndpoint);
|
||||
},
|
||||
|
||||
generateDefaultLists() {
|
||||
return axios.post(this.state.endpoints.listsEndpointGenerate, {});
|
||||
},
|
||||
|
||||
createList(entityId, entityType) {
|
||||
const list = {
|
||||
[entityType]: entityId,
|
||||
};
|
||||
|
||||
return axios.post(this.state.endpoints.listsEndpoint, {
|
||||
list,
|
||||
});
|
||||
},
|
||||
|
||||
updateList(id, position) {
|
||||
return axios.put(`${this.state.endpoints.listsEndpoint}/${id}`, {
|
||||
list: {
|
||||
position,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
destroyList(id) {
|
||||
return axios.delete(`${this.state.endpoints.listsEndpoint}/${id}`);
|
||||
},
|
||||
|
||||
getIssuesForList(id, filter = {}) {
|
||||
const data = { id };
|
||||
Object.keys(filter).forEach(key => {
|
||||
data[key] = filter[key];
|
||||
});
|
||||
|
||||
return axios.get(mergeUrlParams(data, this.generateIssuesPath(id)));
|
||||
},
|
||||
|
||||
moveIssue(id, fromListId = null, toListId = null, moveBeforeId = null, moveAfterId = null) {
|
||||
return axios.put(this.generateIssuePath(this.state.endpoints.boardId, id), {
|
||||
from_list_id: fromListId,
|
||||
to_list_id: toListId,
|
||||
move_before_id: moveBeforeId,
|
||||
move_after_id: moveAfterId,
|
||||
});
|
||||
},
|
||||
|
||||
newIssue(id, issue) {
|
||||
return axios.post(this.generateIssuesPath(id), {
|
||||
issue,
|
||||
});
|
||||
},
|
||||
|
||||
getBacklog(data) {
|
||||
return axios.get(
|
||||
mergeUrlParams(
|
||||
data,
|
||||
`${gon.relative_url_root}/-/boards/${this.state.endpoints.boardId}/issues.json`,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
bulkUpdate(issueIds, extraData = {}) {
|
||||
const data = {
|
||||
update: Object.assign(extraData, {
|
||||
issuable_ids: issueIds.join(','),
|
||||
}),
|
||||
};
|
||||
|
||||
return axios.post(this.state.endpoints.bulkUpdatePath, data);
|
||||
},
|
||||
|
||||
getIssueInfo(endpoint) {
|
||||
return axios.get(endpoint);
|
||||
},
|
||||
|
||||
toggleIssueSubscription(endpoint) {
|
||||
return axios.post(endpoint);
|
||||
},
|
||||
|
||||
allBoards() {
|
||||
return axios.get(this.generateBoardsPath());
|
||||
},
|
||||
|
||||
recentBoards() {
|
||||
return axios.get(this.state.endpoints.recentBoardsEndpoint);
|
||||
},
|
||||
|
||||
createBoard(board) {
|
||||
const boardPayload = { ...board };
|
||||
boardPayload.label_ids = (board.labels || []).map(b => b.id);
|
||||
|
||||
if (boardPayload.label_ids.length === 0) {
|
||||
boardPayload.label_ids = [''];
|
||||
}
|
||||
|
||||
if (boardPayload.assignee) {
|
||||
boardPayload.assignee_id = boardPayload.assignee.id;
|
||||
}
|
||||
|
||||
if (boardPayload.milestone) {
|
||||
boardPayload.milestone_id = boardPayload.milestone.id;
|
||||
}
|
||||
|
||||
if (boardPayload.id) {
|
||||
return axios.put(this.generateBoardsPath(boardPayload.id), { board: boardPayload });
|
||||
}
|
||||
return axios.post(this.generateBoardsPath(), { board: boardPayload });
|
||||
},
|
||||
|
||||
deleteBoard({ id }) {
|
||||
return axios.delete(this.generateBoardsPath(id));
|
||||
},
|
||||
|
||||
setCurrentBoard(board) {
|
||||
this.state.currentBoard = board;
|
||||
},
|
||||
};
|
||||
|
||||
BoardsStoreEE.initEESpecific(boardsStore);
|
||||
|
|
1
app/assets/javascripts/boards/toggle_focus.js
Normal file
1
app/assets/javascripts/boards/toggle_focus.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default () => {};
|
|
@ -0,0 +1,72 @@
|
|||
<script>
|
||||
import { sprintf, __ } from '~/locale';
|
||||
import GraphBar from './graph_bar.vue';
|
||||
import { MAX_COMMIT_COUNT } from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GraphBar,
|
||||
},
|
||||
props: {
|
||||
defaultBranch: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
distance: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
aheadCount: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
behindCount: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
maxCommits: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
if (this.distance) {
|
||||
return sprintf(
|
||||
__('More than %{number_commits_distance} commits different with %{default_branch}'),
|
||||
{
|
||||
number_commits_distance:
|
||||
this.distance >= MAX_COMMIT_COUNT ? `${MAX_COMMIT_COUNT - 1}+` : this.distance,
|
||||
default_branch: this.defaultBranch,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
__(
|
||||
'%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead',
|
||||
),
|
||||
{
|
||||
number_commits_behind: this.behindCount,
|
||||
number_commits_ahead: this.aheadCount,
|
||||
default_branch: this.defaultBranch,
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :title="title" class="divergence-graph px-2 d-none d-md-block">
|
||||
<template v-if="distance">
|
||||
<graph-bar :count="distance" :max-commits="maxCommits" position="full" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<graph-bar :count="behindCount" :max-commits="maxCommits" position="left" />
|
||||
<div class="graph-separator pull-left mt-1"></div>
|
||||
<graph-bar :count="aheadCount" :max-commits="maxCommits" position="right" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
69
app/assets/javascripts/branches/components/graph_bar.vue
Normal file
69
app/assets/javascripts/branches/components/graph_bar.vue
Normal file
|
@ -0,0 +1,69 @@
|
|||
<script>
|
||||
import { SIDES, MAX_COMMIT_COUNT } from '../constants';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
position: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
maxCommits: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
label() {
|
||||
if (this.count >= MAX_COMMIT_COUNT) {
|
||||
return `${MAX_COMMIT_COUNT - 1}+`;
|
||||
}
|
||||
|
||||
return this.count;
|
||||
},
|
||||
barGraphWidthFactor() {
|
||||
return this.maxCommits > 0 ? 100 / this.maxCommits : 0;
|
||||
},
|
||||
style() {
|
||||
return {
|
||||
width: `${this.count * this.barGraphWidthFactor}%`,
|
||||
};
|
||||
},
|
||||
isFullWidth() {
|
||||
return this.position === SIDES.full;
|
||||
},
|
||||
isLeftSide() {
|
||||
return this.position === SIDES.left;
|
||||
},
|
||||
roundedClass() {
|
||||
if (this.isFullWidth) return 'rounded';
|
||||
|
||||
return `rounded-${this.position}`;
|
||||
},
|
||||
textAlignmentClass() {
|
||||
if (this.isFullWidth) return 'text-center';
|
||||
|
||||
return `text-${this.isLeftSide ? SIDES.right : SIDES.left}`;
|
||||
},
|
||||
positionSideClass() {
|
||||
return `position-${this.isLeftSide ? SIDES.right : SIDES.left}-0`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="{ full: isFullWidth }" class="position-relative pull-left pt-1 graph-side h-100">
|
||||
<div
|
||||
:style="style"
|
||||
:class="[roundedClass, positionSideClass]"
|
||||
class="position-absolute bar js-graph-bar"
|
||||
></div>
|
||||
<span :class="textAlignmentClass" class="d-block pt-1 pr-1 count js-graph-count">
|
||||
{{ label }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
6
app/assets/javascripts/branches/constants.js
Normal file
6
app/assets/javascripts/branches/constants.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
export const SIDES = {
|
||||
full: 'full',
|
||||
left: 'left',
|
||||
right: 'right',
|
||||
};
|
||||
export const MAX_COMMIT_COUNT = 1000;
|
51
app/assets/javascripts/branches/divergence_graph.js
Normal file
51
app/assets/javascripts/branches/divergence_graph.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
import Vue from 'vue';
|
||||
import { __ } from '../locale';
|
||||
import createFlash from '../flash';
|
||||
import axios from '../lib/utils/axios_utils';
|
||||
import DivergenceGraph from './components/divergence_graph.vue';
|
||||
|
||||
export function createGraphVueApp(el, data, maxCommits) {
|
||||
return new Vue({
|
||||
el,
|
||||
render(h) {
|
||||
return h(DivergenceGraph, {
|
||||
props: {
|
||||
defaultBranch: 'master',
|
||||
distance: data.distance ? parseInt(data.distance, 10) : null,
|
||||
aheadCount: parseInt(data.ahead, 10),
|
||||
behindCount: parseInt(data.behind, 10),
|
||||
maxCommits,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default endpoint => {
|
||||
const names = [...document.querySelectorAll('.js-branch-item')].map(
|
||||
({ dataset }) => dataset.name,
|
||||
);
|
||||
return axios
|
||||
.get(endpoint, {
|
||||
params: { names },
|
||||
})
|
||||
.then(({ data }) => {
|
||||
const maxCommits = Object.entries(data).reduce((acc, [, val]) => {
|
||||
const max = Math.max(...Object.values(val));
|
||||
return max > acc ? max : acc;
|
||||
}, 100);
|
||||
|
||||
Object.entries(data).forEach(([branchName, val]) => {
|
||||
const el = document.querySelector(
|
||||
`[data-name="${branchName}"] .js-branch-divergence-graph`,
|
||||
);
|
||||
|
||||
if (!el) return;
|
||||
|
||||
createGraphVueApp(el, val, maxCommits);
|
||||
});
|
||||
})
|
||||
.catch(() =>
|
||||
createFlash(__('Error fetching diverging counts for branches. Please try again.')),
|
||||
);
|
||||
};
|
|
@ -207,7 +207,7 @@ export default {
|
|||
return __('Updating');
|
||||
}
|
||||
|
||||
return __('Updated');
|
||||
return this.updateSuccessful ? __('Updated to') : __('Updated');
|
||||
},
|
||||
updateFailureDescription() {
|
||||
return s__('ClusterIntegration|Update failed. Please check the logs and try again.');
|
||||
|
@ -331,8 +331,6 @@ export default {
|
|||
class="form-text text-muted label p-0 js-cluster-application-update-details"
|
||||
>
|
||||
{{ versionLabel }}
|
||||
<span v-if="updateSuccessful">to</span>
|
||||
|
||||
<gl-link
|
||||
v-if="updateSuccessful"
|
||||
:href="chartRepo"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import LoadingButton from '~/vue_shared/components/loading_button.vue';
|
||||
import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import { __, s__ } from '~/locale';
|
||||
|
||||
import { APPLICATION_STATUS } from '~/clusters/constants';
|
||||
|
||||
|
@ -32,7 +32,7 @@ export default {
|
|||
return [UPDATING].includes(this.knative.status);
|
||||
},
|
||||
saveButtonLabel() {
|
||||
return this.saving ? this.__('Saving') : this.__('Save changes');
|
||||
return this.saving ? __('Saving') : __('Save changes');
|
||||
},
|
||||
knativeInstalled() {
|
||||
return this.knative.installed;
|
||||
|
@ -122,9 +122,9 @@ export default {
|
|||
`ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint.`,
|
||||
)
|
||||
}}
|
||||
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
|
||||
{{ __('More information') }}
|
||||
</a>
|
||||
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">{{
|
||||
__('More information')
|
||||
}}</a>
|
||||
</p>
|
||||
|
||||
<p
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import LoadingButton from '~/vue_shared/components/loading_button.vue';
|
||||
import { APPLICATION_STATUS } from '~/clusters/constants';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
const { UPDATING, UNINSTALLING } = APPLICATION_STATUS;
|
||||
|
||||
|
@ -22,7 +23,7 @@ export default {
|
|||
return this.status === UNINSTALLING;
|
||||
},
|
||||
label() {
|
||||
return this.loading ? this.__('Uninstalling') : this.__('Uninstall');
|
||||
return this.loading ? __('Uninstalling') : __('Uninstall');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -14,7 +14,9 @@ const CUSTOM_APP_WARNING_TEXT = {
|
|||
[PROMETHEUS]: s__('ClusterIntegration|All data will be deleted and cannot be restored.'),
|
||||
[RUNNER]: s__('ClusterIntegration|Any running pipelines will be canceled.'),
|
||||
[KNATIVE]: s__('ClusterIntegration|The associated IP will be deleted and cannot be restored.'),
|
||||
[JUPYTER]: '',
|
||||
[JUPYTER]: s__(
|
||||
'ClusterIntegration|All data not committed to GitLab will be deleted and cannot be restored.',
|
||||
),
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -80,6 +80,9 @@ const applicationStateMachine = {
|
|||
installFailed: false,
|
||||
},
|
||||
},
|
||||
[NOT_INSTALLABLE]: {
|
||||
target: NOT_INSTALLABLE,
|
||||
},
|
||||
// This is possible in artificial environments for E2E testing
|
||||
[INSTALLED]: {
|
||||
target: INSTALLED,
|
||||
|
@ -108,6 +111,9 @@ const applicationStateMachine = {
|
|||
updateSuccessful: false,
|
||||
},
|
||||
},
|
||||
[NOT_INSTALLABLE]: {
|
||||
target: NOT_INSTALLABLE,
|
||||
},
|
||||
[UNINSTALL_EVENT]: {
|
||||
target: UNINSTALLING,
|
||||
effects: {
|
||||
|
|
|
@ -4,3 +4,6 @@ import './jquery';
|
|||
import './bootstrap';
|
||||
import './vue';
|
||||
import '../lib/utils/axios_utils';
|
||||
import { openUserCountsBroadcast } from './nav/user_merge_requests';
|
||||
|
||||
openUserCountsBroadcast();
|
||||
|
|
67
app/assets/javascripts/commons/nav/user_merge_requests.js
Normal file
67
app/assets/javascripts/commons/nav/user_merge_requests.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
import Api from '~/api';
|
||||
|
||||
let channel;
|
||||
|
||||
function broadcastCount(newCount) {
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
channel.postMessage(newCount);
|
||||
}
|
||||
|
||||
function updateUserMergeRequestCounts(newCount) {
|
||||
const mergeRequestsCountEl = document.querySelector('.merge-requests-count');
|
||||
mergeRequestsCountEl.textContent = newCount.toLocaleString();
|
||||
mergeRequestsCountEl.classList.toggle('hidden', Number(newCount) === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh user counts (and broadcast if open)
|
||||
*/
|
||||
export function refreshUserMergeRequestCounts() {
|
||||
return Api.userCounts()
|
||||
.then(({ data }) => {
|
||||
const count = data.merge_requests;
|
||||
|
||||
updateUserMergeRequestCounts(count);
|
||||
broadcastCount(count);
|
||||
})
|
||||
.catch(ex => {
|
||||
console.error(ex); // eslint-disable-line no-console
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the broadcast channel for user counts
|
||||
*/
|
||||
export function closeUserCountsBroadcast() {
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
channel.close();
|
||||
channel = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the broadcast channel for user counts, adds user id so we only update
|
||||
*
|
||||
* **Please note:**
|
||||
* Not supported in all browsers, but not polyfilling for now
|
||||
* to keep bundle size small and
|
||||
* no special functionality lost except cross tab notifications
|
||||
*/
|
||||
export function openUserCountsBroadcast() {
|
||||
closeUserCountsBroadcast();
|
||||
|
||||
if (window.BroadcastChannel) {
|
||||
const currentUserId = typeof gon !== 'undefined' && gon && gon.current_user_id;
|
||||
if (currentUserId) {
|
||||
channel = new BroadcastChannel(`mr_count_channel_${currentUserId}`);
|
||||
channel.onmessage = ev => {
|
||||
updateUserMergeRequestCounts(ev.data);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import 'core-js/es/promise/finally';
|
|||
import 'core-js/es/string/code-point-at';
|
||||
import 'core-js/es/string/from-code-point';
|
||||
import 'core-js/es/string/includes';
|
||||
import 'core-js/es/string/starts-with';
|
||||
import 'core-js/es/symbol';
|
||||
import 'core-js/es/map';
|
||||
import 'core-js/es/weak-map';
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<script>
|
||||
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
projects: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
selectedProject: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dropdownText() {
|
||||
if (Object.keys(this.selectedProject).length) {
|
||||
return this.selectedProject.name;
|
||||
}
|
||||
|
||||
return __('Select private project');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
selectProject(project) {
|
||||
this.$emit('click', project);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-dropdown toggle-class="d-flex align-items-center w-100" class="w-100">
|
||||
<template slot="button-content">
|
||||
<span class="str-truncated-100 mr-2">
|
||||
<icon name="lock" />
|
||||
{{ dropdownText }}
|
||||
</span>
|
||||
<icon name="chevron-down" class="ml-auto" />
|
||||
</template>
|
||||
<gl-dropdown-item v-for="project in projects" :key="project.id" @click="selectProject(project)">
|
||||
<icon
|
||||
name="mobile-issue-close"
|
||||
:class="{ icon: project.id !== selectedProject.id }"
|
||||
class="js-active-project-check"
|
||||
/>
|
||||
<span class="ml-1">{{ project.name }}</span>
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</template>
|
|
@ -0,0 +1,140 @@
|
|||
<script>
|
||||
import { GlLink } from '@gitlab/ui';
|
||||
import { __, sprintf } from '../../locale';
|
||||
import createFlash from '../../flash';
|
||||
import Api from '../../api';
|
||||
import state from '../state';
|
||||
import Dropdown from './dropdown.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLink,
|
||||
Dropdown,
|
||||
},
|
||||
props: {
|
||||
namespacePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
newForkPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
helpPagePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
projects: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedProject() {
|
||||
return state.selectedProject;
|
||||
},
|
||||
noForkText() {
|
||||
return sprintf(
|
||||
__(
|
||||
"To protect this issue's confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private.",
|
||||
),
|
||||
{ link_start: `<a href="${this.newForkPath}" class="help-link">`, link_end: '</a>' },
|
||||
false,
|
||||
);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchProjects();
|
||||
this.createBtn = document.querySelector('.js-create-target');
|
||||
this.warningText = document.querySelector('.js-exposed-info-warning');
|
||||
},
|
||||
methods: {
|
||||
selectProject(project) {
|
||||
if (project) {
|
||||
Object.assign(state, {
|
||||
selectedProject: project,
|
||||
});
|
||||
|
||||
if (project.namespaceFullPath !== this.namespacePath) {
|
||||
this.showWarning();
|
||||
}
|
||||
} else if (this.createBtn) {
|
||||
this.createBtn.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
},
|
||||
normalizeProjectData(data) {
|
||||
return data.map(p => ({
|
||||
id: p.id,
|
||||
name: p.name_with_namespace,
|
||||
pathWithNamespace: p.path_with_namespace,
|
||||
namespaceFullpath: p.namespace.full_path,
|
||||
}));
|
||||
},
|
||||
fetchProjects() {
|
||||
Api.projectForks(this.projectPath, {
|
||||
with_merge_requests_enabled: true,
|
||||
min_access_level: 30,
|
||||
visibility: 'private',
|
||||
})
|
||||
.then(({ data }) => {
|
||||
this.projects = this.normalizeProjectData(data);
|
||||
this.selectProject(this.projects[0]);
|
||||
})
|
||||
.catch(e => {
|
||||
createFlash(__('Error fetching forked projects. Please try again.'));
|
||||
throw e;
|
||||
});
|
||||
},
|
||||
showWarning() {
|
||||
if (this.warningText) {
|
||||
this.warningText.classList.remove('hidden');
|
||||
}
|
||||
|
||||
if (this.createBtn) {
|
||||
this.createBtn.classList.add('btn-warning');
|
||||
this.createBtn.classList.remove('btn-success');
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="confidential-merge-request-fork-group form-group">
|
||||
<label>{{ __('Project') }}</label>
|
||||
<div>
|
||||
<dropdown
|
||||
v-if="projects.length"
|
||||
:projects="projects"
|
||||
:selected-project="selectedProject"
|
||||
@click="selectProject"
|
||||
/>
|
||||
<p class="text-muted mt-1 mb-0">
|
||||
<template v-if="projects.length">
|
||||
{{
|
||||
__(
|
||||
"To protect this issue's confidentiality, a private fork of this project was selected.",
|
||||
)
|
||||
}}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ __('No forks available to you.') }}<br />
|
||||
<span v-html="noForkText"></span>
|
||||
</template>
|
||||
<gl-link
|
||||
:href="helpPagePath"
|
||||
class="w-auto p-0 d-inline-block text-primary bg-transparent"
|
||||
target="_blank"
|
||||
>
|
||||
<span class="sr-only">{{ __('Read more') }}</span>
|
||||
<i class="fa fa-question-circle" aria-hidden="true"></i>
|
||||
</gl-link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
30
app/assets/javascripts/confidential_merge_request/index.js
Normal file
30
app/assets/javascripts/confidential_merge_request/index.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import Vue from 'vue';
|
||||
import { parseBoolean } from '../lib/utils/common_utils';
|
||||
import ProjectFormGroup from './components/project_form_group.vue';
|
||||
import state from './state';
|
||||
|
||||
export function isConfidentialIssue() {
|
||||
return parseBoolean(document.querySelector('.js-create-mr').dataset.isConfidential);
|
||||
}
|
||||
|
||||
export function canCreateConfidentialMergeRequest() {
|
||||
return isConfidentialIssue() && Object.keys(state.selectedProject).length > 0;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
const el = document.getElementById('js-forked-project');
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
render(h) {
|
||||
return h(ProjectFormGroup, {
|
||||
props: {
|
||||
namespacePath: el.dataset.namespacePath,
|
||||
projectPath: el.dataset.projectPath,
|
||||
newForkPath: el.dataset.newForkPath,
|
||||
helpPagePath: el.dataset.helpPagePath,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
export default Vue.observable({
|
||||
selectedProject: {},
|
||||
});
|
|
@ -5,6 +5,12 @@ import Flash from './flash';
|
|||
import DropLab from './droplab/drop_lab';
|
||||
import ISetter from './droplab/plugins/input_setter';
|
||||
import { __, sprintf } from './locale';
|
||||
import {
|
||||
init as initConfidentialMergeRequest,
|
||||
isConfidentialIssue,
|
||||
canCreateConfidentialMergeRequest,
|
||||
} from './confidential_merge_request';
|
||||
import confidentialMergeRequestState from './confidential_merge_request/state';
|
||||
|
||||
// Todo: Remove this when fixing issue in input_setter plugin
|
||||
const InputSetter = Object.assign({}, ISetter);
|
||||
|
@ -12,6 +18,17 @@ const InputSetter = Object.assign({}, ISetter);
|
|||
const CREATE_MERGE_REQUEST = 'create-mr';
|
||||
const CREATE_BRANCH = 'create-branch';
|
||||
|
||||
function createEndpoint(projectPath, endpoint) {
|
||||
if (canCreateConfidentialMergeRequest()) {
|
||||
return endpoint.replace(
|
||||
projectPath,
|
||||
confidentialMergeRequestState.selectedProject.pathWithNamespace,
|
||||
);
|
||||
}
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
export default class CreateMergeRequestDropdown {
|
||||
constructor(wrapperEl) {
|
||||
this.wrapperEl = wrapperEl;
|
||||
|
@ -42,6 +59,8 @@ export default class CreateMergeRequestDropdown {
|
|||
this.refIsValid = true;
|
||||
this.refsPath = this.wrapperEl.dataset.refsPath;
|
||||
this.suggestedRef = this.refInput.value;
|
||||
this.projectPath = this.wrapperEl.dataset.projectPath;
|
||||
this.projectId = this.wrapperEl.dataset.projectId;
|
||||
|
||||
// These regexps are used to replace
|
||||
// a backend generated new branch name and its source (ref)
|
||||
|
@ -58,6 +77,14 @@ export default class CreateMergeRequestDropdown {
|
|||
};
|
||||
|
||||
this.init();
|
||||
|
||||
if (isConfidentialIssue()) {
|
||||
this.createMergeRequestButton.setAttribute(
|
||||
'data-dropdown-trigger',
|
||||
'#create-merge-request-dropdown',
|
||||
);
|
||||
initConfidentialMergeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
available() {
|
||||
|
@ -113,7 +140,9 @@ export default class CreateMergeRequestDropdown {
|
|||
this.isCreatingBranch = true;
|
||||
|
||||
return axios
|
||||
.post(this.createBranchPath)
|
||||
.post(createEndpoint(this.projectPath, this.createBranchPath), {
|
||||
confidential_issue_project_id: canCreateConfidentialMergeRequest() ? this.projectId : null,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
this.branchCreated = true;
|
||||
window.location.href = data.url;
|
||||
|
@ -125,7 +154,11 @@ export default class CreateMergeRequestDropdown {
|
|||
this.isCreatingMergeRequest = true;
|
||||
|
||||
return axios
|
||||
.post(this.createMrPath)
|
||||
.post(this.createMrPath, {
|
||||
target_project_id: canCreateConfidentialMergeRequest()
|
||||
? confidentialMergeRequestState.selectedProject.id
|
||||
: null,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
this.mergeRequestCreated = true;
|
||||
window.location.href = data.url;
|
||||
|
@ -149,6 +182,8 @@ export default class CreateMergeRequestDropdown {
|
|||
}
|
||||
|
||||
enable() {
|
||||
if (isConfidentialIssue() && !canCreateConfidentialMergeRequest()) return;
|
||||
|
||||
this.createMergeRequestButton.classList.remove('disabled');
|
||||
this.createMergeRequestButton.removeAttribute('disabled');
|
||||
|
||||
|
@ -205,7 +240,7 @@ export default class CreateMergeRequestDropdown {
|
|||
if (!ref) return false;
|
||||
|
||||
return axios
|
||||
.get(`${this.refsPath}${encodeURIComponent(ref)}`)
|
||||
.get(`${createEndpoint(this.projectPath, this.refsPath)}${encodeURIComponent(ref)}`)
|
||||
.then(({ data }) => {
|
||||
const branches = data[Object.keys(data)[0]];
|
||||
const tags = data[Object.keys(data)[1]];
|
||||
|
@ -325,6 +360,12 @@ export default class CreateMergeRequestDropdown {
|
|||
let xhr = null;
|
||||
event.preventDefault();
|
||||
|
||||
if (isConfidentialIssue() && !event.target.classList.contains('js-create-target')) {
|
||||
this.droplab.hooks.forEach(hook => hook.list.toggle());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isBusy()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ Vue.use(Translate);
|
|||
|
||||
export default () => {
|
||||
const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
|
||||
const cycleAnalyticsEl = document.querySelector('#cycle-analytics');
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
|
@ -33,7 +34,6 @@ export default () => {
|
|||
'stage-production-component': stageComponent,
|
||||
},
|
||||
data() {
|
||||
const cycleAnalyticsEl = document.querySelector('#cycle-analytics');
|
||||
const cycleAnalyticsService = new CycleAnalyticsService({
|
||||
requestPath: cycleAnalyticsEl.dataset.requestPath,
|
||||
});
|
||||
|
@ -56,7 +56,13 @@ export default () => {
|
|||
},
|
||||
},
|
||||
created() {
|
||||
this.fetchCycleAnalyticsData();
|
||||
// Conditional check placed here to prevent this method from being called on the
|
||||
// new Cycle Analytics page (i.e. the new page will be initialized blank and only
|
||||
// after a group is selected the cycle analyitcs data will be fetched). Once the
|
||||
// old (current) page has been removed this entire created method as well as the
|
||||
// variable itself can be completely removed.
|
||||
// Follow up issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/64490
|
||||
if (cycleAnalyticsEl.dataset.requestPath) this.fetchCycleAnalyticsData();
|
||||
},
|
||||
methods: {
|
||||
handleError() {
|
||||
|
|
|
@ -32,15 +32,15 @@ const CommentAndResolveBtn = Vue.extend({
|
|||
buttonText: function() {
|
||||
if (this.isDiscussionResolved) {
|
||||
if (this.textareaIsEmpty) {
|
||||
return __('Unresolve discussion');
|
||||
return __('Unresolve thread');
|
||||
} else {
|
||||
return __('Comment & unresolve discussion');
|
||||
return __('Comment & unresolve thread');
|
||||
}
|
||||
} else {
|
||||
if (this.textareaIsEmpty) {
|
||||
return __('Resolve discussion');
|
||||
return __('Resolve thread');
|
||||
} else {
|
||||
return __('Comment & resolve discussion');
|
||||
return __('Comment & resolve thread');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -49,6 +49,8 @@ export default {
|
|||
return this.author.id ? this.author.id : '';
|
||||
},
|
||||
authorUrl() {
|
||||
// TODO: when the vue i18n rules are merged need to disable @gitlab/i18n/no-non-i18n-strings
|
||||
// name: 'mailto:' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives
|
||||
return this.author.web_url || `mailto:${this.commit.author_email}`;
|
||||
},
|
||||
authorAvatar() {
|
||||
|
@ -80,7 +82,7 @@ export default {
|
|||
v-html="commit.title_html"
|
||||
></a>
|
||||
|
||||
<span class="commit-row-message d-block d-sm-none"> · {{ commit.short_id }} </span>
|
||||
<span class="commit-row-message d-block d-sm-none">· {{ commit.short_id }}</span>
|
||||
|
||||
<button
|
||||
v-if="commit.description_html"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import { n__, __ } from '~/locale';
|
||||
import { n__, __, sprintf } from '~/locale';
|
||||
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
|
||||
export default {
|
||||
|
@ -54,11 +54,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
commitsText(version) {
|
||||
return n__(
|
||||
`${version.commits_count} commit,`,
|
||||
`${version.commits_count} commits,`,
|
||||
version.commits_count,
|
||||
);
|
||||
return n__(`%d commit,`, `%d commits,`, version.commits_count);
|
||||
},
|
||||
href(version) {
|
||||
if (this.isBase(version)) {
|
||||
|
@ -76,7 +72,7 @@ export default {
|
|||
if (this.targetBranch && (this.isBase(version) || !version)) {
|
||||
return this.targetBranch.branchName;
|
||||
}
|
||||
return `version ${version.version_index}`;
|
||||
return sprintf(__(`version %{versionIndex}`), { versionIndex: version.version_index });
|
||||
},
|
||||
isActive(version) {
|
||||
if (!version) {
|
||||
|
@ -125,9 +121,9 @@ export default {
|
|||
<div>
|
||||
<strong>
|
||||
{{ versionName(version) }}
|
||||
<template v-if="isBase(version)">
|
||||
(base)
|
||||
</template>
|
||||
<template v-if="isBase(version)">{{
|
||||
s__('DiffsCompareBaseBranch|(base)')
|
||||
}}</template>
|
||||
</strong>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import NoteSignedOutWidget from '~/notes/components/note_signed_out_widget.vue';
|
||||
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
|
||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
|
||||
export default {
|
||||
name: 'DiffDiscussionReply',
|
||||
components: {
|
||||
NoteSignedOutWidget,
|
||||
ReplyPlaceholder,
|
||||
UserAvatarLink,
|
||||
},
|
||||
props: {
|
||||
hasForm: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
renderReplyPlaceholder: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'getUserData',
|
||||
userCanReply: 'userCanReply',
|
||||
}),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="discussion-reply-holder d-flex clearfix">
|
||||
<template v-if="userCanReply">
|
||||
<slot v-if="hasForm" name="form"></slot>
|
||||
<template v-else-if="renderReplyPlaceholder">
|
||||
<user-avatar-link
|
||||
:link-href="currentUser.path"
|
||||
:img-src="currentUser.avatar_url"
|
||||
:img-alt="currentUser.name"
|
||||
:img-size="40"
|
||||
class="d-none d-sm-block"
|
||||
/>
|
||||
<reply-placeholder
|
||||
class="qa-discussion-reply"
|
||||
:button-text="__('Start a new discussion...')"
|
||||
@onClick="$emit('showNewDiscussionForm')"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<note-signed-out-widget v-else />
|
||||
</div>
|
||||
</template>
|
|
@ -80,7 +80,6 @@ export default {
|
|||
v-show="isExpanded(discussion)"
|
||||
:discussion="discussion"
|
||||
:render-diff-file="false"
|
||||
:always-expanded="true"
|
||||
:discussions-by-diff-order="true"
|
||||
:line="line"
|
||||
:help-page-path="helpPagePath"
|
||||
|
|
|
@ -67,6 +67,18 @@ export default {
|
|||
errorMessage() {
|
||||
return this.file.viewer.error_message;
|
||||
},
|
||||
forkMessage() {
|
||||
return sprintf(
|
||||
__(
|
||||
"You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request.",
|
||||
),
|
||||
{
|
||||
tag_start: '<span class="js-file-fork-suggestion-section-action">',
|
||||
tag_end: '</span>',
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isCollapsed: function fileCollapsedWatch(newVal, oldVal) {
|
||||
|
@ -150,22 +162,18 @@ export default {
|
|||
/>
|
||||
|
||||
<div v-if="forkMessageVisible" class="js-file-fork-suggestion-section file-fork-suggestion">
|
||||
<span class="file-fork-suggestion-note">
|
||||
You're not allowed to <span class="js-file-fork-suggestion-section-action">edit</span> files
|
||||
in this project directly. Please fork this project, make your changes there, and submit a
|
||||
merge request.
|
||||
</span>
|
||||
<span class="file-fork-suggestion-note" v-html="forkMessage"></span>
|
||||
<a
|
||||
:href="file.fork_path"
|
||||
class="js-fork-suggestion-button btn btn-grouped btn-inverted btn-success"
|
||||
>Fork</a
|
||||
>{{ __('Fork') }}</a
|
||||
>
|
||||
<button
|
||||
class="js-cancel-fork-suggestion-button btn btn-grouped"
|
||||
type="button"
|
||||
@click="hideForkMessage"
|
||||
>
|
||||
Cancel
|
||||
{{ __('Cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
<gl-loading-icon v-if="showLoadingIcon" class="diff-content loading" />
|
||||
|
|
|
@ -151,7 +151,11 @@ export default {
|
|||
stickyMonitor(this.$refs.header, contentTop() - fileHeaderHeight - 1, false);
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['toggleFileDiscussions', 'toggleFullDiff']),
|
||||
...mapActions('diffs', [
|
||||
'toggleFileDiscussions',
|
||||
'toggleFileDiscussionWrappers',
|
||||
'toggleFullDiff',
|
||||
]),
|
||||
handleToggleFile(e, checkTarget) {
|
||||
if (
|
||||
!checkTarget ||
|
||||
|
@ -165,7 +169,7 @@ export default {
|
|||
this.$emit('showForkMessage');
|
||||
},
|
||||
handleToggleDiscussions() {
|
||||
this.toggleFileDiscussions(this.diffFile);
|
||||
this.toggleFileDiscussionWrappers(this.diffFile);
|
||||
},
|
||||
handleFileNameClick(e) {
|
||||
const isLinkToOtherPage =
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import { pluralize, truncate } from '~/lib/utils/text_utility';
|
||||
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
|
||||
|
@ -19,11 +18,13 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
discussionsExpanded: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
discussionsExpanded() {
|
||||
return this.discussions.every(discussion => discussion.expanded);
|
||||
},
|
||||
allDiscussions() {
|
||||
return this.discussions.reduce((acc, note) => acc.concat(note.notes), []);
|
||||
},
|
||||
|
@ -45,26 +46,14 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['toggleDiscussion']),
|
||||
getTooltipText(noteData) {
|
||||
let { note } = noteData;
|
||||
|
||||
if (note.length > LENGTH_OF_AVATAR_TOOLTIP) {
|
||||
note = truncate(note, LENGTH_OF_AVATAR_TOOLTIP);
|
||||
}
|
||||
|
||||
return `${noteData.author.name}: ${note}`;
|
||||
},
|
||||
toggleDiscussions() {
|
||||
const forceExpanded = this.discussions.some(discussion => !discussion.expanded);
|
||||
|
||||
this.discussions.forEach(discussion => {
|
||||
this.toggleDiscussion({
|
||||
discussionId: discussion.id,
|
||||
forceExpanded,
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -74,9 +63,9 @@ export default {
|
|||
<button
|
||||
v-if="discussionsExpanded"
|
||||
type="button"
|
||||
aria-label="Show comments"
|
||||
:aria-label="__('Show comments')"
|
||||
class="diff-notes-collapse js-diff-comment-avatar js-diff-comment-button"
|
||||
@click="toggleDiscussions"
|
||||
@click="$emit('toggleLineDiscussions')"
|
||||
>
|
||||
<icon :size="12" name="collapse" />
|
||||
</button>
|
||||
|
@ -87,7 +76,7 @@ export default {
|
|||
:img-src="note.author.avatar_url"
|
||||
:tooltip-text="getTooltipText(note)"
|
||||
class="diff-comment-avatar js-diff-comment-avatar"
|
||||
@click.native="toggleDiscussions"
|
||||
@click.native="$emit('toggleLineDiscussions')"
|
||||
/>
|
||||
<span
|
||||
v-if="moreText"
|
||||
|
@ -97,7 +86,7 @@ export default {
|
|||
data-container="body"
|
||||
data-placement="top"
|
||||
role="button"
|
||||
@click="toggleDiscussions"
|
||||
@click="$emit('toggleLineDiscussions')"
|
||||
>+{{ moreCount }}</span
|
||||
>
|
||||
</template>
|
||||
|
|
|
@ -105,7 +105,13 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['loadMoreLines', 'showCommentForm', 'setHighlightedRow']),
|
||||
...mapActions('diffs', [
|
||||
'loadMoreLines',
|
||||
'showCommentForm',
|
||||
'setHighlightedRow',
|
||||
'toggleLineDiscussions',
|
||||
'toggleLineDiscussionWrappers',
|
||||
]),
|
||||
handleCommentButton() {
|
||||
this.showCommentForm({ lineCode: this.line.line_code, fileHash: this.fileHash });
|
||||
},
|
||||
|
@ -184,7 +190,14 @@ export default {
|
|||
@click="setHighlightedRow(lineCode)"
|
||||
>
|
||||
</a>
|
||||
<diff-gutter-avatars v-if="shouldShowAvatarsOnGutter" :discussions="line.discussions" />
|
||||
<diff-gutter-avatars
|
||||
v-if="shouldShowAvatarsOnGutter"
|
||||
:discussions="line.discussions"
|
||||
:discussions-expanded="line.discussionsExpanded"
|
||||
@toggleLineDiscussions="
|
||||
toggleLineDiscussions({ lineCode, fileHash, expanded: !line.discussionsExpanded })
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<script>
|
||||
import diffDiscussions from './diff_discussions.vue';
|
||||
import diffLineNoteForm from './diff_line_note_form.vue';
|
||||
import { mapActions } from 'vuex';
|
||||
import DiffDiscussions from './diff_discussions.vue';
|
||||
import DiffLineNoteForm from './diff_line_note_form.vue';
|
||||
import DiffDiscussionReply from './diff_discussion_reply.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
diffDiscussions,
|
||||
diffLineNoteForm,
|
||||
DiffDiscussions,
|
||||
DiffLineNoteForm,
|
||||
DiffDiscussionReply,
|
||||
},
|
||||
props: {
|
||||
line: {
|
||||
|
@ -21,6 +24,11 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
hasDraft: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
className() {
|
||||
|
@ -32,10 +40,12 @@ export default {
|
|||
if (!this.line.discussions || !this.line.discussions.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.line.discussions.every(discussion => discussion.expanded);
|
||||
return this.line.discussionsExpanded;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['showCommentForm']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -49,13 +59,23 @@ export default {
|
|||
:discussions="line.discussions"
|
||||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
<diff-line-note-form
|
||||
v-if="line.hasForm"
|
||||
:diff-file-hash="diffFileHash"
|
||||
:line="line"
|
||||
:note-target-line="line"
|
||||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
<diff-discussion-reply
|
||||
v-if="!hasDraft"
|
||||
:has-form="line.hasForm"
|
||||
:render-reply-placeholder="Boolean(line.discussions.length)"
|
||||
@showNewDiscussionForm="
|
||||
showCommentForm({ lineCode: line.line_code, fileHash: diffFileHash })
|
||||
"
|
||||
>
|
||||
<template #form>
|
||||
<diff-line-note-form
|
||||
:diff-file-hash="diffFileHash"
|
||||
:line="line"
|
||||
:note-target-line="line"
|
||||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
</template>
|
||||
</diff-discussion-reply>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -57,6 +57,7 @@ export default {
|
|||
:diff-file-hash="diffFile.file_hash"
|
||||
:line="line"
|
||||
:help-page-path="helpPagePath"
|
||||
:has-draft="shouldRenderDraftRow(diffFile.file_hash, line) || false"
|
||||
/>
|
||||
<inline-draft-comment-row
|
||||
v-if="shouldRenderDraftRow(diffFile.file_hash, line)"
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<script>
|
||||
import diffDiscussions from './diff_discussions.vue';
|
||||
import diffLineNoteForm from './diff_line_note_form.vue';
|
||||
import { mapActions } from 'vuex';
|
||||
import DiffDiscussions from './diff_discussions.vue';
|
||||
import DiffLineNoteForm from './diff_line_note_form.vue';
|
||||
import DiffDiscussionReply from './diff_discussion_reply.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
diffDiscussions,
|
||||
diffLineNoteForm,
|
||||
DiffDiscussions,
|
||||
DiffLineNoteForm,
|
||||
DiffDiscussionReply,
|
||||
},
|
||||
props: {
|
||||
line: {
|
||||
|
@ -25,28 +28,44 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
hasDraftLeft: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
hasDraftRight: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hasExpandedDiscussionOnLeft() {
|
||||
return this.line.left && this.line.left.discussions.length
|
||||
? this.line.left.discussions.every(discussion => discussion.expanded)
|
||||
? this.line.left.discussionsExpanded
|
||||
: false;
|
||||
},
|
||||
hasExpandedDiscussionOnRight() {
|
||||
return this.line.right && this.line.right.discussions.length
|
||||
? this.line.right.discussions.every(discussion => discussion.expanded)
|
||||
? this.line.right.discussionsExpanded
|
||||
: false;
|
||||
},
|
||||
hasAnyExpandedDiscussion() {
|
||||
return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight;
|
||||
},
|
||||
shouldRenderDiscussionsOnLeft() {
|
||||
return this.line.left && this.line.left.discussions && this.hasExpandedDiscussionOnLeft;
|
||||
return (
|
||||
this.line.left &&
|
||||
this.line.left.discussions &&
|
||||
this.line.left.discussions.length &&
|
||||
this.hasExpandedDiscussionOnLeft
|
||||
);
|
||||
},
|
||||
shouldRenderDiscussionsOnRight() {
|
||||
return (
|
||||
this.line.right &&
|
||||
this.line.right.discussions &&
|
||||
this.line.right.discussions.length &&
|
||||
this.hasExpandedDiscussionOnRight &&
|
||||
this.line.right.type
|
||||
);
|
||||
|
@ -81,6 +100,22 @@ export default {
|
|||
|
||||
return hasCommentFormOnLeft || hasCommentFormOnRight;
|
||||
},
|
||||
shouldRenderReplyPlaceholderOnLeft() {
|
||||
return Boolean(
|
||||
this.line.left && this.line.left.discussions && this.line.left.discussions.length,
|
||||
);
|
||||
},
|
||||
shouldRenderReplyPlaceholderOnRight() {
|
||||
return Boolean(
|
||||
this.line.right && this.line.right.discussions && this.line.right.discussions.length,
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['showCommentForm']),
|
||||
showNewDiscussionForm() {
|
||||
this.showCommentForm({ lineCode: this.line.line_code, fileHash: this.diffFileHash });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -90,37 +125,51 @@ export default {
|
|||
<td class="notes-content parallel old" colspan="2">
|
||||
<div v-if="shouldRenderDiscussionsOnLeft" class="content">
|
||||
<diff-discussions
|
||||
v-if="line.left.discussions.length"
|
||||
:discussions="line.left.discussions"
|
||||
:line="line.left"
|
||||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
</div>
|
||||
<diff-line-note-form
|
||||
v-if="showLeftSideCommentForm"
|
||||
:diff-file-hash="diffFileHash"
|
||||
:line="line.left"
|
||||
:note-target-line="line.left"
|
||||
:help-page-path="helpPagePath"
|
||||
line-position="left"
|
||||
/>
|
||||
<diff-discussion-reply
|
||||
v-if="!hasDraftLeft"
|
||||
:has-form="showLeftSideCommentForm"
|
||||
:render-reply-placeholder="shouldRenderReplyPlaceholderOnLeft"
|
||||
@showNewDiscussionForm="showNewDiscussionForm"
|
||||
>
|
||||
<template #form>
|
||||
<diff-line-note-form
|
||||
:diff-file-hash="diffFileHash"
|
||||
:line="line.left"
|
||||
:note-target-line="line.left"
|
||||
:help-page-path="helpPagePath"
|
||||
line-position="left"
|
||||
/>
|
||||
</template>
|
||||
</diff-discussion-reply>
|
||||
</td>
|
||||
<td class="notes-content parallel new" colspan="2">
|
||||
<div v-if="shouldRenderDiscussionsOnRight" class="content">
|
||||
<diff-discussions
|
||||
v-if="line.right.discussions.length"
|
||||
:discussions="line.right.discussions"
|
||||
:line="line.right"
|
||||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
</div>
|
||||
<diff-line-note-form
|
||||
v-if="showRightSideCommentForm"
|
||||
:diff-file-hash="diffFileHash"
|
||||
:line="line.right"
|
||||
:note-target-line="line.right"
|
||||
line-position="right"
|
||||
/>
|
||||
<diff-discussion-reply
|
||||
v-if="!hasDraftRight"
|
||||
:has-form="showRightSideCommentForm"
|
||||
:render-reply-placeholder="shouldRenderReplyPlaceholderOnRight"
|
||||
@showNewDiscussionForm="showNewDiscussionForm"
|
||||
>
|
||||
<template #form>
|
||||
<diff-line-note-form
|
||||
:diff-file-hash="diffFileHash"
|
||||
:line="line.right"
|
||||
:note-target-line="line.right"
|
||||
line-position="right"
|
||||
/>
|
||||
</template>
|
||||
</diff-discussion-reply>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
|
|
@ -58,6 +58,8 @@ export default {
|
|||
:diff-file-hash="diffFile.file_hash"
|
||||
:line-index="index"
|
||||
:help-page-path="helpPagePath"
|
||||
:has-draft-left="hasParallelDraftLeft(diffFile.file_hash, line) || false"
|
||||
:has-draft-right="hasParallelDraftRight(diffFile.file_hash, line) || false"
|
||||
/>
|
||||
<parallel-draft-comment-row
|
||||
v-if="shouldRenderParallelDraftRow(diffFile.file_hash, line)"
|
||||
|
|
|
@ -6,5 +6,7 @@ export default {
|
|||
imageDiscussions() {
|
||||
return this.diffFile.discussions;
|
||||
},
|
||||
hasParallelDraftLeft: () => () => false,
|
||||
hasParallelDraftRight: () => () => false,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
getNoteFormData,
|
||||
convertExpandLines,
|
||||
idleCallback,
|
||||
allDiscussionWrappersExpanded,
|
||||
} from './utils';
|
||||
import * as types from './mutation_types';
|
||||
import {
|
||||
|
@ -79,6 +80,7 @@ export const assignDiscussionsToDiff = (
|
|||
discussions = rootState.notes.discussions,
|
||||
) => {
|
||||
const diffPositionByLineCode = getDiffPositionByLineCode(state.diffFiles);
|
||||
const hash = getLocationHash();
|
||||
|
||||
discussions
|
||||
.filter(discussion => discussion.diff_discussion)
|
||||
|
@ -86,6 +88,7 @@ export const assignDiscussionsToDiff = (
|
|||
commit(types.SET_LINE_DISCUSSIONS_FOR_FILE, {
|
||||
discussion,
|
||||
diffPositionByLineCode,
|
||||
hash,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -99,6 +102,10 @@ export const removeDiscussionsFromDiff = ({ commit }, removeDiscussion) => {
|
|||
commit(types.REMOVE_LINE_DISCUSSIONS_FOR_FILE, { fileHash: file_hash, lineCode: line_code, id });
|
||||
};
|
||||
|
||||
export const toggleLineDiscussions = ({ commit }, options) => {
|
||||
commit(types.TOGGLE_LINE_DISCUSSIONS, options);
|
||||
};
|
||||
|
||||
export const renderFileForDiscussionId = ({ commit, rootState, state }, discussionId) => {
|
||||
const discussion = rootState.notes.discussions.find(d => d.id === discussionId);
|
||||
|
||||
|
@ -257,6 +264,31 @@ export const toggleFileDiscussions = ({ getters, dispatch }, diff) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const toggleFileDiscussionWrappers = ({ commit }, diff) => {
|
||||
const discussionWrappersExpanded = allDiscussionWrappersExpanded(diff);
|
||||
let linesWithDiscussions;
|
||||
if (diff.highlighted_diff_lines) {
|
||||
linesWithDiscussions = diff.highlighted_diff_lines.filter(line => line.discussions.length);
|
||||
}
|
||||
if (diff.parallel_diff_lines) {
|
||||
linesWithDiscussions = diff.parallel_diff_lines.filter(
|
||||
line =>
|
||||
(line.left && line.left.discussions.length) ||
|
||||
(line.right && line.right.discussions.length),
|
||||
);
|
||||
}
|
||||
|
||||
if (linesWithDiscussions.length) {
|
||||
linesWithDiscussions.forEach(line => {
|
||||
commit(types.TOGGLE_LINE_DISCUSSIONS, {
|
||||
fileHash: diff.file_hash,
|
||||
lineCode: line.line_code,
|
||||
expanded: !discussionWrappersExpanded,
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const saveDiffDiscussion = ({ state, dispatch }, { note, formData }) => {
|
||||
const postData = getNoteFormData({
|
||||
commit: state.commit,
|
||||
|
@ -267,7 +299,7 @@ export const saveDiffDiscussion = ({ state, dispatch }, { note, formData }) => {
|
|||
return dispatch('saveNote', postData, { root: true })
|
||||
.then(result => dispatch('updateDiscussion', result.discussion, { root: true }))
|
||||
.then(discussion => dispatch('assignDiscussionsToDiff', [discussion]))
|
||||
.then(() => dispatch('updateResolvableDiscussonsCounts', null, { root: true }))
|
||||
.then(() => dispatch('updateResolvableDiscussionsCounts', null, { root: true }))
|
||||
.then(() => dispatch('closeDiffFileCommentForm', formData.diffFile.file_hash))
|
||||
.catch(() => createFlash(s__('MergeRequests|Saving the comment failed')));
|
||||
};
|
||||
|
|
|
@ -35,3 +35,5 @@ export const ADD_CURRENT_VIEW_DIFF_FILE_LINES = 'ADD_CURRENT_VIEW_DIFF_FILE_LINE
|
|||
export const TOGGLE_DIFF_FILE_RENDERING_MORE = 'TOGGLE_DIFF_FILE_RENDERING_MORE';
|
||||
|
||||
export const SET_SHOW_SUGGEST_POPOVER = 'SET_SHOW_SUGGEST_POPOVER';
|
||||
|
||||
export const TOGGLE_LINE_DISCUSSIONS = 'TOGGLE_LINE_DISCUSSIONS';
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
addContextLines,
|
||||
prepareDiffData,
|
||||
isDiscussionApplicableToLine,
|
||||
updateLineInFile,
|
||||
} from './utils';
|
||||
import * as types from './mutation_types';
|
||||
|
||||
|
@ -109,7 +110,7 @@ export default {
|
|||
}));
|
||||
},
|
||||
|
||||
[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { discussion, diffPositionByLineCode }) {
|
||||
[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { discussion, diffPositionByLineCode, hash }) {
|
||||
const { latestDiff } = state;
|
||||
|
||||
const discussionLineCode = discussion.line_code;
|
||||
|
@ -130,13 +131,27 @@ export default {
|
|||
: [],
|
||||
});
|
||||
|
||||
const setDiscussionsExpanded = line => {
|
||||
const isLineNoteTargeted = line.discussions.some(
|
||||
disc => disc.notes && disc.notes.find(note => hash === `note_${note.id}`),
|
||||
);
|
||||
|
||||
return {
|
||||
...line,
|
||||
discussionsExpanded:
|
||||
line.discussions && line.discussions.length
|
||||
? line.discussions.some(disc => !disc.resolved) || isLineNoteTargeted
|
||||
: false,
|
||||
};
|
||||
};
|
||||
|
||||
state.diffFiles = state.diffFiles.map(diffFile => {
|
||||
if (diffFile.file_hash === fileHash) {
|
||||
const file = { ...diffFile };
|
||||
|
||||
if (file.highlighted_diff_lines) {
|
||||
file.highlighted_diff_lines = file.highlighted_diff_lines.map(line =>
|
||||
lineCheck(line) ? mapDiscussions(line) : line,
|
||||
setDiscussionsExpanded(lineCheck(line) ? mapDiscussions(line) : line),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -148,8 +163,10 @@ export default {
|
|||
if (left || right) {
|
||||
return {
|
||||
...line,
|
||||
left: line.left ? mapDiscussions(line.left) : null,
|
||||
right: line.right ? mapDiscussions(line.right, () => !left) : null,
|
||||
left: line.left ? setDiscussionsExpanded(mapDiscussions(line.left)) : null,
|
||||
right: line.right
|
||||
? setDiscussionsExpanded(mapDiscussions(line.right, () => !left))
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -173,32 +190,11 @@ export default {
|
|||
[types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode }) {
|
||||
const selectedFile = state.diffFiles.find(f => f.file_hash === fileHash);
|
||||
if (selectedFile) {
|
||||
if (selectedFile.parallel_diff_lines) {
|
||||
const targetLine = selectedFile.parallel_diff_lines.find(
|
||||
line =>
|
||||
(line.left && line.left.line_code === lineCode) ||
|
||||
(line.right && line.right.line_code === lineCode),
|
||||
);
|
||||
if (targetLine) {
|
||||
const side = targetLine.left && targetLine.left.line_code === lineCode ? 'left' : 'right';
|
||||
|
||||
Object.assign(targetLine[side], {
|
||||
discussions: targetLine[side].discussions.filter(discussion => discussion.notes.length),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedFile.highlighted_diff_lines) {
|
||||
const targetInlineLine = selectedFile.highlighted_diff_lines.find(
|
||||
line => line.line_code === lineCode,
|
||||
);
|
||||
|
||||
if (targetInlineLine) {
|
||||
Object.assign(targetInlineLine, {
|
||||
discussions: targetInlineLine.discussions.filter(discussion => discussion.notes.length),
|
||||
});
|
||||
}
|
||||
}
|
||||
updateLineInFile(selectedFile, lineCode, line =>
|
||||
Object.assign(line, {
|
||||
discussions: line.discussions.filter(discussion => discussion.notes.length),
|
||||
}),
|
||||
);
|
||||
|
||||
if (selectedFile.discussions && selectedFile.discussions.length) {
|
||||
selectedFile.discussions = selectedFile.discussions.filter(
|
||||
|
@ -207,6 +203,15 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
[types.TOGGLE_LINE_DISCUSSIONS](state, { fileHash, lineCode, expanded }) {
|
||||
const selectedFile = state.diffFiles.find(f => f.file_hash === fileHash);
|
||||
|
||||
updateLineInFile(selectedFile, lineCode, line =>
|
||||
Object.assign(line, { discussionsExpanded: expanded }),
|
||||
);
|
||||
},
|
||||
|
||||
[types.TOGGLE_FOLDER_OPEN](state, path) {
|
||||
state.treeEntries[path].opened = !state.treeEntries[path].opened;
|
||||
},
|
||||
|
|
|
@ -454,3 +454,48 @@ export const convertExpandLines = ({
|
|||
};
|
||||
|
||||
export const idleCallback = cb => requestIdleCallback(cb);
|
||||
|
||||
export const updateLineInFile = (selectedFile, lineCode, updateFn) => {
|
||||
if (selectedFile.parallel_diff_lines) {
|
||||
const targetLine = selectedFile.parallel_diff_lines.find(
|
||||
line =>
|
||||
(line.left && line.left.line_code === lineCode) ||
|
||||
(line.right && line.right.line_code === lineCode),
|
||||
);
|
||||
if (targetLine) {
|
||||
const side = targetLine.left && targetLine.left.line_code === lineCode ? 'left' : 'right';
|
||||
|
||||
updateFn(targetLine[side]);
|
||||
}
|
||||
}
|
||||
if (selectedFile.highlighted_diff_lines) {
|
||||
const targetInlineLine = selectedFile.highlighted_diff_lines.find(
|
||||
line => line.line_code === lineCode,
|
||||
);
|
||||
|
||||
if (targetInlineLine) {
|
||||
updateFn(targetInlineLine);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const allDiscussionWrappersExpanded = diff => {
|
||||
const discussionsExpandedArray = [];
|
||||
if (diff.parallel_diff_lines) {
|
||||
diff.parallel_diff_lines.forEach(line => {
|
||||
if (line.left && line.left.discussions.length) {
|
||||
discussionsExpandedArray.push(line.left.discussionsExpanded);
|
||||
}
|
||||
if (line.right && line.right.discussions.length) {
|
||||
discussionsExpandedArray.push(line.right.discussionsExpanded);
|
||||
}
|
||||
});
|
||||
} else if (diff.highlighted_diff_lines) {
|
||||
diff.parallel_diff_lines.forEach(line => {
|
||||
if (line.discussions.length) {
|
||||
discussionsExpandedArray.push(line.discussionsExpanded);
|
||||
}
|
||||
});
|
||||
}
|
||||
return discussionsExpandedArray.every(el => el);
|
||||
};
|
||||
|
|
|
@ -57,6 +57,7 @@ export default {
|
|||
:user-callouts-path="userCalloutsPath"
|
||||
:lock-promotion-svg-path="lockPromotionSvgPath"
|
||||
:help-canary-deployments-path="helpCanaryDeploymentsPath"
|
||||
:deploy-boards-help-path="deployBoardsHelpPath"
|
||||
/>
|
||||
|
||||
<table-pagination
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import { formatTime } from '~/lib/utils/datetime_utility';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import eventHub from '../event_hub';
|
||||
|
@ -28,7 +28,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
title() {
|
||||
return 'Deploy to...';
|
||||
return __('Deploy to...');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -80,7 +80,8 @@ export default {
|
|||
data-toggle="dropdown"
|
||||
>
|
||||
<span>
|
||||
<icon name="play" /> <icon name="chevron-down" />
|
||||
<icon name="play" />
|
||||
<icon name="chevron-down" />
|
||||
<gl-loading-icon v-if="isLoading" />
|
||||
</span>
|
||||
</button>
|
||||
|
@ -94,9 +95,10 @@ export default {
|
|||
class="js-manual-action-link no-btn btn d-flex align-items-center"
|
||||
@click="onClickAction(action)"
|
||||
>
|
||||
<span class="flex-fill"> {{ action.name }} </span>
|
||||
<span class="flex-fill">{{ action.name }}</span>
|
||||
<span v-if="action.scheduledAt" class="text-secondary">
|
||||
<icon name="clock" /> {{ remainingTime(action) }}
|
||||
<icon name="clock" />
|
||||
{{ remainingTime(action) }}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { __, sprintf } from '~/locale';
|
||||
import Timeago from 'timeago.js';
|
||||
import _ from 'underscore';
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
|
@ -14,7 +15,6 @@ import MonitoringButtonComponent from './environment_monitoring.vue';
|
|||
import CommitComponent from '../../vue_shared/components/commit.vue';
|
||||
import eventHub from '../event_hub';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import { CLUSTER_TYPE } from '~/clusters/constants';
|
||||
|
||||
/**
|
||||
* Environment Item Component
|
||||
|
@ -79,15 +79,6 @@ export default {
|
|||
return this.model && this.model.is_protected;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide group cluster features which are not currently implemented.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
disableGroupClusterFeatures() {
|
||||
return this.model && this.model.cluster_type === CLUSTER_TYPE.GROUP;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns whether the environment can be stopped.
|
||||
*
|
||||
|
@ -172,7 +163,9 @@ export default {
|
|||
this.model.last_deployment.user &&
|
||||
this.model.last_deployment.user.username
|
||||
) {
|
||||
return `${this.model.last_deployment.user.username}'s avatar'`;
|
||||
return sprintf(__("%{username}'s avatar"), {
|
||||
username: this.model.last_deployment.user.username,
|
||||
});
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
@ -293,6 +286,9 @@ export default {
|
|||
* @returns {Boolean|Undefined}
|
||||
*/
|
||||
isLastDeployment() {
|
||||
// TODO: when the vue i18n rules are merged need to disable @gitlab/i18n/no-non-i18n-strings
|
||||
// name: 'last?' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives
|
||||
// Vue i18n ESLint rules issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/63560
|
||||
return this.model && this.model.last_deployment && this.model.last_deployment['last?'];
|
||||
},
|
||||
|
||||
|
@ -575,7 +571,6 @@ export default {
|
|||
<terminal-button-component
|
||||
v-if="model && model.terminal_path"
|
||||
:terminal-path="model.terminal_path"
|
||||
:disabled="disableGroupClusterFeatures"
|
||||
/>
|
||||
|
||||
<rollback-component
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { __ } from '~/locale';
|
||||
/**
|
||||
* Renders the Monitoring (Metrics) link in environments table.
|
||||
*/
|
||||
|
@ -21,7 +22,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
title() {
|
||||
return 'Monitoring';
|
||||
return __('Monitoring');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -27,7 +28,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
title() {
|
||||
return 'Terminal';
|
||||
return __('Terminal');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -43,6 +43,11 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
deployBoardsHelpPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
|
@ -112,6 +117,7 @@ export default {
|
|||
:user-callouts-path="userCalloutsPath"
|
||||
:lock-promotion-svg-path="lockPromotionSvgPath"
|
||||
:help-canary-deployments-path="helpCanaryDeploymentsPath"
|
||||
:deploy-boards-help-path="deployBoardsHelpPath"
|
||||
@onChangePage="onChangePage"
|
||||
>
|
||||
<empty-state
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue