New upstream version 10.6.0+dfsg
This commit is contained in:
parent
2058b5ccaf
commit
514980f938
2165 changed files with 64683 additions and 31732 deletions
|
@ -11,11 +11,11 @@ engines:
|
|||
exclude_paths:
|
||||
- "lib/api/v3/*"
|
||||
eslint:
|
||||
# eslint-plugin-vue is locked to version 2 in codeclimate, we need version 4
|
||||
enabled: false
|
||||
enabled: true
|
||||
channel: "eslint-4"
|
||||
rubocop:
|
||||
enabled: true
|
||||
channel: "gitlab-rubocop-0-52"
|
||||
channel: "gitlab-rubocop-0-52-1"
|
||||
ratings:
|
||||
paths:
|
||||
- Gemfile.lock
|
||||
|
@ -45,3 +45,4 @@ exclude_paths:
|
|||
- log/
|
||||
- backups/
|
||||
- coverage-javascript/
|
||||
- plugins/
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
"import/no-commonjs": "error",
|
||||
"no-multiple-empty-lines": ["error", { "max": 1 }],
|
||||
"promise/catch-or-return": "error",
|
||||
"no-underscore-dangle": ["error", { "allow": ["__"]}],
|
||||
"no-underscore-dangle": ["error", { "allow": ["__", "_links"]}],
|
||||
"vue/html-self-closing": ["error", {
|
||||
"html": {
|
||||
"void": "always",
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -66,3 +66,4 @@ eslint-report.html
|
|||
/locale/**/LC_MESSAGES
|
||||
/locale/**/*.time_stamp
|
||||
/.rspec
|
||||
/plugins/*
|
||||
|
|
387
.gitlab-ci.yml
387
.gitlab-ci.yml
|
@ -1,4 +1,4 @@
|
|||
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6"
|
||||
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.16-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6"
|
||||
|
||||
.dedicated-runner: &dedicated-runner
|
||||
retry: 1
|
||||
|
@ -35,8 +35,14 @@ variables:
|
|||
|
||||
before_script:
|
||||
- bundle --version
|
||||
- date
|
||||
- source scripts/utils.sh
|
||||
- date
|
||||
- source scripts/prepare_build.sh
|
||||
- date
|
||||
|
||||
after_script:
|
||||
- date
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
@ -88,6 +94,26 @@ stages:
|
|||
- /(^docs[\/-].*|.*-docs$)/
|
||||
- /(^qa[\/-].*|.*-qa$)/
|
||||
|
||||
# Jobs that only need to pull cache
|
||||
.dedicated-no-docs-pull-cache-job: &dedicated-no-docs-pull-cache-job
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
<<: *pull-cache
|
||||
dependencies:
|
||||
- setup-test-env
|
||||
stage: test
|
||||
|
||||
# Jobs that do not need a DB
|
||||
.dedicated-no-docs-no-db-pull-cache-job: &dedicated-no-docs-no-db-pull-cache-job
|
||||
<<: *dedicated-no-docs-pull-cache-job
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
|
||||
.rake-exec: &rake-exec
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
script:
|
||||
- bundle exec rake $CI_JOB_NAME
|
||||
|
||||
.rspec-metadata: &rspec-metadata
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
|
@ -164,21 +190,23 @@ stages:
|
|||
- master@gitlab/gitlabhq
|
||||
- master@gitlab/gitlab-ee
|
||||
|
||||
##
|
||||
# Trigger a package build in omnibus-gitlab repository
|
||||
#
|
||||
package-qa:
|
||||
<<: *dedicated-runner
|
||||
image: ruby:2.4-alpine
|
||||
before_script: []
|
||||
stage: build
|
||||
cache: {}
|
||||
when: manual
|
||||
.gitlab-setup: &gitlab-setup
|
||||
<<: *dedicated-no-docs-pull-cache-job
|
||||
<<: *use-pg
|
||||
variables:
|
||||
CREATE_DB_USER: "true"
|
||||
script:
|
||||
- scripts/trigger-build-omnibus
|
||||
only:
|
||||
- //@gitlab-org/gitlab-ce
|
||||
- //@gitlab-org/gitlab-ee
|
||||
# Manually clone gitlab-test and only seed this project in
|
||||
# db/fixtures/development/04_project.rb thanks to SIZE=1 below
|
||||
- git clone https://gitlab.com/gitlab-org/gitlab-test.git
|
||||
/home/git/repositories/gitlab-org/gitlab-test.git
|
||||
- scripts/gitaly-test-spawn
|
||||
- force=yes SIZE=1 FIXTURE_PATH="db/fixtures/development" bundle exec rake gitlab:setup
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expire_in: 1d
|
||||
paths:
|
||||
- log/development.log
|
||||
|
||||
# Review docs base
|
||||
.review-docs: &review-docs
|
||||
|
@ -201,6 +229,47 @@ package-qa:
|
|||
only:
|
||||
- branches
|
||||
|
||||
# DB migration, rollback, and seed jobs
|
||||
.db-migrate-reset: &db-migrate-reset
|
||||
<<: *dedicated-no-docs-pull-cache-job
|
||||
script:
|
||||
- bundle exec rake db:migrate:reset
|
||||
|
||||
.migration-paths: &migration-paths
|
||||
<<: *dedicated-no-docs-pull-cache-job
|
||||
variables:
|
||||
CREATE_DB_USER: "true"
|
||||
script:
|
||||
- git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v9.3.0
|
||||
- git checkout -f FETCH_HEAD
|
||||
- bundle install $BUNDLE_INSTALL_FLAGS
|
||||
- date
|
||||
- cp config/gitlab.yml.example config/gitlab.yml
|
||||
- bundle exec rake db:drop db:create db:schema:load db:seed_fu
|
||||
- date
|
||||
- git checkout $CI_COMMIT_SHA
|
||||
- bundle install $BUNDLE_INSTALL_FLAGS
|
||||
- date
|
||||
- . scripts/prepare_build.sh
|
||||
- date
|
||||
- bundle exec rake db:migrate
|
||||
|
||||
##
|
||||
# Trigger a package build in omnibus-gitlab repository
|
||||
#
|
||||
package-qa:
|
||||
<<: *dedicated-runner
|
||||
image: ruby:2.4-alpine
|
||||
before_script: []
|
||||
stage: build
|
||||
cache: {}
|
||||
when: manual
|
||||
script:
|
||||
- scripts/trigger-build-omnibus
|
||||
only:
|
||||
- //@gitlab-org/gitlab-ce
|
||||
- //@gitlab-org/gitlab-ee
|
||||
|
||||
# Trigger a docs build in gitlab-docs
|
||||
# Useful to preview the docs changes live
|
||||
review-docs-deploy:
|
||||
|
@ -265,7 +334,7 @@ update-tests-metadata:
|
|||
|
||||
flaky-examples-check:
|
||||
<<: *dedicated-runner
|
||||
image: ruby:2.3-alpine
|
||||
image: ruby:2.4-alpine
|
||||
services: []
|
||||
before_script: []
|
||||
variables:
|
||||
|
@ -299,7 +368,9 @@ compile-assets:
|
|||
<<: *default-cache
|
||||
script:
|
||||
- node --version
|
||||
- date
|
||||
- yarn install --frozen-lockfile --cache-folder .yarn-cache
|
||||
- date
|
||||
- bundle exec rake gitlab:assets:compile
|
||||
artifacts:
|
||||
expire_in: 7d
|
||||
|
@ -323,90 +394,75 @@ setup-test-env:
|
|||
- tmp/tests
|
||||
- config/secrets.yml
|
||||
|
||||
rspec-pg 0 27: *rspec-metadata-pg
|
||||
rspec-pg 1 27: *rspec-metadata-pg
|
||||
rspec-pg 2 27: *rspec-metadata-pg
|
||||
rspec-pg 3 27: *rspec-metadata-pg
|
||||
rspec-pg 4 27: *rspec-metadata-pg
|
||||
rspec-pg 5 27: *rspec-metadata-pg
|
||||
rspec-pg 6 27: *rspec-metadata-pg
|
||||
rspec-pg 7 27: *rspec-metadata-pg
|
||||
rspec-pg 8 27: *rspec-metadata-pg
|
||||
rspec-pg 9 27: *rspec-metadata-pg
|
||||
rspec-pg 10 27: *rspec-metadata-pg
|
||||
rspec-pg 11 27: *rspec-metadata-pg
|
||||
rspec-pg 12 27: *rspec-metadata-pg
|
||||
rspec-pg 13 27: *rspec-metadata-pg
|
||||
rspec-pg 14 27: *rspec-metadata-pg
|
||||
rspec-pg 15 27: *rspec-metadata-pg
|
||||
rspec-pg 16 27: *rspec-metadata-pg
|
||||
rspec-pg 17 27: *rspec-metadata-pg
|
||||
rspec-pg 18 27: *rspec-metadata-pg
|
||||
rspec-pg 19 27: *rspec-metadata-pg
|
||||
rspec-pg 20 27: *rspec-metadata-pg
|
||||
rspec-pg 21 27: *rspec-metadata-pg
|
||||
rspec-pg 22 27: *rspec-metadata-pg
|
||||
rspec-pg 23 27: *rspec-metadata-pg
|
||||
rspec-pg 24 27: *rspec-metadata-pg
|
||||
rspec-pg 25 27: *rspec-metadata-pg
|
||||
rspec-pg 26 27: *rspec-metadata-pg
|
||||
rspec-pg 0 28: *rspec-metadata-pg
|
||||
rspec-pg 1 28: *rspec-metadata-pg
|
||||
rspec-pg 2 28: *rspec-metadata-pg
|
||||
rspec-pg 3 28: *rspec-metadata-pg
|
||||
rspec-pg 4 28: *rspec-metadata-pg
|
||||
rspec-pg 5 28: *rspec-metadata-pg
|
||||
rspec-pg 6 28: *rspec-metadata-pg
|
||||
rspec-pg 7 28: *rspec-metadata-pg
|
||||
rspec-pg 8 28: *rspec-metadata-pg
|
||||
rspec-pg 9 28: *rspec-metadata-pg
|
||||
rspec-pg 10 28: *rspec-metadata-pg
|
||||
rspec-pg 11 28: *rspec-metadata-pg
|
||||
rspec-pg 12 28: *rspec-metadata-pg
|
||||
rspec-pg 13 28: *rspec-metadata-pg
|
||||
rspec-pg 14 28: *rspec-metadata-pg
|
||||
rspec-pg 15 28: *rspec-metadata-pg
|
||||
rspec-pg 16 28: *rspec-metadata-pg
|
||||
rspec-pg 17 28: *rspec-metadata-pg
|
||||
rspec-pg 18 28: *rspec-metadata-pg
|
||||
rspec-pg 19 28: *rspec-metadata-pg
|
||||
rspec-pg 20 28: *rspec-metadata-pg
|
||||
rspec-pg 21 28: *rspec-metadata-pg
|
||||
rspec-pg 22 28: *rspec-metadata-pg
|
||||
rspec-pg 23 28: *rspec-metadata-pg
|
||||
rspec-pg 24 28: *rspec-metadata-pg
|
||||
rspec-pg 25 28: *rspec-metadata-pg
|
||||
rspec-pg 26 28: *rspec-metadata-pg
|
||||
rspec-pg 27 28: *rspec-metadata-pg
|
||||
|
||||
rspec-mysql 0 27: *rspec-metadata-mysql
|
||||
rspec-mysql 1 27: *rspec-metadata-mysql
|
||||
rspec-mysql 2 27: *rspec-metadata-mysql
|
||||
rspec-mysql 3 27: *rspec-metadata-mysql
|
||||
rspec-mysql 4 27: *rspec-metadata-mysql
|
||||
rspec-mysql 5 27: *rspec-metadata-mysql
|
||||
rspec-mysql 6 27: *rspec-metadata-mysql
|
||||
rspec-mysql 7 27: *rspec-metadata-mysql
|
||||
rspec-mysql 8 27: *rspec-metadata-mysql
|
||||
rspec-mysql 9 27: *rspec-metadata-mysql
|
||||
rspec-mysql 10 27: *rspec-metadata-mysql
|
||||
rspec-mysql 11 27: *rspec-metadata-mysql
|
||||
rspec-mysql 12 27: *rspec-metadata-mysql
|
||||
rspec-mysql 13 27: *rspec-metadata-mysql
|
||||
rspec-mysql 14 27: *rspec-metadata-mysql
|
||||
rspec-mysql 15 27: *rspec-metadata-mysql
|
||||
rspec-mysql 16 27: *rspec-metadata-mysql
|
||||
rspec-mysql 17 27: *rspec-metadata-mysql
|
||||
rspec-mysql 18 27: *rspec-metadata-mysql
|
||||
rspec-mysql 19 27: *rspec-metadata-mysql
|
||||
rspec-mysql 20 27: *rspec-metadata-mysql
|
||||
rspec-mysql 21 27: *rspec-metadata-mysql
|
||||
rspec-mysql 22 27: *rspec-metadata-mysql
|
||||
rspec-mysql 23 27: *rspec-metadata-mysql
|
||||
rspec-mysql 24 27: *rspec-metadata-mysql
|
||||
rspec-mysql 25 27: *rspec-metadata-mysql
|
||||
rspec-mysql 26 27: *rspec-metadata-mysql
|
||||
rspec-mysql 0 28: *rspec-metadata-mysql
|
||||
rspec-mysql 1 28: *rspec-metadata-mysql
|
||||
rspec-mysql 2 28: *rspec-metadata-mysql
|
||||
rspec-mysql 3 28: *rspec-metadata-mysql
|
||||
rspec-mysql 4 28: *rspec-metadata-mysql
|
||||
rspec-mysql 5 28: *rspec-metadata-mysql
|
||||
rspec-mysql 6 28: *rspec-metadata-mysql
|
||||
rspec-mysql 7 28: *rspec-metadata-mysql
|
||||
rspec-mysql 8 28: *rspec-metadata-mysql
|
||||
rspec-mysql 9 28: *rspec-metadata-mysql
|
||||
rspec-mysql 10 28: *rspec-metadata-mysql
|
||||
rspec-mysql 11 28: *rspec-metadata-mysql
|
||||
rspec-mysql 12 28: *rspec-metadata-mysql
|
||||
rspec-mysql 13 28: *rspec-metadata-mysql
|
||||
rspec-mysql 14 28: *rspec-metadata-mysql
|
||||
rspec-mysql 15 28: *rspec-metadata-mysql
|
||||
rspec-mysql 16 28: *rspec-metadata-mysql
|
||||
rspec-mysql 17 28: *rspec-metadata-mysql
|
||||
rspec-mysql 18 28: *rspec-metadata-mysql
|
||||
rspec-mysql 19 28: *rspec-metadata-mysql
|
||||
rspec-mysql 20 28: *rspec-metadata-mysql
|
||||
rspec-mysql 21 28: *rspec-metadata-mysql
|
||||
rspec-mysql 22 28: *rspec-metadata-mysql
|
||||
rspec-mysql 23 28: *rspec-metadata-mysql
|
||||
rspec-mysql 24 28: *rspec-metadata-mysql
|
||||
rspec-mysql 25 28: *rspec-metadata-mysql
|
||||
rspec-mysql 26 28: *rspec-metadata-mysql
|
||||
rspec-mysql 27 28: *rspec-metadata-mysql
|
||||
|
||||
spinach-pg 0 3: *spinach-metadata-pg
|
||||
spinach-pg 1 3: *spinach-metadata-pg
|
||||
spinach-pg 2 3: *spinach-metadata-pg
|
||||
spinach-pg 0 2: *spinach-metadata-pg
|
||||
spinach-pg 1 2: *spinach-metadata-pg
|
||||
|
||||
spinach-mysql 0 3: *spinach-metadata-mysql
|
||||
spinach-mysql 1 3: *spinach-metadata-mysql
|
||||
spinach-mysql 2 3: *spinach-metadata-mysql
|
||||
|
||||
# Static analysis jobs
|
||||
.ruby-static-analysis: &ruby-static-analysis
|
||||
variables:
|
||||
SIMPLECOV: "false"
|
||||
SETUP_DB: "false"
|
||||
|
||||
.rake-exec: &rake-exec
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
<<: *pull-cache
|
||||
<<: *ruby-static-analysis
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec rake $CI_JOB_NAME
|
||||
spinach-mysql 0 2: *spinach-metadata-mysql
|
||||
spinach-mysql 1 2: *spinach-metadata-mysql
|
||||
|
||||
static-analysis:
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs
|
||||
<<: *ruby-static-analysis
|
||||
stage: test
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
dependencies:
|
||||
- compile-assets
|
||||
- setup-test-env
|
||||
script:
|
||||
- scripts/static-analysis
|
||||
cache:
|
||||
|
@ -463,15 +519,6 @@ ee_compat_check:
|
|||
paths:
|
||||
- ee_compat_check/patches/*.patch
|
||||
|
||||
# DB migration, rollback, and seed jobs
|
||||
.db-migrate-reset: &db-migrate-reset
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
<<: *pull-cache
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec rake db:migrate:reset
|
||||
|
||||
db:migrate:reset-pg:
|
||||
<<: *db-migrate-reset
|
||||
<<: *use-pg
|
||||
|
@ -486,25 +533,6 @@ db:check-schema-pg:
|
|||
script:
|
||||
- source scripts/schema_changed.sh
|
||||
|
||||
.migration-paths: &migration-paths
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
<<: *pull-cache
|
||||
stage: test
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
CREATE_DB_USER: "true"
|
||||
script:
|
||||
- git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v9.3.0
|
||||
- git checkout -f FETCH_HEAD
|
||||
- bundle install $BUNDLE_INSTALL_FLAGS
|
||||
- cp config/gitlab.yml.example config/gitlab.yml
|
||||
- bundle exec rake db:drop db:create db:schema:load db:seed_fu
|
||||
- git checkout $CI_COMMIT_SHA
|
||||
- bundle install $BUNDLE_INSTALL_FLAGS
|
||||
- . scripts/prepare_build.sh
|
||||
- bundle exec rake db:migrate
|
||||
|
||||
migration:path-pg:
|
||||
<<: *migration-paths
|
||||
<<: *use-pg
|
||||
|
@ -514,10 +542,7 @@ migration:path-mysql:
|
|||
<<: *use-mysql
|
||||
|
||||
.db-rollback: &db-rollback
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
<<: *pull-cache
|
||||
stage: test
|
||||
<<: *dedicated-no-docs-pull-cache-job
|
||||
script:
|
||||
- bundle exec rake db:rollback STEP=119
|
||||
- bundle exec rake db:migrate
|
||||
|
@ -530,27 +555,6 @@ db:rollback-mysql:
|
|||
<<: *db-rollback
|
||||
<<: *use-mysql
|
||||
|
||||
.gitlab-setup: &gitlab-setup
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
<<: *pull-cache
|
||||
stage: test
|
||||
variables:
|
||||
SIZE: "1"
|
||||
SETUP_DB: "false"
|
||||
CREATE_DB_USER: "true"
|
||||
FIXTURE_PATH: db/fixtures/development
|
||||
script:
|
||||
- git clone https://gitlab.com/gitlab-org/gitlab-test.git
|
||||
/home/git/repositories/gitlab-org/gitlab-test.git
|
||||
- scripts/gitaly-test-spawn
|
||||
- force=yes bundle exec rake gitlab:setup
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expire_in: 1d
|
||||
paths:
|
||||
- log/development.log
|
||||
|
||||
gitlab:setup-pg:
|
||||
<<: *gitlab-setup
|
||||
<<: *use-pg
|
||||
|
@ -561,10 +565,7 @@ gitlab:setup-mysql:
|
|||
|
||||
# Frontend-related jobs
|
||||
gitlab:assets:compile:
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
<<: *pull-cache
|
||||
stage: test
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
dependencies: []
|
||||
variables:
|
||||
NODE_ENV: "production"
|
||||
|
@ -574,7 +575,9 @@ gitlab:assets:compile:
|
|||
WEBPACK_REPORT: "true"
|
||||
NO_COMPRESSION: "true"
|
||||
script:
|
||||
- date
|
||||
- yarn install --frozen-lockfile --production --cache-folder .yarn-cache
|
||||
- date
|
||||
- bundle exec rake gitlab:assets:compile
|
||||
artifacts:
|
||||
name: webpack-report
|
||||
|
@ -583,17 +586,16 @@ gitlab:assets:compile:
|
|||
- webpack-report/
|
||||
|
||||
karma:
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
<<: *pull-cache
|
||||
<<: *dedicated-no-docs-pull-cache-job
|
||||
<<: *use-pg
|
||||
stage: test
|
||||
variables:
|
||||
BABEL_ENV: "coverage"
|
||||
CHROME_LOG_FILE: "chrome_debug.log"
|
||||
dependencies:
|
||||
- compile-assets
|
||||
- setup-test-env
|
||||
script:
|
||||
- export BABEL_ENV=coverage CHROME_LOG_FILE=chrome_debug.log
|
||||
- date
|
||||
- scripts/gitaly-test-spawn
|
||||
- bundle exec rake gettext:po_to_json
|
||||
- date
|
||||
- bundle exec rake karma
|
||||
coverage: '/^Statements *: (\d+\.\d+%)/'
|
||||
artifacts:
|
||||
|
@ -601,28 +603,29 @@ karma:
|
|||
expire_in: 31d
|
||||
when: always
|
||||
paths:
|
||||
- chrome_debug.log
|
||||
- coverage-javascript/
|
||||
- chrome_debug.log
|
||||
- coverage-javascript/
|
||||
|
||||
codequality:
|
||||
<<: *except-docs
|
||||
<<: *pull-cache
|
||||
before_script: []
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
image: docker:latest
|
||||
stage: test
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
DOCKER_DRIVER: overlay
|
||||
before_script: []
|
||||
services:
|
||||
- docker:dind
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
DOCKER_DRIVER: overlay2
|
||||
CODECLIMATE_FORMAT: json
|
||||
cache: {}
|
||||
dependencies: []
|
||||
script:
|
||||
- cp .rubocop.yml .rubocop.yml.bak
|
||||
- grep -v "rubocop-gitlab-security" .rubocop.yml.bak > .rubocop.yml
|
||||
- docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc dev.gitlab.org:5005/gitlab/gitlab-build-images:gitlab-codeclimate-v2 analyze -f json > raw_codeclimate.json
|
||||
- cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
|
||||
- mv .rubocop.yml.bak .rubocop.yml
|
||||
- apk update && apk add jq
|
||||
- ./scripts/codequality analyze -f json > raw_codeclimate.json || true
|
||||
# The following line keeps only the fields used in the MR widget, reducing the JSON artifact size
|
||||
- jq -c 'map({check_name,description,fingerprint,location})' raw_codeclimate.json > codeclimate.json
|
||||
artifacts:
|
||||
paths: [codeclimate.json]
|
||||
expire_in: 1 week
|
||||
|
||||
sast:
|
||||
<<: *except-docs
|
||||
|
@ -636,11 +639,7 @@ sast:
|
|||
paths: [gl-sast-report.json]
|
||||
|
||||
qa:internal:
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs
|
||||
stage: test
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
services: []
|
||||
script:
|
||||
- cd qa/
|
||||
|
@ -648,11 +647,7 @@ qa:internal:
|
|||
- bundle exec rspec
|
||||
|
||||
qa:selectors:
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs
|
||||
stage: test
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
services: []
|
||||
script:
|
||||
- cd qa/
|
||||
|
@ -660,14 +655,8 @@ qa:selectors:
|
|||
- bundle exec bin/qa Test::Sanity::Selectors
|
||||
|
||||
coverage:
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
<<: *pull-cache
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
stage: post-test
|
||||
services: []
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "true"
|
||||
script:
|
||||
- bundle exec scripts/merge-simplecov
|
||||
coverage: '/LOC \((\d+\.\d+%)\) covered.$/'
|
||||
|
@ -679,26 +668,25 @@ coverage:
|
|||
- coverage/assets/
|
||||
|
||||
lint:javascript:report:
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
<<: *pull-cache
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
stage: post-test
|
||||
dependencies:
|
||||
- compile-assets
|
||||
- setup-test-env
|
||||
before_script: []
|
||||
script:
|
||||
- date
|
||||
- find app/ spec/ -name '*.js' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
|
||||
- date
|
||||
- yarn run eslint-report || true # ignore exit code
|
||||
artifacts:
|
||||
name: eslint-report
|
||||
expire_in: 31d
|
||||
paths:
|
||||
- eslint-report.html
|
||||
- eslint-report.html
|
||||
|
||||
pages:
|
||||
<<: *dedicated-runner
|
||||
<<: *pull-cache
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
before_script: []
|
||||
stage: pages
|
||||
dependencies:
|
||||
|
@ -723,10 +711,7 @@ pages:
|
|||
# Insurance in case a gem needed by one of our releases gets yanked from
|
||||
# rubygems.org in the future.
|
||||
cache gems:
|
||||
<<: *dedicated-runner
|
||||
<<: *pull-cache
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
script:
|
||||
- bundle package --all --all-platforms
|
||||
artifacts:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<!---
|
||||
Please read this!
|
||||
|
||||
Before opening a new issue, make sure to search for keywords in the issues
|
||||
|
@ -14,10 +15,7 @@ For the Enterprise Edition issue tracker:
|
|||
- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=bug
|
||||
|
||||
and verify the issue you're about to submit isn't a duplicate.
|
||||
|
||||
Please remove this notice if you're confident your issue isn't a duplicate.
|
||||
|
||||
------
|
||||
--->
|
||||
|
||||
### Summary
|
||||
|
||||
|
|
29
.rubocop.yml
29
.rubocop.yml
|
@ -10,15 +10,27 @@ AllCops:
|
|||
Exclude:
|
||||
- 'vendor/**/*'
|
||||
- 'node_modules/**/*'
|
||||
- 'db/*'
|
||||
- 'db/**/*'
|
||||
- 'db/fixtures/**/*'
|
||||
- 'db/geo/*'
|
||||
- 'ee/db/**/*'
|
||||
- 'tmp/**/*'
|
||||
- 'bin/**/*'
|
||||
- 'generator_templates/**/*'
|
||||
- 'builds/**/*'
|
||||
- 'plugins/**/*'
|
||||
CacheRootDirectory: tmp
|
||||
|
||||
# This cop checks whether some constant value isn't a
|
||||
# mutable literal (e.g. array or hash).
|
||||
Style/MutableConstant:
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- 'db/migrate/**/*'
|
||||
- 'db/post_migrate/**/*'
|
||||
- 'ee/db/migrate/**/*'
|
||||
- 'ee/db/post_migrate/**/*'
|
||||
- 'ee/db/geo/migrate/**/*'
|
||||
|
||||
# Gitlab ###################################################################
|
||||
|
||||
Gitlab/ModuleWithInstanceVariables:
|
||||
|
@ -36,3 +48,16 @@ Gitlab/ModuleWithInstanceVariables:
|
|||
|
||||
Gitlab/HTTParty:
|
||||
Enabled: true
|
||||
|
||||
GitlabSecurity/PublicSend:
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- 'config/**/*'
|
||||
- 'db/**/*'
|
||||
- 'features/**/*'
|
||||
- 'lib/**/*.rake'
|
||||
- 'qa/**/*'
|
||||
- 'spec/**/*'
|
||||
- 'ee/db/**/*'
|
||||
- 'ee/lib/**/*.rake'
|
||||
- 'ee/spec/**/*'
|
||||
|
|
|
@ -124,8 +124,8 @@ Lint/DuplicateMethods:
|
|||
- 'lib/gitlab/git/repository.rb'
|
||||
- 'lib/gitlab/git/tree.rb'
|
||||
- 'lib/gitlab/git/wiki_page.rb'
|
||||
- 'lib/gitlab/ldap/person.rb'
|
||||
- 'lib/gitlab/o_auth/user.rb'
|
||||
- 'lib/gitlab/auth/ldap/person.rb'
|
||||
- 'lib/gitlab/auth/o_auth/user.rb'
|
||||
|
||||
# Offense count: 4
|
||||
Lint/InterpolationCheck:
|
||||
|
@ -812,7 +812,7 @@ Style/TrivialAccessors:
|
|||
Exclude:
|
||||
- 'app/models/external_issue.rb'
|
||||
- 'app/serializers/base_serializer.rb'
|
||||
- 'lib/gitlab/ldap/person.rb'
|
||||
- 'lib/gitlab/auth/ldap/person.rb'
|
||||
- 'lib/system_check/base_check.rb'
|
||||
|
||||
# Offense count: 4
|
||||
|
|
218
CHANGELOG.md
218
CHANGELOG.md
|
@ -2,47 +2,188 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 10.5.6 (2018-03-16)
|
||||
## 10.6.0 (2018-03-22)
|
||||
|
||||
### Security (2 changes)
|
||||
### Security (4 changes)
|
||||
|
||||
- Fixed some SSRF vulnerabilities in services, hooks and integrations. !2337
|
||||
- Ensure that OTP backup codes are always invalidated.
|
||||
- Add verification for GitLab Pages custom domains.
|
||||
- Fix GitLab Auth0 integration signing in the wrong user.
|
||||
|
||||
### Fixed (75 changes, 17 of them are from the community)
|
||||
|
||||
## 10.5.5 (2018-03-15)
|
||||
|
||||
### Fixed (3 changes)
|
||||
|
||||
- Fix missing uploads after group transfer. !17658
|
||||
- Fix code and wiki search results when filename is non-ASCII.
|
||||
- Remove double caching of Repository#empty?.
|
||||
|
||||
### Performance (2 changes)
|
||||
|
||||
- Adding missing indexes on taggings table.
|
||||
- Add index on section_name_id on ci_build_trace_sections table.
|
||||
|
||||
|
||||
## 10.5.4 (2018-03-08)
|
||||
|
||||
### Fixed (11 changes)
|
||||
|
||||
- Ensure users cannot create environments with leading or trailing slashes (Fixes #39885). !15273
|
||||
- Fix new project path input overlapping. !16755 (George Tsiolis)
|
||||
- Respect description and visibility when creating project from template. !16820 (George Tsiolis)
|
||||
- Remove user notification settings for groups and projects when user leaves. !16906 (Jacopo Beschi @jacopo-beschi)
|
||||
- Fix Teleporting Emoji. !16963 (Jared Deckard <jared.deckard@gmail.com>)
|
||||
- Fix duplicate system notes when merging a merge request. !17035
|
||||
- Fix breadcrumb on labels page for groups. !17045 (Onuwa Nnachi Isaac)
|
||||
- Fix user avatar's vertical align on the issues and merge requests pages. !17072 (Laszlo Karpati)
|
||||
- Fix settings panels not expanding when fragment hash linked. !17074
|
||||
- Fix 404 when listing archived projects in a group where all projects have been archived. !17077 (Ashley Dumaine)
|
||||
- Allow to call PUT /projects/:id API with only ci_config_path specified. !17105 (Laszlo Karpati)
|
||||
- Fix long list of recipients on group request membership email. !17121 (Jacopo Beschi @jacopo-beschi)
|
||||
- Remove duplicated error message on duplicate variable validation. !17135
|
||||
- Keep "Import project" tab/form active when validation fails trying to import "Repo by URL". !17136
|
||||
- Fixed bug with unauthenticated requests through git ssh. !17149
|
||||
- Allows project rename after validation error. !17150
|
||||
- Fix "Remove source branch" button in Merge request widget during merge when pipeline succeeds state. !17192
|
||||
- Add missing pagination on the commit diff endpoint. !17203 (Maxime Roussin-Bélanger)
|
||||
- Fix get a single pages domain when project path contains a period. !17206 (Travis Miller)
|
||||
- remove avater underline. !17219 (Ken Ding)
|
||||
- Allows the usage of /milestone quick action for group milestones. !17239 (Jacopo Beschi @jacopo-beschi)
|
||||
- Encode branch name as binary before creating a RPC request to copy attributes. !17291
|
||||
- Restart Unicorn and Sidekiq when GRPC throws 14:Endpoint read failed. !17293
|
||||
- Do not persist Google Project verification flash errors after a page reload. !17299
|
||||
- Ensure group issues and merge requests pages show results from subgroups when there are no results from the current group. !17312
|
||||
- Prevent trace artifact migration to incur data loss. !17313
|
||||
- Fixes gpg popover layout. !17323
|
||||
- Return a 404 instead of 403 if the repository does not exist on disk. !17341
|
||||
- Fix Slack/Mattermost notifications not respecting `notify_only_default_branch` setting for pushes. !17345
|
||||
- Fix Group labels load failure when there are duplicate labels present. !17353
|
||||
- Allow Prometheus application to be installed from Cluster applications. !17372
|
||||
- Fixes Prometheus admin configuration page. !17377
|
||||
- Enable filtering MR list based on clicked label in MR sidebar. !17390
|
||||
- Fix code and wiki search results pages when non-ASCII text is displayed. !17413
|
||||
- Count comments on diffs and discussions as contributions for the contributions calendar. !17418 (Riccardo Padovani)
|
||||
- Add Assignees vue component missing data container. !17426 (George Tsiolis)
|
||||
- Update tooltip on pipeline cancel to Stop (#42946). !17444
|
||||
- Removing the two factor check when the user sets a new password. !17457
|
||||
- Fix quick actions for users who cannot update issues and merge requests. !17482
|
||||
- Stop loading spinner on error of milestone update on issue. !17507 (Takuya Noguchi)
|
||||
- Set margins around dropdown dividers to 4px. !17517
|
||||
- Fix pages flaky failure by reloading stale object. !17522
|
||||
- Fixed issue edit shortcut not opening edit form.
|
||||
- Remove extra breadcrumb on tags. !17562 (Takuya Noguchi)
|
||||
- Fix missing uploads after group transfer. !17658
|
||||
- Fix markdown table showing extra column. !17669
|
||||
- Ensure the API returns https links when https is configured. !17681
|
||||
- Sanitize extra blank spaces used when uploading a SSH key. !40552
|
||||
- Render htmlentities correctly for links not supported by Rinku.
|
||||
- Keep link when redacting unauthorized object links.
|
||||
- Handle empty state in Pipelines page.
|
||||
- Revert Project.public_or_visible_to_user changes and only apply to snippets.
|
||||
- Release libgit2 cache and open file descriptors after `git gc` run.
|
||||
- Fix project dashboard showing the wrong timestamps.
|
||||
- Fix "Can't modify frozen hash" error when project is destroyed.
|
||||
- Fix Error 500 when viewing a commit with a GPG signature in Geo.
|
||||
- Don't error out in system hook if user has `nil` datetime columns.
|
||||
- Remove double caching of Repository#empty?.
|
||||
- Don't delete todos or unassign issues and MRs when a user leaves a project.
|
||||
- Don't cache a nil repository root ref to prevent caching issues.
|
||||
- Escape HTML entities in commit messages.
|
||||
- Verify project import status again before marking as failed.
|
||||
- [GitHub Import] Create an empty wiki if wiki import failed.
|
||||
- Create empty wiki when import from GitLab and wiki is not there.
|
||||
- Make sure wiki exists when it's enabled.
|
||||
- Fix broken loading state for close issue button.
|
||||
- Fix code and wiki search results when filename is non-ASCII.
|
||||
- Fix file upload on project show page.
|
||||
- Fix squashing when a file is renamed.
|
||||
- Show loading button inline in refresh button in MR widget.
|
||||
- Fix close button on issues not working on mobile.
|
||||
- Adds tooltip in environment names to increase readability.
|
||||
- Fixed issue edit shortcut not opening edit form.
|
||||
- Fix 500 error being shown when diff has context marker with invalid encoding.
|
||||
- Render modified icon for moved file in changes dropdown.
|
||||
- Remember assignee when moving an issue.
|
||||
|
||||
### Performance (1 change)
|
||||
### Changed (16 changes, 9 of them are from the community)
|
||||
|
||||
- Allow including custom attributes in API responses. !16526 (Markus Koller)
|
||||
- Apply new default and inline label design. !16956 (George Tsiolis)
|
||||
- Remove whitespace from the username/email sign in form field. !17020 (Peter lauck)
|
||||
- CI charts now include the current day. !17032 (Dakkaron)
|
||||
- Hide CI secret variable values after saving. !17044
|
||||
- Add new modal Vue component. !17108
|
||||
- Asciidoc now support inter-document cross references between files in repository. !17125 (Turo Soisenniemi)
|
||||
- Update issue closing pattern to allow variations in punctuation. !17198 (Vicky Chijwani)
|
||||
- Add a button to deploy a runner to a Kubernetes cluster in the settings page. !17278
|
||||
- Pages custom domain: allow update of key/certificate. !17376 (rfwatson)
|
||||
- Clear the Labels dropdown search filter after a selection is made. !17393 (Andrew Torres)
|
||||
- Hook data for pipelines includes detailed_status. !17607
|
||||
- Avoid showing unnecessary Trigger checkboxes for project Integrations with only one event. !17607
|
||||
- Display a link to external issue tracker when enabled.
|
||||
- Allow token authentication on go-get request.
|
||||
- Update SSH key link to include existing keys. (Brendan O'Leary)
|
||||
|
||||
### Performance (24 changes, 5 of them are from the community)
|
||||
|
||||
- Add catch-up background migration to migrate pipeline stages. !15741
|
||||
- Move BoardNewIssue vue component. !16947 (George Tsiolis)
|
||||
- Move IssuableTimeTracker vue component. !16948 (George Tsiolis)
|
||||
- Move RecentSearchesDropdownContent vue component. !16951 (George Tsiolis)
|
||||
- Move Assignees vue component. !16952 (George Tsiolis)
|
||||
- Improve performance of pipeline page by reducing DB queries. !17168
|
||||
- Store sha256 checksum to job artifacts. !17354
|
||||
- Move SidebarAssignees vue component. !17398 (George Tsiolis)
|
||||
- Improve database response time for user activity listing. !17454
|
||||
- Use persisted/memoized value for MRs shas instead of doing git lookups. !17555
|
||||
- Cache MergeRequests can_be_resolved_in_ui? git operations. !17589
|
||||
- Prevent the graphs page from generating unnecessary Gitaly requests. !37602
|
||||
- Use a user object in ApplicationHelper#avatar_icon where possible to avoid N+1 queries. !42800
|
||||
- Submit a single batch blob RPC to Gitaly per HTTP request when viewing diffs.
|
||||
- Avoid re-fetching merge-base SHA from Gitaly unnecessarily.
|
||||
- Don't use ProjectsFinder in TodosFinder.
|
||||
- Adding missing indexes on taggings table.
|
||||
- Add index on section_name_id on ci_build_trace_sections table.
|
||||
- Cache column_exists? for application settings.
|
||||
- Cache table_exists?('application_settings') to reduce repeated schema reloads.
|
||||
- Make --prune a configurable parameter in fetching a git remote.
|
||||
- Fix timeouts loading /admin/projects page.
|
||||
- Add partial indexes on todos to handle users with many todos.
|
||||
- Optimize search queries on the search page by setting a limit for matching records in project scope.
|
||||
|
||||
### Added (30 changes, 9 of them are from the community)
|
||||
|
||||
- Add CommonMark markdown engine (experimental). !14835 (blackst0ne)
|
||||
- API: Get references a commit is pushed to. !15026 (Robert Schilling)
|
||||
- Add overview of branches and a filter for active/stale branches. !15402 (Takuya Noguchi)
|
||||
- Add project export API. !15860 (Travis Miller)
|
||||
- expose more metrics in merge requests api. !16589 (haseebeqx)
|
||||
- #28481: Display time tracking totals on milestone page. !16753 (Riccardo Padovani)
|
||||
- Add a button on the project page to set up a Kubernetes cluster and enable Auto DevOps. !16900
|
||||
- Include cycle time in usage ping data. !16973
|
||||
- Add ability to use external plugins as an alternative to system hooks. !17003
|
||||
- Add search param to Branches API. !17005 (bunufi)
|
||||
- API endpoint for importing a project export. !17025
|
||||
- Display ingress IP address in the Kubernetes page. !17052
|
||||
- Implemented badge API endpoints. !17082
|
||||
- Allow installation of GitLab Runner with a single click. !17134
|
||||
- Allow commits endpoint to work over all commits of a repository. !17182
|
||||
- Display Runner IP Address. !17286
|
||||
- Add archive feature to trace. !17314
|
||||
- Allow maintainers to push to forks of their projects when a merge request is open. !17395
|
||||
- Foreground verification of uploads and LFS objects. !17402
|
||||
- Adds updated_at filter to issues and merge_requests API. !17417 (Jacopo Beschi @jacopo-beschi)
|
||||
- Port /wip quick action command to Merge Request creation (on description). !17463 (Adam Pahlevi)
|
||||
- Add a paragraph about security implications on Cluster's page. !17486
|
||||
- Add plugins list to the system hooks page. !17518
|
||||
- Enable privileged mode for GitLab Runner. !17528
|
||||
- Expose GITLAB_FEATURES as CI/CD variable (fixes #40994).
|
||||
- Upgrade GitLab Workhorse to 4.0.0.
|
||||
- Allow CI/CD Jobs being grouped on version strings.
|
||||
- Add discussions API for Issues and Snippets.
|
||||
- Add one group board to Libre.
|
||||
- Add support for filtering by source and target branch to merge requests API.
|
||||
|
||||
### Other (14 changes, 3 of them are from the community)
|
||||
|
||||
- Update vue component naming guidelines. !17018 (George Tsiolis)
|
||||
- Added new design for promotion modals. !17197
|
||||
- Update to github-linguist 5.3.x. !17241 (Ken Ding)
|
||||
- update toml-rb to 1.0.0. !17259 (Ken Ding)
|
||||
- Keep track of projects a user interacted with. !17327
|
||||
- Enables eslint in codeclimate job. !17392
|
||||
- Port Labels Select dropdown to Vue. !17411
|
||||
- Add NOT NULL constraint to projects.namespace_id. !17448
|
||||
- Ensure foreign keys on clusters applications. !17488
|
||||
- Started translation into Turkish, Indonesian and Filipino. !17526
|
||||
- Add documentation for displayed K8s Ingress IP address (#44330). !17836
|
||||
- Move Ruby endpoints to OPT_OUT.
|
||||
- Upgrade Workhorse to version 3.8.0 to support structured logging.
|
||||
- Use host URL to build JIRA remote link icon.
|
||||
|
||||
|
||||
## 10.5.3 (2018-03-01)
|
||||
|
@ -269,6 +410,32 @@ entry.
|
|||
- Adds empty state illustration for pending job.
|
||||
|
||||
|
||||
## 10.4.5 (2018-03-01)
|
||||
|
||||
### Security (1 change)
|
||||
|
||||
- Ensure that OTP backup codes are always invalidated.
|
||||
|
||||
|
||||
## 10.4.4 (2018-02-16)
|
||||
|
||||
### Security (1 change)
|
||||
|
||||
- Update nokogiri to 1.8.2. !16807
|
||||
|
||||
### Fixed (9 changes)
|
||||
|
||||
- Fix 500 error when loading a merge request with an invalid comment. !16795
|
||||
- Cleanup new branch/merge request form in issues. !16854
|
||||
- Fix GitLab import leaving group_id on ProjectLabel. !16877
|
||||
- Fix forking projects when no restricted visibility levels are defined applicationwide. !16881
|
||||
- Resolve PrepareUntrackedUploads PostgreSQL syntax error. !17019
|
||||
- Fixed error 500 when removing an identity with synced attributes and visiting the profile page. !17054
|
||||
- Validate user namespace before saving so that errors persist on model.
|
||||
- LDAP Person no longer throws exception on invalid entry.
|
||||
- Fix JIRA not working when a trailing slash is included.
|
||||
|
||||
|
||||
## 10.4.3 (2018-02-05)
|
||||
|
||||
### Security (4 changes)
|
||||
|
@ -474,6 +641,13 @@ entry.
|
|||
- Use a background migration for issues.closed_at.
|
||||
|
||||
|
||||
## 10.3.8 (2018-03-01)
|
||||
|
||||
### Security (1 change)
|
||||
|
||||
- Ensure that OTP backup codes are always invalidated.
|
||||
|
||||
|
||||
## 10.3.7 (2018-02-05)
|
||||
|
||||
### Security (4 changes)
|
||||
|
|
|
@ -174,7 +174,7 @@ Assigning a team label makes sure issues get the attention of the appropriate
|
|||
people.
|
||||
|
||||
The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Edge,
|
||||
~Geo, ~Gitaly, ~Platform, ~Monitoring, ~Release, and ~"UX".
|
||||
~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products" and ~"UX".
|
||||
|
||||
The descriptions on the [labels page][labels-page] explain what falls under the
|
||||
responsibility of each team.
|
||||
|
@ -196,6 +196,17 @@ release. There are two levels of priority labels:
|
|||
milestone. If these issues are not done in the current release, they will
|
||||
strongly be considered for the next release.
|
||||
|
||||
### Severity labels (~S1, ~S2, etc.)
|
||||
|
||||
Severity labels help us clearly communicate the impact of a ~bug on users.
|
||||
|
||||
| Label | Meaning | Example |
|
||||
|-------|------------------------------------------|---------|
|
||||
| ~S1 | Feature broken, no workaround | Unable to create an issue |
|
||||
| ~S2 | Feature broken, workaround unacceptable | Can push commits, but only via the command line |
|
||||
| ~S3 | Feature broken, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue |
|
||||
| ~S4 | Cosmetic issue | Label colors are incorrect / not being displayed |
|
||||
|
||||
### Label for community contributors (~"Accepting Merge Requests")
|
||||
|
||||
Issues that are beneficial to our users, 'nice to haves', that we currently do
|
||||
|
@ -397,9 +408,9 @@ For issues related to the open source stewardship of GitLab,
|
|||
there is the ~"stewardship" label.
|
||||
|
||||
This label is to be used for issues in which the stewardship of GitLab
|
||||
is a topic of discussion. For instance if GitLab Inc. is planning to remove
|
||||
features from GitLab CE to make exclusive in GitLab EE, related issues
|
||||
would be labelled with ~"stewardship".
|
||||
is a topic of discussion. For instance if GitLab Inc. is planning to add
|
||||
features from GitLab EE to GitLab CE, related issues would be labelled with
|
||||
~"stewardship".
|
||||
|
||||
A recent example of this was the issue for
|
||||
[bringing the time tracking API to GitLab CE][time-tracking-issue].
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.81.0
|
||||
0.91.0
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.6.1
|
||||
0.7.1
|
||||
|
|
|
@ -1 +1 @@
|
|||
6.0.3
|
||||
6.0.4
|
||||
|
|
|
@ -1 +1 @@
|
|||
3.6.0
|
||||
4.0.0
|
||||
|
|
10
Gemfile
10
Gemfile
|
@ -81,7 +81,7 @@ gem 'gollum-lib', '~> 4.2', require: false
|
|||
gem 'gollum-rugged_adapter', '~> 0.4.4', require: false
|
||||
|
||||
# Language detection
|
||||
gem 'github-linguist', '~> 4.7.0', require: 'linguist'
|
||||
gem 'github-linguist', '~> 5.3.3', require: 'linguist'
|
||||
|
||||
# API
|
||||
gem 'grape', '~> 1.0'
|
||||
|
@ -126,6 +126,7 @@ gem 'html-pipeline', '~> 1.11.0'
|
|||
gem 'deckar01-task_list', '2.0.0'
|
||||
gem 'gitlab-markup', '~> 1.6.2'
|
||||
gem 'redcarpet', '~> 3.4'
|
||||
gem 'commonmarker', '~> 0.17'
|
||||
gem 'RedCloth', '~> 4.3.2'
|
||||
gem 'rdoc', '~> 4.2'
|
||||
gem 'org-ruby', '~> 0.9.12'
|
||||
|
@ -401,6 +402,7 @@ gem 'sys-filesystem', '~> 1.1.6'
|
|||
|
||||
# SSH host key support
|
||||
gem 'net-ssh', '~> 4.1.0'
|
||||
gem 'sshkey', '~> 1.9.0'
|
||||
|
||||
# Required for ED25519 SSH host key support
|
||||
group :ed25519 do
|
||||
|
@ -410,11 +412,13 @@ group :ed25519 do
|
|||
end
|
||||
|
||||
# Gitaly GRPC client
|
||||
gem 'gitaly-proto', '~> 0.84.0', require: 'gitaly'
|
||||
gem 'gitaly-proto', '~> 0.88.0', require: 'gitaly'
|
||||
gem 'grpc', '~> 1.10.0'
|
||||
|
||||
# Locked until https://github.com/google/protobuf/issues/4210 is closed
|
||||
gem 'google-protobuf', '= 3.5.1'
|
||||
|
||||
gem 'toml-rb', '~> 0.3.15', require: false
|
||||
gem 'toml-rb', '~> 1.0.0', require: false
|
||||
|
||||
# Feature toggles
|
||||
gem 'flipper', '~> 0.11.0'
|
||||
|
|
40
Gemfile.lock
40
Gemfile.lock
|
@ -131,6 +131,8 @@ GEM
|
|||
coercible (1.0.0)
|
||||
descendants_tracker (~> 0.0.1)
|
||||
colorize (0.7.7)
|
||||
commonmarker (0.17.8)
|
||||
ruby-enum (~> 0.5)
|
||||
concord (0.1.5)
|
||||
adamantium (~> 0.2.0)
|
||||
equalizer (~> 0.0.9)
|
||||
|
@ -285,14 +287,14 @@ GEM
|
|||
po_to_json (>= 1.0.0)
|
||||
rails (>= 3.2.0)
|
||||
gherkin-ruby (0.3.2)
|
||||
gitaly-proto (0.84.0)
|
||||
gitaly-proto (0.88.0)
|
||||
google-protobuf (~> 3.1)
|
||||
grpc (~> 1.0)
|
||||
github-linguist (4.7.6)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
github-linguist (5.3.3)
|
||||
charlock_holmes (~> 0.7.5)
|
||||
escape_utils (~> 1.1.0)
|
||||
mime-types (>= 1.19)
|
||||
rugged (>= 0.23.0b)
|
||||
rugged (>= 0.25.1)
|
||||
github-markup (1.6.1)
|
||||
gitlab-flowdock-git-hook (1.0.1)
|
||||
flowdock (~> 0.7)
|
||||
|
@ -343,9 +345,9 @@ GEM
|
|||
google-protobuf (3.5.1)
|
||||
googleapis-common-protos-types (1.0.1)
|
||||
google-protobuf (~> 3.0)
|
||||
googleauth (0.5.3)
|
||||
googleauth (0.6.2)
|
||||
faraday (~> 0.12)
|
||||
jwt (~> 1.4)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
logging (~> 2.0)
|
||||
memoist (~> 0.12)
|
||||
multi_json (~> 1.11)
|
||||
|
@ -369,7 +371,7 @@ GEM
|
|||
rake
|
||||
grape_logging (1.7.0)
|
||||
grape
|
||||
grpc (1.8.3)
|
||||
grpc (1.10.0)
|
||||
google-protobuf (~> 3.1)
|
||||
googleapis-common-protos-types (~> 1.0.0)
|
||||
googleauth (>= 0.5.1, < 0.7)
|
||||
|
@ -503,7 +505,7 @@ GEM
|
|||
mini_portile2 (2.3.0)
|
||||
minitest (5.7.0)
|
||||
mousetrap-rails (1.4.6)
|
||||
multi_json (1.12.2)
|
||||
multi_json (1.13.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
mustermann (1.0.0)
|
||||
|
@ -601,7 +603,7 @@ GEM
|
|||
atomic (>= 1.0.0)
|
||||
mysql2
|
||||
peek
|
||||
peek-performance_bar (1.3.0)
|
||||
peek-performance_bar (1.3.1)
|
||||
peek (>= 0.1.0)
|
||||
peek-pg (1.3.0)
|
||||
concurrent-ruby
|
||||
|
@ -646,7 +648,7 @@ GEM
|
|||
pry (~> 0.10)
|
||||
pry-rails (0.3.5)
|
||||
pry (>= 0.9.10)
|
||||
public_suffix (3.0.0)
|
||||
public_suffix (3.0.2)
|
||||
pyu-ruby-sasl (0.0.3.3)
|
||||
rack (1.6.8)
|
||||
rack-accept (0.4.5)
|
||||
|
@ -797,6 +799,8 @@ GEM
|
|||
rubocop (>= 0.51)
|
||||
rubocop-rspec (1.22.1)
|
||||
rubocop (>= 0.52.1)
|
||||
ruby-enum (0.7.2)
|
||||
i18n
|
||||
ruby-fogbugz (0.2.1)
|
||||
crack (~> 0.4)
|
||||
ruby-prof (0.16.2)
|
||||
|
@ -858,10 +862,10 @@ GEM
|
|||
sidekiq (>= 4.2.1)
|
||||
sidekiq-limit_fetch (3.4.0)
|
||||
sidekiq (>= 4)
|
||||
signet (0.7.3)
|
||||
signet (0.8.1)
|
||||
addressable (~> 2.3)
|
||||
faraday (~> 0.9)
|
||||
jwt (~> 1.5)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simple_po_parser (1.1.2)
|
||||
simplecov (0.14.1)
|
||||
|
@ -895,6 +899,7 @@ GEM
|
|||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
sqlite3 (1.3.13)
|
||||
sshkey (1.9.0)
|
||||
stackprof (0.2.10)
|
||||
state_machines (0.4.0)
|
||||
state_machines-activemodel (0.4.0)
|
||||
|
@ -923,7 +928,7 @@ GEM
|
|||
timfel-krb5-auth (0.8.3)
|
||||
toml (0.1.2)
|
||||
parslet (~> 1.5.0)
|
||||
toml-rb (0.3.15)
|
||||
toml-rb (1.0.0)
|
||||
citrus (~> 3.0, > 3.0)
|
||||
truncato (0.7.10)
|
||||
htmlentities (~> 4.3.1)
|
||||
|
@ -1018,6 +1023,7 @@ DEPENDENCIES
|
|||
charlock_holmes (~> 0.7.5)
|
||||
chronic (~> 0.10.2)
|
||||
chronic_duration (~> 0.10.6)
|
||||
commonmarker (~> 0.17)
|
||||
concurrent-ruby (~> 1.0.5)
|
||||
connection_pool (~> 2.0)
|
||||
creole (~> 0.5.0)
|
||||
|
@ -1056,8 +1062,8 @@ DEPENDENCIES
|
|||
gettext (~> 3.2.2)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.2.0)
|
||||
gitaly-proto (~> 0.84.0)
|
||||
github-linguist (~> 4.7.0)
|
||||
gitaly-proto (~> 0.88.0)
|
||||
github-linguist (~> 5.3.3)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab-markup (~> 1.6.2)
|
||||
gitlab-styles (~> 2.3)
|
||||
|
@ -1072,6 +1078,7 @@ DEPENDENCIES
|
|||
grape-entity (~> 0.6.0)
|
||||
grape-route-helpers (~> 2.1.0)
|
||||
grape_logging (~> 1.7)
|
||||
grpc (~> 1.10.0)
|
||||
haml_lint (~> 0.26.0)
|
||||
hamlit (~> 2.6.1)
|
||||
hashie-forbidden_attributes
|
||||
|
@ -1192,6 +1199,7 @@ DEPENDENCIES
|
|||
spring-commands-rspec (~> 1.0.4)
|
||||
spring-commands-spinach (~> 1.1.0)
|
||||
sprockets (~> 3.7.0)
|
||||
sshkey (~> 1.9.0)
|
||||
stackprof (~> 0.2.10)
|
||||
state_machines-activerecord (~> 0.4.0)
|
||||
sys-filesystem (~> 1.1.6)
|
||||
|
@ -1199,7 +1207,7 @@ DEPENDENCIES
|
|||
test_after_commit (~> 1.1)
|
||||
thin (~> 1.7.0)
|
||||
timecop (~> 0.8.0)
|
||||
toml-rb (~> 0.3.15)
|
||||
toml-rb (~> 1.0.0)
|
||||
truncato (~> 0.7.9)
|
||||
u2f (~> 0.2.1)
|
||||
uglifier (~> 2.7.2)
|
||||
|
|
13
PROCESS.md
13
PROCESS.md
|
@ -71,11 +71,15 @@ star, smile, etc.). Some good tips about code reviews can be found in our
|
|||
|
||||
## Feature freeze on the 7th for the release on the 22nd
|
||||
|
||||
After the 7th (Pacific Standard Time Zone) of each month, RC1 of the upcoming release (to be shipped on the 22nd) is created and deployed to GitLab.com and the stable branch for this release is frozen, which means master is no longer merged into it.
|
||||
After 7th at 23:59 (Pacific Time Zone) of each month, RC1 of the upcoming release (to be shipped on the 22nd) is created and deployed to GitLab.com and the stable branch for this release is frozen, which means master is no longer merged into it.
|
||||
Merge requests may still be merged into master during this period,
|
||||
but they will go into the _next_ release, unless they are manually cherry-picked into the stable branch.
|
||||
|
||||
By freezing the stable branches 2 weeks prior to a release, we reduce the risk of a last minute merge request potentially breaking things.
|
||||
|
||||
Any release candidate that gets created after this date can become a final release,
|
||||
hence the name release candidate.
|
||||
|
||||
### Between the 1st and the 7th
|
||||
|
||||
These types of merge requests for the upcoming release need special consideration:
|
||||
|
@ -193,11 +197,10 @@ to be backported down to the `9.5` release, you will need to assign it the
|
|||
### Asking for an exception
|
||||
|
||||
If you think a merge request should go into an RC or patch even though it does not meet these requirements,
|
||||
you can ask for an exception to be made. Exceptions require sign-off from 3 people besides the developer:
|
||||
you can ask for an exception to be made.
|
||||
|
||||
1. a Release Manager
|
||||
2. an Engineering Lead
|
||||
3. an Engineering Director, the VP of Engineering, or the CTO
|
||||
Go to [Release tasks issue tracker](https://gitlab.com/gitlab-org/release/tasks/issues/new) and create an issue
|
||||
using the `Exception-request` issue template.
|
||||
|
||||
You can find who is who on the [team page](https://about.gitlab.com/team/).
|
||||
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
10.5.6
|
||||
10.6.0
|
||||
|
|
|
@ -9,7 +9,7 @@ const Api = {
|
|||
projectsPath: '/api/:version/projects.json',
|
||||
projectPath: '/api/:version/projects/:id',
|
||||
projectLabelsPath: '/:namespace_path/:project_path/labels',
|
||||
groupLabelsPath: '/groups/:namespace_path/labels',
|
||||
groupLabelsPath: '/groups/:namespace_path/-/labels',
|
||||
licensePath: '/api/:version/templates/licenses/:key',
|
||||
gitignorePath: '/api/:version/templates/gitignores/:key',
|
||||
gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key',
|
||||
|
@ -32,7 +32,7 @@ const Api = {
|
|||
},
|
||||
|
||||
// Return groups list. Filtered by query
|
||||
groups(query, options, callback) {
|
||||
groups(query, options, callback = $.noop) {
|
||||
const url = Api.buildUrl(Api.groupsPath);
|
||||
return axios.get(url, {
|
||||
params: Object.assign({
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
import AccessorUtilities from './lib/utils/accessor';
|
||||
|
||||
export default class Autosave {
|
||||
constructor(field, key, resource) {
|
||||
constructor(field, key) {
|
||||
this.field = field;
|
||||
|
||||
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
|
||||
this.resource = resource;
|
||||
if (key.join != null) {
|
||||
key = key.join('/');
|
||||
}
|
||||
|
@ -17,31 +17,27 @@ export default class Autosave {
|
|||
}
|
||||
|
||||
restore() {
|
||||
var text;
|
||||
|
||||
if (!this.isLocalStorageAvailable) return;
|
||||
if (!this.field.length) return;
|
||||
|
||||
text = window.localStorage.getItem(this.key);
|
||||
const text = window.localStorage.getItem(this.key);
|
||||
|
||||
if ((text != null ? text.length : void 0) > 0) {
|
||||
this.field.val(text);
|
||||
}
|
||||
if (!this.resource && this.resource !== 'issue') {
|
||||
this.field.trigger('input');
|
||||
} else {
|
||||
// v-model does not update with jQuery trigger
|
||||
// https://github.com/vuejs/vue/issues/2804#issuecomment-216968137
|
||||
const event = new Event('change', { bubbles: true, cancelable: false });
|
||||
const field = this.field.get(0);
|
||||
if (field) {
|
||||
field.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
this.field.trigger('input');
|
||||
// v-model does not update with jQuery trigger
|
||||
// https://github.com/vuejs/vue/issues/2804#issuecomment-216968137
|
||||
const event = new Event('change', { bubbles: true, cancelable: false });
|
||||
const field = this.field.get(0);
|
||||
field.dispatchEvent(event);
|
||||
}
|
||||
|
||||
save() {
|
||||
var text;
|
||||
text = this.field.val();
|
||||
if (!this.field.length) return;
|
||||
|
||||
const text = this.field.val();
|
||||
|
||||
if (this.isLocalStorageAvailable && (text != null ? text.length : void 0) > 0) {
|
||||
return window.localStorage.setItem(this.key, text);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import _ from 'underscore';
|
||||
import Cookies from 'js-cookie';
|
||||
import { __ } from './locale';
|
||||
import { isInIssuePage, updateTooltipTitle } from './lib/utils/common_utils';
|
||||
import { isInIssuePage, isInMRPage, hasVueMRDiscussionsCookie, updateTooltipTitle } from './lib/utils/common_utils';
|
||||
import flash from './flash';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
|
||||
|
@ -50,10 +50,8 @@ class AwardsHandler {
|
|||
|
||||
this.registerEventListener('on', $('html'), 'click', (e) => {
|
||||
const $target = $(e.target);
|
||||
if (!$target.closest('.emoji-menu-content').length) {
|
||||
$('.js-awards-block.current').removeClass('current');
|
||||
}
|
||||
if (!$target.closest('.emoji-menu').length) {
|
||||
$('.js-awards-block.current').removeClass('current');
|
||||
if ($('.emoji-menu').is(':visible')) {
|
||||
$('.js-add-award.is-active').removeClass('is-active');
|
||||
this.hideMenuElement($('.emoji-menu'));
|
||||
|
@ -241,9 +239,9 @@ class AwardsHandler {
|
|||
}
|
||||
|
||||
addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) {
|
||||
const isMainAwardsBlock = votesBlock.closest('.js-issue-note-awards').length;
|
||||
const isMainAwardsBlock = votesBlock.closest('.js-noteable-awards').length;
|
||||
|
||||
if (isInIssuePage() && !isMainAwardsBlock) {
|
||||
if (this.isInVueNoteablePage() && !isMainAwardsBlock) {
|
||||
const id = votesBlock.attr('id').replace('note_', '');
|
||||
|
||||
this.hideMenuElement($('.emoji-menu'));
|
||||
|
@ -295,8 +293,16 @@ class AwardsHandler {
|
|||
}
|
||||
}
|
||||
|
||||
isVueMRDiscussions() {
|
||||
return isInMRPage() && hasVueMRDiscussionsCookie() && !$('#diffs').is(':visible');
|
||||
}
|
||||
|
||||
isInVueNoteablePage() {
|
||||
return isInIssuePage() || this.isVueMRDiscussions();
|
||||
}
|
||||
|
||||
getVotesBlock() {
|
||||
if (isInIssuePage()) {
|
||||
if (this.isInVueNoteablePage()) {
|
||||
const $el = $('.js-add-award.is-active').closest('.note.timeline-entry');
|
||||
|
||||
if ($el.length) {
|
||||
|
@ -314,7 +320,7 @@ class AwardsHandler {
|
|||
}
|
||||
|
||||
getAwardUrl() {
|
||||
return this.getVotesBlock().data('award-url');
|
||||
return this.getVotesBlock().data('awardUrl');
|
||||
}
|
||||
|
||||
checkMutuality(votesBlock, emoji) {
|
||||
|
|
|
@ -2,7 +2,7 @@ import Clipboard from 'clipboard';
|
|||
|
||||
function showTooltip(target, title) {
|
||||
const $target = $(target);
|
||||
const originalTitle = $target.data('original-title');
|
||||
const originalTitle = $target.data('originalTitle');
|
||||
|
||||
if (!$target.data('hideTooltip')) {
|
||||
$target
|
||||
|
|
|
@ -43,7 +43,7 @@ $(document).on('keydown.quick_submit', '.js-quick-submit', (e) => {
|
|||
const $form = $(e.target).closest('form');
|
||||
const $submitButton = $form.find('input[type=submit], button[type=submit]').first();
|
||||
|
||||
if (!$submitButton.attr('disabled')) {
|
||||
if (!$submitButton.prop('disabled')) {
|
||||
$submitButton.trigger('click', [e]);
|
||||
|
||||
if (!isInIssuePage()) {
|
||||
|
|
|
@ -40,7 +40,7 @@ $.fn.requiresInput = function requiresInput() {
|
|||
// based on the option selected
|
||||
function hideOrShowHelpBlock(form) {
|
||||
const selected = $('.js-select-namespace option:selected');
|
||||
if (selected.length && selected.data('options-parent') === 'groups') {
|
||||
if (selected.length && selected.data('optionsParent') === 'groups') {
|
||||
form.find('.help-block').hide();
|
||||
} else if (selected.length) {
|
||||
form.find('.help-block').show();
|
||||
|
|
|
@ -12,7 +12,7 @@ $(() => {
|
|||
const $container = $(container);
|
||||
|
||||
$container
|
||||
.find('.js-toggle-button .fa')
|
||||
.find('.js-toggle-button .fa-chevron-up, .js-toggle-button .fa-chevron-down')
|
||||
.toggleClass('fa-chevron-up', toggleState)
|
||||
.toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
|
||||
|
||||
|
@ -22,7 +22,7 @@ $(() => {
|
|||
}
|
||||
|
||||
$('body').on('click', '.js-toggle-button', function toggleButton(e) {
|
||||
e.target.classList.toggle('open');
|
||||
e.currentTarget.classList.toggle(e.currentTarget.dataset.toggleOpenClass || 'open');
|
||||
toggleContainer($(this).closest('.js-toggle-container'));
|
||||
|
||||
const targetTag = e.currentTarget.tagName.toLowerCase();
|
||||
|
|
|
@ -7,7 +7,7 @@ function onError() {
|
|||
return flash;
|
||||
}
|
||||
|
||||
function loadBalsamiqFile() {
|
||||
export default function loadBalsamiqFile() {
|
||||
const viewer = document.getElementById('js-balsamiq-viewer');
|
||||
|
||||
if (!(viewer instanceof Element)) return;
|
||||
|
@ -17,5 +17,3 @@ function loadBalsamiqFile() {
|
|||
const balsamiqViewer = new BalsamiqViewer(viewer);
|
||||
balsamiqViewer.loadFile(endpoint).catch(onError);
|
||||
}
|
||||
|
||||
$(loadBalsamiqFile);
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import renderNotebook from './notebook';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', renderNotebook);
|
||||
export default renderNotebook;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import renderPDF from './pdf';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', renderPDF);
|
||||
export default renderPDF;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable no-new */
|
||||
import SketchLoader from './sketch';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
export default () => {
|
||||
const el = document.getElementById('js-sketch-viewer');
|
||||
|
||||
new SketchLoader(el);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Renderer from './3d_viewer';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
export default () => {
|
||||
const viewer = new Renderer(document.getElementById('js-stl-viewer'));
|
||||
|
||||
[].slice.call(document.querySelectorAll('.js-material-changer')).forEach((el) => {
|
||||
|
@ -16,4 +16,4 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
viewer.changeObjectMaterials(target.dataset.type);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@ import axios from '../../lib/utils/axios_utils';
|
|||
export default class BlobViewer {
|
||||
constructor() {
|
||||
BlobViewer.initAuxiliaryViewer();
|
||||
BlobViewer.initRichViewer();
|
||||
|
||||
this.initMainViewers();
|
||||
}
|
||||
|
@ -16,6 +17,38 @@ export default class BlobViewer {
|
|||
BlobViewer.loadViewer(auxiliaryViewer);
|
||||
}
|
||||
|
||||
static initRichViewer() {
|
||||
const viewer = document.querySelector('.blob-viewer[data-type="rich"]');
|
||||
if (!viewer || !viewer.dataset.richType) return;
|
||||
|
||||
const initViewer = promise => promise
|
||||
.then(module => module.default(viewer))
|
||||
.catch((error) => {
|
||||
Flash('Error loading file viewer.');
|
||||
throw error;
|
||||
});
|
||||
|
||||
switch (viewer.dataset.richType) {
|
||||
case 'balsamiq':
|
||||
initViewer(import(/* webpackChunkName: 'balsamiq_viewer' */ '../balsamiq_viewer'));
|
||||
break;
|
||||
case 'notebook':
|
||||
initViewer(import(/* webpackChunkName: 'notebook_viewer' */ '../notebook_viewer'));
|
||||
break;
|
||||
case 'pdf':
|
||||
initViewer(import(/* webpackChunkName: 'pdf_viewer' */ '../pdf_viewer'));
|
||||
break;
|
||||
case 'sketch':
|
||||
initViewer(import(/* webpackChunkName: 'sketch_viewer' */ '../sketch_viewer'));
|
||||
break;
|
||||
case 'stl':
|
||||
initViewer(import(/* webpackChunkName: 'stl_viewer' */ '../stl_viewer'));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
initMainViewers() {
|
||||
this.$fileHolder = $('.file-holder');
|
||||
if (!this.$fileHolder.length) return;
|
||||
|
|
|
@ -4,16 +4,16 @@ import NewCommitForm from '../new_commit_form';
|
|||
import EditBlob from './edit_blob';
|
||||
import BlobFileDropzone from '../blob/blob_file_dropzone';
|
||||
|
||||
$(() => {
|
||||
export default () => {
|
||||
const editBlobForm = $('.js-edit-blob-form');
|
||||
const uploadBlobForm = $('.js-upload-blob-form');
|
||||
const deleteBlobForm = $('.js-delete-blob-form');
|
||||
|
||||
if (editBlobForm.length) {
|
||||
const urlRoot = editBlobForm.data('relative-url-root');
|
||||
const assetsPath = editBlobForm.data('assets-prefix');
|
||||
const blobLanguage = editBlobForm.data('blob-language');
|
||||
const currentAction = $('.js-file-title').data('current-action');
|
||||
const urlRoot = editBlobForm.data('relativeUrlRoot');
|
||||
const assetsPath = editBlobForm.data('assetsPrefix');
|
||||
const blobLanguage = editBlobForm.data('blobLanguage');
|
||||
const currentAction = $('.js-file-title').data('currentAction');
|
||||
|
||||
new EditBlob(`${urlRoot}${assetsPath}`, blobLanguage, currentAction);
|
||||
new NewCommitForm(editBlobForm);
|
||||
|
@ -34,4 +34,4 @@ $(() => {
|
|||
if (deleteBlobForm.length) {
|
||||
new NewCommitForm(deleteBlobForm);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -59,7 +59,7 @@ export default class EditBlob {
|
|||
|
||||
if (paneId === '#preview') {
|
||||
this.$toggleButton.hide();
|
||||
axios.post(currentLink.data('preview-url'), {
|
||||
axios.post(currentLink.data('previewUrl'), {
|
||||
content: this.editor.getValue(),
|
||||
})
|
||||
.then(({ data }) => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
/* eslint-disable vue/require-default-prop */
|
||||
import './issue_card_inner';
|
||||
import eventHub from '../eventhub';
|
||||
|
||||
|
@ -34,6 +35,9 @@ export default {
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
groupId: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -88,6 +92,7 @@ export default {
|
|||
:list="list"
|
||||
:issue="issue"
|
||||
:issue-link-base="issueLinkBase"
|
||||
:group-id="groupId"
|
||||
:root-path="rootPath"
|
||||
:update-filters="true"
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import Sortable from 'vendor/Sortable';
|
||||
import boardNewIssue from './board_new_issue';
|
||||
import boardNewIssue from './board_new_issue.vue';
|
||||
import boardCard from './board_card.vue';
|
||||
import eventHub from '../eventhub';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
|
@ -15,6 +15,11 @@ export default {
|
|||
loadingIcon,
|
||||
},
|
||||
props: {
|
||||
groupId: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
|
@ -170,6 +175,7 @@ export default {
|
|||
<loading-icon />
|
||||
</div>
|
||||
<board-new-issue
|
||||
:group-id="groupId"
|
||||
:list="list"
|
||||
v-if="list.type !== 'closed' && showIssueForm"/>
|
||||
<ul
|
||||
|
@ -185,6 +191,7 @@ export default {
|
|||
:list="list"
|
||||
:issue="issue"
|
||||
:issue-link-base="issueLinkBase"
|
||||
:group-id="groupId"
|
||||
:root-path="rootPath"
|
||||
:disabled="disabled"
|
||||
:key="issue.id" />
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
/* global ListIssue */
|
||||
<script>
|
||||
import eventHub from '../eventhub';
|
||||
import ProjectSelect from './project_select.vue';
|
||||
import ListIssue from '../models/issue';
|
||||
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
export default {
|
||||
name: 'BoardNewIssue',
|
||||
components: {
|
||||
ProjectSelect,
|
||||
},
|
||||
props: {
|
||||
groupId: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
list: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
@ -15,8 +25,21 @@ export default {
|
|||
return {
|
||||
title: '',
|
||||
error: false,
|
||||
selectedProject: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
disabled() {
|
||||
if (this.groupId) {
|
||||
return this.title === '' || !this.selectedProject.name;
|
||||
}
|
||||
return this.title === '';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.input.focus();
|
||||
eventHub.$on('setSelectedProject', this.setSelectedProject);
|
||||
},
|
||||
methods: {
|
||||
submit(e) {
|
||||
e.preventDefault();
|
||||
|
@ -30,6 +53,7 @@ export default {
|
|||
labels,
|
||||
subscribed: true,
|
||||
assignees: [],
|
||||
project_id: this.selectedProject.id,
|
||||
});
|
||||
|
||||
eventHub.$emit(`scroll-board-list-${this.list.id}`);
|
||||
|
@ -58,43 +82,62 @@ export default {
|
|||
this.title = '';
|
||||
eventHub.$emit(`hide-issue-form-${this.list.id}`);
|
||||
},
|
||||
setSelectedProject(selectedProject) {
|
||||
this.selectedProject = selectedProject;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.input.focus();
|
||||
},
|
||||
template: `
|
||||
<div class="card board-new-issue-form">
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="board-new-issue-form">
|
||||
<div class="card">
|
||||
<form @submit="submit($event)">
|
||||
<div class="flash-container"
|
||||
v-if="error">
|
||||
<div
|
||||
class="flash-container"
|
||||
v-if="error"
|
||||
>
|
||||
<div class="flash-alert">
|
||||
An error occurred. Please try again.
|
||||
</div>
|
||||
</div>
|
||||
<label class="label-light"
|
||||
:for="list.id + '-title'">
|
||||
<label
|
||||
class="label-light"
|
||||
:for="list.id + '-title'"
|
||||
>
|
||||
Title
|
||||
</label>
|
||||
<input class="form-control"
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
v-model="title"
|
||||
ref="input"
|
||||
autocomplete="off"
|
||||
:id="list.id + '-title'" />
|
||||
:id="list.id + '-title'"
|
||||
/>
|
||||
<project-select
|
||||
v-if="groupId"
|
||||
:group-id="groupId"
|
||||
/>
|
||||
<div class="clearfix prepend-top-10">
|
||||
<button class="btn btn-success pull-left"
|
||||
<button
|
||||
class="btn btn-success pull-left"
|
||||
type="submit"
|
||||
:disabled="title === ''"
|
||||
ref="submit-button">
|
||||
:disabled="disabled"
|
||||
ref="submit-button"
|
||||
>
|
||||
Submit issue
|
||||
</button>
|
||||
<button class="btn btn-default pull-right"
|
||||
<button
|
||||
class="btn btn-default pull-right"
|
||||
type="button"
|
||||
@click="cancel">
|
||||
@click="cancel"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
import Vue from 'vue';
|
||||
import Flash from '../../flash';
|
||||
import { __ } from '../../locale';
|
||||
import Sidebar from '../../right_sidebar';
|
||||
import eventHub from '../../sidebar/event_hub';
|
||||
import assigneeTitle from '../../sidebar/components/assignees/assignee_title';
|
||||
import assignees from '../../sidebar/components/assignees/assignees';
|
||||
import assignees from '../../sidebar/components/assignees/assignees.vue';
|
||||
import DueDateSelectors from '../../due_date_select';
|
||||
import './sidebar/remove_issue';
|
||||
import IssuableContext from '../../issuable_context';
|
||||
|
@ -95,7 +96,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({
|
|||
})
|
||||
.catch(() => {
|
||||
this.loadingAssignees = false;
|
||||
return new Flash('An error occurred while saving assignees');
|
||||
Flash(__('An error occurred while saving assignees'));
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
|
@ -31,6 +31,10 @@ gl.issueBoards.IssueCardInner = Vue.extend({
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
groupId: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -64,7 +68,13 @@ gl.issueBoards.IssueCardInner = Vue.extend({
|
|||
return this.issue.assignees.length > this.numberOverLimit;
|
||||
},
|
||||
cardUrl() {
|
||||
return `${this.issueLinkBase}/${this.issue.iid}`;
|
||||
let baseUrl = this.issueLinkBase;
|
||||
|
||||
if (this.groupId && this.issue.project) {
|
||||
baseUrl = this.issueLinkBase.replace(':project_path', this.issue.project.path);
|
||||
}
|
||||
|
||||
return `${baseUrl}/${this.issue.iid}`;
|
||||
},
|
||||
issueId() {
|
||||
if (this.issue.iid) {
|
||||
|
@ -148,7 +158,7 @@ gl.issueBoards.IssueCardInner = Vue.extend({
|
|||
class="card-number"
|
||||
v-if="issueId"
|
||||
>
|
||||
{{ issueId }}
|
||||
<template v-if="groupId && issue.project">{{issue.project.path}}</template>{{ issueId }}
|
||||
</span>
|
||||
</h4>
|
||||
<div class="card-assignee">
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* eslint-disable no-new */
|
||||
|
||||
import Vue from 'vue';
|
||||
import Flash from '../../../flash';
|
||||
import { __ } from '../../../locale';
|
||||
import './lists_dropdown';
|
||||
import { pluralize } from '../../../lib/utils/text_utility';
|
||||
|
||||
|
@ -36,7 +35,7 @@ gl.issueBoards.ModalFooter = Vue.extend({
|
|||
gl.boardService.bulkUpdate(issueIds, {
|
||||
add_label_ids: [list.label.id],
|
||||
}).catch(() => {
|
||||
new Flash('Failed to update issues, please try again.', 'alert');
|
||||
Flash(__('Failed to update issues, please try again.'));
|
||||
|
||||
selectedIssues.forEach((issue) => {
|
||||
list.removeIssue(issue);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* eslint-disable func-names, no-new, space-before-function-paren, one-var,
|
||||
promise/catch-or-return */
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import _ from 'underscore';
|
||||
import CreateLabelDropdown from '../../create_label';
|
||||
|
||||
|
@ -24,13 +25,13 @@ $(document).off('created.label').on('created.label', (e, label) => {
|
|||
gl.issueBoards.newListDropdownInit = () => {
|
||||
$('.js-new-board-list').each(function () {
|
||||
const $this = $(this);
|
||||
new CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
|
||||
new CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespacePath'), $this.data('projectPath'));
|
||||
|
||||
$this.glDropdown({
|
||||
data(term, callback) {
|
||||
$.get($this.attr('data-list-labels-path'))
|
||||
.then((resp) => {
|
||||
callback(resp);
|
||||
axios.get($this.attr('data-list-labels-path'))
|
||||
.then(({ data }) => {
|
||||
callback(data);
|
||||
});
|
||||
},
|
||||
renderRow (label) {
|
||||
|
|
127
app/assets/javascripts/boards/components/project_select.vue
Normal file
127
app/assets/javascripts/boards/components/project_select.vue
Normal file
|
@ -0,0 +1,127 @@
|
|||
<script>
|
||||
/* global ListIssue */
|
||||
import _ from 'underscore';
|
||||
import eventHub from '../eventhub';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import Api from '../../api';
|
||||
|
||||
export default {
|
||||
name: 'BoardProjectSelect',
|
||||
components: {
|
||||
loadingIcon,
|
||||
},
|
||||
props: {
|
||||
groupId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
selectedProject: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedProjectName() {
|
||||
return this.selectedProject.name || 'Select a project';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
$(this.$refs.projectsDropdown).glDropdown({
|
||||
filterable: true,
|
||||
filterRemote: true,
|
||||
search: {
|
||||
fields: ['name_with_namespace'],
|
||||
},
|
||||
clicked: ({ $el, e }) => {
|
||||
e.preventDefault();
|
||||
this.selectedProject = {
|
||||
id: $el.data('project-id'),
|
||||
name: $el.data('project-name'),
|
||||
};
|
||||
eventHub.$emit('setSelectedProject', this.selectedProject);
|
||||
},
|
||||
selectable: true,
|
||||
data: (term, callback) => {
|
||||
this.loading = true;
|
||||
return Api.groupProjects(this.groupId, term, (projects) => {
|
||||
this.loading = false;
|
||||
callback(projects);
|
||||
});
|
||||
},
|
||||
renderRow(project) {
|
||||
return `
|
||||
<li>
|
||||
<a href='#' class='dropdown-menu-link' data-project-id="${project.id}" data-project-name="${project.name}">
|
||||
${_.escape(project.name)}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
},
|
||||
text: project => project.name,
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<label class="label-light prepend-top-10">
|
||||
Project
|
||||
</label>
|
||||
<div
|
||||
ref="projectsDropdown"
|
||||
class="dropdown"
|
||||
>
|
||||
<button
|
||||
class="dropdown-menu-toggle wide"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{{ selectedProjectName }}
|
||||
<i
|
||||
class="fa fa-chevron-down"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-full-width">
|
||||
<div class="dropdown-title">
|
||||
<span>Projects</span>
|
||||
<button
|
||||
aria-label="Close"
|
||||
type="button"
|
||||
class="dropdown-title-button dropdown-menu-close"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
data-hidden="true"
|
||||
class="fa fa-times dropdown-menu-close-icon"
|
||||
>
|
||||
</i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-input">
|
||||
<input
|
||||
class="dropdown-input-field"
|
||||
type="search"
|
||||
placeholder="Search projects"
|
||||
/>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
data-hidden="true"
|
||||
class="fa fa-search dropdown-input-search"
|
||||
>
|
||||
</i>
|
||||
</div>
|
||||
<div class="dropdown-content"></div>
|
||||
<div class="dropdown-loading">
|
||||
<loading-icon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,7 +1,6 @@
|
|||
/* eslint-disable no-new */
|
||||
|
||||
import Vue from 'vue';
|
||||
import Flash from '../../../flash';
|
||||
import { __ } from '../../../locale';
|
||||
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
|
@ -25,7 +24,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
|
|||
},
|
||||
computed: {
|
||||
updateUrl() {
|
||||
return this.issueUpdate;
|
||||
return this.issueUpdate.replace(':project_path', this.issue.project.path);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -33,19 +32,23 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
|
|||
const issue = this.issue;
|
||||
const lists = issue.getLists();
|
||||
const listLabelIds = lists.map(list => list.label.id);
|
||||
let labelIds = this.issue.labels
|
||||
|
||||
let labelIds = issue.labels
|
||||
.map(label => label.id)
|
||||
.filter(id => !listLabelIds.includes(id));
|
||||
if (labelIds.length === 0) {
|
||||
labelIds = [''];
|
||||
}
|
||||
|
||||
const data = {
|
||||
issue: {
|
||||
label_ids: labelIds,
|
||||
},
|
||||
};
|
||||
|
||||
// Post the remove data
|
||||
Vue.http.patch(this.updateUrl, data).catch(() => {
|
||||
new Flash('Failed to remove issue from board, please try again.', 'alert');
|
||||
Flash(__('Failed to remove issue from board, please try again.'));
|
||||
|
||||
lists.forEach((list) => {
|
||||
list.addIssue(issue);
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
import FilteredSearchContainer from '../filtered_search/container';
|
||||
import FilteredSearchManager from '../filtered_search/filtered_search_manager';
|
||||
|
||||
export default class FilteredSearchBoards extends gl.FilteredSearchManager {
|
||||
export default class FilteredSearchBoards extends FilteredSearchManager {
|
||||
constructor(store, updateUrl = false, cantEdit = []) {
|
||||
super('boards');
|
||||
super({
|
||||
page: 'boards',
|
||||
stateFiltersSelector: '.issues-state-filters',
|
||||
});
|
||||
|
||||
this.store = store;
|
||||
this.updateUrl = updateUrl;
|
||||
|
|
|
@ -2,15 +2,18 @@
|
|||
|
||||
import _ from 'underscore';
|
||||
import Vue from 'vue';
|
||||
import Flash from '../flash';
|
||||
import { __ } from '../locale';
|
||||
|
||||
import Flash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import '~/vue_shared/models/label';
|
||||
|
||||
import FilteredSearchBoards from './filtered_search_boards';
|
||||
import eventHub from './eventhub';
|
||||
import sidebarEventHub from '../sidebar/event_hub';
|
||||
import sidebarEventHub from '~/sidebar/event_hub'; // eslint-disable-line import/first
|
||||
import './models/issue';
|
||||
import './models/label';
|
||||
import './models/list';
|
||||
import './models/milestone';
|
||||
import './models/project';
|
||||
import './models/assignee';
|
||||
import './stores/boards_store';
|
||||
import './stores/modal_store';
|
||||
|
@ -22,9 +25,9 @@ import './components/board';
|
|||
import './components/board_sidebar';
|
||||
import './components/new_list_dropdown';
|
||||
import './components/modal/index';
|
||||
import '../vue_shared/vue_resource_interceptor';
|
||||
import '~/vue_shared/vue_resource_interceptor'; // eslint-disable-line import/first
|
||||
|
||||
$(() => {
|
||||
export default () => {
|
||||
const $boardApp = document.getElementById('board-app');
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
@ -87,7 +90,7 @@ $(() => {
|
|||
sidebarEventHub.$off('toggleSubscription', this.toggleSubscription);
|
||||
},
|
||||
mounted () {
|
||||
this.filterManager = new FilteredSearchBoards(Store.filter, true);
|
||||
this.filterManager = new FilteredSearchBoards(Store.filter, true, Store.cantEdit);
|
||||
this.filterManager.setup();
|
||||
|
||||
Store.disabled = this.disabled;
|
||||
|
@ -177,6 +180,7 @@ $(() => {
|
|||
return {
|
||||
modal: ModalStore.store,
|
||||
store: Store.state,
|
||||
canAdminList: this.$options.el.hasAttribute('data-can-admin-list'),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -230,10 +234,11 @@ $(() => {
|
|||
:class="{ 'disabled': disabled }"
|
||||
:title="tooltipTitle"
|
||||
:aria-disabled="disabled"
|
||||
v-if="canAdminList"
|
||||
@click="openModal">
|
||||
Add issues
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1,6 +1,8 @@
|
|||
/* eslint-disable no-unused-vars, no-mixed-operators, comma-dangle */
|
||||
/* global DocumentTouch */
|
||||
|
||||
import sortableConfig from '../../sortable/sortable_config';
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
|
@ -18,19 +20,14 @@ gl.issueBoards.onEnd = () => {
|
|||
gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
|
||||
|
||||
gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
|
||||
const defaultSortOptions = {
|
||||
animation: 200,
|
||||
forceFallback: true,
|
||||
fallbackClass: 'is-dragging',
|
||||
fallbackOnBody: true,
|
||||
ghostClass: 'is-ghost',
|
||||
const defaultSortOptions = Object.assign({}, sortableConfig, {
|
||||
filter: '.board-delete, .btn',
|
||||
delay: gl.issueBoards.touchEnabled ? 100 : 0,
|
||||
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
|
||||
scrollSpeed: 20,
|
||||
onStart: gl.issueBoards.onStart,
|
||||
onEnd: gl.issueBoards.onEnd
|
||||
};
|
||||
onEnd: gl.issueBoards.onEnd,
|
||||
});
|
||||
|
||||
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
|
||||
return defaultSortOptions;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
/* global ListAssignee */
|
||||
|
||||
import Vue from 'vue';
|
||||
import IssueProject from './project';
|
||||
|
||||
class ListIssue {
|
||||
constructor (obj, defaultAvatar) {
|
||||
|
@ -23,6 +24,12 @@ class ListIssue {
|
|||
this.isLoading = {};
|
||||
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
|
||||
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
|
||||
this.milestone_id = obj.milestone_id;
|
||||
this.project_id = obj.project_id;
|
||||
|
||||
if (obj.project) {
|
||||
this.project = new IssueProject(obj.project);
|
||||
}
|
||||
|
||||
if (obj.milestone) {
|
||||
this.milestone = new ListMilestone(obj.milestone);
|
||||
|
@ -105,8 +112,11 @@ class ListIssue {
|
|||
data.issue.label_ids = [''];
|
||||
}
|
||||
|
||||
return Vue.http.patch(url, data);
|
||||
const projectPath = this.project ? this.project.path : '';
|
||||
return Vue.http.patch(url.replace(':project_path', projectPath), data);
|
||||
}
|
||||
}
|
||||
|
||||
window.ListIssue = ListIssue;
|
||||
|
||||
export default ListIssue;
|
||||
|
|
6
app/assets/javascripts/boards/models/project.js
Normal file
6
app/assets/javascripts/boards/models/project.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
export default class IssueProject {
|
||||
constructor(obj) {
|
||||
this.id = obj.id;
|
||||
this.path = obj.path;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
/* global List */
|
||||
import _ from 'underscore';
|
||||
import Cookies from 'js-cookie';
|
||||
import { getUrlParamsArray } from '../../lib/utils/common_utils';
|
||||
import { getUrlParamsArray } from '~/lib/utils/common_utils';
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
|
|
@ -75,6 +75,7 @@ export default class AjaxVariableList {
|
|||
|
||||
if (res.status === statusCodes.OK && res.data) {
|
||||
this.updateRowsWithPersistedVariables(res.data.variables);
|
||||
this.variableList.hideValues();
|
||||
} else if (res.status === statusCodes.BAD_REQUEST) {
|
||||
// Validation failed
|
||||
this.errorBox.innerHTML = generateErrorBoxContent(res.data);
|
||||
|
|
|
@ -178,6 +178,10 @@ export default class VariableList {
|
|||
this.$container.find('.js-row-remove-button').attr('disabled', !isEnabled);
|
||||
}
|
||||
|
||||
hideValues() {
|
||||
this.secretValues.updateDom(false);
|
||||
}
|
||||
|
||||
getAllData() {
|
||||
// Ignore the last empty row because we don't want to try persist
|
||||
// a blank variable and run into validation problems.
|
||||
|
|
|
@ -37,10 +37,11 @@ export default class Clusters {
|
|||
clusterStatusReason,
|
||||
helpPath,
|
||||
ingressHelpPath,
|
||||
ingressDnsHelpPath,
|
||||
} = document.querySelector('.js-edit-cluster-form').dataset;
|
||||
|
||||
this.store = new ClustersStore();
|
||||
this.store.setHelpPaths(helpPath, ingressHelpPath);
|
||||
this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath);
|
||||
this.store.setManagePrometheusPath(managePrometheusPath);
|
||||
this.store.updateStatus(clusterStatus);
|
||||
this.store.updateStatusReason(clusterStatusReason);
|
||||
|
@ -98,6 +99,7 @@ export default class Clusters {
|
|||
helpPath: this.state.helpPath,
|
||||
ingressHelpPath: this.state.ingressHelpPath,
|
||||
managePrometheusPath: this.state.managePrometheusPath,
|
||||
ingressDnsHelpPath: this.state.ingressDnsHelpPath,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -36,10 +36,6 @@
|
|||
type: String,
|
||||
required: false,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -148,7 +144,7 @@
|
|||
class="table-section section-wrap"
|
||||
role="gridcell"
|
||||
>
|
||||
<div v-html="description"></div>
|
||||
<slot name="description"></slot>
|
||||
</div>
|
||||
<div
|
||||
class="table-section table-button-footer section-align-top"
|
||||
|
|
|
@ -2,10 +2,16 @@
|
|||
import _ from 'underscore';
|
||||
import { s__, sprintf } from '../../locale';
|
||||
import applicationRow from './application_row.vue';
|
||||
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
|
||||
import {
|
||||
APPLICATION_INSTALLED,
|
||||
INGRESS,
|
||||
} from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
applicationRow,
|
||||
clipboardButton,
|
||||
},
|
||||
props: {
|
||||
applications: {
|
||||
|
@ -23,6 +29,11 @@
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
ingressDnsHelpPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
managePrometheusPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -43,19 +54,16 @@
|
|||
false,
|
||||
);
|
||||
},
|
||||
helmTillerDescription() {
|
||||
return _.escape(s__(
|
||||
`ClusterIntegration|Helm streamlines installing and managing Kubernetes applications.
|
||||
Tiller runs inside of your Kubernetes Cluster, and manages
|
||||
releases of your charts.`,
|
||||
));
|
||||
ingressId() {
|
||||
return INGRESS;
|
||||
},
|
||||
ingressInstalled() {
|
||||
return this.applications.ingress.status === APPLICATION_INSTALLED;
|
||||
},
|
||||
ingressExternalIp() {
|
||||
return this.applications.ingress.externalIp;
|
||||
},
|
||||
ingressDescription() {
|
||||
const descriptionParagraph = _.escape(s__(
|
||||
`ClusterIntegration|Ingress gives you a way to route requests to services based on the
|
||||
request host or path, centralizing a number of services into a single entrypoint.`,
|
||||
));
|
||||
|
||||
const extraCostParagraph = sprintf(
|
||||
_.escape(s__(
|
||||
`ClusterIntegration|%{boldNotice} This will add some extra resources
|
||||
|
@ -83,9 +91,6 @@
|
|||
);
|
||||
|
||||
return `
|
||||
<p>
|
||||
${descriptionParagraph}
|
||||
</p>
|
||||
<p>
|
||||
${extraCostParagraph}
|
||||
</p>
|
||||
|
@ -94,12 +99,6 @@
|
|||
</p>
|
||||
`;
|
||||
},
|
||||
gitlabRunnerDescription() {
|
||||
return _.escape(s__(
|
||||
`ClusterIntegration|GitLab Runner is the open source project that is used to run your jobs
|
||||
and send the results back to GitLab.`,
|
||||
));
|
||||
},
|
||||
prometheusDescription() {
|
||||
return sprintf(
|
||||
_.escape(s__(
|
||||
|
@ -118,7 +117,10 @@
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<section class="settings no-animate expanded">
|
||||
<section
|
||||
id="cluster-applications"
|
||||
class="settings no-animate expanded"
|
||||
>
|
||||
<div class="settings-header">
|
||||
<h4>
|
||||
{{ s__('ClusterIntegration|Applications') }}
|
||||
|
@ -136,33 +138,137 @@
|
|||
id="helm"
|
||||
:title="applications.helm.title"
|
||||
title-link="https://docs.helm.sh/"
|
||||
:description="helmTillerDescription"
|
||||
:status="applications.helm.status"
|
||||
:status-reason="applications.helm.statusReason"
|
||||
:request-status="applications.helm.requestStatus"
|
||||
:request-reason="applications.helm.requestReason"
|
||||
/>
|
||||
>
|
||||
<div slot="description">
|
||||
{{ s__(`ClusterIntegration|Helm streamlines installing
|
||||
and managing Kubernetes applications.
|
||||
Tiller runs inside of your Kubernetes Cluster,
|
||||
and manages releases of your charts.`) }}
|
||||
</div>
|
||||
</application-row>
|
||||
<application-row
|
||||
id="ingress"
|
||||
:id="ingressId"
|
||||
:title="applications.ingress.title"
|
||||
title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
|
||||
:description="ingressDescription"
|
||||
:status="applications.ingress.status"
|
||||
:status-reason="applications.ingress.statusReason"
|
||||
:request-status="applications.ingress.requestStatus"
|
||||
:request-reason="applications.ingress.requestReason"
|
||||
/>
|
||||
>
|
||||
<div slot="description">
|
||||
<p>
|
||||
{{ s__(`ClusterIntegration|Ingress gives you a way to route
|
||||
requests to services based on the request host or path,
|
||||
centralizing a number of services into a single entrypoint.`) }}
|
||||
</p>
|
||||
|
||||
<template v-if="ingressInstalled">
|
||||
<div class="form-group">
|
||||
<label for="ingress-ip-address">
|
||||
{{ s__('ClusterIntegration|Ingress IP Address') }}
|
||||
</label>
|
||||
<div
|
||||
v-if="ingressExternalIp"
|
||||
class="input-group"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="ingress-ip-address"
|
||||
class="form-control js-ip-address"
|
||||
:value="ingressExternalIp"
|
||||
readonly
|
||||
/>
|
||||
<span class="input-group-btn">
|
||||
<clipboard-button
|
||||
:text="ingressExternalIp"
|
||||
:title="s__('ClusterIntegration|Copy Ingress IP Address to clipboard')"
|
||||
class="js-clipboard-btn"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
v-else
|
||||
type="text"
|
||||
class="form-control js-ip-address"
|
||||
readonly
|
||||
value="?"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p
|
||||
v-if="!ingressExternalIp"
|
||||
class="settings-message js-no-ip-message"
|
||||
>
|
||||
{{ s__(`ClusterIntegration|The IP address is in
|
||||
the process of being assigned. Please check your Kubernetes
|
||||
cluster or Quotas on GKE if it takes a long time.`) }}
|
||||
|
||||
<a
|
||||
:href="ingressHelpPath"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{{ __('More information') }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{ s__(`ClusterIntegration|Point a wildcard DNS to this
|
||||
generated IP address in order to access
|
||||
your application after it has been deployed.`) }}
|
||||
<a
|
||||
:href="ingressDnsHelpPath"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{{ __('More information') }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
</template>
|
||||
<div
|
||||
v-else
|
||||
v-html="ingressDescription"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</application-row>
|
||||
<application-row
|
||||
id="prometheus"
|
||||
:title="applications.prometheus.title"
|
||||
title-link="https://prometheus.io/docs/introduction/overview/"
|
||||
:manage-link="managePrometheusPath"
|
||||
:description="prometheusDescription"
|
||||
:status="applications.prometheus.status"
|
||||
:status-reason="applications.prometheus.statusReason"
|
||||
:request-status="applications.prometheus.requestStatus"
|
||||
:request-reason="applications.prometheus.requestReason"
|
||||
/>
|
||||
>
|
||||
<div
|
||||
slot="description"
|
||||
v-html="prometheusDescription"
|
||||
>
|
||||
</div>
|
||||
</application-row>
|
||||
<application-row
|
||||
id="runner"
|
||||
:title="applications.runner.title"
|
||||
title-link="https://docs.gitlab.com/runner/"
|
||||
:status="applications.runner.status"
|
||||
:status-reason="applications.runner.statusReason"
|
||||
:request-status="applications.runner.requestStatus"
|
||||
:request-reason="applications.runner.requestReason"
|
||||
>
|
||||
<div slot="description">
|
||||
{{ s__(`ClusterIntegration|GitLab Runner connects to this
|
||||
project's repository and executes CI/CD jobs,
|
||||
pushing results back and deploying,
|
||||
applications to production.`) }}
|
||||
</div>
|
||||
</application-row>
|
||||
<!--
|
||||
NOTE: Don't forget to update `clusters.scss`
|
||||
min-height for this block and uncomment `application_spec` tests
|
||||
|
|
|
@ -10,3 +10,4 @@ export const APPLICATION_ERROR = 'errored';
|
|||
export const REQUEST_LOADING = 'request-loading';
|
||||
export const REQUEST_SUCCESS = 'request-success';
|
||||
export const REQUEST_FAILURE = 'request-failure';
|
||||
export const INGRESS = 'ingress';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { s__ } from '../../locale';
|
||||
import { INGRESS } from '../constants';
|
||||
|
||||
export default class ClusterStore {
|
||||
constructor() {
|
||||
|
@ -21,6 +22,7 @@ export default class ClusterStore {
|
|||
statusReason: null,
|
||||
requestStatus: null,
|
||||
requestReason: null,
|
||||
externalIp: null,
|
||||
},
|
||||
runner: {
|
||||
title: s__('ClusterIntegration|GitLab Runner'),
|
||||
|
@ -40,9 +42,10 @@ export default class ClusterStore {
|
|||
};
|
||||
}
|
||||
|
||||
setHelpPaths(helpPath, ingressHelpPath) {
|
||||
setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath) {
|
||||
this.state.helpPath = helpPath;
|
||||
this.state.ingressHelpPath = ingressHelpPath;
|
||||
this.state.ingressDnsHelpPath = ingressDnsHelpPath;
|
||||
}
|
||||
|
||||
setManagePrometheusPath(managePrometheusPath) {
|
||||
|
@ -64,6 +67,7 @@ export default class ClusterStore {
|
|||
updateStateFromServer(serverState = {}) {
|
||||
this.state.status = serverState.status;
|
||||
this.state.statusReason = serverState.status_reason;
|
||||
|
||||
serverState.applications.forEach((serverAppEntry) => {
|
||||
const {
|
||||
name: appId,
|
||||
|
@ -76,6 +80,10 @@ export default class ClusterStore {
|
|||
status,
|
||||
statusReason,
|
||||
};
|
||||
|
||||
if (appId === INGRESS) {
|
||||
this.state.applications.ingress.externalIp = serverAppEntry.external_ip;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */
|
||||
import 'vendor/jquery.waitforimages';
|
||||
|
||||
// Width where images must fits in, for 2-up this gets divided by 2
|
||||
const availWidth = 900;
|
||||
|
|
|
@ -15,7 +15,7 @@ const CommitPipelinesTable = Vue.extend(commitPipelinesTable);
|
|||
window.gl = window.gl || {};
|
||||
window.gl.CommitPipelinesTable = CommitPipelinesTable;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
export default () => {
|
||||
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
|
||||
|
||||
if (pipelineTableViewEl) {
|
||||
|
@ -43,4 +43,4 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
pipelineTableViewEl.appendChild(table.$el);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
mixins: [
|
||||
pipelinesMixin,
|
||||
],
|
||||
|
||||
props: {
|
||||
endpoint: {
|
||||
type: String,
|
||||
|
@ -21,10 +20,6 @@
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
emptyStateSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
errorStateSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -46,23 +41,14 @@
|
|||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* Empty state is only rendered if after the first request we receive no pipelines.
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
shouldRenderEmptyState() {
|
||||
return !this.state.pipelines.length &&
|
||||
!this.isLoading &&
|
||||
this.hasMadeRequest &&
|
||||
!this.hasError;
|
||||
},
|
||||
|
||||
shouldRenderTable() {
|
||||
return !this.isLoading &&
|
||||
this.state.pipelines.length > 0 &&
|
||||
!this.hasError;
|
||||
},
|
||||
shouldRenderErrorState() {
|
||||
return this.hasError && !this.isLoading;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.service = new PipelinesService(this.endpoint);
|
||||
|
@ -93,25 +79,22 @@
|
|||
<div class="content-list pipelines">
|
||||
|
||||
<loading-icon
|
||||
label="Loading pipelines"
|
||||
:label="s__('Pipelines|Loading Pipelines')"
|
||||
size="3"
|
||||
v-if="isLoading"
|
||||
class="prepend-top-20"
|
||||
/>
|
||||
|
||||
<empty-state
|
||||
v-if="shouldRenderEmptyState"
|
||||
:help-page-path="helpPagePath"
|
||||
:empty-state-svg-path="emptyStateSvgPath"
|
||||
/>
|
||||
|
||||
<error-state
|
||||
v-if="shouldRenderErrorState"
|
||||
:error-state-svg-path="errorStateSvgPath"
|
||||
<svg-blank-state
|
||||
v-else-if="shouldRenderErrorState"
|
||||
:svg-path="errorStateSvgPath"
|
||||
:message="s__(`Pipelines|There was an error fetching the pipelines.
|
||||
Try again in a few moments or contact your support team.`)"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="table-holder"
|
||||
v-if="shouldRenderTable"
|
||||
v-else-if="shouldRenderTable"
|
||||
>
|
||||
<pipelines-table-component
|
||||
:pipelines="state.pipelines"
|
||||
|
|
|
@ -1,52 +1,36 @@
|
|||
/* eslint-disable func-names, wrap-iife, consistent-return,
|
||||
no-return-assign, no-param-reassign, one-var-declaration-per-line, no-unused-vars,
|
||||
prefer-template, object-shorthand, prefer-arrow-callback */
|
||||
|
||||
import { pluralize } from './lib/utils/text_utility';
|
||||
import { localTimeAgo } from './lib/utils/datetime_utility';
|
||||
import Pager from './pager';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
|
||||
export default (function () {
|
||||
const CommitsList = {};
|
||||
export default class CommitsList {
|
||||
constructor(limit = 0) {
|
||||
this.timer = null;
|
||||
|
||||
CommitsList.timer = null;
|
||||
|
||||
CommitsList.init = function (limit) {
|
||||
this.$contentList = $('.content_list');
|
||||
|
||||
$('body').on('click', '.day-commits-table li.commit', function (e) {
|
||||
if (e.target.nodeName !== 'A') {
|
||||
location.href = $(this).attr('url');
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
Pager.init(parseInt(limit, 10), false, false, this.processCommits);
|
||||
Pager.init(parseInt(limit, 10), false, false, this.processCommits.bind(this));
|
||||
|
||||
this.content = $('#commits-list');
|
||||
this.searchField = $('#commits-search');
|
||||
this.lastSearch = this.searchField.val();
|
||||
return this.initSearch();
|
||||
};
|
||||
this.initSearch();
|
||||
}
|
||||
|
||||
CommitsList.initSearch = function () {
|
||||
initSearch() {
|
||||
this.timer = null;
|
||||
return this.searchField.keyup((function (_this) {
|
||||
return function () {
|
||||
clearTimeout(_this.timer);
|
||||
return _this.timer = setTimeout(_this.filterResults, 500);
|
||||
};
|
||||
})(this));
|
||||
};
|
||||
this.searchField.on('keyup', () => {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(this.filterResults.bind(this), 500);
|
||||
});
|
||||
}
|
||||
|
||||
CommitsList.filterResults = function () {
|
||||
filterResults() {
|
||||
const form = $('.commits-search-form');
|
||||
const search = CommitsList.searchField.val();
|
||||
if (search === CommitsList.lastSearch) return Promise.resolve();
|
||||
const commitsUrl = form.attr('action') + '?' + form.serialize();
|
||||
CommitsList.content.fadeTo('fast', 0.5);
|
||||
const search = this.searchField.val();
|
||||
if (search === this.lastSearch) return Promise.resolve();
|
||||
const commitsUrl = `${form.attr('action')}?${form.serialize()}`;
|
||||
this.content.fadeTo('fast', 0.5);
|
||||
const params = form.serializeArray().reduce((acc, obj) => Object.assign(acc, {
|
||||
[obj.name]: obj.value,
|
||||
}), {});
|
||||
|
@ -55,9 +39,9 @@ export default (function () {
|
|||
params,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
CommitsList.lastSearch = search;
|
||||
CommitsList.content.html(data.html);
|
||||
CommitsList.content.fadeTo('fast', 1.0);
|
||||
this.lastSearch = search;
|
||||
this.content.html(data.html);
|
||||
this.content.fadeTo('fast', 1.0);
|
||||
|
||||
// Change url so if user reload a page - search results are saved
|
||||
history.replaceState({
|
||||
|
@ -65,16 +49,16 @@ export default (function () {
|
|||
}, document.title, commitsUrl);
|
||||
})
|
||||
.catch(() => {
|
||||
CommitsList.content.fadeTo('fast', 1.0);
|
||||
CommitsList.lastSearch = null;
|
||||
this.content.fadeTo('fast', 1.0);
|
||||
this.lastSearch = null;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Prepare loaded data.
|
||||
CommitsList.processCommits = (data) => {
|
||||
processCommits(data) {
|
||||
let processedData = data;
|
||||
const $processedData = $(processedData);
|
||||
const $commitsHeadersLast = CommitsList.$contentList.find('li.js-commit-header').last();
|
||||
const $commitsHeadersLast = this.$contentList.find('li.js-commit-header').last();
|
||||
const lastShownDay = $commitsHeadersLast.data('day');
|
||||
const $loadedCommitsHeadersFirst = $processedData.filter('li.js-commit-header').first();
|
||||
const loadedShownDayFirst = $loadedCommitsHeadersFirst.data('day');
|
||||
|
@ -97,7 +81,5 @@ export default (function () {
|
|||
localTimeAgo($processedData.find('.js-timeago'));
|
||||
|
||||
return processedData;
|
||||
};
|
||||
|
||||
return CommitsList;
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
|
4
app/assets/javascripts/commons/bootstrap.js
vendored
4
app/assets/javascripts/commons/bootstrap.js
vendored
|
@ -13,6 +13,6 @@ import 'bootstrap-sass/assets/javascripts/bootstrap/popover';
|
|||
|
||||
// custom jQuery functions
|
||||
$.fn.extend({
|
||||
disable() { return $(this).attr('disabled', 'disabled').addClass('disabled'); },
|
||||
enable() { return $(this).removeAttr('disabled').removeClass('disabled'); },
|
||||
disable() { return $(this).prop('disabled', true).addClass('disabled'); },
|
||||
enable() { return $(this).prop('disabled', false).removeClass('disabled'); },
|
||||
});
|
||||
|
|
2
app/assets/javascripts/commons/jquery.js
vendored
2
app/assets/javascripts/commons/jquery.js
vendored
|
@ -6,5 +6,5 @@ import 'vendor/jquery.endless-scroll';
|
|||
import 'vendor/jquery.caret';
|
||||
import 'vendor/jquery.atwho';
|
||||
import 'vendor/jquery.scrollTo';
|
||||
import 'vendor/jquery.waitforimages';
|
||||
import 'jquery.waitforimages';
|
||||
import 'select2/select2';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import '../vue_shared/vue_resource_interceptor';
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Vue.config.productionTip = false;
|
||||
|
|
|
@ -13,7 +13,7 @@ export default class Compare {
|
|||
$dropdown = $(dropdown);
|
||||
return $dropdown.glDropdown({
|
||||
selectable: true,
|
||||
fieldName: $dropdown.data('field-name'),
|
||||
fieldName: $dropdown.data('fieldName'),
|
||||
filterable: true,
|
||||
id: function(obj, $el) {
|
||||
return $el.data('id');
|
||||
|
|
|
@ -9,7 +9,7 @@ export default function initCompareAutocomplete() {
|
|||
$dropdown = $(this);
|
||||
selected = $dropdown.data('selected');
|
||||
const $dropdownContainer = $dropdown.closest('.dropdown');
|
||||
const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer);
|
||||
const $fieldInput = $(`input[name="${$dropdown.data('fieldName')}"]`, $dropdownContainer);
|
||||
const $filterInput = $('input[type="search"]', $dropdownContainer);
|
||||
$dropdown.glDropdown({
|
||||
data: function(term, callback) {
|
||||
|
@ -25,7 +25,7 @@ export default function initCompareAutocomplete() {
|
|||
selectable: true,
|
||||
filterable: true,
|
||||
filterRemote: true,
|
||||
fieldName: $dropdown.data('field-name'),
|
||||
fieldName: $dropdown.data('fieldName'),
|
||||
filterInput: 'input[type="search"]',
|
||||
renderRow: function(ref) {
|
||||
var link;
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
>
|
||||
<div class="item-details">
|
||||
<!-- FIXME: Pass an alt attribute here for accessibility -->
|
||||
<user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
|
||||
<user-avatar-image :img-src="mergeRequest.author.avatarUrl" />
|
||||
<h5 class="item-title merge-merquest-title">
|
||||
<a :href="mergeRequest.url">
|
||||
{{ mergeRequest.title }}
|
||||
|
|
|
@ -14,10 +14,10 @@ import CycleAnalyticsStore from './cycle_analytics_store';
|
|||
|
||||
Vue.use(Translate);
|
||||
|
||||
$(() => {
|
||||
export default () => {
|
||||
const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
|
||||
|
||||
gl.cycleAnalyticsApp = new Vue({
|
||||
new Vue({ // eslint-disable-line no-new
|
||||
el: '#cycle-analytics',
|
||||
name: 'CycleAnalytics',
|
||||
components: {
|
||||
|
@ -132,4 +132,4 @@ $(() => {
|
|||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Vue from 'vue';
|
||||
import deployKeysApp from './components/app.vue';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => new Vue({
|
||||
export default () => new Vue({
|
||||
el: document.getElementById('js-deploy-keys'),
|
||||
components: {
|
||||
deployKeysApp,
|
||||
|
@ -18,4 +18,4 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
|
|||
},
|
||||
});
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
import flash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import { getLocationHash } from './lib/utils/url_utility';
|
||||
import FilesCommentButton from './files_comment_button';
|
||||
import SingleFileDiff from './single_file_diff';
|
||||
|
@ -65,11 +68,13 @@ export default class Diff {
|
|||
}
|
||||
|
||||
const file = $target.parents('.diff-file');
|
||||
const link = file.data('blob-diff-path');
|
||||
const link = file.data('blobDiffPath');
|
||||
const view = file.data('view');
|
||||
|
||||
const params = { since, to, bottom, offset, unfold, view };
|
||||
$.get(link, params, response => $target.parent().replaceWith(response));
|
||||
axios.get(link, { params })
|
||||
.then(({ data }) => $target.parent().replaceWith(data))
|
||||
.catch(() => flash(__('An error occurred while loading diff')));
|
||||
}
|
||||
|
||||
openAnchoredDiff(cb) {
|
||||
|
@ -116,7 +121,7 @@ export default class Diff {
|
|||
}
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
diffViewType() {
|
||||
return $('.inline-parallel-buttons a.active').data('view-type');
|
||||
return $('.inline-parallel-buttons a.active').data('viewType');
|
||||
}
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
lineNumbers(line) {
|
||||
|
|
|
@ -197,7 +197,7 @@ const JumpToDiscussion = Vue.extend({
|
|||
}
|
||||
|
||||
$.scrollTo($target, {
|
||||
offset: 0
|
||||
offset: -150
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
@ -87,6 +87,7 @@ const ResolveBtn = Vue.extend({
|
|||
CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by);
|
||||
this.discussion.updateHeadline(data);
|
||||
gl.mrWidget.checkStatus();
|
||||
document.dispatchEvent(new CustomEvent('refreshVueNotes'));
|
||||
|
||||
this.updateTooltip();
|
||||
})
|
||||
|
|
|
@ -14,8 +14,9 @@ import './components/resolve_count';
|
|||
import './components/resolve_discussion_btn';
|
||||
import './components/diff_note_avatars';
|
||||
import './components/new_issue_for_discussion';
|
||||
import { hasVueMRDiscussionsCookie } from '../lib/utils/common_utils';
|
||||
|
||||
$(() => {
|
||||
export default () => {
|
||||
const projectPathHolder = document.querySelector('.merge-request') || document.querySelector('.commit-box');
|
||||
const projectPath = projectPathHolder.dataset.projectPath;
|
||||
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn, new-issue-for-discussion-btn';
|
||||
|
@ -67,12 +68,14 @@ $(() => {
|
|||
|
||||
gl.diffNotesCompileComponents();
|
||||
|
||||
new Vue({
|
||||
el: '#resolve-count-app',
|
||||
components: {
|
||||
'resolve-count': ResolveCount
|
||||
}
|
||||
});
|
||||
if (!hasVueMRDiscussionsCookie()) {
|
||||
new Vue({
|
||||
el: '#resolve-count-app',
|
||||
components: {
|
||||
'resolve-count': ResolveCount
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
$(window).trigger('resize.nav');
|
||||
});
|
||||
};
|
||||
|
|
|
@ -8,8 +8,8 @@ window.gl = window.gl || {};
|
|||
|
||||
class ResolveServiceClass {
|
||||
constructor(root) {
|
||||
this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`);
|
||||
this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`);
|
||||
this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve?html=true`);
|
||||
this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve?html=true`);
|
||||
}
|
||||
|
||||
resolve(noteId) {
|
||||
|
@ -45,6 +45,7 @@ class ResolveServiceClass {
|
|||
|
||||
if (gl.mrWidget) gl.mrWidget.checkStatus();
|
||||
discussion.updateHeadline(data);
|
||||
document.dispatchEvent(new CustomEvent('refreshVueNotes'));
|
||||
})
|
||||
.catch(() => new Flash('An error occurred when trying to resolve a discussion. Please try again.'));
|
||||
}
|
||||
|
|
|
@ -1,648 +1,85 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
|
||||
import MergeRequest from './merge_request';
|
||||
import Flash from './flash';
|
||||
import GfmAutoComplete from './gfm_auto_complete';
|
||||
import ZenMode from './zen_mode';
|
||||
import initNotes from './init_notes';
|
||||
import initIssuableSidebar from './init_issuable_sidebar';
|
||||
import { convertPermissionToBoolean } from './lib/utils/common_utils';
|
||||
import GlFieldErrors from './gl_field_errors';
|
||||
import Shortcuts from './shortcuts';
|
||||
import ShortcutsIssuable from './shortcuts_issuable';
|
||||
import Diff from './diff';
|
||||
import SearchAutocomplete from './search_autocomplete';
|
||||
|
||||
var Dispatcher;
|
||||
|
||||
(function() {
|
||||
Dispatcher = (function() {
|
||||
function Dispatcher() {
|
||||
this.initSearch();
|
||||
this.initFieldErrors();
|
||||
this.initPageScripts();
|
||||
}
|
||||
|
||||
Dispatcher.prototype.initPageScripts = function() {
|
||||
var path, shortcut_handler;
|
||||
const page = $('body').attr('data-page');
|
||||
if (!page) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fail = () => Flash('Error loading dynamic module');
|
||||
const callDefault = m => m.default();
|
||||
|
||||
path = page.split(':');
|
||||
shortcut_handler = null;
|
||||
|
||||
$('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
|
||||
const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
|
||||
const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete);
|
||||
gfm.setup($(el), {
|
||||
emojis: true,
|
||||
members: enableGFM,
|
||||
issues: enableGFM,
|
||||
milestones: enableGFM,
|
||||
mergeRequests: enableGFM,
|
||||
labels: enableGFM,
|
||||
});
|
||||
});
|
||||
|
||||
switch (page) {
|
||||
case 'projects:environments:metrics':
|
||||
import('./pages/projects/environments/metrics')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:merge_requests:index':
|
||||
case 'projects:issues:index':
|
||||
case 'projects:issues:show':
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:milestones:index':
|
||||
import('./pages/projects/milestones/index')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:milestones:show':
|
||||
import('./pages/projects/milestones/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'groups:milestones:show':
|
||||
import('./pages/groups/milestones/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'dashboard:milestones:show':
|
||||
import('./pages/dashboard/milestones/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'dashboard:issues':
|
||||
import('./pages/dashboard/issues')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'dashboard:merge_requests':
|
||||
import('./pages/dashboard/merge_requests')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'groups:issues':
|
||||
import('./pages/groups/issues')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'groups:merge_requests':
|
||||
import('./pages/groups/merge_requests')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'dashboard:todos:index':
|
||||
import('./pages/dashboard/todos/index')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'admin:jobs:index':
|
||||
import('./pages/admin/jobs/index')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'admin:projects:index':
|
||||
import('./pages/admin/projects/index/index')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'admin:users:index':
|
||||
import('./pages/admin/users/shared')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'admin:users:show':
|
||||
import('./pages/admin/users/shared')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'dashboard:projects:index':
|
||||
case 'dashboard:projects:starred':
|
||||
import('./pages/dashboard/projects')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'explore:projects:index':
|
||||
case 'explore:projects:trending':
|
||||
case 'explore:projects:starred':
|
||||
import('./pages/explore/projects')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'explore:groups:index':
|
||||
import('./pages/explore/groups')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:milestones:new':
|
||||
case 'projects:milestones:create':
|
||||
import('./pages/projects/milestones/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:milestones:edit':
|
||||
case 'projects:milestones:update':
|
||||
import('./pages/projects/milestones/edit')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'groups:milestones:new':
|
||||
case 'groups:milestones:create':
|
||||
import('./pages/groups/milestones/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'groups:milestones:edit':
|
||||
case 'groups:milestones:update':
|
||||
import('./pages/groups/milestones/edit')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:compare:show':
|
||||
import('./pages/projects/compare/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:branches:new':
|
||||
import('./pages/projects/branches/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:branches:create':
|
||||
import('./pages/projects/branches/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:branches:index':
|
||||
import('./pages/projects/branches/index')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:issues:new':
|
||||
import('./pages/projects/issues/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:issues:edit':
|
||||
import('./pages/projects/issues/edit')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:merge_requests:creations:new':
|
||||
import('./pages/projects/merge_requests/creations/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
case 'projects:merge_requests:creations:diffs':
|
||||
import('./pages/projects/merge_requests/creations/diffs')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:merge_requests:edit':
|
||||
import('./pages/projects/merge_requests/edit')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:tags:new':
|
||||
import('./pages/projects/tags/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:snippets:show':
|
||||
import('./pages/projects/snippets/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:snippets:new':
|
||||
case 'projects:snippets:create':
|
||||
import('./pages/projects/snippets/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:snippets:edit':
|
||||
case 'projects:snippets:update':
|
||||
import('./pages/projects/snippets/edit')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'snippets:new':
|
||||
import('./pages/snippets/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'snippets:edit':
|
||||
import('./pages/snippets/edit')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'snippets:create':
|
||||
import('./pages/snippets/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'snippets:update':
|
||||
import('./pages/snippets/edit')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:releases:edit':
|
||||
import('./pages/projects/releases/edit')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:merge_requests:show':
|
||||
new Diff();
|
||||
new ZenMode();
|
||||
|
||||
initIssuableSidebar();
|
||||
initNotes();
|
||||
|
||||
const mrShowNode = document.querySelector('.merge-request');
|
||||
window.mergeRequest = new MergeRequest({
|
||||
action: mrShowNode.dataset.mrAction,
|
||||
});
|
||||
shortcut_handler = new ShortcutsIssuable(true);
|
||||
break;
|
||||
case 'dashboard:activity':
|
||||
import('./pages/dashboard/activity')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:commit:show':
|
||||
import('./pages/projects/commit/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:commit:pipelines':
|
||||
import('./pages/projects/commit/pipelines')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:activity':
|
||||
import('./pages/projects/activity')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:commits:show':
|
||||
import('./pages/projects/commits/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:show':
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:edit':
|
||||
import('./pages/projects/edit')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:imports:show':
|
||||
import('./pages/projects/imports/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:pipelines:new':
|
||||
case 'projects:pipelines:create':
|
||||
import('./pages/projects/pipelines/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:pipelines:builds':
|
||||
case 'projects:pipelines:failures':
|
||||
case 'projects:pipelines:show':
|
||||
import('./pages/projects/pipelines/builds')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'groups:activity':
|
||||
import('./pages/groups/activity')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'groups:show':
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'groups:group_members:index':
|
||||
import('./pages/groups/group_members/index')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:project_members:index':
|
||||
import('./pages/projects/project_members')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'groups:create':
|
||||
case 'groups:new':
|
||||
import('./pages/groups/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'groups:edit':
|
||||
import('./pages/groups/edit')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'admin:groups:create':
|
||||
case 'admin:groups:new':
|
||||
import('./pages/admin/groups/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'admin:groups:edit':
|
||||
import('./pages/admin/groups/edit')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:tree:show':
|
||||
import('./pages/projects/tree/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:find_file:show':
|
||||
import('./pages/projects/find_file/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:blob:show':
|
||||
import('./pages/projects/blob/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:blame:show':
|
||||
import('./pages/projects/blame/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'groups:labels:new':
|
||||
import('./pages/groups/labels/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'groups:labels:edit':
|
||||
import('./pages/groups/labels/edit')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:labels:new':
|
||||
import('./pages/projects/labels/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:labels:edit':
|
||||
import('./pages/projects/labels/edit')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'groups:labels:index':
|
||||
import('./pages/groups/labels/index')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:labels:index':
|
||||
import('./pages/projects/labels/index')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:network:show':
|
||||
// Ensure we don't create a particular shortcut handler here. This is
|
||||
// already created, where the network graph is created.
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:forks:new':
|
||||
import('./pages/projects/forks/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:artifacts:browse':
|
||||
import('./pages/projects/artifacts/browse')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:artifacts:file':
|
||||
import('./pages/projects/artifacts/file')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'help:index':
|
||||
import('./pages/help')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'search:show':
|
||||
import('./pages/search/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:settings:repository:show':
|
||||
import('./pages/projects/settings/repository/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:settings:ci_cd:show':
|
||||
import('./pages/projects/settings/ci_cd/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'groups:settings:ci_cd:show':
|
||||
import('./pages/groups/settings/ci_cd/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'ci:lints:create':
|
||||
case 'ci:lints:show':
|
||||
import('./pages/ci/lints')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'users:show':
|
||||
import('./pages/users/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'admin:conversational_development_index:show':
|
||||
import('./pages/admin/conversational_development_index/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'snippets:show':
|
||||
import('./pages/snippets/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'import:fogbugz:new_user_map':
|
||||
import('./pages/import/fogbugz/new_user_map')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'profiles:personal_access_tokens:index':
|
||||
import('./pages/profiles/personal_access_tokens')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'admin:impersonation_tokens:index':
|
||||
import('./pages/admin/impersonation_tokens')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:clusters:show':
|
||||
case 'projects:clusters:update':
|
||||
case 'projects:clusters:destroy':
|
||||
import('./pages/projects/clusters/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects:clusters:index':
|
||||
import('./pages/projects/clusters/index')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'dashboard:groups:index':
|
||||
import('./pages/dashboard/groups/index')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
}
|
||||
switch (path[0]) {
|
||||
case 'sessions':
|
||||
import('./pages/sessions')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'omniauth_callbacks':
|
||||
import('./pages/omniauth_callbacks')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'admin':
|
||||
import('./pages/admin')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
switch (path[1]) {
|
||||
case 'broadcast_messages':
|
||||
import('./pages/admin/broadcast_messages')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'cohorts':
|
||||
import('./pages/admin/cohorts')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'groups':
|
||||
switch (path[2]) {
|
||||
case 'show':
|
||||
import('./pages/admin/groups/show')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'projects':
|
||||
import('./pages/admin/projects')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'labels':
|
||||
switch (path[2]) {
|
||||
case 'new':
|
||||
import('./pages/admin/labels/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'edit':
|
||||
import('./pages/admin/labels/edit')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
}
|
||||
case 'abuse_reports':
|
||||
import('./pages/admin/abuse_reports')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'profiles':
|
||||
import('./pages/profiles/index')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'projects':
|
||||
import('./pages/projects')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
switch (path[1]) {
|
||||
case 'compare':
|
||||
import('./pages/projects/compare')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'create':
|
||||
case 'new':
|
||||
import('./pages/projects/new')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
break;
|
||||
case 'wikis':
|
||||
import('./pages/projects/wikis')
|
||||
.then(callDefault)
|
||||
.catch(fail);
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// If we haven't installed a custom shortcut handler, install the default one
|
||||
if (!shortcut_handler) {
|
||||
new Shortcuts();
|
||||
}
|
||||
|
||||
if (document.querySelector('#peek')) {
|
||||
import('./performance_bar')
|
||||
.then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap
|
||||
.catch(fail);
|
||||
}
|
||||
};
|
||||
|
||||
Dispatcher.prototype.initSearch = function() {
|
||||
// Only when search form is present
|
||||
if ($('.search').length) {
|
||||
return new SearchAutocomplete();
|
||||
}
|
||||
};
|
||||
|
||||
Dispatcher.prototype.initFieldErrors = function() {
|
||||
$('.gl-show-field-errors').each((i, form) => {
|
||||
new GlFieldErrors(form);
|
||||
});
|
||||
};
|
||||
|
||||
return Dispatcher;
|
||||
})();
|
||||
})();
|
||||
|
||||
export default function initDispatcher() {
|
||||
return new Dispatcher();
|
||||
function initSearch() {
|
||||
// Only when search form is present
|
||||
if ($('.search').length) {
|
||||
return new SearchAutocomplete();
|
||||
}
|
||||
}
|
||||
|
||||
function initFieldErrors() {
|
||||
$('.gl-show-field-errors').each((i, form) => {
|
||||
new GlFieldErrors(form);
|
||||
});
|
||||
}
|
||||
|
||||
function initPageShortcuts(page) {
|
||||
const pagesWithCustomShortcuts = [
|
||||
'projects:activity',
|
||||
'projects:artifacts:browse',
|
||||
'projects:artifacts:file',
|
||||
'projects:blame:show',
|
||||
'projects:blob:show',
|
||||
'projects:commit:show',
|
||||
'projects:commits:show',
|
||||
'projects:find_file:show',
|
||||
'projects:issues:edit',
|
||||
'projects:issues:index',
|
||||
'projects:issues:new',
|
||||
'projects:issues:show',
|
||||
'projects:merge_requests:creations:diffs',
|
||||
'projects:merge_requests:creations:new',
|
||||
'projects:merge_requests:edit',
|
||||
'projects:merge_requests:index',
|
||||
'projects:merge_requests:show',
|
||||
'projects:network:show',
|
||||
'projects:show',
|
||||
'projects:tree:show',
|
||||
'groups:show',
|
||||
];
|
||||
|
||||
if (pagesWithCustomShortcuts.indexOf(page) === -1) {
|
||||
new Shortcuts();
|
||||
}
|
||||
}
|
||||
|
||||
function initGFMInput() {
|
||||
$('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
|
||||
const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
|
||||
const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete);
|
||||
gfm.setup($(el), {
|
||||
emojis: true,
|
||||
members: enableGFM,
|
||||
issues: enableGFM,
|
||||
milestones: enableGFM,
|
||||
mergeRequests: enableGFM,
|
||||
labels: enableGFM,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initPerformanceBar() {
|
||||
if (document.querySelector('#peek')) {
|
||||
import('./performance_bar')
|
||||
.then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap
|
||||
.catch(() => Flash('Error loading performance bar module'));
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
initSearch();
|
||||
initFieldErrors();
|
||||
|
||||
const page = $('body').attr('data-page');
|
||||
if (page) {
|
||||
initPageShortcuts(page);
|
||||
initGFMInput();
|
||||
initPerformanceBar();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -4,10 +4,7 @@ function addMousetrapClick(el, key) {
|
|||
el.addEventListener('click', () => Mousetrap.trigger(key));
|
||||
}
|
||||
|
||||
function domContentLoaded() {
|
||||
export default () => {
|
||||
addMousetrapClick(document.querySelector('.js-trigger-shortcut'), '?');
|
||||
addMousetrapClick(document.querySelector('.js-trigger-search-bar'), 's');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', domContentLoaded);
|
||||
|
||||
};
|
||||
|
|
|
@ -17,9 +17,9 @@ class DueDateSelect {
|
|||
this.$value = $block.find('.value');
|
||||
this.$valueContent = $block.find('.value-content');
|
||||
this.$sidebarValue = $('.js-due-date-sidebar-value', $block);
|
||||
this.fieldName = $dropdown.data('field-name');
|
||||
this.abilityName = $dropdown.data('ability-name');
|
||||
this.issueUpdateURL = $dropdown.data('issue-update');
|
||||
this.fieldName = $dropdown.data('fieldName');
|
||||
this.abilityName = $dropdown.data('abilityName');
|
||||
this.issueUpdateURL = $dropdown.data('issueUpdate');
|
||||
|
||||
this.rawSelectedDate = null;
|
||||
this.displayedDate = null;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<script>
|
||||
import Timeago from 'timeago.js';
|
||||
import _ from 'underscore';
|
||||
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import { humanize } from '../../lib/utils/text_utility';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import { humanize } from '~/lib/utils/text_utility';
|
||||
import ActionsComponent from './environment_actions.vue';
|
||||
import ExternalUrlComponent from './environment_external_url.vue';
|
||||
import StopComponent from './environment_stop.vue';
|
||||
|
@ -21,14 +22,18 @@
|
|||
|
||||
export default {
|
||||
components: {
|
||||
userAvatarLink,
|
||||
'commit-component': CommitComponent,
|
||||
'actions-component': ActionsComponent,
|
||||
'external-url-component': ExternalUrlComponent,
|
||||
'stop-component': StopComponent,
|
||||
'rollback-component': RollbackComponent,
|
||||
'terminal-button-component': TerminalButtonComponent,
|
||||
'monitoring-button-component': MonitoringButtonComponent,
|
||||
UserAvatarLink,
|
||||
CommitComponent,
|
||||
ActionsComponent,
|
||||
ExternalUrlComponent,
|
||||
StopComponent,
|
||||
RollbackComponent,
|
||||
TerminalButtonComponent,
|
||||
MonitoringButtonComponent,
|
||||
},
|
||||
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
|
||||
props: {
|
||||
|
@ -443,7 +448,11 @@
|
|||
v-if="!model.isFolder"
|
||||
class="environment-name flex-truncate-parent table-mobile-content"
|
||||
:href="environmentPath">
|
||||
<span class="flex-truncate-child">{{ model.name }}</span>
|
||||
<span
|
||||
class="flex-truncate-child"
|
||||
v-tooltip
|
||||
:title="model.name"
|
||||
>{{ model.name }}</span>
|
||||
</a>
|
||||
<span
|
||||
v-else
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
/**
|
||||
* Render environments table.
|
||||
*/
|
||||
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
|
||||
import environmentItem from './environment_item.vue';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
|
@ -5,7 +5,7 @@ import Translate from '../../vue_shared/translate';
|
|||
|
||||
Vue.use(Translate);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => new Vue({
|
||||
export default () => new Vue({
|
||||
el: '#environments-folder-list-view',
|
||||
components: {
|
||||
environmentsFolderApp,
|
||||
|
@ -32,4 +32,4 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
|
|||
},
|
||||
});
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@ import Translate from '../vue_shared/translate';
|
|||
|
||||
Vue.use(Translate);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => new Vue({
|
||||
export default () => new Vue({
|
||||
el: '#environments-list-view',
|
||||
components: {
|
||||
environmentsComponent,
|
||||
|
@ -36,4 +36,4 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
|
|||
},
|
||||
});
|
||||
},
|
||||
}));
|
||||
});
|
|
@ -25,7 +25,7 @@ export default {
|
|||
|
||||
if (!this.userCanCreateNote) {
|
||||
// data-can-create-note is an empty string when true, otherwise undefined
|
||||
this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('can-create-note') === '';
|
||||
this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('canCreateNote') === '';
|
||||
}
|
||||
|
||||
this.isParallelView = Cookies.get('diff_view') === 'parallel';
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
import eventHub from '../event_hub';
|
||||
|
||||
export default {
|
||||
name: 'RecentSearchesDropdownContent',
|
||||
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
isLocalStorageAvailable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
allowedKeys: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
processedItems() {
|
||||
return this.items.map((item) => {
|
||||
const { tokens, searchToken }
|
||||
= gl.FilteredSearchTokenizer.processTokens(item, this.allowedKeys);
|
||||
|
||||
const resultantTokens = tokens.map(token => ({
|
||||
prefix: `${token.key}:`,
|
||||
suffix: `${token.symbol}${token.value}`,
|
||||
}));
|
||||
|
||||
return {
|
||||
text: item,
|
||||
tokens: resultantTokens,
|
||||
searchToken,
|
||||
};
|
||||
});
|
||||
},
|
||||
hasItems() {
|
||||
return this.items.length > 0;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onItemActivated(text) {
|
||||
eventHub.$emit('recentSearchesItemSelected', text);
|
||||
},
|
||||
onRequestClearRecentSearches(e) {
|
||||
// Stop the dropdown from closing
|
||||
e.stopPropagation();
|
||||
|
||||
eventHub.$emit('requestClearRecentSearches');
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<div>
|
||||
<div
|
||||
v-if="!isLocalStorageAvailable"
|
||||
class="dropdown-info-note">
|
||||
This feature requires local storage to be enabled
|
||||
</div>
|
||||
<ul v-else-if="hasItems">
|
||||
<li
|
||||
v-for="(item, index) in processedItems"
|
||||
:key="index">
|
||||
<button
|
||||
type="button"
|
||||
class="filtered-search-history-dropdown-item"
|
||||
@click="onItemActivated(item.text)">
|
||||
<span>
|
||||
<span
|
||||
v-for="(token, tokenIndex) in item.tokens"
|
||||
class="filtered-search-history-dropdown-token">
|
||||
<span class="name">{{ token.prefix }}</span><span class="value">{{ token.suffix }}</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="filtered-search-history-dropdown-search-token">
|
||||
{{ item.searchToken }}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
class="filtered-search-history-clear-button"
|
||||
@click="onRequestClearRecentSearches($event)">
|
||||
Clear recent searches
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
v-else
|
||||
class="dropdown-info-note">
|
||||
You don't have any recent searches
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
|
@ -0,0 +1,104 @@
|
|||
<script>
|
||||
import eventHub from '../event_hub';
|
||||
import FilteredSearchTokenizer from '../filtered_search_tokenizer';
|
||||
|
||||
export default {
|
||||
name: 'RecentSearchesDropdownContent',
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
isLocalStorageAvailable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
allowedKeys: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
processedItems() {
|
||||
return this.items.map((item) => {
|
||||
const { tokens, searchToken }
|
||||
= FilteredSearchTokenizer.processTokens(item, this.allowedKeys);
|
||||
|
||||
const resultantTokens = tokens.map(token => ({
|
||||
prefix: `${token.key}:`,
|
||||
suffix: `${token.symbol}${token.value}`,
|
||||
}));
|
||||
|
||||
return {
|
||||
text: item,
|
||||
tokens: resultantTokens,
|
||||
searchToken,
|
||||
};
|
||||
});
|
||||
},
|
||||
hasItems() {
|
||||
return this.items.length > 0;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onItemActivated(text) {
|
||||
eventHub.$emit('recentSearchesItemSelected', text);
|
||||
},
|
||||
onRequestClearRecentSearches(e) {
|
||||
// Stop the dropdown from closing
|
||||
e.stopPropagation();
|
||||
|
||||
eventHub.$emit('requestClearRecentSearches');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-if="!isLocalStorageAvailable"
|
||||
class="dropdown-info-note">
|
||||
This feature requires local storage to be enabled
|
||||
</div>
|
||||
<ul v-else-if="hasItems">
|
||||
<li
|
||||
v-for="(item, index) in processedItems"
|
||||
:key="`processed-items-${index}`"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="filtered-search-history-dropdown-item"
|
||||
@click="onItemActivated(item.text)">
|
||||
<span>
|
||||
<span
|
||||
class="filtered-search-history-dropdown-token"
|
||||
v-for="(token, index) in item.tokens"
|
||||
:key="`dropdown-token-${index}`"
|
||||
>
|
||||
<span class="name">{{ token.prefix }}</span>
|
||||
<span class="value">{{ token.suffix }}</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="filtered-search-history-dropdown-search-token">
|
||||
{{ item.searchToken }}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
class="filtered-search-history-clear-button"
|
||||
@click="onRequestClearRecentSearches($event)">
|
||||
Clear recent searches
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
v-else
|
||||
class="dropdown-info-note">
|
||||
You don't have any recent searches
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,9 +1,10 @@
|
|||
import Flash from '../flash';
|
||||
import Ajax from '../droplab/plugins/ajax';
|
||||
import Filter from '../droplab/plugins/filter';
|
||||
import './filtered_search_dropdown';
|
||||
import FilteredSearchDropdown from './filtered_search_dropdown';
|
||||
import DropdownUtils from './dropdown_utils';
|
||||
|
||||
class DropdownEmoji extends gl.FilteredSearchDropdown {
|
||||
export default class DropdownEmoji extends FilteredSearchDropdown {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this.config = {
|
||||
|
@ -49,7 +50,7 @@ class DropdownEmoji extends gl.FilteredSearchDropdown {
|
|||
itemClicked(e) {
|
||||
super.itemClicked(e, (selected) => {
|
||||
const name = selected.querySelector('.js-data-value').innerText.trim();
|
||||
return gl.DropdownUtils.getEscapedText(name);
|
||||
return DropdownUtils.getEscapedText(name);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -76,6 +77,3 @@ class DropdownEmoji extends gl.FilteredSearchDropdown {
|
|||
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.DropdownEmoji = DropdownEmoji;
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import Filter from '~/droplab/plugins/filter';
|
||||
import './filtered_search_dropdown';
|
||||
import FilteredSearchDropdown from './filtered_search_dropdown';
|
||||
import DropdownUtils from './dropdown_utils';
|
||||
import FilteredSearchDropdownManager from './filtered_search_dropdown_manager';
|
||||
import FilteredSearchVisualTokens from './filtered_search_visual_tokens';
|
||||
|
||||
class DropdownHint extends gl.FilteredSearchDropdown {
|
||||
export default class DropdownHint extends FilteredSearchDropdown {
|
||||
constructor(options = {}) {
|
||||
const { input, tokenKeys } = options;
|
||||
super(options);
|
||||
this.config = {
|
||||
Filter: {
|
||||
template: 'hint',
|
||||
filterFunction: gl.DropdownUtils.filterHint.bind(null, {
|
||||
filterFunction: DropdownUtils.filterHint.bind(null, {
|
||||
input,
|
||||
allowedKeys: tokenKeys.getKeys(),
|
||||
}),
|
||||
|
@ -45,10 +48,10 @@ class DropdownHint extends gl.FilteredSearchDropdown {
|
|||
});
|
||||
|
||||
if (searchTerms.length > 0) {
|
||||
gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
|
||||
FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
|
||||
}
|
||||
|
||||
gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container);
|
||||
FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container);
|
||||
}
|
||||
this.dismissDropdown();
|
||||
this.dispatchInputEvent();
|
||||
|
@ -73,6 +76,3 @@ class DropdownHint extends gl.FilteredSearchDropdown {
|
|||
this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init();
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.DropdownHint = DropdownHint;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import Flash from '../flash';
|
||||
import Ajax from '../droplab/plugins/ajax';
|
||||
import Filter from '../droplab/plugins/filter';
|
||||
import './filtered_search_dropdown';
|
||||
import FilteredSearchDropdown from './filtered_search_dropdown';
|
||||
import DropdownUtils from './dropdown_utils';
|
||||
|
||||
class DropdownNonUser extends gl.FilteredSearchDropdown {
|
||||
export default class DropdownNonUser extends FilteredSearchDropdown {
|
||||
constructor(options = {}) {
|
||||
const { input, endpoint, symbol, preprocessing } = options;
|
||||
super(options);
|
||||
|
@ -21,7 +22,7 @@ class DropdownNonUser extends gl.FilteredSearchDropdown {
|
|||
},
|
||||
},
|
||||
Filter: {
|
||||
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
|
||||
filterFunction: DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
|
||||
template: 'title',
|
||||
},
|
||||
};
|
||||
|
@ -30,7 +31,7 @@ class DropdownNonUser extends gl.FilteredSearchDropdown {
|
|||
itemClicked(e) {
|
||||
super.itemClicked(e, (selected) => {
|
||||
const title = selected.querySelector('.js-data-value').innerText.trim();
|
||||
return `${this.symbol}${gl.DropdownUtils.getEscapedText(title)}`;
|
||||
return `${this.symbol}${DropdownUtils.getEscapedText(title)}`;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -45,6 +46,3 @@ class DropdownNonUser extends gl.FilteredSearchDropdown {
|
|||
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.DropdownNonUser = DropdownNonUser;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import Flash from '../flash';
|
||||
import AjaxFilter from '../droplab/plugins/ajax_filter';
|
||||
import './filtered_search_dropdown';
|
||||
import FilteredSearchDropdown from './filtered_search_dropdown';
|
||||
import { addClassIfElementExists } from '../lib/utils/dom_utils';
|
||||
import DropdownUtils from './dropdown_utils';
|
||||
import FilteredSearchTokenizer from './filtered_search_tokenizer';
|
||||
|
||||
class DropdownUser extends gl.FilteredSearchDropdown {
|
||||
export default class DropdownUser extends FilteredSearchDropdown {
|
||||
constructor(options = {}) {
|
||||
const { tokenKeys } = options;
|
||||
super(options);
|
||||
|
@ -55,8 +57,8 @@ class DropdownUser extends gl.FilteredSearchDropdown {
|
|||
}
|
||||
|
||||
getSearchInput() {
|
||||
const query = gl.DropdownUtils.getSearchInput(this.input);
|
||||
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query, this.tokenKeys.get());
|
||||
const query = DropdownUtils.getSearchInput(this.input);
|
||||
const { lastToken } = FilteredSearchTokenizer.processTokens(query, this.tokenKeys.get());
|
||||
|
||||
let value = lastToken || '';
|
||||
|
||||
|
@ -77,6 +79,3 @@ class DropdownUser extends gl.FilteredSearchDropdown {
|
|||
this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.DropdownUser = DropdownUser;
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import _ from 'underscore';
|
||||
import FilteredSearchContainer from './container';
|
||||
import FilteredSearchTokenizer from './filtered_search_tokenizer';
|
||||
import FilteredSearchDropdownManager from './filtered_search_dropdown_manager';
|
||||
import FilteredSearchVisualTokens from './filtered_search_visual_tokens';
|
||||
|
||||
class DropdownUtils {
|
||||
export default class DropdownUtils {
|
||||
static getEscapedText(text) {
|
||||
let escapedText = text;
|
||||
const hasSpace = text.indexOf(' ') !== -1;
|
||||
|
@ -24,7 +27,7 @@ class DropdownUtils {
|
|||
|
||||
static filterWithSymbol(filterSymbol, input, item) {
|
||||
const updatedItem = item;
|
||||
const searchInput = gl.DropdownUtils.getSearchInput(input);
|
||||
const searchInput = DropdownUtils.getSearchInput(input);
|
||||
|
||||
const title = updatedItem.title.toLowerCase();
|
||||
let value = searchInput.toLowerCase();
|
||||
|
@ -114,9 +117,9 @@ class DropdownUtils {
|
|||
static filterHint(config, item) {
|
||||
const { input, allowedKeys } = config;
|
||||
const updatedItem = item;
|
||||
const searchInput = gl.DropdownUtils.getSearchQuery(input);
|
||||
const searchInput = DropdownUtils.getSearchQuery(input);
|
||||
const { lastToken, tokens } =
|
||||
gl.FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
|
||||
FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
|
||||
const lastKey = lastToken.key || lastToken || '';
|
||||
const allowMultiple = item.type === 'array';
|
||||
const itemInExistingTokens = tokens.some(t => t.key === item.hint);
|
||||
|
@ -140,7 +143,7 @@ class DropdownUtils {
|
|||
const dataValue = selected.getAttribute('data-value');
|
||||
|
||||
if (dataValue) {
|
||||
gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue, true);
|
||||
FilteredSearchDropdownManager.addWordToInput(filter, dataValue, true);
|
||||
}
|
||||
|
||||
// Return boolean based on whether it was set
|
||||
|
@ -190,7 +193,7 @@ class DropdownUtils {
|
|||
}
|
||||
} else if (token.classList.contains('input-token')) {
|
||||
const { isLastVisualTokenValid } =
|
||||
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
|
||||
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
|
||||
const inputValue = input && input.value;
|
||||
|
@ -211,7 +214,7 @@ class DropdownUtils {
|
|||
|
||||
static getSearchInput(filteredSearchInput) {
|
||||
const inputValue = filteredSearchInput.value;
|
||||
const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput);
|
||||
const { right } = DropdownUtils.getInputSelectionPosition(filteredSearchInput);
|
||||
|
||||
return inputValue.slice(0, right);
|
||||
}
|
||||
|
@ -252,6 +255,3 @@ class DropdownUtils {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.DropdownUtils = DropdownUtils;
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
import './dropdown_emoji';
|
||||
import './dropdown_hint';
|
||||
import './dropdown_non_user';
|
||||
import './dropdown_user';
|
||||
import './dropdown_utils';
|
||||
import './filtered_search_dropdown_manager';
|
||||
import './filtered_search_dropdown';
|
||||
import './filtered_search_manager';
|
||||
import './filtered_search_tokenizer';
|
||||
import './filtered_search_visual_tokens';
|
|
@ -1,6 +1,9 @@
|
|||
import DropdownUtils from './dropdown_utils';
|
||||
import FilteredSearchDropdownManager from './filtered_search_dropdown_manager';
|
||||
|
||||
const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
|
||||
|
||||
class FilteredSearchDropdown {
|
||||
export default class FilteredSearchDropdown {
|
||||
constructor({ droplab, dropdown, input, filter }) {
|
||||
this.droplab = droplab;
|
||||
this.hookId = input && input.id;
|
||||
|
@ -30,11 +33,11 @@ class FilteredSearchDropdown {
|
|||
const { selected } = e.detail;
|
||||
|
||||
if (selected.tagName === 'LI' && selected.innerHTML) {
|
||||
const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(this.filter, selected);
|
||||
const dataValueSet = DropdownUtils.setDataValueIfSelected(this.filter, selected);
|
||||
|
||||
if (!dataValueSet) {
|
||||
const value = getValueFunction(selected);
|
||||
gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true);
|
||||
FilteredSearchDropdownManager.addWordToInput(this.filter, value, true);
|
||||
}
|
||||
|
||||
this.resetFilters();
|
||||
|
@ -108,6 +111,9 @@ class FilteredSearchDropdown {
|
|||
|
||||
if (hook) {
|
||||
const data = hook.list.data || [];
|
||||
|
||||
if (!data) return;
|
||||
|
||||
const results = data.map((o) => {
|
||||
const updated = o;
|
||||
updated.droplab_hidden = false;
|
||||
|
@ -117,6 +123,3 @@ class FilteredSearchDropdown {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.FilteredSearchDropdown = FilteredSearchDropdown;
|
||||
|
|
|
@ -1,15 +1,33 @@
|
|||
import _ from 'underscore';
|
||||
import DropLab from '~/droplab/drop_lab';
|
||||
import FilteredSearchContainer from './container';
|
||||
import FilteredSearchTokenKeys from './filtered_search_token_keys';
|
||||
import DropdownUtils from './dropdown_utils';
|
||||
import DropdownHint from './dropdown_hint';
|
||||
import DropdownEmoji from './dropdown_emoji';
|
||||
import DropdownNonUser from './dropdown_non_user';
|
||||
import DropdownUser from './dropdown_user';
|
||||
import FilteredSearchVisualTokens from './filtered_search_visual_tokens';
|
||||
|
||||
class FilteredSearchDropdownManager {
|
||||
constructor(baseEndpoint = '', tokenizer, page, isGroup, filteredSearchTokenKeys) {
|
||||
export default class FilteredSearchDropdownManager {
|
||||
constructor({
|
||||
baseEndpoint = '',
|
||||
tokenizer,
|
||||
page,
|
||||
isGroup,
|
||||
isGroupAncestor,
|
||||
isGroupDecendent,
|
||||
filteredSearchTokenKeys,
|
||||
}) {
|
||||
this.container = FilteredSearchContainer.container;
|
||||
this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
|
||||
this.tokenizer = tokenizer;
|
||||
this.filteredSearchTokenKeys = filteredSearchTokenKeys;
|
||||
this.filteredSearchTokenKeys = filteredSearchTokenKeys || FilteredSearchTokenKeys;
|
||||
this.filteredSearchInput = this.container.querySelector('.filtered-search');
|
||||
this.page = page;
|
||||
this.groupsOnly = isGroup;
|
||||
this.groupAncestor = isGroupAncestor;
|
||||
this.isGroupDecendent = isGroupDecendent;
|
||||
|
||||
this.setupMapping();
|
||||
|
||||
|
@ -33,43 +51,43 @@ class FilteredSearchDropdownManager {
|
|||
const allowedMappings = {
|
||||
hint: {
|
||||
reference: null,
|
||||
gl: 'DropdownHint',
|
||||
gl: DropdownHint,
|
||||
element: this.container.querySelector('#js-dropdown-hint'),
|
||||
},
|
||||
};
|
||||
const availableMappings = {
|
||||
author: {
|
||||
reference: null,
|
||||
gl: 'DropdownUser',
|
||||
gl: DropdownUser,
|
||||
element: this.container.querySelector('#js-dropdown-author'),
|
||||
},
|
||||
assignee: {
|
||||
reference: null,
|
||||
gl: 'DropdownUser',
|
||||
gl: DropdownUser,
|
||||
element: this.container.querySelector('#js-dropdown-assignee'),
|
||||
},
|
||||
milestone: {
|
||||
reference: null,
|
||||
gl: 'DropdownNonUser',
|
||||
gl: DropdownNonUser,
|
||||
extraArguments: {
|
||||
endpoint: `${this.baseEndpoint}/milestones.json`,
|
||||
endpoint: this.getMilestoneEndpoint(),
|
||||
symbol: '%',
|
||||
},
|
||||
element: this.container.querySelector('#js-dropdown-milestone'),
|
||||
},
|
||||
label: {
|
||||
reference: null,
|
||||
gl: 'DropdownNonUser',
|
||||
gl: DropdownNonUser,
|
||||
extraArguments: {
|
||||
endpoint: `${this.baseEndpoint}/labels.json`,
|
||||
endpoint: this.getLabelsEndpoint(),
|
||||
symbol: '~',
|
||||
preprocessing: gl.DropdownUtils.duplicateLabelPreprocessing,
|
||||
preprocessing: DropdownUtils.duplicateLabelPreprocessing,
|
||||
},
|
||||
element: this.container.querySelector('#js-dropdown-label'),
|
||||
},
|
||||
'my-reaction': {
|
||||
reference: null,
|
||||
gl: 'DropdownEmoji',
|
||||
gl: DropdownEmoji,
|
||||
element: this.container.querySelector('#js-dropdown-my-reaction'),
|
||||
},
|
||||
};
|
||||
|
@ -83,14 +101,26 @@ class FilteredSearchDropdownManager {
|
|||
this.mapping = allowedMappings;
|
||||
}
|
||||
|
||||
getMilestoneEndpoint() {
|
||||
const endpoint = `${this.baseEndpoint}/milestones.json`;
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
getLabelsEndpoint() {
|
||||
const endpoint = `${this.baseEndpoint}/labels.json`;
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
static addWordToInput(tokenName, tokenValue = '', clicked = false) {
|
||||
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
|
||||
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue);
|
||||
FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue);
|
||||
input.value = '';
|
||||
|
||||
if (clicked) {
|
||||
gl.FilteredSearchVisualTokens.moveInputToTheRight();
|
||||
FilteredSearchVisualTokens.moveInputToTheRight();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,9 +161,9 @@ class FilteredSearchDropdownManager {
|
|||
const extraArguments = mappingKey.extraArguments || {};
|
||||
const glArguments = Object.assign({}, defaultArguments, extraArguments);
|
||||
|
||||
// Passing glArguments to `new gl[glClass](<arguments>)`
|
||||
// Passing glArguments to `new glClass(<arguments>)`
|
||||
mappingKey.reference =
|
||||
new (Function.prototype.bind.apply(gl[glClass], [null, glArguments]))();
|
||||
new (Function.prototype.bind.apply(glClass, [null, glArguments]))();
|
||||
}
|
||||
|
||||
if (firstLoad) {
|
||||
|
@ -171,7 +201,7 @@ class FilteredSearchDropdownManager {
|
|||
}
|
||||
|
||||
setDropdown() {
|
||||
const query = gl.DropdownUtils.getSearchQuery(true);
|
||||
const query = DropdownUtils.getSearchQuery(true);
|
||||
const { lastToken, searchToken } =
|
||||
this.tokenizer.processTokens(query, this.filteredSearchTokenKeys.getKeys());
|
||||
|
||||
|
@ -216,6 +246,3 @@ class FilteredSearchDropdownManager {
|
|||
this.droplab.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.FilteredSearchDropdownManager = FilteredSearchDropdownManager;
|
||||
|
|
|
@ -1,21 +1,34 @@
|
|||
import _ from 'underscore';
|
||||
import {
|
||||
getParameterByName,
|
||||
getUrlParamsArray,
|
||||
} from '~/lib/utils/common_utils';
|
||||
import { visitUrl } from '../lib/utils/url_utility';
|
||||
import Flash from '../flash';
|
||||
import FilteredSearchContainer from './container';
|
||||
import RecentSearchesRoot from './recent_searches_root';
|
||||
import FilteredSearchTokenKeys from './filtered_search_token_keys';
|
||||
import RecentSearchesRoot from './recent_searches_root';
|
||||
import RecentSearchesStore from './stores/recent_searches_store';
|
||||
import RecentSearchesService from './services/recent_searches_service';
|
||||
import eventHub from './event_hub';
|
||||
import { addClassIfElementExists } from '../lib/utils/dom_utils';
|
||||
import FilteredSearchTokenizer from './filtered_search_tokenizer';
|
||||
import FilteredSearchDropdownManager from './filtered_search_dropdown_manager';
|
||||
import FilteredSearchVisualTokens from './filtered_search_visual_tokens';
|
||||
import DropdownUtils from './dropdown_utils';
|
||||
|
||||
class FilteredSearchManager {
|
||||
export default class FilteredSearchManager {
|
||||
constructor({
|
||||
page,
|
||||
isGroup = false,
|
||||
isGroupAncestor = false,
|
||||
isGroupDecendent = false,
|
||||
filteredSearchTokenKeys = FilteredSearchTokenKeys,
|
||||
stateFiltersSelector = '.issues-state-filters',
|
||||
}) {
|
||||
this.isGroup = false;
|
||||
this.isGroup = isGroup;
|
||||
this.isGroupAncestor = isGroupAncestor;
|
||||
this.isGroupDecendent = isGroupDecendent;
|
||||
this.states = ['opened', 'closed', 'merged', 'all'];
|
||||
|
||||
this.page = page;
|
||||
|
@ -66,14 +79,15 @@ class FilteredSearchManager {
|
|||
});
|
||||
|
||||
if (this.filteredSearchInput) {
|
||||
this.tokenizer = gl.FilteredSearchTokenizer;
|
||||
this.dropdownManager = new gl.FilteredSearchDropdownManager(
|
||||
this.filteredSearchInput.getAttribute('data-base-endpoint') || '',
|
||||
this.tokenizer,
|
||||
this.page,
|
||||
this.isGroup,
|
||||
this.filteredSearchTokenKeys,
|
||||
);
|
||||
this.tokenizer = FilteredSearchTokenizer;
|
||||
this.dropdownManager = new FilteredSearchDropdownManager({
|
||||
baseEndpoint: this.filteredSearchInput.getAttribute('data-base-endpoint') || '',
|
||||
tokenizer: this.tokenizer,
|
||||
page: this.page,
|
||||
isGroup: this.isGroup,
|
||||
isGroupAncestor: this.isGroupAncestor,
|
||||
filteredSearchTokenKeys: this.filteredSearchTokenKeys,
|
||||
});
|
||||
|
||||
this.recentSearchesRoot = new RecentSearchesRoot(
|
||||
this.recentSearchesStore,
|
||||
|
@ -85,7 +99,6 @@ class FilteredSearchManager {
|
|||
this.bindEvents();
|
||||
this.loadSearchParamsFromURL();
|
||||
this.dropdownManager.setDropdown();
|
||||
|
||||
this.cleanupWrapper = this.cleanup.bind(this);
|
||||
document.addEventListener('beforeunload', this.cleanupWrapper);
|
||||
}
|
||||
|
@ -197,8 +210,8 @@ class FilteredSearchManager {
|
|||
// 8 = Backspace Key
|
||||
// 46 = Delete Key
|
||||
if (e.keyCode === 8 || e.keyCode === 46) {
|
||||
const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(lastVisualToken);
|
||||
const { lastVisualToken } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
const { tokenName, tokenValue } = DropdownUtils.getVisualTokenValues(lastVisualToken);
|
||||
const canEdit = tokenName && this.canEdit && this.canEdit(tokenName, tokenValue);
|
||||
|
||||
if (this.filteredSearchInput.value === '' && lastVisualToken && canEdit) {
|
||||
|
@ -206,8 +219,8 @@ class FilteredSearchManager {
|
|||
|
||||
if (backspaceCount === 2) {
|
||||
backspaceCount = 0;
|
||||
this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
|
||||
gl.FilteredSearchVisualTokens.removeLastTokenPartial();
|
||||
this.filteredSearchInput.value = FilteredSearchVisualTokens.getLastTokenPartial();
|
||||
FilteredSearchVisualTokens.removeLastTokenPartial();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -275,7 +288,7 @@ class FilteredSearchManager {
|
|||
e.stopImmediatePropagation();
|
||||
|
||||
const button = e.target.closest('.selectable');
|
||||
gl.FilteredSearchVisualTokens.selectToken(button, true);
|
||||
FilteredSearchVisualTokens.selectToken(button, true);
|
||||
this.removeSelectedToken();
|
||||
}
|
||||
}
|
||||
|
@ -287,7 +300,7 @@ class FilteredSearchManager {
|
|||
const isElementTokensContainer = e.target.classList.contains('tokens-container');
|
||||
|
||||
if ((!isElementInFilteredSearch && !isElementInFilterDropdown) || isElementTokensContainer) {
|
||||
gl.FilteredSearchVisualTokens.moveInputToTheRight();
|
||||
FilteredSearchVisualTokens.moveInputToTheRight();
|
||||
this.dropdownManager.resetDropdowns();
|
||||
}
|
||||
}
|
||||
|
@ -300,13 +313,13 @@ class FilteredSearchManager {
|
|||
if (token && canEdit) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
gl.FilteredSearchVisualTokens.editToken(token);
|
||||
FilteredSearchVisualTokens.editToken(token);
|
||||
this.tokenChange();
|
||||
}
|
||||
}
|
||||
|
||||
toggleClearSearchButton() {
|
||||
const query = gl.DropdownUtils.getSearchQuery();
|
||||
const query = DropdownUtils.getSearchQuery();
|
||||
const hidden = 'hidden';
|
||||
const hasHidden = this.clearSearchButton.classList.contains(hidden);
|
||||
|
||||
|
@ -318,7 +331,7 @@ class FilteredSearchManager {
|
|||
}
|
||||
|
||||
handleInputPlaceholder() {
|
||||
const query = gl.DropdownUtils.getSearchQuery();
|
||||
const query = DropdownUtils.getSearchQuery();
|
||||
const placeholder = 'Search or filter results...';
|
||||
const currentPlaceholder = this.filteredSearchInput.placeholder;
|
||||
|
||||
|
@ -338,7 +351,7 @@ class FilteredSearchManager {
|
|||
}
|
||||
|
||||
removeSelectedToken() {
|
||||
gl.FilteredSearchVisualTokens.removeSelectedToken();
|
||||
FilteredSearchVisualTokens.removeSelectedToken();
|
||||
this.handleInputPlaceholder();
|
||||
this.toggleClearSearchButton();
|
||||
this.dropdownManager.updateCurrentDropdownOffset();
|
||||
|
@ -358,7 +371,7 @@ class FilteredSearchManager {
|
|||
let canClearToken = t.classList.contains('js-visual-token');
|
||||
|
||||
if (canClearToken) {
|
||||
const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(t);
|
||||
const { tokenName, tokenValue } = DropdownUtils.getVisualTokenValues(t);
|
||||
canClearToken = this.canEdit && this.canEdit(tokenName, tokenValue);
|
||||
}
|
||||
|
||||
|
@ -386,12 +399,12 @@ class FilteredSearchManager {
|
|||
const { tokens, searchToken }
|
||||
= this.tokenizer.processTokens(input.value, this.filteredSearchTokenKeys.getKeys());
|
||||
const { isLastVisualTokenValid }
|
||||
= gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
= FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
|
||||
if (isLastVisualTokenValid) {
|
||||
tokens.forEach((t) => {
|
||||
input.value = input.value.replace(`${t.key}:${t.symbol}${t.value}`, '');
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(t.key, `${t.symbol}${t.value}`);
|
||||
FilteredSearchVisualTokens.addFilterVisualToken(t.key, `${t.symbol}${t.value}`);
|
||||
});
|
||||
|
||||
const fragments = searchToken.split(':');
|
||||
|
@ -404,10 +417,10 @@ class FilteredSearchManager {
|
|||
const searchTerms = inputValues.join(' ');
|
||||
|
||||
input.value = input.value.replace(searchTerms, '');
|
||||
gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms);
|
||||
FilteredSearchVisualTokens.addSearchVisualToken(searchTerms);
|
||||
}
|
||||
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenKey);
|
||||
FilteredSearchVisualTokens.addFilterVisualToken(tokenKey);
|
||||
input.value = input.value.replace(`${tokenKey}:`, '');
|
||||
}
|
||||
} else {
|
||||
|
@ -415,7 +428,7 @@ class FilteredSearchManager {
|
|||
const valueCompletedRegex = /([~%@]{0,1}".+")|([~%@]{0,1}'.+')|^((?![~%@]')(?![~%@]")(?!')(?!")).*/g;
|
||||
|
||||
if (searchToken.match(valueCompletedRegex) && input.value[input.value.length - 1] === ' ') {
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(searchToken);
|
||||
FilteredSearchVisualTokens.addFilterVisualToken(searchToken);
|
||||
|
||||
// Trim the last space as seen in the if statement above
|
||||
input.value = input.value.replace(searchToken, '').trim();
|
||||
|
@ -431,7 +444,7 @@ class FilteredSearchManager {
|
|||
saveCurrentSearchQuery() {
|
||||
// Don't save before we have fetched the already saved searches
|
||||
this.fetchingRecentSearchesPromise.then(() => {
|
||||
const searchQuery = gl.DropdownUtils.getSearchQuery();
|
||||
const searchQuery = DropdownUtils.getSearchQuery();
|
||||
if (searchQuery.length > 0) {
|
||||
const resultantSearches = this.recentSearchesStore.addRecentSearch(searchQuery);
|
||||
this.recentSearchesService.save(resultantSearches);
|
||||
|
@ -447,7 +460,7 @@ class FilteredSearchManager {
|
|||
}
|
||||
|
||||
loadSearchParamsFromURL() {
|
||||
const urlParams = gl.utils.getUrlParamsArray();
|
||||
const urlParams = getUrlParamsArray();
|
||||
const params = this.getAllParams(urlParams);
|
||||
const usernameParams = this.getUsernameParams();
|
||||
let hasFilteredSearch = false;
|
||||
|
@ -463,7 +476,7 @@ class FilteredSearchManager {
|
|||
if (condition) {
|
||||
hasFilteredSearch = true;
|
||||
const canEdit = this.canEdit && this.canEdit(condition.tokenKey);
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(
|
||||
FilteredSearchVisualTokens.addFilterVisualToken(
|
||||
condition.tokenKey,
|
||||
condition.value,
|
||||
canEdit,
|
||||
|
@ -492,7 +505,7 @@ class FilteredSearchManager {
|
|||
|
||||
hasFilteredSearch = true;
|
||||
const canEdit = this.canEdit && this.canEdit(sanitizedKey, sanitizedValue);
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(
|
||||
FilteredSearchVisualTokens.addFilterVisualToken(
|
||||
sanitizedKey,
|
||||
`${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`,
|
||||
canEdit,
|
||||
|
@ -503,7 +516,7 @@ class FilteredSearchManager {
|
|||
hasFilteredSearch = true;
|
||||
const tokenName = 'assignee';
|
||||
const canEdit = this.canEdit && this.canEdit(tokenName);
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, canEdit);
|
||||
FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, canEdit);
|
||||
}
|
||||
} else if (!match && keyParam === 'author_id') {
|
||||
const id = parseInt(value, 10);
|
||||
|
@ -511,7 +524,7 @@ class FilteredSearchManager {
|
|||
hasFilteredSearch = true;
|
||||
const tokenName = 'author';
|
||||
const canEdit = this.canEdit && this.canEdit(tokenName);
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, canEdit);
|
||||
FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, canEdit);
|
||||
}
|
||||
} else if (!match && keyParam === 'search') {
|
||||
hasFilteredSearch = true;
|
||||
|
@ -543,13 +556,13 @@ class FilteredSearchManager {
|
|||
|
||||
search(state = null) {
|
||||
const paths = [];
|
||||
const searchQuery = gl.DropdownUtils.getSearchQuery();
|
||||
const searchQuery = DropdownUtils.getSearchQuery();
|
||||
|
||||
this.saveCurrentSearchQuery();
|
||||
|
||||
const { tokens, searchToken }
|
||||
= this.tokenizer.processTokens(searchQuery, this.filteredSearchTokenKeys.getKeys());
|
||||
const currentState = state || gl.utils.getParameterByName('state') || 'opened';
|
||||
const currentState = state || getParameterByName('state') || 'opened';
|
||||
paths.push(`state=${currentState}`);
|
||||
|
||||
tokens.forEach((token) => {
|
||||
|
@ -628,6 +641,3 @@ class FilteredSearchManager {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.FilteredSearchManager = FilteredSearchManager;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import './filtered_search_token_keys';
|
||||
|
||||
class FilteredSearchTokenizer {
|
||||
export default class FilteredSearchTokenizer {
|
||||
static processTokens(input, allowedKeys) {
|
||||
// Regex extracts `(token):(symbol)(value)`
|
||||
// Values that start with a double quote must end in a double quote (same for single)
|
||||
|
@ -50,6 +50,3 @@ class FilteredSearchTokenizer {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.FilteredSearchTokenizer = FilteredSearchTokenizer;
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import _ from 'underscore';
|
||||
import AjaxCache from '../lib/utils/ajax_cache';
|
||||
import AjaxCache from '~/lib/utils/ajax_cache';
|
||||
import { objectToQueryString } from '~/lib/utils/common_utils';
|
||||
import Flash from '../flash';
|
||||
import FilteredSearchContainer from './container';
|
||||
import UsersCache from '../lib/utils/users_cache';
|
||||
import DropdownUtils from './dropdown_utils';
|
||||
|
||||
class FilteredSearchVisualTokens {
|
||||
export default class FilteredSearchVisualTokens {
|
||||
static getLastVisualTokenBeforeInput() {
|
||||
const inputLi = FilteredSearchContainer.container.querySelector('.input-token');
|
||||
const lastVisualToken = inputLi && inputLi.previousElementSibling;
|
||||
|
@ -15,6 +17,21 @@ class FilteredSearchVisualTokens {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a computed API endpoint
|
||||
* and query string composed of values from endpointQueryParams
|
||||
* @param {String} endpoint
|
||||
* @param {String} endpointQueryParams
|
||||
*/
|
||||
static getEndpointWithQueryParams(endpoint, endpointQueryParams) {
|
||||
if (!endpointQueryParams) {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
const queryString = objectToQueryString(JSON.parse(endpointQueryParams));
|
||||
return `${endpoint}?${queryString}`;
|
||||
}
|
||||
|
||||
static unselectTokens() {
|
||||
const otherTokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token .selectable.selected');
|
||||
[].forEach.call(otherTokens, t => t.classList.remove('selected'));
|
||||
|
@ -74,7 +91,7 @@ class FilteredSearchVisualTokens {
|
|||
let processed = labels;
|
||||
|
||||
if (!labels.preprocessed) {
|
||||
processed = gl.DropdownUtils.duplicateLabelPreprocessing(labels);
|
||||
processed = DropdownUtils.duplicateLabelPreprocessing(labels);
|
||||
AjaxCache.override(labelsEndpoint, processed);
|
||||
processed.preprocessed = true;
|
||||
}
|
||||
|
@ -85,12 +102,15 @@ class FilteredSearchVisualTokens {
|
|||
static updateLabelTokenColor(tokenValueContainer, tokenValue) {
|
||||
const filteredSearchInput = FilteredSearchContainer.container.querySelector('.filtered-search');
|
||||
const baseEndpoint = filteredSearchInput.dataset.baseEndpoint;
|
||||
const labelsEndpoint = `${baseEndpoint}/labels.json`;
|
||||
const labelsEndpoint = FilteredSearchVisualTokens.getEndpointWithQueryParams(
|
||||
`${baseEndpoint}/labels.json`,
|
||||
filteredSearchInput.dataset.endpointQueryParams,
|
||||
);
|
||||
|
||||
return AjaxCache.retrieve(labelsEndpoint)
|
||||
.then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint))
|
||||
.then((labels) => {
|
||||
const matchingLabel = (labels || []).find(label => `~${gl.DropdownUtils.getEscapedText(label.title)}` === tokenValue);
|
||||
const matchingLabel = (labels || []).find(label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue);
|
||||
|
||||
if (!matchingLabel) {
|
||||
return;
|
||||
|
@ -259,11 +279,11 @@ class FilteredSearchVisualTokens {
|
|||
static tokenizeInput() {
|
||||
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
|
||||
const { isLastVisualTokenValid } =
|
||||
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
|
||||
if (input.value) {
|
||||
if (isLastVisualTokenValid) {
|
||||
gl.FilteredSearchVisualTokens.addSearchVisualToken(input.value);
|
||||
FilteredSearchVisualTokens.addSearchVisualToken(input.value);
|
||||
} else {
|
||||
FilteredSearchVisualTokens.addValueToPreviousVisualTokenElement(input.value);
|
||||
}
|
||||
|
@ -324,12 +344,12 @@ class FilteredSearchVisualTokens {
|
|||
|
||||
if (!tokenContainer.lastElementChild.isEqualNode(inputLi)) {
|
||||
const { isLastVisualTokenValid } =
|
||||
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
|
||||
if (!isLastVisualTokenValid) {
|
||||
const lastPartial = gl.FilteredSearchVisualTokens.getLastTokenPartial();
|
||||
gl.FilteredSearchVisualTokens.removeLastTokenPartial();
|
||||
gl.FilteredSearchVisualTokens.addSearchVisualToken(lastPartial);
|
||||
const lastPartial = FilteredSearchVisualTokens.getLastTokenPartial();
|
||||
FilteredSearchVisualTokens.removeLastTokenPartial();
|
||||
FilteredSearchVisualTokens.addSearchVisualToken(lastPartial);
|
||||
}
|
||||
|
||||
tokenContainer.removeChild(inputLi);
|
||||
|
@ -337,6 +357,3 @@ class FilteredSearchVisualTokens {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.FilteredSearchVisualTokens = FilteredSearchVisualTokens;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import RecentSearchesDropdownContent from './components/recent_searches_dropdown_content';
|
||||
import RecentSearchesDropdownContent from './components/recent_searches_dropdown_content.vue';
|
||||
import eventHub from './event_hub';
|
||||
|
||||
class RecentSearchesRoot {
|
||||
|
@ -33,7 +33,7 @@ class RecentSearchesRoot {
|
|||
this.vm = new Vue({
|
||||
el: this.wrapperElement,
|
||||
components: {
|
||||
'recent-searches-dropdown-content': RecentSearchesDropdownContent,
|
||||
RecentSearchesDropdownContent,
|
||||
},
|
||||
data() { return state; },
|
||||
template: `
|
||||
|
|
|
@ -485,7 +485,7 @@ GitLabDropdown = (function() {
|
|||
$target = $(e.target);
|
||||
if ($target && !$target.hasClass('dropdown-menu-close') &&
|
||||
!$target.hasClass('dropdown-menu-close-icon') &&
|
||||
!$target.data('is-link')) {
|
||||
!$target.data('isLink')) {
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
} else {
|
||||
|
@ -607,7 +607,20 @@ GitLabDropdown = (function() {
|
|||
};
|
||||
|
||||
GitLabDropdown.prototype.renderItem = function(data, group, index) {
|
||||
var field, fieldName, html, selected, text, url, value;
|
||||
var field, fieldName, html, selected, text, url, value, rowHidden;
|
||||
|
||||
if (!this.options.renderRow) {
|
||||
value = this.options.id ? this.options.id(data) : data.id;
|
||||
|
||||
if (value) {
|
||||
value = value.toString().replace(/'/g, '\\\'');
|
||||
}
|
||||
}
|
||||
|
||||
// Hide element
|
||||
if (this.options.hideRow && this.options.hideRow(value)) {
|
||||
rowHidden = true;
|
||||
}
|
||||
if (group == null) {
|
||||
group = false;
|
||||
}
|
||||
|
@ -616,6 +629,7 @@ GitLabDropdown = (function() {
|
|||
index = false;
|
||||
}
|
||||
html = document.createElement('li');
|
||||
|
||||
if (data === 'divider' || data === 'separator') {
|
||||
html.className = data;
|
||||
return html;
|
||||
|
@ -631,11 +645,9 @@ GitLabDropdown = (function() {
|
|||
html = this.options.renderRow.call(this.options, data, this);
|
||||
} else {
|
||||
if (!selected) {
|
||||
value = this.options.id ? this.options.id(data) : data.id;
|
||||
fieldName = this.options.fieldName;
|
||||
|
||||
if (value) {
|
||||
value = value.toString().replace(/'/g, '\\\'');
|
||||
field = this.dropdown.parent().find(`input[name='${fieldName}'][value='${value}']`);
|
||||
if (field.length) {
|
||||
selected = true;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/* global autosize */
|
||||
|
||||
import autosize from 'autosize';
|
||||
import GfmAutoComplete from './gfm_auto_complete';
|
||||
import dropzoneInput from './dropzone_input';
|
||||
import textUtils from './lib/utils/text_markdown';
|
||||
|
@ -13,7 +12,7 @@ export default class GLForm {
|
|||
this.destroy();
|
||||
// Setup the form
|
||||
this.setupForm();
|
||||
this.form.data('gl-form', this);
|
||||
this.form.data('glForm', this);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
@ -22,7 +21,7 @@ export default class GLForm {
|
|||
if (this.autoComplete) {
|
||||
this.autoComplete.destroy();
|
||||
}
|
||||
this.form.data('gl-form', null);
|
||||
this.form.data('glForm', null);
|
||||
}
|
||||
|
||||
setupForm() {
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
import { parseQueryStringIntoObject } from '~/lib/utils/common_utils';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import flash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default class GpgBadges {
|
||||
static fetch() {
|
||||
const badges = $('.js-loading-gpg-badge');
|
||||
|
@ -5,13 +10,13 @@ export default class GpgBadges {
|
|||
|
||||
badges.html('<i class="fa fa-spinner fa-spin"></i>');
|
||||
|
||||
$.get({
|
||||
url: form.data('signatures-path'),
|
||||
data: form.serialize(),
|
||||
}).done((response) => {
|
||||
response.signatures.forEach((signature) => {
|
||||
const params = parseQueryStringIntoObject(form.serialize());
|
||||
return axios.get(form.data('signaturesPath'), { params })
|
||||
.then(({ data }) => {
|
||||
data.signatures.forEach((signature) => {
|
||||
badges.filter(`[data-commit-sha="${signature.commit_sha}"]`).replaceWith(signature.html);
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => flash(__('An error occurred while loading commits')));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
import Chart from 'vendor/Chart';
|
||||
|
||||
// export to global scope
|
||||
window.Chart = Chart;
|
|
@ -30,11 +30,11 @@
|
|||
default: 'bottom',
|
||||
},
|
||||
/**
|
||||
* value could either be number or string
|
||||
* as `memberCount` is always passed as string
|
||||
* while `subgroupCount` & `projectCount`
|
||||
* are always number
|
||||
*/
|
||||
* value could either be number or string
|
||||
* as `memberCount` is always passed as string
|
||||
* while `subgroupCount` & `projectCount`
|
||||
* are always number
|
||||
*/
|
||||
value: {
|
||||
type: [Number, String],
|
||||
required: false,
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import axios from './lib/utils/axios_utils';
|
||||
import Api from './api';
|
||||
import { normalizeCRLFHeaders } from './lib/utils/common_utils';
|
||||
import { normalizeHeaders } from './lib/utils/common_utils';
|
||||
|
||||
export default function groupsSelect() {
|
||||
// Needs to be accessible in rspec
|
||||
window.GROUP_SELECT_PER_PAGE = 20;
|
||||
$('.ajax-groups-select').each(function setAjaxGroupsSelect2() {
|
||||
const $select = $(this);
|
||||
const allAvailable = $select.data('all-available');
|
||||
const skipGroups = $select.data('skip-groups') || [];
|
||||
const allAvailable = $select.data('allAvailable');
|
||||
const skipGroups = $select.data('skipGroups') || [];
|
||||
$select.select2({
|
||||
placeholder: 'Search for a group',
|
||||
multiple: $select.hasClass('multiselect'),
|
||||
|
@ -17,24 +18,23 @@ export default function groupsSelect() {
|
|||
dataType: 'json',
|
||||
quietMillis: 250,
|
||||
transport(params) {
|
||||
return $.ajax(params)
|
||||
.then((data, status, xhr) => {
|
||||
const results = data || [];
|
||||
|
||||
const headers = normalizeCRLFHeaders(xhr.getAllResponseHeaders());
|
||||
axios[params.type.toLowerCase()](params.url, {
|
||||
params: params.data,
|
||||
})
|
||||
.then((res) => {
|
||||
const results = res.data || [];
|
||||
const headers = normalizeHeaders(res.headers);
|
||||
const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
|
||||
const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0;
|
||||
const more = currentPage < totalPages;
|
||||
|
||||
return {
|
||||
params.success({
|
||||
results,
|
||||
pagination: {
|
||||
more,
|
||||
},
|
||||
};
|
||||
})
|
||||
.then(params.success)
|
||||
.fail(params.error);
|
||||
});
|
||||
}).catch(params.error);
|
||||
},
|
||||
data(search, page) {
|
||||
return {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// We will render the icons list here
|
||||
if ($('#user-content-gitlab-icons').length > 0) {
|
||||
const $iconsHeader = $('#user-content-gitlab-icons');
|
||||
const $iconsList = $('<div id="iconsList">ICONS</div>');
|
||||
$($iconsList).insertAfter($iconsHeader.parent());
|
||||
}
|
||||
export default () => {
|
||||
if ($('#user-content-gitlab-icons').length > 0) {
|
||||
const $iconsHeader = $('#user-content-gitlab-icons');
|
||||
const $iconsList = $('<div id="iconsList">ICONS</div>');
|
||||
$($iconsList).insertAfter($iconsHeader.parent());
|
||||
}
|
||||
};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue