Update upstream source from tag 'upstream/12.5.4'

Update to upstream version '12.5.4'
with Debian dir d9c413fb5f
This commit is contained in:
Pirate Praveen 2019-12-26 22:15:43 +05:30
commit 38f7d08512
2910 changed files with 92827 additions and 25855 deletions

2
.gitignore vendored
View file

@ -59,6 +59,7 @@ eslint-report.html
/public/uploads.* /public/uploads.*
/public/uploads/ /public/uploads/
/shared/artifacts/ /shared/artifacts/
/spec/examples.txt
/rails_best_practices_output.html /rails_best_practices_output.html
/tags /tags
/vendor/bundle/* /vendor/bundle/*
@ -82,3 +83,4 @@ jsdoc/
**/tmp/rubocop_cache/** **/tmp/rubocop_cache/**
.overcommit.yml .overcommit.yml
.projections.json .projections.json
/qa/.rakeTasks

View file

@ -1,6 +1,7 @@
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33"
stages: stages:
- sync
- prepare - prepare
- quick-test - quick-test
- test - test
@ -8,7 +9,6 @@ stages:
- review - review
- qa - qa
- post-test - post-test
- notification
- pages - pages
variables: variables:
@ -33,7 +33,6 @@ include:
- local: .gitlab/ci/frontend.gitlab-ci.yml - local: .gitlab/ci/frontend.gitlab-ci.yml
- local: .gitlab/ci/global.gitlab-ci.yml - local: .gitlab/ci/global.gitlab-ci.yml
- local: .gitlab/ci/memory.gitlab-ci.yml - local: .gitlab/ci/memory.gitlab-ci.yml
- local: .gitlab/ci/notifications.gitlab-ci.yml
- local: .gitlab/ci/pages.gitlab-ci.yml - local: .gitlab/ci/pages.gitlab-ci.yml
- local: .gitlab/ci/qa.gitlab-ci.yml - local: .gitlab/ci/qa.gitlab-ci.yml
- local: .gitlab/ci/reports.gitlab-ci.yml - local: .gitlab/ci/reports.gitlab-ci.yml
@ -42,3 +41,4 @@ include:
- local: .gitlab/ci/setup.gitlab-ci.yml - local: .gitlab/ci/setup.gitlab-ci.yml
- local: .gitlab/ci/test-metadata.gitlab-ci.yml - local: .gitlab/ci/test-metadata.gitlab-ci.yml
- local: .gitlab/ci/yaml.gitlab-ci.yml - local: .gitlab/ci/yaml.gitlab-ci.yml
- local: .gitlab/ci/releases.gitlab-ci.yml

View file

@ -3,11 +3,12 @@
*.rake @gitlab-org/maintainers/rails-backend *.rake @gitlab-org/maintainers/rails-backend
# Technical writing team are the default reviewers for everything in `doc/` # Technical writing team are the default reviewers for everything in `doc/`
/doc/ @axil @marcia @eread @mikelewis /doc/ @gl-docsteam
# Frontend maintainers should see everything in `app/assets/` # Frontend maintainers should see everything in `app/assets/`
app/assets/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina app/assets/ @gitlab-org/maintainers/frontend
*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina *.scss @annabeldunstone @gitlab-org/maintainers/frontend
/scripts/frontend/ @gitlab-org/maintainers/frontend
# Database maintainers should review changes in `db/` # Database maintainers should review changes in `db/`
db/ @gitlab-org/maintainers/database db/ @gitlab-org/maintainers/database
@ -32,4 +33,5 @@ lib/gitlab/github_import/ @gitlab-org/maintainers/database
/.gitlab/ci/ @gl-quality/eng-prod /.gitlab/ci/ @gl-quality/eng-prod
Dangerfile @gl-quality/eng-prod Dangerfile @gl-quality/eng-prod
/danger/ @gl-quality/eng-prod /danger/ @gl-quality/eng-prod
/lib/gitlab/danger/ @gl-quality/eng-prod
/scripts/ @gl-quality/eng-prod /scripts/ @gl-quality/eng-prod

View file

@ -1,4 +1,5 @@
cloud-native-image: cloud-native-image:
extends: .only:variables-canonical-dot-com
image: ruby:2.6-alpine image: ruby:2.6-alpine
dependencies: [] dependencies: []
stage: post-test stage: post-test
@ -12,5 +13,3 @@ cloud-native-image:
only: only:
refs: refs:
- tags - tags
variables:
- $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org"

View file

@ -2,12 +2,11 @@
extends: extends:
- .default-tags - .default-tags
- .default-retry - .default-retry
- .only-docs-changes - .only:variables-canonical-dot-com
- .only:changes-docs
only: only:
refs: refs:
- merge_requests - merge_requests
variables:
- $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org"
image: ruby:2.6-alpine image: ruby:2.6-alpine
stage: review stage: review
dependencies: [] dependencies: []
@ -50,7 +49,7 @@ docs lint:
- .default-tags - .default-tags
- .default-retry - .default-retry
- .default-only - .default-only
- .only-docs-changes - .only:changes-docs
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-docs-lint" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-docs-lint"
stage: test stage: test
dependencies: [] dependencies: []
@ -68,7 +67,7 @@ docs lint:
# Check the internal anchor links # Check the internal anchor links
- bundle exec nanoc check internal_anchors - bundle exec nanoc check internal_anchors
graphql-docs-verify: graphql-reference-verify:
extends: extends:
- .only-ee - .only-ee
- .default-tags - .default-tags
@ -76,10 +75,10 @@ graphql-docs-verify:
- .default-cache - .default-cache
- .default-only - .default-only
- .default-before_script - .default-before_script
- .only-graphql-changes - .only:changes-code-backstage-qa
variables: - .use-pg9
SETUP_DB: "false"
stage: test stage: test
needs: ["setup-test-env"] needs: ["setup-test-env"]
script: script:
- bundle exec rake gitlab:graphql:check_docs - bundle exec rake gitlab:graphql:check_docs
- bundle exec rake gitlab:graphql:check_schema

View file

@ -12,7 +12,7 @@
- .default-only - .default-only
- .default-before_script - .default-before_script
- .assets-compile-cache - .assets-compile-cache
- .only-code-qa-changes - .only:changes-code-backstage-qa
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-git-2.22-chrome-73.0-node-12.x-yarn-1.16-graphicsmagick-1.3.33-docker-18.06.1 image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-git-2.22-chrome-73.0-node-12.x-yarn-1.16-graphicsmagick-1.3.33-docker-18.06.1
stage: test stage: test
dependencies: ["setup-test-env"] dependencies: ["setup-test-env"]
@ -73,7 +73,7 @@ gitlab:assets:compile pull-cache:
- .default-only - .default-only
- .default-before_script - .default-before_script
- .assets-compile-cache - .assets-compile-cache
- .only-code-qa-changes - .only:changes-code-backstage-qa
- .use-pg9 - .use-pg9
stage: prepare stage: prepare
script: script:
@ -128,7 +128,7 @@ compile-assets pull-cache foss:
- .default-cache - .default-cache
- .default-only - .default-only
- .default-before_script - .default-before_script
- .only-code-changes - .only:changes-code-backstage
- .use-pg9 - .use-pg9
stage: test stage: test
needs: ["setup-test-env", "compile-assets pull-cache"] needs: ["setup-test-env", "compile-assets pull-cache"]
@ -205,7 +205,7 @@ jest-foss:
- .default-retry - .default-retry
- .default-cache - .default-cache
- .default-only - .default-only
- .only-code-changes - .only:changes-code-backstage
stage: test stage: test
dependencies: [] dependencies: []
cache: cache:
@ -238,7 +238,7 @@ webpack-dev-server:
- .default-retry - .default-retry
- .default-cache - .default-cache
- .default-only - .default-only
- .only-code-changes - .only:changes-code-backstage
stage: test stage: test
needs: ["setup-test-env", "compile-assets pull-cache"] needs: ["setup-test-env", "compile-assets pull-cache"]
dependencies: ["setup-test-env", "compile-assets pull-cache"] dependencies: ["setup-test-env", "compile-assets pull-cache"]

View file

@ -40,87 +40,160 @@
- merge_requests - merge_requests
- tags - tags
.only-code-changes: .only:variables-canonical-dot-com:
only:
changes:
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- ".csscomb.json"
- "Dangerfile"
- "Dockerfile.assets"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"
- "{babel.config,jest.config}.js"
- "config.ru"
- "{package.json,yarn.lock}"
- "{app,bin,config,danger,db,ee,fixtures,haml_lint,lib,locale,public,rubocop,scripts,spec,symbol,vendor}/**/*"
- "doc/README.md" # Some RSpec test rely on this file
.only-qa-changes:
only:
changes:
- ".dockerignore"
- "qa/**/*"
.only-docs-changes:
only:
changes:
- ".gitlab/route-map.yml"
- "doc/**/*"
- ".markdownlint.json"
.only-graphql-changes:
only:
changes:
- "{,ee/}app/graphql/**/*"
- "{,ee/}lib/gitlab/graphql/**/*"
.only-code-qa-changes:
only:
changes:
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- ".csscomb.json"
- "Dangerfile"
- "Dockerfile.assets"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"
- "{babel.config,jest.config}.js"
- "config.ru"
- "{package.json,yarn.lock}"
- "{app,bin,config,danger,db,ee,fixtures,haml_lint,lib,locale,public,rubocop,scripts,spec,symbol,vendor}/**/*"
- "doc/README.md" # Some RSpec test rely on this file
- ".dockerignore"
- "qa/**/*"
.only-review:
only: only:
variables: variables:
- $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" - $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE =~ /^gitlab-org($|\/)/ # Matches the gitlab-org group or its subgroups
kubernetes: active
except:
refs:
- master
- /^\d+-\d+-auto-deploy-\d+$/
- /^[\d-]+-stable(-ee)?$/
.only-review-schedules: .only:variables_refs-canonical-dot-com-schedules:
extends: .only:variables-canonical-dot-com
only: only:
refs: refs:
- schedules - schedules
variables:
- $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" .except:refs-deploy:
except:
refs:
- /^\d+-\d+-auto-deploy-\d+$/
.except:refs-master-tags-stable-deploy:
except:
refs:
- master
- tags
- /^[\d-]+-stable(-ee)?$/
- /^\d+-\d+-auto-deploy-\d+$/
.only:kubernetes:
only:
kubernetes: active kubernetes: active
.only-canonical-schedules: .only-review:
extends:
- .only:variables-canonical-dot-com
- .only:kubernetes
- .except:refs-master-tags-stable-deploy
.only-review-schedules:
extends:
- .only:variables_refs-canonical-dot-com-schedules
- .only:kubernetes
- .except:refs-deploy
.code-patterns: &code-patterns
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- ".csscomb.json"
- "Dockerfile.assets"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"
- "{babel.config,jest.config}.js"
- "config.ru"
- "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/**/*"
.backstage-patterns: &backstage-patterns
- "Dangerfile"
- "danger/**/*"
- "{,ee/}fixtures/**/*"
- "{,ee/}rubocop/**/*"
- "{,ee/}spec/**/*"
- "doc/README.md" # Some RSpec test rely on this file
.qa-patterns: &qa-patterns
- ".dockerignore"
- "qa/**/*"
.docs-patterns: &docs-patterns
- ".gitlab/route-map.yml"
- "doc/**/*"
- ".markdownlint.json"
.only:changes-code:
only: only:
refs: changes: *code-patterns
- schedules@gitlab-org/gitlab
- schedules@gitlab-org/gitlab-foss .only:changes-qa:
only:
changes: *qa-patterns
.only:changes-docs:
only:
changes: *docs-patterns
.only:changes-code-backstage:
only:
changes:
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- ".csscomb.json"
- "Dockerfile.assets"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"
- "{babel.config,jest.config}.js"
- "config.ru"
- "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/**/*"
# Backstage changes
- "Dangerfile"
- "danger/**/*"
- "{,ee/}fixtures/**/*"
- "{,ee/}rubocop/**/*"
- "{,ee/}spec/**/*"
- "doc/README.md" # Some RSpec test rely on this file
.only:changes-code-qa:
only:
changes:
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- ".csscomb.json"
- "Dockerfile.assets"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"
- "{babel.config,jest.config}.js"
- "config.ru"
- "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/**/*"
# QA changes
- ".dockerignore"
- "qa/**/*"
.only:changes-code-backstage-qa:
only:
changes:
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- ".csscomb.json"
- "Dockerfile.assets"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"
- "{babel.config,jest.config}.js"
- "config.ru"
- "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/**/*"
# Backstage changes
- "Dangerfile"
- "danger/**/*"
- "{,ee/}fixtures/**/*"
- "{,ee/}rubocop/**/*"
- "{,ee/}spec/**/*"
- "doc/README.md" # Some RSpec test rely on this file
# QA changes
- ".dockerignore"
- "qa/**/*"
.use-pg9: .use-pg9:
services: services:

View file

@ -5,7 +5,7 @@
- .default-cache - .default-cache
- .default-only - .default-only
- .default-before_script - .default-before_script
- .only-code-changes - .only:changes-code
memory-static: memory-static:
extends: .only-code-memory-job-base extends: .only-code-memory-job-base

View file

@ -1,29 +0,0 @@
.notify:
image: alpine
stage: notification
dependencies: []
cache: {}
before_script:
- apk update && apk add git curl bash
schedule:package-and-qa:notify-success:
extends:
- .only-canonical-schedules
- .notify
variables:
COMMIT_NOTES_URL: "https://$CI_SERVER_HOST/$CI_PROJECT_PATH/commit/$CI_COMMIT_SHA#notes-list"
script:
- 'scripts/notify-slack qa-master ":tada: Scheduled QA against master passed! :tada: See $CI_PIPELINE_URL. For downstream pipelines, see $COMMIT_NOTES_URL" ci_passing'
needs: ["schedule:package-and-qa"]
when: on_success
schedule:package-and-qa:notify-failure:
extends:
- .only-canonical-schedules
- .notify
variables:
COMMIT_NOTES_URL: "https://$CI_SERVER_HOST/$CI_PROJECT_PATH/commit/$CI_COMMIT_SHA#notes-list"
script:
- 'scripts/notify-slack qa-master ":skull_and_crossbones: Scheduled QA against master failed! :skull_and_crossbones: See $CI_PIPELINE_URL. For downstream pipelines, see $COMMIT_NOTES_URL" ci_failing'
needs: ["schedule:package-and-qa"]
when: on_failure

View file

@ -4,12 +4,11 @@ pages:
- .default-retry - .default-retry
- .default-cache - .default-cache
- .default-only - .default-only
- .only-code-qa-changes - .only:variables-canonical-dot-com
- .only:changes-code-backstage-qa
only: only:
refs: refs:
- master - master
variables:
- $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org"
stage: pages stage: pages
dependencies: ["coverage", "karma", "gitlab:assets:compile pull-cache"] dependencies: ["coverage", "karma", "gitlab:assets:compile pull-cache"]
script: script:

View file

@ -3,7 +3,7 @@
- .default-tags - .default-tags
- .default-retry - .default-retry
- .default-only - .default-only
- .only-code-qa-changes - .only:changes-code-qa
stage: test stage: test
dependencies: [] dependencies: []
cache: cache:
@ -31,7 +31,6 @@ qa:selectors-foss:
- .only-ee-as-if-foss - .only-ee-as-if-foss
.package-and-qa-base: .package-and-qa-base:
extends: .default-only
image: ruby:2.6-alpine image: ruby:2.6-alpine
stage: qa stage: qa
dependencies: [] dependencies: []
@ -40,35 +39,31 @@ qa:selectors-foss:
- source scripts/utils.sh - source scripts/utils.sh
- install_gitlab_gem - install_gitlab_gem
- ./scripts/trigger-build omnibus - ./scripts/trigger-build omnibus
only:
variables:
- $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE =~ /^gitlab-org($|\/)/ # Matches the gitlab-org group or its subgroups
package-and-qa-manual: package-and-qa-manual:
extends: extends:
- .package-and-qa-base - .package-and-qa-base
- .only-code-changes - .default-only
except: - .only:variables-canonical-dot-com
refs: - .except:refs-deploy
- master - .only:changes-code
- /^\d+-\d+-auto-deploy-\d+$/
when: manual when: manual
needs: ["build-qa-image", "gitlab:assets:compile pull-cache"] needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
package-and-qa: package-and-qa:
extends: extends:
- .package-and-qa-base - .package-and-qa-base
- .only-qa-changes - .default-only
except: - .only:variables-canonical-dot-com
refs: - .except:refs-master-tags-stable-deploy
- master - .only:changes-qa
- /^\d+-\d+-auto-deploy-\d+$/
needs: ["build-qa-image", "gitlab:assets:compile pull-cache"] needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
allow_failure: true allow_failure: true
schedule:package-and-qa: schedule:package-and-qa:
extends: extends:
- .package-and-qa-base - .package-and-qa-base
- .only-code-qa-changes - .default-only
- .only-canonical-schedules - .only:variables_refs-canonical-dot-com-schedules
needs: ["build-qa-image", "gitlab:assets:compile pull-cache"] needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
allow_failure: true

View file

@ -22,7 +22,7 @@
- .default-cache - .default-cache
- .default-only - .default-only
- .default-before_script - .default-before_script
- .only-code-changes - .only:changes-code-backstage
.only-code-qa-rails-job-base: .only-code-qa-rails-job-base:
extends: extends:
@ -31,7 +31,7 @@
- .default-cache - .default-cache
- .default-only - .default-only
- .default-before_script - .default-before_script
- .only-code-qa-changes - .only:changes-code-backstage-qa
setup-test-env: setup-test-env:
extends: extends:
@ -92,6 +92,14 @@ setup-test-env:
- .use-pg10 - .use-pg10
- .only-master - .only-master
rspec migration pg9:
extends: .rspec-base-pg9
parallel: 4
rspec migration pg9-foss:
extends: .rspec-base-pg9-foss
parallel: 4
rspec unit pg9: rspec unit pg9:
extends: .rspec-base-pg9 extends: .rspec-base-pg9
parallel: 20 parallel: 20
@ -140,9 +148,13 @@ rspec system pg10:
- .only-ee - .only-ee
- .use-pg10-ee - .use-pg10-ee
rspec-ee migration pg9:
extends: .rspec-ee-base-pg9
parallel: 2
rspec-ee unit pg9: rspec-ee unit pg9:
extends: .rspec-ee-base-pg9 extends: .rspec-ee-base-pg9
parallel: 7 parallel: 5
rspec-ee integration pg9: rspec-ee integration pg9:
extends: .rspec-ee-base-pg9 extends: .rspec-ee-base-pg9
@ -152,11 +164,17 @@ rspec-ee system pg9:
extends: .rspec-ee-base-pg9 extends: .rspec-ee-base-pg9
parallel: 5 parallel: 5
rspec-ee migration pg10:
extends:
- .rspec-ee-base-pg10
- .only-master
parallel: 2
rspec-ee unit pg10: rspec-ee unit pg10:
extends: extends:
- .rspec-ee-base-pg10 - .rspec-ee-base-pg10
- .only-master - .only-master
parallel: 7 parallel: 5
rspec-ee integration pg10: rspec-ee integration pg10:
extends: extends:
@ -239,6 +257,7 @@ static-analysis:
dependencies: ["setup-test-env", "compile-assets pull-cache"] dependencies: ["setup-test-env", "compile-assets pull-cache"]
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
parallel: 2
script: script:
- scripts/static-analysis - scripts/static-analysis
cache: cache:
@ -251,13 +270,8 @@ static-analysis:
downtime_check: downtime_check:
extends: extends:
- .rake-exec - .rake-exec
- .only-code-changes - .only:changes-code-backstage
except: - .except:refs-master-tags-stable-deploy
refs:
- master
- tags
variables:
- $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/
stage: test stage: test
needs: ["setup-test-env"] needs: ["setup-test-env"]
dependencies: ["setup-test-env"] dependencies: ["setup-test-env"]

View file

@ -0,0 +1,22 @@
---
# Syncs any changes pushed to a stable branch to the corresponding CE stable
# branch. We run this prior to any tests so that random failures don't prevent a
# sync.
sync-stable-branch:
# We don't need/want any global before/after commands, so we overwrite these
# settings.
image: alpine:edge
stage: sync
# This job should only run on EE stable branches on the canonical GitLab.com
# repository.
only:
variables:
- $CI_SERVER_HOST == "gitlab.com"
refs:
- /^[\d-]+-stable-ee$/@gitlab-org/gitlab
before_script:
- apk add --no-cache --update curl bash
after_script: []
script:
- bash scripts/sync-stable-branch.sh

View file

@ -11,7 +11,7 @@ code_quality:
extends: extends:
- .default-retry - .default-retry
- .default-only - .default-only
- .only-code-changes - .only:changes-code-backstage
stage: test stage: test
image: docker:stable image: docker:stable
allow_failure: true allow_failure: true
@ -50,7 +50,7 @@ sast:
extends: extends:
- .default-retry - .default-retry
- .default-only - .default-only
- .only-code-changes - .only:changes-code-backstage-qa
stage: test stage: test
image: docker:stable image: docker:stable
variables: variables:
@ -132,7 +132,7 @@ dependency_scanning:
extends: extends:
- .default-retry - .default-retry
- .default-only - .default-only
- .only-code-changes - .only:changes-code-backstage-qa
stage: test stage: test
image: docker:stable image: docker:stable
variables: variables:
@ -195,7 +195,7 @@ dast:
extends: extends:
- .default-retry - .default-retry
- .default-only - .default-only
- .only-code-qa-changes - .only:changes-code-qa
- .only-review - .only-review
stage: qa stage: qa
needs: ["review-deploy"] needs: ["review-deploy"]

View file

@ -1,14 +1,8 @@
.except-deploys:
except:
refs:
- /^\d+-\d+-auto-deploy-\d+$/
.review-docker: .review-docker:
extends: extends:
- .default-tags - .default-tags
- .default-retry - .default-retry
- .default-only - .default-only
- .except-deploys
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine
services: services:
- docker:19.03.0-dind - docker:19.03.0-dind
@ -23,10 +17,9 @@
build-qa-image: build-qa-image:
extends: extends:
- .review-docker - .review-docker
- .only-code-qa-changes - .only:variables-canonical-dot-com
only: - .except:refs-deploy
variables: - .only:changes-code-qa
- $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org"
stage: prepare stage: prepare
script: script:
- '[[ ! -d "ee/" ]] || export GITLAB_EDITION="ee"' - '[[ ! -d "ee/" ]] || export GITLAB_EDITION="ee"'
@ -35,14 +28,11 @@ build-qa-image:
- echo "${CI_JOB_TOKEN}" | docker login --username gitlab-ci-token --password-stdin ${CI_REGISTRY} - echo "${CI_JOB_TOKEN}" | docker login --username gitlab-ci-token --password-stdin ${CI_REGISTRY}
- time docker push ${QA_IMAGE} - time docker push ${QA_IMAGE}
schedule:review-cleanup: .base-review-cleanup:
extends: extends:
- .default-tags - .default-tags
- .default-retry - .default-retry
- .default-only - .default-only
- .only-code-qa-changes
- .only-review-schedules
- .except-deploys
stage: prepare stage: prepare
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
allow_failure: true allow_failure: true
@ -55,11 +45,22 @@ schedule:review-cleanup:
script: script:
- ruby -rrubygems scripts/review_apps/automated_cleanup.rb - ruby -rrubygems scripts/review_apps/automated_cleanup.rb
schedule:review-cleanup:
extends:
- .base-review-cleanup
- .only-review-schedules
manual:review-cleanup:
extends:
- .base-review-cleanup
- .only:changes-code-qa
when: manual
.review-build-cng-base: .review-build-cng-base:
extends: extends:
- .default-tags
- .default-retry
- .default-only - .default-only
- .only-code-qa-changes
- .except-deploys
image: ruby:2.6-alpine image: ruby:2.6-alpine
stage: review-prepare stage: review-prepare
before_script: before_script:
@ -74,6 +75,7 @@ review-build-cng:
extends: extends:
- .review-build-cng-base - .review-build-cng-base
- .only-review - .only-review
- .only:changes-code-qa
needs: ["gitlab:assets:compile pull-cache"] needs: ["gitlab:assets:compile pull-cache"]
schedule:review-build-cng: schedule:review-build-cng:
@ -82,26 +84,30 @@ schedule:review-build-cng:
- .only-review-schedules - .only-review-schedules
needs: ["gitlab:assets:compile pull-cache"] needs: ["gitlab:assets:compile pull-cache"]
.review-deploy-base: .review-workflow-base:
extends: extends:
- .default-tags - .default-tags
- .default-retry - .default-retry
- .default-only - .default-only
- .only-code-qa-changes
- .except-deploys
stage: review
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
dependencies: [] dependencies: []
allow_failure: true
variables: variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}" HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}" DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
GITLAB_HELM_CHART_REF: "v2.3.7" # v2.4.4 + two improvements:
# - Allow to pass an EE license when installing the chart: https://gitlab.com/gitlab-org/charts/gitlab/merge_requests/1008
# - Allow to customize the livenessProbe for `gitlab-shell`: https://gitlab.com/gitlab-org/charts/gitlab/merge_requests/1021
GITLAB_HELM_CHART_REF: "6c655ed77e60f1f7f533afb97bef8c9cb7dc61eb"
GITLAB_EDITION: "ce" GITLAB_EDITION: "ce"
environment: environment:
name: review/${CI_COMMIT_REF_NAME} name: review/${CI_COMMIT_REF_NAME}
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN} url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
on_stop: review-stop on_stop: review-stop
.review-deploy-base:
extends: .review-workflow-base
stage: review
allow_failure: true
before_script: before_script:
- '[[ ! -d "ee/" ]] || export GITLAB_EDITION="ee"' - '[[ ! -d "ee/" ]] || export GITLAB_EDITION="ee"'
- export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION) - export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
@ -112,21 +118,13 @@ schedule:review-build-cng:
- install_api_client_dependencies_with_apk - install_api_client_dependencies_with_apk
- source scripts/review_apps/review-apps.sh - source scripts/review_apps/review-apps.sh
script: script:
- date
- check_kube_domain - check_kube_domain
- date
- ensure_namespace - ensure_namespace
- date
- install_tiller - install_tiller
- date
- install_external_dns - install_external_dns
- date
- download_chart - download_chart
- date - date
- deploy || (display_deployment_debug && exit 1) - deploy || (display_deployment_debug && exit 1)
- date
- add_license
- date
artifacts: artifacts:
paths: [review_app_url.txt] paths: [review_app_url.txt]
expire_in: 2 days expire_in: 2 days
@ -136,6 +134,7 @@ review-deploy:
extends: extends:
- .review-deploy-base - .review-deploy-base
- .only-review - .only-review
- .only:changes-code-qa
needs: ["review-build-cng"] needs: ["review-build-cng"]
schedule:review-deploy: schedule:review-deploy:
@ -144,11 +143,11 @@ schedule:review-deploy:
- .only-review-schedules - .only-review-schedules
needs: ["schedule:review-build-cng"] needs: ["schedule:review-build-cng"]
review-stop: .base-review-stop:
extends: extends:
- .review-deploy-base - .review-workflow-base
- .only-review - .only-review
when: manual - .only:changes-code-qa
environment: environment:
action: stop action: stop
variables: variables:
@ -161,24 +160,26 @@ review-stop:
- wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/utils.sh - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/utils.sh
- source utils.sh - source utils.sh
- source review-apps.sh - source review-apps.sh
script:
- delete_release
artifacts:
paths: []
review-cleanup-failed-deployment: review-stop-failed-deployment:
extends: review-stop extends: .base-review-stop
stage: prepare stage: prepare
when: on_success
allow_failure: false
script: script:
- delete_failed_release - delete_failed_release
review-stop:
extends: .base-review-stop
stage: review
when: manual
allow_failure: true
script:
- delete_release
.review-qa-base: .review-qa-base:
extends: extends:
- .review-docker - .review-docker
- .only-review - .only-review
- .only-code-qa-changes - .only:changes-code-qa
stage: qa stage: qa
allow_failure: true allow_failure: true
variables: variables:
@ -223,9 +224,7 @@ review-qa-all:
- gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}" -- --format RspecJunitFormatter --out tmp/rspec-${CI_JOB_ID}.xml --format html --out tmp/rspec.htm --color --format documentation - gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}" -- --format RspecJunitFormatter --out tmp/rspec-${CI_JOB_ID}.xml --format html --out tmp/rspec.htm --color --format documentation
.review-performance-base: .review-performance-base:
extends: extends: .review-docker
- .review-docker
- .only-code-qa-changes
stage: qa stage: qa
allow_failure: true allow_failure: true
before_script: before_script:
@ -248,6 +247,7 @@ review-performance:
extends: extends:
- .review-performance-base - .review-performance-base
- .only-review - .only-review
- .only:changes-code-qa
needs: ["review-deploy"] needs: ["review-deploy"]
dependencies: ["review-deploy"] dependencies: ["review-deploy"]
before_script: before_script:
@ -277,9 +277,8 @@ parallel-spec-reports:
extends: extends:
- .default-tags - .default-tags
- .default-only - .default-only
- .only-code-qa-changes
- .only-review - .only-review
- .except-deploys - .only:changes-code-qa
image: ruby:2.6-alpine image: ruby:2.6-alpine
stage: post-test stage: post-test
dependencies: ["review-qa-all"] dependencies: ["review-qa-all"]
@ -310,18 +309,13 @@ danger-review:
- .default-retry - .default-retry
- .default-cache - .default-cache
- .default-only - .default-only
- .except:refs-master-tags-stable-deploy
image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger
stage: test stage: test
dependencies: [] dependencies: []
only: only:
variables: variables:
- $DANGER_GITLAB_API_TOKEN - $DANGER_GITLAB_API_TOKEN
except:
refs:
- master
variables:
- $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/
- $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/
script: script:
- git version - git version
- node --version - node --version

View file

@ -6,7 +6,8 @@ cache gems:
- .default-retry - .default-retry
- .default-cache - .default-cache
- .default-before_script - .default-before_script
- .only-code-qa-changes - .only:variables-canonical-dot-com
- .only:changes-code-backstage-qa
stage: test stage: test
dependencies: ["setup-test-env"] dependencies: ["setup-test-env"]
needs: ["setup-test-env"] needs: ["setup-test-env"]
@ -21,15 +22,13 @@ cache gems:
refs: refs:
- master - master
- tags - tags
variables:
- $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org"
.minimal-job: .minimal-job:
extends: extends:
- .default-tags - .default-tags
- .default-retry - .default-retry
- .default-only - .default-only
- .only-code-changes - .only:changes-code-backstage
dependencies: [] dependencies: []
gitlab_git_test: gitlab_git_test:

View file

@ -1,7 +1,7 @@
.tests-metadata-state: .tests-metadata-state:
extends: extends:
- .default-only - .default-only
- .only-code-changes - .only:changes-code-backstage
variables: variables:
TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache" TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache"
before_script: before_script:
@ -48,7 +48,7 @@ flaky-examples-check:
- .default-tags - .default-tags
- .default-retry - .default-retry
- .default-only - .default-only
- .only-code-changes - .only:changes-code-backstage
image: ruby:2.6-alpine image: ruby:2.6-alpine
stage: post-test stage: post-test
variables: variables:

View file

@ -29,7 +29,7 @@ Set the title to: `Description of the original issue`
#### Documentation and final details #### Documentation and final details
- [ ] Check the topic on #security to see when the next release is going to happen and add a link to the [links section](#links) - [ ] Check the topic on #releases to see when the next release is going to happen and add a link to the [links section](#links)
- [ ] Add links to this issue and your MRs in the description of the security release issue - [ ] Add links to this issue and your MRs in the description of the security release issue
- [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details) - [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details)
- [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details) - [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details)

View file

@ -34,7 +34,7 @@ All reviewers can help ensure accuracy, clarity, completeness, and adherence to
**3. Maintainer** **3. Maintainer**
1. [ ] Review by assigned maintainer, who can always request/require the above reviews. Maintainer's review can occur before or after a technical writer review. 1. [ ] Review by assigned maintainer, who can always request/require the above reviews. Maintainer's review can occur before or after a technical writer review.
1. [ ] Ensure a release milestone is set and that you merge the equivalent EE MR before the CE MR if both exist. 1. [ ] Ensure a release milestone is set.
1. [ ] If there has not been a technical writer review, [create an issue for one using the Doc Review template](https://gitlab.com/gitlab-org/gitlab/issues/new?issuable_template=Doc%20Review). 1. [ ] If there has not been a technical writer review, [create an issue for one using the Doc Review template](https://gitlab.com/gitlab-org/gitlab/issues/new?issuable_template=Doc%20Review).
/label ~documentation /label ~documentation

View file

@ -416,9 +416,6 @@ linters:
- 'app/views/u2f/_register.html.haml' - 'app/views/u2f/_register.html.haml'
- 'app/views/users/_deletion_guidance.html.haml' - 'app/views/users/_deletion_guidance.html.haml'
- 'ee/app/views/admin/_namespace_plan_info.html.haml' - 'ee/app/views/admin/_namespace_plan_info.html.haml'
- 'ee/app/views/admin/application_settings/_elasticsearch_form.html.haml'
- 'ee/app/views/admin/application_settings/_slack.html.haml'
- 'ee/app/views/admin/application_settings/_snowplow.html.haml'
- 'ee/app/views/admin/application_settings/_templates.html.haml' - 'ee/app/views/admin/application_settings/_templates.html.haml'
- 'ee/app/views/admin/audit_logs/index.html.haml' - 'ee/app/views/admin/audit_logs/index.html.haml'
- 'ee/app/views/admin/dashboard/stats.html.haml' - 'ee/app/views/admin/dashboard/stats.html.haml'
@ -495,7 +492,6 @@ linters:
- 'ee/app/views/projects/services/prometheus/_metrics.html.haml' - 'ee/app/views/projects/services/prometheus/_metrics.html.haml'
- 'ee/app/views/projects/settings/slacks/edit.html.haml' - 'ee/app/views/projects/settings/slacks/edit.html.haml'
- 'ee/app/views/shared/_additional_email_text.html.haml' - 'ee/app/views/shared/_additional_email_text.html.haml'
- 'ee/app/views/shared/_geo_info_modal.html.haml'
- 'ee/app/views/shared/_mirror_update_button.html.haml' - 'ee/app/views/shared/_mirror_update_button.html.haml'
- 'ee/app/views/shared/_shared_runners_minutes_limit.html.haml' - 'ee/app/views/shared/_shared_runners_minutes_limit.html.haml'
- 'ee/app/views/shared/audit_events/_event_table.html.haml' - 'ee/app/views/shared/audit_events/_event_table.html.haml'

View file

@ -16,10 +16,25 @@
# Uncomment the following lines to make the configuration take effect. # Uncomment the following lines to make the configuration take effect.
PreCommit: PreCommit:
AuthorName:
enabled: false
EsLint:
enabled: true
# https://github.com/sds/overcommit/issues/338
command: './node_modules/eslint/bin/eslint.js'
HamlLint:
enabled: true
MergeConflicts:
enabled: true
exclude:
- '**/conflict/file_spec.rb'
- '**/git/conflict/parser_spec.rb'
# prettier? https://github.com/sds/overcommit/issues/614 https://github.com/sds/overcommit/issues/390#issuecomment-495703284
RuboCop: RuboCop:
enabled: true enabled: true
# on_warn: fail # Treat all warnings as failures # on_warn: fail # Treat all warnings as failures
# ScssLint:
enabled: true
#PostCheckout: #PostCheckout:
# ALL: # Special hook name that customizes all hooks of this type # ALL: # Special hook name that customizes all hooks of this type
# quiet: true # Change all post-checkout hooks to only display output on failure # quiet: true # Change all post-checkout hooks to only display output on failure

View file

@ -56,7 +56,7 @@ Style/FrozenStringLiteralComment:
- 'qa/**/*' - 'qa/**/*'
- 'rubocop/**/*' - 'rubocop/**/*'
- 'scripts/**/*' - 'scripts/**/*'
- 'spec/**/*' - 'spec/lib/gitlab/**/*'
RSpec/FilePath: RSpec/FilePath:
Exclude: Exclude:
@ -297,3 +297,6 @@ Graphql/Descriptions:
Include: Include:
- 'app/graphql/**/*' - 'app/graphql/**/*'
- 'ee/app/graphql/**/*' - 'ee/app/graphql/**/*'
RSpec/AnyInstanceOf:
Enabled: false

View file

@ -401,13 +401,6 @@ Rails/FilePath:
Rails/HasManyOrHasOneDependent: Rails/HasManyOrHasOneDependent:
Enabled: false Enabled: false
# Offense count: 40
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: numeric, symbolic
Rails/HttpStatus:
Enabled: false
# Offense count: 2 # Offense count: 2
# Configuration parameters: Include. # Configuration parameters: Include.
# Include: app/controllers/**/*.rb # Include: app/controllers/**/*.rb

View file

@ -1,10 +1,21 @@
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 12.4.5 ## 12.5.3
### Performance (1 change)
- Geo - Improve query performance to determine job artifacts to sync when selective sync is enabled. !19583
### Other (1 change)
- Geo - Does not schedule duplicated jobs while backfilling uploads, LFS objects and job artifacts. !20324
## 12.5.2
- No changes. - No changes.
## 12.4.4 ## 12.5.1
### Security (6 changes) ### Security (6 changes)
@ -16,12 +27,90 @@ Please view this file on the master branch, on stable branches it's out of date.
- Prevent IDOR when adding users to protected environments. - Prevent IDOR when adding users to protected environments.
## 12.4.3 ## 12.5.0
### Fixed (2 changes) ### Security (5 changes)
- Fixes a Open Redirect issue in `InternalRedirect`.
- Filter out packages the user does'nt have permission to see at group level.
- Do not show private cross references in epic notes.
- Redact search results based on Ability.allowed?.
- Do not index system notes for issue update.
### Removed (2 changes, 1 of them is from the community)
- Remove the Geo Clone Modal. !18897 (Zack Cuddy)
- Remove Pendo Snippet. !19400
### Fixed (17 changes)
- Fix notification button size in notification settings. !16672
- Don't store full blob path in ES filename field. !18470
- Add messages to warn and stop users when attempting to change the path of projects with NPM packages. !18515
- Pass pipeline variables when expanding Bridge downstream variables. !18875
- Fix equality operator for Prometheus alerts. !18919
- Fix rake task to rollback Geo migrations. !18975
- Default current user to mirror user when creating pipelines for GitHub pull requests. !19072
- Fix overlapping `Skip Trial` block. !19218
- Fix Dependency List is empty if last pipeline is retried. !19241
- SCIM pagination startIndex handles string input. !19331
- Display packages with multiple licenses. !19333
- Expose commit sha on Vulnerabilities::Occurrence. !19668
- Fix admin welcome image not found. !19676 - Fix admin welcome image not found. !19676
- Revert ES support for public/internal project snippets. !19715 - Revert ES support for public/internal project snippets. !19715
- Updated View documentation link on cluster page. !19780
- Enable pod logs nav menu only for maintainers in projects with k8s environments. !19927
- Hide labels from issue board cards. !20072
### Changed (13 changes)
- Inherit children epics start and due dates. !14366
- Update the frontend diffing code to support v2 license scan reports. !18105
- Implement pod logs page using Vue. !18567
- Move DAST reports logic for the Merge Request widget to the backend. !18660
- Add created_before/after filter to audit events. !19035
- Get rid of unnecessary duplication of alerts title from Alert Details. !19214
- Hashed storage is now a requirement for Design Management. !19259
- Expose epic in issues API. !19300
- SCIM GET /Users supports requests without a filter. !19421
- Enable Cycle Analytics Feature by default. !19484
- Enforce a max size accepted for sentry issues list. !19649
- Limit input size for Prometheus alert JSON payload. !19940
- Adds in a URL field for DAST reports modal data. !20162
### Performance (2 changes)
- Fix new project page load performance. !18180
- Geo - Improve query performance to determine LFS objects to sync when selective sync is enabled. !19051
### Added (17 changes, 1 of them is from the community)
- Add filter for dismissed vulnerabilities on security dashboards. !16692
- Data API endpoint for tasks by type chart within the analytics workspace. !17944
- Hide labels from issue board cards. !18533
- Skip Onboarding feedback when tracking is disabled. !18671
- API endpoint to list the packages of a group. !18871
- Allow to create epics with GraphQL. !19030
- CI_JOB_TOKEN can be accepted with 'Bearer ' prefix to allow for NPM registry usage. !19059
- Add issue IID to a title of generic alerts with a default title. !19086
- Update sidebar to differentiate between groups, subgroups, and projects. !19158
- SCIM can be used to manage group membership. !19329
- Expose number of sub-epics and epic issues in GraphQL API. !19450
- Add logs menu item to the sidebar. !19471
- Add public API for Feature Flags. !19547
- Ignore project_ci_cd_settings.merge_trains_enabled column. !19695
- Add a usage ping metric for number of activated Alert Services. !19765
- New discussions on designs will generate a system note on the issue. !19990
- Expose SHA of squashed commit via API when fast-forward merge is enabled. (minghuan lei)
### Other (6 changes, 1 of them is from the community)
- Migrated contributors charts to echarts. !16677
- Added autogenerated Markdown support for Vulnerability title and description. !18283
- Rename user_id to author_id in design_management_versions table. !18506
- Revert notification for updated privacy policy. !18900
- Remove plaintext tokens for feature flags clients. !18923
- Remove IIFEs from jira_connect.js file. !19248 (nuwe1)
## 12.4.2 ## 12.4.2
@ -4205,7 +4294,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Show hook errors for fast-forward merges. !1375 - Show hook errors for fast-forward merges. !1375
- Allow all parameters of group webhooks to be set through the UI. !1376 - Allow all parameters of group webhooks to be set through the UI. !1376
- Fix Elasticsearch queries when a group_id is specified. !1423 - Fix Elasticsearch queries when a group_id is specified. !1423
- Check the right index mapping based on Rails environment for rake gitlab:elastic:add_feature_visiblity_levels_to_project. !1473 - Check the right index mapping based on Rails environment for rake gitlab:elastic:add_feature_visibility_levels_to_project. !1473
- Fix issues with another milestone that has a matching list label could not be added to a board. - Fix issues with another milestone that has a matching list label could not be added to a board.
- Only admins or group owners can set LDAP overrides. - Only admins or group owners can set LDAP overrides.
- Add support for load balancing database queries. - Add support for load balancing database queries.

View file

@ -2,38 +2,397 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 12.4.6 ## 12.5.4
- No changes. - No changes.
## 12.4.5 ## 12.5.3
- No changes. ### Fixed (4 changes)
## 12.4.4 - Fix project creation with templates using /projects/user/:id API. !20590
- Fix merging merge requests from push options. !20639
- Fix Crossplane help link in cluster applications page. !20668
- Fixes job log not scrolling to the bottom.
### Security (12 changes) ### Changed (1 change)
- Flatten exception details in API and controller logs. !20434
## 12.5.2
### Security (1 change)
- Fix 500 error caused by invalid byte sequences in links.
## 12.5.1
### Security (11 changes)
- Do not create todos for approvers without access. !1442 - Do not create todos for approvers without access. !1442
- Limit potential for DNS rebind SSRF in chat notifications. - Hide commit counts from guest users in Cycle Analytics.
- Encrypt application setting tokens. - Encrypt application setting tokens.
- Update Workhorse and Gitaly to fix a security issue. - Update Workhorse and Gitaly to fix a security issue.
- Add maven file_name regex validation on incoming files. - Add maven file_name regex validation on incoming files.
- Hide commit counts from guest users in Cycle Analytics.
- Check permissions before showing a forked project's source. - Check permissions before showing a forked project's source.
- Fix 500 error caused by invalid byte sequences in links. - Limit potential for DNS rebind SSRF in chat notifications.
- Ensure are cleaned by ImportExport::AttributeCleaner. - Ensure are cleaned by ImportExport::AttributeCleaner.
- Remove notes regarding Related Branches from Issue activity feeds for guest users. - Remove notes regarding Related Branches from Issue activity feeds for guest users.
- Escape namespace in label references to prevent XSS. - Escape namespace in label references to prevent XSS.
- Add authorization to using filter vulnerable in Dependency List. - Add authorization to using filter vulnerable in Dependency List.
## 12.4.3 ## 12.5.0
### Fixed (2 changes) ### Security (15 changes)
- Enable the HttpOnly flag for experimentation_subject_id cookie. !19189
- Update incrementing of failed logins to be thread-safe. !19614
- Sanitize all wiki markup formats with GitLab sanitization pipelines.
- Sanitize search text to prevent XSS.
- Remove deploy access level when project/group link is deleted.
- Mask sentry auth token in Error Tracking dashboard.
- Return 404 on LFS request if project doesn't exist.
- Don't leak private members in project member autocomplete suggestions.
- Require Maintainer permission on group where project is transferred to.
- Don't allow maintainers of a target project to delete the source branch of a merge request from a fork.
- Disallow unprivileged users from commenting on private repository commits.
- Analyze incoming GraphQL queries and check for recursion.
- Show cross-referenced label and milestones in issues' activities only to authorized users.
- Do not display project labels that are not visible for user accessing group labels.
- Standardize error response when route is missing.
### Fixed (99 changes, 14 of them are from the community)
- Fix incorrect selection of custom templates. !17205
- Smaller width for design comments layout, truncate image title. !17547
- Correctly cleanup orphan job artifacts. !17679 (Adam Mulvany)
- Add Infinite scroll to Add Projects modal in the operations dashboard. !17842
- Allow emojis to be linkable. !18014
- Enable image link and lazy loading in AsciiDoc documents. !18164 (Guillaume Grossetie)
- Expose prometheus status to monitor dashboard. !18289
- Time limit the database lock when rebasing a merge request. !18481
- Fix missing admin mode UI buttons on bigger screen sizes. !18585 (Diego Louzán)
- Abort only MWPS when FF only merge is impossible. !18591
- Remove pointer cursor from MemoryUsage chart on MR widget deployment. !18599
- Fix keyboard shortcuts in header search autocomplete. !18685
- Fix empty chart in collapsed sections. !18699
- Fix error when viewing group billing page. !18740
- Fix query validation in custom metrics form. !18769
- Fix Gitaly call duration measurements. !18785
- Resolve Error when uploading a few designs in a row. !18811
- Block MR with OMIPS on skipped pipelines. !18838
- Pipeline vulnerability dashboard sort vulnerabilities by severity then confidence. !18863
- Remove empty Github service templates from database. !18868
- Fix broken images when previewing markdown files in Web IDE. !18899
- fixed #27164 Image cannot be collapsed on merge request changes tab. !18917 (Jannik Lehmann)
- Let ANSI \r code replace the current job log line. !18933
- Fix serverless function descriptions not showing on Knative 0.7. !18973
- Fix "project or group was moved" alerts showing up in the wrong pages. !18985
- Add missing breadcrumb in Project > Settings > Integrations. !18990
- Fixed admin geo collapsed sidebar fly out not showing. !19012
- Serialize short sha as nil if head commit is blank. !19014
- Add max width on manifest file attachment input. !19028
- Do not generate To-Dos additional when editing group mentions. !19037
- Fix previewing quick actions for epics. !19042
- Fix errors in GraphQL Todos API due to missing TargetTypeEnum values. !19052
- Hashed Storage Migration: Handle failed attachment migrations with existing target path. !19061
- Set shorter TTL for all unauthenticated requests. !19064
- Fix Todo IDs in GraphQL API. !19068
- Triggers the correct endpoint on licence approval. !19078
- Fix search button height on 404 page. !19080
- Fix Kubernetes help text link. !19121
- Make `jobs/request` to be resillient. !19150
- Disable pull mirror if repository is in read-only state. !19182
- Only enable protected paths for POST requests. !19184 - Only enable protected paths for POST requests. !19184
- Enforce default, global project and snippet visibilities. !19188
- Make Bitbucket Cloud superseded pull requests as closed. !19193
- Fix crash when docker fails deleting tags. !19208
- Fix environment name in rollback dialog. !19209
- Fixed a typo in the "Keyboard Shortcuts" pop-up. !19217 (Manuel Stein)
- Fix unable to expand or collapse files in merge request by clicking caret. !19222 (Brian T)
- Allow release block edit button to be visible. !19226
- Fix double escaping in /tableflip quick action. !19271 (Brian T)
- Add missing bottom padding in CI/CD settings. !19284 (George Tsiolis)
- Prevents console warning on design upload. !19297
- Resolve: Web IDE does not create POSIX Compliant Files. !19339
- Use initial commit SHA instead of branch id to request IDE files and contents. !19348 (David Palubin)
- Resolve: Web IDE Throws Error When Viewing Diff for Renamed Files. !19348
- Fix project service API 500 error. !19367
- Fix cluster feature highlight popover image. !19372
- Fix template selector filename bug. !19376
- Fixes mobile styling issues on security modals. !19391
- Only move repos for legacy project storage. !19410
- Show correct total number of commit diff's changes. !19424
- Increase the timeout for GitLab-managed cert-manager installation to 90 seconds (was 30 seconds). !19447
- Fix uninitialized constant SystemDashboardService. !19453
- Properly handle exceptions in StuckCiJobsWorker. !19465
- Fix user popover not being displayed when the user has a status message. !19519
- Update omniauth_openid_connect to v0.3.3. !19525
- Fix project clone dropdown button width. !19551 (George Tsiolis)
- Do not escape HTML tags in Ansi2json as they are escaped in the frontend. !19610
- [Geo] Fix: undefined Gitlab::BackgroundMigration::PruneOrphanedGeoEvents. !19638
- Revert btn-xs styling in projects scss. !19640
- Fix canary badge and favicon inconsistency. !19645
- Use fingerprint when comparing security reports in MR widget. !19654
- Update GCP credit URLs. !19683
- Update squash_commit_sha only on successful merge. !19688
- Fix import of snippets having `award_emoji` (Project Export/Import). !19690
- Allow admins to administer personal snippets. !19693 (Oren Kanner)
- Re-add missing file sizes in 2-Up diff file viewer. !19710
- Fix checking task item when previous tasks contain only spaces. !19724
- Fix Bitbucket Cloud importer pull request state. !19734 - Fix Bitbucket Cloud importer pull request state. !19734
- Fix merge train is not refreshed when the system aborts/drops a merge request. !19763
- Resolve Hide Delete selected in designs when viewing an old version. !19889
- Use new trial registration URL in billing. !19978
- Helm v2.16.1. !19981
- Ensure milestone titles are never empty. !19985
- Remove unused image/screenshot. !20030 (Lee Tickett)
- Remove local qualifier from geo sync indicators. !20034 (Lee Tickett)
- Fixed the scale of embedded videos to fit the page. !20056
- Fix broken monitor cluster health dashboard. !20120
- Fix expanding collapsed threads when reference link clicked. !20148
- Fix sub group export to export direct children. !20172
- Remove update hook from date filter to prevent js from getting stuck. !20215
- Prevent Dropzone.js initialisation error by checking target element existence. !20256 (Fabio Huser)
- Fix style reset in job log when empty ANSI sequence is encoutered. !20367
- Add productivity analytics merge date filtering limit. !32052
- Fix productivity analytics listing with multiple labels. !33182
- Fix closed board list loading issue.
- Apply correctly the limit of 10 designs per upload.
- Only allow confirmed users to run pipelines.
- Fix scroll to bottom with new job log.
- Fixed protected branches flash styling.
### Deprecated (2 changes)
- Ignore deprecated column and remove references to it. !18911
- Move some project routes under - scope. !19954
### Changed (56 changes, 6 of them are from the community)
- Upgrade design/copy for issue weights locked feature. !17352
- Reduce new MR page redundancy by moving the source/target branch selector to the top. !17559
- Replace raven-js with @sentry/browser. !17715
- Ask if the user is setting up GitLab for a company during signup. !17999
- When a user views a file's blame or blob and switches to a branch where the current file does not exist, they will now be redirected to the root of the repository. !18169 (Jesse Hall @jessehall3)
- Propagate custom environment variables to SAST analyzers. !18193
- Fix any approver project rule records. !18265
- Minor UX improvements to Environments Dashboard page. !18280
- Reduce the allocated IP for Cluster and Services. !18341
- Update flash messages color sitewide. !18369
- Add modsecurity template for ingress-controller. !18485
- Hide projects without access to admin user when admin mode is disabled. !18530 (Diego Louzán)
- Update Runners Settings Text + Link to Docs. !18534
- Store Zoom URLs in a table rather than in the issue description. !18620
- Improve admin dashboard features. !18666
- Drop `id` column from `ci_build_trace_sections` table. !18741
- Truncate recommended branch name to a sane length. !18821
- Add support for YAML anchors in CI scripts. !18849
- Save dashboard changes by the user into the vuex store. !18862
- Update expired trial status copy. !18962
- Can directly add approvers to approval rule. !18965
- Rename Vulnerabilities API to Vulnerability Findings API. !19029
- Improve clarity of text for merge train position. !19031
- Updated Auto-DevOps to kubectl v1.13.12 and helm v2.15.1. !19054 (Leo Antunes)
- Refactor maximum user counts in license. !19071 (briankabiro)
- Change return type of getDateInPast to Date. !19081
- Show approval required status in license compliance. !19114
- Handle new Container Scanning report format. !19123
- Allow container scanning to run offline by specifying the Clair DB image to use. !19161
- Add maven cli opts flag to maven security analyzer (part of dependency scanning). !19174
- Added report_type attribute to Vulnerabilities. !19179
- Migrate enabled flag on grafana_integrations table. !19234
- Improve handling of gpg-agent processes. !19311
- Update help text of "Tag name" field on Edit Release page. !19321
- Add user filtering to abuse reports page. !19365
- Move add license button to project buttons. !19370
- Update to Mermaid v8.4.2 to support more graph types. !19444
- Move release meta-data into footer on Releases page. !19451
- Expose subscribed field in issue lists queried with GraphQL. !19458 (briankabiro)
- [Geo] Fix: rake gitlab:geo:check on the primary is cluttered. !19460
- Hide trial banner for namespaces with expired trials. !19510
- Hide repeated trial offers on self-hosted instances. !19511
- Add loading icon to error tracking settings page. !19539
- Upgrade to Gitaly v1.71.0. !19611
- Make role required when editing profile. !19636
- Made `name` optional parameter of Release entity. !19705
- Vulnerabilities history chart - use sparklines. !19745
- Add event tracking to container registry. !19772
- Update SaaS trial header to include the tier Gold. !19970
- Update start a trial option in top right drop down to include Gold. !19971
- Improve merge request description placeholder. !20032 (Jacopo Beschi @jacopo-beschi)
- Add backtrace to production_json.log. !20122
- Change the default concurrency factor of merge train to 20. !20201
- Upgrade to Gitaly v1.72.0.
- Require explicit null parameters to remove pages domain certificate and allow to use Let's Encrypt certificates through API.
- Replace wording trace with log.
### Performance (13 changes)
- Record latencies for Sidekiq failures. !18909
- Fix N+1 for group container repositories view. !18979
- Do not render links in commit message on blame page. !19128
- Puma only: database connection pool now always >= number of worker threads. !19286
- Run check_mergeability only if merge status requires it. !19364
- Execute limited request for diff commits instead of preloading. !19485
- Improve performance of admin/abuse_reports page. !19630
- Remove N+1 DB calls from branches API. !19661
- Improve performance of linking LFS objects during import. !19709
- Optimize MergeRequest#mergeable_discussions_state? method. !19988
- Add index for unauthenticated requests to projects API default endpoint. !19989
- Add index for authenticated requests to projects API default endpoint. !19993
- Increase PumaWorkerKiller memory limit in development environment. !20039
### Added (83 changes, 8 of them are from the community)
- Adds Application Settings and ui settings in the integration admin area for Pendo. !15086
- Add endpoint for a group's vulnerable projects. !15317
- Added new chart component to display an anomaly boundary. !16530
- Add links to associated releases on the Milestones page. !16558
- Merge Details Page and Edit Page for Page Domains. !16687
- Share groups with groups. !17117
- Add links to associated release(s) to the milestone detail page. !17278
- New group path uniqueness check. !17394
- Unify html email layout for member html emails. !17699 (Diego Louzán)
- The Security Dashboard displays DAST vulnerabilities for all the scanned sites, not just the first. !17779
- Create table for elastic stack. !18015
- Allow to define a default CI configuration path for new projects. !18073 (Mathieu Parent)
- Issues queried in GraphQL now sortable by due date. !18094
- Add cleanup status to clusters. !18144
- Added Tests tab to pipeline detail that contains a UI for browsing test reports produced by JUnit. !18255
- Users can verify SAML configuration and view SamlResponse XML. !18362
- Support Enable/Disable operations in Feature Flag API. !18368
- Expose arbitrary job artifacts in Merge Request widget. !18385
- Add project option for deleting source branch. !18408 (Zsolt Kovari)
- Adds ability to set management project for cluster via API. !18429
- Close issues on Prometheus alert recovery. !18431
- Add ApplicationSetting for snowplow_iglu_registry_url. !18449
- Allow Grafana charts to be embedded in Gitlab Flavored Markdown. !18486
- Mark todo done by GraphQL API. !18581
- Create a users_security_dashboard_projects table to store the projects a user has added to their personal security dashboard. !18708
- New API endpoint for creating anonymous merge request discussions from Visual Review Tools. !18710
- Enable the color chip in AsciiDoc documents. !18723
- Add prevent_ldap_sign_in option so LDAP can be used exclusively for sync. !18749
- Show inherited group variables in project view. !18759
- Add "release" filter to issue search page. !18761
- Search list of Sentry errors by title in Gitlab. !18772
- Add migrations and changes for soft-delete for projects. !18791
- Support for Crossplane as a managed app. !18797 (Mahendra Bagul)
- Bump Auto-Deploy image to v0.3.0. !18809
- Set X-GitLab-NotificationReason header if notification reason is explicit subscription. !18812
- Add issues, MRs, participants, and labels tabs in group milestone page. !18818
- Add ability to reorder projects on operations dashboard. !18855
- Make `Job`, `Bridge` and `Default` inheritable. !18867
- Show epic events on group activity page. !18869
- Detail view of Sentry error in GitLab. !18878
- Expose mergeable state of a merge request. !18888 (briankabiro)
- Add ability to select a Cluster management project. !18928
- Add a Slack slash command to add a comment to an issue. !18946
- Added installation commands for npm and yarn packages to package detail page. !18999
- Show start and end dates in Epics list page. !19006
- Populate new pipeline CI vars from params. !19023
- Add warnings about pages access control settings. !19067
- Graphql mutation for (un)subscribing to an epic. !19083
- API for stack trace & detail view of Sentry error in GitLab. !19137
- Add grafana integration active status checkbox. !19255
- GraphQL: Add Merge Request milestone mutation. !19257
- Add MergeRequestSetAssignees GraphQL mutation. !19272
- Add edit button to metrics dashboard. !19279
- Add "release" filter to merge request search page. !19315
- Add dead jobs to Sidekiq metrics API. !19350 (Marco Peterseil)
- Add pipeline information to dependency list header. !19352
- Build CI cache key from commit SHAs that changed given files. !19392
- Adding support for searching tags using '^' and '$'. !19435 (Cauhx Milloy)
- Sentry error stacktrace. !19492
- Add an `error_code` attribute to the API response when a cherry-pick or revert fails. !19518
- Add documentation for sign-in application setting. !19561 (Horatiu Eugen Vlad)
- Create AWS EKS cluster. !19578
- Add modsecurity logging sidecar to ingress controller. !19600
- Add start a trial option in the top-right user dropdown. !19632
- Manage and display labels from epic in the GraphQL API. !19642
- Allow order_by updated_at in Deployments API. !19658
- Add can_edit and project_blob_path to metrics_dashboard endpoint. !19663
- Add usage ping data for project services. !19687
- Graphql query for issues can now be sorted by relative_position. !19713
- Add API endpoint to trigger Group Structure Export. !19779
- Show Tree UI containing child Epics and Issues within an Epic. !19812
- Enable environments dashboard by default. !19838
- Update the DB schema to allow linking between Vulnerabilities and Issues. !19852
- Add Group Audit Events API. !19868
- Adds a copy button next to package metadata on the details page. !19881
- GraphQL: Create MR mutations needed for the sidebar. !19913
- Add id_before, id_after filter param to projects API. !19949
- Add modsecurity feature flag to usage ping. !20194
- Specify management project for a Kubernetes cluster. !20216
- Upgrade pages to 1.12.0. !20217
- Support template_project_id parameter in project creation API. !20258
- Add heatmap chart support. !32424
- Add template for Serverless Framework/JS. !33805
### Other (59 changes, 26 of them are from the community)
- Add EKS cluster count to usage data. !17059
- Track the starting and stopping of the current signup flow and the experimental signup flow. !17521
- Attribute Sidekiq workers according to their workloads. !18066
- Add ApplicationSetting entries for EKS integration. !18307
- Geo: Add resigns-related fields to Geo Node Status table. !18379
- Allow adding requests to performance bar manually. !18464
- Removes `export_designs` feature flag. !18507 (nate geslin)
- Update AWS SDK to 2.11.374. !18601
- Remove required dependecy of Postgresql for Gitaly. !18659
- Add deployment_merge_requests table. !18755
- Bump Gitaly to 1.70.0 and remove cache invalidation feature flag. !18766
- Update gRPC to v1.24.0. !18837
- Update GitLab Runner Helm Chart to 0.10.0. !18879
- Adds a Sidekiq queue duration metric. !19005
- Create explicit Default and Free plans. !19033
- Improve instance mirroring help text. !19047
- Add Codesandbox metrics to usage ping. !19075
- Add internal_socket_dir to gitaly config in setup helper. !19170
- Use Rails 5.2 Redis caching store. !19202
- Update GitLab Runner Helm Chart to 0.10.1. !19232
- Rename snowplow_site_id to snowplow_app_id in application_settings table. !19252
- Removed IIFEs from network.js file. !19254 (nuwe1)
- Remove IIFEs from project_select.js. !19288 (minghuan lei)
- Remove IIFEs from merge_request.js. !19294 (minghuan lei)
- Make snippet list easier to scan. !19490
- Removed IIFEs from image_file.js. !19548 (nuwe1)
- Fix api docs for deleting project cluster. !19558
- Change blob edit view button styling. !19566
- Include exception and backtrace in API logs. !19671
- Add index on marked_for_deletion_at in projects table. !19788
- Visual design for edit buttons in blob view. !19932
- Refactor disabled sidebar notifications to Vue. !20007 (minghuan lei)
- Remove IIFEs from branch_graph.js. !20008 (minghuan lei)
- Remove IIFEs from new_branch_form.js. !20009 (minghuan lei)
- Remove duplication from slugifyWithUnderscore function. !20016 (Arun Kumar Mohan)
- Update registry.gitlab.com/gitlab-org/security-products/codequality to 12-5-stable. !20046 (Takuya Noguchi)
- Add mb-2 class to global alerts. !20081 (2knal)
- Remove var from syntax_highlight_spec.js. !20086 (Lee Tickett)
- Remove var from merge_request_tabs_spec.js. !20087 (Lee Tickett)
- Remove var from bootstrap_jquery_spec.js. !20089 (Lee Tickett)
- Remove var from project_select.js. !20091 (Lee Tickett)
- Remove var from new_commit_form.js. !20095 (Lee Tickett)
- Remove var from issue.js. !20098 (Lee Tickett)
- Remove var from new_branch_form.js. !20099 (Lee Tickett)
- Remove var from tree.js. !20103 (Lee Tickett)
- Remove var from line_highlighter.js. !20108 (Lee Tickett)
- Remove var from preview_markdown.js. !20115 (Lee Tickett)
- remove all references of BoardService in boards_selector.vue. !20147 (nuwe1)
- Remove all references to BoardsService in index.vue. !20152 (nuwe1)
- Remove var from labels_select.js. !20153 (Lee Tickett)
- Remove all reference to BoardService in board_form.vue. !20158 (nuwe1)
- Remove calendar icon from personal access tokens. !20183
- Move margin-top from flash container to flash. !20211
- Bump Auto DevOps deploy image to v0.7.0. !20250
- Make 'Sidekiq::Testing.fake!' mode as default. !31662 (@blackst0ne)
- Replace task-done icon with list-task icon to better align with other toolbar list icons.
- Dependency Scanning template that doesn't rely on Docker-in-Docker.
- Adding dropdown arrow icon and updated text alignment.
- Change selects from default browser style to custom style.
## 12.4.2 ## 12.4.2

View file

@ -1 +1 @@
1.67.1 1.72.1

View file

@ -1 +1 @@
1.4.0 1.5.0

View file

@ -1 +1 @@
1.11.0 1.12.0

47
Gemfile
View file

@ -8,12 +8,12 @@ gem 'bootsnap', '~> 1.4'
gem 'nakayoshi_fork', '~> 0.0.4' gem 'nakayoshi_fork', '~> 0.0.4'
# Responders respond_to and respond_with # Responders respond_to and respond_with
gem 'responders', '~> 2.0' gem 'responders', '~> 3.0'
gem 'sprockets', '~> 3.7.0' gem 'sprockets', '~> 3.7.0'
# Default values for AR models # Default values for AR models
gem 'default_value_for', '~> 3.2.0' gem 'default_value_for', '~> 3.3.0'
# Supported DBs # Supported DBs
gem 'pg', '~> 1.1' gem 'pg', '~> 1.1'
@ -42,7 +42,7 @@ gem 'omniauth-shibboleth', '~> 1.3.0'
gem 'omniauth-twitter', '~> 1.4' gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.3.3' gem 'omniauth-authentiq', '~> 0.3.3'
gem 'omniauth_openid_connect', '~> 0.3.1' gem 'omniauth_openid_connect', '~> 0.3.3'
gem "omniauth-ultraauth", '~> 0.0.2' gem "omniauth-ultraauth", '~> 0.0.2'
gem 'omniauth-salesforce', '~> 1.0.5' gem 'omniauth-salesforce', '~> 1.0.5'
gem 'rack-oauth2', '~> 1.9.3' gem 'rack-oauth2', '~> 1.9.3'
@ -64,7 +64,7 @@ gem 'u2f', '~> 0.2.1'
# GitLab Pages # GitLab Pages
gem 'validates_hostname', '~> 1.0.6' gem 'validates_hostname', '~> 1.0.6'
gem 'rubyzip', '~> 1.2.2', require: 'zip' gem 'rubyzip', '~> 1.3.0', require: 'zip'
# GitLab Pages letsencrypt support # GitLab Pages letsencrypt support
gem 'acme-client', '~> 2.0.2' gem 'acme-client', '~> 2.0.2'
@ -72,7 +72,7 @@ gem 'acme-client', '~> 2.0.2'
gem 'browser', '~> 2.5' gem 'browser', '~> 2.5'
# GPG # GPG
gem 'gpgme', '~> 2.0.18' gem 'gpgme', '~> 2.0.19'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
@ -136,7 +136,7 @@ gem 'faraday_middleware-aws-signers-v4'
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 2.8' gem 'html-pipeline', '~> 2.8'
gem 'deckar01-task_list', '2.2.0' gem 'deckar01-task_list', '2.2.1'
gem 'gitlab-markup', '~> 1.7.0' gem 'gitlab-markup', '~> 1.7.0'
gem 'github-markup', '~> 1.7.0', require: 'github/markup' gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'commonmarker', '~> 0.17' gem 'commonmarker', '~> 0.17'
@ -151,7 +151,7 @@ gem 'asciidoctor-plantuml', '0.0.9'
gem 'rouge', '~> 3.11.0' gem 'rouge', '~> 3.11.0'
gem 'truncato', '~> 0.7.11' gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0' gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.10.4' gem 'nokogiri', '~> 1.10.5'
gem 'escape_utils', '~> 1.1' gem 'escape_utils', '~> 1.1'
# Calendar rendering # Calendar rendering
@ -159,6 +159,7 @@ gem 'icalendar'
# Diffs # Diffs
gem 'diffy', '~> 3.1.0' gem 'diffy', '~> 3.1.0'
gem 'diff_match_patch', '~> 0.1.0'
# Application server # Application server
gem 'rack', '~> 2.0.7' gem 'rack', '~> 2.0.7'
@ -175,7 +176,7 @@ group :puma do
end end
# State machine # State machine
gem 'state_machines-activerecord', '~> 0.5.1' gem 'state_machines-activerecord', '~> 0.6.0'
# Issue tags # Issue tags
gem 'acts-as-taggable-on', '~> 6.0' gem 'acts-as-taggable-on', '~> 6.0'
@ -259,9 +260,6 @@ gem 'loofah', '~> 2.2'
# Working with license # Working with license
gem 'licensee', '~> 8.9' gem 'licensee', '~> 8.9'
# Protect against bruteforcing
gem 'rack-attack', '~> 4.4.1'
# Ace editor # Ace editor
gem 'ace-rails-ap', '~> 4.1.0' gem 'ace-rails-ap', '~> 4.1.0'
@ -293,10 +291,13 @@ gem 'base32', '~> 0.3.0'
gem "gitlab-license", "~> 1.0" gem "gitlab-license", "~> 1.0"
# Protect against bruteforcing
gem 'rack-attack', '~> 6.2.0'
# Sentry integration # Sentry integration
gem 'sentry-raven', '~> 2.9' gem 'sentry-raven', '~> 2.9'
gem 'premailer-rails', '~> 1.9.7' gem 'premailer-rails', '~> 1.10.3'
# LabKit: Tracing and Correlation # LabKit: Tracing and Correlation
gem 'gitlab-labkit', '~> 0.5' gem 'gitlab-labkit', '~> 0.5'
@ -331,7 +332,6 @@ group :metrics do
end end
group :development do group :development do
gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 4.2', require: false gem 'brakeman', '~> 4.2', require: false
gem 'danger', '~> 6.0', require: false gem 'danger', '~> 6.0', require: false
@ -388,7 +388,6 @@ group :development, :test do
gem 'benchmark-ips', '~> 2.3.0', require: false gem 'benchmark-ips', '~> 2.3.0', require: false
gem 'license_finder', '~> 5.4', require: false
gem 'knapsack', '~> 1.17' gem 'knapsack', '~> 1.17'
gem 'stackprof', '~> 0.2.10', require: false gem 'stackprof', '~> 0.2.10', require: false
@ -398,6 +397,11 @@ group :development, :test do
gem 'timecop', '~> 0.8.0' gem 'timecop', '~> 0.8.0'
end end
# Gems required in omnibus-gitlab pipeline
group :development, :test, :omnibus do
gem 'license_finder', '~> 5.4', require: false
end
group :test do group :test do
gem 'shoulda-matchers', '~> 4.0.1', require: false gem 'shoulda-matchers', '~> 4.0.1', require: false
gem 'email_spec', '~> 2.2.0' gem 'email_spec', '~> 2.2.0'
@ -407,6 +411,7 @@ group :test do
gem 'concurrent-ruby', '~> 1.1' gem 'concurrent-ruby', '~> 1.1'
gem 'test-prof', '~> 0.10.0' gem 'test-prof', '~> 0.10.0'
gem 'rspec_junit_formatter' gem 'rspec_junit_formatter'
gem 'guard-rspec'
end end
gem 'octokit', '~> 4.9' gem 'octokit', '~> 4.9'
@ -446,18 +451,18 @@ group :ed25519 do
end end
# Gitaly GRPC protocol definitions # Gitaly GRPC protocol definitions
gem 'gitaly', '~> 1.65.0' gem 'gitaly', '~> 1.70.0'
gem 'grpc', '~> 1.19.0' gem 'grpc', '~> 1.24.0'
gem 'google-protobuf', '~> 3.7.1' gem 'google-protobuf', '~> 3.8.0'
gem 'toml-rb', '~> 1.0.0', require: false gem 'toml-rb', '~> 1.0.0', require: false
# Feature toggles # Feature toggles
gem 'flipper', '~> 0.13.0' gem 'flipper', '~> 0.17.1'
gem 'flipper-active_record', '~> 0.13.0' gem 'flipper-active_record', '~> 0.17.1'
gem 'flipper-active_support_cache_store', '~> 0.13.0' gem 'flipper-active_support_cache_store', '~> 0.17.1'
gem 'unleash', '~> 0.1.5' gem 'unleash', '~> 0.1.5'
# Structured logging # Structured logging
@ -469,3 +474,5 @@ gem 'gitlab-net-dns', '~> 0.9.1'
# Countries list # Countries list
gem 'countries', '~> 3.0' gem 'countries', '~> 3.0'
gem 'retriable', '~> 3.1.2'

View file

@ -50,8 +50,8 @@ GEM
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
tzinfo (~> 1.1) tzinfo (~> 1.1)
acts-as-taggable-on (6.0.0) acts-as-taggable-on (6.5.0)
activerecord (~> 5.0) activerecord (>= 5.0, < 6.1)
adamantium (0.2.0) adamantium (0.2.0)
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
memoizable (~> 0.4.0) memoizable (~> 0.4.0)
@ -80,14 +80,16 @@ GEM
encryptor (~> 3.0.0) encryptor (~> 3.0.0)
attr_required (1.0.1) attr_required (1.0.1)
awesome_print (1.8.0) awesome_print (1.8.0)
aws-sdk (2.9.32) aws-eventstream (1.0.3)
aws-sdk-resources (= 2.9.32) aws-sdk (2.11.374)
aws-sdk-core (2.9.32) aws-sdk-resources (= 2.11.374)
aws-sdk-core (2.11.374)
aws-sigv4 (~> 1.0) aws-sigv4 (~> 1.0)
jmespath (~> 1.0) jmespath (~> 1.0)
aws-sdk-resources (2.9.32) aws-sdk-resources (2.11.374)
aws-sdk-core (= 2.9.32) aws-sdk-core (= 2.11.374)
aws-sigv4 (1.0.0) aws-sigv4 (1.1.0)
aws-eventstream (~> 1.0, >= 1.0.2)
axiom-types (0.1.1) axiom-types (0.1.1)
descendants_tracker (~> 0.0.4) descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
@ -171,9 +173,9 @@ GEM
unicode_utils (~> 1.4) unicode_utils (~> 1.4)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
crass (1.0.4) crass (1.0.5)
creole (0.5.0) creole (0.5.0)
css_parser (1.5.0) css_parser (1.7.0)
addressable addressable
daemons (1.2.6) daemons (1.2.6)
danger (6.0.9) danger (6.0.9)
@ -192,12 +194,12 @@ GEM
database_cleaner (1.7.0) database_cleaner (1.7.0)
debug_inspector (0.0.3) debug_inspector (0.0.3)
debugger-ruby_core_source (1.3.8) debugger-ruby_core_source (1.3.8)
deckar01-task_list (2.2.0) deckar01-task_list (2.2.1)
html-pipeline html-pipeline
declarative (0.0.10) declarative (0.0.10)
declarative-option (0.1.0) declarative-option (0.1.0)
default_value_for (3.2.0) default_value_for (3.3.0)
activerecord (>= 3.2.0, < 6.0) activerecord (>= 3.2.0, < 6.1)
derailed_benchmarks (1.3.5) derailed_benchmarks (1.3.5)
benchmark-ips (~> 2) benchmark-ips (~> 2)
get_process_mem (~> 0) get_process_mem (~> 0)
@ -222,6 +224,7 @@ GEM
railties railties
rotp (~> 2.0) rotp (~> 2.0)
diff-lcs (1.3) diff-lcs (1.3)
diff_match_patch (0.1.0)
diffy (3.1.0) diffy (3.1.0)
discordrb-webhooks-blackst0ne (3.3.0) discordrb-webhooks-blackst0ne (3.3.0)
rest-client (~> 2.0) rest-client (~> 2.0)
@ -285,13 +288,13 @@ GEM
fast_gettext (1.6.0) fast_gettext (1.6.0)
ffaker (2.10.0) ffaker (2.10.0)
ffi (1.11.1) ffi (1.11.1)
flipper (0.13.0) flipper (0.17.1)
flipper-active_record (0.13.0) flipper-active_record (0.17.1)
activerecord (>= 3.2, < 6) activerecord (>= 4.2, < 7)
flipper (~> 0.13.0) flipper (~> 0.17.1)
flipper-active_support_cache_store (0.13.0) flipper-active_support_cache_store (0.17.1)
activesupport (>= 3.2, < 6) activesupport (>= 4.2, < 7)
flipper (~> 0.13.0) flipper (~> 0.17.1)
flowdock (0.7.1) flowdock (0.7.1)
httparty (~> 0.7) httparty (~> 0.7)
multi_json multi_json
@ -332,10 +335,8 @@ GEM
fog-xml (0.1.3) fog-xml (0.1.3)
fog-core fog-core
nokogiri (>= 1.5.11, < 2.0.0) nokogiri (>= 1.5.11, < 2.0.0)
font-awesome-rails (4.7.0.4) font-awesome-rails (4.7.0.5)
railties (>= 3.2, < 6.0) railties (>= 3.2, < 6.1)
foreman (0.84.0)
thor (~> 0.19.1)
formatador (0.2.5) formatador (0.2.5)
fugit (1.2.1) fugit (1.2.1)
et-orbi (~> 1.1, >= 1.1.8) et-orbi (~> 1.1, >= 1.1.8)
@ -358,12 +359,12 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
git (1.5.0) git (1.5.0)
gitaly (1.65.0) gitaly (1.70.0)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-labkit (0.5.2) gitlab-labkit (0.7.0)
actionpack (~> 5) actionpack (>= 5.0.0, < 6.1.0)
activesupport (~> 5) activesupport (>= 5.0.0, < 6.1.0)
grpc (~> 1.19) grpc (~> 1.19)
jaeger-client (~> 0.10) jaeger-client (~> 0.10)
opentracing (~> 0.4) opentracing (~> 0.4)
@ -400,7 +401,7 @@ GEM
mime-types (~> 3.0) mime-types (~> 3.0)
representable (~> 3.0) representable (~> 3.0)
retriable (>= 2.0, < 4.0) retriable (>= 2.0, < 4.0)
google-protobuf (3.7.1) google-protobuf (3.8.0)
googleapis-common-protos-types (1.0.4) googleapis-common-protos-types (1.0.4)
google-protobuf (~> 3.0) google-protobuf (~> 3.0)
googleauth (0.6.6) googleauth (0.6.6)
@ -410,7 +411,7 @@ GEM
multi_json (~> 1.11) multi_json (~> 1.11)
os (>= 0.9, < 2.0) os (>= 0.9, < 2.0)
signet (~> 0.7) signet (~> 0.7)
gpgme (2.0.18) gpgme (2.0.19)
mini_portile2 (~> 2.3) mini_portile2 (~> 2.3)
grape (1.1.0) grape (1.1.0)
activesupport activesupport
@ -440,11 +441,25 @@ GEM
graphql (~> 1.6) graphql (~> 1.6)
html-pipeline (~> 2.8) html-pipeline (~> 2.8)
sass (~> 3.4) sass (~> 3.4)
grpc (1.19.0) grpc (1.24.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.8)
googleapis-common-protos-types (~> 1.0.0) googleapis-common-protos-types (~> 1.0)
gssapi (1.2.0) gssapi (1.2.0)
ffi (>= 1.0.1) ffi (>= 1.0.1)
guard (2.15.1)
formatador (>= 0.2.4)
listen (>= 2.7, < 4.0)
lumberjack (>= 1.0.12, < 2.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.9.12)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-compat (1.2.1)
guard-rspec (4.7.3)
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
haml (5.0.4) haml (5.0.4)
temple (>= 0.8.0) temple (>= 0.8.0)
tilt tilt
@ -508,7 +523,7 @@ GEM
atlassian-jwt atlassian-jwt
multipart-post multipart-post
oauth (~> 0.5, >= 0.5.0) oauth (~> 0.5, >= 0.5.0)
jmespath (1.3.1) jmespath (1.4.0)
js_regex (3.1.1) js_regex (3.1.1)
character_set (~> 1.1) character_set (~> 1.1)
regexp_parser (~> 1.1) regexp_parser (~> 1.1)
@ -560,15 +575,20 @@ GEM
xml-simple xml-simple
licensee (8.9.2) licensee (8.9.2)
rugged (~> 0.24) rugged (~> 0.24)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
locale (2.1.2) locale (2.1.2)
lograge (0.10.0) lograge (0.10.0)
actionpack (>= 4) actionpack (>= 4)
activesupport (>= 4) activesupport (>= 4)
railties (>= 4) railties (>= 4)
request_store (~> 1.0) request_store (~> 1.0)
loofah (2.3.0) loofah (2.3.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
lumberjack (1.0.13)
mail (2.7.1) mail (2.7.1)
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
mail_room (0.9.1) mail_room (0.9.1)
@ -584,7 +604,7 @@ GEM
mime-types-data (3.2019.0331) mime-types-data (3.2019.0331)
mimemagic (0.3.2) mimemagic (0.3.2)
mini_magick (4.9.5) mini_magick (4.9.5)
mini_mime (1.0.1) mini_mime (1.0.2)
mini_portile2 (2.4.0) mini_portile2 (2.4.0)
minitest (5.11.3) minitest (5.11.3)
msgpack (1.3.1) msgpack (1.3.1)
@ -597,16 +617,20 @@ GEM
mustermann (~> 1.0.0) mustermann (~> 1.0.0)
nakayoshi_fork (0.0.4) nakayoshi_fork (0.0.4)
nap (1.1.0) nap (1.1.0)
nenv (0.3.0)
net-ldap (0.16.0) net-ldap (0.16.0)
net-ntp (2.1.3) net-ntp (2.1.3)
net-ssh (5.2.0) net-ssh (5.2.0)
netrc (0.11.0) netrc (0.11.0)
nio4r (2.3.1) nio4r (2.3.1)
no_proxy_fix (0.1.2) no_proxy_fix (0.1.2)
nokogiri (1.10.4) nokogiri (1.10.5)
mini_portile2 (~> 2.4.0) mini_portile2 (~> 2.4.0)
nokogumbo (1.5.0) nokogumbo (1.5.0)
nokogiri nokogiri
notiffany (0.1.3)
nenv (~> 0.1)
shellany (~> 0.0)
numerizer (0.1.1) numerizer (0.1.1)
oauth (0.5.4) oauth (0.5.4)
oauth2 (1.4.1) oauth2 (1.4.1)
@ -675,12 +699,12 @@ GEM
activesupport activesupport
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth_openid_connect (0.3.1) omniauth_openid_connect (0.3.3)
addressable (~> 2.5) addressable (~> 2.5)
omniauth (~> 1.3) omniauth (~> 1.9)
openid_connect (~> 1.1) openid_connect (~> 1.1)
open4 (1.3.4) open4 (1.3.4)
openid_connect (1.1.6) openid_connect (1.1.8)
activemodel activemodel
attr_required (>= 1.0.0) attr_required (>= 1.0.0)
json-jwt (>= 1.5.0) json-jwt (>= 1.5.0)
@ -703,12 +727,12 @@ GEM
pg (1.1.4) pg (1.1.4)
po_to_json (1.0.1) po_to_json (1.0.1)
json (>= 1.6.0) json (>= 1.6.0)
premailer (1.10.4) premailer (1.11.1)
addressable addressable
css_parser (>= 1.4.10) css_parser (>= 1.6.0)
htmlentities (>= 4.0.0) htmlentities (>= 4.0.0)
premailer-rails (1.9.7) premailer-rails (1.10.3)
actionmailer (>= 3, < 6) actionmailer (>= 3)
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
proc_to_ast (0.1.0) proc_to_ast (0.1.0)
coderay coderay
@ -724,7 +748,7 @@ GEM
pry (~> 0.10) pry (~> 0.10)
pry-rails (0.3.6) pry-rails (0.3.6)
pry (>= 0.10.4) pry (>= 0.10.4)
public_suffix (3.1.0) public_suffix (3.1.1)
puma (3.12.0) puma (3.12.0)
puma_worker_killer (0.1.0) puma_worker_killer (0.1.0)
get_process_mem (~> 0.2) get_process_mem (~> 0.2)
@ -734,8 +758,8 @@ GEM
rack (2.0.7) rack (2.0.7)
rack-accept (0.4.5) rack-accept (0.4.5)
rack (>= 0.4) rack (>= 0.4)
rack-attack (4.4.1) rack-attack (6.2.0)
rack rack (>= 1.0, < 3)
rack-cors (1.0.2) rack-cors (1.0.2)
rack-oauth2 (1.9.3) rack-oauth2 (1.9.3)
activesupport activesupport
@ -763,10 +787,10 @@ GEM
bundler (>= 1.3.0) bundler (>= 1.3.0)
railties (= 5.2.3) railties (= 5.2.3)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.2) rails-controller-testing (1.0.4)
actionpack (~> 5.x, >= 5.0.1) actionpack (>= 5.0.1.x)
actionview (~> 5.x, >= 5.0.1) actionview (>= 5.0.1.x)
activesupport (~> 5.x) activesupport (>= 5.0.1.x)
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
@ -798,25 +822,25 @@ GEM
recaptcha (4.13.1) recaptcha (4.13.1)
json json
recursive-open-struct (1.1.0) recursive-open-struct (1.1.0)
redis (4.1.2) redis (4.1.3)
redis-actionpack (5.0.2) redis-actionpack (5.1.0)
actionpack (>= 4.0, < 6) actionpack (>= 4.0, < 7)
redis-rack (>= 1, < 3) redis-rack (>= 1, < 3)
redis-store (>= 1.1.0, < 2) redis-store (>= 1.1.0, < 2)
redis-activesupport (5.0.7) redis-activesupport (5.2.0)
activesupport (>= 3, < 6) activesupport (>= 3, < 7)
redis-store (>= 1.3, < 2) redis-store (>= 1.3, < 2)
redis-namespace (1.6.0) redis-namespace (1.6.0)
redis (>= 3.0.4) redis (>= 3.0.4)
redis-rack (2.0.5) redis-rack (2.0.6)
rack (>= 1.5, < 3) rack (>= 1.5, < 3)
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-rails (5.0.2) redis-rails (5.0.2)
redis-actionpack (>= 5.0, < 6) redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6) redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-store (1.6.0) redis-store (1.8.1)
redis (>= 2.2, < 5) redis (>= 4, < 5)
regexp_parser (1.5.1) regexp_parser (1.5.1)
regexp_property_values (0.3.4) regexp_property_values (0.3.4)
representable (3.0.4) representable (3.0.4)
@ -824,9 +848,9 @@ GEM
declarative-option (< 0.2.0) declarative-option (< 0.2.0)
uber (< 0.2.0) uber (< 0.2.0)
request_store (1.3.1) request_store (1.3.1)
responders (2.4.1) responders (3.0.0)
actionpack (>= 4.2.0, < 6.0) actionpack (>= 5.0)
railties (>= 4.2.0, < 6.0) railties (>= 5.0)
rest-client (2.0.2) rest-client (2.0.2)
http-cookie (>= 1.0.2, < 2.0) http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0) mime-types (>= 1.16, < 4.0)
@ -897,11 +921,12 @@ GEM
ruby-progressbar (1.10.1) ruby-progressbar (1.10.1)
ruby-saml (1.7.2) ruby-saml (1.7.2)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
ruby_dep (1.5.0)
ruby_parser (3.13.1) ruby_parser (3.13.1)
sexp_processor (~> 4.9) sexp_processor (~> 4.9)
rubyntlm (0.6.2) rubyntlm (0.6.2)
rubypants (0.2.0) rubypants (0.2.0)
rubyzip (1.2.2) rubyzip (1.3.0)
rugged (0.28.3.1) rugged (0.28.3.1)
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (4.6.6) sanitize (4.6.6)
@ -938,6 +963,7 @@ GEM
faraday (>= 0.7.6, < 1.0) faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.12.0) sexp_processor (4.12.0)
shellany (0.0.1)
shoulda-matchers (4.0.1) shoulda-matchers (4.0.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
sidekiq (5.2.7) sidekiq (5.2.7)
@ -978,11 +1004,11 @@ GEM
sshkey (2.0.0) sshkey (2.0.0)
stackprof (0.2.10) stackprof (0.2.10)
state_machines (0.5.0) state_machines (0.5.0)
state_machines-activemodel (0.5.1) state_machines-activemodel (0.7.1)
activemodel (>= 4.1, < 6.0) activemodel (>= 4.1)
state_machines (>= 0.5.0) state_machines (>= 0.5.0)
state_machines-activerecord (0.5.1) state_machines-activerecord (0.6.0)
activerecord (>= 4.1, < 6.0) activerecord (>= 4.1)
state_machines-activemodel (>= 0.5.0) state_machines-activemodel (>= 0.5.0)
swd (1.1.2) swd (1.1.2)
activesupport (>= 3) activesupport (>= 3)
@ -1127,12 +1153,13 @@ DEPENDENCIES
creole (~> 0.5.0) creole (~> 0.5.0)
danger (~> 6.0) danger (~> 6.0)
database_cleaner (~> 1.7.0) database_cleaner (~> 1.7.0)
deckar01-task_list (= 2.2.0) deckar01-task_list (= 2.2.1)
default_value_for (~> 3.2.0) default_value_for (~> 3.3.0)
derailed_benchmarks derailed_benchmarks
device_detector device_detector
devise (~> 4.6) devise (~> 4.6)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
diff_match_patch (~> 0.1.0)
diffy (~> 3.1.0) diffy (~> 3.1.0)
discordrb-webhooks-blackst0ne (~> 3.3) discordrb-webhooks-blackst0ne (~> 3.3)
doorkeeper (~> 4.3) doorkeeper (~> 4.3)
@ -1149,9 +1176,9 @@ DEPENDENCIES
faraday_middleware-aws-signers-v4 faraday_middleware-aws-signers-v4
fast_blank fast_blank
ffaker (~> 2.10) ffaker (~> 2.10)
flipper (~> 0.13.0) flipper (~> 0.17.1)
flipper-active_record (~> 0.13.0) flipper-active_record (~> 0.17.1)
flipper-active_support_cache_store (~> 0.13.0) flipper-active_support_cache_store (~> 0.17.1)
flowdock (~> 0.7) flowdock (~> 0.7)
fog-aliyun (~> 0.3) fog-aliyun (~> 0.3)
fog-aws (~> 3.5) fog-aws (~> 3.5)
@ -1161,14 +1188,13 @@ DEPENDENCIES
fog-openstack (~> 1.0) fog-openstack (~> 1.0)
fog-rackspace (~> 0.1.1) fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7) font-awesome-rails (~> 4.7)
foreman (~> 0.84.0)
fugit (~> 1.2.1) fugit (~> 1.2.1)
fuubar (~> 2.2.0) fuubar (~> 2.2.0)
gemojione (~> 3.3) gemojione (~> 3.3)
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly (~> 1.65.0) gitaly (~> 1.70.0)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-labkit (~> 0.5) gitlab-labkit (~> 0.5)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
@ -1181,8 +1207,8 @@ DEPENDENCIES
gitlab_omniauth-ldap (~> 2.1.1) gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2) gon (~> 6.2)
google-api-client (~> 0.23) google-api-client (~> 0.23)
google-protobuf (~> 3.7.1) google-protobuf (~> 3.8.0)
gpgme (~> 2.0.18) gpgme (~> 2.0.19)
grape (~> 1.1.0) grape (~> 1.1.0)
grape-entity (~> 0.7.1) grape-entity (~> 0.7.1)
grape-path-helpers (~> 1.1) grape-path-helpers (~> 1.1)
@ -1190,8 +1216,9 @@ DEPENDENCIES
graphiql-rails (~> 1.4.10) graphiql-rails (~> 1.4.10)
graphql (~> 1.9.11) graphql (~> 1.9.11)
graphql-docs (~> 1.6.0) graphql-docs (~> 1.6.0)
grpc (~> 1.19.0) grpc (~> 1.24.0)
gssapi gssapi
guard-rspec
haml_lint (~> 0.31.0) haml_lint (~> 0.31.0)
hamlit (~> 2.8.8) hamlit (~> 2.8.8)
hangouts-chat (~> 0.0.5) hangouts-chat (~> 0.0.5)
@ -1226,7 +1253,7 @@ DEPENDENCIES
net-ldap net-ldap
net-ntp net-ntp
net-ssh (~> 5.2) net-ssh (~> 5.2)
nokogiri (~> 1.10.4) nokogiri (~> 1.10.5)
oauth2 (~> 1.4) oauth2 (~> 1.4)
octokit (~> 4.9) octokit (~> 4.9)
omniauth (~> 1.8) omniauth (~> 1.8)
@ -1246,17 +1273,17 @@ DEPENDENCIES
omniauth-twitter (~> 1.4) omniauth-twitter (~> 1.4)
omniauth-ultraauth (~> 0.0.2) omniauth-ultraauth (~> 0.0.2)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
omniauth_openid_connect (~> 0.3.1) omniauth_openid_connect (~> 0.3.3)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
pg (~> 1.1) pg (~> 1.1)
premailer-rails (~> 1.9.7) premailer-rails (~> 1.10.3)
prometheus-client-mmap (~> 0.9.10) prometheus-client-mmap (~> 0.9.10)
pry-byebug (~> 3.5.1) pry-byebug (~> 3.5.1)
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
puma (~> 3.12) puma (~> 3.12)
puma_worker_killer puma_worker_killer
rack (~> 2.0.7) rack (~> 2.0.7)
rack-attack (~> 4.4.1) rack-attack (~> 6.2.0)
rack-cors (~> 1.0.0) rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.9.3) rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0) rack-proxy (~> 0.6.0)
@ -1275,7 +1302,8 @@ DEPENDENCIES
redis-namespace (~> 1.6.0) redis-namespace (~> 1.6.0)
redis-rails (~> 5.0.2) redis-rails (~> 5.0.2)
request_store (~> 1.3) request_store (~> 1.3)
responders (~> 2.0) responders (~> 3.0)
retriable (~> 3.1.2)
rouge (~> 3.11.0) rouge (~> 3.11.0)
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-parameterized rspec-parameterized
@ -1291,7 +1319,7 @@ DEPENDENCIES
ruby-prof (~> 1.0.0) ruby-prof (~> 1.0.0)
ruby-progressbar ruby-progressbar
ruby_parser (~> 3.8) ruby_parser (~> 3.8)
rubyzip (~> 1.2.2) rubyzip (~> 1.3.0)
rugged (~> 0.28) rugged (~> 0.28)
sanitize (~> 4.6) sanitize (~> 4.6)
sassc-rails (~> 2.1.0) sassc-rails (~> 2.1.0)
@ -1312,7 +1340,7 @@ DEPENDENCIES
sprockets (~> 3.7.0) sprockets (~> 3.7.0)
sshkey (~> 2.0) sshkey (~> 2.0)
stackprof (~> 0.2.10) stackprof (~> 0.2.10)
state_machines-activerecord (~> 0.5.1) state_machines-activerecord (~> 0.6.0)
sys-filesystem (~> 1.1.6) sys-filesystem (~> 1.1.6)
test-prof (~> 0.10.0) test-prof (~> 0.10.0)
thin (~> 1.7.0) thin (~> 1.7.0)

43
Guardfile Normal file
View file

@ -0,0 +1,43 @@
# frozen_string_literal: true
# More info at https://github.com/guard/guard#readme
cmd = ENV['SPRING'] ? 'spring rspec' : 'bundle exec rspec'
guard :rspec, cmd: cmd do
require "guard/rspec/dsl"
dsl = Guard::RSpec::Dsl.new(self)
directories %w(app ee lib spec)
# RSpec files
rspec = dsl.rspec
watch(rspec.spec_helper) { rspec.spec_dir }
watch(rspec.spec_support) { rspec.spec_dir }
watch(rspec.spec_files)
# Ruby files
ruby = dsl.ruby
dsl.watch_spec_files_for(ruby.lib_files)
# Rails files
rails = dsl.rails(view_extensions: %w(erb haml slim))
dsl.watch_spec_files_for(rails.app_files)
dsl.watch_spec_files_for(rails.views)
watch(rails.controllers) do |m|
[
rspec.spec.call("routing/#{m[1]}_routing"),
rspec.spec.call("controllers/#{m[1]}_controller")
]
end
# Rails config changes
watch(rails.spec_helper) { rspec.spec_dir }
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
# Capybara features specs
watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
end

View file

@ -79,7 +79,7 @@ star, smile, etc.). Some good tips about code reviews can be found in our
Overview and details of feature flag processes in development of GitLab itself is described in [feature flags process documentation](https://docs.gitlab.com/ee/development/feature_flags/process.html). Overview and details of feature flag processes in development of GitLab itself is described in [feature flags process documentation](https://docs.gitlab.com/ee/development/feature_flags/process.html).
Guides on how to include feature flags in your backend/frontend code while developing GitLab are described in [developing with feature flags documentation](https://docs.gitlab.com/ee/development/feature_flags/developing.html). Guides on how to include feature flags in your backend/frontend code while developing GitLab are described in [developing with feature flags documentation](https://docs.gitlab.com/ee/development/feature_flags/development.html).
Getting access and how to expose the feature to users is detailed in [controlling feature flags documentation](https://docs.gitlab.com/ee/development/feature_flags/controls.html). Getting access and how to expose the feature to users is detailed in [controlling feature flags documentation](https://docs.gitlab.com/ee/development/feature_flags/controls.html).

View file

@ -1 +1 @@
12.4.6 12.5.4

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -2,6 +2,8 @@ import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import { joinPaths } from './lib/utils/url_utility'; import { joinPaths } from './lib/utils/url_utility';
import flash from '~/flash';
import { __ } from '~/locale';
const Api = { const Api = {
groupsPath: '/api/:version/groups.json', groupsPath: '/api/:version/groups.json',
@ -29,6 +31,7 @@ const Api = {
usersPath: '/api/:version/users.json', usersPath: '/api/:version/users.json',
userPath: '/api/:version/users/:id', userPath: '/api/:version/users/:id',
userStatusPath: '/api/:version/users/:id/status', userStatusPath: '/api/:version/users/:id/status',
userProjectsPath: '/api/:version/users/:id/projects',
userPostStatusPath: '/api/:version/user/status', userPostStatusPath: '/api/:version/user/status',
commitPath: '/api/:version/projects/:id/repository/commits', commitPath: '/api/:version/projects/:id/repository/commits',
applySuggestionPath: '/api/:version/suggestions/:id/apply', applySuggestionPath: '/api/:version/suggestions/:id/apply',
@ -110,10 +113,9 @@ const Api = {
.get(url, { .get(url, {
params: Object.assign(defaults, options), params: Object.assign(defaults, options),
}) })
.then(({ data }) => { .then(({ data, headers }) => {
callback(data); callback(data);
return { data, headers };
return data;
}); });
}, },
@ -239,7 +241,8 @@ const Api = {
.get(url, { .get(url, {
params: Object.assign({}, defaults, options), params: Object.assign({}, defaults, options),
}) })
.then(({ data }) => callback(data)); .then(({ data }) => callback(data))
.catch(() => flash(__('Something went wrong while fetching projects')));
}, },
commitMultiple(id, data) { commitMultiple(id, data) {
@ -348,6 +351,20 @@ const Api = {
}); });
}, },
userProjects(userId, query, options, callback) {
const url = Api.buildUrl(Api.userProjectsPath).replace(':id', userId);
const defaults = {
search: query,
per_page: 20,
};
return axios
.get(url, {
params: Object.assign({}, defaults, options),
})
.then(({ data }) => callback(data))
.catch(() => flash(__('Something went wrong while fetching projects')));
},
branches(id, query = '', options = {}) { branches(id, query = '', options = {}) {
const url = Api.buildUrl(this.createBranchPath).replace(':id', encodeURIComponent(id)); const url = Api.buildUrl(this.createBranchPath).replace(':id', encodeURIComponent(id));

View file

@ -1,4 +1,4 @@
/* eslint-disable func-names, no-var */ /* eslint-disable func-names */
import $ from 'jquery'; import $ from 'jquery';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
@ -12,11 +12,8 @@ import { __ } from '~/locale';
// more than `x` users are referenced. // more than `x` users are referenced.
// //
var lastTextareaPreviewed; let lastTextareaHeight;
var lastTextareaHeight = null; let lastTextareaPreviewed;
var markdownPreview;
var previewButtonSelector;
var writeButtonSelector;
function MarkdownPreview() {} function MarkdownPreview() {}
@ -27,14 +24,13 @@ MarkdownPreview.prototype.emptyMessage = __('Nothing to preview.');
MarkdownPreview.prototype.ajaxCache = {}; MarkdownPreview.prototype.ajaxCache = {};
MarkdownPreview.prototype.showPreview = function($form) { MarkdownPreview.prototype.showPreview = function($form) {
var mdText; const preview = $form.find('.js-md-preview');
var preview = $form.find('.js-md-preview'); const url = preview.data('url');
var url = preview.data('url');
if (preview.hasClass('md-preview-loading')) { if (preview.hasClass('md-preview-loading')) {
return; return;
} }
mdText = $form.find('textarea.markdown-area').val(); const mdText = $form.find('textarea.markdown-area').val();
if (mdText === undefined) { if (mdText === undefined) {
return; return;
@ -46,7 +42,7 @@ MarkdownPreview.prototype.showPreview = function($form) {
} else { } else {
preview.addClass('md-preview-loading').text(__('Loading...')); preview.addClass('md-preview-loading').text(__('Loading...'));
this.fetchMarkdownPreview(mdText, url, response => { this.fetchMarkdownPreview(mdText, url, response => {
var body; let body;
if (response.body.length > 0) { if (response.body.length > 0) {
({ body } = response); ({ body } = response);
} else { } else {
@ -91,8 +87,7 @@ MarkdownPreview.prototype.hideReferencedUsers = function($form) {
}; };
MarkdownPreview.prototype.renderReferencedUsers = function(users, $form) { MarkdownPreview.prototype.renderReferencedUsers = function(users, $form) {
var referencedUsers; const referencedUsers = $form.find('.referenced-users');
referencedUsers = $form.find('.referenced-users');
if (referencedUsers.length) { if (referencedUsers.length) {
if (users.length >= this.referenceThreshold) { if (users.length >= this.referenceThreshold) {
referencedUsers.show(); referencedUsers.show();
@ -108,8 +103,7 @@ MarkdownPreview.prototype.hideReferencedCommands = function($form) {
}; };
MarkdownPreview.prototype.renderReferencedCommands = function(commands, $form) { MarkdownPreview.prototype.renderReferencedCommands = function(commands, $form) {
var referencedCommands; const referencedCommands = $form.find('.referenced-commands');
referencedCommands = $form.find('.referenced-commands');
if (commands.length > 0) { if (commands.length > 0) {
referencedCommands.html(commands); referencedCommands.html(commands);
referencedCommands.show(); referencedCommands.show();
@ -119,15 +113,15 @@ MarkdownPreview.prototype.renderReferencedCommands = function(commands, $form) {
} }
}; };
markdownPreview = new MarkdownPreview(); const markdownPreview = new MarkdownPreview();
previewButtonSelector = '.js-md-preview-button'; const previewButtonSelector = '.js-md-preview-button';
writeButtonSelector = '.js-md-write-button'; const writeButtonSelector = '.js-md-write-button';
lastTextareaPreviewed = null; lastTextareaPreviewed = null;
const markdownToolbar = $('.md-header-toolbar'); const markdownToolbar = $('.md-header-toolbar');
$.fn.setupMarkdownPreview = function() { $.fn.setupMarkdownPreview = function() {
var $form = $(this); const $form = $(this);
$form.find('textarea.markdown-area').on('input', () => { $form.find('textarea.markdown-area').on('input', () => {
markdownPreview.hideReferencedUsers($form); markdownPreview.hideReferencedUsers($form);
}); });
@ -188,7 +182,7 @@ $(document).on('markdown-preview:hide', (e, $form) => {
}); });
$(document).on('markdown-preview:toggle', (e, keyboardEvent) => { $(document).on('markdown-preview:toggle', (e, keyboardEvent) => {
var $target; let $target;
$target = $(keyboardEvent.target); $target = $(keyboardEvent.target);
if ($target.is('textarea.markdown-area')) { if ($target.is('textarea.markdown-area')) {
$(document).triggerHandler('markdown-preview:show', [$target.closest('form')]); $(document).triggerHandler('markdown-preview:show', [$target.closest('form')]);
@ -201,16 +195,14 @@ $(document).on('markdown-preview:toggle', (e, keyboardEvent) => {
}); });
$(document).on('click', previewButtonSelector, function(e) { $(document).on('click', previewButtonSelector, function(e) {
var $form;
e.preventDefault(); e.preventDefault();
$form = $(this).closest('form'); const $form = $(this).closest('form');
$(document).triggerHandler('markdown-preview:show', [$form]); $(document).triggerHandler('markdown-preview:show', [$form]);
}); });
$(document).on('click', writeButtonSelector, function(e) { $(document).on('click', writeButtonSelector, function(e) {
var $form;
e.preventDefault(); e.preventDefault();
$form = $(this).closest('form'); const $form = $(this).closest('form');
$(document).triggerHandler('markdown-preview:hide', [$form]); $(document).triggerHandler('markdown-preview:hide', [$form]);
}); });

View file

@ -118,8 +118,6 @@ export default class FileTemplateMediator {
} }
}); });
this.setFilename(item.name);
if (this.editor.getValue() !== '') { if (this.editor.getValue() !== '') {
this.setTypeSelectorToggleText(item.name); this.setTypeSelectorToggleText(item.name);
} }
@ -133,14 +131,16 @@ export default class FileTemplateMediator {
selectTemplateFile(selector, query, data) { selectTemplateFile(selector, query, data) {
const self = this; const self = this;
const { name } = selector.config;
selector.renderLoading(); selector.renderLoading();
this.fetchFileTemplate(selector.config.type, query, data) this.fetchFileTemplate(selector.config.type, query, data)
.then(file => { .then(file => {
this.setEditorContent(file); this.setEditorContent(file);
this.setFilename(name);
selector.renderLoaded(); selector.renderLoaded();
this.typeSelector.setToggleText(selector.config.name); this.typeSelector.setToggleText(name);
toast(__(`${query} template applied`), { toast(__(`${query} template applied`), {
action: { action: {
text: __('Undo'), text: __('Undo'),

View file

@ -133,7 +133,7 @@ export default {
if (this.board.name.length === 0) return; if (this.board.name.length === 0) return;
this.isLoading = true; this.isLoading = true;
if (this.isDeleteForm) { if (this.isDeleteForm) {
gl.boardService boardsStore
.deleteBoard(this.currentBoard) .deleteBoard(this.currentBoard)
.then(() => { .then(() => {
visitUrl(boardsStore.rootPath); visitUrl(boardsStore.rootPath);
@ -143,7 +143,7 @@ export default {
this.isLoading = false; this.isLoading = false;
}); });
} else { } else {
gl.boardService boardsStore
.createBoard(this.board) .createBoard(this.board)
.then(resp => resp.data) .then(resp => resp.data)
.then(data => { .then(data => {

View file

@ -84,7 +84,8 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
if ( if (
this.scrollHeight() <= this.listHeight() && this.scrollHeight() <= this.listHeight() &&
this.list.issuesSize > this.list.issues.length this.list.issuesSize > this.list.issues.length &&
this.list.isExpanded
) { ) {
this.list.page += 1; this.list.page += 1;
this.list.getIssues(false).catch(() => { this.list.getIssues(false).catch(() => {

View file

@ -168,7 +168,7 @@ export default {
} }
const recentBoardsPromise = new Promise((resolve, reject) => const recentBoardsPromise = new Promise((resolve, reject) =>
gl.boardService boardsStore
.recentBoards() .recentBoards()
.then(resolve) .then(resolve)
.catch(err => { .catch(err => {
@ -184,7 +184,7 @@ export default {
}), }),
); );
Promise.all([gl.boardService.allBoards(), recentBoardsPromise]) Promise.all([boardsStore.allBoards(), recentBoardsPromise])
.then(([allBoards, recentBoards]) => [allBoards.data, recentBoards.data]) .then(([allBoards, recentBoards]) => [allBoards.data, recentBoards.data])
.then(([allBoardsJson, recentBoardsJson]) => { .then(([allBoardsJson, recentBoardsJson]) => {
this.loading = false; this.loading = false;

View file

@ -1,5 +1,6 @@
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import { mapState } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui'; import { GlTooltipDirective } from '@gitlab/ui';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
@ -63,6 +64,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['isShowingLabels']),
numberOverLimit() { numberOverLimit() {
return this.issue.assignees.length - this.limitBeforeCounter; return this.issue.assignees.length - this.limitBeforeCounter;
}, },
@ -92,7 +94,7 @@ export default {
return false; return false;
}, },
showLabelFooter() { showLabelFooter() {
return this.issue.labels.find(l => this.showLabel(l)) !== undefined; return this.isShowingLabels && this.issue.labels.find(this.showLabel);
}, },
issueReferencePath() { issueReferencePath() {
const { referencePath, groupId } = this.issue; const { referencePath, groupId } = this.issue;

View file

@ -1,6 +1,7 @@
<script> <script>
/* global ListIssue */ /* global ListIssue */
import { urlParamsToObject } from '~/lib/utils/common_utils'; import { urlParamsToObject } from '~/lib/utils/common_utils';
import boardsStore from '~/boards/stores/boards_store';
import ModalHeader from './header.vue'; import ModalHeader from './header.vue';
import ModalList from './list.vue'; import ModalList from './list.vue';
import ModalFooter from './footer.vue'; import ModalFooter from './footer.vue';
@ -109,7 +110,7 @@ export default {
loadIssues(clearIssues = false) { loadIssues(clearIssues = false) {
if (!this.showAddIssuesModal) return false; if (!this.showAddIssuesModal) return false;
return gl.boardService return boardsStore
.getBacklog({ .getBacklog({
...urlParamsToObject(this.filter.path), ...urlParamsToObject(this.filter.path),
page: this.page, page: this.page,

View file

@ -13,6 +13,7 @@ import 'ee_else_ce/boards/models/issue';
import 'ee_else_ce/boards/models/list'; import 'ee_else_ce/boards/models/list';
import '~/boards/models/milestone'; import '~/boards/models/milestone';
import '~/boards/models/project'; import '~/boards/models/project';
import store from '~/boards/stores';
import boardsStore from '~/boards/stores/boards_store'; import boardsStore from '~/boards/stores/boards_store';
import ModalStore from '~/boards/stores/modal_store'; import ModalStore from '~/boards/stores/modal_store';
import BoardService from 'ee_else_ce/boards/services/board_service'; import BoardService from 'ee_else_ce/boards/services/board_service';
@ -29,6 +30,7 @@ import {
} from '~/lib/utils/common_utils'; } from '~/lib/utils/common_utils';
import boardConfigToggle from 'ee_else_ce/boards/config_toggle'; import boardConfigToggle from 'ee_else_ce/boards/config_toggle';
import toggleFocusMode from 'ee_else_ce/boards/toggle_focus'; import toggleFocusMode from 'ee_else_ce/boards/toggle_focus';
import toggleLabels from 'ee_else_ce/boards/toggle_labels';
import { import {
setPromotionState, setPromotionState,
setWeigthFetchingState, setWeigthFetchingState,
@ -67,6 +69,7 @@ export default () => {
BoardSidebar, BoardSidebar,
BoardAddIssuesModal, BoardAddIssuesModal,
}, },
store,
data: { data: {
state: boardsStore.state, state: boardsStore.state,
loading: true, loading: true,
@ -314,5 +317,6 @@ export default () => {
} }
toggleFocusMode(ModalStore, boardsStore, $boardApp); toggleFocusMode(ModalStore, boardsStore, $boardApp);
toggleLabels();
mountMultipleBoardsSwitcher(); mountMultipleBoardsSwitcher();
}; };

View file

@ -50,8 +50,8 @@ class List {
this.page = 1; this.page = 1;
this.loading = true; this.loading = true;
this.loadingMore = false; this.loadingMore = false;
this.issues = []; this.issues = obj.issues || [];
this.issuesSize = 0; this.issuesSize = obj.issuesSize ? obj.issuesSize : 0;
this.defaultAvatar = defaultAvatar; this.defaultAvatar = defaultAvatar;
if (obj.label) { if (obj.label) {

View file

@ -0,0 +1,3 @@
export default {
getLabelToggleState: state => (state.isShowingLabels ? 'on' : 'off'),
};

View file

@ -1,14 +1,18 @@
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import state from 'ee_else_ce/boards/stores/state'; import state from 'ee_else_ce/boards/stores/state';
import getters from 'ee_else_ce/boards/stores/getters';
import actions from 'ee_else_ce/boards/stores/actions'; import actions from 'ee_else_ce/boards/stores/actions';
import mutations from 'ee_else_ce/boards/stores/mutations'; import mutations from 'ee_else_ce/boards/stores/mutations';
Vue.use(Vuex); Vue.use(Vuex);
export default () => export const createStore = () =>
new Vuex.Store({ new Vuex.Store({
state, state,
getters,
actions, actions,
mutations, mutations,
}); });
export default createStore();

View file

@ -1,3 +1,3 @@
export default () => ({ export default () => ({
// ... isShowingLabels: true,
}); });

View file

@ -0,0 +1 @@
export default () => {};

View file

@ -8,11 +8,12 @@ import Flash from '../flash';
import Poll from '../lib/utils/poll'; import Poll from '../lib/utils/poll';
import initSettingsPanels from '../settings_panels'; import initSettingsPanels from '../settings_panels';
import eventHub from './event_hub'; import eventHub from './event_hub';
import { APPLICATION_STATUS, INGRESS, INGRESS_DOMAIN_SUFFIX } from './constants'; import { APPLICATION_STATUS, INGRESS, INGRESS_DOMAIN_SUFFIX, CROSSPLANE } from './constants';
import ClustersService from './services/clusters_service'; import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store'; import ClustersStore from './stores/clusters_store';
import Applications from './components/applications.vue'; import Applications from './components/applications.vue';
import setupToggleButtons from '../toggle_buttons'; import setupToggleButtons from '../toggle_buttons';
import initProjectSelectDropdown from '~/project_select';
const Environments = () => import('ee_component/clusters/components/environments.vue'); const Environments = () => import('ee_component/clusters/components/environments.vue');
@ -37,6 +38,8 @@ export default class Clusters {
installJupyterPath, installJupyterPath,
installKnativePath, installKnativePath,
updateKnativePath, updateKnativePath,
installElasticStackPath,
installCrossplanePath,
installPrometheusPath, installPrometheusPath,
managePrometheusPath, managePrometheusPath,
clusterEnvironmentsPath, clusterEnvironmentsPath,
@ -81,11 +84,13 @@ export default class Clusters {
installHelmEndpoint: installHelmPath, installHelmEndpoint: installHelmPath,
installIngressEndpoint: installIngressPath, installIngressEndpoint: installIngressPath,
installCertManagerEndpoint: installCertManagerPath, installCertManagerEndpoint: installCertManagerPath,
installCrossplaneEndpoint: installCrossplanePath,
installRunnerEndpoint: installRunnerPath, installRunnerEndpoint: installRunnerPath,
installPrometheusEndpoint: installPrometheusPath, installPrometheusEndpoint: installPrometheusPath,
installJupyterEndpoint: installJupyterPath, installJupyterEndpoint: installJupyterPath,
installKnativeEndpoint: installKnativePath, installKnativeEndpoint: installKnativePath,
updateKnativeEndpoint: updateKnativePath, updateKnativeEndpoint: updateKnativePath,
installElasticStackEndpoint: installElasticStackPath,
clusterEnvironmentsEndpoint: clusterEnvironmentsPath, clusterEnvironmentsEndpoint: clusterEnvironmentsPath,
}); });
@ -108,8 +113,10 @@ export default class Clusters {
this.ingressDomainHelpText && this.ingressDomainHelpText &&
this.ingressDomainHelpText.querySelector('.js-ingress-domain-snippet'); this.ingressDomainHelpText.querySelector('.js-ingress-domain-snippet');
initProjectSelectDropdown();
Clusters.initDismissableCallout(); Clusters.initDismissableCallout();
initSettingsPanels(); initSettingsPanels();
const toggleButtonsContainer = document.querySelector('.js-cluster-enable-toggle-area'); const toggleButtonsContainer = document.querySelector('.js-cluster-enable-toggle-area');
if (toggleButtonsContainer) { if (toggleButtonsContainer) {
setupToggleButtons(toggleButtonsContainer); setupToggleButtons(toggleButtonsContainer);
@ -222,6 +229,7 @@ export default class Clusters {
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data)); eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data)); eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data));
eventHub.$on('uninstallApplication', data => this.uninstallApplication(data)); eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
eventHub.$on('setCrossplaneProviderStack', data => this.setCrossplaneProviderStack(data));
// Add event listener to all the banner close buttons // Add event listener to all the banner close buttons
this.addBannerCloseHandler(this.unreachableContainer, 'unreachable'); this.addBannerCloseHandler(this.unreachableContainer, 'unreachable');
this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure'); this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure');
@ -233,6 +241,7 @@ export default class Clusters {
eventHub.$off('updateApplication', this.updateApplication); eventHub.$off('updateApplication', this.updateApplication);
eventHub.$off('saveKnativeDomain'); eventHub.$off('saveKnativeDomain');
eventHub.$off('setKnativeHostname'); eventHub.$off('setKnativeHostname');
eventHub.$off('setCrossplaneProviderStack');
eventHub.$off('uninstallApplication'); eventHub.$off('uninstallApplication');
} }
@ -399,18 +408,33 @@ export default class Clusters {
} }
installApplication({ id: appId, params }) { installApplication({ id: appId, params }) {
this.store.updateAppProperty(appId, 'requestReason', null); return Clusters.validateInstallation(appId, params)
this.store.updateAppProperty(appId, 'statusReason', null); .then(() => {
this.store.updateAppProperty(appId, 'requestReason', null);
this.store.updateAppProperty(appId, 'statusReason', null);
this.store.installApplication(appId);
this.store.installApplication(appId); // eslint-disable-next-line promise/no-nesting
this.service.installApplication(appId, params).catch(() => {
this.store.notifyInstallFailure(appId);
this.store.updateAppProperty(
appId,
'requestReason',
s__('ClusterIntegration|Request to begin installing failed'),
);
});
})
.catch(error => this.store.updateAppProperty(appId, 'validationError', error));
}
return this.service.installApplication(appId, params).catch(() => { static validateInstallation(appId, params) {
this.store.notifyInstallFailure(appId); return new Promise((resolve, reject) => {
this.store.updateAppProperty( if (appId === CROSSPLANE && !params.stack) {
appId, reject(s__('ClusterIntegration|Select a stack to install Crossplane.'));
'requestReason', return;
s__('ClusterIntegration|Request to begin installing failed'), }
);
resolve();
}); });
} }
@ -458,6 +482,12 @@ export default class Clusters {
this.store.updateAppProperty(appId, 'hostname', data.hostname); this.store.updateAppProperty(appId, 'hostname', data.hostname);
} }
setCrossplaneProviderStack(data) {
const appId = data.id;
this.store.updateAppProperty(appId, 'stack', data.stack.code);
this.store.updateAppProperty(appId, 'validationError', null);
}
destroy() { destroy() {
this.destroyed = true; this.destroyed = true;

View file

@ -9,9 +9,11 @@ import jeagerLogo from 'images/cluster_app_logos/jeager.png';
import jupyterhubLogo from 'images/cluster_app_logos/jupyterhub.png'; import jupyterhubLogo from 'images/cluster_app_logos/jupyterhub.png';
import kubernetesLogo from 'images/cluster_app_logos/kubernetes.png'; import kubernetesLogo from 'images/cluster_app_logos/kubernetes.png';
import certManagerLogo from 'images/cluster_app_logos/cert_manager.png'; import certManagerLogo from 'images/cluster_app_logos/cert_manager.png';
import crossplaneLogo from 'images/cluster_app_logos/crossplane.png';
import knativeLogo from 'images/cluster_app_logos/knative.png'; import knativeLogo from 'images/cluster_app_logos/knative.png';
import meltanoLogo from 'images/cluster_app_logos/meltano.png'; import meltanoLogo from 'images/cluster_app_logos/meltano.png';
import prometheusLogo from 'images/cluster_app_logos/prometheus.png'; import prometheusLogo from 'images/cluster_app_logos/prometheus.png';
import elasticStackLogo from 'images/cluster_app_logos/elastic_stack.png';
import { s__, sprintf } from '../../locale'; import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue'; import applicationRow from './application_row.vue';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
@ -19,6 +21,7 @@ import KnativeDomainEditor from './knative_domain_editor.vue';
import { CLUSTER_TYPE, PROVIDER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants'; import { CLUSTER_TYPE, PROVIDER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants';
import LoadingButton from '~/vue_shared/components/loading_button.vue'; import LoadingButton from '~/vue_shared/components/loading_button.vue';
import eventHub from '~/clusters/event_hub'; import eventHub from '~/clusters/event_hub';
import CrossplaneProviderStack from './crossplane_provider_stack.vue';
export default { export default {
components: { components: {
@ -27,6 +30,7 @@ export default {
LoadingButton, LoadingButton,
GlLoadingIcon, GlLoadingIcon,
KnativeDomainEditor, KnativeDomainEditor,
CrossplaneProviderStack,
}, },
props: { props: {
type: { type: {
@ -88,9 +92,11 @@ export default {
jupyterhubLogo, jupyterhubLogo,
kubernetesLogo, kubernetesLogo,
certManagerLogo, certManagerLogo,
crossplaneLogo,
knativeLogo, knativeLogo,
meltanoLogo, meltanoLogo,
prometheusLogo, prometheusLogo,
elasticStackLogo,
}), }),
computed: { computed: {
isProjectCluster() { isProjectCluster() {
@ -114,6 +120,15 @@ export default {
certManagerInstalled() { certManagerInstalled() {
return this.applications.cert_manager.status === APPLICATION_STATUS.INSTALLED; return this.applications.cert_manager.status === APPLICATION_STATUS.INSTALLED;
}, },
crossplaneInstalled() {
return this.applications.crossplane.status === APPLICATION_STATUS.INSTALLED;
},
enableClusterApplicationCrossplane() {
return gon.features && gon.features.enableClusterApplicationCrossplane;
},
enableClusterApplicationElasticStack() {
return gon.features && gon.features.enableClusterApplicationElasticStack;
},
ingressDescription() { ingressDescription() {
return sprintf( return sprintf(
_.escape( _.escape(
@ -146,6 +161,24 @@ export default {
false, false,
); );
}, },
crossplaneDescription() {
return sprintf(
_.escape(
s__(
`ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{kubectl} or %{gitlabIntegrationLink}.
Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on.`,
),
),
{
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ee/user/clusters/applications.html#crossplane"
target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|Gitlab Integration'))}</a>`,
kubectl: `<code>kubectl</code>`,
},
false,
);
},
prometheusDescription() { prometheusDescription() {
return sprintf( return sprintf(
_.escape( _.escape(
@ -168,9 +201,18 @@ export default {
jupyterHostname() { jupyterHostname() {
return this.applications.jupyter.hostname; return this.applications.jupyter.hostname;
}, },
elasticStackInstalled() {
return this.applications.elastic_stack.status === APPLICATION_STATUS.INSTALLED;
},
elasticStackKibanaHostname() {
return this.applications.elastic_stack.kibana_hostname;
},
knative() { knative() {
return this.applications.knative; return this.applications.knative;
}, },
crossplane() {
return this.applications.crossplane;
},
cloudRun() { cloudRun() {
return this.providerType === PROVIDER_TYPE.GCP && this.preInstalledKnative; return this.providerType === PROVIDER_TYPE.GCP && this.preInstalledKnative;
}, },
@ -207,6 +249,12 @@ export default {
hostname, hostname,
}); });
}, },
setCrossplaneProviderStack(stack) {
eventHub.$emit('setCrossplaneProviderStack', {
id: 'crossplane',
stack,
});
},
}, },
}; };
</script> </script>
@ -217,7 +265,7 @@ export default {
<p class="append-bottom-0"> <p class="append-bottom-0">
{{ {{
s__(`ClusterIntegration|Choose which applications to install on your Kubernetes cluster. s__(`ClusterIntegration|Choose which applications to install on your Kubernetes cluster.
Helm Tiller is required to install any of the following applications.`) Helm Tiller is required to install any of the following applications.`)
}} }}
<a :href="helpPath">{{ __('More information') }}</a> <a :href="helpPath">{{ __('More information') }}</a>
</p> </p>
@ -242,9 +290,9 @@ export default {
<div slot="description"> <div slot="description">
{{ {{
s__(`ClusterIntegration|Helm streamlines installing s__(`ClusterIntegration|Helm streamlines installing
and managing Kubernetes applications. and managing Kubernetes applications.
Tiller runs inside of your Kubernetes Cluster, Tiller runs inside of your Kubernetes Cluster,
and manages releases of your charts.`) and manages releases of your charts.`)
}} }}
</div> </div>
</application-row> </application-row>
@ -252,7 +300,7 @@ export default {
<div class="svg-container" v-html="helmInstallIllustration"></div> <div class="svg-container" v-html="helmInstallIllustration"></div>
{{ {{
s__(`ClusterIntegration|You must first install Helm Tiller before s__(`ClusterIntegration|You must first install Helm Tiller before
installing the applications below`) installing the applications below`)
}} }}
</div> </div>
<application-row <application-row
@ -275,8 +323,8 @@ export default {
<p> <p>
{{ {{
s__(`ClusterIntegration|Ingress gives you a way to route s__(`ClusterIntegration|Ingress gives you a way to route
requests to services based on the request host or path, requests to services based on the request host or path,
centralizing a number of services into a single entrypoint.`) centralizing a number of services into a single entrypoint.`)
}} }}
</p> </p>
@ -308,8 +356,8 @@ export default {
<p class="form-text text-muted"> <p class="form-text text-muted">
{{ {{
s__(`ClusterIntegration|Point a wildcard DNS to this s__(`ClusterIntegration|Point a wildcard DNS to this
generated endpoint in order to access generated endpoint in order to access
your application after it has been deployed.`) your application after it has been deployed.`)
}} }}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer"> <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }} {{ __('More information') }}
@ -320,8 +368,8 @@ export default {
<p v-if="!ingressExternalEndpoint" class="settings-message js-no-endpoint-message"> <p v-if="!ingressExternalEndpoint" class="settings-message js-no-endpoint-message">
{{ {{
s__(`ClusterIntegration|The endpoint is in s__(`ClusterIntegration|The endpoint is in
the process of being assigned. Please check your Kubernetes the process of being assigned. Please check your Kubernetes
cluster or Quotas on Google Kubernetes Engine if it takes a long time.`) cluster or Quotas on Google Kubernetes Engine if it takes a long time.`)
}} }}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer"> <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }} {{ __('More information') }}
@ -368,7 +416,7 @@ export default {
<p class="form-text text-muted"> <p class="form-text text-muted">
{{ {{
s__(`ClusterIntegration|Issuers represent a certificate authority. s__(`ClusterIntegration|Issuers represent a certificate authority.
You must provide an email address for your Issuer. `) You must provide an email address for your Issuer. `)
}} }}
<a <a
href="http://docs.cert-manager.io/en/latest/reference/issuers.html?highlight=email" href="http://docs.cert-manager.io/en/latest/reference/issuers.html?highlight=email"
@ -424,12 +472,40 @@ export default {
<div slot="description"> <div slot="description">
{{ {{
s__(`ClusterIntegration|GitLab Runner connects to the s__(`ClusterIntegration|GitLab Runner connects to the
repository and executes CI/CD jobs, repository and executes CI/CD jobs,
pushing results back and deploying pushing results back and deploying
applications to production.`) applications to production.`)
}} }}
</div> </div>
</application-row> </application-row>
<application-row
v-if="enableClusterApplicationCrossplane"
id="crossplane"
:logo-url="crossplaneLogo"
:title="applications.crossplane.title"
:status="applications.crossplane.status"
:status-reason="applications.crossplane.statusReason"
:request-status="applications.crossplane.requestStatus"
:request-reason="applications.crossplane.requestReason"
:installed="applications.crossplane.installed"
:install-failed="applications.crossplane.installFailed"
:uninstallable="applications.crossplane.uninstallable"
:uninstall-successful="applications.crossplane.uninstallSuccessful"
:uninstall-failed="applications.crossplane.uninstallFailed"
:install-application-request-params="{ stack: applications.crossplane.stack }"
:disabled="!helmInstalled"
title-link="https://crossplane.io"
>
<template>
<div slot="description">
<p v-html="crossplaneDescription"></p>
<div class="form-group">
<CrossplaneProviderStack :crossplane="crossplane" @set="setCrossplaneProviderStack" />
</div>
</div>
</template>
</application-row>
<application-row <application-row
id="jupyter" id="jupyter"
:logo-url="jupyterhubLogo" :logo-url="jupyterhubLogo"
@ -451,10 +527,10 @@ export default {
<p> <p>
{{ {{
s__(`ClusterIntegration|JupyterHub, a multi-user Hub, spawns, s__(`ClusterIntegration|JupyterHub, a multi-user Hub, spawns,
manages, and proxies multiple instances of the single-user manages, and proxies multiple instances of the single-user
Jupyter notebook server. JupyterHub can be used to serve Jupyter notebook server. JupyterHub can be used to serve
notebooks to a class of students, a corporate data science group, notebooks to a class of students, a corporate data science group,
or a scientific research group.`) or a scientific research group.`)
}} }}
</p> </p>
@ -481,7 +557,7 @@ export default {
<p v-if="ingressInstalled" class="form-text text-muted"> <p v-if="ingressInstalled" class="form-text text-muted">
{{ {{
s__(`ClusterIntegration|Replace this with your own hostname if you want. s__(`ClusterIntegration|Replace this with your own hostname if you want.
If you do so, point hostname to Ingress IP Address from above.`) If you do so, point hostname to Ingress IP Address from above.`)
}} }}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer"> <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }} {{ __('More information') }}
@ -527,9 +603,9 @@ export default {
<p> <p>
{{ {{
s__(`ClusterIntegration|Knative extends Kubernetes to provide s__(`ClusterIntegration|Knative extends Kubernetes to provide
a set of middleware components that are essential to build modern, a set of middleware components that are essential to build modern,
source-centric, and container-based applications that can run source-centric, and container-based applications that can run
anywhere: on premises, in the cloud, or even in a third-party data center.`) anywhere: on premises, in the cloud, or even in a third-party data center.`)
}} }}
</p> </p>
@ -542,6 +618,75 @@ export default {
/> />
</div> </div>
</application-row> </application-row>
<application-row
v-if="enableClusterApplicationElasticStack"
id="elastic_stack"
:logo-url="elasticStackLogo"
:title="applications.elastic_stack.title"
:status="applications.elastic_stack.status"
:status-reason="applications.elastic_stack.statusReason"
:request-status="applications.elastic_stack.requestStatus"
:request-reason="applications.elastic_stack.requestReason"
:version="applications.elastic_stack.version"
:chart-repo="applications.elastic_stack.chartRepo"
:update-available="applications.elastic_stack.updateAvailable"
:installed="applications.elastic_stack.installed"
:install-failed="applications.elastic_stack.installFailed"
:update-successful="applications.elastic_stack.updateSuccessful"
:update-failed="applications.elastic_stack.updateFailed"
:uninstallable="applications.elastic_stack.uninstallable"
:uninstall-successful="applications.elastic_stack.uninstallSuccessful"
:uninstall-failed="applications.elastic_stack.uninstallFailed"
:disabled="!helmInstalled"
:install-application-request-params="{
kibana_hostname: applications.elastic_stack.kibana_hostname,
}"
title-link="https://github.com/helm/charts/tree/master/stable/elastic-stack"
>
<div slot="description">
<p>
{{
s__(
`ClusterIntegration|The elastic stack collects logs from all pods in your cluster`,
)
}}
</p>
<template v-if="ingressExternalEndpoint">
<div class="form-group">
<label for="elastic-stack-kibana-hostname">{{
s__('ClusterIntegration|Kibana Hostname')
}}</label>
<div class="input-group">
<input
v-model="applications.elastic_stack.kibana_hostname"
:readonly="elasticStackInstalled"
type="text"
class="form-control js-hostname"
/>
<span class="input-group-btn">
<clipboard-button
:text="elasticStackKibanaHostname"
:title="s__('ClusterIntegration|Copy Kibana Hostname')"
class="js-clipboard-btn"
/>
</span>
</div>
<p v-if="ingressInstalled" class="form-text text-muted">
{{
s__(`ClusterIntegration|Replace this with your own hostname if you want.
If you do so, point hostname to Ingress IP Address from above.`)
}}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }}
</a>
</p>
</div>
</template>
</div>
</application-row>
</div> </div>
</section> </section>
</template> </template>

View file

@ -0,0 +1,93 @@
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import { s__ } from '../../locale';
export default {
name: 'CrossplaneProviderStack',
components: {
GlDropdown,
GlDropdownItem,
Icon,
},
props: {
stacks: {
type: Array,
required: false,
default: () => [
{
name: s__('Google Cloud Platform'),
code: 'gcp',
},
{
name: s__('Amazon Web Services'),
code: 'aws',
},
{
name: s__('Microsoft Azure'),
code: 'azure',
},
{
name: s__('Rook'),
code: 'rook',
},
],
},
crossplane: {
type: Object,
required: true,
},
},
computed: {
dropdownText() {
const result = this.stacks.reduce((map, obj) => {
// eslint-disable-next-line no-param-reassign
map[obj.code] = obj.name;
return map;
}, {});
const { stack } = this.crossplane;
if (stack !== '') {
return result[stack];
}
return s__('Select Stack');
},
validationError() {
return this.crossplane.validationError;
},
},
methods: {
selectStack(stack) {
this.$emit('set', stack);
},
},
};
</script>
<template>
<div>
<label>
{{ s__('ClusterIntegration|Enabled stack') }}
</label>
<gl-dropdown
:disabled="crossplane.installed"
:text="dropdownText"
toggle-class="dropdown-menu-toggle gl-field-error-outline"
class="w-100"
:class="{ 'gl-show-field-errors': validationError }"
>
<gl-dropdown-item v-for="stack in stacks" :key="stack.code" @click="selectStack(stack)">
<span class="ml-1">{{ stack.name }}</span>
</gl-dropdown-item>
</gl-dropdown>
<span v-if="validationError" class="gl-field-error">{{ validationError }}</span>
<p class="form-text text-muted">
{{ s__(`You must select a stack for configuring your cloud provider. Learn more about`) }}
<a
href="https://crossplane.io/docs/master/stacks-guide.html"
target="_blank"
rel="noopener noreferrer"
>{{ __('Crossplane') }}</a
>
</p>
</div>
</template>

View file

@ -2,7 +2,16 @@
import { GlModal } from '@gitlab/ui'; import { GlModal } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale'; import { sprintf, s__ } from '~/locale';
import trackUninstallButtonClickMixin from 'ee_else_ce/clusters/mixins/track_uninstall_button_click'; import trackUninstallButtonClickMixin from 'ee_else_ce/clusters/mixins/track_uninstall_button_click';
import { HELM, INGRESS, CERT_MANAGER, PROMETHEUS, RUNNER, KNATIVE, JUPYTER } from '../constants'; import {
HELM,
INGRESS,
CERT_MANAGER,
PROMETHEUS,
RUNNER,
KNATIVE,
JUPYTER,
ELASTIC_STACK,
} from '../constants';
const CUSTOM_APP_WARNING_TEXT = { const CUSTOM_APP_WARNING_TEXT = {
[HELM]: sprintf( [HELM]: sprintf(
@ -28,6 +37,7 @@ const CUSTOM_APP_WARNING_TEXT = {
[JUPYTER]: s__( [JUPYTER]: s__(
'ClusterIntegration|All data not committed to GitLab will be deleted and cannot be restored.', 'ClusterIntegration|All data not committed to GitLab will be deleted and cannot be restored.',
), ),
[ELASTIC_STACK]: s__('ClusterIntegration|All data will be deleted and cannot be restored.'),
}; };
export default { export default {

View file

@ -50,8 +50,19 @@ export const JUPYTER = 'jupyter';
export const KNATIVE = 'knative'; export const KNATIVE = 'knative';
export const RUNNER = 'runner'; export const RUNNER = 'runner';
export const CERT_MANAGER = 'cert_manager'; export const CERT_MANAGER = 'cert_manager';
export const CROSSPLANE = 'crossplane';
export const PROMETHEUS = 'prometheus'; export const PROMETHEUS = 'prometheus';
export const ELASTIC_STACK = 'elastic_stack';
export const APPLICATIONS = [HELM, INGRESS, JUPYTER, KNATIVE, RUNNER, CERT_MANAGER, PROMETHEUS]; export const APPLICATIONS = [
HELM,
INGRESS,
JUPYTER,
KNATIVE,
RUNNER,
CERT_MANAGER,
PROMETHEUS,
ELASTIC_STACK,
];
export const INGRESS_DOMAIN_SUFFIX = '.nip.io'; export const INGRESS_DOMAIN_SUFFIX = '.nip.io';

View file

@ -7,10 +7,12 @@ export default class ClusterService {
helm: this.options.installHelmEndpoint, helm: this.options.installHelmEndpoint,
ingress: this.options.installIngressEndpoint, ingress: this.options.installIngressEndpoint,
cert_manager: this.options.installCertManagerEndpoint, cert_manager: this.options.installCertManagerEndpoint,
crossplane: this.options.installCrossplaneEndpoint,
runner: this.options.installRunnerEndpoint, runner: this.options.installRunnerEndpoint,
prometheus: this.options.installPrometheusEndpoint, prometheus: this.options.installPrometheusEndpoint,
jupyter: this.options.installJupyterEndpoint, jupyter: this.options.installJupyterEndpoint,
knative: this.options.installKnativeEndpoint, knative: this.options.installKnativeEndpoint,
elastic_stack: this.options.installElasticStackEndpoint,
}; };
this.appUpdateEndpointMap = { this.appUpdateEndpointMap = {
knative: this.options.updateKnativeEndpoint, knative: this.options.updateKnativeEndpoint,

View file

@ -5,6 +5,8 @@ import {
JUPYTER, JUPYTER,
KNATIVE, KNATIVE,
CERT_MANAGER, CERT_MANAGER,
ELASTIC_STACK,
CROSSPLANE,
RUNNER, RUNNER,
APPLICATION_INSTALLED_STATUSES, APPLICATION_INSTALLED_STATUSES,
APPLICATION_STATUS, APPLICATION_STATUS,
@ -25,6 +27,7 @@ const applicationInitialState = {
uninstallable: false, uninstallable: false,
uninstallFailed: false, uninstallFailed: false,
uninstallSuccessful: false, uninstallSuccessful: false,
validationError: null,
}; };
export default class ClusterStore { export default class ClusterStore {
@ -57,6 +60,11 @@ export default class ClusterStore {
title: s__('ClusterIntegration|Cert-Manager'), title: s__('ClusterIntegration|Cert-Manager'),
email: null, email: null,
}, },
crossplane: {
...applicationInitialState,
title: s__('ClusterIntegration|Crossplane'),
stack: null,
},
runner: { runner: {
...applicationInitialState, ...applicationInitialState,
title: s__('ClusterIntegration|GitLab Runner'), title: s__('ClusterIntegration|GitLab Runner'),
@ -85,6 +93,11 @@ export default class ClusterStore {
updateSuccessful: false, updateSuccessful: false,
updateFailed: false, updateFailed: false,
}, },
elastic_stack: {
...applicationInitialState,
title: s__('ClusterIntegration|Elastic Stack'),
kibana_hostname: null,
},
}, },
environments: [], environments: [],
fetchingEnvironments: false, fetchingEnvironments: false,
@ -197,13 +210,15 @@ export default class ClusterStore {
} else if (appId === CERT_MANAGER) { } else if (appId === CERT_MANAGER) {
this.state.applications.cert_manager.email = this.state.applications.cert_manager.email =
this.state.applications.cert_manager.email || serverAppEntry.email; this.state.applications.cert_manager.email || serverAppEntry.email;
} else if (appId === CROSSPLANE) {
this.state.applications.crossplane.stack =
this.state.applications.crossplane.stack || serverAppEntry.stack;
} else if (appId === JUPYTER) { } else if (appId === JUPYTER) {
this.state.applications.jupyter.hostname = this.state.applications.jupyter.hostname = this.updateHostnameIfUnset(
this.state.applications.jupyter.hostname || this.state.applications.jupyter.hostname,
serverAppEntry.hostname || serverAppEntry.hostname,
(this.state.applications.ingress.externalIp 'jupyter',
? `jupyter.${this.state.applications.ingress.externalIp}.nip.io` );
: '');
} else if (appId === KNATIVE) { } else if (appId === KNATIVE) {
if (!this.state.applications.knative.isEditingHostName) { if (!this.state.applications.knative.isEditingHostName) {
this.state.applications.knative.hostname = this.state.applications.knative.hostname =
@ -216,10 +231,26 @@ export default class ClusterStore {
} else if (appId === RUNNER) { } else if (appId === RUNNER) {
this.state.applications.runner.version = version; this.state.applications.runner.version = version;
this.state.applications.runner.updateAvailable = updateAvailable; this.state.applications.runner.updateAvailable = updateAvailable;
} else if (appId === ELASTIC_STACK) {
this.state.applications.elastic_stack.kibana_hostname = this.updateHostnameIfUnset(
this.state.applications.elastic_stack.kibana_hostname,
serverAppEntry.kibana_hostname,
'kibana',
);
} }
}); });
} }
updateHostnameIfUnset(current, updated, fallback) {
return (
current ||
updated ||
(this.state.applications.ingress.externalIp
? `${fallback}.${this.state.applications.ingress.externalIp}.nip.io`
: '')
);
}
toggleFetchEnvironments(isFetching) { toggleFetchEnvironments(isFetching) {
this.state.fetchingEnvironments = isFetching; this.state.fetchingEnvironments = isFetching;
} }

View file

@ -1,4 +1,4 @@
/* eslint-disable func-names, no-var, no-else-return, consistent-return, one-var, no-return-assign, no-unused-expressions, no-sequences */ /* eslint-disable func-names, no-var, no-else-return, consistent-return, one-var, no-return-assign */
import $ from 'jquery'; import $ from 'jquery';
@ -9,40 +9,29 @@ const viewModes = ['two-up', 'swipe'];
export default class ImageFile { export default class ImageFile {
constructor(file) { constructor(file) {
this.file = file; this.file = file;
this.requestImageInfo( this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), () =>
$('.two-up.view .frame.deleted img', this.file), this.requestImageInfo($('.two-up.view .frame.added img', this.file), () => {
(function(_this) { this.initViewModes();
return function() {
return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), () => {
_this.initViewModes();
// Load two-up view after images are loaded // Load two-up view after images are loaded
// so that we can display the correct width and height information // so that we can display the correct width and height information
const $images = $('.two-up.view img', _this.file); const $images = $('.two-up.view img', this.file);
$images.waitForImages(() => { $images.waitForImages(() => {
_this.initView('two-up'); this.initView('two-up');
}); });
}); }),
};
})(this),
); );
} }
initViewModes() { initViewModes() {
const viewMode = viewModes[0]; const viewMode = viewModes[0];
$('.view-modes', this.file).removeClass('hide'); $('.view-modes', this.file).removeClass('hide');
$('.view-modes-menu', this.file).on( $('.view-modes-menu', this.file).on('click', 'li', event => {
'click', if (!$(event.currentTarget).hasClass('active')) {
'li', return this.activateViewMode(event.currentTarget.className);
(function(_this) { }
return function(event) { });
if (!$(event.currentTarget).hasClass('active')) {
return _this.activateViewMode(event.currentTarget.className);
}
};
})(this),
);
return this.activateViewMode(viewMode); return this.activateViewMode(viewMode);
} }
@ -51,15 +40,10 @@ export default class ImageFile {
.removeClass('active') .removeClass('active')
.filter(`.${viewMode}`) .filter(`.${viewMode}`)
.addClass('active'); .addClass('active');
return $(`.view:visible:not(.${viewMode})`, this.file).fadeOut( return $(`.view:visible:not(.${viewMode})`, this.file).fadeOut(200, () => {
200, $(`.view.${viewMode}`, this.file).fadeIn(200);
(function(_this) { return this.initView(viewMode);
return function() { });
$(`.view.${viewMode}`, _this.file).fadeIn(200);
return _this.initView(viewMode);
};
})(this),
);
} }
initView(viewMode) { initView(viewMode) {
@ -103,22 +87,18 @@ export default class ImageFile {
.on('touchmove', dragMove); .on('touchmove', dragMove);
} }
prepareFrames(view) { static prepareFrames(view) {
var maxHeight, maxWidth; var maxHeight, maxWidth;
maxWidth = 0; maxWidth = 0;
maxHeight = 0; maxHeight = 0;
$('.frame', view) $('.frame', view)
.each( .each((index, frame) => {
(function() { var height, width;
return function(index, frame) { width = $(frame).width();
var height, width; height = $(frame).height();
width = $(frame).width(); maxWidth = width > maxWidth ? width : maxWidth;
height = $(frame).height(); return (maxHeight = height > maxHeight ? height : maxHeight);
maxWidth = width > maxWidth ? width : maxWidth; })
return (maxHeight = height > maxHeight ? height : maxHeight);
};
})(this),
)
.css({ .css({
width: maxWidth, width: maxWidth,
height: maxHeight, height: maxHeight,
@ -128,104 +108,95 @@ export default class ImageFile {
views = { views = {
'two-up': function() { 'two-up': function() {
return $('.two-up.view .wrap', this.file).each( return $('.two-up.view .wrap', this.file).each((index, wrap) => {
(function(_this) { $('img', wrap).each(function() {
return function(index, wrap) { var currentWidth;
$('img', wrap).each(function() { currentWidth = $(this).width();
var currentWidth; if (currentWidth > availWidth / 2) {
currentWidth = $(this).width(); return $(this).width(availWidth / 2);
if (currentWidth > availWidth / 2) { }
return $(this).width(availWidth / 2); });
} return this.requestImageInfo($('img', wrap), (width, height) => {
}); $('.image-info .meta-width', wrap).text(`${width}px`);
return _this.requestImageInfo($('img', wrap), (width, height) => { $('.image-info .meta-height', wrap).text(`${height}px`);
$('.image-info .meta-width', wrap).text(`${width}px`); return $('.image-info', wrap).removeClass('hide');
$('.image-info .meta-height', wrap).text(`${height}px`); });
return $('.image-info', wrap).removeClass('hide'); });
});
};
})(this),
);
}, },
swipe() { swipe() {
var maxHeight, maxWidth; var maxHeight, maxWidth;
maxWidth = 0; maxWidth = 0;
maxHeight = 0; maxHeight = 0;
return $('.swipe.view', this.file).each( return $('.swipe.view', this.file).each((index, view) => {
(function(_this) { var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding;
return function(index, view) { const ref = ImageFile.prepareFrames(view);
var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref; [maxWidth, maxHeight] = ref;
(ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref); $swipeFrame = $('.swipe-frame', view);
$swipeFrame = $('.swipe-frame', view); $swipeWrap = $('.swipe-wrap', view);
$swipeWrap = $('.swipe-wrap', view); $swipeBar = $('.swipe-bar', view);
$swipeBar = $('.swipe-bar', view);
$swipeFrame.css({ $swipeFrame.css({
width: maxWidth + 16, width: maxWidth + 16,
height: maxHeight + 28, height: maxHeight + 28,
}); });
$swipeWrap.css({ $swipeWrap.css({
width: maxWidth + 1, width: maxWidth + 1,
height: maxHeight + 2, height: maxHeight + 2,
}); });
// Set swipeBar left position to match image frame // Set swipeBar left position to match image frame
$swipeBar.css({ $swipeBar.css({
left: 1, left: 1,
}); });
wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10); wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
_this.initDraggable($swipeBar, wrapPadding, (e, left) => { this.initDraggable($swipeBar, wrapPadding, (e, left) => {
if (left > 0 && left < $swipeFrame.width() - wrapPadding * 2) { if (left > 0 && left < $swipeFrame.width() - wrapPadding * 2) {
$swipeWrap.width(maxWidth + 1 - left); $swipeWrap.width(maxWidth + 1 - left);
$swipeBar.css('left', left); $swipeBar.css('left', left);
} }
}); });
}; });
})(this),
);
}, },
'onion-skin': function() { 'onion-skin': function() {
var dragTrackWidth, maxHeight, maxWidth; var dragTrackWidth, maxHeight, maxWidth;
maxWidth = 0; maxWidth = 0;
maxHeight = 0; maxHeight = 0;
dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width(); dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width();
return $('.onion-skin.view', this.file).each( return $('.onion-skin.view', this.file).each((index, view) => {
(function(_this) { var $frame, $track, $dragger, $frameAdded, framePadding;
return function(index, view) {
var $frame, $track, $dragger, $frameAdded, framePadding, ref;
(ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref);
$frame = $('.onion-skin-frame', view);
$frameAdded = $('.frame.added', view);
$track = $('.drag-track', view);
$dragger = $('.dragger', $track);
$frame.css({ const ref = ImageFile.prepareFrames(view);
width: maxWidth + 16, [maxWidth, maxHeight] = ref;
height: maxHeight + 28, $frame = $('.onion-skin-frame', view);
}); $frameAdded = $('.frame.added', view);
$('.swipe-wrap', view).css({ $track = $('.drag-track', view);
width: maxWidth + 1, $dragger = $('.dragger', $track);
height: maxHeight + 2,
});
$dragger.css({
left: dragTrackWidth,
});
$frameAdded.css('opacity', 1); $frame.css({
framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10); width: maxWidth + 16,
height: maxHeight + 28,
});
$('.swipe-wrap', view).css({
width: maxWidth + 1,
height: maxHeight + 2,
});
$dragger.css({
left: dragTrackWidth,
});
_this.initDraggable($dragger, framePadding, (e, left) => { $frameAdded.css('opacity', 1);
var opacity = left / dragTrackWidth; framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10);
if (opacity >= 0 && opacity <= 1) { this.initDraggable($dragger, framePadding, (e, left) => {
$dragger.css('left', left); var opacity = left / dragTrackWidth;
$frameAdded.css('opacity', opacity);
} if (opacity >= 0 && opacity <= 1) {
}); $dragger.css('left', left);
}; $frameAdded.css('opacity', opacity);
})(this), }
); });
});
}, },
}; };
@ -235,14 +206,7 @@ export default class ImageFile {
if (domImg.complete) { if (domImg.complete) {
return callback.call(this, domImg.naturalWidth, domImg.naturalHeight); return callback.call(this, domImg.naturalWidth, domImg.naturalHeight);
} else { } else {
return img.on( return img.on('load', () => callback.call(this, domImg.naturalWidth, domImg.naturalHeight));
'load',
(function(_this) {
return function() {
return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight);
};
})(this),
);
} }
} }
} }

View file

@ -1,4 +1,4 @@
/* eslint-disable func-names, one-var, no-var, no-else-return */ /* eslint-disable func-names, no-else-return */
import $ from 'jquery'; import $ from 'jquery';
import { __ } from './locale'; import { __ } from './locale';
@ -8,9 +8,8 @@ import { capitalizeFirstCharacter } from './lib/utils/text_utility';
export default function initCompareAutocomplete(limitTo = null, clickHandler = () => {}) { export default function initCompareAutocomplete(limitTo = null, clickHandler = () => {}) {
$('.js-compare-dropdown').each(function() { $('.js-compare-dropdown').each(function() {
var $dropdown, selected; const $dropdown = $(this);
$dropdown = $(this); const selected = $dropdown.data('selected');
selected = $dropdown.data('selected');
const $dropdownContainer = $dropdown.closest('.dropdown'); const $dropdownContainer = $dropdown.closest('.dropdown');
const $fieldInput = $(`input[name="${$dropdown.data('fieldName')}"]`, $dropdownContainer); const $fieldInput = $(`input[name="${$dropdown.data('fieldName')}"]`, $dropdownContainer);
const $filterInput = $('input[type="search"]', $dropdownContainer); const $filterInput = $('input[type="search"]', $dropdownContainer);
@ -44,17 +43,16 @@ export default function initCompareAutocomplete(limitTo = null, clickHandler = (
fieldName: $dropdown.data('fieldName'), fieldName: $dropdown.data('fieldName'),
filterInput: 'input[type="search"]', filterInput: 'input[type="search"]',
renderRow(ref) { renderRow(ref) {
var link; const link = $('<a />')
.attr('href', '#')
.addClass(ref === selected ? 'is-active' : '')
.text(ref)
.attr('data-ref', ref);
if (ref.header != null) { if (ref.header != null) {
return $('<li />') return $('<li />')
.addClass('dropdown-header') .addClass('dropdown-header')
.text(ref.header); .text(ref.header);
} else { } else {
link = $('<a />')
.attr('href', '#')
.addClass(ref === selected ? 'is-active' : '')
.text(ref)
.attr('data-ref', ref);
return $('<li />').append(link); return $('<li />').append(link);
} }
}, },

View file

@ -41,7 +41,7 @@ export default {
noForkText() { noForkText() {
return sprintf( return sprintf(
__( __(
"To protect this issue's confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private.", "To protect this issue's confidentiality, %{link_start}fork the project%{link_end} and set the forks visibility to private.",
), ),
{ link_start: `<a href="${this.newForkPath}" class="help-link">`, link_end: '</a>' }, { link_start: `<a href="${this.newForkPath}" class="help-link">`, link_end: '</a>' },
false, false,

View file

@ -0,0 +1,227 @@
<script>
import { __ } from '~/locale';
import _ from 'underscore';
import { mapActions, mapState, mapGetters } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import { GlAreaChart } from '@gitlab/ui/dist/charts';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import { getDatesInRange } from '~/lib/utils/datetime_utility';
import { xAxisLabelFormatter, dateFormatter } from '../utils';
export default {
components: {
GlAreaChart,
GlLoadingIcon,
},
props: {
endpoint: {
type: String,
required: true,
},
branch: {
type: String,
required: true,
},
},
data() {
return {
masterChart: null,
individualCharts: [],
svgs: {},
masterChartHeight: 264,
individualChartHeight: 216,
};
},
computed: {
...mapState(['chartData', 'loading']),
...mapGetters(['showChart', 'parsedData']),
masterChartData() {
const data = {};
this.xAxisRange.forEach(date => {
data[date] = this.parsedData.total[date] || 0;
});
return [
{
name: __('Commits'),
data: Object.entries(data),
},
];
},
masterChartOptions() {
return {
...this.getCommonChartOptions(true),
yAxis: {
name: __('Number of commits'),
},
grid: {
bottom: 64,
left: 64,
right: 20,
top: 20,
},
};
},
individualChartsData() {
const maxNumberOfIndividualContributorsCharts = 100;
return Object.keys(this.parsedData.byAuthor)
.map(name => {
const author = this.parsedData.byAuthor[name];
return {
name,
email: author.email,
commits: author.commits,
dates: [
{
name: __('Commits'),
data: this.xAxisRange.map(date => [date, author.dates[date] || 0]),
},
],
};
})
.sort((a, b) => b.commits - a.commits)
.slice(0, maxNumberOfIndividualContributorsCharts);
},
individualChartOptions() {
return {
...this.getCommonChartOptions(false),
yAxis: {
name: __('Commits'),
max: this.individualChartYAxisMax,
},
grid: {
bottom: 27,
left: 64,
right: 20,
top: 8,
},
};
},
individualChartYAxisMax() {
return this.individualChartsData.reduce((acc, item) => {
const values = item.dates[0].data.map(value => value[1]);
return Math.max(acc, ...values);
}, 0);
},
xAxisRange() {
const dates = Object.keys(this.parsedData.total).sort((a, b) => new Date(a) - new Date(b));
const firstContributionDate = new Date(dates[0]);
const lastContributionDate = new Date(dates[dates.length - 1]);
return getDatesInRange(firstContributionDate, lastContributionDate, dateFormatter);
},
firstContributionDate() {
return this.xAxisRange[0];
},
lastContributionDate() {
return this.xAxisRange[this.xAxisRange.length - 1];
},
charts() {
return _.uniq(this.individualCharts);
},
},
mounted() {
this.fetchChartData(this.endpoint);
},
methods: {
...mapActions(['fetchChartData']),
getCommonChartOptions(isMasterChart) {
return {
xAxis: {
type: 'time',
name: '',
data: this.xAxisRange,
axisLabel: {
formatter: xAxisLabelFormatter,
showMaxLabel: false,
showMinLabel: false,
},
boundaryGap: false,
splitNumber: isMasterChart ? 24 : 18,
// 28 days
minInterval: 28 * 86400 * 1000,
min: this.firstContributionDate,
max: this.lastContributionDate,
},
};
},
setSvg(name) {
return getSvgIconPathContent(name)
.then(path => {
if (path) {
this.$set(this.svgs, name, `path://${path}`);
}
})
.catch(() => {});
},
onMasterChartCreated(chart) {
this.masterChart = chart;
this.setSvg('scroll-handle')
.then(() => {
this.masterChart.setOption({
dataZoom: [
{
type: 'slider',
handleIcon: this.svgs['scroll-handle'],
},
],
});
})
.catch(() => {});
this.masterChart.on('datazoom', _.debounce(this.setIndividualChartsZoom, 200));
},
onIndividualChartCreated(chart) {
this.individualCharts.push(chart);
},
setIndividualChartsZoom(options) {
this.charts.forEach(chart =>
chart.setOption(
{
dataZoom: {
start: options.start,
end: options.end,
show: false,
},
},
{ lazyUpdate: true },
),
);
},
},
};
</script>
<template>
<div>
<div v-if="loading" class="contributors-loader text-center">
<gl-loading-icon :inline="true" :size="4" />
</div>
<div v-else-if="showChart" class="contributors-charts">
<h4>{{ __('Commits to') }} {{ branch }}</h4>
<span>{{ __('Excluding merge commits. Limited to 6,000 commits.') }}</span>
<div>
<gl-area-chart
:data="masterChartData"
:option="masterChartOptions"
:height="masterChartHeight"
@created="onMasterChartCreated"
/>
</div>
<div class="row">
<div v-for="contributor in individualChartsData" :key="contributor.name" class="col-6">
<h4>{{ contributor.name }}</h4>
<p>{{ n__('%d commit', '%d commits', contributor.commits) }} ({{ contributor.email }})</p>
<gl-area-chart
:data="contributor.dates"
:option="individualChartOptions"
:height="individualChartHeight"
@created="onIndividualChartCreated"
/>
</div>
</div>
</div>
</div>
</template>

View file

@ -0,0 +1,23 @@
import Vue from 'vue';
import ContributorsGraphs from './components/contributors.vue';
import store from './stores';
export default () => {
const el = document.querySelector('.js-contributors-graph');
if (!el) return null;
return new Vue({
el,
store,
render(createElement) {
return createElement(ContributorsGraphs, {
props: {
endpoint: el.dataset.projectGraphPath,
branch: el.dataset.projectBranch,
},
});
},
});
};

View file

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

View file

@ -0,0 +1,20 @@
import flash from '~/flash';
import { __ } from '~/locale';
import service from '../services/contributors_service';
import * as types from './mutation_types';
export const fetchChartData = ({ commit }, endpoint) => {
commit(types.SET_LOADING_STATE, true);
return service
.fetchChartData(endpoint)
.then(res => res.data)
.then(data => {
commit(types.SET_CHART_DATA, data);
commit(types.SET_LOADING_STATE, false);
})
.catch(() => flash(__('An error occurred while loading chart data')));
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};

View file

@ -0,0 +1,33 @@
export const showChart = state => Boolean(!state.loading && state.chartData);
export const parsedData = state => {
const byAuthor = {};
const total = {};
state.chartData.forEach(({ date, author_name, author_email }) => {
total[date] = total[date] ? total[date] + 1 : 1;
const authorData = byAuthor[author_name];
if (!authorData) {
byAuthor[author_name] = {
email: author_email.toLowerCase(),
commits: 1,
dates: {
[date]: 1,
},
};
} else {
authorData.commits += 1;
authorData.dates[date] = authorData.dates[date] ? authorData.dates[date] + 1 : 1;
}
});
return {
total,
byAuthor,
};
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};

View file

@ -0,0 +1,18 @@
import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import mutations from './mutations';
import * as getters from './getters';
import * as actions from './actions';
Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
actions,
mutations,
getters,
state: state(),
});
export default createStore();

View file

@ -0,0 +1,3 @@
export const SET_CHART_DATA = 'SET_CHART_DATA';
export const SET_LOADING_STATE = 'SET_LOADING_STATE';
export const SET_ACTIVE_BRANCH = 'SET_ACTIVE_BRANCH';

View file

@ -0,0 +1,17 @@
import * as types from './mutation_types';
export default {
[types.SET_LOADING_STATE](state, value) {
state.loading = value;
},
[types.SET_CHART_DATA](state, chartData) {
Object.assign(state, {
chartData,
});
},
[types.SET_ACTIVE_BRANCH](state, branch) {
Object.assign(state, {
branch,
});
},
};

View file

@ -0,0 +1,5 @@
export default () => ({
loading: false,
chartData: null,
branch: 'master',
});

View file

@ -0,0 +1,30 @@
import { getMonthNames } from '~/lib/utils/datetime_utility';
/**
* Converts provided string to date and returns formatted value as a year for date in January and month name for the rest
* @param {String}
* @returns {String} - formatted value
*
* xAxisLabelFormatter('01-12-2019') will return '2019'
* xAxisLabelFormatter('02-12-2019') will return 'Feb'
* xAxisLabelFormatter('07-12-2019') will return 'Jul'
*/
export const xAxisLabelFormatter = val => {
const date = new Date(val);
const month = date.getUTCMonth();
const year = date.getUTCFullYear();
return month === 0 ? `${year}` : getMonthNames(true)[month];
};
/**
* Formats provided date to YYYY-MM-DD format
* @param {Date}
* @returns {String} - formatted value
*/
export const dateFormatter = date => {
const year = date.getUTCFullYear();
const month = date.getUTCMonth();
const day = date.getUTCDate();
return `${year}-${`0${month + 1}`.slice(-2)}-${`0${day}`.slice(-2)}`;
};

View file

@ -2,14 +2,19 @@
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue'; import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue'; import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue'; import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
import { GlIcon } from '@gitlab/ui';
const findItem = (items, valueProp, value) => items.find(item => item[valueProp] === value); const toArray = value => [].concat(value);
const itemsProp = (items, prop) => items.map(item => item[prop]);
const defaultSearchFn = (searchQuery, labelProp) => item =>
item[labelProp].toLowerCase().indexOf(searchQuery) > -1;
export default { export default {
components: { components: {
DropdownButton, DropdownButton,
DropdownSearchInput, DropdownSearchInput,
DropdownHiddenInput, DropdownHiddenInput,
GlIcon,
}, },
props: { props: {
fieldName: { fieldName: {
@ -28,7 +33,7 @@ export default {
default: '', default: '',
}, },
value: { value: {
type: [Object, String], type: [Object, Array, String],
required: false, required: false,
default: () => null, default: () => null,
}, },
@ -72,6 +77,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
multiple: {
type: Boolean,
required: false,
default: false,
},
errorMessage: { errorMessage: {
type: String, type: String,
required: false, required: false,
@ -90,12 +100,11 @@ export default {
searchFn: { searchFn: {
type: Function, type: Function,
required: false, required: false,
default: searchQuery => item => item.name.toLowerCase().indexOf(searchQuery) > -1, default: defaultSearchFn,
}, },
}, },
data() { data() {
return { return {
selectedItem: findItem(this.items, this.value),
searchQuery: '', searchQuery: '',
}; };
}, },
@ -109,36 +118,52 @@ export default {
return this.disabledText; return this.disabledText;
} }
if (!this.selectedItem) { if (!this.selectedItems.length) {
return this.placeholder; return this.placeholder;
} }
return this.selectedItemLabel; return this.selectedItemsLabels;
}, },
results() { results() {
if (!this.items) { return this.getItemsOrEmptyList().filter(this.searchFn(this.searchQuery, this.labelProperty));
return []; },
} selectedItems() {
const valueProp = this.valueProperty;
const valueList = toArray(this.value);
const items = this.getItemsOrEmptyList();
return this.items.filter(this.searchFn(this.searchQuery)); return items.filter(item => valueList.some(value => item[valueProp] === value));
}, },
selectedItemLabel() { selectedItemsLabels() {
return this.selectedItem && this.selectedItem[this.labelProperty]; return itemsProp(this.selectedItems, this.labelProperty).join(', ');
}, },
selectedItemValue() { selectedItemsValues() {
return (this.selectedItem && this.selectedItem[this.valueProperty]) || ''; return itemsProp(this.selectedItems, this.valueProperty).join(', ');
},
},
watch: {
value(value) {
this.selectedItem = findItem(this.items, this.valueProperty, value);
}, },
}, },
methods: { methods: {
select(item) { getItemsOrEmptyList() {
this.selectedItem = item; return this.items || [];
},
selectSingle(item) {
this.$emit('input', item[this.valueProperty]); this.$emit('input', item[this.valueProperty]);
}, },
selectMultiple(item) {
const value = toArray(this.value);
const itemValue = item[this.valueProperty];
const itemValueIndex = value.indexOf(itemValue);
if (itemValueIndex > -1) {
value.splice(itemValueIndex, 1);
} else {
value.push(itemValue);
}
this.$emit('input', value);
},
isSelected(item) {
return this.selectedItems.includes(item);
},
}, },
}; };
</script> </script>
@ -146,7 +171,7 @@ export default {
<template> <template>
<div> <div>
<div class="js-gcp-machine-type-dropdown dropdown"> <div class="js-gcp-machine-type-dropdown dropdown">
<dropdown-hidden-input :name="fieldName" :value="selectedItemValue" /> <dropdown-hidden-input :name="fieldName" :value="selectedItemsValues" />
<dropdown-button <dropdown-button
:class="{ 'border-danger': hasErrors }" :class="{ 'border-danger': hasErrors }"
:is-disabled="disabled" :is-disabled="disabled"
@ -158,15 +183,28 @@ export default {
<div class="dropdown-content"> <div class="dropdown-content">
<ul> <ul>
<li v-if="!results.length"> <li v-if="!results.length">
<span class="js-empty-text menu-item"> <span class="js-empty-text menu-item">{{ emptyText }}</span>
{{ emptyText }}
</span>
</li> </li>
<li v-for="item in results" :key="item.id"> <li v-for="item in results" :key="item.id">
<button class="js-dropdown-item" type="button" @click.prevent="select(item)"> <button
<slot name="item" :item="item"> v-if="multiple"
{{ item.name }} class="js-dropdown-item d-flex align-items-center"
</slot> type="button"
@click.stop.prevent="selectMultiple(item)"
>
<gl-icon
:class="[{ invisible: !isSelected(item) }, 'mr-1']"
name="mobile-issue-close"
/>
<slot name="item" :item="item">{{ item.name }}</slot>
</button>
<button
v-else
class="js-dropdown-item"
type="button"
@click.prevent="selectSingle(item)"
>
<slot name="item" :item="item">{{ item.name }}</slot>
</button> </button>
</li> </li>
</ul> </ul>
@ -182,8 +220,7 @@ export default {
'text-muted': !hasErrors, 'text-muted': !hasErrors,
}, },
]" ]"
>{{ errorMessage }}</span
> >
{{ errorMessage }}
</span>
</div> </div>
</template> </template>

View file

@ -1,4 +1,5 @@
<script> <script>
import { mapState } from 'vuex';
import ServiceCredentialsForm from './service_credentials_form.vue'; import ServiceCredentialsForm from './service_credentials_form.vue';
import EksClusterConfigurationForm from './eks_cluster_configuration_form.vue'; import EksClusterConfigurationForm from './eks_cluster_configuration_form.vue';
@ -16,14 +17,37 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
accountAndExternalIdsHelpPath: {
type: String,
required: true,
},
createRoleArnHelpPath: {
type: String,
required: true,
},
externalLinkIcon: {
type: String,
required: true,
},
},
computed: {
...mapState(['hasCredentials']),
}, },
}; };
</script> </script>
<template> <template>
<div class="js-create-eks-cluster"> <div class="js-create-eks-cluster">
<eks-cluster-configuration-form <eks-cluster-configuration-form
v-if="hasCredentials"
:gitlab-managed-cluster-help-path="gitlabManagedClusterHelpPath" :gitlab-managed-cluster-help-path="gitlabManagedClusterHelpPath"
:kubernetes-integration-help-path="kubernetesIntegrationHelpPath" :kubernetes-integration-help-path="kubernetesIntegrationHelpPath"
:external-link-icon="externalLinkIcon"
/>
<service-credentials-form
v-else
:create-role-arn-help-path="createRoleArnHelpPath"
:account-and-external-ids-help-path="accountAndExternalIdsHelpPath"
:external-link-icon="externalLinkIcon"
/> />
</div> </div>
</template> </template>

View file

@ -4,8 +4,8 @@ import { sprintf, s__ } from '~/locale';
import _ from 'underscore'; import _ from 'underscore';
import { GlFormInput, GlFormCheckbox } from '@gitlab/ui'; import { GlFormInput, GlFormCheckbox } from '@gitlab/ui';
import ClusterFormDropdown from './cluster_form_dropdown.vue'; import ClusterFormDropdown from './cluster_form_dropdown.vue';
import RegionDropdown from './region_dropdown.vue';
import { KUBERNETES_VERSIONS } from '../constants'; import { KUBERNETES_VERSIONS } from '../constants';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
const { mapState: mapRolesState, mapActions: mapRolesActions } = createNamespacedHelpers('roles'); const { mapState: mapRolesState, mapActions: mapRolesActions } = createNamespacedHelpers('roles');
const { mapState: mapRegionsState, mapActions: mapRegionsActions } = createNamespacedHelpers( const { mapState: mapRegionsState, mapActions: mapRegionsActions } = createNamespacedHelpers(
@ -22,13 +22,17 @@ const {
mapState: mapSecurityGroupsState, mapState: mapSecurityGroupsState,
mapActions: mapSecurityGroupsActions, mapActions: mapSecurityGroupsActions,
} = createNamespacedHelpers('securityGroups'); } = createNamespacedHelpers('securityGroups');
const {
mapState: mapInstanceTypesState,
mapActions: mapInstanceTypesActions,
} = createNamespacedHelpers('instanceTypes');
export default { export default {
components: { components: {
ClusterFormDropdown, ClusterFormDropdown,
RegionDropdown,
GlFormInput, GlFormInput,
GlFormCheckbox, GlFormCheckbox,
LoadingButton,
}, },
props: { props: {
gitlabManagedClusterHelpPath: { gitlabManagedClusterHelpPath: {
@ -39,6 +43,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
externalLinkIcon: {
type: String,
required: true,
},
}, },
computed: { computed: {
...mapState([ ...mapState([
@ -51,7 +59,10 @@ export default {
'selectedSubnet', 'selectedSubnet',
'selectedRole', 'selectedRole',
'selectedSecurityGroup', 'selectedSecurityGroup',
'selectedInstanceType',
'nodeCount',
'gitlabManagedCluster', 'gitlabManagedCluster',
'isCreatingCluster',
]), ]),
...mapRolesState({ ...mapRolesState({
roles: 'items', roles: 'items',
@ -83,6 +94,11 @@ export default {
isLoadingSecurityGroups: 'isLoadingItems', isLoadingSecurityGroups: 'isLoadingItems',
loadingSecurityGroupsError: 'loadingItemsError', loadingSecurityGroupsError: 'loadingItemsError',
}), }),
...mapInstanceTypesState({
instanceTypes: 'items',
isLoadingInstanceTypes: 'isLoadingItems',
loadingInstanceTypesError: 'loadingItemsError',
}),
kubernetesVersions() { kubernetesVersions() {
return KUBERNETES_VERSIONS; return KUBERNETES_VERSIONS;
}, },
@ -98,6 +114,27 @@ export default {
securityGroupDropdownDisabled() { securityGroupDropdownDisabled() {
return !this.selectedVpc; return !this.selectedVpc;
}, },
createClusterButtonDisabled() {
return (
!this.clusterName ||
!this.environmentScope ||
!this.kubernetesVersion ||
!this.selectedRegion ||
!this.selectedKeyPair ||
!this.selectedVpc ||
!this.selectedSubnet ||
!this.selectedRole ||
!this.selectedSecurityGroup ||
!this.selectedInstanceType ||
!this.nodeCount ||
this.isCreatingCluster
);
},
createClusterButtonLabel() {
return this.isCreatingCluster
? s__('ClusterIntegration|Creating Kubernetes cluster')
: s__('ClusterIntegration|Create Kubernetes cluster');
},
kubernetesIntegrationHelpText() { kubernetesIntegrationHelpText() {
const escapedUrl = _.escape(this.kubernetesIntegrationHelpPath); const escapedUrl = _.escape(this.kubernetesIntegrationHelpPath);
@ -115,11 +152,26 @@ export default {
roleDropdownHelpText() { roleDropdownHelpText() {
return sprintf( return sprintf(
s__( s__(
'ClusterIntegration|Select the IAM Role to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role name, first create one on %{startLink}Amazon Web Services%{endLink}.', 'ClusterIntegration|Select the IAM Role to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role name, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}.',
), ),
{ {
startLink: startLink:
'<a href="https://console.aws.amazon.com/iam/home?#roles" target="_blank" rel="noopener noreferrer">', '<a href="https://docs.aws.amazon.com/eks/latest/userguide/getting-started-console.html#role-create" target="_blank" rel="noopener noreferrer">',
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>',
},
false,
);
},
regionsDropdownHelpText() {
return sprintf(
s__(
'ClusterIntegration|Learn more about %{startLink}Regions %{externalLinkIcon}%{endLink}.',
),
{
startLink:
'<a href="https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/" target="_blank" rel="noopener noreferrer">',
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>', endLink: '</a>',
}, },
false, false,
@ -128,11 +180,12 @@ export default {
keyPairDropdownHelpText() { keyPairDropdownHelpText() {
return sprintf( return sprintf(
s__( s__(
'ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{startLink}Amazon Web Services%{endLink}.', 'ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}.',
), ),
{ {
startLink: startLink:
'<a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#having-ec2-create-your-key-pair" target="_blank" rel="noopener noreferrer">', '<a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#having-ec2-create-your-key-pair" target="_blank" rel="noopener noreferrer">',
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>', endLink: '</a>',
}, },
false, false,
@ -141,11 +194,12 @@ export default {
vpcDropdownHelpText() { vpcDropdownHelpText() {
return sprintf( return sprintf(
s__( s__(
'ClusterIntegration|Select a VPC to use for your EKS Cluster resources. To use a new VPC, first create one on %{startLink}Amazon Web Services%{endLink}.', 'ClusterIntegration|Select a VPC to use for your EKS Cluster resources. To use a new VPC, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}.',
), ),
{ {
startLink: startLink:
'<a href="https://console.aws.amazon.com/vpc/home?#vpc" target="_blank" rel="noopener noreferrer">', '<a href="https://docs.aws.amazon.com/eks/latest/userguide/getting-started-console.html#vpc-create" target="_blank" rel="noopener noreferrer">',
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>', endLink: '</a>',
}, },
false, false,
@ -154,11 +208,12 @@ export default {
subnetDropdownHelpText() { subnetDropdownHelpText() {
return sprintf( return sprintf(
s__( s__(
'ClusterIntegration|Choose the %{startLink}subnets%{endLink} in your VPC where your worker nodes will run.', 'ClusterIntegration|Choose the %{startLink}subnets %{externalLinkIcon} %{endLink} in your VPC where your worker nodes will run.',
), ),
{ {
startLink: startLink:
'<a href="https://console.aws.amazon.com/vpc/home?#subnets" target="_blank" rel="noopener noreferrer">', '<a href="https://console.aws.amazon.com/vpc/home?#subnets" target="_blank" rel="noopener noreferrer">',
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>', endLink: '</a>',
}, },
false, false,
@ -167,11 +222,26 @@ export default {
securityGroupDropdownHelpText() { securityGroupDropdownHelpText() {
return sprintf( return sprintf(
s__( s__(
'ClusterIntegration|Choose the %{startLink}security groups%{endLink} to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets.', 'ClusterIntegration|Choose the %{startLink}security group %{externalLinkIcon} %{endLink} to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets.',
), ),
{ {
startLink: startLink:
'<a href="https://console.aws.amazon.com/vpc/home?#securityGroups" target="_blank" rel="noopener noreferrer">', '<a href="https://console.aws.amazon.com/vpc/home?#securityGroups" target="_blank" rel="noopener noreferrer">',
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>',
},
false,
);
},
instanceTypesDropdownHelpText() {
return sprintf(
s__(
'ClusterIntegration|Choose the worker node %{startLink}instance type %{externalLinkIcon} %{endLink}.',
),
{
startLink:
'<a href="https://aws.amazon.com/ec2/instance-types" target="_blank" rel="noopener noreferrer">',
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>', endLink: '</a>',
}, },
false, false,
@ -195,9 +265,12 @@ export default {
mounted() { mounted() {
this.fetchRegions(); this.fetchRegions();
this.fetchRoles(); this.fetchRoles();
this.fetchInstanceTypes();
}, },
methods: { methods: {
...mapActions([ ...mapActions([
'createCluster',
'signOut',
'setClusterName', 'setClusterName',
'setEnvironmentScope', 'setEnvironmentScope',
'setKubernetesVersion', 'setKubernetesVersion',
@ -207,6 +280,8 @@ export default {
'setRole', 'setRole',
'setKeyPair', 'setKeyPair',
'setSecurityGroup', 'setSecurityGroup',
'setInstanceType',
'setNodeCount',
'setGitlabManagedCluster', 'setGitlabManagedCluster',
]), ]),
...mapRegionsActions({ fetchRegions: 'fetchItems' }), ...mapRegionsActions({ fetchRegions: 'fetchItems' }),
@ -215,15 +290,22 @@ export default {
...mapRolesActions({ fetchRoles: 'fetchItems' }), ...mapRolesActions({ fetchRoles: 'fetchItems' }),
...mapKeyPairsActions({ fetchKeyPairs: 'fetchItems' }), ...mapKeyPairsActions({ fetchKeyPairs: 'fetchItems' }),
...mapSecurityGroupsActions({ fetchSecurityGroups: 'fetchItems' }), ...mapSecurityGroupsActions({ fetchSecurityGroups: 'fetchItems' }),
...mapInstanceTypesActions({ fetchInstanceTypes: 'fetchItems' }),
setRegionAndFetchVpcsAndKeyPairs(region) { setRegionAndFetchVpcsAndKeyPairs(region) {
this.setRegion({ region }); this.setRegion({ region });
this.setVpc({ vpc: null });
this.setKeyPair({ keyPair: null });
this.setSubnet({ subnet: null });
this.setSecurityGroup({ securityGroup: null });
this.fetchVpcs({ region }); this.fetchVpcs({ region });
this.fetchKeyPairs({ region }); this.fetchKeyPairs({ region });
}, },
setVpcAndFetchSubnets(vpc) { setVpcAndFetchSubnets(vpc) {
this.setVpc({ vpc }); this.setVpc({ vpc });
this.fetchSubnets({ vpc }); this.setSubnet({ subnet: null });
this.fetchSecurityGroups({ vpc }); this.setSecurityGroup({ securityGroup: null });
this.fetchSubnets({ vpc, region: this.selectedRegion });
this.fetchSecurityGroups({ vpc, region: this.selectedRegion });
}, },
}, },
}; };
@ -233,7 +315,12 @@ export default {
<h2> <h2>
{{ s__('ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster') }} {{ s__('ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster') }}
</h2> </h2>
<p v-html="kubernetesIntegrationHelpText"></p> <div class="mb-3" v-html="kubernetesIntegrationHelpText"></div>
<div class="mb-3">
<button class="btn btn-link js-sign-out" @click.prevent="signOut()">
{{ s__('ClusterIntegration|Select a different AWS role') }}
</button>
</div>
<div class="form-group"> <div class="form-group">
<label class="label-bold" for="eks-cluster-name">{{ <label class="label-bold" for="eks-cluster-name">{{
s__('ClusterIntegration|Kubernetes cluster name') s__('ClusterIntegration|Kubernetes cluster name')
@ -273,7 +360,7 @@ export default {
<cluster-form-dropdown <cluster-form-dropdown
field-id="eks-role" field-id="eks-role"
field-name="eks-role" field-name="eks-role"
:input="selectedRole" :value="selectedRole"
:items="roles" :items="roles"
:loading="isLoadingRoles" :loading="isLoadingRoles"
:loading-text="s__('ClusterIntegration|Loading IAM Roles')" :loading-text="s__('ClusterIntegration|Loading IAM Roles')"
@ -288,13 +375,21 @@ export default {
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="label-bold" for="eks-role">{{ s__('ClusterIntegration|Region') }}</label> <label class="label-bold" for="eks-role">{{ s__('ClusterIntegration|Region') }}</label>
<region-dropdown <cluster-form-dropdown
field-id="eks-region"
field-name="eks-region"
:value="selectedRegion" :value="selectedRegion"
:regions="regions" :items="regions"
:error="loadingRegionsError"
:loading="isLoadingRegions" :loading="isLoadingRegions"
:loading-text="s__('ClusterIntegration|Loading Regions')"
:placeholder="s__('ClusterIntergation|Select a region')"
:search-field-placeholder="s__('ClusterIntegration|Search regions')"
:empty-text="s__('ClusterIntegration|No region found')"
:has-errors="Boolean(loadingRegionsError)"
:error-message="s__('ClusterIntegration|Could not load regions from your AWS account')"
@input="setRegionAndFetchVpcsAndKeyPairs($event)" @input="setRegionAndFetchVpcsAndKeyPairs($event)"
/> />
<p class="form-text text-muted" v-html="regionsDropdownHelpText"></p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="label-bold" for="eks-key-pair">{{ <label class="label-bold" for="eks-key-pair">{{
@ -303,7 +398,7 @@ export default {
<cluster-form-dropdown <cluster-form-dropdown
field-id="eks-key-pair" field-id="eks-key-pair"
field-name="eks-key-pair" field-name="eks-key-pair"
:input="selectedKeyPair" :value="selectedKeyPair"
:items="keyPairs" :items="keyPairs"
:disabled="keyPairDropdownDisabled" :disabled="keyPairDropdownDisabled"
:disabled-text="s__('ClusterIntegration|Select a region to choose a Key Pair')" :disabled-text="s__('ClusterIntegration|Select a region to choose a Key Pair')"
@ -323,7 +418,7 @@ export default {
<cluster-form-dropdown <cluster-form-dropdown
field-id="eks-vpc" field-id="eks-vpc"
field-name="eks-vpc" field-name="eks-vpc"
:input="selectedVpc" :value="selectedVpc"
:items="vpcs" :items="vpcs"
:loading="isLoadingVpcs" :loading="isLoadingVpcs"
:disabled="vpcDropdownDisabled" :disabled="vpcDropdownDisabled"
@ -339,11 +434,12 @@ export default {
<p class="form-text text-muted" v-html="vpcDropdownHelpText"></p> <p class="form-text text-muted" v-html="vpcDropdownHelpText"></p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="label-bold" for="eks-role">{{ s__('ClusterIntegration|Subnet') }}</label> <label class="label-bold" for="eks-role">{{ s__('ClusterIntegration|Subnets') }}</label>
<cluster-form-dropdown <cluster-form-dropdown
field-id="eks-subnet" field-id="eks-subnet"
field-name="eks-subnet" field-name="eks-subnet"
:input="selectedSubnet" multiple
:value="selectedSubnet"
:items="subnets" :items="subnets"
:loading="isLoadingSubnets" :loading="isLoadingSubnets"
:disabled="subnetDropdownDisabled" :disabled="subnetDropdownDisabled"
@ -360,12 +456,12 @@ export default {
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="label-bold" for="eks-security-group">{{ <label class="label-bold" for="eks-security-group">{{
s__('ClusterIntegration|Security groups') s__('ClusterIntegration|Security group')
}}</label> }}</label>
<cluster-form-dropdown <cluster-form-dropdown
field-id="eks-security-group" field-id="eks-security-group"
field-name="eks-security-group" field-name="eks-security-group"
:input="selectedSecurityGroup" :value="selectedSecurityGroup"
:items="securityGroups" :items="securityGroups"
:loading="isLoadingSecurityGroups" :loading="isLoadingSecurityGroups"
:disabled="securityGroupDropdownDisabled" :disabled="securityGroupDropdownDisabled"
@ -382,6 +478,39 @@ export default {
/> />
<p class="form-text text-muted" v-html="securityGroupDropdownHelpText"></p> <p class="form-text text-muted" v-html="securityGroupDropdownHelpText"></p>
</div> </div>
<div class="form-group">
<label class="label-bold" for="eks-instance-type">{{
s__('ClusterIntegration|Instance type')
}}</label>
<cluster-form-dropdown
field-id="eks-instance-type"
field-name="eks-instance-type"
:value="selectedInstanceType"
:items="instanceTypes"
:loading="isLoadingInstanceTypes"
:loading-text="s__('ClusterIntegration|Loading instance types')"
:placeholder="s__('ClusterIntergation|Select an instance type')"
:search-field-placeholder="s__('ClusterIntegration|Search instance types')"
:empty-text="s__('ClusterIntegration|No instance type found')"
:has-errors="Boolean(loadingInstanceTypesError)"
:error-message="s__('ClusterIntegration|Could not load instance types')"
@input="setInstanceType({ instanceType: $event })"
/>
<p class="form-text text-muted" v-html="instanceTypesDropdownHelpText"></p>
</div>
<div class="form-group">
<label class="label-bold" for="eks-node-count">{{
s__('ClusterIntegration|Number of nodes')
}}</label>
<gl-form-input
id="eks-node-count"
type="number"
min="1"
step="1"
:value="nodeCount"
@input="setNodeCount({ nodeCount: $event })"
/>
</div>
<div class="form-group"> <div class="form-group">
<gl-form-checkbox <gl-form-checkbox
:checked="gitlabManagedCluster" :checked="gitlabManagedCluster"
@ -390,5 +519,14 @@ export default {
> >
<p class="form-text text-muted" v-html="gitlabManagedHelpText"></p> <p class="form-text text-muted" v-html="gitlabManagedHelpText"></p>
</div> </div>
<div class="form-group">
<loading-button
class="js-create-cluster btn-success"
:disabled="createClusterButtonDisabled"
:loading="isCreatingCluster"
:label="createClusterButtonLabel"
@click="createCluster()"
/>
</div>
</form> </form>
</template> </template>

View file

@ -1,63 +0,0 @@
<script>
import { sprintf, s__ } from '~/locale';
import ClusterFormDropdown from './cluster_form_dropdown.vue';
export default {
components: {
ClusterFormDropdown,
},
props: {
regions: {
type: Array,
required: false,
default: () => [],
},
loading: {
type: Boolean,
required: false,
default: false,
},
error: {
type: Object,
required: false,
default: null,
},
},
computed: {
hasErrors() {
return Boolean(this.error);
},
helpText() {
return sprintf(
s__('ClusterIntegration|Learn more about %{startLink}Regions%{endLink}.'),
{
startLink:
'<a href="https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/" target="_blank" rel="noopener noreferrer">',
endLink: '</a>',
},
false,
);
},
},
};
</script>
<template>
<div>
<cluster-form-dropdown
field-id="eks-region"
field-name="eks-region"
:items="regions"
:loading="loading"
:loading-text="s__('ClusterIntegration|Loading Regions')"
:placeholder="s__('ClusterIntergation|Select a region')"
:search-field-placeholder="s__('ClusterIntegration|Search regions')"
:empty-text="s__('ClusterIntegration|No region found')"
:has-errors="hasErrors"
:error-message="s__('ClusterIntegration|Could not load regions from your AWS account')"
v-bind="$attrs"
v-on="$listeners"
/>
<p class="form-text text-muted" v-html="helpText"></p>
</div>
</template>

View file

@ -1,3 +1,141 @@
<script>
import { GlFormInput } from '@gitlab/ui';
import { sprintf, s__, __ } from '~/locale';
import _ from 'underscore';
import { mapState, mapActions } from 'vuex';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
export default {
components: {
GlFormInput,
LoadingButton,
ClipboardButton,
},
props: {
accountAndExternalIdsHelpPath: {
type: String,
required: true,
},
createRoleArnHelpPath: {
type: String,
required: true,
},
externalLinkIcon: {
type: String,
required: true,
},
},
data() {
return {
roleArn: '',
};
},
computed: {
...mapState(['accountId', 'externalId', 'isCreatingRole', 'createRoleError']),
submitButtonDisabled() {
return this.isCreatingRole || !this.roleArn;
},
submitButtonLabel() {
return this.isCreatingRole
? __('Authenticating')
: s__('ClusterIntegration|Authenticate with AWS');
},
accountAndExternalIdsHelpText() {
const escapedUrl = _.escape(this.accountAndExternalIdsHelpPath);
return sprintf(
s__(
'ClusterIntegration|Create a provision role on %{startAwsLink}Amazon Web Services %{externalLinkIcon}%{endLink} using the account and external ID above. %{startMoreInfoLink}More information%{endLink}',
),
{
startAwsLink:
'<a href="https://console.aws.amazon.com/iam/home?#roles" target="_blank" rel="noopener noreferrer">',
startMoreInfoLink: `<a href="${escapedUrl}" target="_blank" rel="noopener noreferrer">`,
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>',
},
false,
);
},
provisionRoleArnHelpText() {
const escapedUrl = _.escape(this.createRoleArnHelpPath);
return sprintf(
s__(
'ClusterIntegration|The Amazon Resource Name (ARN) associated with your role. If you do not have a provision role, first create one on %{startAwsLink}Amazon Web Services %{externalLinkIcon}%{endLink} using the above account and external IDs. %{startMoreInfoLink}More information%{endLink}',
),
{
startAwsLink:
'<a href="https://console.aws.amazon.com/iam/home?#roles" target="_blank" rel="noopener noreferrer">',
startMoreInfoLink: `<a href="${escapedUrl}" target="_blank" rel="noopener noreferrer">`,
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>',
},
false,
);
},
},
methods: {
...mapActions(['createRole']),
},
};
</script>
<template> <template>
<form name="service-credentials-form"></form> <form name="service-credentials-form" @submit.prevent="createRole({ roleArn, externalId })">
<h2>{{ s__('ClusterIntegration|Authenticate with Amazon Web Services') }}</h2>
<p>
{{
s__(
'ClusterIntegration|You must grant access to your organizations AWS resources in order to create a new EKS cluster. To grant access, create a provision role using the account and external ID below and provide us the ARN.',
)
}}
</p>
<div v-if="createRoleError" class="js-invalid-credentials bs-callout bs-callout-danger">
{{ createRoleError }}
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="gitlab-account-id">{{ __('Account ID') }}</label>
<div class="input-group">
<gl-form-input id="gitlab-account-id" type="text" readonly :value="accountId" />
<div class="input-group-append">
<clipboard-button
:text="accountId"
:title="__('Copy Account ID to clipboard')"
class="input-group-text js-copy-account-id-button"
/>
</div>
</div>
</div>
<div class="form-group col-md-6">
<label for="eks-external-id">{{ __('External ID') }}</label>
<div class="input-group">
<gl-form-input id="eks-external-id" type="text" readonly :value="externalId" />
<div class="input-group-append">
<clipboard-button
:text="externalId"
:title="__('Copy External ID to clipboard')"
class="input-group-text js-copy-external-id-button"
/>
</div>
</div>
</div>
<div class="col-12 mb-3 mt-n3">
<p class="form-text text-muted" v-html="accountAndExternalIdsHelpText"></p>
</div>
</div>
<div class="form-group">
<label for="eks-provision-role-arn">{{ s__('ClusterIntegration|Provision Role ARN') }}</label>
<gl-form-input id="eks-provision-role-arn" v-model="roleArn" />
<p class="form-text text-muted" v-html="provisionRoleArnHelpText"></p>
</div>
<loading-button
class="js-submit-service-credentials btn-success"
type="submit"
:disabled="submitButtonDisabled"
:loading="isCreatingRole"
:label="submitButtonLabel"
/>
</form>
</template> </template>

View file

@ -1,7 +1,2 @@
// eslint-disable-next-line import/prefer-default-export // eslint-disable-next-line import/prefer-default-export
export const KUBERNETES_VERSIONS = [ export const KUBERNETES_VERSIONS = [{ name: '1.14', value: '1.14' }];
{ name: '1.14', value: '1.14' },
{ name: '1.13', value: '1.13' },
{ name: '1.12', value: '1.12' },
{ name: '1.11', value: '1.11' },
];

View file

@ -1,16 +1,54 @@
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { parseBoolean } from '~/lib/utils/common_utils';
import CreateEksCluster from './components/create_eks_cluster.vue'; import CreateEksCluster from './components/create_eks_cluster.vue';
import createStore from './store'; import createStore from './store';
Vue.use(Vuex); Vue.use(Vuex);
export default el => { export default el => {
const { gitlabManagedClusterHelpPath, kubernetesIntegrationHelpPath } = el.dataset; const {
gitlabManagedClusterHelpPath,
kubernetesIntegrationHelpPath,
accountAndExternalIdsHelpPath,
createRoleArnHelpPath,
getRolesPath,
getRegionsPath,
getKeyPairsPath,
getVpcsPath,
getSubnetsPath,
getSecurityGroupsPath,
getInstanceTypesPath,
externalId,
accountId,
hasCredentials,
createRolePath,
createClusterPath,
signOutPath,
externalLinkIcon,
} = el.dataset;
return new Vue({ return new Vue({
el, el,
store: createStore(), store: createStore({
initialState: {
hasCredentials: parseBoolean(hasCredentials),
externalId,
accountId,
createRolePath,
createClusterPath,
signOutPath,
},
apiPaths: {
getRolesPath,
getRegionsPath,
getKeyPairsPath,
getVpcsPath,
getSubnetsPath,
getSecurityGroupsPath,
getInstanceTypesPath,
},
}),
components: { components: {
CreateEksCluster, CreateEksCluster,
}, },
@ -19,6 +57,9 @@ export default el => {
props: { props: {
gitlabManagedClusterHelpPath, gitlabManagedClusterHelpPath,
kubernetesIntegrationHelpPath, kubernetesIntegrationHelpPath,
accountAndExternalIdsHelpPath,
createRoleArnHelpPath,
externalLinkIcon,
}, },
}); });
}, },

View file

@ -1,84 +1,58 @@
import EC2 from 'aws-sdk/clients/ec2'; import axios from '~/lib/utils/axios_utils';
import IAM from 'aws-sdk/clients/iam';
export const fetchRoles = () => { export default apiPaths => ({
const iam = new IAM(); fetchRoles() {
return axios
return iam .get(apiPaths.getRolesPath)
.listRoles() .then(({ data: { roles } }) =>
.promise() roles.map(({ role_name: name, arn: value }) => ({ name, value })),
.then(({ Roles: roles }) => roles.map(({ RoleName: name }) => ({ name }))); );
}; },
fetchKeyPairs({ region }) {
export const fetchKeyPairs = () => { return axios
const ec2 = new EC2(); .get(apiPaths.getKeyPairsPath, { params: { region } })
.then(({ data: { key_pairs: keyPairs } }) =>
return ec2 keyPairs.map(({ key_name }) => ({ name: key_name, value: key_name })),
.describeKeyPairs() );
.promise() },
.then(({ KeyPairs: keyPairs }) => keyPairs.map(({ RegionName: name }) => ({ name }))); fetchRegions() {
}; return axios.get(apiPaths.getRegionsPath).then(({ data: { regions } }) =>
regions.map(({ region_name }) => ({
export const fetchRegions = () => { name: region_name,
const ec2 = new EC2(); value: region_name,
return ec2
.describeRegions()
.promise()
.then(({ Regions: regions }) =>
regions.map(({ RegionName: name }) => ({
name,
value: name,
})), })),
); );
}; },
fetchVpcs({ region }) {
export const fetchVpcs = () => { return axios.get(apiPaths.getVpcsPath, { params: { region } }).then(({ data: { vpcs } }) =>
const ec2 = new EC2(); vpcs.map(({ vpc_id }) => ({
value: vpc_id,
return ec2 name: vpc_id,
.describeVpcs()
.promise()
.then(({ Vpcs: vpcs }) =>
vpcs.map(({ VpcId: id }) => ({
value: id,
name: id,
})), })),
); );
}; },
fetchSubnets({ vpc, region }) {
export const fetchSubnets = ({ vpc }) => { return axios
const ec2 = new EC2(); .get(apiPaths.getSubnetsPath, { params: { vpc_id: vpc, region } })
.then(({ data: { subnets } }) =>
return ec2 subnets.map(({ subnet_id }) => ({ name: subnet_id, value: subnet_id })),
.describeSubnets({ );
Filters: [ },
{ fetchSecurityGroups({ vpc, region }) {
Name: 'vpc-id', return axios
Values: [vpc], .get(apiPaths.getSecurityGroupsPath, { params: { vpc_id: vpc, region } })
}, .then(({ data: { security_groups: securityGroups } }) =>
], securityGroups.map(({ group_name: name, group_id: value }) => ({ name, value })),
}) );
.promise() },
.then(({ Subnets: subnets }) => subnets.map(({ SubnetId: id }) => ({ id, name: id }))); fetchInstanceTypes() {
}; return axios
.get(apiPaths.getInstanceTypesPath)
export const fetchSecurityGroups = ({ vpc }) => { .then(({ data: { instance_types: instanceTypes } }) =>
const ec2 = new EC2(); instanceTypes.map(({ instance_type_name }) => ({
name: instance_type_name,
return ec2 value: instance_type_name,
.describeSecurityGroups({ })),
Filters: [ );
{ },
Name: 'vpc-id', });
Values: [vpc],
},
],
})
.promise()
.then(({ SecurityGroups: securityGroups }) =>
securityGroups.map(({ GroupName: name, GroupId: value }) => ({ name, value })),
);
};
export default () => {};

View file

@ -1,4 +1,12 @@
import * as types from './mutation_types'; import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
const getErrorMessage = data => {
const errorKey = Object.keys(data)[0];
return data[errorKey][0];
};
export const setClusterName = ({ commit }, payload) => { export const setClusterName = ({ commit }, payload) => {
commit(types.SET_CLUSTER_NAME, payload); commit(types.SET_CLUSTER_NAME, payload);
@ -12,6 +20,68 @@ export const setKubernetesVersion = ({ commit }, payload) => {
commit(types.SET_KUBERNETES_VERSION, payload); commit(types.SET_KUBERNETES_VERSION, payload);
}; };
export const createRole = ({ dispatch, state: { createRolePath } }, payload) => {
dispatch('requestCreateRole');
return axios
.post(createRolePath, {
role_arn: payload.roleArn,
role_external_id: payload.externalId,
})
.then(() => dispatch('createRoleSuccess'))
.catch(error => dispatch('createRoleError', { error }));
};
export const requestCreateRole = ({ commit }) => {
commit(types.REQUEST_CREATE_ROLE);
};
export const createRoleSuccess = ({ commit }) => {
commit(types.CREATE_ROLE_SUCCESS);
};
export const createRoleError = ({ commit }, payload) => {
commit(types.CREATE_ROLE_ERROR, payload);
};
export const createCluster = ({ dispatch, state }) => {
dispatch('requestCreateCluster');
return axios
.post(state.createClusterPath, {
name: state.clusterName,
environment_scope: state.environmentScope,
managed: state.gitlabManagedCluster,
provider_aws_attributes: {
region: state.selectedRegion,
vpc_id: state.selectedVpc,
subnet_ids: state.selectedSubnet,
role_arn: state.selectedRole,
key_name: state.selectedKeyPair,
security_group_id: state.selectedSecurityGroup,
instance_type: state.selectedInstanceType,
num_nodes: state.nodeCount,
},
})
.then(({ headers: { location } }) => dispatch('createClusterSuccess', location))
.catch(({ response: { data } }) => {
dispatch('createClusterError', data);
});
};
export const requestCreateCluster = ({ commit }) => {
commit(types.REQUEST_CREATE_CLUSTER);
};
export const createClusterSuccess = (_, location) => {
window.location.assign(location);
};
export const createClusterError = ({ commit }, error) => {
commit(types.CREATE_CLUSTER_ERROR, error);
createFlash(getErrorMessage(error));
};
export const setRegion = ({ commit }, payload) => { export const setRegion = ({ commit }, payload) => {
commit(types.SET_REGION, payload); commit(types.SET_REGION, payload);
}; };
@ -40,4 +110,16 @@ export const setGitlabManagedCluster = ({ commit }, payload) => {
commit(types.SET_GITLAB_MANAGED_CLUSTER, payload); commit(types.SET_GITLAB_MANAGED_CLUSTER, payload);
}; };
export default () => {}; export const setInstanceType = ({ commit }, payload) => {
commit(types.SET_INSTANCE_TYPE, payload);
};
export const setNodeCount = ({ commit }, payload) => {
commit(types.SET_NODE_COUNT, payload);
};
export const signOut = ({ commit, state: { signOutPath } }) =>
axios
.delete(signOutPath)
.then(() => commit(types.SIGN_OUT))
.catch(({ response: { data } }) => createFlash(getErrorMessage(data)));

View file

@ -6,14 +6,16 @@ import state from './state';
import clusterDropdownStore from './cluster_dropdown'; import clusterDropdownStore from './cluster_dropdown';
import * as awsServices from '../services/aws_services_facade'; import awsServicesFactory from '../services/aws_services_facade';
const createStore = () => const createStore = ({ initialState, apiPaths }) => {
new Vuex.Store({ const awsServices = awsServicesFactory(apiPaths);
return new Vuex.Store({
actions, actions,
getters, getters,
mutations, mutations,
state: state(), state: Object.assign(state(), initialState),
modules: { modules: {
roles: { roles: {
namespaced: true, namespaced: true,
@ -39,7 +41,12 @@ const createStore = () =>
namespaced: true, namespaced: true,
...clusterDropdownStore(awsServices.fetchSecurityGroups), ...clusterDropdownStore(awsServices.fetchSecurityGroups),
}, },
instanceTypes: {
namespaced: true,
...clusterDropdownStore(awsServices.fetchInstanceTypes),
},
}, },
}); });
};
export default createStore; export default createStore;

View file

@ -7,4 +7,13 @@ export const SET_KEY_PAIR = 'SET_KEY_PAIR';
export const SET_SUBNET = 'SET_SUBNET'; export const SET_SUBNET = 'SET_SUBNET';
export const SET_ROLE = 'SET_ROLE'; export const SET_ROLE = 'SET_ROLE';
export const SET_SECURITY_GROUP = 'SET_SECURITY_GROUP'; export const SET_SECURITY_GROUP = 'SET_SECURITY_GROUP';
export const SET_INSTANCE_TYPE = 'SET_INSTANCE_TYPE';
export const SET_NODE_COUNT = 'SET_NODE_COUNT';
export const SET_GITLAB_MANAGED_CLUSTER = 'SET_GITLAB_MANAGED_CLUSTER'; export const SET_GITLAB_MANAGED_CLUSTER = 'SET_GITLAB_MANAGED_CLUSTER';
export const REQUEST_CREATE_ROLE = 'REQUEST_CREATE_ROLE';
export const CREATE_ROLE_SUCCESS = 'CREATE_ROLE_SUCCESS';
export const CREATE_ROLE_ERROR = 'CREATE_ROLE_ERROR';
export const SIGN_OUT = 'SIGN_OUT';
export const REQUEST_CREATE_CLUSTER = 'REQUEST_CREATE_CLUSTER';
export const CREATE_CLUSTER_SUCCESS = 'CREATE_CLUSTER_SUCCESS';
export const CREATE_CLUSTER_ERROR = 'CREATE_CLUSTER_ERROR';

View file

@ -28,7 +28,39 @@ export default {
[types.SET_SECURITY_GROUP](state, { securityGroup }) { [types.SET_SECURITY_GROUP](state, { securityGroup }) {
state.selectedSecurityGroup = securityGroup; state.selectedSecurityGroup = securityGroup;
}, },
[types.SET_INSTANCE_TYPE](state, { instanceType }) {
state.selectedInstanceType = instanceType;
},
[types.SET_NODE_COUNT](state, { nodeCount }) {
state.nodeCount = nodeCount;
},
[types.SET_GITLAB_MANAGED_CLUSTER](state, { gitlabManagedCluster }) { [types.SET_GITLAB_MANAGED_CLUSTER](state, { gitlabManagedCluster }) {
state.gitlabManagedCluster = gitlabManagedCluster; state.gitlabManagedCluster = gitlabManagedCluster;
}, },
[types.REQUEST_CREATE_ROLE](state) {
state.isCreatingRole = true;
state.createRoleError = null;
state.hasCredentials = false;
},
[types.CREATE_ROLE_SUCCESS](state) {
state.isCreatingRole = false;
state.createRoleError = null;
state.hasCredentials = true;
},
[types.CREATE_ROLE_ERROR](state, { error }) {
state.isCreatingRole = false;
state.createRoleError = error;
state.hasCredentials = false;
},
[types.REQUEST_CREATE_CLUSTER](state) {
state.isCreatingCluster = true;
state.createClusterError = null;
},
[types.CREATE_CLUSTER_ERROR](state, { error }) {
state.isCreatingCluster = false;
state.createClusterError = error;
},
[types.SIGN_OUT](state) {
state.hasCredentials = false;
},
}; };

View file

@ -1,18 +1,31 @@
import { KUBERNETES_VERSIONS } from '../constants'; import { KUBERNETES_VERSIONS } from '../constants';
const [{ value: kubernetesVersion }] = KUBERNETES_VERSIONS;
export default () => ({ export default () => ({
isValidatingCredentials: false, createRolePath: null,
validCredentials: false,
isCreatingRole: false,
roleCreated: false,
createRoleError: false,
accountId: '',
externalId: '',
clusterName: '', clusterName: '',
environmentScope: '*', environmentScope: '*',
kubernetesVersion: [KUBERNETES_VERSIONS].value, kubernetesVersion,
selectedRegion: '', selectedRegion: '',
selectedRole: '', selectedRole: '',
selectedKeyPair: '', selectedKeyPair: '',
selectedVpc: '', selectedVpc: '',
selectedSubnet: '', selectedSubnet: '',
selectedSecurityGroup: '', selectedSecurityGroup: '',
selectedInstanceType: 'm5.large',
nodeCount: '3',
isCreatingCluster: false,
createClusterError: false,
gitlabManagedCluster: true, gitlabManagedCluster: true,
}); });

View file

@ -0,0 +1,37 @@
import initGkeDropdowns from './gke_cluster';
import initGkeNamespace from './gke_cluster_namespace';
import PersistentUserCallout from '~/persistent_user_callout';
const newClusterViews = [':clusters:new', ':clusters:create_gcp', ':clusters:create_user'];
const isProjectLevelCluster = page => page.startsWith('project:clusters');
export default (document, gon) => {
const { page } = document.body.dataset;
const isNewClusterView = newClusterViews.some(view => page.endsWith(view));
if (!isNewClusterView) {
return;
}
const callout = document.querySelector('.gcp-signup-offer');
PersistentUserCallout.factory(callout);
initGkeDropdowns();
if (gon.features.createEksClusters) {
import(/* webpackChunkName: 'eks_cluster' */ '~/create_cluster/eks_cluster')
.then(({ default: initCreateEKSCluster }) => {
const el = document.querySelector('.js-create-eks-cluster-form-container');
if (el) {
initCreateEKSCluster(el);
}
})
.catch(() => {});
}
if (isProjectLevelCluster(page)) {
initGkeNamespace();
}
};

View file

@ -1,44 +0,0 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
import { GlButton } from '@gitlab/ui';
export default {
name: 'StageCardListItem',
components: {
Icon,
GlButton,
},
props: {
isActive: {
type: Boolean,
required: true,
},
canEdit: {
type: Boolean,
default: false,
required: false,
},
},
};
</script>
<template>
<div
:class="{ active: isActive }"
class="stage-nav-item d-flex pl-4 pr-4 m-0 mb-1 ml-2 rounded border-color-default border-style-solid border-width-1px"
>
<slot></slot>
<div v-if="canEdit" class="dropdown">
<gl-button
:title="__('More actions')"
class="more-actions-toggle btn btn-transparent p-0"
data-toggle="dropdown"
>
<icon class="icon" name="ellipsis_v" />
</gl-button>
<ul class="more-actions-dropdown dropdown-menu dropdown-open-left">
<slot name="dropdown-options"></slot>
</ul>
</div>
</div>
</template>

View file

@ -1,11 +1,6 @@
<script> <script>
import StageCardListItem from './stage_card_list_item.vue';
export default { export default {
name: 'StageNavItem', name: 'StageNavItem',
components: {
StageCardListItem,
},
props: { props: {
isDefaultStage: { isDefaultStage: {
type: Boolean, type: Boolean,
@ -40,16 +35,16 @@ export default {
hasValue() { hasValue() {
return this.value && this.value.length > 0; return this.value && this.value.length > 0;
}, },
editable() {
return this.isUserAllowed && this.canEdit;
},
}, },
}; };
</script> </script>
<template> <template>
<li @click="$emit('select')"> <li @click="$emit('select')">
<stage-card-list-item :is-active="isActive" :can-edit="editable"> <div
:class="{ active: isActive }"
class="stage-nav-item d-flex pl-4 pr-4 m-0 mb-1 ml-2 rounded border-color-default border-style-solid border-width-1px"
>
<div class="stage-nav-item-cell stage-name p-0" :class="{ 'font-weight-bold': isActive }"> <div class="stage-nav-item-cell stage-name p-0" :class="{ 'font-weight-bold': isActive }">
{{ title }} {{ title }}
</div> </div>
@ -62,27 +57,6 @@ export default {
<span class="not-available">{{ __('Not available') }}</span> <span class="not-available">{{ __('Not available') }}</span>
</template> </template>
</div> </div>
<template v-slot:dropdown-options> </div>
<template v-if="isDefaultStage">
<li>
<button type="button" class="btn-default btn-transparent">
{{ __('Hide stage') }}
</button>
</li>
</template>
<template v-else>
<li>
<button type="button" class="btn-default btn-transparent">
{{ __('Edit stage') }}
</button>
</li>
<li>
<button type="button" class="btn-danger danger">
{{ __('Remove stage') }}
</button>
</li>
</template>
</template>
</stage-card-list-item>
</li> </li>
</template> </template>

View file

@ -124,8 +124,10 @@ export default {
:diff-viewer-mode="diffViewerMode" :diff-viewer-mode="diffViewerMode"
:new-path="diffFile.new_path" :new-path="diffFile.new_path"
:new-sha="diffFile.diff_refs.head_sha" :new-sha="diffFile.diff_refs.head_sha"
:new-size="diffFile.new_size"
:old-path="diffFile.old_path" :old-path="diffFile.old_path"
:old-sha="diffFile.diff_refs.base_sha" :old-sha="diffFile.diff_refs.base_sha"
:old-size="diffFile.old_size"
:file-hash="diffFileHash" :file-hash="diffFileHash"
:project-path="projectPath" :project-path="projectPath"
:a-mode="diffFile.a_mode" :a-mode="diffFile.a_mode"

View file

@ -54,11 +54,12 @@ export default {
showLoadingIcon() { showLoadingIcon() {
return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed); return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed);
}, },
hasDiffLines() { hasDiff() {
return ( return (
this.file.highlighted_diff_lines && (this.file.highlighted_diff_lines &&
this.file.parallel_diff_lines && this.file.parallel_diff_lines &&
this.file.parallel_diff_lines.length > 0 this.file.parallel_diff_lines.length > 0) ||
!this.file.blob.readable_text
); );
}, },
isFileTooLarge() { isFileTooLarge() {
@ -82,7 +83,7 @@ export default {
}, },
watch: { watch: {
isCollapsed: function fileCollapsedWatch(newVal, oldVal) { isCollapsed: function fileCollapsedWatch(newVal, oldVal) {
if (!newVal && oldVal && !this.hasDiffLines) { if (!newVal && oldVal && !this.hasDiff) {
this.handleLoadCollapsedDiff(); this.handleLoadCollapsedDiff();
} }
@ -103,7 +104,7 @@ export default {
'setFileCollapsed', 'setFileCollapsed',
]), ]),
handleToggle() { handleToggle() {
if (!this.hasDiffLines) { if (!this.hasDiff) {
this.handleLoadCollapsedDiff(); this.handleLoadCollapsedDiff();
} else { } else {
this.isCollapsed = !this.isCollapsed; this.isCollapsed = !this.isCollapsed;

View file

@ -290,5 +290,5 @@ export default function dropzoneInput(form) {
formTextarea.focus(); formTextarea.focus();
}); });
return Dropzone.forElement($formDropzone.get(0)); return $formDropzone.get(0) ? Dropzone.forElement($formDropzone.get(0)) : null;
} }

View file

@ -0,0 +1,141 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import dateFormat from 'dateformat';
import { __, sprintf } from '~/locale';
import { GlButton, GlLink, GlLoadingIcon } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import Stacktrace from './stacktrace.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { trackClickErrorLinkToSentryOptions } from '../utils';
export default {
components: {
GlButton,
GlLink,
GlLoadingIcon,
TooltipOnTruncate,
Icon,
Stacktrace,
},
directives: {
TrackEvent: TrackEventDirective,
},
mixins: [timeagoMixin],
props: {
issueDetailsPath: {
type: String,
required: true,
},
issueStackTracePath: {
type: String,
required: true,
},
},
computed: {
...mapState('details', ['error', 'loading', 'loadingStacktrace', 'stacktraceData']),
...mapGetters('details', ['stacktrace']),
reported() {
return sprintf(
__('Reported %{timeAgo} by %{reportedBy}'),
{
reportedBy: `<strong>${this.error.culprit}</strong>`,
timeAgo: this.timeFormated(this.stacktraceData.date_received),
},
false,
);
},
firstReleaseLink() {
return `${this.error.external_base_url}/releases/${this.error.first_release_short_version}`;
},
lastReleaseLink() {
return `${this.error.external_base_url}releases/${this.error.last_release_short_version}`;
},
showDetails() {
return Boolean(!this.loading && this.error && this.error.id);
},
showStacktrace() {
return Boolean(!this.loadingStacktrace && this.stacktrace && this.stacktrace.length);
},
},
mounted() {
this.startPollingDetails(this.issueDetailsPath);
this.startPollingStacktrace(this.issueStackTracePath);
},
methods: {
...mapActions('details', ['startPollingDetails', 'startPollingStacktrace']),
trackClickErrorLinkToSentryOptions,
formatDate(date) {
return `${this.timeFormated(date)} (${dateFormat(date, 'UTC:yyyy-mm-dd h:MM:ssTT Z')})`;
},
},
};
</script>
<template>
<div>
<div v-if="loading" class="py-3">
<gl-loading-icon :size="3" />
</div>
<div v-else-if="showDetails" class="error-details">
<div class="top-area align-items-center justify-content-between py-3">
<span v-if="!loadingStacktrace && stacktrace" v-html="reported"></span>
<!-- <gl-button class="my-3 ml-auto" variant="success">
{{ __('Create Issue') }}
</gl-button>-->
</div>
<div>
<tooltip-on-truncate :title="error.title" truncate-target="child" placement="top">
<h2 class="text-truncate">{{ error.title }}</h2>
</tooltip-on-truncate>
<h3>{{ __('Error details') }}</h3>
<ul>
<li>
<span class="bold">{{ __('Sentry event') }}:</span>
<gl-link
v-track-event="trackClickErrorLinkToSentryOptions(error.external_url)"
:href="error.external_url"
target="_blank"
>
<span class="text-truncate">{{ error.external_url }}</span>
<icon name="external-link" class="ml-1 flex-shrink-0" />
</gl-link>
</li>
<li v-if="error.first_release_short_version">
<span class="bold">{{ __('First seen') }}:</span>
{{ formatDate(error.first_seen) }}
<gl-link :href="firstReleaseLink" target="_blank">
<span>{{ __('Release') }}: {{ error.first_release_short_version }}</span>
</gl-link>
</li>
<li v-if="error.last_release_short_version">
<span class="bold">{{ __('Last seen') }}:</span>
{{ formatDate(error.last_seen) }}
<gl-link :href="lastReleaseLink" target="_blank">
<span>{{ __('Release') }}: {{ error.last_release_short_version }}</span>
</gl-link>
</li>
<li>
<span class="bold">{{ __('Events') }}:</span>
<span>{{ error.count }}</span>
</li>
<li>
<span class="bold">{{ __('Users') }}:</span>
<span>{{ error.user_count }}</span>
</li>
</ul>
<div v-if="loadingStacktrace" class="py-3">
<gl-loading-icon :size="3" />
</div>
<template v-if="showStacktrace">
<h3 class="my-4">{{ __('Stack trace') }}</h3>
<stacktrace :entries="stacktrace" />
</template>
</div>
</div>
</div>
</template>

View file

@ -1,11 +1,19 @@
<script> <script>
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { GlEmptyState, GlButton, GlLink, GlLoadingIcon, GlTable } from '@gitlab/ui'; import {
GlEmptyState,
GlButton,
GlLink,
GlLoadingIcon,
GlTable,
GlSearchBoxByType,
} from '@gitlab/ui';
import { visitUrl } from '~/lib/utils/url_utility';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { __ } from '~/locale'; import { __ } from '~/locale';
import TrackEventDirective from '~/vue_shared/directives/track_event'; import TrackEventDirective from '~/vue_shared/directives/track_event';
import { trackViewInSentryOptions, trackClickErrorLinkToSentryOptions } from '../utils'; import { trackViewInSentryOptions } from '../utils';
export default { export default {
fields: [ fields: [
@ -20,6 +28,7 @@ export default {
GlLink, GlLink,
GlLoadingIcon, GlLoadingIcon,
GlTable, GlTable,
GlSearchBoxByType,
Icon, Icon,
TimeAgo, TimeAgo,
}, },
@ -48,8 +57,17 @@ export default {
required: true, required: true,
}, },
}, },
data() {
return {
errorSearchQuery: '',
};
},
computed: { computed: {
...mapState(['errors', 'externalUrl', 'loading']), ...mapState('list', ['errors', 'externalUrl', 'loading']),
...mapGetters('list', ['filterErrorsByTitle']),
filteredErrors() {
return this.errorSearchQuery ? this.filterErrorsByTitle(this.errorSearchQuery) : this.errors;
},
}, },
created() { created() {
if (this.errorTrackingEnabled) { if (this.errorTrackingEnabled) {
@ -57,9 +75,11 @@ export default {
} }
}, },
methods: { methods: {
...mapActions(['startPolling', 'restartPolling']), ...mapActions('list', ['startPolling', 'restartPolling']),
trackViewInSentryOptions, trackViewInSentryOptions,
trackClickErrorLinkToSentryOptions, viewDetails(errorId) {
visitUrl(`error_tracking/${errorId}/details`);
},
}, },
}; };
</script> </script>
@ -71,10 +91,17 @@ export default {
<gl-loading-icon :size="3" /> <gl-loading-icon :size="3" />
</div> </div>
<div v-else> <div v-else>
<div class="d-flex justify-content-end"> <div class="d-flex flex-row justify-content-around bg-secondary border">
<gl-search-box-by-type
v-model="errorSearchQuery"
class="col-lg-10 m-3 p-0"
:placeholder="__('Search or filter results...')"
type="search"
autofocus
/>
<gl-button <gl-button
v-track-event="trackViewInSentryOptions(externalUrl)" v-track-event="trackViewInSentryOptions(externalUrl)"
class="my-3 ml-auto" class="m-3"
variant="primary" variant="primary"
:href="externalUrl" :href="externalUrl"
target="_blank" target="_blank"
@ -84,7 +111,14 @@ export default {
</gl-button> </gl-button>
</div> </div>
<gl-table :items="errors" :fields="$options.fields" :show-empty="true" fixed stacked="sm"> <gl-table
class="mt-3"
:items="filteredErrors"
:fields="$options.fields"
:show-empty="true"
fixed
stacked="sm"
>
<template slot="HEAD_events" slot-scope="data"> <template slot="HEAD_events" slot-scope="data">
<div class="text-md-right">{{ data.label }}</div> <div class="text-md-right">{{ data.label }}</div>
</template> </template>
@ -94,13 +128,11 @@ export default {
<template slot="error" slot-scope="errors"> <template slot="error" slot-scope="errors">
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<gl-link <gl-link
v-track-event="trackClickErrorLinkToSentryOptions(errors.item.externalUrl)"
:href="errors.item.externalUrl"
class="d-flex text-dark" class="d-flex text-dark"
target="_blank" target="_blank"
@click="viewDetails(errors.item.id)"
> >
<strong class="text-truncate">{{ errors.item.title.trim() }}</strong> <strong class="text-truncate">{{ errors.item.title.trim() }}</strong>
<icon name="external-link" class="ml-1 flex-shrink-0" />
</gl-link> </gl-link>
<span class="text-secondary text-truncate"> <span class="text-secondary text-truncate">
{{ errors.item.culprit }} {{ errors.item.culprit }}

View file

@ -0,0 +1,33 @@
<script>
import StackTraceEntry from './stacktrace_entry.vue';
export default {
components: {
StackTraceEntry,
},
props: {
entries: {
type: Array,
required: true,
},
},
methods: {
isFirstEntry(index) {
return index === 0;
},
},
};
</script>
<template>
<div class="stacktrace">
<stack-trace-entry
v-for="(entry, index) in entries"
:key="`stacktrace-entry-${index}`"
:lines="entry.context"
:file-path="entry.filename"
:error-line="entry.lineNo"
:expanded="isFirstEntry(index)"
/>
</div>
</template>

View file

@ -0,0 +1,110 @@
<script>
import { GlTooltip } from '@gitlab/ui';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
ClipboardButton,
FileIcon,
Icon,
},
directives: {
GlTooltip,
},
props: {
lines: {
type: Array,
required: true,
},
filePath: {
type: String,
required: true,
},
errorLine: {
type: Number,
required: true,
},
expanded: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
isExpanded: this.expanded,
};
},
computed: {
linesLength() {
return this.lines.length;
},
collapseIcon() {
return this.isExpanded ? 'chevron-down' : 'chevron-right';
},
},
methods: {
isHighlighted(lineNum) {
return lineNum === this.errorLine;
},
toggle() {
this.isExpanded = !this.isExpanded;
},
lineNum(line) {
return line[0];
},
lineCode(line) {
return line[1];
},
},
userColorScheme: window.gon.user_color_scheme,
};
</script>
<template>
<div class="file-holder">
<div ref="header" class="file-title file-title-flex-parent">
<div class="file-header-content ">
<div class="d-inline-block cursor-pointer" @click="toggle()">
<icon :name="collapseIcon" :size="16" aria-hidden="true" class="append-right-5" />
</div>
<div class="d-inline-block append-right-4">
<file-icon
:file-name="filePath"
:size="18"
aria-hidden="true"
css-classes="append-right-5"
/>
<strong v-gl-tooltip :title="filePath" class="file-title-name" data-container="body">
{{ filePath }}
</strong>
</div>
<clipboard-button
:title="__('Copy file path')"
:text="filePath"
css-class="btn-default btn-transparent btn-clipboard"
/>
</div>
</div>
<table v-if="isExpanded" :class="$options.userColorScheme" class="code js-syntax-highlight">
<tbody>
<template v-for="(line, index) in lines">
<tr :key="`stacktrace-line-${index}`" class="line_holder">
<td class="diff-line-num" :class="{ old: isHighlighted(lineNum(line)) }">
{{ lineNum(line) }}
</td>
<td
class="line_content"
:class="{ old: isHighlighted(lineNum(line)) }"
v-html="lineCode(line)"
></td>
</tr>
</template>
</tbody>
</table>
</div>
</template>

View file

@ -0,0 +1,25 @@
import Vue from 'vue';
import store from './store';
import ErrorDetails from './components/error_details.vue';
export default () => {
// eslint-disable-next-line no-new
new Vue({
el: '#js-error_details',
components: {
ErrorDetails,
},
store,
render(createElement) {
const domEl = document.querySelector(this.$options.el);
const { issueDetailsPath, issueStackTracePath } = domEl.dataset;
return createElement('error-details', {
props: {
issueDetailsPath,
issueStackTracePath,
},
});
},
});
};

View file

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

View file

@ -0,0 +1,63 @@
import service from '../../services';
import * as types from './mutation_types';
import createFlash from '~/flash';
import Poll from '~/lib/utils/poll';
import { __ } from '~/locale';
let stackTracePoll;
let detailPoll;
const stopPolling = poll => {
if (poll) poll.stop();
};
export function startPollingDetails({ commit }, endpoint) {
detailPoll = new Poll({
resource: service,
method: 'getSentryData',
data: { endpoint },
successCallback: ({ data }) => {
if (!data) {
detailPoll.restart();
return;
}
commit(types.SET_ERROR, data.error);
commit(types.SET_LOADING, false);
stopPolling(detailPoll);
},
errorCallback: () => {
commit(types.SET_LOADING, false);
createFlash(__('Failed to load error details from Sentry.'));
},
});
detailPoll.makeRequest();
}
export function startPollingStacktrace({ commit }, endpoint) {
stackTracePoll = new Poll({
resource: service,
method: 'getSentryData',
data: { endpoint },
successCallback: ({ data }) => {
if (!data) {
stackTracePoll.restart();
return;
}
commit(types.SET_STACKTRACE_DATA, data.error);
commit(types.SET_LOADING_STACKTRACE, false);
stopPolling(stackTracePoll);
},
errorCallback: () => {
commit(types.SET_LOADING_STACKTRACE, false);
createFlash(__('Failed to load stacktrace.'));
},
});
stackTracePoll.makeRequest();
}
export default () => {};

View file

@ -0,0 +1,3 @@
export const stacktrace = state => state.stacktraceData.stack_trace_entries.reverse();
export default () => {};

View file

@ -0,0 +1,4 @@
export const SET_ERROR = 'SET_ERRORS';
export const SET_LOADING = 'SET_LOADING';
export const SET_LOADING_STACKTRACE = 'SET_LOADING_STACKTRACE';
export const SET_STACKTRACE_DATA = 'SET_STACKTRACE_DATA';

View file

@ -0,0 +1,16 @@
import * as types from './mutation_types';
export default {
[types.SET_ERROR](state, data) {
state.error = data;
},
[types.SET_LOADING](state, loading) {
state.loading = loading;
},
[types.SET_LOADING_STACKTRACE](state, data) {
state.loadingStacktrace = data;
},
[types.SET_STACKTRACE_DATA](state, data) {
state.stacktraceData = data;
},
};

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