Update upstream source from tag 'upstream/13.7.7'

Update to upstream version '13.7.7'
with Debian dir 2ce297e60a
This commit is contained in:
Pirate Praveen 2021-02-22 17:31:28 +05:30
commit 8d88b2aefc
5720 changed files with 226111 additions and 84446 deletions

View file

@ -42,6 +42,7 @@ rules:
no-jquery/no-serialize: error
promise/always-return: off
promise/no-callback-in-promise: off
"@gitlab/no-global-event-off": error
overrides:
- files:
- '**/spec/**/*'

1
.gitattributes vendored
View file

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

1
.gitignore vendored
View file

@ -77,6 +77,7 @@ eslint-report.html
/.gitlab_kas_secret
/webpack-report/
/crystalball/
/deprecations/
/knapsack/
/rspec_flaky/
/locale/**/LC_MESSAGES

View file

@ -109,3 +109,4 @@ include:
- local: .gitlab/ci/releases.gitlab-ci.yml
- local: .gitlab/ci/notify.gitlab-ci.yml
- local: .gitlab/ci/dast.gitlab-ci.yml
- local: .gitlab/ci/workhorse.gitlab-ci.yml

View file

@ -147,6 +147,8 @@
/ee/spec/javascripts/ @gitlab-org/maintainers/frontend
/spec/frontend/ @gitlab-org/maintainers/frontend
/ee/spec/frontend/ @gitlab-org/maintainers/frontend
/spec/frontend_integration/ @gitlab-org/maintainers/frontend
/ee/spec/frontend_integration/ @gitlab-org/maintainers/frontend
[Database]
/db/ @gitlab-org/maintainers/database
@ -159,6 +161,7 @@
/lib/gitlab/github_import/ @gitlab-org/maintainers/database
/app/finders/ @gitlab-org/maintainers/database
/ee/app/finders/ @gitlab-org/maintainers/database
/rubocop/rubocop-migrations.yml @gitlab-org/maintainers/database
[Engineering Productivity]
/.gitlab-ci.yml @gl-quality/eng-prod
@ -194,12 +197,17 @@ Dangerfile @gl-quality/eng-prod
# Secure & Threat Management ownership delineation
# https://about.gitlab.com/handbook/engineering/development/threat-management/delineate-secure-threat-management.html#technical-boundaries
[Secure]
[Threat Insights]
/ee/app/finders/security/ @gitlab-org/secure/threat-insights-backend-team
/ee/app/models/security/ @gitlab-org/secure/threat-insights-backend-team
/ee/app/models/vulnerabilities/ @gitlab-org/secure/threat-insights-backend-team
/ee/app/models/vulnerability.rb @gitlab-org/secure/threat-insights-backend-team
/ee/app/policies/vulnerabilities/ @gitlab-org/secure/threat-insights-backend-team
/ee/app/policies/vulnerability*.rb @gitlab-org/secure/threat-insights-backend-team
/ee/lib/api/vulnerabilit*.rb @gitlab-org/secure/threat-insights-backend-team
/ee/spec/policies/vulnerabilities/ @gitlab-org/secure/threat-insights-backend-team
/ee/spec/policies/vulnerabilities/vulnerability*.rb @gitlab-org/secure/threat-insights-backend-team
[Secure]
/ee/lib/gitlab/ci/parsers/license_compliance/ @gitlab-org/secure/composition-analysis-be
/ee/lib/gitlab/ci/parsers/security/ @gitlab-org/secure/composition-analysis-be @gitlab-org/secure/dynamic-analysis-be @gitlab-org/secure/static-analysis-be @gitlab-org/secure/fuzzing-be
/ee/lib/gitlab/ci/reports/coverage_fuzzing/ @gitlab-org/secure/fuzzing-be

View file

@ -8,9 +8,9 @@
needs: ["setup-test-env"]
variables:
FIXTURE_PATH: "db/fixtures/development"
SEED_CYCLE_ANALYTICS: "true"
SEED_VSA: "true"
SEED_PRODUCTIVITY_ANALYTICS: "true"
CYCLE_ANALYTICS_ISSUE_COUNT: 1
VSA_ISSUE_COUNT: 1
SIZE: 0 # number of external projects to fork, requires network connection
# SEED_NESTED_GROUPS: "false" # requires network connection

View file

@ -53,7 +53,7 @@ docs-lint links:
extends:
- .default-retry
- .docs:rules:docs-lint
image: "registry.gitlab.com/gitlab-org/gitlab-docs/lint:ruby-2.7.2-alpine-3.12-vale-2.4.3-markdownlint-0.24.0"
image: "registry.gitlab.com/gitlab-org/gitlab-docs/lint-html:alpine-3.12-ruby-2.7.2"
stage: test
needs: []
script:
@ -66,6 +66,13 @@ docs-lint links:
- bundle exec nanoc
# Check the internal links
- bundle exec nanoc check internal_links
# Delete the redirect files, rebuild, and check internal links again, to see if we are linking to redirects.
# Don't delete the documentation/index.md, which is a false positive for the simple grep.
- grep -rl "redirect_to:" /tmp/gitlab-docs/content/ee/ | grep -v "development/documentation/index.md" | xargs rm -f
- bundle exec nanoc
- echo -e "\e[1;96mThe following test fails when a doc links to a redirect file."
- echo -e "\e[1;96mMake sure all links point to the correct page."
- bundle exec nanoc check internal_links
# Check the internal anchor links
- bundle exec nanoc check internal_anchors

View file

@ -14,6 +14,10 @@
- run_timed_command "scripts/gitaly-test-spawn"
- source ./scripts/rspec_helpers.sh
.minimal-rspec-tests:
variables:
RSPEC_TESTS_MAPPING_ENABLED: "true"
.rspec-base:
extends: .rails-job-base
stage: test
@ -21,7 +25,8 @@
RUBY_GC_MALLOC_LIMIT: 67108864
RUBY_GC_MALLOC_LIMIT_MAX: 134217728
CRYSTALBALL: "true"
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
RECORD_DEPRECATIONS: "true"
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets", "detect-tests"]
script:
- *base-script
- rspec_paralellized_job "--tag ~quarantine --tag ~geo --tag ~level:migration"
@ -31,6 +36,7 @@
paths:
- coverage/
- crystalball/
- deprecations/
- knapsack/
- rspec_flaky/
- rspec_profiling/
@ -62,7 +68,7 @@
- .rspec-base
- .as-if-foss
- .use-pg11
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets as-if-foss"]
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets as-if-foss", "detect-tests"]
.rspec-ee-base-pg11:
extends:
@ -238,24 +244,48 @@ rspec migration pg11:
- .rspec-base-migration
- .rspec-migration-parallel
rspec migration pg11 minimal:
extends:
- rspec migration pg11
- .minimal-rspec-tests
- .rails:rules:ee-and-foss-migration:minimal
rspec unit pg11:
extends:
- .rspec-base-pg11
- .rails:rules:ee-and-foss-unit
- .rspec-unit-parallel
rspec unit pg11 minimal:
extends:
- rspec unit pg11
- .minimal-rspec-tests
- .rails:rules:ee-and-foss-unit:minimal
rspec integration pg11:
extends:
- .rspec-base-pg11
- .rails:rules:ee-and-foss-integration
- .rspec-integration-parallel
rspec integration pg11 minimal:
extends:
- rspec integration pg11
- .minimal-rspec-tests
- .rails:rules:ee-and-foss-integration:minimal
rspec system pg11:
extends:
- .rspec-base-pg11
- .rails:rules:ee-and-foss-system
- .rspec-system-parallel
rspec system pg11 minimal:
extends:
- rspec system pg11
- .minimal-rspec-tests
- .rails:rules:ee-and-foss-system:minimal
rspec fast_spec_helper:
extends:
- .rspec-base-pg11
@ -263,6 +293,12 @@ rspec fast_spec_helper:
script:
- bin/rspec spec/fast_spec_helper.rb
rspec fast_spec_helper minimal:
extends:
- rspec fast_spec_helper
- .minimal-rspec-tests
- .rails:rules:ee-and-foss-fast_spec_helper:minimal
db:migrate:reset:
extends: .db-job-base
script:
@ -284,7 +320,7 @@ db:migrate-from-v12.10.0:
- export TAG_TO_CHECKOUT="v12.10.0-ee"
- '[[ -d "ee/" ]] || export PROJECT_TO_CHECKOUT="gitlab-foss"'
- '[[ -d "ee/" ]] || export TAG_TO_CHECKOUT="v12.10.0"'
- git fetch https://gitlab.com/gitlab-org/$PROJECT_TO_CHECKOUT.git $TAG_TO_CHECKOUT
- retry 'git fetch https://gitlab.com/gitlab-org/$PROJECT_TO_CHECKOUT.git $TAG_TO_CHECKOUT'
- git checkout -f FETCH_HEAD
- sed -i -e "s/gem 'grpc', '~> 1.24.0'/gem 'grpc', '~> 1.30.2'/" Gemfile # Update gRPC for Ruby 2.7
- sed -i -e "s/gem 'google-protobuf', '~> 3.8.0'/gem 'google-protobuf', '~> 3.12.0'/" Gemfile
@ -382,6 +418,7 @@ rspec:feature-flags:
- .coverage-base
- .rails:rules:rspec-feature-flags
stage: post-test
allow_failure: true
# We cannot use needs since it would mean needing 84 jobs (since most are parallelized)
# so we use `dependencies` here.
dependencies:
@ -401,7 +438,8 @@ rspec:feature-flags:
- memory-on-boot
script:
- run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --without default development test production puma unicorn kerberos metrics omnibus ed25519"
- run_timed_command "bundle exec scripts/used-feature-flags"
- 'run_timed_command "bundle exec scripts/used-feature-flags" || (scripts/slack master-broken "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL}" ci_failing "GitLab Bot" && exit 1)'
# EE/FOSS: default refs (MRs, master, schedules) jobs #
#######################################################
@ -414,24 +452,48 @@ rspec migration pg11-as-if-foss:
- .rails:rules:as-if-foss-migration
- .rspec-migration-parallel
rspec migration pg11-as-if-foss minimal:
extends:
- rspec migration pg11-as-if-foss
- .minimal-rspec-tests
- .rails:rules:as-if-foss-migration:minimal
rspec unit pg11-as-if-foss:
extends:
- .rspec-base-pg11-as-if-foss
- .rails:rules:as-if-foss-unit
- .rspec-unit-parallel
rspec unit pg11-as-if-foss minimal:
extends:
- rspec unit pg11-as-if-foss
- .minimal-rspec-tests
- .rails:rules:as-if-foss-unit:minimal
rspec integration pg11-as-if-foss:
extends:
- .rspec-base-pg11-as-if-foss
- .rails:rules:as-if-foss-integration
- .rspec-integration-parallel
rspec integration pg11-as-if-foss minimal:
extends:
- rspec integration pg11-as-if-foss
- .minimal-rspec-tests
- .rails:rules:as-if-foss-integration:minimal
rspec system pg11-as-if-foss:
extends:
- .rspec-base-pg11-as-if-foss
- .rails:rules:as-if-foss-system
- .rspec-system-parallel
rspec system pg11-as-if-foss minimal:
extends:
- rspec system pg11-as-if-foss
- .minimal-rspec-tests
- .rails:rules:as-if-foss-system:minimal
rspec-ee migration pg11:
extends:
- .rspec-ee-base-pg11
@ -439,40 +501,82 @@ rspec-ee migration pg11:
- .rails:rules:ee-only-migration
- .rspec-ee-migration-parallel
rspec-ee migration pg11 minimal:
extends:
- rspec-ee migration pg11
- .minimal-rspec-tests
- .rails:rules:ee-only-migration:minimal
rspec-ee unit pg11:
extends:
- .rspec-ee-base-pg11
- .rails:rules:ee-only-unit
- .rspec-ee-unit-parallel
rspec-ee unit pg11 minimal:
extends:
- rspec-ee unit pg11
- .minimal-rspec-tests
- .rails:rules:ee-only-unit:minimal
rspec-ee integration pg11:
extends:
- .rspec-ee-base-pg11
- .rails:rules:ee-only-integration
- .rspec-ee-integration-parallel
rspec-ee integration pg11 minimal:
extends:
- rspec-ee integration pg11
- .minimal-rspec-tests
- .rails:rules:ee-only-integration:minimal
rspec-ee system pg11:
extends:
- .rspec-ee-base-pg11
- .rails:rules:ee-only-system
- .rspec-ee-system-parallel
rspec-ee system pg11 minimal:
extends:
- rspec-ee system pg11
- .minimal-rspec-tests
- .rails:rules:ee-only-system:minimal
rspec-ee unit pg11 geo:
extends:
- .rspec-ee-base-geo-pg11
- .rails:rules:ee-only-unit
- .rspec-ee-unit-geo-parallel
rspec-ee unit pg11 geo minimal:
extends:
- rspec-ee unit pg11 geo
- .minimal-rspec-tests
- .rails:rules:ee-only-unit:minimal
rspec-ee integration pg11 geo:
extends:
- .rspec-ee-base-geo-pg11
- .rails:rules:ee-only-integration
rspec-ee integration pg11 geo minimal:
extends:
- rspec-ee integration pg11 geo
- .minimal-rspec-tests
- .rails:rules:ee-only-integration:minimal
rspec-ee system pg11 geo:
extends:
- .rspec-ee-base-geo-pg11
- .rails:rules:ee-only-system
rspec-ee system pg11 geo minimal:
extends:
- rspec-ee system pg11 geo
- .minimal-rspec-tests
- .rails:rules:ee-only-system:minimal
db:rollback geo:
extends:
- db:rollback

View file

@ -145,6 +145,10 @@ dependency_scanning:
--volume "$PWD:/code" \
--volume /var/run/docker.sock:/var/run/docker.sock \
"registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$DS_MAJOR_VERSION" /code
# Post-processing: This will be an after_script once this job will use the Dependency Scanning CI template
- apk add jq
# Lower execa severity based on https://gitlab.com/gitlab-org/gitlab/-/issues/223859#note_452922390
- jq '(.vulnerabilities[] | select (.cve == "yarn.lock:execa:gemnasium:05cfa2e8-2d0c-42c1-8894-638e2f12ff3d")).severity = "Medium"' gl-dependency-scanning-report.json > temp.json && mv temp.json gl-dependency-scanning-report.json
artifacts:
paths:
- gl-dependency-scanning-report.json # GitLab-specific

View file

@ -38,7 +38,7 @@ review-build-cng:
- BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
# When the job is manual, review-deploy is also manual and we don't want people
# to have to manually start the jobs in sequence, so we do it for them.
- '[ -z $CI_JOB_MANUAL ] || play_job "review-deploy"'
- '[ -z $CI_JOB_MANUAL ] || scripts/api/play_job --job-name "review-deploy"'
.review-workflow-base:
extends:
@ -78,8 +78,8 @@ review-deploy:
- disable_sign_ups || (delete_release && exit 1)
# When the job is manual, review-qa-smoke is also manual and we don't want people
# to have to manually start the jobs in sequence, so we do it for them.
- '[ -z $CI_JOB_MANUAL ] || play_job "review-qa-smoke"'
- '[ -z $CI_JOB_MANUAL ] || play_job "review-performance"'
- '[ -z $CI_JOB_MANUAL ] || scripts/api/play_job --job-name "review-qa-smoke"'
- '[ -z $CI_JOB_MANUAL ] || scripts/api/play_job --job-name "review-performance"'
after_script:
# Run seed-dast-test-data.sh only when DAST_RUN is set to true. This is to pupulate review app with data for DAST scan.
# Set DAST_RUN to true when jobs are manually scheduled.

View file

@ -46,6 +46,9 @@
.if-security-merge-request: &if-security-merge-request
if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_MERGE_REQUEST_IID'
.if-security-schedule: &if-security-schedule
if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_PIPELINE_SOURCE == "schedule"'
.if-dot-com-gitlab-org-schedule: &if-dot-com-gitlab-org-schedule
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_PIPELINE_SOURCE == "schedule"'
@ -67,6 +70,9 @@
.if-cache-credentials-schedule: &if-cache-credentials-schedule
if: '$CI_REPO_CACHE_CREDENTIALS && $CI_PIPELINE_SOURCE == "schedule"'
.if-merge-request-rspec-minimal-disabled: &if-merge-request-rspec-minimal-disabled
if: '$CI_MERGE_REQUEST_IID && $RSPEC_MINIMAL_ENABLED != "true"'
.if-rspec-fail-fast-disabled: &if-rspec-fail-fast-disabled
if: '$RSPEC_FAIL_FAST_ENABLED != "true"'
@ -103,6 +109,10 @@
- ".gitlab/ci/build-images.gitlab-ci.yml"
- ".gitlab/ci/qa.gitlab-ci.yml"
.workhorse-patterns: &workhorse-patterns
- "GITLAB_WORKHORSE_VERSION"
- "workhorse/**/*"
.yaml-lint-patterns: &yaml-lint-patterns
- ".gitlab-ci.yml"
- ".gitlab/ci/**/*.yml"
@ -154,6 +164,7 @@
- "{,ee/}fixtures/**/*"
- "{,ee/}rubocop/**/*"
- "{,ee/}spec/**/*"
- "{,spec/}tooling/**/*"
.code-patterns: &code-patterns
- "{package.json,yarn.lock}"
@ -200,6 +211,7 @@
- "{,ee/}fixtures/**/*"
- "{,ee/}rubocop/**/*"
- "{,ee/}spec/**/*"
- "{,spec/}tooling/**/*"
.code-qa-patterns: &code-qa-patterns
- "{package.json,yarn.lock}"
@ -245,6 +257,7 @@
- "{,ee/}fixtures/**/*"
- "{,ee/}rubocop/**/*"
- "{,ee/}spec/**/*"
- "{,spec/}tooling/**/*"
# QA changes
- ".dockerignore"
- "qa/**/*"
@ -255,6 +268,7 @@
.shared:rules:update-cache:
rules:
- <<: *if-master-schedule-2-hourly
- <<: *if-security-schedule
- <<: *if-merge-request-title-update-caches
######################
@ -395,6 +409,7 @@
when: never
- <<: *if-merge-request
changes: *code-backstage-patterns
when: always
- <<: *if-master-refs
changes: *code-backstage-patterns
@ -480,26 +495,86 @@
- changes: *db-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-and-foss-migration:minimal:
rules:
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request-title-run-all-rspec
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-merge-request
changes: *db-patterns
.rails:rules:ee-and-foss-unit:
rules:
- changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-and-foss-unit:minimal:
rules:
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request-title-run-all-rspec
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-merge-request
changes: *backend-patterns
.rails:rules:ee-and-foss-integration:
rules:
- changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-and-foss-integration:minimal:
rules:
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request-title-run-all-rspec
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-merge-request
changes: *backend-patterns
.rails:rules:ee-and-foss-system:
rules:
- changes: *code-backstage-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-and-foss-system:minimal:
rules:
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request-title-run-all-rspec
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-merge-request
changes: *code-backstage-patterns
.rails:rules:ee-and-foss-fast_spec_helper:
rules:
- changes: ["config/**/*"]
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-and-foss-fast_spec_helper:minimal:
rules:
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request-title-run-all-rspec
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-merge-request
changes: ["config/**/*"]
.rails:rules:default-refs-code-backstage-qa:
rules:
- <<: *if-default-refs
@ -513,6 +588,20 @@
- changes: *db-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-only-migration:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request-title-run-all-rspec
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-merge-request
changes: *db-patterns
.rails:rules:ee-only-unit:
rules:
- <<: *if-not-ee
@ -520,6 +609,20 @@
- changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-only-unit:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request-title-run-all-rspec
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-merge-request
changes: *backend-patterns
.rails:rules:ee-only-integration:
rules:
- <<: *if-not-ee
@ -527,6 +630,20 @@
- changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-only-integration:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request-title-run-all-rspec
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-merge-request
changes: *backend-patterns
.rails:rules:ee-only-system:
rules:
- <<: *if-not-ee
@ -534,6 +651,20 @@
- changes: *code-backstage-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-only-system:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request-title-run-all-rspec
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-merge-request
changes: *code-backstage-patterns
.rails:rules:as-if-foss-migration:
rules:
- <<: *if-not-ee
@ -545,6 +676,20 @@
- <<: *if-merge-request
changes: *ci-patterns
.rails:rules:as-if-foss-migration:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-security-merge-request
changes: *db-patterns
- <<: *if-merge-request-title-as-if-foss
changes: *db-patterns
.rails:rules:as-if-foss-unit:
rules:
- <<: *if-not-ee
@ -556,6 +701,20 @@
- <<: *if-merge-request
changes: *ci-patterns
.rails:rules:as-if-foss-unit:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-security-merge-request
changes: *backend-patterns
- <<: *if-merge-request-title-as-if-foss
changes: *backend-patterns
.rails:rules:as-if-foss-integration:
rules:
- <<: *if-not-ee
@ -567,6 +726,20 @@
- <<: *if-merge-request
changes: *ci-patterns
.rails:rules:as-if-foss-integration:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-security-merge-request
changes: *backend-patterns
- <<: *if-merge-request-title-as-if-foss
changes: *backend-patterns
.rails:rules:as-if-foss-system:
rules:
- <<: *if-not-ee
@ -578,6 +751,20 @@
- <<: *if-merge-request
changes: *ci-patterns
.rails:rules:as-if-foss-system:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-security-merge-request
changes: *code-backstage-patterns
- <<: *if-merge-request-title-as-if-foss
changes: *code-backstage-patterns
.rails:rules:ee-mr-and-master-only:
rules:
- <<: *if-not-ee
@ -590,12 +777,9 @@
.rails:rules:detect-tests:
rules:
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
changes: *code-backstage-patterns
- <<: *if-dot-com-gitlab-org-merge-request
- <<: *if-default-refs
changes: *code-backstage-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:rspec-foss-impact:
rules:
@ -647,8 +831,10 @@
when: never
- <<: *if-merge-request
changes: *code-backstage-patterns
when: always
- <<: *if-master-schedule-2-hourly
- <<: *if-merge-request-title-run-all-rspec
when: always
.rails:rules:rspec-feature-flags:
rules:
@ -912,6 +1098,14 @@
- <<: *if-dot-com-ee-schedule
changes: *code-backstage-patterns
###################
# workhorse rules #
###################
.workhorse:rules:workhorse:
rules:
- <<: *if-default-refs
changes: *workhorse-patterns
###################
# yaml-lint rules #
###################

View file

@ -61,15 +61,17 @@ verify-tests-yml:
- scripts/verify-tff-mapping
.detect-test-base:
image: ruby:2.7-alpine
image: ruby:2.7
needs: []
stage: prepare
script:
- source scripts/utils.sh
- source ./scripts/utils.sh
- source ./scripts/rspec_helpers.sh
- install_gitlab_gem
- install_tff_gem
- tooling/bin/find_foss_tests ${MATCHED_TESTS_FILE}
- 'echo "test files affected: $(cat $MATCHED_TESTS_FILE)"'
- retrieve_tests_mapping
- 'if [ -n "$CI_MERGE_REQUEST_IID" ]; then tooling/bin/find_tests ${MATCHED_TESTS_FILE}; fi'
- 'if [ -n "$CI_MERGE_REQUEST_IID" ]; then echo "test files affected: $(cat $MATCHED_TESTS_FILE)"; fi'
artifacts:
expire_in: 7d
paths:
@ -80,6 +82,7 @@ detect-tests:
- .detect-test-base
- .rails:rules:detect-tests
variables:
RSPEC_TESTS_MAPPING_ENABLED: "true"
MATCHED_TESTS_FILE: tmp/matching_tests.txt
detect-tests as-if-foss:

View file

@ -1,6 +1,5 @@
.tests-metadata-state:
variables:
TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache"
image: ruby:2.7
before_script:
- source scripts/utils.sh
artifacts:
@ -17,7 +16,8 @@ retrieve-tests-metadata:
- .test-metadata:rules:retrieve-tests-metadata
stage: prepare
script:
- source scripts/rspec_helpers.sh
- install_gitlab_gem
- source ./scripts/rspec_helpers.sh
- retrieve_tests_metadata
update-tests-metadata:

View file

@ -0,0 +1,10 @@
workhorse:
extends: .workhorse:rules:workhorse
image: golang:1.14
stage: test
needs: []
script:
- rm .git/hooks/post-checkout
- git checkout .
- scripts/update-workhorse check
- make -C workhorse

View file

@ -3,7 +3,7 @@
<!-- NOTE: Please add a DevOps stage label (format `devops:<stage_name>`)
and assign the technical writer who is
[listed for that stage](https://about.gitlab.com/handbook/product/product-categories/#devops-stages). -->
[listed for that stage](https://about.gitlab.com/handbook/product/categories/#devops-stages). -->
## References

View file

@ -0,0 +1,18 @@
<!-- Title suggestion: [Experiment Name] Successful Cleanup -->
## Summary
The experiment is currently rolled out to 100% of users and has been deemed a success.
The changes need to become an official part of the product.
## Steps
- [ ] Determine whether the feature should apply to SaaS and/or self-managed
- [ ] Determine whether the feature should apply to EE - and which tiers - and/or Core
- [ ] Determine if tracking should be kept as is, removed, or modified.
- [ ] Migrate experiment to a default enabled [feature flag](https://docs.gitlab.com/ee/development/feature_flags/development.html) for one milestone and add a changelog. Converting to a feature flag can be skipped at the ICs discretion if risk is deemed low with consideration to both SaaS and (if applicable) self managed.
- [ ] Ensure any relevant documentation has been updated.
- [ ] In the next milestone, [remove the feature flag](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up).
- [ ] After the flag removal is deployed, [clean up the feature/experiment feature flags](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up) by running chatops command in `#production` channel
/label ~"feature" ~"feature::maintenance" ~"workflow::scheduling" ~"growth experiment" ~"feature flag"

View file

@ -18,7 +18,8 @@ Remove the `:feature_name` feature flag ...
### What can we monitor to detect problems with this?
<!-- Which dashboards from https://dashboards.gitlab.net are most relevant? -->
<!-- Which dashboards from https://dashboards.gitlab.net are most relevant? Sentry errors reports can alse be useful to review -->
## Beta groups/projects
@ -30,13 +31,13 @@ If applicable, any groups/projects that are happy to have this feature turned on
## Roll Out Steps
- [ ] Confirm that QA tests pass with the feature flag enabled (if you're unsure how, contact the relevant [stable counterpart in the Quality department](https://about.gitlab.com/handbook/engineering/quality/#individual-contributors))
- [ ] Enable on staging (`/chatops run feature set feature_name true --staging`)
- [ ] Test on staging
- [ ] Ensure that documentation has been updated
- [ ] Enable on GitLab.com for individual groups/projects listed above and verify behaviour (`/chatops run feature set --project=gitlab-org/gitlab feature_name true`)
- [ ] Coordinate a time to enable the flag with the SRE oncall and release managers
- In `#production` by pinging `@sre-oncall`
- In `#g_delivery` by pinging `@release-managers`
- In `#production` mention `@sre-oncall` and `@release-managers`. Once an SRE on call and Release Manager on call confirm, you can proceed with the rollout
- [ ] Announce on the issue an estimated time this will be enabled on GitLab.com
- [ ] Enable on GitLab.com by running chatops command in `#production` (`/chatops run feature set feature_name true`)
- [ ] Cross post chatops Slack command to `#support_gitlab-com` ([more guidance when this is necessary in the dev docs](https://docs.gitlab.com/ee/development/feature_flags/controls.html#where-to-run-commands)) and in your team channel

View file

@ -96,8 +96,11 @@ In which enterprise tier should this feature go? See https://about.gitlab.com/ha
### Links / references
<!-- Label reminders - you should have one of each of the following labels.
Read the descriptions on https://gitlab.com/gitlab-org/gitlab/-/labels to find the correct ones -->
<!-- Label reminders - you should have one of each of the following labels.
Use the following resources to find the appropriate labels:
- https://gitlab.com/gitlab-org/gitlab/-/labels
- https://about.gitlab.com/handbook/product/categories/features/
-->
/label ~devops:: ~group: ~Category:
/label ~feature

View file

@ -42,7 +42,7 @@ call-out responsibilities for other team members or teams.
-->
- [ ] ~frontend Step 1
- [ ] @person Step 1a
- [ ] `@person` Step 1a
- [ ] ~frontend Step 2

View file

@ -17,7 +17,11 @@
/label ~"feature" ~"group::" ~"section::" ~"Category::" ~"GitLab Core"/~"GitLab Starter"/~"GitLab Premium"/~"GitLab Ultimate"
<!-- Read the labels descriptions on https://gitlab.com/gitlab-org/gitlab/-/labels to find the appropriate labels. Consider adding related issues and epics to this issue. You can also reference the Feature Proposal Template (https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Feature%20proposal.md) for additional details to consider adding to this issue. Additionally, as a data oriented organization, when your feature exits planning breakdown, consider adding the `What does success look like, and how can we measure that?` section.
<!--- Use the following resources to find the appropriate labels:
- https://gitlab.com/gitlab-org/gitlab/-/labels
- https://about.gitlab.com/handbook/product/categories/features/
Consider adding related issues and epics to this issue. You can also reference the Feature Proposal Template (https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Feature%20proposal.md) for additional details to consider adding to this issue. Additionally, as a data oriented organization, when your feature exits planning breakdown, consider adding the `What does success look like, and how can we measure that?` section.
Other sections to consider adding:

View file

@ -1,41 +0,0 @@
<!--
# Read me first!
Set the title to: `Security Release: 12.2.X, 12.1.X, and 12.0.X`
-->
:warning: **Only Release Managers and members of the AppSec team can edit the description of this issue**
-------
## Version issues:
12.2.X, 12.1.X, 12.0.X: {release task link}
## Issues in GitLab Security
To include your issue and merge requests in this Security Release, please mark
your security issues as related to this release tracking issue. You can do this
in the "Linked issues" section below this issue description.
:warning: If your security issues are not marked as related to this release
tracking issue, their merge requests will not be included in the security
release.
### Branches to target in GitLab Security
Your Security Implementation Issue should have `4` merge requests associated:
- [master and 3 backports](https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/developer.md#backports)
- Backports should target the stable branches for the versions mentioned included in this Security Release
## Blog post
Security: {https://gitlab.com/gitlab-org/security/www-gitlab-com/merge_requests/ link}<br/>
GitLab.com: {https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/ link}
## Email notification
{https://gitlab.com/gitlab-com/marketing/general/issues/ link}
/label ~security ~"upcoming security release"
/confidential

View file

@ -37,6 +37,10 @@ We generally recommend events be tracked using a [structured event](https://docs
* [ ] Create chart(s) to track your event(s) in the relevant dashboard
* [ ] Use the [Chart Snowplow Actions](https://app.periscopedata.com/app/gitlab/snippet/Chart-Snowplow-Actions/5546da87ae2c4a3fbc98415c88b3eedd/edit) SQL snippet to quickly visualize usage. See [example](https://app.periscopedata.com/app/gitlab/737489/Health-Group-Dashboard?widget=9797112&udv=0)
<!-- Label reminders - you should have one of each of the following labels if you can figure out the correct ones -->
<!-- Label reminders - you should have one of each of the following labels.
Use the following resources to find the appropriate labels:
- https://gitlab.com/gitlab-org/gitlab/-/labels
- https://about.gitlab.com/handbook/product/categories/features/
-->
/label ~devops:: ~group: ~Category:
/label ~"snowplow tracking events"

View file

@ -31,5 +31,4 @@ Actionable insights always have a follow-up action that needs to take place as a
/label ~"Actionable Insight"
/label ~"Actionable Insight"

View file

@ -15,9 +15,9 @@
## Author's checklist (required)
- [ ] Follow the [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide.html).
- [ ] Follow the [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide/).
- If you have **Developer** permissions or higher:
- [ ] Ensure that the [product tier badge](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges) is added to doc's `h1`.
- [ ] Ensure that the [product tier badge](https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#product-tier-badges) is added to doc's `h1`.
- [ ] Apply the ~documentation label, plus:
- The corresponding DevOps stage and group labels, if applicable.
- ~"development guidelines" when changing docs under `doc/development/*`, `CONTRIBUTING.md`, or `README.md`.
@ -45,7 +45,7 @@ All reviewers can help ensure accuracy, clarity, completeness, and adherence to
**2. Technical Writer**
- [ ] Technical writer review. If not requested for this MR, must be scheduled post-merge. To request for this MR, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/product-categories/#devops-stages).
- [ ] Technical writer review. If not requested for this MR, must be scheduled post-merge. To request for this MR, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages).
- [ ] Ensure docs metadata are present and up-to-date.
- [ ] Ensure ~"Technical Writing" and ~"documentation" are added.
- [ ] Add the corresponding `docs::` [scoped label](https://gitlab.com/groups/gitlab-org/-/labels?utf8=%E2%9C%93&subscribed=&search=docs%3A%3A).

View file

@ -0,0 +1,26 @@
## Description of the test
<!--
Please link to the respective test case in the testcases project
-->
### Check-list
- [ ] Confirm the test has a [`testcase:` tag linking to an existing test case](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/best_practices.html#link-a-test-to-its-test-case-issue) in the test case project.
- [ ] Note if the test is intended to run in specific scenarios. If a scenario is new, add a link to the MR that adds the new scenario.
- [ ] Follow the end-to-end tests [style guide](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/style_guide.html) and [best practices](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/best_practices.html).
- [ ] Use the appropriate [RSpec metadata tag(s)](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/rspec_metadata_tests.html#rspec-metadata-for-end-to-end-tests).
- [ ] Ensure that a created resource is removed after test execution.
- [ ] Verify the tags to ensure it runs on the desired test environments.
- [ ] If this MR has a dependency on another MR, such as a GitLab QA MR, specify the order in which the MRs should be merged.
- [ ] (If applicable) Create a follow-up issue to document [the special setup](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/running_tests_that_require_special_setup.html) necessary to run the test: ISSUE_LINK
<!-- Base labels. -->
/label ~"Quality" ~"QA" ~test
<!-- If the test is addressing a test gap, select a label according to the feature under test, please use just one. -->
/label ~"Quality:test-gap" ~"Quality:EE test gaps"
<!-- Select the appropriate feature label, ~"feature::addition" for tests added for new features, ~"feature::maintenance" for tests added for existing features -->
/label ~"feature::addition" ~"feature::maintenance"

View file

@ -214,7 +214,6 @@ linters:
- 'app/views/projects/mattermosts/_team_selection.html.haml'
- 'app/views/projects/mattermosts/new.html.haml'
- 'app/views/projects/merge_requests/_commits.html.haml'
- 'app/views/projects/merge_requests/_how_to_merge.html.haml'
- 'app/views/projects/merge_requests/_mr_title.html.haml'
- 'app/views/projects/merge_requests/conflicts/_commit_stats.html.haml'
- 'app/views/projects/merge_requests/conflicts/_file_actions.html.haml'
@ -286,7 +285,6 @@ linters:
- 'app/views/shared/hook_logs/_content.html.haml'
- 'app/views/shared/issuable/_assignees.html.haml'
- 'app/views/shared/issuable/_board_create_list_dropdown.html.haml'
- 'app/views/shared/issuable/_close_reopen_report_toggle.html.haml'
- 'app/views/shared/issuable/_form.html.haml'
- 'app/views/shared/issuable/_search_bar.html.haml'
- 'app/views/shared/issuable/_sidebar.html.haml'

View file

@ -32,6 +32,7 @@ AllCops:
- 'builds/**/*'
- 'plugins/**/*'
- 'file_hooks/**/*'
- 'workhorse/**/*'
CacheRootDirectory: tmp
MaxFilesInCache: 18000
@ -47,6 +48,10 @@ Cop/StaticTranslationDefinition:
- 'spec/**/*'
- 'ee/spec/**/*'
Lint/LastKeywordArgument:
Enabled: true
Safe: false
# This cop checks whether some constant value isn't a
# mutable literal (e.g. array or hash).
Style/MutableConstant:
@ -271,6 +276,12 @@ GitlabSecurity/PublicSend:
Gitlab/DuplicateSpecLocation:
Enabled: true
Gitlab/PolicyRuleBoolean:
Enabled: true
Include:
- 'app/policies/**/*'
- 'ee/app/policies/**/*'
Cop/InjectEnterpriseEditionModule:
Enabled: true
Exclude:
@ -357,6 +368,8 @@ Cop/SidekiqOptionsQueue:
Graphql/ResolverType:
Enabled: true
Exclude:
- 'app/graphql/resolvers/base_resolver.rb'
Include:
- 'app/graphql/resolvers/**/*'
- 'ee/app/graphql/resolvers/**/*'
@ -422,13 +435,11 @@ Scalability/FileUploads:
Graphql/Descriptions:
Enabled: true
AutoCorrect: true
Include:
- 'app/graphql/**/*'
- 'ee/app/graphql/**/*'
RSpec/AnyInstanceOf:
Enabled: false
# Cops for upgrade to gitlab-styles 3.1.0
RSpec/ImplicitSubject:
Enabled: false

File diff suppressed because it is too large Load diff

View file

@ -7,3 +7,6 @@ MinAlertLevel = suggestion
[*.md]
BasedOnStyles = gitlab
# Ignore SVG markup
TokenIgnores = (\*\*\{\w*\}\*\*)

View file

@ -2,20 +2,22 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 13.6.7 (2021-02-11)
## 13.7.7 (2021-02-11)
### Security (7 changes)
### Security (9 changes)
- Cancel running and pending jobs when a project is deleted. !1220
- Updates authorization for linting API.
- Prevent exposure of confidential issue titles in file browser.
- Check user access on API merge request read actions.
- Prevent Denial of Service Attack on gitlab-shell.
- Prevent exposure of confidential issue titles in file browser.
- Updates authorization for linting API.
- Check user access on API merge request read actions.
- Limit daily invitations to groups and projects.
- Enforce the analytics enabled project setting for project-level analytics features.
- Perform SSL verification for FortiTokenCloud Integration.
- Prevent Server-side Request Forgery for Prometheus when secured by Google IAP.
## 13.6.6 (2021-02-01)
## 13.7.6 (2021-02-01)
### Security (5 changes)
@ -26,14 +28,35 @@ entry.
- Add routes for unmatched url for not-get requests.
## 13.6.5 (2021-01-13)
## 13.7.5 (2021-01-25)
### Fixed (2 changes, 1 of them is from the community)
- New project guidelines are no longer displayed. !50736 (Roger Meier)
- Fix LFS not working with S3 specific-storage settings. !52296
## 13.7.4 (2021-01-13)
### Security (1 change)
- Deny implicit flow for confidential apps.
## 13.6.4 (2021-01-07)
## 13.7.3 (2021-01-08)
### Fixed (7 changes)
- Fix Canary Ingress weight is not reflected on UI immediately. !50246
- Change pages deployments size to bigint. !50262
- Fix viewing container repositories with tags with corrupted manifest. !50362
- Fix the graphQL type for container repository tags. !50419
- Fix(eetrialbanner): fix EE trial banner to allow dismiss. !50436
- Update Helm 2 version to 2.17.0. !50547
- Fix project access token regression. !50800
## 13.7.2 (2021-01-07)
### Security (7 changes)
@ -41,9 +64,478 @@ entry.
- Deny implicit flow for confidential apps.
- Update NuGet regular expression to protect against ReDoS.
- Fix regular expression backtracking issue in package name validation.
- Upgrade GitLab Pages to 1.30.2.
- Fix stealing API token from GitLab Pages and DoS Prometheus through GitLab Pages.
- Update trusted OAuth applications to set them as confidential.
- Upgrade Workhorse to 8.54.2.
- Upgrade Workhorse to 8.58.2.
## 13.7.1 (2020-12-23)
### Fixed (1 change)
- Fix project transfer corrupting shared runners state. !47316
## 13.7.0 (2020-12-22)
### Security (1 change)
- Fix regular expression backtracking issue in custom emoji name validation.
### Removed (2 changes, 1 of them is from the community)
- Remove Google Code importer. !48139 (Getulio Valentin Sánchez)
- Remove release notes from Tags page. !49979
### Fixed (109 changes, 7 of them are from the community)
- Update user mentions when markdown columns are directly saved to DB. !38034
- Retain spinner when applying MR suggestions. !46203
- Skipped jobs no longer trigger a cancelled deployment. !46614 (David Barr @davebarr)
- Catch wiki timeouts when rendering pages. !46627
- Fix single file snippets display for Geo secondary sites. !46812
- Fix Jira Connect styles not loaded when startup_css is enabled. !47043
- Add migration that updated users that don't need to have 2fa established. !47193
- Fix project integration form validation when integration is inactive. !47201
- Fix project access token build authentication error. !47247
- Support S3 server side encryption in CI cloud native job logs. !47536
- Fix repository clone panel for wikis. !47676
- Hide Mark as draft button in a merged MR even on mobile. !47678 (Takuya Noguchi)
- Eliminate N+1 performance issues in MergeRequest.pipelines in GraphQL API. !47784
- Add cascade delete foreign key to web_hooks on service_id without validation. !47821
- Implement passing dotenv variables to bridge jobs. !47905
- Allow canceling all pipelines with auto-cancel. !47906
- Fix error in Issuable::ImportCsv::BaseService when CSV file is empty. !47918
- Fixed editing labels on the swimlanes sidebar. !47946
- Scroll exactly to the top of a discussion on the MR Overview tab. !47970
- Search page: fix empty results status. !48034
- Move fuzz license check to .pre stage. !48076
- Add link in Access Request API. !48081 (jimcser)
- Add gitlab:db:active task. !48083
- Fix overscroll for MR diffs in mobile view. !48091
- Fix incorrect line height in file header. !48117
- Repopulate historical vulnerability statistics. !48128
- Fixed image diff comments positioning. !48132
- Manually trigger pipelines correctly when branches and tags have the same name. Separate tags and branches in trigger pipeline form. !48142
- Allow failure for Secret Detection job. !48152
- Change services.inherit_from_id foreign key to ON DELETE CASCADE. !48163
- Avoid exception when validating diff_note support. !48187
- Avoid invalid notes on Project Import. !48189
- Update alert setting form to handle JSON payload submit when mapping builder is not enabled. !48231
- Adds id desc to index_ci_builds_on_runner_id_and_id_desc. !48241
- Adds type="button" to the close button for the issue type selector to prevent accidental form submission. !48249
- Remove orphan service hooks. !48263
- Fix console error being thrown when file is renamed. !48275
- Update alert details sidebar assignee dropdown to use correct styling and formatting. !48285
- Consider design repositories when determining if there is a git transfer in progress. !48304
- Set Retry-After header when RackAttack throttling. !48310
- Fix misaligned buttons for CI Jobs page. !48332 (mgandres)
- Use incident instead of issue for operation settings. !48406
- Fix missing item with same name in autocomplete suggestions. !48410 (Paul Ungureanu @ungps)
- Fix misalignment of commit search by message input. !48430
- Prometheus integration name should not have a modifiable input field. !48437
- Fix stretched flash in project commit show page. !48439
- Ensure job trace endpoint is not called if the current job has not started or the browser is not visible. !48516
- Update fog-aws to v3.6.7. !48519
- Fixed double-border style on WebIDE button. !48605
- Fix spacing between buttons on pipeline header. !48660
- Fix vulnerability deduplication logic for the "pipeline security tab". !48704
- Add type filtering in appearance page of the admin panel. !48709 (Paul Ungureanu @ungps)
- Fix confirmation modal showing on project integration. !48720
- Fix import of LFS files in GitHub import. !48722
- Github importer - Avoid touching MR when importing pull request `merged by` field. !48729
- Fix styling of various dropdowns. !48800
- Fix MR buttons when fork is deleted. !48813
- Add menu-item class to non-details-job-component. !48834
- GraphQL: Add gitlay field to CiStatusAction. !48892
- Global Search - Fix Dark Mode Font. !48927
- Fix flex overflow bug. !48931
- Restrict access to job page to developers only when use CI_DEBUG_TRACE is true. !48932
- Resolve Cannot remove namespace. !48973
- Resolve Save button should have a different color on press. !48975
- Fix last_value record in internal_ids for epics. !48988
- Fix failed group imports getting stuck by long error messages. !48989
- Avoid branch name checking when creating a new snippet. !48995
- Ensure default_branch from settings is not blank. !49018
- Make sure Sourcegraph asset always loads successfully. !49030
- Fix avatar size in profile activity. !49047
- Fix margin and selected state in file header. !49059
- Fix comment highlighting for unified diff components. !49061
- Fix Jupyter notebook code and image rendering. !49067
- Fix bug in ProjectRepositoryStorageMove transition to scheduled. !49105
- Do not crash the ingestion of all security reports if there is an invalid report artifact. !49181
- Do not automatically reapply incident label after user removes it. !49188
- Update fog-google to v1.12. !49196
- Check for a status in the current user dropdown. !49203
- Fix pipeline page in dark mode. !49214
- Handle prometheus-formatted alert notifications through HTTP integrations. !49268
- Update repository size after import. !49319
- Fix typo on merge locally step. !49330
- Fix getting security report information on merge requests from forks. !49354
- Conan packages show build and commit information when published using CI. !49426
- Clear emoji status in issue/mr header. !49439
- Hide extra breadcrumb arrow that overlaps with last breadcrumb item. !49456
- Fix division by error when upload max size is set to 0. !49482
- Capture subgroup creation failure during Group Import via archive file. !49484
- Alert Service integration only available for projects. !49561
- Update projects_imported.total usage metric. !49568
- Fix usage data tracking of some issue events. !49571
- Fix copy to clipboard on Firefox. !49648
- Fix outline on selected button in Snippets Rendered/Source buttons. !49676
- Add final newline on submit in blob editor. !49681
- Fail import state whenever repository import fails. !49682
- Fix author on /clone quickaction usage to be current user. !49830
- Upgrade mailroom to v0.0.8. !49834
- Fix the header name for basic auth authentication in package managers APIs. !49836
- Allow opsgenie manage form to be displayed when opsgenie is enabled. !49863
- Add custom cop to prevent invalid HTTParty usage. !49878 (Ethan Reesor (@firelizzard))
- Remove last-child bottom-margin: 0 from page-title class. !49884
- Fix wording of some 400 Bad request API responses. !49895
- Set Web IDE Live Preview default background to white. !49901
- Fix bug in snippets mark as spam mutation. !49912
- Make the strategies env wrap. !49951
- Fix get endpoint not returning members with minimal access. !49996
- Fix feature flag logging is not working on API. !50025
- Resolve No boards found message showing when loading boards. !50140
- Fix Markdown attachments in Releases not rendering with full URL. !50146
### Deprecated (1 change)
- Drop unused feature_filter_type experiment column. !48221
### Changed (124 changes, 12 of them are from the community)
- Move Jenkins to Core. !37797 (Ben Bodenmiller (@bbodenmiller))
- Migrate Bootstrap button to GitLab UI GlButton in IDE. !39988
- Replace bootstrap alerts in ee/app/views/admin/licenses/new.html.haml. !41275 (Gilang Gumilar)
- Replace bootstrap alerts in app/views/profiles/notifications/show.html.haml. !41310 (Gilang Gumilar)
- Replace bootstrap alerts in app/views/admin/runners/show.html.haml. !41378 (Gilang Gumilar)
- Replace Runner Page Title with Runners Hash. !44854 (Kev @KevSlashNull)
- Rename Piwik config items and layout file after rebranding to Matomo. !45658 (Kate Grechishkina @kategrechishkina)
- Improve clarity of admin Rate Limiting UI. !46142
- Replace fa-exclamation-triangle icons with GitLab SVG warning-solid icon. !47089
- Add `converted_at` timestamp column to `experiment_users` to record when the user performs an experiment's conversion action. !47093
- Preserve cross references in AsciiDoc documents. !47131 (Guillaume Grossetie)
- Darker background for dark mode, plus small fixes to MR page. !47359
- Add option to uninstall the legacy Tiller server for clusters added before GitLab 13.2. !47457
- Use GitLab UI styles on Integrations page. !47478
- Add a job to the DAST template that shows an error in the console if the user is not licensed to use DAST. !47484
- Add BulkImports::Failure to store import failures of the Group Migration (BulkImports) process. !47526
- Remove brackets in no scopes selected message in access and deploy tokens lists. !47628
- Begin auto-stop countdown for environment after initial creation. !47702
- Change default project listing sort order to name. !47734 (Lee Tickett)
- Finalize new create project UI experiment. !47804
- Make ImportIssuesCsvWorker idempotent. !47808
- Remove "Details" from breadcrumb item and LD+JSON from Project top. !47817 (Takuya Noguchi)
- Convert knative error alert to glalert. !47840
- Remember last used project ordering option across groups. !47850 (Lee Tickett)
- Remove "Details" from breadcrumb and JSON+JD on Group top page. !47854 (Takuya Noguchi)
- Hide open registration user callout on gitlab.com. !47865
- Make "How to merge" modal in merge requests conform to correct modal styling. !47889
- Remove `Add Issues` button and a related modal. !47898
- Remove Feature Flag that controls data limit on Deploy Boards, thus making 10MB limits mandatory. !47950
- Removed boards promotion. !47972
- Replace fa-chevron-down in template selector dropdown. !48015
- Remove http_integrations_list feature flag. !48030
- Add filtering by current iteration to issue lists and issue boards. !48040
- Implement smart cobertura class path correction. !48048
- Replace fa-chevron-down icon in pikaday. !48054
- Add User.location field to GraphQL API. !48059
- Add support for filtering direct group members by 2FA enabled/disabled. !48084
- Iterate on the copy in the “Novice or Experienced” page of the registration onboarding flow. !48086
- Add metrics for count of unique users of alerts and incidents to usage ping. !48087
- Allow Pages to define a storage-specific connection. !48098
- Replace fa-exclamation-triangle in users select. !48116
- Add a generic packages tab to the Packages UI. !48121
- Replace fa icons in single file diff. !48136
- Add `checksum` column into the `vulnerability_remediations` table. !48165
- Replace fa-chevron-down with GitLab SVG in dropdowns. !48171
- Add ldap encrypted credentials to the usage data. !48210
- Replace fa-spinner in metrics dashboard yaml definition. !48227
- Update Design of the Container Registry Cleanup Policy for tags. !48243
- Enable LFS chunked encoding. !48269
- Authorize the project for the cluster agent if it is the agent's project. !48314
- Add GlFormCheckbox to squash commits. !48338
- Add metric for dead Sidekiq jobs. !48361
- Make How to merge modal in merge requests widget conform to correct modal styling. !48370
- Allow filtering project and group members by relationship in GraphQL. !48372
- Add Attributes cleaner to Group Migration. !48374
- Add additional fields to GraphQl terraform state version. !48411
- Bumps Managed-Cluster-Applications CI template to v0.36.0, which upgrades Runner. !48444
- Expose public email field for user in GraphQL. !48468
- Add MAU counter for snippet show action. !48477
- Refine group creation form. !48490
- Geo: Remove unused indexes. !48504
- Update empty state for no commits result. !48538
- Github Importer - import the pull request `merged by` field. !48561
- Refactor container registry list page to grapqhl. !48602
- Transfer a project/group to a new namespace inheriting integrations. !48621
- Replace fa-chevron-down icons with GitLab SVG in gcp cluster form. !48656
- Add containerRepositoriesCount to project and group queries. !48685
- Track test failures on pipeline completion. !48695
- Include actual limit in pipeline limit errors. !48710
- Replace how to merge HAML with Vue component. !48766
- Global Search - Fix Sidebar Whitespace. !48832
- Move CanaryIngress to core. !48836
- Finish removing unused replication columns from terraform state. !48839
- The dependency proxy caches manifests and makes HEAD requests to help with rate limiting. !48845
- Update package_file table to display commits when present. !48882
- Avoid creating wiki empty repo when not present in export files. !48890
- Update nodejs-scan rule to wildcard prefix. !48902
- Add primary key to elasticsearch_indexed_projects. !48919
- Upgrade fog-aws to v3.7.0. !48921
- Add new column `finding_uuid` into `vulnerability_feedback` table. !48923
- Add primary key to elasticsearch_indexed_namespaces. !48944
- Add ability to type a number in related issues and prepend #. !48952
- Improve CI for external repo with configurable maximum mirroring frequency on self-hosted. !48955
- Enable pages_serve_from_deployments FF by default. !48974
- Replace fa-cirlce in runners helper. !48981
- Add GitHub Importer pagination. !48983
- Add primary key to merge_request_context_commit_diff_files. !49024
- Update template to use codequality 0.85.18-gitlab.1. !49034
- Display more pipelines info in package history. !49040
- Use a separate commit to store formatting changes in the Static Site Editor. !49052
- Delete manifests when purging the dependency proxy using the API. !49056
- Auto approve users if Admin approval after sign up setting is disabled. !49068
- Boards - Remove default labels lists generation. !49071
- Sort merge request diff files directory first. !49118
- Add user ID based allowlist for Rack::Attack. !49127
- Sort commit/compare diff files directory first. !49136
- Move IssueType notes and discussions count logic to resolvers. !49160
- Let `rake gitlab:workhorse:install` use vendored workhorse. !49250
- Allow alert list to be visible when alerts exist, even if alerting integrations are disabled. !49257
- EKS: Provide user feedback on AWS authorization errors. !49278
- Remove user_search_secondary_email feature flag. !49312
- Update gitlab-kas to v13.7.0. !49318
- Convert fa-caret-down icons to chevron-down SVG. !49332
- Enable file tree highlighting by default. !49356
- Merge 'Sample Data' and 'Built-in' tabs on Project Templates page. !49374
- Add Merge Train Setting to the graphql api. !49402
- Migrate HAML buttons to Pajamas in app/views/profiles/keys. !49421 (Jonston Chan @JonstonChan)
- Migrate `createBoard` away from boardStore. !49450
- Support merge requests filtered by reviewer in GraphQL API. !49464
- Gradually load more diffs async. !49476
- Require users to copy, download, or print 2FA recovery codes. !49493
- Convert group member filter dropdowns to filtered search bar. !49505
- Update GitLab Workhorse to v8.58.0. !49534
- Refactor container registry to use GraphQL API. !49584
- Remove unneeded pagination code for project importers. !49589
- Update deprecated button on pipeline security table. !49620
- Update ide pipeline alert to use gitlab ui. !49634
- Updates the copy on empty users list tabs. !49642
- Enable LFS chunked encoding by default. !49649
- Add visibility and last updated image repository details. !49703
- Allow updating `hideBacklogList` and `hideClosedList` board attributes. !49947
- Add expires_at param to GroupMemberBuilder data. !49981
- Change the unique index on `security_findings` table. !50046
- Remove dast_unlicensed job. !50129
### Performance (24 changes, 2 of them are from the community)
- Remove redundant index. !47072
- Add database index for deployment rollback targets. !47159
- Add index for API Fuzzing usage data. !47692
- Paginate unit test report. !47953
- Remove .issue-box from static (classic) Issuable list. !47998 (Takuya Noguchi)
- Remove Bootstrap 4's Cards components from Issuables and Todos. !48004 (Takuya Noguchi)
- Fix N+1 when looking up user's solo owned groups. !48340
- Paginate first page of branches using Gitaly. !48595
- Add approvals created_at index. !48684
- Update index for notes to include `system`. !48864
- Remove unnecessary Gitaly calls from raw endpoint. !48917
- Reduce SQL queries when no pipeline hooks are active. !49186
- Improve query that finds all pipelines in the same family. !49240
- Rendering Loading State of Last Commit earlier. !49362
- Fix N+1 queries loading milestones when exporting CSVs. !49429
- Update snippet repository finder for namespace replication. !49518
- Reduce object allocations for large merge request. !49563
- Remove unnecessary Gitaly calls from projects#show. !49565
- Expand index on ci_pipelines. !49604
- Remove unnecessary queries in milestone page. !49662
- Improve the performance of the diff change access check. !49803
- Remove initial data check on project level value stream page. !49936
- Improve UI and performance of branches overview page. !50096
- Use the improved version of Value Stream Analytics backend on the project level. !50141
### Added (125 changes, 13 of them are from the community)
- Configurable personal access token prefix. !20968 (Max Wittig & Diego Louzán)
- Add CI_OPEN_MERGE_REQUESTS environment variable. !38673 (Ben Bodenmiller @bbodenmiller)
- Add Kroki to support more diagrams in AsciiDoc and Markdown. !44851 (Guillaume Grossetie)
- Fix the unreachable CLI image in OpenShift CI template. !44933 (Klaus Mueller @klml)
- Add other role column in user details table. !45635
- Add encrypted ldap secrets support. !45712
- Add the gitlab-experiment gem, with configuration. !45840
- Support Git access for group wikis. !45892
- Add toggle to remove Analytics left nav item. !46011
- Add merge requests total time to merge field to the GraphQL API. !46040
- Cleanup webauthn background migration. !46179 (Jan Beckmann)
- Add GraphQL mutation to update a release. !46611
- Capture design detail views via usage ping. !46751
- Add metric image uploading to incidents via REST API. !46845
- Expose GraphQL resolver for processing CI config. !46912
- Limit maximum deployments per pipeline to 500. !46931
- Enable Crowd auth for git-over-https. !46935 (Thomas Mendoza @tgmachina)
- Create a new `ExperimentSubject` model, associated to the `Experiment` model, and related database migrations. !47042
- Add GraphQL mutations for Devops Adoption Segment. !47066
- Allow passing `commit_id` when creating MR discussions via the API and expose `commit_id` for MR diff notes. !47130 (Johannes Altmanninger @krobelus)
- Adds bulk project repository storage move API. !47142
- Add packages_size to ProjectStatistics API entity. !47156 (Roger Meier)
- Create `vulnerability_findings_remediations` and `vulnerability_remediations` tables. !47166
- Geo: Add verification state machine fields to package files table. !47260
- Add `increment_counter` to Usage Ping API. !47309
- Geo: Add verification indexes for package files. !47372
- Add SEO structured markup for groups. !47374
- Create `incident_management_oncall_schedules` table. !47407
- Add confirm modal to unblock user. !47442
- Add API endoint for Administrators to approve pending users. !47564
- Allow secondary emails in user search. !47587
- Frontend client for increment_counter API. !47622
- Schedule CreateEvidenceWorker jobs in a sliding window. !47638
- Send Static Site Editor events to Usage Ping API. !47640
- Add rake task to disable personal project and group creation. !47655
- Add assign self to group boards sidebar. !47705
- Toggle File-By-File setting from the MR settings dropdown. !47726
- Add regulated field to compliance management frameworks. !47761
- Add lock button to the Terraform State list view. !47842
- Adds migration for user permission uploads. !47846
- Add loading state to boards assignees header dropdown. !47848
- Use CS_ANALYZER_IMAGE in CS template. !47856
- Add cloud_license_enabled column to application_settings. !47882
- Add invitation reminders. !47920
- Create namespace onboarding actions table. !48018
- Expose creation/update times for issue links. !48051
- Add upcoming deployment column to Environments page. !48062
- Add `service_desk_reply_to` to issues list and header. !48089 (Lee Tickett)
- Add iteration_id column to lists. !48103
- Add Epic Board Position model to store relative positioning of epics on a board. !48120
- Add code coverage overall activity to group repository analytics. !48155
- Add confirm modal to reactivate user. !48173
- Email user when registration request is rejected. !48185
- Add artifacts field to JobType. !48207
- Add database index on deployments. !48265
- Add secondary indexes to partitioned audit_events. !48270
- Obfuscate user profile for unconfirmed users. !48271
- Add flash message for setAssignees on group issue boards. !48277
- Add an URL to get user's GPG key if registerd. !48321 (Shimura Rin @blackenedgold)
- Add Operations project setting logic. !48347
- Add GraphQL mutation to delete a release. !48364
- Track MAU for SSE edit. !48377
- Add loading state to assignees header. !48392
- Implement a /clone quick-action to quickly clone an Issue. !48394
- Expose upcoming deployment in environment.json. !48449
- Add Vulnerabilities External Link model. !48465
- Add migration to populate remaining dismissal information for vulnerabilities. !48472
- Mark SCIM-created accounts as provisioned by group. !48483
- Add delete button to terraform list vue. !48485
- Show if a Pipeline was Ran in a Fork. !48517
- Added email notifications when an Issue is cloned. !48534
- Add dependency_proxy_manifests table and associations. !48535
- Add usage metrics for issue clone. !48537
- Implement a /clone_with_notes quick-action to quickly clone an Issue will all its notes. !48539
- Tracks guest package events. !48547
- Retry rsync when source files vanish during backup. !48568
- Add Setting to disable feed_tokens. !48600
- Enable by default usage data API tracking. !48607
- Add GraphQL API to delete container repository tags. !48617
- Github Importer - import pull request reviews from Github. !48632
- Added epic boards and epic board labels tables. !48658
- Allow alerts to be filtered by monitoring tool. !48699
- Adds guest package events to usage data. !48734
- Render http and https URLs as clickable links in Job logs. !48758 (Łukasz Groszkowski @falxcerebri)
- Add Merge Request diff CI variables. !48764 (Jonas Hahnfeld)
- Add admin users serializer and entity. !48791
- Set vulnerability as dismissed when there is dismissal feedback. !48795
- Create package build_info records for Conan, NuGet, PyPI, and Composer packages and package files. !48811
- Add download action to the Terraform state listing. !48837
- Add context to the experiment user records. !48896
- Add index for the `vulnerabilities` table on `project_id`, `state`, and `severity` columns. !48930
- Add uuid column into security_findings table. !48968 (Harrison Brock @harrisonbrock)
- Detect corrupted build logs and report them by incrementing Prometheus counter. !49004
- Add details column to vulnerability findings table. !49005
- Add Project to ContainerRepository GraphQL type. !49019
- Add pipeline information to Terraform state list. !49042
- Add oncall rotations and participants tables. !49058
- Add domain column to alerts table. !49120
- Add dependency proxy predefined environment variables. !49133
- Add usage data rake tasks to prettify JSON output. !49137
- Resolve Transition ID section should include help text. !49204
- Support instance profiles for IAM role for Amazon EKS integration. !49212
- Add `project_id` column into the `vulnerability_remediations` table to scope the records with projects. !49219
- Add member_events column to web_hooks table. !49273
- Upgrade Pages to 1.31.0. !49352
- Add CI/CD analytics GraphQL types. !49384
- Truncate the `security_findings` table. !49385
- Add validating jsonb fields with json schema draft-07. !49451
- Adds sha checksum to composer URL. !49511
- Dependency Proxy for private groups and Dependency Proxy authentication. !49519
- Save usage ping payload in raw_usage_data table. !49559
- Allow downloading of security reports directly from merge request page. !49572
- Show upgrade popover in security widget in merge requests when the user is able to upgrade. !49613
- Introduce frontend for group migration MVC. !49709
- Add issue header mobile dropdown loading state. !49734
- Support extensions as configurable ES6 classes in Editor Lite. !49813
- Allow job to download artifacts in parent-child pipeline hierarchy. !49837
- Add ability to aggregated metrics in Usage Ping. !49886
- Add expiration policy completed at support in container repositories. !49924
- Allow to configure custom service desk email address suffix. !49932
- Add payload_example and payload_attribute_mapping columns to alert_management_http_integrations table. !49941
- Add prefilled variables for run pipeline page. !49985
- Add operations_access_level to project settings API. !50023
- Upgrade GitLab Pages to 1.32.0. !50062
- Add MergeRequest to VulnerabilityType in GraphQL. !50082
### Other (49 changes, 15 of them are from the community)
- Replace-GlDeprecatedDropdown-with-GlDropdown-in-app/assets/javascripts/boards. !41410 (nuwe1)
- Migrate bootstrap dropdown to GlDropdown in app/assets/javascripts/diffs. !41451 (nuwe1)
- Migrate awards list buttons to new buttons. !43061
- Apply GitLab UI button styles to buttons in app/views/projects/graphs directory. !44295 (Lakshit)
- Use GitLab's standard dropdown for the review mode chooser in the WebIDE. !46820
- Replaces elements with the bs-callout class with gl-alert vue component. !47331 (Gary Bell @garybell)
- Add analytics_devops_adoption_snapshots table. !47388
- Add relation name to indexes view. !47422
- Migrate chevron-down icon to svg. !47591
- Disable auto admin mode in features. !47670 (Diego Louzán)
- Remove avg_cycle_analytics from usage ping. !47812
- Remove unused .issue-box CSS. !48002 (Takuya Noguchi)
- Convert shared runner limit alert to gl-alert. !48063
- Remove temporary blocking issues scheduling indexes. !48064
- Update icons to svg in several sort dropdowns. !48092
- Move Terraform state versioning default to database. !48194
- Replace wrong index definition on labels (project_id, title). !48238
- Update GitLab Runner Helm Chart to 0.23.0. !48284
- Add `external_author` alias to `service_desk_reply_to`. !48363 (Lee Tickett)
- Migrate bs-callout to GlAlert in …/unmet_prerequisites_block.vue. !48398
- Improve logging on feature flag modification. !48417
- Replace bootstrap caret-down with chevron-down. !48424
- Convert bootstrap carets to svg chevrons. !48492
- Rename "Cycle Analytics" with "Value Stream Analytics" under /spec. !48531 (Takuya Noguchi)
- Update GitLab Workhorse to v8.56.0. !48592
- Update gitaly gem to 13.6.1. !48601
- Rename "CYCLE_ANALYTICS_*" variables for CI with "VSA_*". !48675 (Takuya Noguchi)
- Adds gl button classes to manifest imports. !48697
- Add btree bloat estimation view. !48698
- Disable auto admin mode on requests and views specs. !48700 (Diego Louzán)
- Move users#show.json to users#activity.json. !48712 (Takuya Noguchi)
- Remove `view_diffs_file_by_file` feature flag. !48966
- Move profiles/keys#get_keys to users#ssh_keys. !48991 (Takuya Noguchi)
- Replace wiki fontawesome icons with emojis. !49097
- Add a project setting to allow editing commit messages. !49152
- Updated UI text to match style guidelines. !49275
- Move profiles/gpg_keys#get_keys to users#gpg_keys. !49448 (Takuya Noguchi)
- Remove references to cross_project_pipeline source in documentation. !49579
- Updated UI text to match style guidelines. !49632
- Migrate bs-callout to GlAlert for components using app/assets/javascripts/vue_shared/components/callout.vue. !49732 (Gary Bell @garybell)
- Bump gitlab-shell version to v13.14.0. !49810
- Track index bloat estimate. !49822
- Conver create merge request button to gl. !49864
- Adds gitlab ui classes to project dir buttons. !49939
- Removed count_uploads_size_in_storage_stats feature flag. !49998
- Delete MockDeploymentService records, used only in development environments. !50030
- Add feed_token specs to spec/features/profiles/personal_access_tokens_spec.rb. !50059
- Replace spec/controllers/ide_controller_spec.rb with request spec. !50075 (Takuya Noguchi)
- Update GitLab Workhorse to v8.57.0.
## 13.6.3 (2020-12-10)
@ -604,6 +1096,22 @@ entry.
- Change wording on the project remove fork page. !47878
## 13.5.5 (2020-12-07)
### Security (10 changes)
- Validate zoom links to start with https only. !1055
- Require at least 3 characters when searching for project in the Explore page.
- Do not show emails of users in confirmation page.
- Forbid setting a gitlabUserList strategy to a list from another project.
- Fix mermaid resource consumption in GFM fields.
- Ensure group and project memberships are not leaked via API for users with private profiles.
- GraphQL User: do not expose email if set to private.
- Filter search parameter to prevent data leaks.
- Do not expose starred projects of users with private profile via API.
- Do not show starred & contributed projects of users with private profile.
## 13.5.4 (2020-11-13)
### Fixed (4 changes)
@ -1223,6 +1731,22 @@ entry.
- Bump cluster applications CI template. !45472
## 13.4.7 (2020-12-07)
### Security (10 changes)
- Validate zoom links to start with https only. !1055
- Require at least 3 characters when searching for project in the Explore page.
- Do not show emails of users in confirmation page.
- Forbid setting a gitlabUserList strategy to a list from another project.
- Fix mermaid resource consumption in GFM fields.
- Ensure group and project memberships are not leaked via API for users with private profiles.
- GraphQL User: do not expose email if set to private.
- Filter search parameter to prevent data leaks.
- Do not expose starred projects of users with private profile via API.
- Do not show starred & contributed projects of users with private profile.
## 13.4.6 (2020-11-03)
### Fixed (1 change)

View file

@ -1 +1 @@
13.6.7
13.7.7

View file

@ -1 +1 @@
2.7.0
2.8.0

View file

@ -1 +1 @@
13.6.1
13.7.0

View file

@ -1 +1 @@
1.30.2
1.34.0

View file

@ -1 +1 @@
13.13.1
13.14.1

View file

@ -1 +1 @@
8.54.2
8.58.2

24
Gemfile
View file

@ -66,7 +66,7 @@ gem 'attr_encrypted', '~> 3.1.0'
gem 'u2f', '~> 0.2.1'
# GitLab Pages
gem 'validates_hostname', '~> 1.0.10'
gem 'validates_hostname', '~> 1.0.11'
gem 'rubyzip', '~> 2.0.0', require: 'zip'
# GitLab Pages letsencrypt support
gem 'acme-client', '~> 2.0', '>= 2.0.6'
@ -115,11 +115,11 @@ gem 'carrierwave', '~> 1.3'
gem 'mini_magick', '~> 4.10.1'
# for backups
gem 'fog-aws', '~> 3.5'
gem 'fog-aws', '~> 3.7'
# Locked until fog-google resolves https://github.com/fog/fog-google/issues/421.
# Also see config/initializers/fog_core_patch.rb.
gem 'fog-core', '= 2.1.0'
gem 'fog-google', '~> 1.11'
gem 'fog-google', '~> 1.12'
gem 'fog-local', '~> 0.6'
gem 'fog-openstack', '~> 1.0'
gem 'fog-rackspace', '~> 0.1.1'
@ -149,7 +149,7 @@ gem 'html-pipeline', '~> 2.12'
gem 'deckar01-task_list', '2.3.1'
gem 'gitlab-markup', '~> 1.7.1'
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'commonmarker', '~> 0.20'
gem 'commonmarker', '~> 0.21'
gem 'kramdown', '~> 2.3.0'
gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~> 6.1.2'
@ -159,7 +159,8 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 2.0.10'
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
gem 'asciidoctor-plantuml', '~> 0.0.12'
gem 'rouge', '~> 3.25.0'
gem 'asciidoctor-kroki', '~> 0.2.2', require: false
gem 'rouge', '~> 3.26.0'
gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.10.9'
@ -209,7 +210,7 @@ gem 'httparty', '~> 0.16.4'
gem 'rainbow', '~> 3.0'
# Progress bar
gem 'ruby-progressbar'
gem 'ruby-progressbar', '~> 1.10'
# GitLab settings
gem 'settingslogic', '~> 2.0.9'
@ -291,7 +292,6 @@ gem 'sassc-rails', '~> 2.1.0'
gem 'terser', '1.0.2'
gem 'addressable', '~> 2.7'
gem 'font-awesome-rails', '~> 4.7'
gem 'gemojione', '~> 3.3'
gem 'gon', '~> 6.2'
gem 'request_store', '~> 1.5'
@ -311,7 +311,7 @@ gem 'gitlab-pg_query', '~> 1.3', require: 'pg_query'
gem 'premailer-rails', '~> 1.10.3'
# LabKit: Tracing and Correlation
gem 'gitlab-labkit', '0.13.1'
gem 'gitlab-labkit', '0.13.3'
# I18n
gem 'ruby_parser', '~> 3.15', require: false
@ -351,6 +351,7 @@ group :development do
end
group :development, :test do
gem 'deprecation_toolkit', '~> 1.5.1', require: false
gem 'bullet', '~> 6.1.0'
gem 'pry-byebug', '~> 3.9.0', platform: :mri
gem 'pry-rails', '~> 0.3.9'
@ -370,7 +371,7 @@ group :development, :test do
gem 'spring', '~> 2.1.0'
gem 'spring-commands-rspec', '~> 1.0.4'
gem 'gitlab-styles', '~> 5.1.0', require: false
gem 'gitlab-styles', '~> 5.3.0', require: false
gem 'scss_lint', '~> 0.59.0', require: false
gem 'haml_lint', '~> 0.36.0', require: false
@ -428,7 +429,7 @@ end
gem 'octokit', '~> 4.15'
# https://gitlab.com/gitlab-org/gitlab/issues/207207
gem 'gitlab-mail_room', '~> 0.0.7', require: 'mail_room'
gem 'gitlab-mail_room', '~> 0.0.8', require: 'mail_room'
gem 'email_reply_trimmer', '~> 0.1'
gem 'html2text'
@ -464,7 +465,7 @@ group :ed25519 do
end
# Gitaly GRPC protocol definitions
gem 'gitaly', '~> 13.5.0-rc2'
gem 'gitaly', '~> 13.7.0.pre.rc1'
gem 'grpc', '~> 1.30.2'
@ -477,6 +478,7 @@ gem 'flipper', '~> 0.17.1'
gem 'flipper-active_record', '~> 0.17.1'
gem 'flipper-active_support_cache_store', '~> 0.17.1'
gem 'unleash', '~> 0.1.5'
gem 'gitlab-experiment', '~> 0.4.4'
# Structured logging
gem 'lograge', '~> 0.5'

View file

@ -84,6 +84,8 @@ GEM
asciidoctor (2.0.10)
asciidoctor-include-ext (0.3.1)
asciidoctor (>= 1.5.6, < 3.0.0)
asciidoctor-kroki (0.2.2)
asciidoctor (~> 2.0)
asciidoctor-plantuml (0.0.12)
asciidoctor (>= 1.5.6, < 3.0.0)
ast (2.4.1)
@ -177,7 +179,7 @@ GEM
open4 (~> 1.3)
coderay (1.1.3)
colored2 (3.1.2)
commonmarker (0.20.1)
commonmarker (0.21.0)
ruby-enum (~> 0.5)
concord (0.1.5)
adamantium (~> 0.2.0)
@ -220,10 +222,12 @@ GEM
debugger-ruby_core_source (1.3.8)
deckar01-task_list (2.3.1)
html-pipeline
declarative (0.0.10)
declarative (0.0.20)
declarative-option (0.1.0)
default_value_for (3.3.0)
activerecord (>= 3.2.0, < 6.1)
deprecation_toolkit (1.5.1)
activesupport (>= 4.2)
derailed_benchmarks (1.7.0)
benchmark-ips (~> 2)
get_process_mem (~> 0)
@ -359,7 +363,7 @@ GEM
fog-json
ipaddress (~> 0.8)
xml-simple (~> 1.1)
fog-aws (3.5.2)
fog-aws (3.7.0)
fog-core (~> 2.1)
fog-json (~> 1.1)
fog-xml (~> 0.1)
@ -369,11 +373,11 @@ GEM
excon (~> 0.58)
formatador (~> 0.2)
mime-types
fog-google (1.11.0)
fog-google (1.12.0)
fog-core (<= 2.1.0)
fog-json (~> 1.2)
fog-xml (~> 0.1.0)
google-api-client (>= 0.32, < 0.34)
google-api-client (>= 0.44.2, < 0.51)
google-cloud-env (~> 1.2)
fog-json (1.2.0)
fog-core
@ -392,8 +396,6 @@ GEM
fog-xml (0.1.3)
fog-core
nokogiri (>= 1.5.11, < 2.0.0)
font-awesome-rails (4.7.0.5)
railties (>= 3.2, < 6.1)
formatador (0.2.5)
fugit (1.2.1)
et-orbi (~> 1.1, >= 1.1.8)
@ -418,11 +420,14 @@ GEM
rails (>= 3.2.0)
git (1.7.0)
rchardet (~> 1.8)
gitaly (13.5.0.pre.rc2)
gitaly (13.7.0.pre.rc1)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-chronic (0.10.5)
numerizer (~> 0.2)
gitlab-experiment (0.4.4)
activesupport (>= 3.0)
scientist (~> 1.5, >= 1.5.0)
gitlab-fog-azure-rm (1.0.0)
azure-storage-blob (~> 2.0)
azure-storage-common (~> 2.0)
@ -430,15 +435,16 @@ GEM
fog-json (~> 1.2.0)
mime-types
ms_rest_azure (~> 0.12.0)
gitlab-labkit (0.13.1)
gitlab-labkit (0.13.3)
actionpack (>= 5.0.0, < 6.1.0)
activesupport (>= 5.0.0, < 6.1.0)
gitlab-pg_query (~> 1.3)
grpc (~> 1.19)
jaeger-client (~> 1.1)
opentracing (~> 0.4)
redis (> 3.0.0, < 5.0.0)
gitlab-license (1.0.0)
gitlab-mail_room (0.0.7)
gitlab-mail_room (0.0.8)
gitlab-markup (1.7.1)
gitlab-net-dns (0.9.1)
gitlab-pg_query (1.3.0)
@ -449,7 +455,7 @@ GEM
gitlab-puma (>= 2.7, < 5)
gitlab-sidekiq-fetcher (0.5.2)
sidekiq (~> 5)
gitlab-styles (5.1.0)
gitlab-styles (5.3.0)
rubocop (~> 0.89.1)
rubocop-gitlab-security (~> 0.1.0)
rubocop-performance (~> 1.8.1)
@ -468,20 +474,21 @@ GEM
actionpack (>= 3.0)
multi_json
request_store (>= 1.0)
google-api-client (0.33.2)
google-api-client (0.50.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
rexml
signet (~> 0.12)
google-cloud-env (1.4.0)
faraday (>= 0.17.3, < 2.0)
google-protobuf (3.12.4)
googleapis-common-protos-types (1.0.5)
google-protobuf (~> 3.11)
googleauth (0.12.0)
googleauth (0.14.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@ -691,7 +698,7 @@ GEM
marginalia (1.9.0)
actionpack (>= 2.3)
activerecord (>= 2.3)
memoist (0.16.0)
memoist (0.16.2)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
memory_profiler (0.9.14)
@ -832,7 +839,7 @@ GEM
org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
os (1.0.0)
os (1.1.1)
parallel (1.19.2)
parser (2.7.2.0)
ast (~> 2.4.1)
@ -977,7 +984,7 @@ GEM
rexml (3.2.4)
rinku (2.0.0)
rotp (2.1.2)
rouge (3.25.0)
rouge (3.26.0)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
@ -1041,7 +1048,7 @@ GEM
rubocop-rspec (1.44.1)
rubocop (~> 0.87)
rubocop-ast (>= 0.7.1)
ruby-enum (0.7.2)
ruby-enum (0.8.0)
i18n
ruby-fogbugz (0.2.1)
crack (~> 0.4)
@ -1081,6 +1088,7 @@ GEM
sawyer (0.8.2)
addressable (>= 2.3.5)
faraday (> 0.8, < 2.0)
scientist (1.5.0)
scss_lint (0.59.0)
sass (~> 3.5, >= 3.5.5)
securecompare (1.0.0)
@ -1215,7 +1223,7 @@ GEM
validate_url (1.0.8)
activemodel (>= 3.0.0)
public_suffix
validates_hostname (1.0.10)
validates_hostname (1.0.11)
activerecord (>= 3.0)
activesupport (>= 3.0)
version_sorter (2.2.4)
@ -1267,6 +1275,7 @@ DEPENDENCIES
asana (= 0.10.2)
asciidoctor (~> 2.0.10)
asciidoctor-include-ext (~> 0.3.1)
asciidoctor-kroki (~> 0.2.2)
asciidoctor-plantuml (~> 0.0.12)
atlassian-jwt (~> 0.2.0)
attr_encrypted (~> 3.1.0)
@ -1292,7 +1301,7 @@ DEPENDENCIES
capybara-screenshot (~> 1.0.22)
carrierwave (~> 1.3)
charlock_holmes (~> 0.7.7)
commonmarker (~> 0.20)
commonmarker (~> 0.21)
concurrent-ruby (~> 1.1)
connection_pool (~> 2.0)
countries (~> 3.0)
@ -1302,6 +1311,7 @@ DEPENDENCIES
database_cleaner (~> 1.7.0)
deckar01-task_list (= 2.3.1)
default_value_for (~> 3.3.0)
deprecation_toolkit (~> 1.5.1)
derailed_benchmarks
device_detector
devise (~> 4.7.2)
@ -1329,33 +1339,33 @@ DEPENDENCIES
flipper-active_support_cache_store (~> 0.17.1)
flowdock (~> 0.7)
fog-aliyun (~> 0.3)
fog-aws (~> 3.5)
fog-aws (~> 3.7)
fog-core (= 2.1.0)
fog-google (~> 1.11)
fog-google (~> 1.12)
fog-local (~> 0.6)
fog-openstack (~> 1.0)
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7)
fugit (~> 1.2.1)
fuubar (~> 2.2.0)
gemojione (~> 3.3)
gettext (~> 3.3)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly (~> 13.5.0.pre.rc2)
gitaly (~> 13.7.0.pre.rc1)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-experiment (~> 0.4.4)
gitlab-fog-azure-rm (~> 1.0)
gitlab-labkit (= 0.13.1)
gitlab-labkit (= 0.13.3)
gitlab-license (~> 1.0)
gitlab-mail_room (~> 0.0.7)
gitlab-mail_room (~> 0.0.8)
gitlab-markup (~> 1.7.1)
gitlab-net-dns (~> 0.9.1)
gitlab-pg_query (~> 1.3)
gitlab-puma (~> 4.3.3.gitlab.2)
gitlab-puma_worker_killer (~> 0.1.1.gitlab.1)
gitlab-sidekiq-fetcher (= 0.5.2)
gitlab-styles (~> 5.1.0)
gitlab-styles (~> 5.3.0)
gitlab_chronic_duration (~> 0.10.6.2)
gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2)
@ -1468,7 +1478,7 @@ DEPENDENCIES
request_store (~> 1.5)
responders (~> 3.0)
retriable (~> 3.1.2)
rouge (~> 3.25.0)
rouge (~> 3.26.0)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 4.0.0)
@ -1477,7 +1487,7 @@ DEPENDENCIES
rspec_profiling (~> 0.0.6)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 1.3.0)
ruby-progressbar
ruby-progressbar (~> 1.10)
ruby_parser (~> 3.15)
rubyzip (~> 2.0.0)
rugged (~> 0.28)
@ -1515,7 +1525,7 @@ DEPENDENCIES
unicorn-worker-killer (~> 0.4.4)
unleash (~> 0.1.5)
valid_email (~> 0.1)
validates_hostname (~> 1.0.10)
validates_hostname (~> 1.0.11)
version_sorter (~> 2.2.4)
vmstat (~> 2.3.0)
webauthn (~> 2.3)

View file

@ -1 +1 @@
13.6.7
13.7.7

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 B

View file

@ -0,0 +1,33 @@
<svg width="234" height="162" viewBox="0 0 234 162" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M174.68 56.344H200.5C215.412 56.344 227.5 44.1787 227.5 29.172C227.5 14.1653 215.412 2 200.5 2C185.588 2 173.5 14.1653 173.5 29.172C173.5 36.2548 176.193 42.7046 180.604 47.5412" stroke="#C2B7E6" stroke-width="4" stroke-linecap="round"/>
<path d="M145.5 76.4714C145.5 65.3553 154.454 56.344 165.5 56.344" stroke="#C2B7E6" stroke-width="4" stroke-linecap="round"/>
<path d="M102.5 121.758H29.5C14.5883 121.758 2.5 109.593 2.5 94.586C2.5 79.5794 14.5883 67.4141 29.5 67.4141C44.4117 67.4141 56.5 79.5794 56.5 94.586C56.5 101.669 53.8072 108.119 49.3957 112.955" stroke="#C2B7E6" stroke-width="4" stroke-linecap="round"/>
<path d="M67.0466 121.758H52.5C42.5589 121.758 34.5 129.868 34.5 139.873C34.5 149.877 42.5589 157.987 52.5 157.987C62.4411 157.987 70.5 149.877 70.5 139.873C70.5 137.478 70.0384 135.192 69.1998 133.1" stroke="#C2B7E6" stroke-width="4" stroke-linecap="round"/>
<g clip-path="url(#clip0)">
<path d="M55.0188 135.3C55.1617 134.764 54.8451 134.211 54.3117 134.068C53.7782 133.925 53.2298 134.243 53.0869 134.78L49.9811 146.445C49.8381 146.981 50.1547 147.534 50.6882 147.677C51.2217 147.821 51.77 147.503 51.9129 146.965L55.0188 135.3Z" fill="#FC6D26"/>
<path d="M49.2071 137.142C49.5976 137.534 49.5976 138.172 49.2071 138.565L46.9142 140.873L49.2071 143.18C49.5976 143.573 49.5976 144.211 49.2071 144.603C48.8166 144.997 48.1834 144.997 47.7929 144.603L44.7929 141.584C44.4024 141.192 44.4024 140.554 44.7929 140.161L47.7929 137.142C48.1834 136.748 48.8166 136.748 49.2071 137.142Z" fill="#FC6D26"/>
<path d="M55.7929 137.142C55.4024 137.534 55.4024 138.172 55.7929 138.565L58.0858 140.873L55.7929 143.18C55.4024 143.573 55.4024 144.211 55.7929 144.603C56.1834 144.997 56.8166 144.997 57.2071 144.603L60.2071 141.584C60.5976 141.192 60.5976 140.554 60.2071 140.161L57.2071 137.142C56.8166 136.748 56.1834 136.748 55.7929 137.142Z" fill="#FC6D26"/>
</g>
<path d="M212.102 160C222.815 160 231.5 151.214 231.5 140.376C231.5 129.537 222.815 120.752 212.102 120.752H151.5" stroke="#C2B7E6" stroke-width="4" stroke-linecap="round"/>
<path d="M126.5 138.866C107.171 138.866 91.5 123.096 91.5 103.643C91.5 84.191 107.171 68.4204 126.5 68.4204C145.829 68.4204 161.5 84.191 161.5 103.643C161.5 123.096 145.829 138.866 126.5 138.866ZM126.5 131.451C141.76 131.451 154.132 119.001 154.132 103.643C154.132 88.2861 141.76 75.8358 126.5 75.8358C111.24 75.8358 98.8684 88.2861 98.8684 103.643C98.8684 119.001 111.24 131.451 126.5 131.451Z" fill="#FC6D26"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M126.126 87.1326C135.355 87.1326 142.906 94.5624 142.906 103.643C142.906 112.724 135.355 120.154 126.126 120.154C120.672 120.154 115.638 117.265 112.281 113.137L126.126 103.643V87.1326Z" fill="#6E49CB"/>
<g clip-path="url(#clip1)">
<path d="M29.5 90.2659L24.3571 91.9534V93.1629C24.3571 94.9623 25.087 96.6872 26.3846 97.9546L29.5 100.997V90.2659Z" fill="#FC6D26"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5 86.8909L29.5 83.5159L41.5 86.8909V93.1115C41.5 96.6919 40.0551 100.126 37.4832 102.657L29.5 110.516L21.5168 102.657C18.9449 100.126 17.5 96.6919 17.5 93.1115V86.8909ZM20.9286 93.1115V89.4366L29.5 87.0259L38.0714 89.4366V93.1115C38.0714 95.7968 36.9878 98.3721 35.0588 100.271L29.5 105.743L23.9412 100.271C22.0122 98.3721 20.9286 95.7968 20.9286 93.1115Z" fill="#FC6D26"/>
</g>
<g clip-path="url(#clip2)">
<path d="M210.857 19.7297L209.51 24.8237C208.922 27.0445 207.518 28.9576 205.581 30.1752L194.728 36.999L191.862 34.1146L198.642 23.1922C199.852 21.2431 201.753 19.8298 203.96 19.2386L209.022 17.8826C209.822 17.6681 210.644 18.1474 210.857 18.953C210.925 19.2075 210.925 19.4752 210.857 19.7297ZM207.292 21.4702L204.732 22.1561C203.261 22.5503 201.993 23.4925 201.187 24.7918L196.517 32.3146L203.992 27.6148C205.283 26.803 206.219 25.5276 206.611 24.0471L207.292 21.4702ZM196.5 38.2294L204 33.7007V35.2103C204 38.5451 201.314 41.2485 198 41.2485H196.5V38.2294ZM190.5 32.1912H187.5V30.6816C187.5 27.3468 190.186 24.6434 193.5 24.6434H195L190.5 32.1912Z" fill="#FC6D26"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M209.914 132.822C209.384 132.822 208.875 133.032 208.5 133.407L204.796 137.111C204.613 137.293 204.5 137.544 204.5 137.822V144.822C204.5 145.926 205.395 146.822 206.5 146.822H216.5C217.605 146.822 218.5 145.926 218.5 144.822V137.822C218.5 137.546 218.388 137.296 218.207 137.115L214.5 133.407C214.125 133.032 213.616 132.822 213.086 132.822H209.914ZM215.086 136.822L213.086 134.822H212.5V136.822H215.086ZM210.5 134.822H209.914L207.914 136.822H210.5V134.822ZM206.5 138.822H216.5V144.822H206.5V138.822Z" fill="#FC6D26"/>
<defs>
<clipPath id="clip0">
<rect width="16" height="13.6779" fill="white" transform="translate(44.5 134.033)"/>
</clipPath>
<clipPath id="clip1">
<rect width="24" height="27.172" fill="white" transform="translate(17.5 83.5159)"/>
</clipPath>
<clipPath id="clip2">
<rect width="24" height="24.1529" fill="white" transform="translate(187.5 17.0956)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View file

@ -0,0 +1,26 @@
<script>
import UsersTable from './users_table.vue';
export default {
components: {
UsersTable,
},
props: {
users: {
type: Array,
required: false,
default: () => [],
},
paths: {
type: Object,
required: true,
},
},
};
</script>
<template>
<div>
<users-table :users="users" :paths="paths" />
</div>
</template>

View file

@ -0,0 +1,63 @@
<script>
import { GlTable } from '@gitlab/ui';
import { __ } from '~/locale';
const DEFAULT_TH_CLASSES =
'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!';
const thWidthClass = width => `gl-w-${width}p ${DEFAULT_TH_CLASSES}`;
export default {
components: {
GlTable,
},
props: {
users: {
type: Array,
required: true,
},
paths: {
type: Object,
required: true,
},
},
fields: [
{
key: 'name',
label: __('Name'),
thClass: thWidthClass(40),
},
{
key: 'projectsCount',
label: __('Projects'),
thClass: thWidthClass(10),
},
{
key: 'createdAt',
label: __('Created on'),
thClass: thWidthClass(15),
},
{
key: 'lastActivityOn',
label: __('Last activity'),
thClass: thWidthClass(15),
},
{
key: 'settings',
label: '',
thClass: thWidthClass(20),
},
],
};
</script>
<template>
<div>
<gl-table
:items="users"
:fields="$options.fields"
:empty-text="s__('AdminUsers|No users found')"
show-empty
stacked="md"
/>
</div>
</template>

View file

@ -0,0 +1,22 @@
import Vue from 'vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import AdminUsersApp from './components/app.vue';
export default function(el = document.querySelector('#js-admin-users-app')) {
if (!el) {
return false;
}
const { users, paths } = el.dataset;
return new Vue({
el,
render: createElement =>
createElement(AdminUsersApp, {
props: {
users: convertObjectPropsToCamelCase(JSON.parse(users), { deep: true }),
paths: convertObjectPropsToCamelCase(JSON.parse(paths)),
},
}),
});
}

View file

@ -27,25 +27,12 @@ export default {
<gl-dropdown-item
:key="user.username"
data-testid="assigneeDropdownItem"
class="assignee-dropdown-item gl-vertical-align-middle"
:active="active"
active-class="is-active"
:avatar-url="user.avatar_url"
:secondary-text="`@${user.username}`"
@click="$emit('update-alert-assignees', user.username)"
>
<span class="gl-relative mr-2">
<img
:alt="user.username"
:src="user.avatar_url"
:width="32"
class="avatar avatar-inline gl-m-0 s32"
data-qa-selector="avatar_image"
/>
</span>
<span class="d-flex gl-flex-direction-column gl-overflow-hidden">
<strong class="dropdown-menu-user-full-name">
{{ user.name }}
</strong>
<span class="dropdown-menu-user-username"> {{ user.username }}</span>
</span>
{{ user.name }}
</gl-dropdown-item>
</template>

View file

@ -13,7 +13,7 @@ import {
} from '@gitlab/ui';
import { debounce } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
import { s__, __ } from '~/locale';
import alertSetAssignees from '../../graphql/mutations/alert_set_assignees.mutation.graphql';
import SidebarAssignee from './sidebar_assignee.vue';
@ -96,7 +96,10 @@ export default {
.sort((a, b) => (a.active === b.active ? 0 : a.active ? -1 : 1)); // eslint-disable-line no-nested-ternary
},
dropdownClass() {
return this.isDropdownShowing ? 'show' : 'gl-display-none';
return this.isDropdownShowing ? 'dropdown-menu-selectable show' : 'gl-display-none';
},
dropDownTitle() {
return this.userName ?? __('Select assignee');
},
userListValid() {
return !this.isDropdownSearching && this.users.length > 0;
@ -217,81 +220,80 @@ export default {
</a>
</p>
<div class="dropdown dropdown-menu-selectable" :class="dropdownClass">
<gl-dropdown
ref="dropdown"
:text="userName"
class="w-100"
toggle-class="dropdown-menu-toggle"
@keydown.esc.native="hideDropdown"
@hide="hideDropdown"
>
<p class="gl-new-dropdown-header-top">
{{ __('Assign To') }}
</p>
<gl-search-box-by-type v-model.trim="search" :placeholder="__('Search users')" />
<div class="dropdown-content dropdown-body">
<template v-if="userListValid">
<gl-dropdown-item
:active="!userName"
active-class="is-active"
@click="updateAlertAssignees('')"
>
{{ __('Unassigned') }}
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-dropdown
ref="dropdown"
:text="dropDownTitle"
class="gl-w-full"
:class="dropdownClass"
toggle-class="dropdown-menu-toggle"
@keydown.esc.native="hideDropdown"
@hide="hideDropdown"
>
<p class="gl-new-dropdown-header-top">
{{ __('Assign To') }}
</p>
<gl-search-box-by-type v-model.trim="search" :placeholder="__('Search users')" />
<div class="dropdown-content dropdown-body">
<template v-if="userListValid">
<gl-dropdown-item
:active="!userName"
active-class="is-active"
@click="updateAlertAssignees('')"
>
{{ __('Unassigned') }}
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-dropdown-section-header>
{{ __('Assignee') }}
</gl-dropdown-section-header>
<sidebar-assignee
v-for="user in sortedUsers"
:key="user.username"
:user="user"
:active="user.active"
@update-alert-assignees="updateAlertAssignees"
/>
</template>
<p v-else-if="userListEmpty" class="mx-3 my-2">
{{ __('No Matching Results') }}
</p>
<gl-loading-icon v-else />
</div>
</gl-dropdown>
</div>
<gl-loading-icon v-if="isUpdating" :inline="true" />
<div v-else-if="!isDropdownShowing" class="value gl-m-0" :class="{ 'no-value': !userName }">
<div v-if="userName" class="gl-display-inline-flex gl-mt-2" data-testid="assigned-users">
<span class="gl-relative mr-2">
<img
:alt="userName"
:src="userImg"
:width="32"
class="avatar avatar-inline gl-m-0 s32"
data-qa-selector="avatar_image"
<gl-dropdown-section-header>
{{ __('Assignee') }}
</gl-dropdown-section-header>
<sidebar-assignee
v-for="user in sortedUsers"
:key="user.username"
:user="user"
:active="user.active"
@update-alert-assignees="updateAlertAssignees"
/>
</span>
<span class="gl-display-flex gl-flex-direction-column gl-overflow-hidden">
<strong class="dropdown-menu-user-full-name">
{{ userFullName }}
</strong>
<span class="dropdown-menu-user-username">{{ userName }}</span>
</span>
</template>
<p v-else-if="userListEmpty" class="gl-mx-5 gl-my-4">
{{ __('No Matching Results') }}
</p>
<gl-loading-icon v-else />
</div>
<span v-else class="gl-display-flex gl-align-items-center gl-line-height-normal">
{{ __('None') }} -
<gl-button
class="gl-ml-2"
href="#"
variant="link"
data-testid="unassigned-users"
@click="updateAlertAssignees(currentUser)"
>
{{ __('assign yourself') }}
</gl-button>
</gl-dropdown>
</div>
<gl-loading-icon v-if="isUpdating" :inline="true" />
<div v-else-if="!isDropdownShowing" class="value gl-m-0" :class="{ 'no-value': !userName }">
<div v-if="userName" class="gl-display-inline-flex gl-mt-2" data-testid="assigned-users">
<span class="gl-relative gl-mr-4">
<img
:alt="userName"
:src="userImg"
:width="32"
class="avatar avatar-inline gl-m-0 s32"
data-qa-selector="avatar_image"
/>
</span>
<span class="gl-display-flex gl-flex-direction-column gl-overflow-hidden">
<strong class="dropdown-menu-user-full-name">
{{ userFullName }}
</strong>
<span class="dropdown-menu-user-username">@{{ userName }}</span>
</span>
</div>
<span v-else class="gl-display-flex gl-align-items-center gl-line-height-normal">
{{ __('None') }} -
<gl-button
class="gl-ml-2"
href="#"
variant="link"
data-testid="unassigned-users"
@click="updateAlertAssignees(currentUser)"
>
{{ __('assign yourself') }}
</gl-button>
</span>
</div>
</div>
</template>

View file

@ -11,7 +11,6 @@ import {
GlSprintf,
} from '@gitlab/ui';
import { s__, __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import Tracking from '~/tracking';
import {
trackAlertIntegrationsViewsOptions,
@ -54,7 +53,6 @@ export default {
GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective,
},
mixins: [glFeatureFlagsMixin()],
props: {
integrations: {
type: Array,
@ -170,7 +168,7 @@ export default {
</template>
<template #cell(actions)="{ item }">
<gl-button-group v-if="glFeatures.httpIntegrationsList" class="gl-ml-3">
<gl-button-group class="gl-ml-3">
<gl-button icon="pencil" @click="$emit('edit-integration', { id: item.id })" />
<gl-button
v-gl-modal.deleteIntegration

View file

@ -32,6 +32,75 @@ import {
// feature rollout plan - https://gitlab.com/gitlab-org/gitlab/-/issues/262707#note_442529171
import mockedCustomMapping from './mocks/parsedMapping.json';
export const i18n = {
integrationFormSteps: {
step1: {
label: s__('AlertSettings|1. Select integration type'),
enterprise: s__(
'AlertSettings|In free versions of GitLab, only one integration for each type can be added. %{linkStart}Upgrade your subscription%{linkEnd} to add additional integrations.',
),
},
step2: {
label: s__('AlertSettings|2. Name integration'),
placeholder: s__('AlertSettings|Enter integration name'),
prometheus: s__('AlertSettings|Prometheus'),
},
step3: {
label: s__('AlertSettings|3. Set up webhook'),
help: s__(
"AlertSettings|Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.",
),
prometheusHelp: s__(
'AlertSettings|Utilize the URL and authorization key below to authorize Prometheus to send alerts to GitLab. Review the Prometheus documentation to learn where to add these details, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.',
),
info: s__('AlertSettings|Authorization key'),
reset: s__('AlertSettings|Reset Key'),
},
step4: {
label: s__('AlertSettings|4. Sample alert payload (optional)'),
help: s__(
'AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to create a custom mapping (optional), or to test the integration (also optional).',
),
prometheusHelp: s__(
'AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to test the integration (optional).',
),
placeholder: s__('AlertSettings|{ "events": [{ "application": "Name of application" }] }'),
resetHeader: s__('AlertSettings|Reset the mapping'),
resetBody: s__(
"AlertSettings|If you edit the payload, the stored mapping will be reset, and you'll need to re-map the fields.",
),
resetOk: s__('AlertSettings|Proceed with editing'),
editPayload: s__('AlertSettings|Edit payload'),
submitPayload: s__('AlertSettings|Submit payload'),
payloadParsedSucessMsg: s__(
'AlertSettings|Sample payload has been parsed. You can now map the fields.',
),
},
step5: {
label: s__('AlertSettings|5. Map fields (optional)'),
intro: s__(
"AlertSettings|If you've provided a sample alert payload, you can create a custom mapping for your endpoint. The default GitLab alert keys are listed below. Please define which payload key should map to the specified GitLab key.",
),
},
prometheusFormUrl: {
label: s__('AlertSettings|Prometheus API base URL'),
help: s__('AlertSettings|URL cannot be blank and must start with http or https'),
},
restKeyInfo: {
label: s__(
'AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.',
),
},
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
opsgenie: {
label: s__('AlertSettings|2. Add link to your Opsgenie alert list'),
info: s__(
'AlertSettings|Utilizing this option will link the GitLab Alerts navigation item to your existing Opsgenie instance. By selecting this option, you cannot receive alerts from any other source in GitLab; it will effectively be turning Alerts within GitLab off as a feature.',
),
},
},
};
export default {
placeholders: {
prometheus: targetPrometheusUrlPlaceholder,
@ -39,73 +108,7 @@ export default {
},
JSON_VALIDATE_DELAY,
typeSet,
i18n: {
integrationFormSteps: {
step1: {
label: s__('AlertSettings|1. Select integration type'),
enterprise: s__(
'AlertSettings|In free versions of GitLab, only one integration for each type can be added. %{linkStart}Upgrade your subscription%{linkEnd} to add additional integrations.',
),
},
step2: {
label: s__('AlertSettings|2. Name integration'),
placeholder: s__('AlertSettings|Enter integration name'),
},
step3: {
label: s__('AlertSettings|3. Set up webhook'),
help: s__(
"AlertSettings|Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.",
),
prometheusHelp: s__(
'AlertSettings|Utilize the URL and authorization key below to authorize Prometheus to send alerts to GitLab. Review the Prometheus documentation to learn where to add these details, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.',
),
info: s__('AlertSettings|Authorization key'),
reset: s__('AlertSettings|Reset Key'),
},
step4: {
label: s__('AlertSettings|4. Sample alert payload (optional)'),
help: s__(
'AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to create a custom mapping (optional), or to test the integration (also optional).',
),
prometheusHelp: s__(
'AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to test the integration (optional).',
),
placeholder: s__('AlertSettings|{ "events": [{ "application": "Name of application" }] }'),
resetHeader: s__('AlertSettings|Reset the mapping'),
resetBody: s__(
"AlertSettings|If you edit the payload, the stored mapping will be reset, and you'll need to re-map the fields.",
),
resetOk: s__('AlertSettings|Proceed with editing'),
editPayload: s__('AlertSettings|Edit payload'),
submitPayload: s__('AlertSettings|Submit payload'),
payloadParsedSucessMsg: s__(
'AlertSettings|Sample payload has been parsed. You can now map the fields.',
),
},
step5: {
label: s__('AlertSettings|5. Map fields (optional)'),
intro: s__(
"AlertSettings|If you've provided a sample alert payload, you can create a custom mapping for your endpoint. The default GitLab alert keys are listed below. Please define which payload key should map to the specified GitLab key.",
),
},
prometheusFormUrl: {
label: s__('AlertSettings|Prometheus API base URL'),
help: s__('AlertSettings|URL cannot be blank and must start with http or https'),
},
restKeyInfo: {
label: s__(
'AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.',
),
},
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
opsgenie: {
label: s__('AlertSettings|2. Add link to your Opsgenie alert list'),
info: s__(
'AlertSettings|Utilizing this option will link the GitLab Alerts navigation item to your existing Opsgenie instance. By selecting this option, you cannot receive alerts from any other source in GitLab; it will effectively be turning Alerts within GitLab off as a feature.',
),
},
},
},
i18n,
components: {
ClipboardButton,
GlButton,
@ -216,8 +219,12 @@ export default {
return {
name: this.currentIntegration?.name || '',
active: this.currentIntegration?.active || false,
token: this.currentIntegration?.token || this.selectedIntegrationType.token,
url: this.currentIntegration?.url || this.selectedIntegrationType.url,
token:
this.currentIntegration?.token ||
(this.selectedIntegrationType !== this.generic ? this.selectedIntegrationType.token : ''),
url:
this.currentIntegration?.url ||
(this.selectedIntegrationType !== this.generic ? this.selectedIntegrationType.url : ''),
apiUrl: this.currentIntegration?.apiUrl || '',
};
},
@ -246,8 +253,23 @@ export default {
canEditPayload() {
return this.hasSamplePayload && !this.resetSamplePayloadConfirmed;
},
isResetAuthKeyDisabled() {
return !this.active && !this.integrationForm.token !== '';
},
isPayloadEditDisabled() {
return !this.active || this.canEditPayload;
return this.glFeatures.multipleHttpIntegrationsCustomMapping
? !this.active || this.canEditPayload
: !this.active;
},
isSubmitTestPayloadDisabled() {
return (
!this.active ||
Boolean(this.integrationTestPayload.error) ||
this.integrationTestPayload.json === ''
);
},
isSelectDisabled() {
return this.currentIntegration !== null || !this.canAddIntegration;
},
},
watch: {
@ -257,7 +279,7 @@ export default {
}
this.selectedIntegration = val.type;
this.active = val.active;
if (val.type === typeSet.http) this.getIntegrationMapping(val.id);
if (val.type === typeSet.http && this.showMappingBuilder) this.getIntegrationMapping(val.id);
return this.integrationTypeSelect();
},
},
@ -297,14 +319,8 @@ export default {
});
},
submitWithTestPayload() {
return service
.updateTestAlert(this.testAlertPayload)
.then(() => {
this.submit();
})
.catch(() => {
this.$emit('test-payload-failure');
});
this.$emit('set-test-alert-payload', this.testAlertPayload);
this.submit();
},
submit() {
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
@ -323,6 +339,7 @@ export default {
return this.$emit('update-integration', integrationPayload);
}
this.reset();
return this.$emit('create-new-integration', integrationPayload);
},
reset() {
@ -410,7 +427,8 @@ export default {
>
<gl-form-select
v-model="selectedIntegration"
:disabled="currentIntegration !== null || !canAddIntegration"
:disabled="isSelectDisabled"
:class="{ 'gl-bg-gray-100!': isSelectDisabled }"
:options="options"
@change="integrationTypeSelect"
/>
@ -461,8 +479,13 @@ export default {
>
<gl-form-input
v-model="integrationForm.name"
:disabled="isPrometheus"
type="text"
:placeholder="$options.i18n.integrationFormSteps.step2.placeholder"
:placeholder="
isPrometheus
? $options.i18n.integrationFormSteps.step2.prometheus
: $options.i18n.integrationFormSteps.step2.placeholder
"
/>
</gl-form-group>
<gl-form-group
@ -539,7 +562,7 @@ export default {
</template>
</gl-form-input-group>
<gl-button v-gl-modal.authKeyModal :disabled="!active">
<gl-button v-gl-modal.authKeyModal :disabled="isResetAuthKeyDisabled">
{{ $options.i18n.integrationFormSteps.step3.reset }}
</gl-button>
<gl-modal
@ -642,7 +665,7 @@ export default {
<gl-button
v-if="!isManagingOpsgenie"
data-testid="integration-test-and-submit"
:disabled="Boolean(integrationTestPayload.error)"
:disabled="isSubmitTestPayloadDisabled"
category="secondary"
variant="success"
class="gl-mx-3 js-no-auto-disable"

View file

@ -1,494 +0,0 @@
<script>
import {
GlAlert,
GlButton,
GlForm,
GlFormGroup,
GlFormInput,
GlFormInputGroup,
GlFormTextarea,
GlLink,
GlModal,
GlModalDirective,
GlSprintf,
GlFormSelect,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import { doesHashExistInUrl } from '~/lib/utils/url_utility';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ToggleButton from '~/vue_shared/components/toggle_button.vue';
import csrf from '~/lib/utils/csrf';
import service from '../services';
import {
i18n,
integrationTypes,
JSON_VALIDATE_DELAY,
targetPrometheusUrlPlaceholder,
targetOpsgenieUrlPlaceholder,
sectionHash,
} from '../constants';
import createFlash, { FLASH_TYPES } from '~/flash';
export default {
i18n,
csrf,
targetOpsgenieUrlPlaceholder,
targetPrometheusUrlPlaceholder,
components: {
GlAlert,
GlButton,
GlForm,
GlFormGroup,
GlFormInput,
GlFormInputGroup,
GlFormSelect,
GlFormTextarea,
GlLink,
GlModal,
GlSprintf,
ClipboardButton,
ToggleButton,
},
directives: {
'gl-modal': GlModalDirective,
},
inject: ['prometheus', 'generic', 'opsgenie'],
data() {
return {
loading: false,
selectedIntegration: integrationTypes[0].value,
options: integrationTypes,
active: false,
token: '',
targetUrl: '',
feedback: {
variant: 'danger',
feedbackMessage: '',
isFeedbackDismissed: false,
},
testAlert: {
json: null,
error: null,
},
canSaveForm: false,
serverError: null,
};
},
computed: {
sections() {
return [
{
text: this.$options.i18n.usageSection,
url: this.generic.alertsUsageUrl,
},
{
text: this.$options.i18n.setupSection,
url: this.generic.alertsSetupUrl,
},
];
},
isPrometheus() {
return this.selectedIntegration === 'PROMETHEUS';
},
isOpsgenie() {
return this.selectedIntegration === 'OPSGENIE';
},
selectedIntegrationType() {
switch (this.selectedIntegration) {
case 'HTTP': {
return {
url: this.generic.url,
token: this.generic.token,
active: this.generic.active,
resetKey: this.resetKey.bind(this),
};
}
case 'PROMETHEUS': {
return {
url: this.prometheus.url,
token: this.prometheus.token,
active: this.prometheus.active,
resetKey: this.resetKey.bind(this, 'PROMETHEUS'),
targetUrl: this.prometheus.prometheusApiUrl,
};
}
case 'OPSGENIE': {
return {
targetUrl: this.opsgenie.opsgenieMvcTargetUrl,
active: this.opsgenie.active,
};
}
default: {
return {};
}
}
},
showFeedbackMsg() {
return this.feedback.feedbackMessage && !this.isFeedbackDismissed;
},
showAlertSave() {
return (
this.feedback.feedbackMessage === this.$options.i18n.testAlertFailed &&
!this.isFeedbackDismissed
);
},
prometheusInfo() {
return this.isPrometheus ? this.$options.i18n.prometheusInfo : '';
},
jsonIsValid() {
return this.testAlert.error === null;
},
canTestAlert() {
return this.active && this.testAlert.json !== null;
},
canSaveConfig() {
return !this.loading && this.canSaveForm;
},
baseUrlPlaceholder() {
return this.isOpsgenie
? this.$options.targetOpsgenieUrlPlaceholder
: this.$options.targetPrometheusUrlPlaceholder;
},
},
watch: {
'testAlert.json': debounce(function debouncedJsonValidate() {
this.validateJson();
}, JSON_VALIDATE_DELAY),
targetUrl(oldVal, newVal) {
if (newVal && oldVal !== this.selectedIntegrationType.targetUrl) {
this.canSaveForm = true;
}
},
},
mounted() {
if (this.prometheus.active || this.generic.active || !this.opsgenie.opsgenieMvcIsAvailable) {
this.removeOpsGenieOption();
} else if (this.opsgenie.active) {
this.setOpsgenieAsDefault();
}
this.active = this.selectedIntegrationType.active;
this.token = this.selectedIntegrationType.token ?? '';
},
methods: {
createUserErrorMessage(errors = {}) {
const error = Object.entries(errors)?.[0];
if (error) {
const [field, [msg]] = error;
this.serverError = `${field} ${msg}`;
}
},
setOpsgenieAsDefault() {
this.options = this.options.map(el => {
if (el.value !== 'OPSGENIE') {
return { ...el, disabled: true };
}
return { ...el, disabled: false };
});
this.selectedIntegration = this.options.find(({ value }) => value === 'OPSGENIE').value;
if (this.targetUrl === null) {
this.targetUrl = this.selectedIntegrationType.targetUrl;
}
},
removeOpsGenieOption() {
this.options = this.options.map(el => {
if (el.value !== 'OPSGENIE') {
return { ...el, disabled: false };
}
return { ...el, disabled: true };
});
},
resetFormValues() {
this.testAlert.json = null;
this.targetUrl = this.selectedIntegrationType.targetUrl;
this.active = this.selectedIntegrationType.active;
},
dismissFeedback() {
this.serverError = null;
this.feedback = { ...this.feedback, feedbackMessage: null };
this.isFeedbackDismissed = false;
},
resetKey(key) {
const fn = key === 'PROMETHEUS' ? this.resetPrometheusKey() : this.resetGenericKey();
return fn
.then(({ data: { token } }) => {
this.token = token;
this.setFeedback({ feedbackMessage: this.$options.i18n.tokenRest, variant: 'success' });
})
.catch(() => {
this.setFeedback({ feedbackMessage: this.$options.i18n.errorKeyMsg, variant: 'danger' });
});
},
resetGenericKey() {
this.dismissFeedback();
return service.updateGenericKey({
endpoint: this.generic.formPath,
params: { service: { token: '' } },
});
},
resetPrometheusKey() {
return service.updatePrometheusKey({ endpoint: this.prometheus.prometheusResetKeyPath });
},
toggleService(value) {
this.canSaveForm = true;
this.active = value;
},
toggle(value) {
return this.isPrometheus ? this.togglePrometheusActive(value) : this.toggleActivated(value);
},
toggleActivated(value) {
this.loading = true;
const path = this.isOpsgenie ? this.opsgenie.formPath : this.generic.formPath;
return service
.updateGenericActive({
endpoint: path,
params: this.isOpsgenie
? { service: { opsgenie_mvc_target_url: this.targetUrl, opsgenie_mvc_enabled: value } }
: { service: { active: value } },
})
.then(() => this.notifySuccessAndReload())
.catch(({ response: { data: { errors } = {} } = {} }) => {
this.createUserErrorMessage(errors);
this.setFeedback({
feedbackMessage: this.$options.i18n.errorMsg,
variant: 'danger',
});
})
.finally(() => {
this.loading = false;
this.canSaveForm = false;
});
},
reload() {
if (!doesHashExistInUrl(sectionHash)) {
window.location.hash = sectionHash;
}
window.location.reload();
},
togglePrometheusActive(value) {
this.loading = true;
return service
.updatePrometheusActive({
endpoint: this.prometheus.prometheusFormPath,
params: {
token: this.$options.csrf.token,
config: value,
url: this.targetUrl,
redirect: window.location,
},
})
.then(() => this.notifySuccessAndReload())
.catch(({ response: { data: { errors } = {} } = {} }) => {
this.createUserErrorMessage(errors);
this.setFeedback({
feedbackMessage: this.$options.i18n.errorMsg,
variant: 'danger',
});
})
.finally(() => {
this.loading = false;
this.canSaveForm = false;
});
},
notifySuccessAndReload() {
createFlash({ message: this.$options.i18n.changesSaved, type: FLASH_TYPES.NOTICE });
setTimeout(() => this.reload(), 1000);
},
setFeedback({ feedbackMessage, variant }) {
this.feedback = { feedbackMessage, variant };
},
validateJson() {
this.testAlert.error = null;
try {
JSON.parse(this.testAlert.json);
} catch (e) {
this.testAlert.error = JSON.stringify(e.message);
}
},
validateTestAlert() {
this.loading = true;
this.dismissFeedback();
this.validateJson();
return service
.updateTestAlert({
endpoint: this.selectedIntegrationType.url,
data: this.testAlert.json,
token: this.selectedIntegrationType.token,
})
.then(() => {
this.setFeedback({
feedbackMessage: this.$options.i18n.testAlertSuccess,
variant: 'success',
});
})
.catch(() => {
this.setFeedback({
feedbackMessage: this.$options.i18n.testAlertFailed,
variant: 'danger',
});
})
.finally(() => {
this.loading = false;
});
},
onSubmit() {
this.dismissFeedback();
this.toggle(this.active);
},
onReset() {
this.testAlert.json = null;
this.dismissFeedback();
this.targetUrl = this.selectedIntegrationType.targetUrl;
if (this.canSaveForm) {
this.canSaveForm = false;
this.active = this.selectedIntegrationType.active;
}
},
},
};
</script>
<template>
<gl-form @submit.prevent="onSubmit" @reset.prevent="onReset">
<h5 class="gl-font-lg gl-my-5">{{ $options.i18n.integrationsLabel }}</h5>
<gl-alert v-if="showFeedbackMsg" :variant="feedback.variant" @dismiss="dismissFeedback">
{{ feedback.feedbackMessage }}
<br />
<i v-if="serverError">{{ __('Error message:') }} {{ serverError }}</i>
<gl-button
v-if="showAlertSave"
variant="danger"
category="primary"
class="gl-display-block gl-mt-3"
@click="toggle(active)"
>
{{ __('Save anyway') }}
</gl-button>
</gl-alert>
<div data-testid="alert-settings-description">
<p v-for="section in sections" :key="section.text">
<gl-sprintf :message="section.text">
<template #link="{ content }">
<gl-link :href="section.url" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</div>
<gl-form-group label-for="integration-type" :label="$options.i18n.integration">
<gl-form-select
id="integration-type"
v-model="selectedIntegration"
:options="options"
data-testid="alert-settings-select"
@change="resetFormValues"
/>
<span class="gl-text-gray-500">
<gl-sprintf :message="$options.i18n.integrationsInfo">
<template #link="{ content }">
<gl-link
class="gl-display-inline-block"
href="https://gitlab.com/groups/gitlab-org/-/epics/4390"
target="_blank"
>{{ content }}</gl-link
>
</template>
</gl-sprintf>
</span>
</gl-form-group>
<gl-form-group :label="$options.i18n.activeLabel" label-for="active">
<toggle-button
id="active"
:disabled-input="loading"
:is-loading="loading"
:value="active"
@change="toggleService"
/>
</gl-form-group>
<gl-form-group
v-if="isOpsgenie || isPrometheus"
:label="$options.i18n.apiBaseUrlLabel"
label-for="api-url"
>
<gl-form-input
id="api-url"
v-model="targetUrl"
type="url"
:placeholder="baseUrlPlaceholder"
:disabled="!active"
/>
<span class="gl-text-gray-500">
{{ $options.i18n.apiBaseUrlHelpText }}
</span>
</gl-form-group>
<template v-if="!isOpsgenie">
<gl-form-group :label="$options.i18n.urlLabel" label-for="url">
<gl-form-input-group id="url" readonly :value="selectedIntegrationType.url">
<template #append>
<clipboard-button
:text="selectedIntegrationType.url"
:title="$options.i18n.copyToClipboard"
class="gl-m-0!"
/>
</template>
</gl-form-input-group>
<span class="gl-text-gray-500">
{{ prometheusInfo }}
</span>
</gl-form-group>
<gl-form-group :label="$options.i18n.tokenLabel" label-for="authorization-key">
<gl-form-input-group id="authorization-key" class="gl-mb-2" readonly :value="token">
<template #append>
<clipboard-button
:text="token"
:title="$options.i18n.copyToClipboard"
class="gl-m-0!"
/>
</template>
</gl-form-input-group>
<gl-button v-gl-modal.tokenModal :disabled="!active" class="gl-mt-3">{{
$options.i18n.resetKey
}}</gl-button>
<gl-modal
modal-id="tokenModal"
:title="$options.i18n.resetKey"
:ok-title="$options.i18n.resetKey"
ok-variant="danger"
@ok="selectedIntegrationType.resetKey"
>
{{ $options.i18n.restKeyInfo }}
</gl-modal>
</gl-form-group>
<gl-form-group
:label="$options.i18n.alertJson"
label-for="alert-json"
:invalid-feedback="testAlert.error"
>
<gl-form-textarea
id="alert-json"
v-model.trim="testAlert.json"
:disabled="!active"
:state="jsonIsValid"
:placeholder="$options.i18n.alertJsonPlaceholder"
rows="6"
max-rows="10"
/>
</gl-form-group>
<gl-button :disabled="!canTestAlert" @click="validateTestAlert">{{
$options.i18n.testAlertInfo
}}</gl-button>
</template>
<div class="footer-block row-content-block gl-display-flex gl-justify-content-space-between">
<gl-button variant="success" category="primary" :disabled="!canSaveConfig" @click="onSubmit">
{{ __('Save changes') }}
</gl-button>
<gl-button category="primary" :disabled="!canSaveConfig" @click="onReset">
{{ __('Cancel') }}
</gl-button>
</div>
</gl-form>
</template>

View file

@ -1,7 +1,6 @@
<script>
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { fetchPolicies } from '~/lib/graphql';
import createFlash, { FLASH_TYPES } from '~/flash';
import getIntegrationsQuery from '../graphql/queries/get_integrations.query.graphql';
@ -15,8 +14,8 @@ import resetHttpTokenMutation from '../graphql/mutations/reset_http_token.mutati
import resetPrometheusTokenMutation from '../graphql/mutations/reset_prometheus_token.mutation.graphql';
import updateCurrentIntergrationMutation from '../graphql/mutations/update_current_intergration.mutation.graphql';
import IntegrationsList from './alerts_integrations_list.vue';
import SettingsFormOld from './alerts_settings_form_old.vue';
import SettingsFormNew from './alerts_settings_form_new.vue';
import AlertSettingsForm from './alerts_settings_form.vue';
import service from '../services';
import { typeSet } from '../constants';
import {
updateStoreAfterIntegrationDelete,
@ -37,6 +36,9 @@ export default {
'AlertsIntegrations|The integration has been successfully saved. Alerts from this new integration should now appear on your alerts list.',
),
integrationRemoved: s__('AlertsIntegrations|The integration has been successfully removed.'),
alertSent: s__(
'AlertsIntegrations|The test alert has been successfully sent, and should now be visible on your alerts list.',
),
},
components: {
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
@ -44,10 +46,8 @@ export default {
GlLink,
GlSprintf,
IntegrationsList,
SettingsFormOld,
SettingsFormNew,
AlertSettingsForm,
},
mixins: [glFeatureFlagsMixin()],
inject: {
generic: {
default: {},
@ -93,6 +93,7 @@ export default {
data() {
return {
isUpdating: false,
testAlertPayload: null,
integrations: {},
currentIntegration: null,
};
@ -101,25 +102,12 @@ export default {
loading() {
return this.$apollo.queries.integrations.loading;
},
integrationsOptionsOld() {
return [
{
name: s__('AlertSettings|HTTP endpoint'),
type: s__('AlertsIntegrations|HTTP endpoint'),
active: this.generic.active,
},
{
name: s__('AlertSettings|External Prometheus'),
type: s__('AlertsIntegrations|Prometheus'),
active: this.prometheus.active,
},
];
},
canAddIntegration() {
return this.multiIntegrations || this.integrations?.list?.length < 2;
},
canManageOpsgenie() {
return (
this.opsgenie.active ||
this.integrations?.list?.every(({ active }) => active === false) ||
this.integrations?.list?.length === 0
);
@ -149,6 +137,19 @@ export default {
if (error) {
return createFlash({ message: error });
}
if (this.testAlertPayload) {
const integration =
httpIntegrationCreate?.integration || prometheusIntegrationCreate?.integration;
const payload = {
...this.testAlertPayload,
endpoint: integration.url,
token: integration.token,
};
return this.validateAlertPayload(payload);
}
return createFlash({
message: this.$options.i18n.changesSaved,
type: FLASH_TYPES.SUCCESS,
@ -179,6 +180,13 @@ export default {
if (error) {
return createFlash({ message: error });
}
if (this.testAlertPayload) {
return this.validateAlertPayload();
}
this.clearCurrentIntegration();
return createFlash({
message: this.$options.i18n.changesSaved,
type: FLASH_TYPES.SUCCESS,
@ -189,6 +197,7 @@ export default {
})
.finally(() => {
this.isUpdating = false;
this.testAlertPayload = null;
});
},
resetToken({ type, variables }) {
@ -212,7 +221,13 @@ export default {
const integration =
httpIntegrationResetToken?.integration ||
prometheusIntegrationResetToken?.integration;
this.currentIntegration = integration;
this.$apollo.mutate({
mutation: updateCurrentIntergrationMutation,
variables: {
...integration,
},
});
return createFlash({
message: this.$options.i18n.changesSaved,
@ -280,8 +295,21 @@ export default {
variables: {},
});
},
testPayloadFailure() {
createFlash({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
setTestAlertPayload(payload) {
this.testAlertPayload = payload;
},
validateAlertPayload(payload) {
return service
.updateTestAlert(payload ?? this.testAlertPayload)
.then(() => {
return createFlash({
message: this.$options.i18n.alertSent,
type: FLASH_TYPES.SUCCESS,
});
})
.catch(() => {
createFlash({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
});
},
},
};
@ -310,13 +338,12 @@ export default {
</gl-alert>
<integrations-list
v-else
:integrations="glFeatures.httpIntegrationsList ? integrations.list : integrationsOptionsOld"
:integrations="integrations.list"
:loading="loading"
@edit-integration="editIntegration"
@delete-integration="deleteIntegration"
/>
<settings-form-new
v-if="glFeatures.httpIntegrationsList"
<alert-settings-form
:loading="isUpdating"
:can-add-integration="canAddIntegration"
:can-manage-opsgenie="canManageOpsgenie"
@ -324,8 +351,7 @@ export default {
@update-integration="updateIntegration"
@reset-token="resetToken"
@clear-current-integration="clearCurrentIntegration"
@test-payload-failure="testPayloadFailure"
@set-test-alert-payload="setTestAlertPayload"
/>
<settings-form-old v-else />
</div>
</template>

View file

@ -2,30 +2,9 @@
import axios from '~/lib/utils/axios_utils';
export default {
// TODO: All this code save updateTestAlert will be deleted as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/255501
updateGenericKey({ endpoint, params }) {
return axios.put(endpoint, params);
},
updatePrometheusKey({ endpoint }) {
return axios.post(endpoint);
},
updateGenericActive({ endpoint, params }) {
return axios.put(endpoint, params);
},
updatePrometheusActive({ endpoint, params: { token, config, url, redirect } }) {
const data = new FormData();
data.set('_method', 'put');
data.set('authenticity_token', token);
data.set('service[manual_configuration]', config);
data.set('service[api_url]', url);
data.set('redirect_to', redirect);
return axios.post(endpoint, data, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
},
updateTestAlert({ endpoint, data, token }) {
return axios.post(endpoint, data, {
headers: {

View file

@ -1,4 +0,0 @@
fragment Count on InstanceStatisticsMeasurement {
count
recordedAt
}

View file

@ -69,6 +69,7 @@ const Api = {
issuePath: '/api/:version/projects/:id/issues/:issue_iid',
tagsPath: '/api/:version/projects/:id/repository/tags',
freezePeriodsPath: '/api/:version/projects/:id/freeze_periods',
usageDataIncrementCounterPath: '/api/:version/usage_data/increment_counter',
usageDataIncrementUniqueUsersPath: '/api/:version/usage_data/increment_unique_users',
featureFlagUserLists: '/api/:version/projects/:id/feature_flags_user_lists',
featureFlagUserList: '/api/:version/projects/:id/feature_flags_user_lists/:list_iid',
@ -389,7 +390,10 @@ const Api = {
params: { ...defaults, ...options },
})
.then(({ data }) => callback(data))
.catch(() => flash(__('Something went wrong while fetching projects')));
.catch(() => {
flash(__('Something went wrong while fetching projects'));
callback();
});
},
commit(id, sha, params = {}) {
@ -751,6 +755,19 @@ const Api = {
return axios.post(url, freezePeriod);
},
trackRedisCounterEvent(event) {
if (!gon.features?.usageDataApi) {
return null;
}
const url = Api.buildUrl(this.usageDataIncrementCounterPath);
const headers = {
'Content-Type': 'application/json',
};
return axios.post(url, { event }, { headers });
},
trackRedisHllUserEvent(event) {
if (!gon.features?.usageDataApi) {
return null;

View file

@ -13,11 +13,17 @@ export const mount2faAuthentication = () => {
};
export const mount2faRegistration = () => {
const el = $('#js-register-token-2fa');
if (!el.length) {
return;
}
if (gon.webauthn) {
const webauthnRegister = new WebAuthnRegister($('#js-register-token-2fa'), gon.webauthn);
const webauthnRegister = new WebAuthnRegister(el, gon.webauthn);
webauthnRegister.start();
} else {
const u2fRegister = new U2FRegister($('#js-register-token-2fa'), gon.u2f);
const u2fRegister = new U2FRegister(el, gon.u2f);
u2fRegister.start();
}
};

View file

@ -0,0 +1,174 @@
<script>
import Mousetrap from 'mousetrap';
import { GlSprintf, GlButton, GlAlert } from '@gitlab/ui';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Tracking from '~/tracking';
import { __ } from '~/locale';
import {
COPY_BUTTON_ACTION,
DOWNLOAD_BUTTON_ACTION,
PRINT_BUTTON_ACTION,
TRACKING_LABEL_PREFIX,
RECOVERY_CODE_DOWNLOAD_FILENAME,
COPY_KEYBOARD_SHORTCUT,
} from '../constants';
export const i18n = {
pageTitle: __('Two-factor Authentication Recovery codes'),
alertTitle: __('Please copy, download, or print your recovery codes before proceeding.'),
pageDescription: __(
'Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{boldStart}will%{boldEnd} lose access to your account.',
),
copyButton: __('Copy codes'),
downloadButton: __('Download codes'),
printButton: __('Print codes'),
proceedButton: __('Proceed'),
};
export default {
name: 'RecoveryCodes',
copyButtonAction: COPY_BUTTON_ACTION,
downloadButtonAction: DOWNLOAD_BUTTON_ACTION,
printButtonAction: PRINT_BUTTON_ACTION,
trackingLabelPrefix: TRACKING_LABEL_PREFIX,
recoveryCodeDownloadFilename: RECOVERY_CODE_DOWNLOAD_FILENAME,
i18n,
mousetrap: null,
components: { GlSprintf, GlButton, GlAlert, ClipboardButton },
mixins: [Tracking.mixin()],
props: {
codes: {
type: Array,
required: true,
},
profileAccountPath: {
type: String,
required: true,
},
},
data() {
return {
proceedButtonDisabled: true,
};
},
computed: {
codesAsString() {
return this.codes.join('\n');
},
codeDownloadUrl() {
return `data:text/plain;charset=utf-8,${encodeURIComponent(this.codesAsString)}`;
},
},
created() {
this.$options.mousetrap = new Mousetrap();
this.$options.mousetrap.bind(COPY_KEYBOARD_SHORTCUT, this.handleKeyboardCopy);
},
beforeDestroy() {
if (!this.$options.mousetrap) {
return;
}
this.$options.mousetrap.unbind(COPY_KEYBOARD_SHORTCUT);
},
methods: {
handleButtonClick(action) {
this.proceedButtonDisabled = false;
if (action === this.$options.printButtonAction) {
window.print();
}
this.track('click_button', { label: `${this.$options.trackingLabelPrefix}${action}_button` });
},
handleKeyboardCopy() {
if (!window.getSelection) {
return;
}
const copiedText = window.getSelection().toString();
if (copiedText.includes(this.codesAsString)) {
this.proceedButtonDisabled = false;
this.track('copy_keyboard_shortcut', {
label: `${this.$options.trackingLabelPrefix}manual_copy`,
});
}
},
},
};
</script>
<template>
<div>
<h3 class="page-title">
{{ $options.i18n.pageTitle }}
</h3>
<hr />
<gl-alert variant="info" :dismissible="false">
{{ $options.i18n.alertTitle }}
</gl-alert>
<p class="gl-mt-5">
<gl-sprintf :message="$options.i18n.pageDescription">
<template #bold="{ content }"
><strong>{{ content }}</strong></template
>
</gl-sprintf>
</p>
<div
class="codes-to-print gl-my-5 gl-p-5 gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base"
data-testid="recovery-codes"
data-qa-selector="codes_content"
>
<ul class="gl-m-0 gl-pl-5">
<li v-for="(code, index) in codes" :key="index">
<span class="gl-font-monospace" data-qa-selector="code_content">{{ code }}</span>
</li>
</ul>
</div>
<div class="gl-my-n2 gl-mx-n2 gl-display-flex gl-flex-wrap">
<div class="gl-p-2">
<clipboard-button
:title="$options.i18n.copyButton"
:text="codesAsString"
data-qa-selector="copy_button"
@click="handleButtonClick($options.copyButtonAction)"
>
{{ $options.i18n.copyButton }}
</clipboard-button>
</div>
<div class="gl-p-2">
<gl-button
:href="codeDownloadUrl"
:title="$options.i18n.downloadButton"
icon="download"
:download="$options.recoveryCodeDownloadFilename"
@click="handleButtonClick($options.downloadButtonAction)"
>
{{ $options.i18n.downloadButton }}
</gl-button>
</div>
<div class="gl-p-2">
<gl-button
:title="$options.i18n.printButton"
@click="handleButtonClick($options.printButtonAction)"
>
{{ $options.i18n.printButton }}
</gl-button>
</div>
<div class="gl-p-2">
<gl-button
:href="profileAccountPath"
:disabled="proceedButtonDisabled"
:title="$options.i18n.proceedButton"
variant="success"
data-qa-selector="proceed_button"
data-track-event="click_button"
:data-track-label="`${$options.trackingLabelPrefix}proceed_button`"
>{{ $options.i18n.proceedButton }}</gl-button
>
</div>
</div>
</div>
</template>

View file

@ -0,0 +1,11 @@
export const COPY_BUTTON_ACTION = 'copy';
export const DOWNLOAD_BUTTON_ACTION = 'download';
export const PRINT_BUTTON_ACTION = 'print';
export const TRACKING_LABEL_PREFIX = '2fa_recovery_codes_';
export const RECOVERY_CODE_DOWNLOAD_FILENAME = 'gitlab-recovery-codes.txt';
export const SUCCESS_QUERY_PARAM = 'two_factor_auth_enabled_successfully';
export const COPY_KEYBOARD_SHORTCUT = 'mod+c';

View file

@ -0,0 +1,46 @@
import Vue from 'vue';
import { updateHistory, removeParams } from '~/lib/utils/url_utility';
import RecoveryCodes from './components/recovery_codes.vue';
import { SUCCESS_QUERY_PARAM } from './constants';
export const initRecoveryCodes = () => {
const el = document.querySelector('.js-2fa-recovery-codes');
if (!el) {
return false;
}
const { codes = '[]', profileAccountPath = '' } = el.dataset;
return new Vue({
el,
render(createElement) {
return createElement(RecoveryCodes, {
props: {
codes: JSON.parse(codes),
profileAccountPath,
},
});
},
});
};
export const initClose2faSuccessMessage = () => {
const closeButton = document.querySelector('.js-close-2fa-enabled-success-alert');
if (!closeButton) {
return;
}
closeButton.addEventListener(
'click',
() => {
updateHistory({
url: removeParams([SUCCESS_QUERY_PARAM]),
title: document.title,
replace: true,
});
},
{ once: true },
);
};

View file

@ -74,6 +74,7 @@ export default class Autosave {
}
dispose() {
// eslint-disable-next-line @gitlab/no-global-event-off
this.field.off('input');
}
}

View file

@ -596,6 +596,7 @@ export class AwardsHandler {
hideMenuElement($emojiMenu) {
$emojiMenu.on(transitionEndEventString, e => {
if (e.currentTarget === e.target) {
// eslint-disable-next-line @gitlab/no-global-event-off
$emojiMenu.removeClass(IS_RENDERED).off(transitionEndEventString);
}
});

View file

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

View file

@ -1,22 +1,29 @@
import $ from 'jquery';
import { loadCSSFile } from '../lib/utils/css_utils';
export default () => {
if ($('select.select2').length) {
const $select2Elements = $('select.select2');
if ($select2Elements.length) {
import(/* webpackChunkName: 'select2' */ 'select2/select2')
.then(() => {
$('select.select2').select2({
width: 'resolve',
minimumResultsForSearch: 10,
dropdownAutoWidth: true,
});
// eslint-disable-next-line promise/no-nesting
loadCSSFile(gon.select2_css_path)
.then(() => {
$select2Elements.select2({
width: 'resolve',
minimumResultsForSearch: 10,
dropdownAutoWidth: true,
});
// Close select2 on escape
$('.js-select2').on('select2-close', () => {
setTimeout(() => {
$('.select2-container-active').removeClass('select2-container-active');
$(':focus').blur();
}, 1);
});
// Close select2 on escape
$('.js-select2').on('select2-close', () => {
requestAnimationFrame(() => {
$('.select2-container-active').removeClass('select2-container-active');
$(':focus').blur();
});
});
})
.catch(() => {});
})
.catch(() => {});
}

View file

@ -97,6 +97,7 @@ export default class Shortcuts {
e.preventDefault();
});
// eslint-disable-next-line @gitlab/no-global-event-off
$('.js-shortcuts-modal-trigger')
.off('click')
.on('click', this.onToggleHelp);

View file

@ -50,7 +50,6 @@ export default {
:aria-label="$options.SIMPLE_BLOB_VIEWER_TITLE"
:title="$options.SIMPLE_BLOB_VIEWER_TITLE"
:selected="isSimpleViewer"
:class="{ active: isSimpleViewer }"
icon="code"
category="primary"
variant="default"
@ -61,7 +60,6 @@ export default {
:aria-label="$options.RICH_BLOB_VIEWER_TITLE"
:title="$options.RICH_BLOB_VIEWER_TITLE"
:selected="isRichViewer"
:class="{ active: isRichViewer }"
icon="document"
category="primary"
variant="default"

View file

@ -82,7 +82,6 @@ export default class FileTemplateMediator {
initPageEvents() {
this.listenForFilenameInput();
this.prepFileContentForSubmit();
this.listenForPreviewMode();
}
@ -92,12 +91,6 @@ export default class FileTemplateMediator {
});
}
prepFileContentForSubmit() {
this.$commitForm.submit(() => {
this.$fileContent.val(this.editor.getValue());
});
}
listenForPreviewMode() {
this.$navLinks.on('click', 'a', e => {
const urlPieces = e.target.href.split('#');

View file

@ -12,7 +12,10 @@ export default class FileTemplateSelector {
this.$dropdown = $(cfg.dropdown);
this.$wrapper = $(cfg.wrapper);
this.$loadingIcon = this.$wrapper.find('.fa-chevron-down');
this.$dropdownIcon = this.$wrapper.find('.dropdown-menu-toggle-icon');
this.$loadingIcon = $(
'<div class="gl-spinner gl-spinner-orange gl-spinner-sm gl-absolute gl-top-3 gl-right-3 gl-display-none"></div>',
).insertAfter(this.$dropdownIcon);
this.$dropdownToggleText = this.$wrapper.find('.dropdown-toggle-text');
this.initDropdown();
@ -45,15 +48,13 @@ export default class FileTemplateSelector {
}
renderLoading() {
this.$loadingIcon
.addClass('gl-spinner gl-spinner-orange gl-spinner-sm')
.removeClass('fa-chevron-down');
this.$loadingIcon.removeClass('gl-display-none');
this.$dropdownIcon.addClass('gl-display-none');
}
renderLoaded() {
this.$loadingIcon
.addClass('fa-chevron-down')
.removeClass('gl-spinner gl-spinner-orange gl-spinner-sm');
this.$loadingIcon.addClass('gl-display-none');
this.$dropdownIcon.removeClass('gl-display-none');
}
reportSelection(options) {

View file

@ -107,7 +107,7 @@ export default {
v-if="!popoverDismissed"
show
:target="target"
placement="rightbottom"
placement="right"
trigger="manual"
container="viewport"
:css-classes="['suggest-gitlab-ci-yml', 'ml-4']"

View file

@ -10,7 +10,10 @@ export default class TemplateSelector {
this.dropdown = dropdown;
this.$dropdownContainer = wrapper;
this.$filenameInput = $input || $('#file_name');
this.$dropdownIcon = $('.fa-chevron-down', dropdown);
this.$dropdownIcon = $('.dropdown-menu-toggle-icon', dropdown);
this.$loadingIcon = $(
'<div class="gl-spinner gl-spinner-orange gl-spinner-sm gl-absolute gl-top-3 gl-right-3 gl-display-none"></div>',
).insertAfter(this.$dropdownIcon);
this.initDropdown(dropdown, data);
this.listenForFilenameInput();
@ -92,10 +95,12 @@ export default class TemplateSelector {
}
startLoadingSpinner() {
this.$dropdownIcon.addClass('spinner').removeClass('fa-chevron-down');
this.$loadingIcon.removeClass('gl-display-none');
this.$dropdownIcon.addClass('gl-display-none');
}
stopLoadingSpinner() {
this.$dropdownIcon.addClass('fa-chevron-down').removeClass('spinner');
this.$loadingIcon.addClass('gl-display-none');
this.$dropdownIcon.removeClass('gl-display-none');
}
}

View file

@ -132,16 +132,16 @@ export default class BlobViewer {
const newViewer = this.$fileHolder[0].querySelector(`.blob-viewer[data-type='${name}']`);
if (this.activeViewer === newViewer) return;
const oldButton = document.querySelector('.js-blob-viewer-switch-btn.active');
const oldButton = document.querySelector('.js-blob-viewer-switch-btn.selected');
const newButton = document.querySelector(`.js-blob-viewer-switch-btn[data-viewer='${name}']`);
const oldViewer = this.$fileHolder[0].querySelector(`.blob-viewer:not([data-type='${name}'])`);
if (oldButton) {
oldButton.classList.remove('active');
oldButton.classList.remove('selected');
}
if (newButton) {
newButton.classList.add('active');
newButton.classList.add('selected');
newButton.blur();
}

View file

@ -38,9 +38,20 @@ const initPopovers = () => {
}
};
export const initUploadForm = () => {
const uploadBlobForm = $('.js-upload-blob-form');
if (uploadBlobForm.length) {
const method = uploadBlobForm.data('method');
new BlobFileDropzone(uploadBlobForm, method);
new NewCommitForm(uploadBlobForm);
disableButtonIfEmptyField(uploadBlobForm.find('.js-commit-message'), '.btn-upload-file');
}
};
export default () => {
const editBlobForm = $('.js-edit-blob-form');
const uploadBlobForm = $('.js-upload-blob-form');
const deleteBlobForm = $('.js-delete-blob-form');
if (editBlobForm.length) {
@ -80,14 +91,7 @@ export default () => {
window.onbeforeunload = () => '';
}
if (uploadBlobForm.length) {
const method = uploadBlobForm.data('method');
new BlobFileDropzone(uploadBlobForm, method);
new NewCommitForm(uploadBlobForm);
disableButtonIfEmptyField(uploadBlobForm.find('.js-commit-message'), '.btn-upload-file');
}
initUploadForm();
if (deleteBlobForm.length) {
new NewCommitForm(deleteBlobForm);

View file

@ -5,7 +5,8 @@ import { BLOB_EDITOR_ERROR, BLOB_PREVIEW_ERROR } from './constants';
import TemplateSelectorMediator from '../blob/file_template_mediator';
import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
import EditorLite from '~/editor/editor_lite';
import FileTemplateExtension from '~/editor/editor_file_template_ext';
import { FileTemplateExtension } from '~/editor/editor_file_template_ext';
import { insertFinalNewline } from '~/lib/utils/text_utility';
export default class EditBlob {
// The options object has:
@ -16,11 +17,11 @@ export default class EditBlob {
if (this.options.isMarkdown) {
import('~/editor/editor_markdown_ext')
.then(MarkdownExtension => {
this.editor.use(MarkdownExtension.default);
.then(({ EditorMarkdownExtension: MarkdownExtension } = {}) => {
this.editor.use(new MarkdownExtension());
addEditorMarkdownListeners(this.editor);
})
.catch(() => createFlash(BLOB_EDITOR_ERROR));
.catch(e => createFlash(`${BLOB_EDITOR_ERROR}: ${e}`));
}
this.initModePanesAndLinks();
@ -42,14 +43,14 @@ export default class EditBlob {
blobPath: fileNameEl.value,
blobContent: editorEl.innerText,
});
this.editor.use(FileTemplateExtension);
this.editor.use(new FileTemplateExtension());
fileNameEl.addEventListener('change', () => {
this.editor.updateModelLanguage(fileNameEl.value);
});
form.addEventListener('submit', () => {
fileContentEl.value = this.editor.getValue();
fileContentEl.value = insertFinalNewline(this.editor.getValue());
});
}

View file

@ -1,31 +1,39 @@
import { sortBy } from 'lodash';
import ListIssue from 'ee_else_ce/boards/models/issue';
import axios from '~/lib/utils/axios_utils';
import { ListType } from './constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import boardsStore from '~/boards/stores/boards_store';
export function getMilestone() {
return null;
}
export function updateListPosition(listObj) {
const { listType } = listObj;
let { position } = listObj;
if (listType === ListType.closed) {
position = Infinity;
} else if (listType === ListType.backlog) {
position = -Infinity;
}
return { ...listObj, position };
}
export function formatBoardLists(lists) {
const formattedLists = lists.nodes.map(list =>
boardsStore.updateListPosition({ ...list, doNotFetchIssues: true }),
);
return formattedLists.reduce((map, list) => {
return lists.nodes.reduce((map, list) => {
return {
...map,
[list.id]: list,
[list.id]: updateListPosition(list),
};
}, {});
}
export function formatIssue(issue) {
return new ListIssue({
return {
...issue,
labels: issue.labels?.nodes || [],
assignees: issue.assignees?.nodes || [],
});
};
}
export function formatListIssues(listIssues) {
@ -44,12 +52,12 @@ export function formatListIssues(listIssues) {
[list.id]: sortedIssues.map(i => {
const id = getIdFromGraphQLId(i.id);
const listIssue = new ListIssue({
const listIssue = {
...i,
id,
labels: i.labels?.nodes || [],
assignees: i.assignees?.nodes || [],
});
};
issues[id] = listIssue;
@ -83,21 +91,48 @@ export function fullLabelId(label) {
}
export function moveIssueListHelper(issue, fromList, toList) {
if (toList.type === ListType.label) {
issue.addLabel(toList.label);
const updatedIssue = issue;
if (
toList.listType === ListType.label &&
!updatedIssue.labels.find(label => label.id === toList.label.id)
) {
updatedIssue.labels.push(toList.label);
}
if (fromList && fromList.type === ListType.label) {
issue.removeLabel(fromList.label);
if (fromList?.label && fromList.listType === ListType.label) {
updatedIssue.labels = updatedIssue.labels.filter(label => fromList.label.id !== label.id);
}
if (toList.type === ListType.assignee) {
issue.addAssignee(toList.assignee);
if (
toList.listType === ListType.assignee &&
!updatedIssue.assignees.find(assignee => assignee.id === toList.assignee.id)
) {
updatedIssue.assignees.push(toList.assignee);
}
if (fromList && fromList.type === ListType.assignee) {
issue.removeAssignee(fromList.assignee);
if (fromList?.assignee && fromList.listType === ListType.assignee) {
updatedIssue.assignees = updatedIssue.assignees.filter(
assignee => assignee.id !== fromList.assignee.id,
);
}
return issue;
return updatedIssue;
}
export function getBoardsPath(endpoint, board) {
const path = `${endpoint}${board.id ? `/${board.id}` : ''}.json`;
if (board.id) {
return axios.put(path, { board });
}
return axios.post(path, { board });
}
export function isListDraggable(list) {
return list.listType !== ListType.backlog && list.listType !== ListType.closed;
}
// EE-specific feature. Find the implementation in the `ee/`-folder
export function transformBoardConfig() {
return '';
}
export default {
@ -106,4 +141,6 @@ export default {
formatListIssues,
fullBoardId,
fullLabelId,
getBoardsPath,
isListDraggable,
};

View file

@ -1,18 +1,20 @@
<script>
import { mapActions, mapGetters } from 'vuex';
import { mapActions, mapGetters, mapState } from 'vuex';
import { cloneDeep } from 'lodash';
import {
GlDropdownItem,
GlDropdownDivider,
GlAvatarLabeled,
GlAvatarLink,
GlSearchBoxByType,
GlLoadingIcon,
} from '@gitlab/ui';
import { __, n__ } from '~/locale';
import IssuableAssignees from '~/sidebar/components/assignees/issuable_assignees.vue';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dropdown.vue';
import getIssueParticipants from '~/vue_shared/components/sidebar/queries/getIssueParticipants.query.graphql';
import searchUsers from '~/boards/queries/users_search.query.graphql';
import searchUsers from '~/boards/graphql/users_search.query.graphql';
export default {
noSearchDelay: 0,
@ -32,12 +34,13 @@ export default {
GlAvatarLabeled,
GlAvatarLink,
GlSearchBoxByType,
GlLoadingIcon,
},
data() {
return {
search: '',
participants: [],
selected: this.$store.getters.activeIssue.assignees,
selected: [],
};
},
apollo: {
@ -72,6 +75,7 @@ export default {
},
computed: {
...mapGetters(['activeIssue']),
...mapState(['isSettingAssignees']),
assigneeText() {
return n__('Assignee', '%d Assignees', this.selected.length);
},
@ -89,9 +93,20 @@ export default {
isSearchEmpty() {
return this.search === '';
},
currentUser() {
return gon?.current_username;
},
},
created() {
this.selected = cloneDeep(this.activeIssue.assignees);
},
methods: {
...mapActions(['setAssignees']),
async assignSelf() {
const [currentUserObject] = await this.setAssignees(this.currentUser);
this.selectAssignee(currentUserObject);
},
clearSelected() {
this.selected = [];
},
@ -117,9 +132,9 @@ export default {
</script>
<template>
<board-editable-item :title="assigneeText" @close="saveAssignees">
<board-editable-item :loading="isSettingAssignees" :title="assigneeText" @close="saveAssignees">
<template #collapsed>
<issuable-assignees :users="activeIssue.assignees" />
<issuable-assignees :users="activeIssue.assignees" @assign-self="assignSelf" />
</template>
<template #default>
@ -132,45 +147,48 @@ export default {
<gl-search-box-by-type v-model.trim="search" />
</template>
<template #items>
<gl-dropdown-item
:is-checked="selectedIsEmpty"
data-testid="unassign"
class="mt-2"
@click="selectAssignee()"
>{{ $options.i18n.unassigned }}</gl-dropdown-item
>
<gl-dropdown-divider data-testid="unassign-divider" />
<gl-dropdown-item
v-for="item in selected"
:key="item.id"
:is-checked="isChecked(item.username)"
@click="unselect(item.username)"
>
<gl-avatar-link>
<gl-avatar-labeled
:size="32"
:label="item.name"
:sub-label="item.username"
:src="item.avatarUrl || item.avatar"
/>
</gl-avatar-link>
</gl-dropdown-item>
<gl-dropdown-divider v-if="!selectedIsEmpty" data-testid="selected-user-divider" />
<gl-dropdown-item
v-for="unselectedUser in unSelectedFiltered"
:key="unselectedUser.id"
:data-testid="`item_${unselectedUser.name}`"
@click="selectAssignee(unselectedUser)"
>
<gl-avatar-link>
<gl-avatar-labeled
:size="32"
:label="unselectedUser.name"
:sub-label="unselectedUser.username"
:src="unselectedUser.avatarUrl || unselectedUser.avatar"
/>
</gl-avatar-link>
</gl-dropdown-item>
<gl-loading-icon v-if="$apollo.queries.participants.loading" size="lg" />
<template v-else>
<gl-dropdown-item
:is-checked="selectedIsEmpty"
data-testid="unassign"
class="mt-2"
@click="selectAssignee()"
>{{ $options.i18n.unassigned }}</gl-dropdown-item
>
<gl-dropdown-divider data-testid="unassign-divider" />
<gl-dropdown-item
v-for="item in selected"
:key="item.id"
:is-checked="isChecked(item.username)"
@click="unselect(item.username)"
>
<gl-avatar-link>
<gl-avatar-labeled
:size="32"
:label="item.name"
:sub-label="item.username"
:src="item.avatarUrl || item.avatar"
/>
</gl-avatar-link>
</gl-dropdown-item>
<gl-dropdown-divider v-if="!selectedIsEmpty" data-testid="selected-user-divider" />
<gl-dropdown-item
v-for="unselectedUser in unSelectedFiltered"
:key="unselectedUser.id"
:data-testid="`item_${unselectedUser.name}`"
@click="selectAssignee(unselectedUser)"
>
<gl-avatar-link>
<gl-avatar-labeled
:size="32"
:label="unselectedUser.name"
:sub-label="unselectedUser.username"
:src="unselectedUser.avatarUrl || unselectedUser.avatar"
/>
</gl-avatar-link>
</gl-dropdown-item>
</template>
</template>
</multi-select-dropdown>
</template>

View file

@ -2,15 +2,12 @@
// This component is being replaced in favor of './board_column_new.vue' for GraphQL boards
import Sortable from 'sortablejs';
import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue';
import EmptyComponent from '~/vue_shared/components/empty_component';
import BoardList from './board_list.vue';
import boardsStore from '../stores/boards_store';
import { getBoardSortableDefaultOptions, sortableEnd } from '../mixins/sortable_default_options';
import { ListType } from '../constants';
export default {
components: {
BoardPromotionState: EmptyComponent,
BoardListHeader,
BoardList,
},
@ -42,9 +39,6 @@ export default {
};
},
computed: {
showBoardListAndBoardInfo() {
return this.list.type !== ListType.promotion;
},
listIssues() {
return this.list.issues;
},
@ -105,16 +99,7 @@ export default {
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base"
>
<board-list-header :can-admin-list="canAdminList" :list="list" :disabled="disabled" />
<board-list
v-if="showBoardListAndBoardInfo"
ref="board-list"
:disabled="disabled"
:issues="listIssues"
:list="list"
/>
<!-- Will be only available in EE -->
<board-promotion-state v-if="list.id === 'promotion'" />
<board-list ref="board-list" :disabled="disabled" :issues="listIssues" :list="list" />
</div>
</div>
</template>

View file

@ -1,13 +1,11 @@
<script>
import { mapGetters, mapActions, mapState } from 'vuex';
import BoardListHeader from 'ee_else_ce/boards/components/board_list_header_new.vue';
import BoardPromotionState from 'ee_else_ce/boards/components/board_promotion_state';
import BoardList from './board_list_new.vue';
import { ListType } from '../constants';
import { isListDraggable } from '../boards_util';
export default {
components: {
BoardPromotionState,
BoardListHeader,
BoardList,
},
@ -35,22 +33,17 @@ export default {
computed: {
...mapState(['filterParams']),
...mapGetters(['getIssuesByList']),
showBoardListAndBoardInfo() {
return this.list.type !== ListType.promotion;
},
listIssues() {
return this.getIssuesByList(this.list.id);
},
shouldFetchIssues() {
return this.list.type !== ListType.blank;
isListDraggable() {
return isListDraggable(this.list);
},
},
watch: {
filterParams: {
handler() {
if (this.shouldFetchIssues) {
this.fetchIssuesForList({ listId: this.list.id });
}
this.fetchIssuesForList({ listId: this.list.id });
},
deep: true,
immediate: true,
@ -58,7 +51,6 @@ export default {
},
methods: {
...mapActions(['fetchIssuesForList']),
// TODO: Reordering of lists https://gitlab.com/gitlab-org/gitlab/-/issues/280515
},
};
</script>
@ -66,13 +58,12 @@ export default {
<template>
<div
:class="{
'is-draggable': !list.preset,
'is-expandable': list.isExpandable,
'is-collapsed': !list.isExpanded,
'board-type-assignee': list.type === 'assignee',
'is-draggable': isListDraggable,
'is-collapsed': list.collapsed,
'board-type-assignee': list.listType === 'assignee',
}"
:data-id="list.id"
class="board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal"
class="board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal is-expandable"
data-qa-selector="board_list"
>
<div
@ -80,15 +71,12 @@ export default {
>
<board-list-header :can-admin-list="canAdminList" :list="list" :disabled="disabled" />
<board-list
v-if="showBoardListAndBoardInfo"
ref="board-list"
:disabled="disabled"
:issues="listIssues"
:list="list"
:can-admin-list="canAdminList"
/>
<!-- Will be only available in EE -->
<board-promotion-state v-if="list.id === 'promotion'" />
</div>
</div>
</template>

View file

@ -42,7 +42,7 @@ export default {
</script>
<template>
<div class="append-bottom-20">
<div class="gl-mb-5">
<label class="label-bold gl-font-lg" for="board-new-name">
{{ __('List options') }}
</label>

View file

@ -1,10 +1,13 @@
<script>
import Draggable from 'vuedraggable';
import { mapState, mapGetters, mapActions } from 'vuex';
import { sortBy } from 'lodash';
import { GlAlert } from '@gitlab/ui';
import BoardColumn from 'ee_else_ce/boards/components/board_column.vue';
import BoardColumn from './board_column.vue';
import BoardColumnNew from './board_column_new.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import defaultSortableConfig from '~/sortable/sortable_config';
import { sortableEnd, sortableStart } from '~/boards/mixins/sortable_default_options';
export default {
components: {
@ -32,18 +35,51 @@ export default {
...mapState(['boardLists', 'error']),
...mapGetters(['isSwimlanesOn']),
boardListsToUse() {
const lists =
this.glFeatures.graphqlBoardLists || this.isSwimlanesOn ? this.boardLists : this.lists;
return sortBy([...Object.values(lists)], 'position');
return this.glFeatures.graphqlBoardLists || this.isSwimlanesOn
? sortBy([...Object.values(this.boardLists)], 'position')
: this.lists;
},
canDragColumns() {
return this.glFeatures.graphqlBoardLists && this.canAdminList;
},
boardColumnWrapper() {
return this.canDragColumns ? Draggable : 'div';
},
draggableOptions() {
const options = {
...defaultSortableConfig,
disabled: this.disabled,
draggable: '.is-draggable',
fallbackOnBody: false,
group: 'boards-list',
tag: 'div',
value: this.lists,
};
return this.canDragColumns ? options : {};
},
},
mounted() {
if (this.glFeatures.graphqlBoardLists) {
this.showPromotionList();
}
},
methods: {
...mapActions(['showPromotionList']),
...mapActions(['moveList']),
handleDragOnStart() {
sortableStart();
},
handleDragOnEnd(params) {
sortableEnd();
const { item, newIndex, oldIndex, to } = params;
const listId = item.dataset.id;
const replacedListId = to.children[newIndex].dataset.id;
this.moveList({
listId,
replacedListId,
newIndex,
adjustmentValue: newIndex < oldIndex ? 1 : -1,
});
},
},
};
</script>
@ -53,10 +89,14 @@ export default {
<gl-alert v-if="error" variant="danger" :dismissible="false">
{{ error }}
</gl-alert>
<div
<component
:is="boardColumnWrapper"
v-if="!isSwimlanesOn"
ref="list"
v-bind="draggableOptions"
class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
data-qa-selector="boards_list"
@start="handleDragOnStart"
@end="handleDragOnEnd"
>
<board-column
v-for="list in boardListsToUse"
@ -66,7 +106,7 @@ export default {
:list="list"
:disabled="disabled"
/>
</div>
</component>
<template v-else>
<epics-swimlanes

View file

@ -1,11 +1,14 @@
<script>
import { __ } from '~/locale';
import { GlModal } from '@gitlab/ui';
import { pick } from 'lodash';
import { __, s__ } from '~/locale';
import { deprecatedCreateFlash as 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';
import { fullBoardId, getBoardsPath } from '../boards_util';
import BoardConfigurationOptions from './board_configuration_options.vue';
import createBoardMutation from '../graphql/board.mutation.graphql';
const boardDefaults = {
id: false,
@ -19,10 +22,28 @@ const boardDefaults = {
hide_closed_list: false,
};
const formType = {
new: 'new',
delete: 'delete',
edit: 'edit',
};
export default {
i18n: {
[formType.new]: { title: s__('Board|Create new board'), btnText: s__('Board|Create board') },
[formType.delete]: { title: s__('Board|Delete board'), btnText: __('Delete') },
[formType.edit]: { title: s__('Board|Edit board'), btnText: __('Save changes') },
scopeModalTitle: s__('Board|Board scope'),
cancelButtonText: __('Cancel'),
deleteErrorMessage: s__('Board|Failed to delete board. Please try again.'),
saveErrorMessage: __('Unable to save your changes. Please try again.'),
deleteConfirmationMessage: s__('Board|Are you sure you want to delete this board?'),
titleFieldLabel: __('Title'),
titleFieldPlaceholder: s__('Board|Enter board name'),
},
components: {
BoardScope: () => import('ee_component/boards/components/board_scope.vue'),
DeprecatedModal,
GlModal,
BoardConfigurationOptions,
},
props: {
@ -63,36 +84,35 @@ export default {
required: false,
default: false,
},
currentBoard: {
type: Object,
required: true,
},
},
inject: {
endpoints: {
default: {},
},
},
data() {
return {
board: { ...boardDefaults, ...this.currentBoard },
currentBoard: boardsStore.state.currentBoard,
currentPage: boardsStore.state.currentPage,
isLoading: false,
};
},
computed: {
isNewForm() {
return this.currentPage === 'new';
return this.currentPage === formType.new;
},
isDeleteForm() {
return this.currentPage === 'delete';
return this.currentPage === formType.delete;
},
isEditForm() {
return this.currentPage === 'edit';
},
isVisible() {
return this.currentPage !== '';
return this.currentPage === formType.edit;
},
buttonText() {
if (this.isNewForm) {
return __('Create board');
}
if (this.isDeleteForm) {
return __('Delete');
}
return __('Save changes');
return this.$options.i18n[this.currentPage].btnText;
},
buttonKind() {
if (this.isNewForm) {
@ -104,16 +124,11 @@ export default {
return 'info';
},
title() {
if (this.isNewForm) {
return __('Create new board');
}
if (this.isDeleteForm) {
return __('Delete board');
}
if (this.readonly) {
return __('Board scope');
return this.$options.i18n.scopeModalTitle;
}
return __('Edit board');
return this.$options.i18n[this.currentPage].title;
},
readonly() {
return !this.canAdminBoard;
@ -121,6 +136,33 @@ export default {
submitDisabled() {
return this.isLoading || this.board.name.length === 0;
},
primaryProps() {
return {
text: this.buttonText,
attributes: [
{
variant: this.buttonKind,
disabled: this.submitDisabled,
loading: this.isLoading,
'data-qa-selector': 'save_changes_button',
},
],
};
},
cancelProps() {
return {
text: this.$options.i18n.cancelButtonText,
};
},
boardPayload() {
const { assignee, milestone, labels } = this.board;
return {
...this.board,
assignee_id: assignee?.id,
milestone_id: milestone?.id,
label_ids: labels.length ? labels.map(b => b.id) : [''],
};
},
},
mounted() {
this.resetFormState();
@ -129,6 +171,31 @@ export default {
}
},
methods: {
callBoardMutation(id) {
return this.$apollo.mutate({
mutation: createBoardMutation,
variables: {
...pick(this.boardPayload, ['hideClosedList', 'hideBacklogList']),
id,
},
});
},
async updateBoard() {
const responses = await Promise.all([
// Remove unnecessary REST API call when https://gitlab.com/gitlab-org/gitlab/-/issues/282299#note_462996301 is resolved
getBoardsPath(this.endpoints.boardsEndpoint, this.boardPayload),
this.callBoardMutation(fullBoardId(this.boardPayload.id)),
]);
return responses[0].data;
},
async createBoard() {
// TODO: change this to use `createBoard` mutation https://gitlab.com/gitlab-org/gitlab/-/issues/292466 is resolved
const boardData = await getBoardsPath(this.endpoints.boardsEndpoint, this.boardPayload);
this.callBoardMutation(fullBoardId(boardData.data.id));
return boardData.data || boardData;
},
submit() {
if (this.board.name.length === 0) return;
this.isLoading = true;
@ -136,31 +203,21 @@ export default {
boardsStore
.deleteBoard(this.currentBoard)
.then(() => {
this.isLoading = false;
visitUrl(boardsStore.rootPath);
})
.catch(() => {
Flash(__('Failed to delete board. Please try again.'));
Flash(this.$options.i18n.deleteErrorMessage);
this.isLoading = false;
});
} else {
boardsStore
.createBoard(this.board)
.then(resp => {
// This handles 2 use cases
// - In create call we only get one parameter, the new board
// - In update call, due to Promise.all, we get REST response in
// array index 0
if (Array.isArray(resp)) {
return resp[0].data;
}
return resp.data ? resp.data : resp;
})
const boardAction = this.boardPayload.id ? this.updateBoard : this.createBoard;
boardAction()
.then(data => {
visitUrl(data.board_path);
})
.catch(() => {
Flash(__('Unable to save your changes. Please try again.'));
Flash(this.$options.i18n.saveErrorMessage);
this.isLoading = false;
});
}
@ -181,53 +238,58 @@ export default {
</script>
<template>
<deprecated-modal
v-show="isVisible"
<gl-modal
modal-id="board-config-modal"
modal-class="board-config-modal"
content-class="gl-absolute gl-top-7"
visible
:hide-footer="readonly"
:title="title"
:primary-button-label="buttonText"
:kind="buttonKind"
:submit-disabled="submitDisabled"
modal-dialog-class="board-config-modal"
:action-primary="primaryProps"
:action-cancel="cancelProps"
@primary="submit"
@cancel="cancel"
@submit="submit"
@close="cancel"
@hide.prevent
>
<template #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="label-bold gl-font-lg" for="board-new-name">{{ __('Title') }}</label>
<input
id="board-new-name"
ref="name"
v-model="board.name"
class="form-control"
data-qa-selector="board_name_field"
type="text"
:placeholder="__('Enter board name')"
@keyup.enter="submit"
/>
</div>
<board-configuration-options
:is-new-form="isNewForm"
:board="board"
:current-board="currentBoard"
<p v-if="isDeleteForm" data-testid="delete-confirmation-message">
{{ $options.i18n.deleteConfirmationMessage }}
</p>
<form v-else class="js-board-config-modal" data-testid="board-form-wrapper" @submit.prevent>
<div v-if="!readonly" class="gl-mb-5" data-testid="board-form">
<label class="gl-font-weight-bold gl-font-lg" for="board-new-name">
{{ $options.i18n.titleFieldLabel }}
</label>
<input
id="board-new-name"
ref="name"
v-model="board.name"
class="form-control"
data-qa-selector="board_name_field"
type="text"
:placeholder="$options.i18n.titleFieldPlaceholder"
@keyup.enter="submit"
/>
</div>
<board-scope
v-if="scopedIssueBoardFeatureEnabled"
:collapse-scope="isNewForm"
:board="board"
:can-admin-board="canAdminBoard"
:labels-path="labelsPath"
:labels-web-url="labelsWebUrl"
:enable-scoped-labels="enableScopedLabels"
:project-id="projectId"
:group-id="groupId"
:weights="weights"
/>
</form>
</template>
</deprecated-modal>
<board-configuration-options
:is-new-form="isNewForm"
:board="board"
:current-board="currentBoard"
/>
<board-scope
v-if="scopedIssueBoardFeatureEnabled"
:collapse-scope="isNewForm"
:board="board"
:can-admin-board="canAdminBoard"
:labels-path="labelsPath"
:labels-web-url="labelsWebUrl"
:enable-scoped-labels="enableScopedLabels"
:project-id="projectId"
:group-id="groupId"
:weights="weights"
/>
</form>
</gl-modal>
</template>

View file

@ -6,7 +6,6 @@ import boardCard from './board_card.vue';
import eventHub from '../eventhub';
import boardsStore from '../stores/boards_store';
import { sprintf, __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import {
getBoardSortableDefaultOptions,
@ -25,7 +24,6 @@ export default {
boardNewIssue,
GlLoadingIcon,
},
mixins: [glFeatureFlagMixin()],
props: {
disabled: {
type: Boolean,

View file

@ -72,12 +72,7 @@ export default {
return this.list?.label?.description || this.list.title || '';
},
showListHeaderButton() {
return (
!this.disabled &&
this.listType !== ListType.closed &&
this.listType !== ListType.blank &&
this.listType !== ListType.promotion
);
return !this.disabled && this.listType !== ListType.closed;
},
showMilestoneListDetails() {
return (
@ -109,9 +104,6 @@ export default {
this.listType !== ListType.backlog && this.showListHeaderButton && this.list.isExpanded
);
},
showBoardListAndBoardInfo() {
return this.listType !== ListType.blank && this.listType !== ListType.promotion;
},
uniqueKey() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `boards.${this.boardId}.${this.listType}.${this.list.id}`;
@ -190,7 +182,8 @@ export default {
:title="chevronTooltip"
:icon="chevronIcon"
class="board-title-caret no-drag gl-cursor-pointer"
variant="link"
category="tertiary"
size="small"
@click="toggleExpanded"
/>
<!-- The following is only true in EE and if it is a milestone -->
@ -288,7 +281,6 @@ export default {
</gl-tooltip>
<div
v-if="showBoardListAndBoardInfo"
class="issue-count-badge gl-display-inline-flex gl-pr-0 no-drag text-secondary"
:class="{
'gl-display-none!': !list.isExpanded && isSwimlanesHeader,

View file

@ -9,15 +9,22 @@ import {
GlSprintf,
GlTooltipDirective,
} from '@gitlab/ui';
import { n__, s__ } from '~/locale';
import { n__, s__, __ } from '~/locale';
import AccessorUtilities from '../../lib/utils/accessor';
import IssueCount from './issue_count.vue';
import eventHub from '../eventhub';
import sidebarEventHub from '~/sidebar/event_hub';
import { inactiveId, LIST, ListType } from '../constants';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { isListDraggable } from '~/boards/boards_util';
export default {
i18n: {
newIssue: __('New issue'),
listSettings: __('List settings'),
expand: s__('Boards|Expand'),
collapse: s__('Boards|Collapse'),
},
components: {
GlButtonGroup,
GlButton,
@ -66,57 +73,49 @@ export default {
return Boolean(this.currentUserId);
},
listType() {
return this.list.type;
return this.list.listType;
},
listAssignee() {
return this.list?.assignee?.username || '';
},
listTitle() {
return this.list?.label?.description || this.list.title || '';
return this.list?.label?.description || this.list?.assignee?.name || this.list.title || '';
},
showListHeaderButton() {
return (
!this.disabled &&
this.listType !== ListType.closed &&
this.listType !== ListType.blank &&
this.listType !== ListType.promotion
);
return !this.disabled && this.listType !== ListType.closed;
},
showMilestoneListDetails() {
return (
this.list.type === ListType.milestone &&
this.listType === ListType.milestone &&
this.list.milestone &&
(this.list.isExpanded || !this.isSwimlanesHeader)
(!this.list.collapsed || !this.isSwimlanesHeader)
);
},
showAssigneeListDetails() {
return (
this.list.type === ListType.assignee && (this.list.isExpanded || !this.isSwimlanesHeader)
this.listType === ListType.assignee && (!this.list.collapsed || !this.isSwimlanesHeader)
);
},
issuesCount() {
return this.list.issuesSize;
return this.list.issuesCount;
},
issuesTooltipLabel() {
return n__(`%d issue`, `%d issues`, this.issuesCount);
},
chevronTooltip() {
return this.list.isExpanded ? s__('Boards|Collapse') : s__('Boards|Expand');
return this.list.collapsed ? this.$options.i18n.expand : this.$options.i18n.collapse;
},
chevronIcon() {
return this.list.isExpanded ? 'chevron-right' : 'chevron-down';
return this.list.collapsed ? 'chevron-down' : 'chevron-right';
},
isNewIssueShown() {
return this.listType === ListType.backlog || this.showListHeaderButton;
},
isSettingsShown() {
return (
this.listType !== ListType.backlog && this.showListHeaderButton && this.list.isExpanded
this.listType !== ListType.backlog && this.showListHeaderButton && !this.list.collapsed
);
},
showBoardListAndBoardInfo() {
return this.listType !== ListType.blank && this.listType !== ListType.promotion;
},
uniqueKey() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `boards.${this.boardId}.${this.listType}.${this.list.id}`;
@ -127,6 +126,9 @@ export default {
headerStyle() {
return { borderTopColor: this.list?.label?.color };
},
userCanDrag() {
return !this.disabled && isListDraggable(this.list);
},
},
methods: {
...mapActions(['updateList', 'setActiveId']),
@ -145,7 +147,7 @@ export default {
eventHub.$emit(`toggle-issue-form-${this.list.id}`);
},
toggleExpanded() {
this.list.isExpanded = !this.list.isExpanded;
this.list.collapsed = !this.list.collapsed;
if (!this.isLoggedIn) {
this.addToLocalStorage();
@ -159,11 +161,11 @@ export default {
},
addToLocalStorage() {
if (AccessorUtilities.isLocalStorageAccessSafe()) {
localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded);
localStorage.setItem(`${this.uniqueKey}.expanded`, !this.list.collapsed);
}
},
updateListFunction() {
this.updateList({ listId: this.list.id, collapsed: !this.list.isExpanded });
this.updateList({ listId: this.list.id, collapsed: this.list.collapsed });
},
},
};
@ -173,7 +175,7 @@ export default {
<header
:class="{
'has-border': list.label && list.label.color,
'gl-h-full': !list.isExpanded,
'gl-h-full': list.collapsed,
'board-inner gl-rounded-top-left-base gl-rounded-top-right-base': isSwimlanesHeader,
}"
:style="headerStyle"
@ -183,22 +185,22 @@ export default {
>
<h3
:class="{
'user-can-drag': !disabled && !list.preset,
'gl-py-3 gl-h-full': !list.isExpanded && !isSwimlanesHeader,
'gl-border-b-0': !list.isExpanded || isSwimlanesHeader,
'gl-py-2': !list.isExpanded && isSwimlanesHeader,
'gl-flex-direction-column': !list.isExpanded,
'user-can-drag': userCanDrag,
'gl-py-3 gl-h-full': list.collapsed && !isSwimlanesHeader,
'gl-border-b-0': list.collapsed || isSwimlanesHeader,
'gl-py-2': list.collapsed && isSwimlanesHeader,
'gl-flex-direction-column': list.collapsed,
}"
class="board-title gl-m-0 gl-display-flex gl-align-items-center gl-font-base gl-px-3 js-board-handle"
>
<gl-button
v-if="list.isExpandable"
v-gl-tooltip.hover
:aria-label="chevronTooltip"
:title="chevronTooltip"
:icon="chevronIcon"
class="board-title-caret no-drag gl-cursor-pointer"
variant="link"
category="tertiary"
size="small"
@click="toggleExpanded"
/>
<!-- EE start -->
@ -207,8 +209,8 @@ export default {
aria-hidden="true"
class="milestone-icon"
:class="{
'gl-mt-3 gl-rotate-90': !list.isExpanded,
'gl-mr-2': list.isExpanded,
'gl-mt-3 gl-rotate-90': list.collapsed,
'gl-mr-2': !list.collapsed,
}"
>
<gl-icon name="timer" />
@ -216,17 +218,17 @@ export default {
<a
v-if="showAssigneeListDetails"
:href="list.assignee.path"
:href="list.assignee.webUrl"
class="user-avatar-link js-no-trigger"
:class="{
'gl-mt-3 gl-rotate-90': !list.isExpanded,
'gl-mt-3 gl-rotate-90': list.collapsed,
}"
>
<img
v-gl-tooltip.hover.bottom
:title="listAssignee"
:alt="list.assignee.name"
:src="list.assignee.avatar"
:src="list.assignee.avatarUrl"
class="avatar s20"
height="20"
width="20"
@ -236,9 +238,9 @@ export default {
<div
class="board-title-text"
:class="{
'gl-display-none': !list.isExpanded && isSwimlanesHeader,
'gl-flex-grow-0 gl-my-3 gl-mx-0': !list.isExpanded,
'gl-flex-grow-1': list.isExpanded,
'gl-display-none': list.collapsed && isSwimlanesHeader,
'gl-flex-grow-0 gl-my-3 gl-mx-0': list.collapsed,
'gl-flex-grow-1': !list.collapsed,
}"
>
<!-- EE start -->
@ -246,16 +248,16 @@ export default {
v-if="listType !== 'label'"
v-gl-tooltip.hover
:class="{
'gl-display-block': !list.isExpanded || listType === 'milestone',
'gl-display-block': list.collapsed || listType === 'milestone',
}"
:title="listTitle"
class="board-title-main-text gl-text-truncate"
>
{{ list.title }}
{{ listTitle }}
</span>
<span
v-if="listType === 'assignee'"
v-show="list.isExpanded"
v-show="!list.collapsed"
class="gl-ml-2 gl-font-weight-normal gl-text-gray-500"
>
@{{ listAssignee }}
@ -267,21 +269,21 @@ export default {
:background-color="list.label.color"
:description="list.label.description"
:scoped="showScopedLabels(list.label)"
:size="!list.isExpanded ? 'sm' : ''"
:size="list.collapsed ? 'sm' : ''"
:title="list.label.title"
/>
</div>
<!-- EE start -->
<span
v-if="isSwimlanesHeader && !list.isExpanded"
v-if="isSwimlanesHeader && list.collapsed"
ref="collapsedInfo"
aria-hidden="true"
class="board-header-collapsed-info-icon gl-mt-2 gl-cursor-pointer gl-text-gray-500"
class="board-header-collapsed-info-icon gl-cursor-pointer gl-text-gray-500"
>
<gl-icon name="information" />
</span>
<gl-tooltip v-if="isSwimlanesHeader && !list.isExpanded" :target="() => $refs.collapsedInfo">
<gl-tooltip v-if="isSwimlanesHeader && list.collapsed" :target="() => $refs.collapsedInfo">
<div class="gl-font-weight-bold gl-pb-2">{{ collapsedTooltipTitle }}</div>
<div v-if="list.maxIssueCount !== 0">
@ -301,11 +303,10 @@ export default {
<!-- EE end -->
<div
v-if="showBoardListAndBoardInfo"
class="issue-count-badge gl-display-inline-flex gl-pr-0 no-drag gl-text-gray-500"
:class="{
'gl-display-none!': !list.isExpanded && isSwimlanesHeader,
'gl-p-0': !list.isExpanded,
'gl-display-none!': list.collapsed && isSwimlanesHeader,
'gl-p-0': list.collapsed,
}"
>
<span class="gl-display-inline-flex">
@ -331,11 +332,11 @@ export default {
>
<gl-button
v-if="isNewIssueShown"
v-show="list.isExpanded"
v-show="!list.collapsed"
ref="newIssueBtn"
v-gl-tooltip.hover
:aria-label="__('New issue')"
:title="__('New issue')"
:aria-label="$options.i18n.newIssue"
:title="$options.i18n.newIssue"
class="issue-count-badge-add-button no-drag"
icon="plus"
@click="showNewIssueForm"
@ -345,13 +346,13 @@ export default {
v-if="isSettingsShown"
ref="settingsBtn"
v-gl-tooltip.hover
:aria-label="__('List settings')"
:aria-label="$options.i18n.listSettings"
class="no-drag js-board-settings-button"
:title="__('List settings')"
:title="$options.i18n.listSettings"
icon="settings"
@click="openSidebarSettings"
/>
<gl-tooltip :target="() => $refs.settingsBtn">{{ __('List settings') }}</gl-tooltip>
<gl-tooltip :target="() => $refs.settingsBtn">{{ $options.i18n.listSettings }}</gl-tooltip>
</gl-button-group>
</h3>
</header>

View file

@ -1,21 +1,26 @@
<script>
import Draggable from 'vuedraggable';
import { mapActions, mapState } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import defaultSortableConfig from '~/sortable/sortable_config';
import { sortableStart, sortableEnd } from '~/boards/mixins/sortable_default_options';
import BoardNewIssue from './board_new_issue_new.vue';
import BoardCard from './board_card.vue';
import eventHub from '../eventhub';
import boardsStore from '../stores/boards_store';
import { sprintf, __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'BoardList',
i18n: {
loadingIssues: __('Loading issues'),
loadingMoreissues: __('Loading more issues'),
showingAllIssues: __('Showing all issues'),
},
components: {
BoardCard,
BoardNewIssue,
GlLoadingIcon,
},
mixins: [glFeatureFlagMixin()],
props: {
disabled: {
type: Boolean,
@ -29,11 +34,15 @@ export default {
type: Array,
required: true,
},
canAdminList: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
scrollOffset: 250,
filters: boardsStore.state.filters,
showCount: false,
showIssueForm: false,
};
@ -43,11 +52,11 @@ export default {
paginatedIssueText() {
return sprintf(__('Showing %{pageSize} of %{total} issues'), {
pageSize: this.issues.length,
total: this.list.issuesSize,
total: this.list.issuesCount,
});
},
issuesSizeExceedsMax() {
return this.list.maxIssueCount > 0 && this.list.issuesSize > this.list.maxIssueCount;
return this.list.maxIssueCount > 0 && this.list.issuesCount > this.list.maxIssueCount;
},
hasNextPage() {
return this.pageInfoByListId[this.list.id].hasNextPage;
@ -55,15 +64,34 @@ export default {
loading() {
return this.listsFlags[this.list.id]?.isLoading;
},
loadingMore() {
return this.listsFlags[this.list.id]?.isLoadingMore;
},
listRef() {
// When list is draggable, the reference to the list needs to be accessed differently
return this.canAdminList ? this.$refs.list.$el : this.$refs.list;
},
showingAllIssues() {
return this.issues.length === this.list.issuesCount;
},
treeRootWrapper() {
return this.canAdminList ? Draggable : 'ul';
},
treeRootOptions() {
const options = {
...defaultSortableConfig,
fallbackOnBody: false,
group: 'board-list',
tag: 'ul',
'ghost-class': 'board-card-drag-active',
'data-list-id': this.list.id,
value: this.issues,
};
return this.canAdminList ? options : {};
},
},
watch: {
filters: {
handler() {
this.list.loadingMore = false;
this.$refs.list.scrollTop = 0;
},
deep: true,
},
issues() {
this.$nextTick(() => {
this.showCount = this.scrollHeight() > Math.ceil(this.listHeight());
@ -76,35 +104,29 @@ export default {
},
mounted() {
// Scroll event on list to load more
this.$refs.list.addEventListener('scroll', this.onScroll);
this.listRef.addEventListener('scroll', this.onScroll);
},
beforeDestroy() {
eventHub.$off(`toggle-issue-form-${this.list.id}`, this.toggleForm);
eventHub.$off(`scroll-board-list-${this.list.id}`, this.scrollToTop);
this.$refs.list.removeEventListener('scroll', this.onScroll);
this.listRef.removeEventListener('scroll', this.onScroll);
},
methods: {
...mapActions(['fetchIssuesForList']),
...mapActions(['fetchIssuesForList', 'moveIssue']),
listHeight() {
return this.$refs.list.getBoundingClientRect().height;
return this.listRef.getBoundingClientRect().height;
},
scrollHeight() {
return this.$refs.list.scrollHeight;
return this.listRef.scrollHeight;
},
scrollTop() {
return this.$refs.list.scrollTop + this.listHeight();
return this.listRef.scrollTop + this.listHeight();
},
scrollToTop() {
this.$refs.list.scrollTop = 0;
this.listRef.scrollTop = 0;
},
loadNextPage() {
const loadingDone = () => {
this.list.loadingMore = false;
};
this.list.loadingMore = true;
this.fetchIssuesForList({ listId: this.list.id, fetchNext: true })
.then(loadingDone)
.catch(loadingDone);
this.fetchIssuesForList({ listId: this.list.id, fetchNext: true });
},
toggleForm() {
this.showIssueForm = !this.showIssueForm;
@ -112,7 +134,7 @@ export default {
onScroll() {
window.requestAnimationFrame(() => {
if (
!this.list.loadingMore &&
!this.loadingMore &&
this.scrollTop() > this.scrollHeight() - this.scrollOffset &&
this.hasNextPage
) {
@ -120,32 +142,83 @@ export default {
}
});
},
handleDragOnStart() {
sortableStart();
},
handleDragOnEnd(params) {
sortableEnd();
const { newIndex, oldIndex, from, to, item } = params;
const { issueId, issueIid, issuePath } = item.dataset;
const { children } = to;
let moveBeforeId;
let moveAfterId;
const getIssueId = el => Number(el.dataset.issueId);
// If issue is being moved within the same list
if (from === to) {
if (newIndex > oldIndex && children.length > 1) {
// If issue is being moved down we look for the issue that ends up before
moveBeforeId = getIssueId(children[newIndex]);
} else if (newIndex < oldIndex && children.length > 1) {
// If issue is being moved up we look for the issue that ends up after
moveAfterId = getIssueId(children[newIndex]);
} else {
// If issue remains in the same list at the same position we do nothing
return;
}
} else {
// We look for the issue that ends up before the moved issue if it exists
if (children[newIndex - 1]) {
moveBeforeId = getIssueId(children[newIndex - 1]);
}
// We look for the issue that ends up after the moved issue if it exists
if (children[newIndex]) {
moveAfterId = getIssueId(children[newIndex]);
}
}
this.moveIssue({
issueId,
issueIid,
issuePath,
fromListId: from.dataset.listId,
toListId: to.dataset.listId,
moveBeforeId,
moveAfterId,
});
},
},
};
</script>
<template>
<div
v-show="list.isExpanded"
v-show="!list.collapsed"
class="board-list-component gl-relative gl-h-full gl-display-flex gl-flex-direction-column"
data-qa-selector="board_list_cards_area"
>
<div
v-if="loading"
class="gl-mt-4 gl-text-center"
:aria-label="__('Loading issues')"
:aria-label="$options.i18n.loadingIssues"
data-testid="board_list_loading"
>
<gl-loading-icon />
</div>
<board-new-issue v-if="list.type !== 'closed' && showIssueForm" :list="list" />
<ul
<board-new-issue v-if="list.listType !== 'closed' && showIssueForm" :list="list" />
<component
:is="treeRootWrapper"
v-show="!loading"
ref="list"
v-bind="treeRootOptions"
:data-board="list.id"
:data-board-type="list.type"
:data-board-type="list.listType"
:class="{ 'bg-danger-100': issuesSizeExceedsMax }"
class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-2 js-board-list"
data-testid="tree-root-wrapper"
@start="handleDragOnStart"
@end="handleDragOnEnd"
>
<board-card
v-for="(issue, index) in issues"
@ -157,10 +230,10 @@ export default {
:disabled="disabled"
/>
<li v-if="showCount" class="board-list-count gl-text-center" data-issue-id="-1">
<gl-loading-icon v-show="list.loadingMore" label="Loading more issues" />
<span v-if="issues.length === list.issuesSize">{{ __('Showing all issues') }}</span>
<gl-loading-icon v-if="loadingMore" :label="$options.i18n.loadingMoreissues" />
<span v-if="showingAllIssues">{{ $options.i18n.showingAllIssues }}</span>
<span v-else>{{ paginatedIssueText }}</span>
</li>
</ul>
</component>
</div>
</template>

View file

@ -1 +0,0 @@
export default {};

View file

@ -53,7 +53,7 @@ export default {
return this.activeList.label;
},
boardListType() {
return this.activeList.type || null;
return this.activeList.type || this.activeList.listType || null;
},
listTypeTitle() {
return this.$options.labelListText;

View file

@ -3,17 +3,18 @@ import { throttle } from 'lodash';
import {
GlLoadingIcon,
GlSearchBoxByType,
GlDeprecatedDropdown,
GlDeprecatedDropdownDivider,
GlDeprecatedDropdownHeader,
GlDeprecatedDropdownItem,
GlDropdown,
GlDropdownDivider,
GlDropdownSectionHeader,
GlDropdownItem,
GlModalDirective,
} from '@gitlab/ui';
import httpStatusCodes from '~/lib/utils/http_status';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import projectQuery from '../queries/project_boards.query.graphql';
import groupQuery from '../queries/group_boards.query.graphql';
import projectQuery from '../graphql/project_boards.query.graphql';
import groupQuery from '../graphql/group_boards.query.graphql';
import boardsStore from '../stores/boards_store';
import BoardForm from './board_form.vue';
@ -26,10 +27,13 @@ export default {
BoardForm,
GlLoadingIcon,
GlSearchBoxByType,
GlDeprecatedDropdown,
GlDeprecatedDropdownDivider,
GlDeprecatedDropdownHeader,
GlDeprecatedDropdownItem,
GlDropdown,
GlDropdownDivider,
GlDropdownSectionHeader,
GlDropdownItem,
},
directives: {
GlModalDirective,
},
props: {
currentBoard: {
@ -108,7 +112,7 @@ export default {
return this.groupId ? 'group' : 'project';
},
loading() {
return this.loadingRecentBoards && this.loadingBoards;
return this.loadingRecentBoards || Boolean(this.loadingBoards);
},
currentPage() {
return this.state.currentPage;
@ -235,22 +239,17 @@ export default {
<template>
<div class="boards-switcher js-boards-selector gl-mr-3">
<span class="boards-selector-wrapper js-boards-selector-wrapper">
<gl-deprecated-dropdown
<gl-dropdown
data-qa-selector="boards_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-deprecated-dropdown-header class="mt-0">
<gl-search-box-by-type ref="searchBox" v-model="filterTerm" />
</gl-deprecated-dropdown-header>
<p class="gl-new-dropdown-header-top" @mousedown.prevent>
{{ s__('IssueBoards|Switch board') }}
</p>
<gl-search-box-by-type ref="searchBox" v-model="filterTerm" class="m-2" />
<div
v-if="!loading"
@ -259,49 +258,50 @@ export default {
class="dropdown-content flex-fill"
@scroll.passive="throttledSetScrollFade"
>
<gl-deprecated-dropdown-item
<gl-dropdown-item
v-show="filteredBoards.length === 0"
class="gl-pointer-events-none text-secondary"
>
{{ s__('IssueBoards|No matching boards found') }}
</gl-deprecated-dropdown-item>
</gl-dropdown-item>
<h6 v-if="showRecentSection" class="dropdown-bold-header my-0">
<gl-dropdown-section-header v-if="showRecentSection">
{{ __('Recent') }}
</h6>
</gl-dropdown-section-header>
<template v-if="showRecentSection">
<gl-deprecated-dropdown-item
<gl-dropdown-item
v-for="recentBoard in recentBoards"
:key="`recent-${recentBoard.id}`"
class="js-dropdown-item"
:href="`${boardBaseUrl}/${recentBoard.id}`"
>
{{ recentBoard.name }}
</gl-deprecated-dropdown-item>
</gl-dropdown-item>
</template>
<hr v-if="showRecentSection" class="my-1" />
<gl-dropdown-divider v-if="showRecentSection" />
<h6 v-if="showRecentSection" class="dropdown-bold-header my-0">
<gl-dropdown-section-header v-if="showRecentSection">
{{ __('All') }}
</h6>
</gl-dropdown-section-header>
<gl-deprecated-dropdown-item
<gl-dropdown-item
v-for="otherBoard in filteredBoards"
:key="otherBoard.id"
class="js-dropdown-item"
:href="`${boardBaseUrl}/${otherBoard.id}`"
>
{{ otherBoard.name }}
</gl-deprecated-dropdown-item>
<gl-deprecated-dropdown-item v-if="hasMissingBoards" class="small unclickable">
</gl-dropdown-item>
<gl-dropdown-item v-if="hasMissingBoards" class="no-pointer-events">
{{
s__(
'IssueBoards|Some of your boards are hidden, activate a license to see them again.',
)
}}
</gl-deprecated-dropdown-item>
</gl-dropdown-item>
</div>
<div
@ -313,25 +313,27 @@ export default {
<gl-loading-icon v-if="loading" />
<div v-if="canAdminBoard">
<gl-deprecated-dropdown-divider />
<gl-dropdown-divider />
<gl-deprecated-dropdown-item
<gl-dropdown-item
v-if="multipleIssueBoardsAvailable"
v-gl-modal-directive="'board-config-modal'"
data-qa-selector="create_new_board_button"
@click.prevent="showPage('new')"
>
{{ s__('IssueBoards|Create new board') }}
</gl-deprecated-dropdown-item>
</gl-dropdown-item>
<gl-deprecated-dropdown-item
<gl-dropdown-item
v-if="showDelete"
v-gl-modal-directive="'board-config-modal'"
class="text-danger js-delete-board"
@click.prevent="showPage('delete')"
>
{{ s__('IssueBoards|Delete board') }}
</gl-deprecated-dropdown-item>
</gl-dropdown-item>
</div>
</gl-deprecated-dropdown>
</gl-dropdown>
<board-form
v-if="currentPage"
@ -343,6 +345,7 @@ export default {
:scoped-issue-board-feature-enabled="scopedIssueBoardFeatureEnabled"
:weights="weights"
:enable-scoped-labels="enabledScopedLabels"
:current-board="currentBoard"
/>
</span>
</div>

View file

@ -10,6 +10,7 @@ import IssueDueDate from './issue_due_date.vue';
import IssueTimeEstimate from './issue_time_estimate.vue';
import boardsStore from '../stores/boards_store';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { ListType } from '../constants';
export default {
components: {
@ -122,7 +123,13 @@ export default {
return true;
},
isNonListLabel(label) {
return label.id && !(this.list.type === 'label' && this.list.title === label.title);
return (
label.id &&
!(
(this.list.type || this.list.listType) === ListType.label &&
this.list.title === label.title
)
);
},
filterByLabel(label) {
if (!this.updateFilters) return;
@ -158,9 +165,13 @@ export default {
class="confidential-icon gl-mr-2"
:aria-label="__('Confidential')"
/>
<a :href="issue.path" :title="issue.title" class="js-no-trigger" @mousemove.stop>{{
issue.title
}}</a>
<a
:href="issue.path || issue.webUrl || ''"
:title="issue.title"
class="js-no-trigger"
@mousemove.stop
>{{ issue.title }}</a
>
</h4>
</div>
<div v-if="showLabelFooter" class="board-card-labels gl-mt-2 gl-display-flex gl-flex-wrap">
@ -196,7 +207,11 @@ export default {
#{{ issue.iid }}
</span>
<span class="board-info-items gl-mt-3 gl-display-inline-block">
<issue-due-date v-if="issue.dueDate" :date="issue.dueDate" :closed="issue.closed" />
<issue-due-date
v-if="issue.dueDate"
:date="issue.dueDate"
:closed="issue.closed || Boolean(issue.closedAt)"
/>
<issue-time-estimate v-if="issue.timeEstimate" :estimate="issue.timeEstimate" />
<issue-card-weight
v-if="validIssueWeight"

View file

@ -15,6 +15,7 @@ function shouldCreateListGraphQL(label) {
return store.getters.shouldUseGraphQL && !store.getters.getListByLabelId(fullLabelId(label));
}
// eslint-disable-next-line @gitlab/no-global-event-off
$(document)
.off('created.label')
.on('created.label', (e, label, addNewList) => {

View file

@ -7,6 +7,7 @@ import eventHub from '../eventhub';
import Api from '../../api';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import { ListType } from '../constants';
export default {
name: 'BoardProjectSelect',
@ -53,7 +54,7 @@ export default {
this.loading = true;
const additionalAttrs = {};
if (this.list.type && this.list.type !== 'backlog') {
if ((this.list.type || this.list.listType) !== ListType.backlog) {
additionalAttrs.min_access_level = featureAccessLevel.EVERYONE;
}

View file

@ -50,6 +50,13 @@ export default {
}
window.removeEventListener('click', this.collapseWhenOffClick);
},
toggle({ emitEvent = true } = {}) {
if (this.edit) {
this.collapse({ emitEvent });
} else {
this.expand();
}
},
},
};
</script>
@ -64,18 +71,18 @@ export default {
<gl-button
v-if="canUpdate"
variant="link"
class="gl-text-gray-900!"
class="gl-text-gray-900! js-sidebar-dropdown-toggle"
data-testid="edit-button"
@click="expand()"
@click="toggle"
>
{{ __('Edit') }}
</gl-button>
</div>
<div v-show="!edit" class="gl-text-gray-400" data-testid="collapsed-content">
<div v-show="!edit" class="gl-text-gray-500" data-testid="collapsed-content">
<slot name="collapsed">{{ __('None') }}</slot>
</div>
<div v-show="edit" data-testid="expanded-content">
<slot></slot>
<slot :edit="edit"></slot>
</div>
</div>
</template>

View file

@ -79,7 +79,7 @@ export default {
<span class="gl-mx-2">-</span>
<gl-button
variant="link"
class="gl-text-gray-400!"
class="gl-text-gray-500!"
data-testid="reset-button"
:disabled="loading"
@click="setDueDate(null)"

View file

@ -92,7 +92,7 @@ export default {
@close="removeLabel(label.id)"
/>
</template>
<template>
<template #default="{ edit }">
<labels-select
ref="labelsSelect"
:allow-label-edit="false"
@ -105,6 +105,7 @@ export default {
:labels-filter-base-path="labelsFilterBasePath"
:labels-list-title="__('Select label')"
:dropdown-button-text="__('Choose labels')"
:is-editing="edit"
variant="embedded"
class="gl-display-block labels gl-w-full"
@updateSelectedLabels="setLabels"

View file

@ -0,0 +1,161 @@
<script>
import { mapGetters, mapActions } from 'vuex';
import {
GlDropdown,
GlDropdownItem,
GlDropdownText,
GlSearchBoxByType,
GlDropdownDivider,
GlLoadingIcon,
} from '@gitlab/ui';
import { fetchPolicies } from '~/lib/graphql';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
import groupMilestones from '../../graphql/group_milestones.query.graphql';
import createFlash from '~/flash';
import { __, s__ } from '~/locale';
export default {
components: {
BoardEditableItem,
GlDropdown,
GlLoadingIcon,
GlDropdownItem,
GlDropdownText,
GlSearchBoxByType,
GlDropdownDivider,
},
data() {
return {
milestones: [],
searchTitle: '',
loading: false,
edit: false,
};
},
apollo: {
milestones: {
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
query: groupMilestones,
debounce: 250,
skip() {
return !this.edit;
},
variables() {
return {
fullPath: this.groupFullPath,
searchTitle: this.searchTitle,
state: 'active',
includeDescendants: true,
};
},
update(data) {
const edges = data?.group?.milestones?.edges ?? [];
return edges.map(item => item.node);
},
error() {
createFlash({ message: this.$options.i18n.fetchMilestonesError });
},
},
},
computed: {
...mapGetters({ issue: 'activeIssue' }),
hasMilestone() {
return this.issue.milestone !== null;
},
groupFullPath() {
const { referencePath = '' } = this.issue;
return referencePath.slice(0, referencePath.indexOf('/'));
},
projectPath() {
const { referencePath = '' } = this.issue;
return referencePath.slice(0, referencePath.indexOf('#'));
},
dropdownText() {
return this.issue.milestone?.title ?? this.$options.i18n.noMilestone;
},
},
mounted() {
this.$root.$on('bv::dropdown::hide', () => {
this.$refs.sidebarItem.collapse();
});
},
methods: {
...mapActions(['setActiveIssueMilestone']),
handleOpen() {
this.edit = true;
this.$refs.dropdown.show();
},
async setMilestone(milestoneId) {
this.loading = true;
this.searchTitle = '';
this.$refs.sidebarItem.collapse();
try {
const input = { milestoneId, projectPath: this.projectPath };
await this.setActiveIssueMilestone(input);
} catch (e) {
createFlash({ message: this.$options.i18n.updateMilestoneError });
} finally {
this.loading = false;
}
},
},
i18n: {
milestone: __('Milestone'),
noMilestone: __('No milestone'),
assignMilestone: __('Assign milestone'),
noMilestonesFound: s__('Milestones|No milestones found'),
fetchMilestonesError: __('There was a problem fetching milestones.'),
updateMilestoneError: __('An error occurred while updating the milestone.'),
},
};
</script>
<template>
<board-editable-item
ref="sidebarItem"
:title="$options.i18n.milestone"
:loading="loading"
@open="handleOpen()"
@close="edit = false"
>
<template v-if="hasMilestone" #collapsed>
<strong class="gl-text-gray-900">{{ issue.milestone.title }}</strong>
</template>
<template>
<gl-dropdown
ref="dropdown"
:text="dropdownText"
:header-text="$options.i18n.assignMilestone"
block
>
<gl-search-box-by-type ref="search" v-model.trim="searchTitle" class="gl-m-3" />
<gl-dropdown-item
data-testid="no-milestone-item"
:is-check-item="true"
:is-checked="!issue.milestone"
@click="setMilestone(null)"
>
{{ $options.i18n.noMilestone }}
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-loading-icon v-if="$apollo.loading" class="gl-py-4" />
<template v-else-if="milestones.length > 0">
<gl-dropdown-item
v-for="milestone in milestones"
:key="milestone.id"
:is-check-item="true"
:is-checked="issue.milestone && milestone.id === issue.milestone.id"
data-testid="milestone-item"
@click="setMilestone(milestone.id)"
>
{{ milestone.title }}
</gl-dropdown-item>
</template>
<gl-dropdown-text v-else data-testid="no-milestones-found">
{{ $options.i18n.noMilestonesFound }}
</gl-dropdown-text>
</gl-dropdown>
</template>
</board-editable-item>
</template>

View file

@ -9,8 +9,6 @@ export const ListType = {
backlog: 'backlog',
closed: 'closed',
label: 'label',
promotion: 'promotion',
blank: 'blank',
};
export const inactiveId = 0;
@ -18,11 +16,7 @@ export const inactiveId = 0;
export const ISSUABLE = 'issuable';
export const LIST = 'list';
/* eslint-disable-next-line @gitlab/require-i18n-strings */
export const DEFAULT_LABELS = ['to do', 'doing'];
export default {
BoardType,
ListType,
DEFAULT_LABELS,
};

View file

@ -1,5 +1,3 @@
export const setPromotionState = () => {};
export const setWeightFetchingState = () => {};
export const setEpicFetchingState = () => {};

View file

@ -1,7 +1,10 @@
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_manager';
import { transformBoardConfig } from 'ee_else_ce/boards/boards_util';
import FilteredSearchContainer from '../filtered_search/container';
import boardsStore from './stores/boards_store';
import vuexstore from './stores';
import { updateHistory } from '~/lib/utils/url_utility';
export default class FilteredSearchBoards extends FilteredSearchManager {
constructor(store, updateUrl = false, cantEdit = []) {
@ -22,18 +25,28 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
this.isHandledAsync = true;
this.cantEdit = cantEdit.filter(i => typeof i === 'string');
this.cantEditWithValue = cantEdit.filter(i => typeof i === 'object');
if (vuexstore.getters.shouldUseGraphQL && vuexstore.state.boardConfig) {
const boardConfigPath = transformBoardConfig(vuexstore.state.boardConfig);
if (boardConfigPath !== '') {
const filterPath = window.location.search ? `${window.location.search}&` : '?';
updateHistory({
url: `${filterPath}${transformBoardConfig(vuexstore.state.boardConfig)}`,
});
}
}
}
updateObject(path) {
const groupByParam = new URLSearchParams(window.location.search).get('group_by');
this.store.path = `${path.substr(1)}${groupByParam ? `&group_by=${groupByParam}` : ''}`;
if (gon.features.boardsWithSwimlanes || gon.features.graphqlBoardLists) {
boardsStore.updateFiltersUrl();
boardsStore.performSearch();
}
if (this.updateUrl) {
if (vuexstore.getters.shouldUseGraphQL) {
updateHistory({
url: `?${path.substr(1)}${groupByParam ? `&group_by=${groupByParam}` : ''}`,
});
vuexstore.dispatch('performSearch');
} else if (this.updateUrl) {
boardsStore.updateFiltersUrl();
}
}

View file

@ -1,4 +1,4 @@
#import "ee_else_ce/boards/queries/board_list.fragment.graphql"
#import "ee_else_ce/boards/graphql/board_list.fragment.graphql"
mutation CreateBoardList(
$boardId: BoardID!

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