New upstream version 9.5.4+dfsg

This commit is contained in:
Pirate Praveen 2017-09-10 17:25:29 +05:30
parent b03c211899
commit 574775de31
5149 changed files with 159847 additions and 65503 deletions

47
.codeclimate.yml Normal file
View file

@ -0,0 +1,47 @@
---
engines:
brakeman:
enabled: true
bundler-audit:
enabled: true
duplication:
enabled: true
config:
languages:
- ruby
- javascript
exclude_paths:
- "lib/api/v3/*"
eslint:
enabled: true
rubocop:
enabled: true
ratings:
paths:
- Gemfile.lock
- "**.erb"
- "**.haml"
- "**.rb"
- "**.rhtml"
- "**.slim"
- "**.inc"
- "**.js"
- "**.jsx"
- "**.module"
exclude_paths:
- config/
- db/
- features/
- node_modules/
- spec/
- vendor/
- .yarn-cache/
- tmp/
- builds/
- coverage/
- public/
- shared/
- webpack-report/
- log/
- backups/
- coverage-javascript/

View file

@ -11,6 +11,7 @@
"gon": false,
"localStorage": false
},
"parser": "babel-eslint",
"plugins": [
"filenames",
"import",
@ -27,7 +28,9 @@
},
"rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+$"],
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error"
"promise/catch-or-return": "error",
"no-underscore-dangle": ["error", { "allow": ["__"]}]
}
}

View file

@ -3,3 +3,5 @@ lib/gitlab/sanitizers/svg/whitelist.rb
lib/gitlab/diff/position_tracer.rb
app/policies/project_policy.rb
app/models/concerns/relative_positioning.rb
app/workers/stuck_merge_jobs_worker.rb
lib/gitlab/redis/*.rb

7
.gitignore vendored
View file

@ -18,8 +18,10 @@ eslint-report.html
.sass-cache/
/.secret
/.vagrant
/.yarn-cache
/.byebug_history
/Vagrantfile
/app/assets/javascripts/locale/**/app.js
/backups/*
/config/aws.yml
/config/database.yml
@ -29,6 +31,9 @@ eslint-report.html
/config/initializers/smtp_settings.rb
/config/initializers/relative_url.rb
/config/resque.yml
/config/redis.cache.yml
/config/redis.queues.yml
/config/redis.shared_state.yml
/config/unicorn.rb
/config/secrets.yml
/config/sidekiq.yml
@ -48,6 +53,7 @@ eslint-report.html
/public/uploads/
/shared/artifacts/
/spec/javascripts/fixtures/blob/pdf/
/spec/javascripts/fixtures/blob/balsamiq/
/rails_best_practices_output.html
/tags
/tmp/*
@ -57,3 +63,4 @@ eslint-report.html
/.gitlab_workhorse_secret
/webpack-report/
/locale/**/LC_MESSAGES
/.rspec

View file

@ -1,9 +1,20 @@
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-phantomjs-2.1-node-7.1-postgresql-9.6"
cache:
key: "ruby-233"
.default-cache: &default-cache
key: "ruby-233-with-yarn"
paths:
- vendor/ruby
- vendor/ruby
- .yarn-cache/
.push-cache: &push-cache
cache:
<<: *default-cache
policy: push
.pull-cache: &pull-cache
cache:
<<: *default-cache
policy: pull
variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
@ -23,11 +34,11 @@ before_script:
- source scripts/prepare_build.sh
stages:
- build
- prepare
- test
- post-test
- pages
- build
- prepare
- test
- post-test
- pages
# Predefined scopes
.dedicated-runner: &dedicated-runner
@ -40,10 +51,6 @@ stages:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
KNAPSACK_S3_BUCKET: "gitlab-ce-cache"
cache:
key: "knapsack"
paths:
- knapsack/
artifacts:
expire_in: 31d
paths:
@ -59,34 +66,39 @@ stages:
- mysql:latest
- redis:alpine
.only-master-and-ee-or-mysql: &only-master-and-ee-or-mysql
.only-if-want-mysql: &only-if-want-mysql
only:
- /mysql/
- /-stable/
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- master@gitlab/gitlabhq
- master@gitlab/gitlab-ee
- tags@gitlab-org/gitlab-ce
- tags@gitlab-org/gitlab-ee
- tags@gitlab/gitlabhq
- //@gitlab-org/gitlab-ee
- //@gitlab/gitlab-ee
- tags@gitlab/gitlab-ee
# Skip all jobs except the ones that begin with 'docs/'.
# Used for commits including ONLY documentation changes.
# https://docs.gitlab.com/ce/development/writing_documentation.html#testing
.except-docs: &except-docs
except:
- /^docs\/.*/
- /(^docs[\/-].*|.*-docs$)/
.rspec-knapsack: &rspec-knapsack
stage: test
<<: *dedicated-runner
<<: *pull-cache
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[-2]}
- export CI_NODE_TOTAL=${JOB_NAME[-1]}
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_${JOB_NAME[1]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true
- export CACHE_CLASSES=true
- cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
- scripts/gitaly-test-spawn
- knapsack rspec "--color --format documentation"
artifacts:
expire_in: 31d
@ -104,17 +116,18 @@ stages:
.rspec-knapsack-mysql: &rspec-knapsack-mysql
<<: *rspec-knapsack
<<: *use-mysql
<<: *only-master-and-ee-or-mysql
<<: *only-if-want-mysql
<<: *except-docs
.spinach-knapsack: &spinach-knapsack
stage: test
<<: *dedicated-runner
<<: *pull-cache
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[-2]}
- export CI_NODE_TOTAL=${JOB_NAME[-1]}
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_${JOB_NAME[1]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true
- export CACHE_CLASSES=true
- cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
@ -135,30 +148,33 @@ stages:
.spinach-knapsack-mysql: &spinach-knapsack-mysql
<<: *spinach-knapsack
<<: *use-mysql
<<: *only-master-and-ee-or-mysql
<<: *only-if-want-mysql
<<: *except-docs
.only-canonical-masters: &only-canonical-masters
only:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- master@gitlab/gitlabhq
- master@gitlab/gitlab-ee
# Trigger a package build on omnibus-gitlab repository
build-package:
image: ruby:2.3-alpine
before_script: []
services: []
variables:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
stage: build
cache: {}
when: manual
script:
# If no branch in omnibus is specified, trigger pipeline against master
- if [ -z "$OMNIBUS_BRANCH" ] ; then export OMNIBUS_BRANCH=master ;fi
- echo "token=${BUILD_TRIGGER_TOKEN}" > version_details
- echo "ref=${OMNIBUS_BRANCH}" >> version_details
- echo "variables[ALTERNATIVE_SOURCES]=true" >> version_details
- echo "variables[GITLAB_VERSION]=${CI_COMMIT_SHA}" >> version_details
# Collect version details of all components
- for f in *_VERSION; do echo "variables[$f]=$(cat $f)" >> version_details; done
# Trigger the API and pass values collected above as parameters to it
- cat version_details | tr '\n' '&' | curl -X POST https://gitlab.com/api/v4/projects/20699/trigger/pipeline --data-binary @-
- rm version_details
- scripts/trigger-build
only:
- //@gitlab-org/gitlab-ce
- //@gitlab-org/gitlab-ee
# Prepare and merge knapsack tests
knapsack:
@ -166,6 +182,11 @@ knapsack:
<<: *dedicated-runner
<<: *except-docs
stage: prepare
cache:
key: knapsack
paths:
- knapsack/
policy: pull
script:
- mkdir -p knapsack/${CI_PROJECT_NAME}/
- wget -O $KNAPSACK_RSPEC_SUITE_REPORT_PATH http://${KNAPSACK_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_RSPEC_SUITE_REPORT_PATH || rm $KNAPSACK_RSPEC_SUITE_REPORT_PATH
@ -176,28 +197,34 @@ knapsack:
update-knapsack:
<<: *knapsack-state
<<: *dedicated-runner
<<: *only-canonical-masters
stage: post-test
cache:
key: knapsack
paths:
- knapsack/
policy: push
script:
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec_pg_node_*.json
- scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach_pg_node_*.json
- retry gem install fog-aws mime-types
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
- scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json
- '[[ -z ${KNAPSACK_S3_BUCKET} ]] || scripts/sync-reports put $KNAPSACK_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH'
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
only:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- master@gitlab/gitlabhq
- master@gitlab/gitlab-ee
setup-test-env:
<<: *use-pg
<<: *dedicated-runner
<<: *except-docs
stage: prepare
cache:
<<: *default-cache
script:
- node --version
- yarn install --pure-lockfile
- yarn install --pure-lockfile --cache-folder .yarn-cache
- bundle exec rake gettext:po_to_json
- bundle exec rake gitlab:assets:compile
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
- scripts/gitaly-test-build # Do not use 'bundle exec' here
artifacts:
expire_in: 7d
paths:
@ -205,81 +232,82 @@ setup-test-env:
- public/assets
- tmp/tests
rspec pg 0 20: *rspec-knapsack-pg
rspec pg 1 20: *rspec-knapsack-pg
rspec pg 2 20: *rspec-knapsack-pg
rspec pg 3 20: *rspec-knapsack-pg
rspec pg 4 20: *rspec-knapsack-pg
rspec pg 5 20: *rspec-knapsack-pg
rspec pg 6 20: *rspec-knapsack-pg
rspec pg 7 20: *rspec-knapsack-pg
rspec pg 8 20: *rspec-knapsack-pg
rspec pg 9 20: *rspec-knapsack-pg
rspec pg 10 20: *rspec-knapsack-pg
rspec pg 11 20: *rspec-knapsack-pg
rspec pg 12 20: *rspec-knapsack-pg
rspec pg 13 20: *rspec-knapsack-pg
rspec pg 14 20: *rspec-knapsack-pg
rspec pg 15 20: *rspec-knapsack-pg
rspec pg 16 20: *rspec-knapsack-pg
rspec pg 17 20: *rspec-knapsack-pg
rspec pg 18 20: *rspec-knapsack-pg
rspec pg 19 20: *rspec-knapsack-pg
rspec-pg 0 25: *rspec-knapsack-pg
rspec-pg 1 25: *rspec-knapsack-pg
rspec-pg 2 25: *rspec-knapsack-pg
rspec-pg 3 25: *rspec-knapsack-pg
rspec-pg 4 25: *rspec-knapsack-pg
rspec-pg 5 25: *rspec-knapsack-pg
rspec-pg 6 25: *rspec-knapsack-pg
rspec-pg 7 25: *rspec-knapsack-pg
rspec-pg 8 25: *rspec-knapsack-pg
rspec-pg 9 25: *rspec-knapsack-pg
rspec-pg 10 25: *rspec-knapsack-pg
rspec-pg 11 25: *rspec-knapsack-pg
rspec-pg 12 25: *rspec-knapsack-pg
rspec-pg 13 25: *rspec-knapsack-pg
rspec-pg 14 25: *rspec-knapsack-pg
rspec-pg 15 25: *rspec-knapsack-pg
rspec-pg 16 25: *rspec-knapsack-pg
rspec-pg 17 25: *rspec-knapsack-pg
rspec-pg 18 25: *rspec-knapsack-pg
rspec-pg 19 25: *rspec-knapsack-pg
rspec-pg 20 25: *rspec-knapsack-pg
rspec-pg 21 25: *rspec-knapsack-pg
rspec-pg 22 25: *rspec-knapsack-pg
rspec-pg 23 25: *rspec-knapsack-pg
rspec-pg 24 25: *rspec-knapsack-pg
rspec mysql 0 20: *rspec-knapsack-mysql
rspec mysql 1 20: *rspec-knapsack-mysql
rspec mysql 2 20: *rspec-knapsack-mysql
rspec mysql 3 20: *rspec-knapsack-mysql
rspec mysql 4 20: *rspec-knapsack-mysql
rspec mysql 5 20: *rspec-knapsack-mysql
rspec mysql 6 20: *rspec-knapsack-mysql
rspec mysql 7 20: *rspec-knapsack-mysql
rspec mysql 8 20: *rspec-knapsack-mysql
rspec mysql 9 20: *rspec-knapsack-mysql
rspec mysql 10 20: *rspec-knapsack-mysql
rspec mysql 11 20: *rspec-knapsack-mysql
rspec mysql 12 20: *rspec-knapsack-mysql
rspec mysql 13 20: *rspec-knapsack-mysql
rspec mysql 14 20: *rspec-knapsack-mysql
rspec mysql 15 20: *rspec-knapsack-mysql
rspec mysql 16 20: *rspec-knapsack-mysql
rspec mysql 17 20: *rspec-knapsack-mysql
rspec mysql 18 20: *rspec-knapsack-mysql
rspec mysql 19 20: *rspec-knapsack-mysql
rspec-mysql 0 25: *rspec-knapsack-mysql
rspec-mysql 1 25: *rspec-knapsack-mysql
rspec-mysql 2 25: *rspec-knapsack-mysql
rspec-mysql 3 25: *rspec-knapsack-mysql
rspec-mysql 4 25: *rspec-knapsack-mysql
rspec-mysql 5 25: *rspec-knapsack-mysql
rspec-mysql 6 25: *rspec-knapsack-mysql
rspec-mysql 7 25: *rspec-knapsack-mysql
rspec-mysql 8 25: *rspec-knapsack-mysql
rspec-mysql 9 25: *rspec-knapsack-mysql
rspec-mysql 10 25: *rspec-knapsack-mysql
rspec-mysql 11 25: *rspec-knapsack-mysql
rspec-mysql 12 25: *rspec-knapsack-mysql
rspec-mysql 13 25: *rspec-knapsack-mysql
rspec-mysql 14 25: *rspec-knapsack-mysql
rspec-mysql 15 25: *rspec-knapsack-mysql
rspec-mysql 16 25: *rspec-knapsack-mysql
rspec-mysql 17 25: *rspec-knapsack-mysql
rspec-mysql 18 25: *rspec-knapsack-mysql
rspec-mysql 19 25: *rspec-knapsack-mysql
rspec-mysql 20 25: *rspec-knapsack-mysql
rspec-mysql 21 25: *rspec-knapsack-mysql
rspec-mysql 22 25: *rspec-knapsack-mysql
rspec-mysql 23 25: *rspec-knapsack-mysql
rspec-mysql 24 25: *rspec-knapsack-mysql
spinach pg 0 10: *spinach-knapsack-pg
spinach pg 1 10: *spinach-knapsack-pg
spinach pg 2 10: *spinach-knapsack-pg
spinach pg 3 10: *spinach-knapsack-pg
spinach pg 4 10: *spinach-knapsack-pg
spinach pg 5 10: *spinach-knapsack-pg
spinach pg 6 10: *spinach-knapsack-pg
spinach pg 7 10: *spinach-knapsack-pg
spinach pg 8 10: *spinach-knapsack-pg
spinach pg 9 10: *spinach-knapsack-pg
spinach-pg 0 5: *spinach-knapsack-pg
spinach-pg 1 5: *spinach-knapsack-pg
spinach-pg 2 5: *spinach-knapsack-pg
spinach-pg 3 5: *spinach-knapsack-pg
spinach-pg 4 5: *spinach-knapsack-pg
spinach mysql 0 10: *spinach-knapsack-mysql
spinach mysql 1 10: *spinach-knapsack-mysql
spinach mysql 2 10: *spinach-knapsack-mysql
spinach mysql 3 10: *spinach-knapsack-mysql
spinach mysql 4 10: *spinach-knapsack-mysql
spinach mysql 5 10: *spinach-knapsack-mysql
spinach mysql 6 10: *spinach-knapsack-mysql
spinach mysql 7 10: *spinach-knapsack-mysql
spinach mysql 8 10: *spinach-knapsack-mysql
spinach mysql 9 10: *spinach-knapsack-mysql
spinach-mysql 0 5: *spinach-knapsack-mysql
spinach-mysql 1 5: *spinach-knapsack-mysql
spinach-mysql 2 5: *spinach-knapsack-mysql
spinach-mysql 3 5: *spinach-knapsack-mysql
spinach-mysql 4 5: *spinach-knapsack-mysql
# Other generic tests
# Static analysis jobs
.ruby-static-analysis: &ruby-static-analysis
<<: *pull-cache
variables:
SIMPLECOV: "false"
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true"
.rake-exec: &rake-exec
<<: *ruby-static-analysis
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
stage: test
script:
- bundle exec rake $CI_JOB_NAME
@ -290,17 +318,15 @@ static-analysis:
<<: *except-docs
stage: test
script:
- rm -r node_modules
- yarn install --pure-lockfile
- scripts/static-analysis
# Documentation checks:
# - Check validity of relative links
# - Make sure cURL examples in API docs use the full switches
docs lint:
<<: *dedicated-runner
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine"
stage: test
<<: *dedicated-runner
cache: {}
dependencies: []
before_script: []
@ -319,7 +345,7 @@ downtime_check:
- master
- tags
- /^[\d-]+-stable(-ee)?$/
- /^docs\/*/
- /(^docs[\/-].*|.*-docs$)/
ee_compat_check:
<<: *rake-exec
@ -328,7 +354,7 @@ ee_compat_check:
except:
- master
- tags
- /^[\d-]+-stable(-ee)?$/
- /^[\d-]+-stable(-ee)?/
allow_failure: yes
cache:
key: "ee_compat_check_repo"
@ -341,67 +367,96 @@ ee_compat_check:
paths:
- ee_compat_check/patches/*.patch
# DB migration, rollback, and seed jobs
.db-migrate-reset: &db-migrate-reset
stage: test
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
stage: test
script:
- bundle exec rake db:migrate:reset
rake pg db:migrate:reset:
db:migrate:reset-pg:
<<: *db-migrate-reset
<<: *use-pg
rake mysql db:migrate:reset:
db:migrate:reset-mysql:
<<: *db-migrate-reset
<<: *use-mysql
.db-rollback: &db-rollback
.migration-paths: &migration-paths
<<: *dedicated-runner
<<: *only-canonical-masters
<<: *pull-cache
stage: test
variables:
SETUP_DB: "false"
script:
- git fetch origin v8.14.10
- 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
migration:path-mysql:
<<: *migration-paths
<<: *use-mysql
.db-rollback: &db-rollback
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
stage: test
script:
- bundle exec rake db:rollback STEP=120
- bundle exec rake db:migrate
rake pg db:rollback:
db:rollback-pg:
<<: *db-rollback
<<: *use-pg
rake mysql db:rollback:
db:rollback-mysql:
<<: *db-rollback
<<: *use-mysql
.db-seed_fu: &db-seed_fu
stage: test
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
stage: test
variables:
SIZE: "1"
SETUP_DB: "false"
RAILS_ENV: "development"
script:
- git clone https://gitlab.com/gitlab-org/gitlab-test.git
/home/git/repositories/gitlab-org/gitlab-test.git
- bundle exec rake db:setup db:seed_fu
- cp -R db/fixtures/development db/fixtures/test
- bundle exec rake db:seed_fu
artifacts:
when: on_failure
expire_in: 1d
paths:
- log/development.log
rake pg db:seed_fu:
db:seed_fu-pg:
<<: *db-seed_fu
<<: *use-pg
rake mysql db:seed_fu:
db:seed_fu-mysql:
<<: *db-seed_fu
<<: *use-mysql
rake gitlab:assets:compile:
stage: test
# Frontend-related jobs
gitlab:assets:compile:
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
stage: test
dependencies: []
variables:
NODE_ENV: "production"
@ -410,65 +465,63 @@ rake gitlab:assets:compile:
USE_DB: "false"
SKIP_STORAGE_VALIDATION: "true"
WEBPACK_REPORT: "true"
NO_COMPRESSION: "true"
script:
- bundle exec rake yarn:install gitlab:assets:compile
- yarn install --pure-lockfile --production --cache-folder .yarn-cache
- bundle exec rake gettext:po_to_json
- bundle exec rake gitlab:assets:compile
artifacts:
name: webpack-report
expire_in: 31d
paths:
- webpack-report/
- webpack-report/
rake karma:
stage: test
karma:
<<: *use-pg
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-chrome-59.0-node-7.1-postgresql-9.6"
stage: test
variables:
BABEL_ENV: "coverage"
CHROME_LOG_FILE: "chrome_debug.log"
script:
- rm -r node_modules
- yarn install --pure-lockfile
- scripts/gitaly-test-spawn
- bundle exec rake gettext:po_to_json
- bundle exec rake karma
coverage: '/^Statements *: (\d+\.\d+%)/'
artifacts:
name: coverage-javascript
expire_in: 31d
when: always
paths:
- chrome_debug.log
- coverage-javascript/
.migration-paths: &migration-paths
codeclimate:
<<: *except-docs
<<: *pull-cache
before_script: []
image: docker:latest
stage: test
<<: *dedicated-runner
variables:
SETUP_DB: "false"
only:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- master@gitlab/gitlabhq
- master@gitlab/gitlab-ee
DOCKER_DRIVER: overlay
services:
- docker:dind
script:
- git fetch origin v8.14.10
- git checkout -f FETCH_HEAD
- bundle install $BUNDLE_INSTALL_FLAGS
- 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 pg paths:
<<: *migration-paths
<<: *use-pg
migration mysql paths:
<<: *migration-paths
<<: *use-mysql
- docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json
- cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
artifacts:
paths: [codeclimate.json]
coverage:
stage: post-test
services: []
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
stage: post-test
services: []
variables:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true"
@ -485,7 +538,10 @@ coverage:
lint:javascript:report:
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
stage: post-test
dependencies:
- setup-test-env
before_script: []
script:
- find app/ spec/ -name '*.js' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
@ -496,33 +552,15 @@ lint:javascript:report:
paths:
- eslint-report.html
# Trigger docs build
# https://gitlab.com/gitlab-com/doc-gitlab-com/blob/master/README.md#deployment-process
trigger_docs:
stage: post-test
image: "alpine"
<<: *dedicated-runner
before_script:
- apk update && apk add curl
variables:
GIT_STRATEGY: "none"
cache: {}
artifacts: {}
script:
- "HTTP_STATUS=$(curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=${CI_PROJECT_NAME} --silent --output curl.log --write-out '%{http_code}' https://gitlab.com/api/v3/projects/1794617/trigger/builds)"
- if [ "${HTTP_STATUS}" -ne "201" ]; then echo "Error ${HTTP_STATUS}"; cat curl.log; echo; exit 1; fi
only:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
pages:
<<: *dedicated-runner
<<: *pull-cache
before_script: []
stage: pages
<<: *dedicated-runner
dependencies:
- coverage
- rake karma
- rake gitlab:assets:compile
- karma
- gitlab:assets:compile
- lint:javascript:report
script:
- mv public/ .public/
@ -542,6 +580,7 @@ pages:
# rubygems.org in the future.
cache gems:
<<: *dedicated-runner
<<: *pull-cache
only:
- tags
variables:
@ -554,3 +593,11 @@ cache gems:
only:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
gitlab_git_test:
<<: *pull-cache
<<: *except-docs
variables:
SETUP_DB: "false"
script:
- spec/support/prepare-gitlab-git-test-for-commit --check-for-changes

View file

@ -1,11 +1,18 @@
Please read this!
Before opening a new issue, make sure to search for keywords in the issues
filtered by the "regression" or "bug" label:
filtered by the "regression" or "bug" label.
For the Community Edition issue tracker:
- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=regression
- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=bug
For the Enterprise Edition issue tracker:
- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=regression
- 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.
@ -20,6 +27,12 @@ Please remove this notice if you're confident your issue isn't a duplicate.
(How one can reproduce the issue - this is very important)
### Example Project
(If possible, please create an example project here on GitLab.com that exhibits the problematic behaviour, and link to it here in the bug report)
(If you are using an older version of GitLab, this will also determine whether the bug has been fixed in a more recent version)
### What is the current *bug* behavior?
(What actually happens)
@ -40,6 +53,7 @@ logs, and code as it's very hard to read otherwise.)
#### Results of GitLab environment info
<details>
<summary>Expand for output related to GitLab environment info</summary>
<pre>
(For installations with omnibus-gitlab package run and paste the output of:
@ -54,6 +68,7 @@ logs, and code as it's very hard to read otherwise.)
#### Results of GitLab application Check
<details>
<summary>Expand for output related to the GitLab application check</summary>
<pre>
(For installations with omnibus-gitlab package run and paste the output of:

View file

@ -3,8 +3,14 @@ Please read this!
Before opening a new issue, make sure to search for keywords in the issues
filtered by the "feature proposal" label:
For the Community Edition issue tracker:
- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=feature+proposal
For the Enterprise Edition issue tracker:
- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=feature+proposal
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.
@ -21,12 +27,24 @@ Please remove this notice if you're confident your issue isn't a duplicate.
### Documentation blurb
(Write the start of the documentation of this feature here, include:
#### Overview
1. Why should someone use it; what's the underlying problem.
2. What is the solution.
3. How does someone use this
What is it?
Why should someone use this feature?
What is the underlying (business) problem?
How do you use this feature?
During implementation, this can then be copied and used as a starter for the documentation.)
#### Use cases
/label ~"feature proposal"
Who is this for? Provide one or more use cases.
### Feature checklist
Make sure these are completed before closing the issue,
with a link to the relevant commit.
- [ ] [Feature assurance](https://about.gitlab.com/handbook/product/#feature-assurance)
- [ ] Documentation
- [ ] Added to [features.yml](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml)
/label ~"feature proposal"

View file

@ -35,9 +35,21 @@ linters:
HtmlAttributes:
enabled: true
IdNames:
enabled: false
ImplicitDiv:
enabled: true
InlineJavaScript:
enabled: true
InlineStyles:
enabled: false
InstanceVariables:
enabled: false
LeadingCommentSpace:
enabled: false
@ -54,6 +66,9 @@ linters:
ObjectReferenceAttributes:
enabled: true
RepeatedId:
enabled: false
RuboCop:
enabled: false
# These cops are incredibly noisy when it comes to HAML templates, so we
@ -101,3 +116,6 @@ linters:
UnnecessaryStringOutput:
enabled: true
ViewLength:
enabled: false

1
.nvmrc Normal file
View file

@ -0,0 +1 @@
7.5

2
.rspec
View file

@ -1,2 +0,0 @@
--color
--format Fuubar

View file

@ -6,6 +6,7 @@ inherit_from: .rubocop_todo.yml
AllCops:
TargetRubyVersion: 2.3
TargetRailsVersion: 4.2
# Cop names are not d§splayed in offense messages by default. Change behavior
# by overriding DisplayCopNames, or by giving the -D/--display-cop-names
# option.
@ -29,12 +30,221 @@ AllCops:
Bundler/OrderedGems:
Enabled: false
# Style #######################################################################
# Layout ######################################################################
# Check indentation of private/protected visibility modifiers.
Style/AccessModifierIndentation:
Layout/AccessModifierIndentation:
Enabled: true
# Align the elements of an array literal if they span more than one line.
Layout/AlignArray:
Enabled: true
# Align the elements of a hash literal if they span more than one line.
Layout/AlignHash:
Enabled: true
# Here we check if the parameters on a multi-line method call or
# definition are aligned.
Layout/AlignParameters:
Enabled: false
# Put end statement of multiline block on its own line.
Layout/BlockEndNewline:
Enabled: true
# Indentation of when in a case/when/[else/]end.
Layout/CaseIndentation:
Enabled: true
# Indentation of comments.
Layout/CommentIndentation:
Enabled: true
# Multi-line method chaining should be done with leading dots.
Layout/DotPosition:
Enabled: true
EnforcedStyle: leading
# Align elses and elsifs correctly.
Layout/ElseAlignment:
Enabled: true
# Add an empty line after magic comments to separate them from code.
Layout/EmptyLineAfterMagicComment:
Enabled: false
# Use empty lines between defs.
Layout/EmptyLineBetweenDefs:
Enabled: true
# Don't use several empty lines in a row.
Layout/EmptyLines:
Enabled: true
# Keep blank lines around access modifiers.
Layout/EmptyLinesAroundAccessModifier:
Enabled: true
# Keeps track of empty lines around block bodies.
Layout/EmptyLinesAroundBlockBody:
Enabled: true
# Keeps track of empty lines around class bodies.
Layout/EmptyLinesAroundClassBody:
Enabled: true
# Keeps track of empty lines around exception handling keywords.
Layout/EmptyLinesAroundExceptionHandlingKeywords:
Enabled: false
# Keeps track of empty lines around method bodies.
Layout/EmptyLinesAroundMethodBody:
Enabled: true
# Keeps track of empty lines around module bodies.
Layout/EmptyLinesAroundModuleBody:
Enabled: true
# Use Unix-style line endings.
Layout/EndOfLine:
Enabled: true
# Checks for a line break before the first parameter in a multi-line method
# parameter definition.
Layout/FirstMethodParameterLineBreak:
Enabled: true
# Keep indentation straight.
Layout/IndentationConsistency:
Enabled: true
# Use 2 spaces for indentation.
Layout/IndentationWidth:
Enabled: true
# Checks the indentation of the first line of the right-hand-side of a
# multi-line assignment.
Layout/IndentAssignment:
Enabled: true
# This cops checks the indentation of the here document bodies.
Layout/IndentHeredoc:
Enabled: false
# Comments should start with a space.
Layout/LeadingCommentSpace:
Enabled: true
# Checks that the closing brace in an array literal is either on the same line
# as the last array element, or a new line.
Layout/MultilineArrayBraceLayout:
Enabled: true
EnforcedStyle: symmetrical
# Ensures newlines after multiline block do statements.
Layout/MultilineBlockLayout:
Enabled: true
# Checks that the closing brace in a hash literal is either on the same line as
# the last hash element, or a new line.
Layout/MultilineHashBraceLayout:
Enabled: true
EnforcedStyle: symmetrical
# Checks that the closing brace in a method call is either on the same line as
# the last method argument, or a new line.
Layout/MultilineMethodCallBraceLayout:
Enabled: false
EnforcedStyle: symmetrical
# Checks indentation of method calls with the dot operator that span more than
# one line.
Layout/MultilineMethodCallIndentation:
Enabled: false
# Checks that the closing brace in a method definition is symmetrical with
# respect to the opening brace and the method parameters.
Layout/MultilineMethodDefinitionBraceLayout:
Enabled: false
# Checks indentation of binary operations that span more than one line.
Layout/MultilineOperationIndentation:
Enabled: true
EnforcedStyle: indented
# Use spaces after colons.
Layout/SpaceAfterColon:
Enabled: true
# Use spaces after commas.
Layout/SpaceAfterComma:
Enabled: true
# Do not put a space between a method name and the opening parenthesis in a
# method definition.
Layout/SpaceAfterMethodName:
Enabled: true
# Tracks redundant space after the ! operator.
Layout/SpaceAfterNot:
Enabled: true
# Use spaces after semicolons.
Layout/SpaceAfterSemicolon:
Enabled: true
# Use space around equals in parameter default
Layout/SpaceAroundEqualsInParameterDefault:
Enabled: true
# Use a space around keywords if appropriate.
Layout/SpaceAroundKeyword:
Enabled: true
# Use a single space around operators.
Layout/SpaceAroundOperators:
Enabled: true
# No spaces before commas.
Layout/SpaceBeforeComma:
Enabled: true
# Checks for missing space between code and a comment on the same line.
Layout/SpaceBeforeComment:
Enabled: true
# No spaces before semicolons.
Layout/SpaceBeforeSemicolon:
Enabled: true
# Checks for spaces inside square brackets.
Layout/SpaceInsideBrackets:
Enabled: true
# Use spaces inside hash literal braces - or don't.
Layout/SpaceInsideHashLiteralBraces:
Enabled: true
# No spaces inside range literals.
Layout/SpaceInsideRangeLiteral:
Enabled: true
# Checks for padding/surrounding spaces inside string interpolation.
Layout/SpaceInsideStringInterpolation:
EnforcedStyle: no_space
Enabled: true
# No hard tabs.
Layout/Tab:
Enabled: true
# Checks trailing blank lines and final newline.
Layout/TrailingBlankLines:
Enabled: true
# Style #######################################################################
# Check the naming of accessor methods for get_/set_.
Style/AccessorMethodName:
Enabled: false
@ -44,19 +254,6 @@ Style/Alias:
EnforcedStyle: prefer_alias_method
Enabled: true
# Align the elements of an array literal if they span more than one line.
Style/AlignArray:
Enabled: true
# Align the elements of a hash literal if they span more than one line.
Style/AlignHash:
Enabled: true
# Here we check if the parameters on a multi-line method call or
# definition are aligned.
Style/AlignParameters:
Enabled: false
# Whether `and` and `or` are banned only in conditionals (conditionals)
# or completely (always).
Style/AndOr:
@ -91,10 +288,6 @@ Style/BlockComments:
Style/BlockDelimiters:
Enabled: true
# Put end statement of multiline block on its own line.
Style/BlockEndNewline:
Enabled: true
# This cop checks for braces around the last parameter in a method call
# if the last parameter is a hash.
Style/BracesAroundHashParameters:
@ -104,10 +297,6 @@ Style/BracesAroundHashParameters:
Style/CaseEquality:
Enabled: false
# Indentation of when in a case/when/[else/]end.
Style/CaseIndentation:
Enabled: true
# Checks for uses of character literals.
Style/CharacterLiteral:
Enabled: true
@ -142,10 +331,6 @@ Style/ColonMethodCall:
Style/CommentAnnotation:
Enabled: false
# Indentation of comments.
Style/CommentIndentation:
Enabled: true
# Check for `if` and `case` statements where each branch is used for
# assignment to the same variable when using the return of the
# condition can be used instead.
@ -170,46 +355,10 @@ Style/Documentation:
Style/DoubleNegation:
Enabled: false
# Align elses and elsifs correctly.
Style/ElseAlignment:
Enabled: true
# Use empty lines between defs.
Style/EmptyLineBetweenDefs:
Enabled: true
# Don't use several empty lines in a row.
Style/EmptyLines:
Enabled: true
# Keep blank lines around access modifiers.
Style/EmptyLinesAroundAccessModifier:
Enabled: true
# Keeps track of empty lines around block bodies.
Style/EmptyLinesAroundBlockBody:
Enabled: true
# Keeps track of empty lines around class bodies.
Style/EmptyLinesAroundClassBody:
Enabled: true
# Keeps track of empty lines around method bodies.
Style/EmptyLinesAroundMethodBody:
Enabled: true
# Keeps track of empty lines around module bodies.
Style/EmptyLinesAroundModuleBody:
Enabled: true
# Avoid the use of END blocks.
Style/EndBlock:
Enabled: true
# Use Unix-style line endings.
Style/EndOfLine:
Enabled: true
# Favor the use of Fixnum#even? && Fixnum#odd?
Style/EvenOdd:
Enabled: true
@ -218,11 +367,6 @@ Style/EvenOdd:
Style/FileName:
Enabled: true
# Checks for a line break before the first parameter in a multi-line method
# parameter definition.
Style/FirstMethodParameterLineBreak:
Enabled: true
# Checks for flip flops.
Style/FlipFlop:
Enabled: true
@ -231,6 +375,10 @@ Style/FlipFlop:
Style/For:
Enabled: true
# Use a consistent style for format string tokens.
Style/FormatStringToken:
Enabled: false
# Checks if there is a magic comment to enforce string literals
Style/FrozenStringLiteralComment:
Enabled: false
@ -256,31 +404,19 @@ Style/IdenticalConditionalBranches:
Style/IfWithSemicolon:
Enabled: true
# Checks the indentation of the first line of the right-hand-side of a
# multi-line assignment.
Style/IndentAssignment:
Enabled: true
# Keep indentation straight.
Style/IndentationConsistency:
Enabled: true
# Use 2 spaces for indentation.
Style/IndentationWidth:
Enabled: true
# Use Kernel#loop for infinite loops.
Style/InfiniteLoop:
Enabled: true
# Use the inverse method instead of `!.method`
# if an inverse method is defined.
Style/InverseMethods:
Enabled: false
# Use lambda.call(...) instead of lambda.(...).
Style/LambdaCall:
Enabled: true
# Comments should start with a space.
Style/LeadingCommentSpace:
Enabled: true
# Checks if the method definitions have or don't have parentheses.
Style/MethodDefParentheses:
Enabled: true
@ -293,55 +429,23 @@ Style/MethodName:
Style/ModuleFunction:
Enabled: false
# Checks that the closing brace in an array literal is either on the same line
# as the last array element, or a new line.
Style/MultilineArrayBraceLayout:
Enabled: true
EnforcedStyle: symmetrical
# Avoid multi-line chains of blocks.
Style/MultilineBlockChain:
Enabled: true
# Ensures newlines after multiline block do statements.
Style/MultilineBlockLayout:
Enabled: true
# Checks that the closing brace in a hash literal is either on the same line as
# the last hash element, or a new line.
Style/MultilineHashBraceLayout:
Enabled: true
EnforcedStyle: symmetrical
# Do not use then for multi-line if/unless.
Style/MultilineIfThen:
Enabled: true
# Checks that the closing brace in a method call is either on the same line as
# the last method argument, or a new line.
Style/MultilineMethodCallBraceLayout:
Enabled: false
EnforcedStyle: symmetrical
# Checks indentation of method calls with the dot operator that span more than
# one line.
Style/MultilineMethodCallIndentation:
Enabled: false
# Checks that the closing brace in a method definition is symmetrical with
# respect to the opening brace and the method parameters.
Style/MultilineMethodDefinitionBraceLayout:
Enabled: false
# Checks indentation of binary operations that span more than one line.
Style/MultilineOperationIndentation:
Enabled: true
EnforcedStyle: indented
# Avoid multi-line `? :` (the ternary operator), use if/unless instead.
Style/MultilineTernaryOperator:
Enabled: true
# Avoid comparing a variable with multiple items in a conditional,
# use Array#include? instead.
Style/MultipleComparison:
Enabled: false
# This cop checks whether some constant value isn't a
# mutable literal (e.g. array or hash).
Style/MutableConstant:
@ -390,6 +494,15 @@ Style/OpMethod:
Style/ParenthesesAroundCondition:
Enabled: true
# This cop (by default) checks for uses of methods Hash#has_key? and
# Hash#has_value? where it enforces Hash#key? and Hash#value?
# It is configurable to enforce the inverse, using `verbose` method
# names also.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: short, verbose
Style/PreferredHashMethods:
Enabled: false
# Checks for an obsolete RuntimeException argument in raise/fail.
Style/RedundantException:
Enabled: true
@ -407,68 +520,6 @@ Style/SignalException:
EnforcedStyle: only_raise
Enabled: true
# Use spaces after colons.
Style/SpaceAfterColon:
Enabled: true
# Use spaces after commas.
Style/SpaceAfterComma:
Enabled: true
# Do not put a space between a method name and the opening parenthesis in a
# method definition.
Style/SpaceAfterMethodName:
Enabled: true
# Tracks redundant space after the ! operator.
Style/SpaceAfterNot:
Enabled: true
# Use spaces after semicolons.
Style/SpaceAfterSemicolon:
Enabled: true
# Use space around equals in parameter default
Style/SpaceAroundEqualsInParameterDefault:
Enabled: true
# Use a space around keywords if appropriate.
Style/SpaceAroundKeyword:
Enabled: true
# Use a single space around operators.
Style/SpaceAroundOperators:
Enabled: true
# No spaces before commas.
Style/SpaceBeforeComma:
Enabled: true
# Checks for missing space between code and a comment on the same line.
Style/SpaceBeforeComment:
Enabled: true
# No spaces before semicolons.
Style/SpaceBeforeSemicolon:
Enabled: true
# Checks for spaces inside square brackets.
Style/SpaceInsideBrackets:
Enabled: true
# Use spaces inside hash literal braces - or don't.
Style/SpaceInsideHashLiteralBraces:
Enabled: true
# No spaces inside range literals.
Style/SpaceInsideRangeLiteral:
Enabled: true
# Checks for padding/surrounding spaces inside string interpolation.
Style/SpaceInsideStringInterpolation:
EnforcedStyle: no_space
Enabled: true
# Check for the usage of parentheses around stabby lambda arguments.
Style/StabbyLambdaParentheses:
EnforcedStyle: require_parentheses
@ -484,17 +535,19 @@ Style/StringMethods:
intern: to_sym
Enabled: true
# No hard tabs.
Style/Tab:
Enabled: true
# Checks trailing blank lines and final newline.
Style/TrailingBlankLines:
Enabled: true
# Use %i or %I for arrays of symbols.
Style/SymbolArray:
Enabled: false
# This cop checks for trailing comma in array and hash literals.
Style/TrailingCommaInLiteral:
Enabled: false
Enabled: true
EnforcedStyleForMultiline: no_comma
# This cop checks for trailing comma in argument lists.
Style/TrailingCommaInArguments:
Enabled: true
EnforcedStyleForMultiline: no_comma
# Checks for %W when interpolation is not needed.
Style/UnneededCapitalW:
@ -533,6 +586,10 @@ Style/WhileUntilModifier:
Style/WordArray:
Enabled: true
# Do not use literals as the first operand of a comparison.
Style/YodaCondition:
Enabled: false
# Use `proc` instead of `Proc.new`.
Style/Proc:
Enabled: true
@ -543,7 +600,7 @@ Style/Proc:
# branches, and conditions.
Metrics/AbcSize:
Enabled: true
Max: 57.08
Max: 56.96
# This cop checks if the length of a block exceeds some maximum value.
Metrics/BlockLength:
@ -588,6 +645,11 @@ Metrics/PerceivedComplexity:
# Lint ########################################################################
# Checks for ambiguous block association with method when param passed without
# parentheses.
Lint/AmbiguousBlockAssociation:
Enabled: false
# Checks for ambiguous operators in the first argument of a method invocation
# without parentheses.
Lint/AmbiguousOperator:
@ -789,6 +851,10 @@ Lint/Void:
# Performance #################################################################
# Use `caller(n..n)` instead of `caller`.
Performance/Caller:
Enabled: false
# Use `casecmp` rather than `downcase ==`.
Performance/Casecmp:
Enabled: true
@ -863,14 +929,23 @@ Rails/ActionFilter:
Enabled: true
EnforcedStyle: action
# Check that models subclass ApplicationRecord.
Rails/ApplicationRecord:
Enabled: false
# Enforce using `blank?` and `present?`.
Rails/Blank:
Enabled: false
# Checks the correct usage of date aware methods, such as `Date.today`,
# `Date.current`, etc.
Rails/Date:
Enabled: false
# Prefer delegate method for delegations.
# Disabled per https://gitlab.com/gitlab-org/gitlab-ce/issues/35869
Rails/Delegate:
Enabled: true
Enabled: false
# This cop checks dynamic `find_by_*` methods.
Rails/DynamicFindBy:
@ -919,10 +994,18 @@ Rails/OutputSafety:
Rails/PluralizationGrammar:
Enabled: true
# Enforce using `blank?` and `present?`.
Rails/Present:
Enabled: false
# Checks for `read_attribute(:attr)` and `write_attribute(:attr, val)`.
Rails/ReadWriteAttribute:
Enabled: false
# Do not assign relative date to constants.
Rails/RelativeDateConstant:
Enabled: false
# Checks the arguments of ActiveRecord scopes.
Rails/ScopeArgs:
Enabled: true
@ -945,6 +1028,10 @@ RSpec/AnyInstance:
RSpec/BeEql:
Enabled: true
# We don't enforce this as we use this technique in a few places.
RSpec/BeforeAfterAll:
Enabled: false
# Check that the first argument to the top level describe is the tested class or
# module.
RSpec/DescribeClass:
@ -963,6 +1050,12 @@ RSpec/DescribeSymbol:
RSpec/DescribedClass:
Enabled: true
# Checks if an example group does not include any tests.
RSpec/EmptyExampleGroup:
Enabled: true
CustomIncludeMethods:
- run_permission_checks
# Checks for long example.
RSpec/ExampleLength:
Enabled: false
@ -981,6 +1074,10 @@ RSpec/ExampleWording:
RSpec/ExpectActual:
Enabled: true
# Checks for opportunities to use `expect { … }.to output`.
RSpec/ExpectOutput:
Enabled: true
# Checks the file and folder naming of the spec file.
RSpec/FilePath:
Enabled: true
@ -994,6 +1091,12 @@ RSpec/FilePath:
RSpec/Focus:
Enabled: true
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: is_expected, should
RSpec/ImplicitExpect:
Enabled: true
EnforcedStyle: is_expected
# Checks for the usage of instance variables.
RSpec/InstanceVariable:
Enabled: false
@ -1039,6 +1142,13 @@ RSpec/NotToNot:
RSpec/RepeatedDescription:
Enabled: false
# Ensure RSpec hook blocks are always multi-line.
RSpec/SingleLineHook:
Enabled: true
Exclude:
- 'spec/factories/*'
- 'spec/requests/api/v3/*'
# Checks for stubbed test subjects.
RSpec/SubjectStub:
Enabled: false

View file

@ -1,45 +1,89 @@
# This configuration was generated by
# `rubocop --auto-gen-config --exclude-limit 0`
# on 2017-04-07 20:17:35 -0400 using RuboCop version 0.47.1.
# on 2017-07-10 01:48:30 +0900 using RuboCop version 0.49.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 54
RSpec/BeforeAfterAll:
# Offense count: 181
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
Layout/ExtraSpacing:
Enabled: false
# Offense count: 15
# Configuration parameters: CustomIncludeMethods.
RSpec/EmptyExampleGroup:
# Offense count: 119
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_brackets
Layout/IndentArray:
Enabled: false
# Offense count: 233
# Offense count: 208
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_braces
Layout/IndentHash:
Enabled: false
# Offense count: 174
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: space, no_space
Layout/SpaceBeforeBlockBraces:
Enabled: false
# Offense count: 8
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment.
Layout/SpaceBeforeFirstArg:
Enabled: false
# Offense count: 64
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: require_no_space, require_space
Layout/SpaceInLambdaLiteral:
Enabled: false
# Offense count: 256
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SupportedStylesForEmptyBraces, SpaceBeforeBlockParameters.
# SupportedStyles: space, no_space
# SupportedStylesForEmptyBraces: space, no_space
Layout/SpaceInsideBlockBraces:
Enabled: false
# Offense count: 135
# Cop supports --auto-correct.
Layout/SpaceInsideParens:
Enabled: false
# Offense count: 14
# Cop supports --auto-correct.
Layout/SpaceInsidePercentLiteralDelimiters:
Enabled: false
# Offense count: 89
# Cop supports --auto-correct.
Layout/TrailingWhitespace:
Enabled: false
# Offense count: 272
RSpec/EmptyLineAfterFinalLet:
Enabled: false
# Offense count: 167
# Offense count: 181
RSpec/EmptyLineAfterSubject:
Enabled: false
# Offense count: 3
RSpec/ExpectOutput:
Enabled: false
# Offense count: 72
# Offense count: 78
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: implicit, each, example
RSpec/HookArgument:
Enabled: false
# Offense count: 12
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: is_expected, should
RSpec/ImplicitExpect:
Enabled: false
# Offense count: 11
# Offense count: 9
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: it_behaves_like, it_should_behave_like
RSpec/ItBehavesLike:
@ -49,19 +93,19 @@ RSpec/ItBehavesLike:
RSpec/IteratedExpectation:
Enabled: false
# Offense count: 3
# Offense count: 2
RSpec/OverwritingSetup:
Enabled: false
# Offense count: 34
# Offense count: 36
RSpec/RepeatedExample:
Enabled: false
# Offense count: 43
# Offense count: 86
RSpec/ScatteredLet:
Enabled: false
# Offense count: 32
# Offense count: 20
RSpec/ScatteredSetup:
Enabled: false
@ -69,7 +113,7 @@ RSpec/ScatteredSetup:
RSpec/SharedContext:
Enabled: false
# Offense count: 150
# Offense count: 115
Rails/FilePath:
Enabled: false
@ -79,97 +123,71 @@ Rails/FilePath:
Rails/ReversibleMigration:
Enabled: false
# Offense count: 302
# Offense count: 336
# Configuration parameters: Blacklist.
# Blacklist: decrement!, decrement_counter, increment!, increment_counter, toggle!, touch, update_all, update_attribute, update_column, update_columns, update_counters
Rails/SkipsModelValidations:
Enabled: false
# Offense count: 7
# Offense count: 11
# Cop supports --auto-correct.
Security/YAMLLoad:
Enabled: false
# Offense count: 59
# Offense count: 58
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: percent_q, bare_percent
Style/BarePercentLiterals:
Enabled: false
# Offense count: 1403
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: leading, trailing
Style/DotPosition:
Enabled: false
# Offense count: 5
# Offense count: 6
# Cop supports --auto-correct.
Style/EachWithObject:
Enabled: false
# Offense count: 28
# Offense count: 31
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: empty, nil, both
Style/EmptyElse:
Enabled: false
# Offense count: 4
# Offense count: 9
# Cop supports --auto-correct.
Style/EmptyLiteral:
Enabled: false
# Offense count: 59
# Offense count: 78
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: compact, expanded
Style/EmptyMethod:
Enabled: false
# Offense count: 214
# Offense count: 23
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
Style/ExtraSpacing:
Enabled: false
# Offense count: 9
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: format, sprintf, percent
Style/FormatString:
Enabled: false
# Offense count: 285
# Offense count: 301
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Enabled: false
# Offense count: 16
# Offense count: 18
Style/IfInsideElse:
Enabled: false
# Offense count: 186
# Offense count: 182
# Cop supports --auto-correct.
# Configuration parameters: MaxLineLength.
Style/IfUnlessModifier:
Enabled: false
# Offense count: 99
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_brackets
Style/IndentArray:
Enabled: false
# Offense count: 160
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_braces
Style/IndentHash:
Enabled: false
# Offense count: 50
# Offense count: 52
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: line_count_dependent, lambda, literal
@ -181,63 +199,63 @@ Style/Lambda:
Style/LineEndConcatenation:
Enabled: false
# Offense count: 34
# Offense count: 40
# Cop supports --auto-correct.
Style/MethodCallWithoutArgsParentheses:
Enabled: false
# Offense count: 10
# Offense count: 13
Style/MethodMissing:
Enabled: false
# Offense count: 3
# Offense count: 6
# Cop supports --auto-correct.
Style/MultilineIfModifier:
Enabled: false
# Offense count: 24
# Offense count: 26
# Cop supports --auto-correct.
Style/NestedParenthesizedCalls:
Enabled: false
# Offense count: 18
# Offense count: 20
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
# SupportedStyles: skip_modifier_ifs, always
Style/Next:
Enabled: false
# Offense count: 37
# Offense count: 45
# Cop supports --auto-correct.
# Configuration parameters: EnforcedOctalStyle, SupportedOctalStyles.
# SupportedOctalStyles: zero_with_o, zero_only
Style/NumericLiteralPrefix:
Enabled: false
# Offense count: 88
# Offense count: 98
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles.
# SupportedStyles: predicate, comparison
Style/NumericPredicate:
Enabled: false
# Offense count: 36
# Offense count: 42
# Cop supports --auto-correct.
Style/ParallelAssignment:
Enabled: false
# Offense count: 570
# Offense count: 800
# Cop supports --auto-correct.
# Configuration parameters: PreferredDelimiters.
Style/PercentLiteralDelimiters:
Enabled: false
# Offense count: 14
# Offense count: 15
# Cop supports --auto-correct.
Style/PerlBackrefs:
Enabled: false
# Offense count: 83
# Offense count: 105
# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
# NamePrefix: is_, has_, have_
# NamePrefixBlacklist: is_, has_, have_
@ -245,54 +263,47 @@ Style/PerlBackrefs:
Style/PredicateName:
Enabled: false
# Offense count: 45
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: short, verbose
Style/PreferredHashMethods:
Enabled: false
# Offense count: 65
# Offense count: 58
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: compact, exploded
Style/RaiseArgs:
Enabled: false
# Offense count: 5
# Offense count: 6
# Cop supports --auto-correct.
Style/RedundantBegin:
Enabled: false
# Offense count: 32
# Offense count: 37
# Cop supports --auto-correct.
Style/RedundantFreeze:
Enabled: false
# Offense count: 15
# Offense count: 14
# Cop supports --auto-correct.
# Configuration parameters: AllowMultipleReturnValues.
Style/RedundantReturn:
Enabled: false
# Offense count: 382
# Offense count: 406
# Cop supports --auto-correct.
Style/RedundantSelf:
Enabled: false
# Offense count: 111
# Offense count: 115
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral:
Enabled: false
# Offense count: 24
# Offense count: 29
# Cop supports --auto-correct.
Style/RescueModifier:
Enabled: false
# Offense count: 7
# Offense count: 8
# Cop supports --auto-correct.
Style/SelfAssignment:
Enabled: false
@ -303,108 +314,58 @@ Style/SelfAssignment:
Style/SingleLineMethods:
Enabled: false
# Offense count: 168
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: space, no_space
Style/SpaceBeforeBlockBraces:
Enabled: false
# Offense count: 8
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment.
Style/SpaceBeforeFirstArg:
Enabled: false
# Offense count: 46
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: require_no_space, require_space
Style/SpaceInLambdaLiteral:
Enabled: false
# Offense count: 229
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SupportedStylesForEmptyBraces, SpaceBeforeBlockParameters.
# SupportedStyles: space, no_space
# SupportedStylesForEmptyBraces: space, no_space
Style/SpaceInsideBlockBraces:
Enabled: false
# Offense count: 116
# Cop supports --auto-correct.
Style/SpaceInsideParens:
Enabled: false
# Offense count: 12
# Cop supports --auto-correct.
Style/SpaceInsidePercentLiteralDelimiters:
Enabled: false
# Offense count: 57
# Offense count: 64
# Cop supports --auto-correct.
# Configuration parameters: SupportedStyles.
# SupportedStyles: use_perl_names, use_english_names
Style/SpecialGlobalVars:
EnforcedStyle: use_perl_names
# Offense count: 42
# Offense count: 44
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiteralsInInterpolation:
Enabled: false
# Offense count: 64
# Offense count: 84
# Cop supports --auto-correct.
# Configuration parameters: IgnoredMethods.
# IgnoredMethods: respond_to, define_method
Style/SymbolProc:
Enabled: false
# Offense count: 6
# Offense count: 8
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment.
# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex
Style/TernaryParentheses:
Enabled: false
# Offense count: 53
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyleForMultiline, SupportedStylesForMultiline.
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
Style/TrailingCommaInArguments:
Enabled: false
# Offense count: 18
# Offense count: 17
# Cop supports --auto-correct.
# Configuration parameters: AllowNamedUnderscoreVariables.
Style/TrailingUnderscoreVariable:
Enabled: false
# Offense count: 78
# Cop supports --auto-correct.
Style/TrailingWhitespace:
Enabled: false
# Offense count: 3
# Offense count: 4
# Cop supports --auto-correct.
# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist.
# Whitelist: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym
Style/TrivialAccessors:
Enabled: false
# Offense count: 6
# Offense count: 5
# Cop supports --auto-correct.
Style/UnlessElse:
Enabled: false
# Offense count: 24
# Offense count: 28
# Cop supports --auto-correct.
Style/UnneededInterpolation:
Enabled: false
# Offense count: 8
# Offense count: 11
# Cop supports --auto-correct.
Style/ZeroLengthPredicate:
Enabled: false

View file

@ -10,12 +10,12 @@ linters:
# Reports when you use improper spacing around ! (the "bang") in !default,
# !global, !important, and !optional flags.
BangFormat:
enabled: false
enabled: true
# Whether or not to prefer `border: 0` over `border: none`.
BorderZero:
enabled: false
# Reports when you define a rule set using a selector with chained classes
# (a.k.a. adjoining classes).
ChainedClasses:
@ -25,13 +25,13 @@ linters:
# (e.g. `color: green` is a color keyword)
ColorKeyword:
enabled: false
# Prefer color literals (keywords or hexadecimal codes) to be used only in
# variable declarations. They should be referred to via variables everywhere
# else.
ColorVariable:
enabled: true
# Which form of comments to prefer in CSS.
Comment:
enabled: false
@ -39,14 +39,15 @@ linters:
# Reports @debug statements (which you probably left behind accidentally).
DebugStatement:
enabled: false
# Rule sets should be ordered as follows:
# - @extend declarations
# - @include declarations without inner @content
# - properties, @include declarations with inner @content
# - properties
# - @include declarations with inner @content
# - nested rule sets.
DeclarationOrder:
enabled: false
enabled: true
# `scss-lint:disable` control comments should be preceded by a comment
# explaining why these linters are being disabled for this file.
@ -54,19 +55,19 @@ linters:
# more information.
DisableLinterReason:
enabled: true
# Reports when you define the same property twice in a single rule set.
DuplicateProperty:
enabled: false
enabled: true
# Separate rule, function, and mixin declarations with empty lines.
EmptyLineBetweenBlocks:
enabled: true
# Reports when you have an empty rule set.
EmptyRule:
enabled: true
# Reports when you have an @extend directive.
ExtendDirective:
enabled: false
@ -75,49 +76,49 @@ linters:
# when adding lines to the file, since SCM systems such as git won't
# think that you touched the last line.
FinalNewline:
enabled: false
enabled: true
# HEX colors should use three-character values where possible.
HexLength:
enabled: false
# HEX color values should use lower-case colors to differentiate between
# letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`.
HexNotation:
enabled: true
# Avoid using ID selectors.
IdSelector:
enabled: false
# The basenames of @imported SCSS partials should not begin with an
# underscore and should not include the filename extension.
ImportPath:
enabled: false
enabled: true
# Avoid using !important in properties. It is usually indicative of a
# misunderstanding of CSS specificity and can lead to brittle code.
ImportantRule:
enabled: false
# Indentation should always be done in increments of 2 spaces.
Indentation:
enabled: true
width: 2
# Don't write leading zeros for numeric values with a decimal point.
LeadingZero:
enabled: false
# Reports when you define the same selector twice in a single sheet.
MergeableSelector:
enabled: false
# Functions, mixins, variables, and placeholders should be declared
# with all lowercase letters and hyphens instead of underscores.
NameFormat:
enabled: false
# Avoid nesting selectors too deeply.
NestingDepth:
enabled: false
@ -129,12 +130,12 @@ linters:
# Sort properties in a strict order.
PropertySortOrder:
enabled: false
# Reports when you use an unknown or disabled CSS property
# (ignoring vendor-prefixed properties).
PropertySpelling:
enabled: false
enabled: true
# Configure which units are allowed for property values.
PropertyUnits:
enabled: false
@ -144,25 +145,25 @@ linters:
# be declared with one colon.
PseudoElement:
enabled: true
# Avoid qualifying elements in selectors (also known as "tag-qualifying").
QualifyingElement:
enabled: false
# Don't write selectors with a depth of applicability greater than 3.
SelectorDepth:
enabled: false
# Selectors should always use hyphenated-lowercase, rather than camelCase or
# snake_case.
SelectorFormat:
enabled: false
convention: hyphenated_lowercase
# Prefer the shortest shorthand form possible for properties that support it.
Shorthand:
enabled: true
# Each property should have its own line, except in the special case of
# single line rulesets.
SingleLinePerProperty:
@ -173,11 +174,15 @@ linters:
# individual selector occupy a single line.
SingleLinePerSelector:
enabled: true
# Commas in lists should be followed by a space.
SpaceAfterComma:
enabled: true
# Comment literals should be followed by a space.
SpaceAfterComment:
enabled: false
# Properties should be formatted with a single space separating the colon
# from the property's value.
SpaceAfterPropertyColon:
@ -197,12 +202,12 @@ linters:
# colon.
SpaceAfterVariableName:
enabled: false
# Operators should be formatted with a single space on both sides of an
# infix operator.
SpaceAroundOperator:
enabled: true
# Opening braces should be preceded by a single space.
SpaceBeforeBrace:
enabled: true
@ -210,7 +215,7 @@ linters:
# Parentheses should not be padded with spaces.
SpaceBetweenParens:
enabled: false
# Enforces that string literals should be written with a consistent form
# of quotes (single or double).
StringQuotes:
@ -240,8 +245,8 @@ linters:
# Do not use parent selector references (&) when they would otherwise
# be unnecessary.
UnnecessaryParentReference:
enabled: false
enabled: true
# URLs should be valid and not contain protocols or domain names.
UrlFormat:
enabled: true

View file

@ -2,10 +2,712 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 9.2.10 (2017-08-09)
## 9.5.4 (2017-09-06)
- [SECURITY] Upgrade mail and nokogiri gems due to security issues. !13662 (Markus Koller)
- [SECURITY] Prevent a persistent XSS in the commit author block.
- Fix XSS issue in go-get handling.
- Resolve CSRF token leakage via pathname manipulation on environments page.
- Fixes race condition in project uploads.
- Disallow arbitrary properties in `th` and `td` `style` attributes.
- Disallow the `name` attribute on all user-provided markup.
## 9.5.3 (2017-09-03)
- [SECURITY] Filter additional secrets from Rails logs.
- [FIXED] Make username update fail if the namespace update fails. !13642
- [FIXED] Fix failure when issue is authored by a deleted user. !13807
- [FIXED] Reverts changes made to signin_enabled. !13956
- [FIXED] Fix Merge when pipeline succeeds button dropdown caret icon horizontal alignment.
- [FIXED] Fixed diff changes bar buttons from showing/hiding whilst scrolling.
- [FIXED] Fix events error importing GitLab projects.
- [FIXED] Fix pipeline trigger via API fails with 500 Internal Server Error in 9.5.
- [FIXED] Fixed fly-out nav flashing in & out.
- [FIXED] Remove closing external issues by reference error.
- [FIXED] Re-allow appearances.description_html to be NULL.
- [CHANGED] Update and fix resolvable note icons for easier recognition.
- [OTHER] Eager load head pipeline projects for MRs index.
- [OTHER] Instrument MergeRequest#fetch_ref.
- [OTHER] Instrument MergeRequest#ensure_ref_fetched.
## 9.5.2 (2017-08-28)
- [FIXED] Fix signing in using LDAP when attribute mapping uses simple strings instead of arrays.
- [FIXED] Show un-highlighted text diffs when we do not have references to the correct blobs.
- [FIXED] Fix display of push events for removed refs.
- [FIXED] Testing of some integrations were broken due to missing ServiceHook record.
- [FIXED] Fire system hooks when a user is created via LDAP.
- [FIXED] Fix new project form not resetting the template value.
## 9.5.1 (2017-08-23)
- [FIXED] Fix merge request pipeline status when pipeline has errors. !13664
- [FIXED] Commit rows would occasionally render with the wrong language.
- [FIXED] Fix caching of future broadcast messages.
- [FIXED] Only require Sidekiq throttling library when enabled, to reduce cache misses.
- Raise Housekeeping timeout to 24 hours. !13719
## 9.5.0 (2017-08-22)
- [FIXED] Fix timeouts when creating projects in groups with many members. !13508
- [FIXED] Improve API pagination headers when no record found. !13629 (Jordan Patterson)
- [FIXED] Fix deleting GitLab Pages files when a project is removed. !13631
- [FIXED] Fix commit list not loading the correct page when scrolling.
- [OTHER] Cache the number of forks of a project. !13535
- GPG signed commits integration. !9546 (Alexis Reigel)
- Alert the user if a Wiki page changed while they were editing it in order to prevent overwriting changes. !9707 (Hiroyuki Sato)
- Add custom linter for inline JavaScript to haml_lint. !9742 (winniehell)
- Add /shrug and /tableflip commands. !10068 (Alex Ives)
- Allow wiki pages to be renamed in the UI. !10069 (wendy0402)
- Insert user name directly without encoding. !10085 (Nathan Neulinger <nneul@neulinger.org>)
- Avoid plucking Todo ids in TodoService. !10845
- Handle errors while a project is being deleted asynchronously. !11088
- Decrease ABC threshold to 56.96. !11227 (Maxim Rydkin)
- Remove Mattermost team when deleting a group. !11362
- Block access to failing repository storage. !11449
- Add coordinator url to admin area runner page. !11603
- Allow testing any events for project hooks and system hooks. !11728 (Alexander Randa (@randaalex))
- Disallow running the pipeline if ref is protected and user cannot merge the branch or create the tag. !11910
- Remove project_key from the Jira configuration. !12050
- Add CSRF token verification to API. !12154 (Vitaliy @blackst0ne Klachkov)
- Fixes needed when GitLab sign-in is not enabled. !12491 (Robin Bobbitt)
- Lazy load images for better Frontend performance. !12503
- Replaces dashboard/event_filters.feature spinach with rspec. !12651 (Alexander Randa (@randaalex))
- Toggle import description with import_sources_enabled. !12691 (Brianna Kicia)
- Bump scss-lint to 0.54.0. !12733 (Takuya Noguchi)
- Enable SpaceAfterComma in scss-lint. !12734 (Takuya Noguchi)
- Remove CSS for nprogress removed. !12737 (Takuya Noguchi)
- Enable UnnecessaryParentReference in scss-lint. !12738 (Takuya Noguchi)
- Extract "@request.env[devise.mapping] = Devise.mappings[:user]" to a test helper. !12742 (Jacopo Beschi @jacopo-beschi)
- Enable ImportPath in scss-lint. !12749 (Takuya Noguchi)
- Enable PropertySpelling in scss-lint. !12752 (Takuya Noguchi)
- Add API for protected branches to allow for wildcard matching and no access restrictions. !12756 (Eric Yu)
- refactor initializations in dropzone_input.js. !12768 (Brandon Everett)
- Improve CSS for global nav dropdown UI. !12772 (Takuya Noguchi)
- Remove public/ci/favicon.ico. !12803 (Takuya Noguchi)
- Enable DeclarationOrder in scss-lint. !12805 (Takuya Noguchi)
- Increase width of dropdown menus automatically. !12809 (Thomas Wucher)
- Enable BangFormat in scss-lint [ci skip]. !12815 (Takuya Noguchi)
- Added /duplicate quick action to close a duplicate issue. !12845 (Ryan Scott)
- Make all application-settings accessible through the API. !12851
- Remove Inactive Personal Access Tokens list from Access Tokens page. !12866
- Replaces dashboard/dashboard.feature spinach with rspec. !12876 (Alexander Randa (@randaalex))
- Reduce memory usage of the GitHub importer. !12886
- Bump fog-core to 1.44.3 and fog providers' plugins to latest. !12897 (Takuya Noguchi)
- Use only CSS to truncate commit message in blame. !12900 (Takuya Noguchi)
- Protect manual actions against protected tag too. !12908
- Allow to configure automatic retry of a failed CI/CD job. !12909
- Remove help message about prioritized labels for non-members. !12912 (Takuya Noguchi)
- Add link to doc/api/ci/lint.md. !12914 (Takuya Noguchi)
- Add RequestCache which makes caching with RequestStore easier. !12920
- Free up some top level words, reject top level groups named like files in the public folder. !12932
- Extend API for Group Secret Variable. !12936
- Hide description about protected branches to non-member. !12945 (Takuya Noguchi)
- Support custom directory in gitlab:backup:create task. !12984 (Markus Koller)
- Raise guessed encoding confidence threshold to 50. !12990
- Add author_id & assignee_id param to /issues API. !13004
- Fix today day highlight in calendar. !13048
- Prevent LDAP login callback from being called with a GET request. !13059
- Add top-level merge_requests API endpoint. !13060
- Handle maximum pages artifacts size correctly. !13072
- Enable gitaly_post_upload_pack by default. !13078
- Add Prometheus metrics exporter to Sidekiq. !13082
- Fix improperly skipped backups of wikis. !13096
- Projects can be created from templates. !13108
- Fix the /projects/:id/repository/branches endpoint to handle dots in the branch name when the project full path contains a `/`. !13115
- Fix project logos that are not centered vertically on list pages. !13124 (Florian Lemaitre)
- Derive project path from import URL. !13131
- Fix deletion of deploy keys linked to other projects. !13162
- repository archive download url now ends with selected file extension. !13178 (haseebeqx)
- Show auto-generated avatars for Groups without avatars. !13188
- Allow any logged in users to read_users_list even if it's restricted. !13201
- Unlock stuck merge request and set the proper state. !13207
- Fix timezone inconsistencies in user contribution graph. !13208
- Fix Issue board when using Ruby 2.4. !13220
- Don't rename namespace called system when upgrading from 9.1.x to 9.5. !13228
- Fix encoding error for WebHook logging. !13230 (Alexander Randa (@randaalex))
- Uniquify reserved word usernames on OAuth user creation. !13244 (Robin Bobbitt)
- Expose target_iid in Events API. !13247 (sue445)
- Add star for action scope, in order to delete image from registry. !13248 (jean)
- Make Delete Merged Branches handle wildcard protected branches correctly. !13251
- Fix an order of operations for CI connection error message in merge request widget. !13252
- Don't send rejection mails for all auto-generated mails. !13254
- Expose noteable_iid in Note. !13265 (sue445)
- Fix pipeline_schedules pages when active schedule has an abnormal state. !13286
- Move some code from services to workers in order to improve performance. !13326
- Fix destroy of case-insensitive conflicting redirects. !13357
- Fix the /projects/:id/repository/tags endpoint to handle dots in the tag name when the project full path contains a `/`. !13368
- Fix the /projects/:id/repository/commits endpoint to handle dots in the ref name when the project full path contains a `/`. !13370
- Project pending delete no longer return 500 error in admins projects view. !13389
- Use full path of user's avatar in webhooks. !13401 (Vitaliy @blackst0ne Klachkov)
- Make GPGME temporary directory handling thread safe. !13481 (Alexis Reigel)
- Add support for kube_namespace in Metrics queries. !16169
- Fix bar chart does not display label at 0 hour. !35136 (Jason Dai)
- Use project_ref_path to create the link to a branch to fix links that 404.
- Declare related resources into V4 API entities.
- Add Slack and JIRA services counts to Usage Data.
- Prevent web hook and project service background jobs from going to the dead jobs queue.
- Display specific error message when JIRA test fails.
- clean up merge request widget UI.
- Associate Issues tab only with internal issues tracker.
- Remove events column from notification settings table.
- Clarifies and rearranges the input variables on the kubernetes integration page and adjusts the docs slightly to meet the same order.
- Respect blockquote line breaks in markdown.
- Update confidential issue UI - add confidential visibility and settings to sidebar.
- Add icons to contextual sidebars.
- Make contextual sidebar collapsible.
- Update Pipeline's badge count in Merge Request and Commits view to match real-time content.
- Added link to the MR widget that directs to the monitoring dashboard.
- Use jQuery to control scroll behavior in job log for cross browser consistency.
- move edit comment button outside of dropdown.
- Updates vue resource and code according to breaking changes.
- Add GitHub imported projects count to usage data.
- Rename about to overview for group and project page.
- Prevent disabled pagination button to be clicked.
- Remove coffee-rails gem. (Takuya Noguchi)
- Remove net-ssh gem. (Takuya Noguchi)
- Bump rubocop to 0.49.1 and rubocop-rspec to 1.15.1. (Takuya Noguchi)
- improve file upload/replace experience.
- allow closing Cycle Analytics intro box in firefox.
- Fix label creation from new list for subgroup projects.
- fix transient js error in rspec tests.
- fix jump to next discussion button.
- Fix translations for Star/Unstar in JS file.
- Improve mobile sidebar.
- Rename Pipelines tab to CI / CD in new navigation.
- Fix display of new diff comments after changing b between diff views.
- Store & use ConvDev percentages returned by the Version app.
- Fixes new issue button for failed job returning 404.
- Align OR separator to center in new project page.
- Add filtered search to group issue dashboard.
- Cache Appearance instances in Redis.
- Fixed breadcrumbs title aggressively collapsing.
- Better caching and indexing of broadcast messages.
- Moved diff changed files into a dropdown.
- Improve performance of large (initial) push into default branch.
- Improve performance of checking for projects on the projects dashboard.
- Eager load project creators for project dashboards.
- Modify if condition to be more readable.
- Fix links to group milestones from issue and merge request sidebar.
- Remove hidden symlinks from project import files.
- Fixed sign-in restrictions buttons not toggling active state.
- Fix replying to commit comments on merge requests created from forks.
- Support Markdown references, autocomplete, and quick actions for group milestones.
- Cache recent projects for group-level new resource creation.
- Fix API responses when dealing with txt files.
- Fix project milestones import when projects belongs to a group.
- Fix Mattermost integration.
- Memoize the number of personal projects a user has to reduce COUNT queries.
- Merge issuable "reopened" state into "opened".
- Migrate events into a new format to reduce the storage necessary and improve performance.
- MR branch link now links to tree instead of commits.
- Use Prev/Next pagination for exploring projects.
- Pass before_script and script as-is preserving arrays.
- Change project FK migration to skip existing FKs.
- Remove redundant query when retrieving the most recent push of a user.
- Re-organise "issues" indexes for faster ordering.
- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
- Fix search box losing focus when typing.
- Add structured logging for Rails processes.
- Skip oAuth authorization for trusted applications.
- Use a specialized class for querying events to improve performance.
- Update build badges to be pipeline badges and display passing instead of success.
## 9.4.3 (2017-07-31)
- Fix Prometheus client PID reuse bug. !13130
- Improve deploy environment chatops slash command. !13150
- Fix asynchronous javascript paths when GitLab is installed under a relative URL. !13165
- Fix LDAP authentication to Git repository or container registry.
- Fixed new navigation breadcrumb title on help pages.
- Ensure filesystem metrics test files are deleted.
- Properly affixes nav bar in job view in microsoft edge.
## 9.4.2 (2017-07-28)
- Fix job merge request link to a forked source project. !12965
- Improve redirect route query performance. !13062
- Allow admin to read_users_list even if it's restricted. !13066
- Fixes 500 error caused by pending delete projects in admin dashboard. !13067
- Add instrumentation to MarkupHelper#link_to_gfm. !13069
- Pending delete projects should not show in deploy keys. !13088
- Fix sizing of custom header logo in new navigation.
- Fix crash on /help/ui.
- Fix creating merge request diffs when diff contains bytes that are invalid in UTF-8.
- fix vertical alignment of New Project button.
- Add LDAP SSL certificate verification option.
- Fix vertical alignment in firefox and safari for pipeline mini graph.
## 9.4.1 (2017-07-25)
- Fix pipeline_schedules pages throwing error 500 (when ref is empty). !12983
- Fix editing project with container images present. !13028
- Fix some invalid entries in PO files. !13032
- Fix cross site request protection when logging in as a regular user when LDAP is enabled. !13049
- Fix bug causing metrics files to be truncated. !35420
- Fix anonymous access to public projects in groups with pending invites.
- Fixed issue boards sidebar close icon size.
- Fixed duplicate new milestone buttons when new navigation is turned on.
- Fix margins in the mini graph for pipeline in commits box.
## 9.4.0 (2017-07-22)
- Add blame view age mapping. !7198 (Jeff Stubler)
- Add support for image and services configuration in .gitlab-ci.yml. !8578
- Fix an email parsing bug where brackets would be inserted in emails from some Outlook clients. !9045 (jneen)
- Use fa-chevron-down on dropdown arrows for consistency. !9659 (TM Lee)
- Update the devise mail templates to match the design of the pipeline emails. !10483 (Alexis Reigel)
- Handle renamed submodules in repository browser. !10798 (David Turner)
- Display all current broadcast messages, not just the last one. !11113 (rickettm)
- Fix CI/CD status in case there are only allowed to failed jobs in the pipeline. !11166
- Omit trailing / leading hyphens in CI_COMMIT_REF_SLUG variable to make it usable as a hostname. !11218 (Stefan Hanreich)
- Moved "Members in a project" menu entry and path locations. !11560
- Additional Prometheus metrics support. !11712
- Rename all reserved paths that could have been created. !11713
- Move uploads from `uploads/system` to `uploads/-/system` to free up `system` as a group name. !11713
- Fix offline runner detection. !11751 (Alessio Caiazza)
- Use authorize_update_pipeline_schedule in PipelineSchedulesController. !11846
- Rollback project repo move if there is an error in Projects::TransferService. !11877
- Help landing page customizations. !11878 (Robin Bobbitt)
- Fixes "sign in / Register" active state underline misalignment. !11890 (Frank Sierra)
- Honor the "Remember me" parameter for OAuth-based login. !11963
- Instruct user to use personal access token for Git over HTTP. !11986 (Robin Bobbitt)
- Accept image for avatar in project API. !11988 (Ivan Chernov)
- Supplement Simplified Chinese translation of Project Page & Repository Page. !11994 (Huang Tao)
- Supplement Traditional Chinese in Hong Kong translation of Project Page & Repository Page. !11995 (Huang Tao)
- Make the revision on the `/help` page clickable. !12016
- Display issue state in issue links section of merge request widget. !12021
- Enable support for webpack code-splitting by dynamically setting publicPath at runtime. !12032
- Replace PhantomJS with headless Chrome for karma test suite. !12036
- Prevent description change notes when toggling tasks. !12057 (Jared Deckard <jared.deckard@gmail.com>)
- Update QA Dockerfile to lock Chrome browser version. !12071
- Fix FIDO U2F for Opera browser. !12082 (Jakub Kramarz and Jonas Kalderstam)
- Supplement Bulgarian translation of Project Page & Repository Page. !12083 (Lyubomir Vasilev)
- Removes deleted_at and pending_delete occurrences in Project related queries. !12091
- Provide hint to create a personal access token for Git over HTTP. !12105 (Robin Bobbitt)
- Display own user id in account settings page. !12141 (Riccardo Padovani)
- Accept image for avatar in user API. !12143 (Ivan Chernov)
- Disable fork button on project limit. !12145 (Ivan Chernov)
- Added "created_after" and "created_before" params to issuables. !12151 (Kyle Bishop @kybishop)
- Supplement Portuguese Brazil translation of Project Page & Repository Page. !12156 (Huang Tao)
- Add review apps to usage metrics. !12185
- Adding French translations. !12200 (Erwan "Dremor" Georget)
- Ensures default user limits when external user is unchecked. !12218
- Provide KUBECONFIG from KubernetesService for runners. !12223
- Filter archived project in API v3 only if param present. !12245 (Ivan Chernov)
- Add explicit message when no runners on admin. !12266 (Takuya Noguchi)
- Split pipelines as internal and external in the usage data. !12277
- Fix API Scoping. !12300
- Remove registry image delete button if user cant delete it. !12317 (Ivan Chernov)
- Allow the feature flags to be enabled/disabled with more granularity. !12357
- Allow to enable the performance bar per user or Feature group. !12362
- Rename duplicated variables with the same key for projects. Add environment_scope column to variables and add unique constraint to make sure that no variables could be created with the same key within a project. !12363
- Add variables to pipelines schedules. !12372
- Add User#full_private_access? to check if user has access to all private groups & projects. !12373
- Change milestone endpoint for groups. !12374 (Takuya Noguchi)
- Improve performance of the pipeline charts page. !12378
- Add option to run Gitaly on a remote server. !12381
- #20628 Enable implicit grant in GitLab as OAuth Provider. !12384 (Mateusz Pytel)
- Replace 'snippets/snippets.feature' spinach with rspec. !12385 (Alexander Randa @randaalex)
- Add Simplified Chinese translations of Commits Page. !12405 (Huang Tao)
- Add Traditional Chinese in HongKong translations of Commits Page. !12406 (Huang Tao)
- Add Traditional Chinese in Taiwan translations of Commits Page. !12407 (Huang Tao)
- Add Portuguese Brazil translations of Commits Page. !12408 (Huang Tao)
- Add French translations of Commits Page. !12409 (Huang Tao)
- Add Esperanto translations of Commits Page. !12410 (Huang Tao)
- Add Bulgarian translations of Commits Page. !12411 (Huang Tao)
- Remove bin/ci/upgrade.rb as not working all. !12414 (Takuya Noguchi)
- Store merge request ref_fetched status in the database. !12424
- Replace 'dashboard/merge_requests' spinach with rspec. !12440 (Alexander Randa (@randaalex))
- Add Esperanto translations for Cycle Analytics, Project, and Repository pages. !12442 (Huang Tao)
- Allow unauthenticated access to the /api/v4/users API. !12445
- Drop GFM support for the title of Milestone/MergeRequest in template. !12451 (Takuya Noguchi)
- Replace 'dashboard/todos' spinach with rspec. !12453 (Alexander Randa (@randaalex))
- Cache open issue and merge request counts for project tabs to speed up project pages. !12457
- Introduce cache policies for CI jobs. !12483
- Improve support for external issue references. !12485
- Fix errors caused by attempts to report already blocked or deleted users. !12502 (Horacio Bertorello)
- Allow customize CI config path. !12509 (Keith Pope)
- Supplement Traditional Chinese in Taiwan translation of Project Page & Repository Page. !12514 (Huang Tao)
- Closes any open Autocomplete of the markdown editor when the form is closed. !12521
- Inserts exact matches of name, username and email to the top of the search list. !12525
- Use smaller min-width for dropdown-menu-nav only on mobile. !12528 (Takuya Noguchi)
- Hide archived project labels from group issue tracker. !12547 (Horacio Bertorello)
- Replace 'dashboard/new-project.feature' spinach with rspec. !12550 (Alexander Randa (@randaalex))
- Remove group modal like remove project modal (requires typing + confirmation). !12569 (Diego Souza)
- Add Italian translation of Cycle Analytics Page & Project Page & Repository Page. !12578 (Huang Tao)
- Add Group secret variables. !12582
- Update jobs page output to have a scrollable page. !12587
- Add user projects API. !12596 (Ivan Chernov)
- Allow creation of files and directories with spaces through Web UI. !12608
- Improve members view on mobile. !12619
- Fixed the chart legend not being set correctly. !12628
- Add Italian translations of Commits Page. !12645 (Huang Tao)
- Allow admins to disable all restricted visibility levels. !12649
- Allow admins to retrieve user agent details for an issue or snippet. !12655
- Update welcome page UX for new users. !12662
- N+1 problems on milestone page. !12670 (Takuya Noguchi)
- Upgrade GitLab Workhorse to v2.3.0. !12676
- Remove option to disable Gitaly. !12677
- Improve the performance of the project list API. !12679
- Add creation time filters to user search API for admins. !12682
- Add Japanese translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. !12693 (Huang Tao)
- Undo adding the /reassign quick action. !12701
- Fix dashboard labels dropdown. !12708
- Username and password are no longer stripped from import url on mirror update. !12725
- Add Russian translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. !12743 (Huang Tao)
- Add Ukrainian translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. !12744 (Huang Tao)
- Prevent bad data being added to application settings when Redis is unavailable. !12750
- Do not show pipeline schedule button for non-member. !12757 (Takuya Noguchi)
- Return `is_admin` attribute in the GET /user endpoint for admins. !12811
- Recover from renaming project that has container images. !12840
- Exact matches of username and email are now on top of the user search. !12868
- Use Ghost user for last_edited_by and merge_user when original user is deleted. !12933
- Fix docker tag reference routing constraints. !12961
- Optimize creation of commit API by using Repository#commit instead of Repository#commits.
- Speed up used languages calculation on charts page.
- Make loading new merge requests (those created after the 9.4 upgrade) faster.
- Ensure participants for issues, merge requests, etc. are calculated correctly when sending notifications.
- Handle nameless legacy jobs.
- Bump Faraday and dependent OAuth2 gem version to support no_proxy variable.
- Renders 404 if given project is not readable by the user on Todos dashboard.
- Render CI statuses with warnings in orange.
- Document the Delete Merged Branches functionality.
- Add wells to admin dashboard overview to fix spacing problems.
- Removes hover style for nodes that are either links or buttons in the pipeline graph.
- more visual contrast in pagination widget.
- Deprecate Healthcheck Access Token in favor of IP whitelist.
- Drop GFM support for issuable title on milestone for consistency and performance. (Takuya Noguchi)
- fix left & right padding on sidebar.
- Cleanup minor UX issues in the performance dashboard.
- Remove two columned layout from project member settings.
- Make font size of contextual sub menu items 14px.
- Fix vertical space in job details sidebar.
- Fix alignment of controls in mr issuable list.
- Add wip message to new navigation preference section.
- Add group members counting and plan related data on namespaces API.
- Fix spacing on runner buttons.
- Remove uploads/appearance symlink. A leftover from a previous migration.
- Change order of monospace fonts to fix bug on some linux distros.
- Limit commit & snippets comments width.
- Fixed dashboard milestone tabs not loading.
- Detect if file that appears to be text in the first 1024 bytes is actually binary afer loading all data.
- Fix inconsistent display of the "Browse files" button in the commit list.
- Implement diff viewers.
- Fix 'New merge request' button for users who don't have push access to canonical project.
- Fix issues with non-UTF8 filenames by always fixing the encoding of tree and blob paths.
- Show group name instead of path on group page.
- Don't check if MailRoom is running on Omnibus.
- Limit OpenGraph image size to 64x64.
- Don't show auxiliary blob viewer for README when there is no wiki.
- Strip trailing whitespace in relative submodule URL.
- Update /target_branch slash command description to be more consistent.
- Remove unnecessary top padding on group MR index.
- Added printing_merge_requst_link_enabled to the API. (David Turner <dturner@twosigma.com>)
- Re-enable realtime for environments table.
- Create responsive mobile view for pipelines table.
- Adds realtime feature to job show view header and sidebar info. Updates UX.
- Use color inputs for broadcast messages.
- Center dropdown for mini graph.
- Users can subscribe to group labels on the group labels page.
- Add issuable-list class to shared mr/issue lists to fix new responsive layout design.
- Rename "Slash commands" to "Quick actions" and deprecate "chat commands" in favor of "slash commands".
- Don't mark empty MRs as merged on push to the target branch.
- Improve issue rendering performance with lots of notes from other users.
- Fixed overflow on mobile screens for the slash commands.
- Fix an infinite loop when handling user-supplied regular expressions.
- Fixed sidebar not collapsing on merge requests in mobile screens.
- Speed up project removals by adding foreign keys with cascading deletes to various tables.
- Fix mobile view of files view buttons.
- Fixed dropdown filter input not focusing after transition.
- Fixed GFM references not being included when updating issues inline.
- Remove issues/merge requests drag n drop and sorting from milestone view.
- Add native group milestones.
- Fix API bug accepting wrong parameter to create merge request.
- Clean up UI of issuable lists and make more responsive.
- Improve the overall UX for the new monitoring dashboard.
- Fixed the y_label not setting correctly for each graph on the monitoring dashboard.
- Changed utilities imports from ~ to relative paths.
- Remove unused space in sidebar todo toggle when not signed in.
- Limit the width of the projects README text.
- Add a simple mode to merge request API.
- Make Project#ensure_repository force create a repo.
- Use uploads/system directory for personal snippets.
- Defer project destroys within a namespace in Groups::DestroyService#async_execute.
- Log rescued exceptions to Sentry.
- Remove remaining N+1 queries in merge requests API with emojis and labels.
## 9.3.9 (2017-07-20)
- Fix an infinite loop when handling user-supplied regular expressions.
## 9.3.8 (2017-07-19)
- Improve support for external issue references. !12485
- Renders 404 if given project is not readable by the user on Todos dashboard.
- Use uploads/system directory for personal snippets.
- Remove uploads/appearance symlink. A leftover from a previous migration.
## 9.3.7 (2017-07-18)
- Prevent bad data being added to application settings when Redis is unavailable. !12750
- Return `is_admin` attribute in the GET /user endpoint for admins. !12811
## 9.3.6 (2017-07-12)
- Fix API Scoping. !12300
- Username and password are no longer stripped from import url on mirror update. !12725
- Fix issues with non-UTF8 filenames by always fixing the encoding of tree and blob paths.
- Fixed GFM references not being included when updating issues inline.
## 9.3.5 (2017-07-05)
- Remove "Remove from board" button from backlog and closed list. !12430
- Do not delete protected branches when deleting all merged branches. !12624
- Set default for Remove source branch to false.
- Prevent accidental deletion of protected MR source branch by repeating checks before actual deletion.
- Expires full_path cache after a repository is renamed/transferred.
## 9.3.4 (2017-07-03)
- Update gitlab-shell to 5.1.1 !12615
## 9.3.3 (2017-06-30)
- Fix head pipeline stored in merge request for external pipelines. !12478
- Bring back branches badge to main project page. !12548
- Fix diff of requirements.txt file by not matching newlines as part of package names.
- Perform housekeeping only when an import of a fresh project is completed.
- Fixed issue boards closed list not showing all closed issues.
- Fixed multi-line markdown tooltip buttons in issue edit form.
## 9.3.2 (2017-06-27)
- API: Fix optional arugments for POST :id/variables. !12474
- Bump premailer-rails gem to 1.9.7 and its dependencies to prevent network retrieval of assets.
## 9.3.1 (2017-06-26)
- Fix reversed breadcrumb order for nested groups. !12322
- Fix 500 when failing to create private group. !12394
- Fix linking to line number on side-by-side diff creating empty discussion box.
- Don't match tilde and exclamation mark as part of requirements.txt package name.
- Perform project housekeeping after importing projects.
- Fixed ctrl+enter not submit issue edit form.
## 9.3.0 (2017-06-22)
- Refactored gitlab:app:check into SystemCheck liberary and improve some checks. !9173
- Add an ability to cancel attaching file and redesign attaching files UI. !9431 (blackst0ne)
- Add Aliyun OSS as the backup storage provider. !9721 (Yuanfei Zhu)
- Add suport for find_local_branches GRPC from Gitaly. !10059
- Allow manual bypass of auto_sign_in_with_provider with a new param. !10187 (Maxime Besson)
- Redirect to user's keys index instead of user's index after a key is deleted in the admin. !10227 (Cyril Jouve)
- Changed Blame to Annotate in the UI to promote blameless culture. !10378 (Ilya Vassilevsky)
- Implement ability to update deploy keys. !10383 (Alexander Randa)
- Allow numeric values in gitlab-ci.yml. !10607 (blackst0ne)
- Add a feature test for Unicode trace. !10736 (dosuken123)
- Notes: Warning message should go away once resolved. !10823 (Jacopo Beschi @jacopo-beschi)
- Project authorizations are calculated much faster when using PostgreSQL, and nested groups support for MySQL has been removed
. !10885
- Fix long urls in the title of commit. !10938 (Alexander Randa)
- Update gem sidekiq-cron from 0.4.4 to 0.6.0 and rufus-scheduler from 3.1.10 to 3.4.0. !10976 (dosuken123)
- Use relative paths for group/project/user avatars. !11001 (blackst0ne)
- Enable cancelling non-HEAD pending pipelines by default for all projects. !11023
- Implement web hook logging. !11027 (Alexander Randa)
- Add indices for auto_canceled_by_id for ci_pipelines and ci_builds on PostgreSQL. !11034
- Add post-deploy migration to clean up projects in `pending_delete` state. !11044
- Limit User's trackable attributes, like `current_sign_in_at`, to update at most once/hour. !11053
- Disallow multiple selections for Milestone dropdown. !11084
- Link to commit author user page from pipelines. !11100
- Fix the last coverage in trace log should be extracted. !11128 (dosuken123)
- Remove redirect for old issue url containing id instead of iid. !11135 (blackst0ne)
- Backported new SystemHook event: `repository_update`. !11140
- Keep input data after creating a tag that already exists. !11155
- Fix support for external CI services. !11176
- Translate backend for Project & Repository pages. !11183
- Fix LaTeX formatting for AsciiDoc wiki. !11212
- Add foreign key for pipeline schedule owner. !11233
- Print Go version in rake gitlab:env:info. !11241
- Include the blob content when printing a blob page. !11247
- Sync email address from specified omniauth provider. !11268 (Robin Bobbitt)
- Disable reference prefixes in notes for Snippets. !11278
- Rename build_events to job_events. !11287
- Add API support for pipeline schedule. !11307 (dosuken123)
- Use route.cache_key for project list cache key. !11325
- Make environment table realtime. !11333
- Cache npm modules between pipelines with yarn to speed up setup-test-env. !11343
- Allow GitLab instance to start when InfluxDB hostname cannot be resolved. !11356
- Add ConvDev Index page to admin area. !11377
- Fix Git-over-HTTP error statuses and improve error messages. !11398
- Renamed users 'Audit Log'' to 'Authentication Log'. !11400
- Style people in issuable search bar. !11402
- Change /builds in the URL to /-/jobs. Backward URLs were also added. !11407
- Update password field label while editing service settings. !11431
- Add an optional performance bar to view performance metrics for the current page. !11439
- Update task_list to version 2.0.0. !11525 (Jared Deckard <jared.deckard@gmail.com>)
- Avoid resource intensive login checks if password is not provided. !11537 (Horatiu Eugen Vlad)
- Allow numeric pages domain. !11550
- Exclude manual actions when checking if pipeline can be canceled. !11562
- Add server uptime to System Info page in admin dashboard. !11590 (Justin Boltz)
- Simplify testing and saving service integrations. !11599
- Fixed handling of the `can_push` attribute in the v3 deploy_keys api. !11607 (Richard Clamp)
- Improve user experience around slash commands in instant comments. !11612
- Show current user immediately in issuable filters. !11630
- Add extra context-sensitive functionality for the top right menu button. !11632
- Reorder Issue action buttons in order of usability. !11642
- Expose atom links with an RSS token instead of using the private token. !11647 (Alexis Reigel)
- Respect merge, instead of push, permissions for protected actions. !11648
- Job details page update real time. !11651
- Improve performance of ProjectFinder used in /projects API endpoint. !11666
- Remove redundant data-turbolink attributes from links. !11672 (blackst0ne)
- Minimum postgresql version is now 9.2. !11677
- Add protected variables which would only be passed to protected branches or protected tags. !11688
- Introduce optimistic locking support via optional parameter last_commit_sha on File Update API. !11694 (electroma)
- Add $CI_ENVIRONMENT_URL to predefined variables for pipelines. !11695
- Simplify project repository settings page. !11698
- Fix pipeline_schedules pages throwing error 500. !11706 (dosuken123)
- Add performance deltas between app deployments on Merge Request widget. !11730
- Add feature toggles and API endpoints for admins. !11747
- Replace 'starred_projects.feature' spinach test with an rspec analog. !11752 (blackst0ne)
- Introduce an Events API. !11755
- Display Shared Runner status in Admin Dashboard. !11783 (Ivan Chernov)
- Persist pipeline stages in the database. !11790
- Revert the feature that would include the current user's username in the HTTP clone URL. !11792
- Enable Gitaly by default in installations from source. !11796
- Use zopfli compression for frontend assets. !11798
- Add tag_list param to project api. !11799 (Ivan Chernov)
- Add changelog for improved Registry description. !11816
- Automatically adjust project settings to match changes in project visibility. !11831
- Add slugify project path to CI enviroment variables. !11838 (Ivan Chernov)
- Add all pipeline sources as special keywords to 'only' and 'except'. !11844 (Filip Krakowski)
- Allow pulling of container images using personal access tokens. !11845
- Expose import_status in Projects API. !11851 (Robin Bobbitt)
- Allow admins to delete users from the admin users page. !11852
- Allow users to be hard-deleted from the API. !11853
- Fix hard-deleting users when they have authored issues. !11855
- Fix missing optional path parameter in "Create project for user" API. !11868
- Allow users to be hard-deleted from the admin panel. !11874
- Add a Rake task to aid in rotating otp_key_base. !11881
- Fix submodule link to then project under subgroup. !11906
- Fix binary encoding error on MR diffs. !11929
- Limit non-administrators to adding 100 members at a time to groups and projects. !11940
- add bulgarian translation of cycle analytics page to I18N. !11958 (Lyubomir Vasilev)
- Make backup task to continue on corrupt repositories. !11962
- Fix incorrect ETag cache key when relative instance URL is used. !11964
- Reinstate is_admin flag in users api when authenticated user is an admin. !12211 (rickettm)
- Fix edit button for deploy keys available from other projects. !12301 (Alexander Randa)
- Fix passing CI_ENVIRONMENT_NAME and CI_ENVIRONMENT_SLUG for CI_ENVIRONMENT_URL. !12344
- Disable environment list refresh due to bug https://gitlab.com/gitlab-org/gitlab-ee/issues/2677. !12347
- Standardize timeline note margins across different viewport sizes. !12364
- Fix Ordered Task List Items. !31483 (Jared Deckard <jared.deckard@gmail.com>)
- Upgrade dependency to Go 1.8.3. !31943
- Add prometheus metrics on pipeline creation.
- Fix etag route not being a match for environments.
- Sort folder for environments.
- Support descriptions for snippets.
- Hide clone panel and file list when user is only a guest. (James Clark)
- Dont create comment on JIRA if it already exists for the entity.
- Update Dashboard Groups UI with better support for subgroups.
- Confirm Project forking behaviour via the API.
- Add prometheus based metrics collection to gitlab webapp.
- Fix: Wiki is not searchable with Guest permissions.
- Center all empty states.
- Remove 'New issue' button when issues search returns no results.
- Add API URL to JIRA settings.
- animate adding issue to boards.
- Update session cookie key name to be unique to instance in development.
- Single click on filter to open filtered search dropdown.
- Makes header information of pipeline show page realtine.
- Creates a mediator for pipeline details vue in order to mount several vue apps with the same data.
- Scope issue/merge request recent searches to project.
- Increase individual diff collapse limit to 100 KB, and render limit to 200 KB.
- Fix Pipelines table empty state - only render empty state if we receive 0 pipelines.
- Make New environment empty state btn lowercase.
- Removes duplicate environment variable in documentation.
- Change links in issuable meta to black.
- Fix border-bottom for project activity tab.
- Adds new icon for CI skipped status.
- Create equal padding for emoji.
- Use briefcase icon for company in profile page.
- Remove overflow from comment form for confidential issues and vertically aligns confidential issue icon.
- Keep trailing newline when resolving conflicts by picking sides.
- Fix /unsubscribe slash command creating extra todos when you were already mentioned in an issue.
- Fix math rendering on blob pages.
- Allow group reporters to manage group labels.
- Use pre-wrap for commit messages to keep lists indented.
- Count badges depend on translucent color to better adjust to different background colors and permission badges now feature a pill shaped design similar to labels.
- Allow reporters to promote project labels to group labels.
- Enabled keyboard shortcuts on artifacts pages.
- Perform filtered search when state tab is changed.
- Remove duplication for sharing projects with groups in project settings.
- Change order of commits ahead and behind on divergence graph for branch list view.
- Creates CI Header component for Pipelines and Jobs details pages.
- Invalidate cache for issue and MR counters more granularly.
- disable blocked manual actions.
- Load tree readme asynchronously.
- Display extra info about files on .gitlab-ci.yml, .gitlab/route-map.yml and LICENSE blob pages.
- Fix replying to a commit discussion displayed in the context of an MR.
- Consistently use monospace font for commit SHAs and branch and tag names.
- Consistently display last push event widget.
- Don't copy empty elements that were not selected on purpose as GFM.
- Copy as GFM even when parts of other elements are selected.
- Autolink package names in Gemfile.
- Resolve N+1 query issue with discussions.
- Don't match email addresses or foo@bar as user references.
- Fix title of discussion jump button at top of page.
- Don't return nil for missing objects from parser cache.
- Make .gitmodules parsing more resilient to syntax errors.
- Add username parameter to gravatar URL.
- Autolink package names in more dependency files.
- Return nil when looking up config for unknown LDAP provider.
- Add system note with link to diff comparison when MR discussion becomes outdated.
- Don't wrap pasted code when it's already inside code tags.
- Revert 'New file from interface on existing branch'.
- Show last commit for current tree on tree page.
- Add documentation about adding foreign keys.
- add username field to push webhook. (David Turner)
- Rename CI/CD Pipelines to Pipelines in the project settings.
- Make environment tables responsive.
- Expand/collapse backlog & closed lists in issue boards.
- Fix GitHub importer performance on branch existence check.
- Fix counter cache for acts as taggable.
- Github - Fix token interpolation when cloning wiki repository.
- Fix token interpolation when setting the Github remote.
- Fix N+1 queries for non-members in comment threads.
- Fix terminals support for Kubernetes Service.
- Fix: A diff comment on a change at last line of a file shows as two comments in discussion.
- Instrument MergeRequestDiff#load_commits.
- Introduce source to Pipeline entity.
- Fixed create new label form in issue form not working for sub-group projects.
- Fixed style on unsubscribe page. (Gustav Ernberg)
- Enables inline editing for an issues title & description.
- Ask for an example project for bug reports.
- Add summary lines for collapsed details in the bug report template.
- Prevent commits from upstream repositories to be re-processed by forks.
- Avoid repeated queries for pipeline builds on merge requests.
- Preloads head pipeline for merge request collection.
- Handle head pipeline when creating merge requests.
- Migrate artifacts to a new path.
- Rescue OpenSSL::SSL::SSLError in JiraService & IssueTrackerService.
- Repository browser: handle in-repository submodule urls. (David Turner)
- Prevent project transfers if a new group is not selected.
- Allow 'no one' as an option for allowed to merge on a procted branch.
- Reduce time spent waiting for certain Sidekiq jobs to complete.
- Refactor ProjectsFinder#init_collection to produce more efficient queries for retrieving projects.
- Remove unused code and uses underscore.
- Restricts search projects dropdown to group projects when group is selected.
- Properly handle container registry redirects to fix metadata stored on a S3 backend.
- Fix LFS timeouts when trying to save large files.
- Set artifact working directory to be in the destination store to prevent unnecessary I/O.
- Strip trailing whitespaces in submodule URLs.
- Make sure reCAPTCHA configuration is loaded when spam checks are initiated.
- Fix up arrow not editing last discussion comment.
- Added application readiness endpoints to the monitoring health check admin view.
- Use wait_for_requests for both ajax and Vue requests.
- Cleanup ci_variables schema and table.
- Remove foreigh key on ci_trigger_schedules only if it exists.
- Allow translation of Pipeline Schedules.
## 9.2.9 (2017-07-20)
@ -262,6 +964,42 @@ entry.
- Fix preemptive scroll bar on user activity calendar.
- Pipeline chat notifications convert seconds to minutes and hours.
## 9.1.9 (2017-07-20)
- Fix an infinite loop when handling user-supplied regular expressions.
## 9.1.8 (2017-07-19)
- Improve support for external issue references. !12485
- Renders 404 if given project is not readable by the user on Todos dashboard.
- Fix incorrect project authorizations.
- Remove uploads/appearance symlink. A leftover from a previous migration.
## 9.1.7 (2017-06-07)
- No changes.
## 9.1.6 (2017-06-02)
- Fix visibility when referencing snippets.
## 9.1.5 (2017-05-31)
- Move uploads from 'public/uploads' to 'public/uploads/system'.
- Restrict API X-Frame-Options to same origin.
- Allow users autocomplete by author_id only for authenticated users.
## 9.1.4 (2017-05-12)
- Fix error on CI/CD Settings page related to invalid pipeline trigger. !10948 (dosuken123)
- Sort the network graph both by commit date and topographically. !11057
- Fix cross referencing for private and internal projects. !11243
- Handle incoming emails from aliases correctly.
- Gracefully handle failures for incoming emails which do not match on the To header, and have no References header.
- Add missing project attributes to Import/Export.
- Fixed search terms not correctly highlighting.
- Fixed bug where merge request JSON would be displayed.
## 9.1.3 (2017-05-05)
- Do not show private groups on subgroups page if user doesn't have access to.
@ -302,6 +1040,7 @@ entry.
## 9.1.0 (2017-04-22)
- Add Jupyter notebook rendering !10017
- Added merge requests empty state. !7342
- Add option to start a new resolvable discussion in an MR. !7527
- Hide form inputs for group member without editing rights. !7816
@ -548,6 +1287,30 @@ entry.
- Only send chat notifications for the default branch.
- Don't fill in the default kubernetes namespace.
## 9.0.12 (2017-07-20)
- Fix an infinite loop when handling user-supplied regular expressions.
## 9.0.11 (2017-07-19)
- Renders 404 if given project is not readable by the user on Todos dashboard.
- Fix incorrect project authorizations.
- Remove uploads/appearance symlink. A leftover from a previous migration.
## 9.0.10 (2017-06-07)
- No changes.
## 9.0.9 (2017-06-02)
- Fix visibility when referencing snippets.
## 9.0.8 (2017-05-31)
- Move uploads from 'public/uploads' to 'public/uploads/system'.
- Restrict API X-Frame-Options to same origin.
- Allow users autocomplete by author_id only for authenticated users.
## 9.0.7 (2017-05-05)
- Enforce project features when searching blobs and wikis.
@ -904,6 +1667,11 @@ entry.
- Change development tanuki favicon colors to match logo color order.
- API issues - support filtering by iids.
## 8.17.7 (2017-07-19)
- Renders 404 if given project is not readable by the user on Todos dashboard.
- Fix incorrect project authorizations.
## 8.17.6 (2017-05-05)
- Enforce project features when searching blobs and wikis.
@ -1151,8 +1919,6 @@ entry.
## 8.16.7 (2017-02-27)
- No changes.
- No changes.
- Fix MR changes tab size count when there are over 100 files in the diff.
## 8.16.6 (2017-02-17)
@ -1366,6 +2132,14 @@ entry.
- Prevent the GitHub importer from assigning labels and comments to merge requests or issues belonging to other projects.
- Patch XSS vulnerability in RDOC support.
## 8.15.5 (2017-01-20)
- Ensure export files are removed after a namespace is deleted.
- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling)
- Prevent users from creating notes on resources they can't access.
- Prevent users from deleting system deploy keys via the project deploy key API.
- Upgrade omniauth gem to 1.3.2.
## 8.15.4 (2017-01-09)
- Make successful pipeline emails off for watchers. !8176
@ -1648,6 +2422,14 @@ entry.
- Speed up group milestone index by passing group_id to IssuesFinder. !8363
- Ensure issuable state changes only fire webhooks once.
## 8.14.7 (2017-01-21)
- Ensure export files are removed after a namespace is deleted.
- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling)
- Prevent users from creating notes on resources they can't access.
- Prevent users from deleting system deploy keys via the project deploy key API.
- Upgrade omniauth gem to 1.3.2.
## 8.14.6 (2017-01-10)
- Update the gitlab-markup gem to the version 1.5.1. !8509
@ -1930,6 +2712,14 @@ entry.
- Fix "Without projects" filter. !6611 (Ben Bodenmiller)
- Fix 404 when visit /projects page
## 8.13.12 (2017-01-21)
- Ensure export files are removed after a namespace is deleted.
- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling)
- Prevent users from creating notes on resources they can't access.
- Prevent users from deleting system deploy keys via the project deploy key API.
- Upgrade omniauth gem to 1.3.2.
## 8.13.11 (2017-01-10)
- Update the gitlab-markup gem to the version 1.5.1. !8509

View file

@ -31,7 +31,7 @@ _This notice should stay as the first item in the CONTRIBUTING.MD file._
- [Issue tracker guidelines](#issue-tracker-guidelines)
- [Issue weight](#issue-weight)
- [Regression issues](#regression-issues)
- [Technical debt](#technical-debt)
- [Technical and UX debt](#technical-and-ux-debt)
- [Stewardship](#stewardship)
- [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines)
@ -49,6 +49,8 @@ _This notice should stay as the first item in the CONTRIBUTING.MD file._
Thank you for your interest in contributing to GitLab. This guide details how
to contribute to GitLab in a way that is efficient for everyone.
Looking for something to work on? Look for the label [Accepting Merge Requests](#i-want-to-contribute).
GitLab comes into two flavors, GitLab Community Edition (CE) our free and open
source edition, and GitLab Enterprise Edition (EE) which is our commercial
edition. Throughout this guide you will see references to CE and EE for
@ -112,8 +114,8 @@ scheduling into milestones. Labelling is a task for everyone.
Most issues will have labels for at least one of the following:
- Type: ~"feature proposal", ~bug, ~customer, etc.
- Subject: ~wiki, ~"container registry", ~ldap, ~api, etc.
- Team: ~CI, ~Discussion, ~Edge, ~Frontend, ~Platform, etc.
- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
- Team: ~CI, ~Discussion, ~Edge, ~Platform, etc.
- Priority: ~Deliverable, ~Stretch
All labels, their meaning and priority are defined on the
@ -276,7 +278,7 @@ For feature proposals for EE, open an issue on the
In order to help track the feature proposals, we have created a
[`feature proposal`][fpl] label. For the time being, users that are not members
of the project cannot add labels. You can instead ask one of the [core team]
members to add the label `feature proposal` to the issue or add the following
members to add the label ~"feature proposal" to the issue or add the following
code snippet right after your description in a new line: `~"feature proposal"`.
Please keep feature proposals as small and simple as possible, complex ones
@ -342,27 +344,29 @@ addressed.
[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127
[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue
### Technical debt
### Technical and UX debt
In order to track things that can be improved in GitLab's codebase, we created
the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
In order to track things that can be improved in GitLab's codebase,
we use the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
For user experience improvements, we use the ~"UX debt" label.
This label should be added to issues that describe things that can be improved,
shortcuts that have been taken, code that needs refactoring, features that need
additional attention, and all other things that have been left behind due to
high velocity of development.
These labels should be added to issues that describe things that can be improved,
shortcuts that have been taken, features that need additional attention, and all
other things that have been left behind due to high velocity of development.
For example, code that needs refactoring should use the ~"technical debt" label,
user experience refinements should use the ~"UX debt" label.
Everyone can create an issue, though you may need to ask for adding a specific
label, if you do not have permissions to do it by yourself. Additional labels
can be combined with the `technical debt` label, to make it easier to schedule
can be combined with these labels, to make it easier to schedule
the improvements for a release.
Issues tagged with the `technical debt` label have the same priority like issues
Issues tagged with these labels have the same priority like issues
that describe a new feature to be introduced in GitLab, and should be scheduled
for a release by the appropriate person.
Make sure to mention the merge request that the `technical debt` issue is
associated with in the description of the issue.
Make sure to mention the merge request that the ~"technical debt" issue or
~"UX debt" issue is associated with in the description of the issue.
### Stewardship

View file

@ -1 +1 @@
0.10.0
0.35.0

View file

@ -1 +1 @@
0.4.2
0.5.1

View file

@ -1 +1 @@
5.0.4
5.8.0

View file

@ -1 +1 @@
2.0.0
3.0.0

111
Gemfile
View file

@ -12,12 +12,13 @@ gem 'sprockets', '~> 3.7.0'
gem 'default_value_for', '~> 3.0.0'
# Supported DBs
gem 'mysql2', '~> 0.3.16', group: :mysql
gem 'mysql2', '~> 0.4.5', group: :mysql
gem 'pg', '~> 0.18.2', group: :postgres
gem 'rugged', '~> 0.25.1.1'
gem 'rugged', '~> 0.26.0'
gem 'grape-route-helpers', '~> 2.0.0'
gem 'faraday', '~> 0.11.0'
gem 'faraday', '~> 0.12'
# Authentication libraries
gem 'devise', '~> 4.2'
@ -26,7 +27,7 @@ gem 'doorkeeper-openid_connect', '~> 1.1.0'
gem 'omniauth', '~> 1.4.2'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-cas3', '~> 1.1.2'
gem 'omniauth-cas3', '~> 1.1.4'
gem 'omniauth-facebook', '~> 4.0.0'
gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.2'
@ -37,7 +38,7 @@ gem 'omniauth-saml', '~> 1.7.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.3.0'
gem 'omniauth-authentiq', '~> 0.3.1'
gem 'rack-oauth2', '~> 1.2.1'
gem 'jwt', '~> 1.5.6'
@ -57,10 +58,14 @@ gem 'validates_hostname', '~> 1.0.6'
# Browser detection
gem 'browser', '~> 2.2'
# GPG
gem 'gpgme'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: 'omniauth-ldap'
gem 'gitlab_omniauth-ldap', '~> 2.0.3', require: 'omniauth-ldap'
gem 'net-ldap'
# Git Wiki
# Required manually in config/initializers/gollum.rb to control load order
@ -71,7 +76,7 @@ gem 'gollum-rugged_adapter', '~> 0.4.4', require: false
gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API
gem 'grape', '~> 0.19.0'
gem 'grape', '~> 0.19.2'
gem 'grape-entity', '~> 0.6.0'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
@ -79,24 +84,25 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
gem 'hashie-forbidden_attributes'
# Pagination
gem 'kaminari', '~> 0.17.0'
gem 'kaminari', '~> 1.0'
# HAML
gem 'hamlit', '~> 2.6.1'
# Files attachments
gem 'carrierwave', '~> 1.0'
gem 'carrierwave', '~> 1.1'
# Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1'
# for backups
gem 'fog-aws', '~> 0.9'
gem 'fog-aws', '~> 1.4'
gem 'fog-core', '~> 1.44'
gem 'fog-google', '~> 0.5'
gem 'fog-local', '~> 0.3'
gem 'fog-openstack', '~> 0.1'
gem 'fog-rackspace', '~> 0.1.1'
gem 'fog-aliyun', '~> 0.1.0'
# for Google storage
gem 'google-api-client', '~> 0.8.6'
@ -109,7 +115,7 @@ gem 'seed-fu', '~> 2.3.5'
# Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0'
gem 'deckar01-task_list', '1.0.6', require: 'task_list/railtie'
gem 'deckar01-task_list', '2.0.0'
gem 'gitlab-markup', '~> 1.5.1'
gem 'redcarpet', '~> 3.4'
gem 'RedCloth', '~> 4.3.2'
@ -120,11 +126,9 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2'
gem 'asciidoctor-plantuml', '0.0.7'
gem 'rouge', '~> 2.0'
gem 'truncato', '~> 0.7.8'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
gem 'nokogiri', '~> 1.6.7', '>= 1.6.7.2'
gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0'
gem 'nokogiri', '~> 1.8.0'
# Diffs
gem 'diffy', '~> 3.1.0'
@ -145,24 +149,24 @@ gem 'acts-as-taggable-on', '~> 4.0'
# Background jobs
gem 'sidekiq', '~> 5.0'
gem 'sidekiq-cron', '~> 0.4.4'
gem 'sidekiq-cron', '~> 0.6.0'
gem 'redis-namespace', '~> 1.5.2'
gem 'sidekiq-limit_fetch', '~> 3.4'
gem 'sidekiq-limit_fetch', '~> 3.4', require: false
# Cron Parser
gem 'rufus-scheduler', '~> 3.1.10'
gem 'rufus-scheduler', '~> 3.4'
# HTTP requests
gem 'httparty', '~> 0.13.3'
# Colored output to console
gem 'rainbow', '~> 2.1.0'
gem 'rainbow', '~> 2.2'
# GitLab settings
gem 'settingslogic', '~> 2.0.9'
# Linear-time regex library for untrusted regular expressions
gem 're2', '~> 1.0.0'
gem 're2', '~> 1.1.1'
# Misc
@ -238,38 +242,52 @@ gem 'webpack-rails', '~> 0.9.10'
gem 'rack-proxy', '~> 0.6.0'
gem 'sass-rails', '~> 5.0.6'
gem 'coffee-rails', '~> 4.1.0'
gem 'uglifier', '~> 2.7.2'
gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.7'
gem 'gemojione', '~> 3.0'
gem 'gemojione', '~> 3.3'
gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0'
gem 'request_store', '~> 1.3'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0'
# Sentry integration
gem 'sentry-raven', '~> 2.4.0'
gem 'sentry-raven', '~> 2.5.3'
gem 'premailer-rails', '~> 1.9.0'
gem 'premailer-rails', '~> 1.9.7'
# I18n
gem 'ruby_parser', '~> 3.8.4', require: false
gem 'ruby_parser', '~> 3.8', require: false
gem 'rails-i18n', '~> 4.0.9'
gem 'gettext_i18n_rails', '~> 1.8.0'
gem 'gettext_i18n_rails_js', '~> 1.2.0'
gem 'gettext', '~> 3.2.2', require: false, group: :development
# Perf bar
gem 'peek', '~> 1.0.1'
gem 'peek-gc', '~> 0.0.2'
gem 'peek-host', '~> 1.0.0'
gem 'peek-mysql2', '~> 1.1.0', group: :mysql
gem 'peek-performance_bar', '~> 1.3.0'
gem 'peek-pg', '~> 1.3.0', group: :postgres
gem 'peek-rblineprof', '~> 0.2.0'
gem 'peek-redis', '~> 1.2.0'
gem 'peek-sidekiq', '~> 1.0.3'
# Metrics
group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri
gem 'method_source', '~> 0.8', require: false
gem 'influxdb', '~> 0.2', require: false
# Prometheus
gem 'prometheus-client-mmap', '~>0.7.0.beta14'
gem 'raindrops', '~> 0.18'
end
group :development do
@ -293,11 +311,11 @@ group :development, :test do
gem 'pry-rails', '~> 0.3.4'
gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0'
gem 'fuubar', '~> 2.2.0'
gem 'database_cleaner', '~> 1.5.0'
gem 'factory_girl_rails', '~> 4.7.0'
gem 'rspec-rails', '~> 3.5.0'
gem 'rspec-rails', '~> 3.6.0'
gem 'rspec-retry', '~> 0.4.5'
gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
@ -318,10 +336,10 @@ group :development, :test do
gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.1.0'
gem 'rubocop', '~> 0.47.1', require: false
gem 'rubocop-rspec', '~> 1.15.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
gem 'haml_lint', '~> 0.21.0', require: false
gem 'rubocop', '~> 0.49.1', require: false
gem 'rubocop-rspec', '~> 1.15.1', require: false
gem 'scss_lint', '~> 0.54.0', require: false
gem 'haml_lint', '~> 0.26.0', require: false
gem 'simplecov', '~> 0.14.0', require: false
gem 'flay', '~> 2.8.0', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
@ -337,10 +355,10 @@ group :development, :test do
end
group :test do
gem 'shoulda-matchers', '~> 2.8.0', require: false
gem 'shoulda-matchers', '~> 3.1.2', require: false
gem 'email_spec', '~> 1.6.0'
gem 'json-schema', '~> 2.6.2'
gem 'webmock', '~> 1.24.0'
gem 'webmock', '~> 2.3.2'
gem 'test_after_commit', '~> 1.1'
gem 'sham_rack', '~> 1.3.6'
gem 'timecop', '~> 0.8.0'
@ -357,10 +375,10 @@ gem 'html2text'
gem 'ruby-prof', '~> 0.16.2'
# OAuth
gem 'oauth2', '~> 1.3.0'
gem 'oauth2', '~> 1.4'
# Soft deletion
gem 'paranoia', '~> 2.2'
gem 'paranoia', '~> 2.3.1'
# Health check
gem 'health_check', '~> 2.6.0'
@ -369,7 +387,24 @@ gem 'health_check', '~> 2.6.0'
gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
# SSH host key support
gem 'net-ssh', '~> 4.1.0'
# Required for ED25519 SSH host key support
group :ed25519 do
gem 'rbnacl-libsodium'
gem 'rbnacl', '~> 3.2'
gem 'bcrypt_pbkdf', '~> 1.0'
end
# Gitaly GRPC client
gem 'gitaly', '~> 0.6.0'
gem 'gitaly', '~> 0.26.0'
gem 'toml-rb', '~> 0.3.15', require: false
# Feature toggles
gem 'flipper', '~> 0.10.2'
gem 'flipper-active_record', '~> 0.10.2'
# Structured logging
gem 'lograge', '~> 0.5'

View file

@ -56,6 +56,7 @@ GEM
asciidoctor-plantuml (0.0.7)
asciidoctor (~> 1.5)
ast (2.3.0)
atomic (1.1.99)
attr_encrypted (3.0.3)
encryptor (~> 3.0.0)
attr_required (1.0.0)
@ -74,6 +75,7 @@ GEM
babosa (1.0.2)
base32 (0.3.2)
bcrypt (3.1.11)
bcrypt_pbkdf (1.0.0)
benchmark-ips (2.3.0)
better_errors (2.1.1)
coderay (>= 1.0.0)
@ -85,6 +87,7 @@ GEM
bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
bootstrap_form (2.7.0)
brakeman (3.6.1)
browser (2.2.0)
builder (3.2.3)
@ -105,7 +108,7 @@ GEM
capybara-screenshot (1.0.14)
capybara (>= 1.0, < 3)
launchy
carrierwave (1.0.0)
carrierwave (1.1.0)
activemodel (>= 4.0.0)
activesupport (>= 4.0.0)
mime-types (>= 1.16)
@ -120,20 +123,15 @@ GEM
coderay (1.1.1)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
coffee-rails (4.1.1)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.1.x)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.10.0)
colorize (0.7.7)
concurrent-ruby (1.0.5)
concurrent-ruby-ext (1.0.5)
concurrent-ruby (= 1.0.5)
connection_pool (2.2.1)
crack (0.4.3)
safe_yaml (~> 1.0.0)
creole (0.5.0)
css_parser (1.4.1)
css_parser (1.5.0)
addressable
d3_rails (3.5.11)
railties (>= 3.1.0)
@ -141,10 +139,8 @@ GEM
database_cleaner (1.5.3)
debug_inspector (0.0.2)
debugger-ruby_core_source (1.3.8)
deckar01-task_list (1.0.6)
activesupport (~> 4.0)
deckar01-task_list (2.0.0)
html-pipeline
rack (~> 1.0)
default_value_for (3.0.2)
activerecord (>= 3.2.0, < 5.1)
descendants_tracker (0.0.4)
@ -161,7 +157,7 @@ GEM
devise (~> 4.0)
railties
rotp (~> 2.0)
diff-lcs (1.2.5)
diff-lcs (1.3)
diffy (3.1.0)
docile (1.1.5)
domain_name (0.5.20161021)
@ -181,8 +177,10 @@ GEM
equalizer (0.0.11)
erubis (2.7.0)
escape_utils (1.1.1)
et-orbi (1.0.3)
tzinfo
eventmachine (1.0.8)
excon (0.55.0)
excon (0.57.1)
execjs (2.6.0)
expression_parser (0.9.0)
extlib (0.9.16)
@ -191,7 +189,7 @@ GEM
factory_girl_rails (4.7.0)
factory_girl (~> 4.7.0)
railties (>= 3.0.0)
faraday (0.11.0)
faraday (0.12.1)
multipart-post (>= 1.2, < 3)
faraday_middleware (0.11.0.1)
faraday (>= 0.7.4, < 1.0)
@ -206,29 +204,38 @@ GEM
path_expander (~> 1.0)
ruby_parser (~> 3.0)
sexp_processor (~> 4.0)
flipper (0.10.2)
flipper-active_record (0.10.2)
activerecord (>= 3.2, < 6)
flipper (~> 0.10.2)
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
fog-aws (0.13.0)
fog-aliyun (0.1.0)
fog-core (~> 1.27)
fog-json (~> 1.0)
ipaddress (~> 0.8)
xml-simple (~> 1.1)
fog-aws (1.4.0)
fog-core (~> 1.38)
fog-json (~> 1.0)
fog-xml (~> 0.1)
ipaddress (~> 0.8)
fog-core (1.44.1)
fog-core (1.44.3)
builder
excon (~> 0.49)
formatador (~> 0.2)
fog-google (0.5.0)
fog-google (0.5.3)
fog-core
fog-json
fog-xml
fog-json (1.0.2)
fog-core (~> 1.0)
multi_json (~> 1.10)
fog-local (0.3.0)
fog-local (0.3.1)
fog-core (~> 1.27)
fog-openstack (0.1.6)
fog-core (>= 1.39)
fog-openstack (0.1.21)
fog-core (>= 1.40)
fog-json (>= 1.0)
ipaddress (>= 0.8)
fog-rackspace (0.1.1)
@ -244,12 +251,12 @@ GEM
foreman (0.78.0)
thor (~> 0.19.1)
formatador (0.2.5)
fuubar (2.0.0)
rspec (~> 3.0)
fuubar (2.2.0)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
gemnasium-gitlab-service (0.2.6)
rugged (~> 0.21)
gemojione (3.0.1)
gemojione (3.3.0)
json
get_process_mem (0.2.0)
gettext (3.2.2)
@ -263,7 +270,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly (0.6.0)
gitaly (0.26.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@ -271,7 +278,7 @@ GEM
escape_utils (~> 1.1.0)
mime-types (>= 1.19)
rugged (>= 0.23.0b)
github-markup (1.4.0)
github-markup (1.6.1)
gitlab-flowdock-git-hook (1.0.1)
flowdock (~> 0.7)
gitlab-grit (>= 2.4.1)
@ -282,22 +289,23 @@ GEM
mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3)
gitlab-markup (1.5.1)
gitlab_omniauth-ldap (1.2.1)
net-ldap (~> 0.9)
omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.3)
gitlab_omniauth-ldap (2.0.3)
net-ldap (~> 0.16)
omniauth (~> 1.3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5)
globalid (0.3.7)
activesupport (>= 4.1.0)
gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1)
gollum-lib (4.2.1)
github-markup (~> 1.4.0)
gollum-lib (4.2.7)
gemojione (~> 3.2)
github-markup (~> 1.6)
gollum-grit_adapter (~> 1.0)
nokogiri (~> 1.6.4)
rouge (~> 2.0)
sanitize (~> 2.1.0)
stringex (~> 2.5.1)
nokogiri (>= 1.6.1, < 2.0)
rouge (~> 2.1)
sanitize (~> 2.1)
stringex (~> 2.6)
gollum-rugged_adapter (0.4.4)
mime-types (>= 1.15)
rugged (~> 0.25)
@ -317,7 +325,7 @@ GEM
multi_json (~> 1.10)
retriable (~> 1.4)
signet (~> 0.6)
google-protobuf (3.2.0.2)
google-protobuf (3.3.0)
googleauth (0.5.1)
faraday (~> 0.9)
jwt (~> 1.4)
@ -326,34 +334,41 @@ GEM
multi_json (~> 1.11)
os (~> 0.9)
signet (~> 0.7)
grape (0.19.1)
gpgme (2.0.13)
mini_portile2 (~> 2.1)
grape (0.19.2)
activesupport
builder
hashie (>= 2.1.0)
multi_json (>= 1.3.2)
multi_xml (>= 0.5.2)
mustermann-grape (~> 0.4.0)
mustermann-grape (~> 1.0.0)
rack (>= 1.3.0)
rack-accept
virtus (>= 1.0.0)
grape-entity (0.6.0)
activesupport
multi_json (>= 1.3.2)
grpc (1.2.5)
grape-route-helpers (2.0.0)
activesupport
grape (~> 0.16, >= 0.16.0)
rake
grpc (1.4.0)
google-protobuf (~> 3.1)
googleauth (~> 0.5.1)
haml (4.0.7)
tilt
haml_lint (0.21.0)
haml (~> 4.0)
haml_lint (0.26.0)
haml (>= 4.0, < 5.1)
rainbow
rake (>= 10, < 13)
rubocop (>= 0.47.0)
rubocop (>= 0.49.0)
sysexits (~> 1.1)
hamlit (2.6.1)
temple (~> 0.7.6)
thor
tilt
hashdiff (0.3.2)
hashdiff (0.3.4)
hashie (3.5.5)
hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0)
@ -381,7 +396,7 @@ GEM
json (~> 1.8)
multi_xml (>= 0.5.2)
httpclient (2.8.2)
i18n (0.8.1)
i18n (0.8.6)
ice_nine (0.11.2)
influxdb (0.2.3)
cause
@ -405,9 +420,18 @@ GEM
json-schema (2.6.2)
addressable (~> 2.3.8)
jwt (1.5.6)
kaminari (0.17.0)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
kaminari (1.0.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.0.1)
kaminari-activerecord (= 1.0.1)
kaminari-core (= 1.0.1)
kaminari-actionview (1.0.1)
actionview
kaminari-core (= 1.0.1)
kaminari-activerecord (1.0.1)
activerecord
kaminari-core (= 1.0.1)
kaminari-core (1.0.1)
kgio (2.10.0)
knapsack (1.11.0)
rake
@ -437,35 +461,39 @@ GEM
logging (2.2.2)
little-plugger (~> 1.1)
multi_json (~> 1.10)
lograge (0.5.1)
actionpack (>= 4, < 5.2)
activesupport (>= 4, < 5.2)
railties (>= 4, < 5.2)
loofah (2.0.3)
nokogiri (>= 1.5.9)
mail (2.6.5)
mail (2.6.6)
mime-types (>= 1.16, < 4)
mail_room (0.9.1)
memoist (0.15.0)
method_source (0.8.2)
mime-types (2.99.3)
mimemagic (0.3.0)
mini_portile2 (2.1.0)
mini_portile2 (2.2.0)
minitest (5.7.0)
mmap2 (2.2.7)
mousetrap-rails (1.4.6)
multi_json (1.12.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
mustermann (0.4.0)
tool (~> 0.2)
mustermann-grape (0.4.0)
mustermann (= 0.4.0)
mysql2 (0.3.20)
net-ldap (0.12.1)
net-ssh (3.0.1)
mustermann (1.0.0)
mustermann-grape (1.0.0)
mustermann (~> 1.0.0)
mysql2 (0.4.5)
net-ldap (0.16.0)
net-ssh (4.1.0)
netrc (0.11.0)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
nokogiri (1.8.0)
mini_portile2 (~> 2.2.0)
numerizer (0.1.1)
oauth (0.5.1)
oauth2 (1.3.1)
faraday (>= 0.8, < 0.12)
oauth2 (1.4.0)
faraday (>= 0.8, < 0.13)
jwt (~> 1.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
@ -478,15 +506,15 @@ GEM
rack (>= 1.0, < 3)
omniauth-auth0 (1.4.1)
omniauth-oauth2 (~> 1.1)
omniauth-authentiq (0.3.0)
omniauth-authentiq (0.3.1)
omniauth-oauth2 (~> 1.3, >= 1.3.1)
omniauth-azure-oauth2 (0.0.6)
jwt (~> 1.0)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1)
omniauth-cas3 (1.1.3)
omniauth-cas3 (1.1.4)
addressable (~> 2.3)
nokogiri (~> 1.6.6)
nokogiri (~> 1.7, >= 1.7.1)
omniauth (~> 1.2)
omniauth-facebook (4.0.0)
omniauth-oauth2 (~> 1.2)
@ -530,11 +558,42 @@ GEM
rubypants (~> 0.2)
orm_adapter (0.5.0)
os (0.9.6)
paranoia (2.2.0)
activerecord (>= 4.0, < 5.1)
parallel (1.11.2)
paranoia (2.3.1)
activerecord (>= 4.0, < 5.2)
parser (2.4.0.0)
ast (~> 2.2)
path_expander (1.0.1)
peek (1.0.1)
concurrent-ruby (>= 0.9.0)
concurrent-ruby-ext (>= 0.9.0)
railties (>= 4.0.0)
peek-gc (0.0.2)
peek
peek-host (1.0.0)
peek
peek-mysql2 (1.1.0)
atomic (>= 1.0.0)
mysql2
peek
peek-performance_bar (1.3.0)
peek (>= 0.1.0)
peek-pg (1.3.0)
concurrent-ruby
concurrent-ruby-ext
peek
pg
peek-rblineprof (0.2.0)
peek
rblineprof
peek-redis (1.2.0)
atomic (>= 1.0.0)
peek
redis
peek-sidekiq (1.0.3)
atomic (>= 1.0.0)
peek
sidekiq
pg (0.18.4)
po_to_json (1.0.1)
json (>= 1.6.0)
@ -543,14 +602,22 @@ GEM
cliver (~> 0.3.1)
multi_json (~> 1.0)
websocket-driver (>= 0.2.0)
posix-spawn (0.3.11)
posix-spawn (0.3.13)
powerpack (0.1.1)
premailer (1.8.6)
css_parser (>= 1.3.6)
premailer (1.10.4)
addressable
css_parser (>= 1.4.10)
htmlentities (>= 4.0.0)
premailer-rails (1.9.2)
premailer-rails (1.9.7)
actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
proc_to_ast (0.1.0)
coderay
parser
unparser
procto (0.0.3)
prometheus-client-mmap (0.7.0.beta14)
mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
@ -561,7 +628,7 @@ GEM
pry-rails (0.3.5)
pry (>= 0.9.10)
pyu-ruby-sasl (0.0.3.3)
rack (1.6.5)
rack (1.6.8)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (4.4.1)
@ -598,19 +665,27 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
rails-i18n (4.0.9)
i18n (~> 0.7)
railties (~> 4.0)
railties (4.2.8)
actionpack (= 4.2.8)
activesupport (= 4.2.8)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.1.0)
raindrops (0.17.0)
rake (10.5.0)
rainbow (2.2.2)
rake
raindrops (0.18.0)
rake (12.0.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rbnacl (3.4.0)
ffi
rbnacl-libsodium (1.0.11)
rbnacl (>= 3.0.1)
rdoc (4.2.2)
json (~> 1.4)
re2 (1.0.0)
re2 (1.1.1)
recaptcha (3.0.0)
json
recursive-open-struct (1.0.0)
@ -644,47 +719,44 @@ GEM
retriable (1.4.1)
rinku (2.0.0)
rotp (2.1.2)
rouge (2.0.7)
rouge (2.1.0)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
rspec (3.5.0)
rspec-core (~> 3.5.0)
rspec-expectations (~> 3.5.0)
rspec-mocks (~> 3.5.0)
rspec-core (3.5.0)
rspec-support (~> 3.5.0)
rspec-expectations (3.5.0)
rspec-core (3.6.0)
rspec-support (~> 3.6.0)
rspec-expectations (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.5.0)
rspec-mocks (3.5.0)
rspec-support (~> 3.6.0)
rspec-mocks (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.5.0)
rspec-rails (3.5.0)
rspec-support (~> 3.6.0)
rspec-rails (3.6.0)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 3.5.0)
rspec-expectations (~> 3.5.0)
rspec-mocks (~> 3.5.0)
rspec-support (~> 3.5.0)
rspec-core (~> 3.6.0)
rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.6.0)
rspec-support (~> 3.6.0)
rspec-retry (0.4.5)
rspec-core
rspec-set (0.1.3)
rspec-support (3.5.0)
rspec-support (3.6.0)
rspec_profiling (0.0.5)
activerecord
pg
rails
sqlite3
rubocop (0.47.1)
rubocop (0.49.1)
parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
rubocop-rspec (1.15.0)
rubocop-rspec (1.15.1)
rubocop (>= 0.42.0)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
@ -692,13 +764,14 @@ GEM
ruby-progressbar (1.8.1)
ruby-saml (1.4.1)
nokogiri (>= 1.5.10)
ruby_parser (3.8.4)
ruby_parser (3.9.0)
sexp_processor (~> 4.1)
rubyntlm (0.5.2)
rubyntlm (0.6.2)
rubypants (0.2.0)
rubyzip (1.2.1)
rufus-scheduler (3.1.10)
rugged (0.25.1.1)
rufus-scheduler (3.4.0)
et-orbi (~> 1.0)
rugged (0.26.0)
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
@ -712,31 +785,30 @@ GEM
sawyer (0.8.1)
addressable (>= 2.3.5, < 2.6)
faraday (~> 0.8, < 1.0)
scss_lint (0.47.1)
rake (>= 0.9, < 11)
sass (~> 3.4.15)
scss_lint (0.54.0)
rake (>= 0.9, < 13)
sass (~> 3.4.20)
securecompare (1.0.0)
seed-fu (2.3.6)
activerecord (>= 3.1)
activesupport (>= 3.1)
select2-rails (3.5.9.3)
thor (~> 0.14)
sentry-raven (2.4.0)
sentry-raven (2.5.3)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
sexp_processor (4.8.0)
sexp_processor (4.9.0)
sham_rack (1.3.6)
rack
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
sidekiq (5.0.0)
shoulda-matchers (3.1.2)
activesupport (>= 4.0.0)
sidekiq (5.0.4)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
redis (~> 3.3, >= 3.3.3)
sidekiq-cron (0.4.4)
redis-namespace (>= 1.5.2)
rufus-scheduler (>= 2.0.24)
sidekiq-cron (0.6.0)
rufus-scheduler (>= 3.3.0)
sidekiq (>= 4.2.1)
sidekiq-limit_fetch (3.4.0)
sidekiq (>= 4)
@ -784,7 +856,7 @@ GEM
state_machines-activerecord (0.4.0)
activerecord (>= 4.1, < 5.1)
state_machines-activemodel (>= 0.3.0)
stringex (2.5.2)
stringex (2.7.1)
sys-filesystem (1.1.6)
ffi
sysexits (1.2.0)
@ -803,11 +875,10 @@ GEM
timfel-krb5-auth (0.8.3)
toml-rb (0.3.15)
citrus (~> 3.0, > 3.0)
tool (0.2.3)
truncato (0.7.8)
truncato (0.7.10)
htmlentities (~> 4.3.1)
nokogiri (~> 1.6.1)
tzinfo (1.2.2)
nokogiri (~> 1.8.0, >= 1.7.0)
tzinfo (1.2.3)
thread_safe (~> 0.1)
u2f (0.2.1)
uglifier (2.7.2)
@ -817,7 +888,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.2)
unicode-display_width (1.1.3)
unicode-display_width (1.3.0)
unicorn (5.1.0)
kgio (~> 2.6)
raindrops (~> 0.7)
@ -838,7 +909,7 @@ GEM
vmstat (2.3.0)
warden (1.2.6)
rack (>= 1.0)
webmock (1.24.6)
webmock (2.3.2)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
@ -874,27 +945,28 @@ DEPENDENCIES
awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
base32 (~> 0.3.0)
bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0)
better_errors (~> 2.1.0)
binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0)
bootstrap_form (~> 2.7.0)
brakeman (~> 3.6.0)
browser (~> 2.2)
bullet (~> 5.5.0)
bundler-audit (~> 0.5.0)
capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 1.0)
carrierwave (~> 1.1)
charlock_holmes (~> 0.7.3)
chronic (~> 0.10.2)
chronic_duration (~> 0.10.6)
coffee-rails (~> 4.1.0)
concurrent-ruby (~> 1.0.5)
connection_pool (~> 2.0)
creole (~> 0.5.0)
d3_rails (~> 3.5.0)
database_cleaner (~> 1.5.0)
deckar01-task_list (= 1.0.6)
deckar01-task_list (= 2.0.0)
default_value_for (~> 3.0.0)
devise (~> 4.2)
devise-two-factor (~> 3.0.0)
@ -905,10 +977,13 @@ DEPENDENCIES
email_reply_trimmer (~> 0.1)
email_spec (~> 1.6.0)
factory_girl_rails (~> 4.7.0)
faraday (~> 0.11.0)
faraday (~> 0.12)
ffaker (~> 2.4)
flay (~> 2.8.0)
fog-aws (~> 0.9)
flipper (~> 0.10.2)
flipper-active_record (~> 0.10.2)
fog-aliyun (~> 0.1.0)
fog-aws (~> 1.4)
fog-core (~> 1.44)
fog-google (~> 0.5)
fog-local (~> 0.3)
@ -916,24 +991,26 @@ DEPENDENCIES
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7)
foreman (~> 0.78.0)
fuubar (~> 2.0.0)
fuubar (~> 2.2.0)
gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.0)
gemojione (~> 3.3)
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly (~> 0.6.0)
gitaly (~> 0.26.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.1)
gitlab_omniauth-ldap (~> 1.2.1)
gitlab_omniauth-ldap (~> 2.0.3)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0)
google-api-client (~> 0.8.6)
grape (~> 0.19.0)
gpgme
grape (~> 0.19.2)
grape-entity (~> 0.6.0)
haml_lint (~> 0.21.0)
grape-route-helpers (~> 2.0.0)
haml_lint (~> 0.26.0)
hamlit (~> 2.6.1)
hashie-forbidden_attributes
health_check (~> 2.6.0)
@ -947,28 +1024,30 @@ DEPENDENCIES
jquery-rails (~> 4.1.0)
json-schema (~> 2.6.2)
jwt (~> 1.5.6)
kaminari (~> 0.17.0)
kaminari (~> 1.0)
knapsack (~> 1.11.0)
kubeclient (~> 2.2.0)
letter_opener_web (~> 1.3.0)
license_finder (~> 2.1.0)
licensee (~> 8.7.0)
lograge (~> 0.5)
loofah (~> 2.0.3)
mail_room (~> 0.9.1)
method_source (~> 0.8)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16)
net-ssh (~> 3.0.1)
nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.3.0)
mysql2 (~> 0.4.5)
net-ldap
net-ssh (~> 4.1.0)
nokogiri (~> 1.8.0)
oauth2 (~> 1.4)
octokit (~> 4.6.2)
oj (~> 2.17.4)
omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1)
omniauth-authentiq (~> 0.3.0)
omniauth-authentiq (~> 0.3.1)
omniauth-azure-oauth2 (~> 0.0.6)
omniauth-cas3 (~> 1.1.2)
omniauth-cas3 (~> 1.1.4)
omniauth-facebook (~> 4.0.0)
omniauth-github (~> 1.1.1)
omniauth-gitlab (~> 1.0.2)
@ -980,10 +1059,20 @@ DEPENDENCIES
omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12)
paranoia (~> 2.2)
paranoia (~> 2.3.1)
peek (~> 1.0.1)
peek-gc (~> 0.0.2)
peek-host (~> 1.0.0)
peek-mysql2 (~> 1.1.0)
peek-performance_bar (~> 1.3.0)
peek-pg (~> 1.3.0)
peek-rblineprof (~> 0.2.0)
peek-redis (~> 1.2.0)
peek-sidekiq (~> 1.0.3)
pg (~> 0.18.2)
poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0)
premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.7.0.beta14)
pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1)
@ -992,10 +1081,14 @@ DEPENDENCIES
rack-proxy (~> 0.6.0)
rails (= 4.2.8)
rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0)
rails-i18n (~> 4.0.9)
rainbow (~> 2.2)
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rbnacl (~> 3.2)
rbnacl-libsodium
rdoc (~> 4.2)
re2 (~> 1.0.0)
re2 (~> 1.1.1)
recaptcha (~> 3.0)
redcarpet (~> 3.4)
redis (~> 3.2)
@ -1005,28 +1098,28 @@ DEPENDENCIES
responders (~> 2.0)
rouge (~> 2.0)
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.5.0)
rspec-rails (~> 3.6.0)
rspec-retry (~> 0.4.5)
rspec-set (~> 0.1.3)
rspec_profiling (~> 0.0.5)
rubocop (~> 0.47.1)
rubocop-rspec (~> 1.15.0)
rubocop (~> 0.49.1)
rubocop-rspec (~> 1.15.1)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2)
ruby_parser (~> 3.8.4)
rufus-scheduler (~> 3.1.10)
rugged (~> 0.25.1.1)
ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4)
rugged (~> 0.26.0)
sanitize (~> 2.0)
sass-rails (~> 5.0.6)
scss_lint (~> 0.47.0)
scss_lint (~> 0.54.0)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
sentry-raven (~> 2.4.0)
sentry-raven (~> 2.5.3)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
shoulda-matchers (~> 2.8.0)
shoulda-matchers (~> 3.1.2)
sidekiq (~> 5.0)
sidekiq-cron (~> 0.4.4)
sidekiq-cron (~> 0.6.0)
sidekiq-limit_fetch (~> 3.4)
simplecov (~> 0.14.0)
slack-notifier (~> 1.5.1)
@ -1043,7 +1136,7 @@ DEPENDENCIES
thin (~> 1.7.0)
timecop (~> 0.8.0)
toml-rb (~> 0.3.15)
truncato (~> 0.7.8)
truncato (~> 0.7.9)
u2f (~> 0.2.1)
uglifier (~> 2.7.2)
underscore-rails (~> 1.8.0)
@ -1054,9 +1147,9 @@ DEPENDENCIES
version_sorter (~> 2.1.0)
virtus (~> 1.0.1)
vmstat (~> 2.3.0)
webmock (~> 1.24.0)
webmock (~> 2.3.2)
webpack-rails (~> 0.9.10)
wikicloth (= 0.8.1)
BUNDLED WITH
1.15.0
1.15.3

View file

@ -128,7 +128,7 @@ information, see
### After the 7th
Once the stable branch is frozen, only fixes for regressions (bugs introduced in that same release)
Once the stable branch is frozen, only fixes for [regressions](#regressions)
and security issues will be cherry-picked into the stable branch.
Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch.
These fixes will be shipped in the next RC for that release if it is before the 22nd.
@ -158,6 +158,24 @@ release should have the correct milestone assigned _and_ have the label
Merge requests without a milestone and this label will
not be merged into any stable branches.
### Regressions
A regression for a particular monthly release is a bug that exists in that
release, but wasn't present in the release before. This includes bugs in
features that were only added in that monthly release. Every regression **must**
have the milestone of the release it was introduced in - if a regression doesn't
have a milestone, it might be 'just' a bug!
For instance, if 10.5.0 adds a feature, and that feature doesn't work correctly,
then this is a regression in 10.5. If 10.5.1 then fixes that, but 10.5.3 somehow
reintroduces the bug, then this bug is still a regression in 10.5.
Because GitLab.com runs release candidates of new releases, a regression can be
reported in a release before its 'official' release date on the 22nd of the
month. When we say 'the most recent monthly release', this can refer to either
the version currently running on GitLab.com, or the most recent version
available in the package repositories.
## Release retrospective and kickoff
### Retrospective

View file

@ -9,7 +9,7 @@
## Test coverage
- [![Ruby coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) Ruby
- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=rake+karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript
- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript
## Canonical source

View file

@ -1 +1 @@
9.2.10
9.5.4

View file

@ -0,0 +1,4 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 120" enable-background="new 0 0 12 120">
<path d="m12 6c0-3.309-2.691-6-6-6s-6 2.691-6 6c0 2.967 2.167 5.431 5 5.91v108.09h2v-108.09c2.833-.479 5-2.943 5-5.91m-6 4c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4"/>
</svg>

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -5,7 +5,8 @@ import Cookies from 'js-cookie';
class Activities {
constructor() {
Pager.init(20, true, false, this.updateTooltips);
Pager.init(20, true, false, data => data, this.updateTooltips);
$('.event-filter-link').on('click', (e) => {
e.preventDefault();
this.toggleFilter(e.currentTarget);
@ -19,7 +20,7 @@ class Activities {
reloadActivities() {
$('.content_list').html('');
Pager.init(20, true, false, this.updateTooltips);
Pager.init(20, true, false, data => data, this.updateTooltips);
}
toggleFilter(sender) {

View file

@ -10,7 +10,7 @@ class AjaxLoadingSpinner {
e.target.setAttribute('disabled', '');
const iconElement = e.target.querySelector('i');
// get first fa- icon
const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g).first();
const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g)[0];
iconElement.dataset.icon = originalIcon;
AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
$(e.target).off('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);

View file

@ -1,148 +1,190 @@
/* eslint-disable func-names, space-before-function-paren, quotes, object-shorthand, camelcase, no-var, comma-dangle, prefer-arrow-callback, quote-props, no-param-reassign, max-len */
import $ from 'jquery';
var Api = {
groupsPath: "/api/:version/groups.json",
groupPath: "/api/:version/groups/:id.json",
namespacesPath: "/api/:version/namespaces.json",
groupProjectsPath: "/api/:version/groups/:id/projects.json",
projectsPath: "/api/:version/projects.json?simple=true",
labelsPath: "/:namespace_path/:project_path/labels",
licensePath: "/api/:version/templates/licenses/:key",
gitignorePath: "/api/:version/templates/gitignores/:key",
gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
dockerfilePath: "/api/:version/templates/dockerfiles/:key",
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
group: function(group_id, callback) {
var url = Api.buildUrl(Api.groupPath)
.replace(':id', group_id);
const Api = {
groupsPath: '/api/:version/groups.json',
groupPath: '/api/:version/groups/:id.json',
namespacesPath: '/api/:version/namespaces.json',
groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json?simple=true',
labelsPath: '/:namespace_path/:project_path/labels',
licensePath: '/api/:version/templates/licenses/:key',
gitignorePath: '/api/:version/templates/gitignores/:key',
gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key',
dockerfilePath: '/api/:version/templates/dockerfiles/:key',
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
usersPath: '/api/:version/users.json',
commitPath: '/api/:version/projects/:id/repository/commits',
group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath)
.replace(':id', groupId);
return $.ajax({
url: url,
dataType: "json"
}).done(function(group) {
return callback(group);
});
url,
dataType: 'json',
})
.done(group => callback(group));
},
// Return groups list. Filtered by query
groups: function(query, options, callback) {
var url = Api.buildUrl(Api.groupsPath);
groups(query, options, callback) {
const url = Api.buildUrl(Api.groupsPath);
return $.ajax({
url: url,
data: $.extend({
search: query,
per_page: 20
}, options),
dataType: "json"
}).done(function(groups) {
return callback(groups);
});
},
// Return namespaces list. Filtered by query
namespaces: function(query, callback) {
var url = Api.buildUrl(Api.namespacesPath);
return $.ajax({
url: url,
data: {
search: query,
per_page: 20
},
dataType: "json"
}).done(function(namespaces) {
return callback(namespaces);
});
},
// Return projects list. Filtered by query
projects: function(query, options, callback) {
var url = Api.buildUrl(Api.projectsPath);
return $.ajax({
url: url,
data: $.extend({
url,
data: Object.assign({
search: query,
per_page: 20,
membership: true
}, options),
dataType: "json"
}).done(function(projects) {
return callback(projects);
});
dataType: 'json',
})
.done(groups => callback(groups));
},
newLabel: function(namespace_path, project_path, data, callback) {
var url = Api.buildUrl(Api.labelsPath)
.replace(':namespace_path', namespace_path)
.replace(':project_path', project_path);
// Return namespaces list. Filtered by query
namespaces(query, callback) {
const url = Api.buildUrl(Api.namespacesPath);
return $.ajax({
url: url,
type: "POST",
data: { 'label': data },
dataType: "json"
}).done(function(label) {
return callback(label);
}).error(function(message) {
return callback(message.responseJSON);
});
},
// Return group projects list. Filtered by query
groupProjects: function(group_id, query, callback) {
var url = Api.buildUrl(Api.groupProjectsPath)
.replace(':id', group_id);
return $.ajax({
url: url,
url,
data: {
search: query,
per_page: 20
per_page: 20,
},
dataType: "json"
}).done(function(projects) {
return callback(projects);
});
dataType: 'json',
}).done(namespaces => callback(namespaces));
},
// Return projects list. Filtered by query
projects(query, options, callback) {
const url = Api.buildUrl(Api.projectsPath);
return $.ajax({
url,
data: Object.assign({
search: query,
per_page: 20,
membership: true,
}, options),
dataType: 'json',
})
.done(projects => callback(projects));
},
newLabel(namespacePath, projectPath, data, callback) {
const url = Api.buildUrl(Api.labelsPath)
.replace(':namespace_path', namespacePath)
.replace(':project_path', projectPath);
return $.ajax({
url,
type: 'POST',
data: { label: data },
dataType: 'json',
})
.done(label => callback(label))
.fail(message => callback(message.responseJSON));
},
// Return group projects list. Filtered by query
groupProjects(groupId, query, callback) {
const url = Api.buildUrl(Api.groupProjectsPath)
.replace(':id', groupId);
return $.ajax({
url,
data: {
search: query,
per_page: 20,
},
dataType: 'json',
})
.done(projects => callback(projects));
},
commitMultiple(id, data, callback) {
const url = Api.buildUrl(Api.commitPath)
.replace(':id', id);
return $.ajax({
url,
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data),
dataType: 'json',
})
.done(commitData => callback(commitData))
.fail(message => callback(message.responseJSON));
},
// Return text for a specific license
licenseText: function(key, data, callback) {
var url = Api.buildUrl(Api.licensePath)
licenseText(key, data, callback) {
const url = Api.buildUrl(Api.licensePath)
.replace(':key', key);
return $.ajax({
url: url,
data: data
}).done(function(license) {
return callback(license);
});
url,
data,
})
.done(license => callback(license));
},
gitignoreText: function(key, callback) {
var url = Api.buildUrl(Api.gitignorePath)
gitignoreText(key, callback) {
const url = Api.buildUrl(Api.gitignorePath)
.replace(':key', key);
return $.get(url, function(gitignore) {
return callback(gitignore);
});
return $.get(url, gitignore => callback(gitignore));
},
gitlabCiYml: function(key, callback) {
var url = Api.buildUrl(Api.gitlabCiYmlPath)
gitlabCiYml(key, callback) {
const url = Api.buildUrl(Api.gitlabCiYmlPath)
.replace(':key', key);
return $.get(url, function(file) {
return callback(file);
});
return $.get(url, file => callback(file));
},
dockerfileYml: function(key, callback) {
var url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
dockerfileYml(key, callback) {
const url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
$.get(url, callback);
},
issueTemplate: function(namespacePath, projectPath, key, type, callback) {
var url = Api.buildUrl(Api.issuableTemplatePath)
issueTemplate(namespacePath, projectPath, key, type, callback) {
const url = Api.buildUrl(Api.issuableTemplatePath)
.replace(':key', key)
.replace(':type', type)
.replace(':project_path', projectPath)
.replace(':namespace_path', namespacePath);
$.ajax({
url: url,
dataType: 'json'
}).done(function(file) {
callback(null, file);
}).error(callback);
url,
dataType: 'json',
})
.done(file => callback(null, file))
.fail(callback);
},
buildUrl: function(url) {
users(query, options) {
const url = Api.buildUrl(this.usersPath);
return Api.wrapAjaxCall({
url,
data: Object.assign({
search: query,
per_page: 20,
}, options),
dataType: 'json',
});
},
buildUrl(url) {
let urlRoot = '';
if (gon.relative_url_root != null) {
url = gon.relative_url_root + url;
urlRoot = gon.relative_url_root;
}
return url.replace(':version', gon.api_version);
}
return urlRoot + url.replace(':version', gon.api_version);
},
wrapAjaxCall(options) {
return new Promise((resolve, reject) => {
// jQuery 2 is not Promises/A+ compatible (missing catch)
$.ajax(options) // eslint-disable-line promise/catch-or-return
.then(data => resolve(data),
(jqXHR, textStatus, errorThrown) => {
const error = new Error(`${options.url}: ${errorThrown}`);
error.textStatus = textStatus;
reject(error);
},
);
});
},
};
window.Api = Api;
export default Api;

View file

@ -1,12 +1,8 @@
/* eslint-disable class-methods-use-this */
/* global Flash */
import _ from 'underscore';
import Cookies from 'js-cookie';
import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json';
import { glEmojiTag } from './behaviors/gl_emoji';
import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
const requestAnimationFrame = window.requestAnimationFrame ||
@ -16,8 +12,6 @@ const requestAnimationFrame = window.requestAnimationFrame ||
const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence
let categoryMap = null;
const categoryLabelMap = {
activity: 'Activity',
people: 'People',
@ -29,186 +23,144 @@ const categoryLabelMap = {
flags: 'Flags',
};
function buildCategoryMap() {
return Object.keys(emojiMap).reduce((currentCategoryMap, emojiNameKey) => {
const emojiInfo = emojiMap[emojiNameKey];
if (currentCategoryMap[emojiInfo.category]) {
currentCategoryMap[emojiInfo.category].push(emojiNameKey);
class AwardsHandler {
constructor(emoji) {
this.emoji = emoji;
this.eventListeners = [];
// If the user shows intent let's pre-build the menu
this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => {
const $menu = $('.emoji-menu');
if ($menu.length === 0) {
requestAnimationFrame(() => {
this.createEmojiMenu();
});
}
});
this.registerEventListener('on', $(document), 'click', '.js-add-award', (e) => {
e.stopPropagation();
e.preventDefault();
this.showEmojiMenu($(e.currentTarget));
});
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) {
if ($('.emoji-menu').is(':visible')) {
$('.js-add-award.is-active').removeClass('is-active');
$('.emoji-menu').removeClass('is-visible');
}
}
});
this.registerEventListener('on', $(document), 'click', '.js-emoji-btn', (e) => {
e.preventDefault();
const $target = $(e.currentTarget);
const $glEmojiElement = $target.find('gl-emoji');
const $spriteIconElement = $target.find('.icon');
const emojiName = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
$target.closest('.js-awards-block').addClass('current');
this.addAward(this.getVotesBlock(), this.getAwardUrl(), emojiName);
});
}
registerEventListener(method = 'on', element, ...args) {
element[method].call(element, ...args);
this.eventListeners.push({
element,
args,
});
}
showEmojiMenu($addBtn) {
if ($addBtn.hasClass('js-note-emoji')) {
$addBtn.closest('.note').find('.js-awards-block').addClass('current');
} else {
$addBtn.closest('.js-awards-block').addClass('current');
}
return currentCategoryMap;
}, {
activity: [],
people: [],
nature: [],
food: [],
travel: [],
objects: [],
symbols: [],
flags: [],
});
}
function renderCategory(name, emojiList, opts = {}) {
return `
<h5 class="emoji-menu-title">
${name}
</h5>
<ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}">
${emojiList.map(emojiName => `
<li class="emoji-menu-list-item">
<button class="emoji-menu-btn text-center js-emoji-btn" type="button">
${glEmojiTag(emojiName, {
sprite: true,
})}
</button>
</li>
`).join('\n')}
</ul>
`;
}
function AwardsHandler() {
this.eventListeners = [];
this.aliases = emojiAliases;
// If the user shows intent let's pre-build the menu
this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => {
const $menu = $('.emoji-menu');
if ($menu.length === 0) {
requestAnimationFrame(() => {
this.createEmojiMenu();
const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent();
const $userAuthored = this.isUserAuthored($addBtn);
if ($menu.length) {
if ($menu.is('.is-visible')) {
$addBtn.removeClass('is-active');
$menu.removeClass('is-visible');
$('.js-emoji-menu-search').blur();
} else {
$addBtn.addClass('is-active');
this.positionMenu($menu, $addBtn);
$menu.addClass('is-visible');
$('.js-emoji-menu-search').focus();
}
} else {
$addBtn.addClass('is-loading is-active');
this.createEmojiMenu(() => {
const $createdMenu = $('.emoji-menu');
$addBtn.removeClass('is-loading');
this.positionMenu($createdMenu, $addBtn);
return setTimeout(() => {
$createdMenu.addClass('is-visible');
$('.js-emoji-menu-search').focus();
}, 200);
});
}
// Prebuild the categoryMap
categoryMap = categoryMap || buildCategoryMap();
});
this.registerEventListener('on', $(document), 'click', '.js-add-award', (e) => {
e.stopPropagation();
e.preventDefault();
this.showEmojiMenu($(e.currentTarget));
});
this.registerEventListener('on', $('html'), 'click', (e) => {
const $target = $(e.target);
if (!$target.closest('.emoji-menu-content').length) {
$('.js-awards-block.current').removeClass('current');
$thumbsBtn.toggleClass('disabled', $userAuthored);
}
// Create the emoji menu with the first category of emojis.
// Then render the remaining categories of emojis one by one to avoid jank.
createEmojiMenu(callback) {
if (this.isCreatingEmojiMenu) {
return;
}
if (!$target.closest('.emoji-menu').length) {
if ($('.emoji-menu').is(':visible')) {
$('.js-add-award.is-active').removeClass('is-active');
$('.emoji-menu').removeClass('is-visible');
}
this.isCreatingEmojiMenu = true;
// Render the first category
const categoryMap = this.emoji.getEmojiCategoryMap();
const categoryNameKey = Object.keys(categoryMap)[0];
const emojisInCategory = categoryMap[categoryNameKey];
const firstCategory = this.renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory);
// Render the frequently used
const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
let frequentlyUsedCatgegory = '';
if (frequentlyUsedEmojis.length > 0) {
frequentlyUsedCatgegory = this.renderCategory('Frequently used', frequentlyUsedEmojis, {
menuListClass: 'frequent-emojis',
});
}
});
this.registerEventListener('on', $(document), 'click', '.js-emoji-btn', (e) => {
e.preventDefault();
const $target = $(e.currentTarget);
const $glEmojiElement = $target.find('gl-emoji');
const $spriteIconElement = $target.find('.icon');
const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
$target.closest('.js-awards-block').addClass('current');
this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji);
});
}
const emojiMenuMarkup = `
<div class="emoji-menu">
<input type="text" name="emoji-menu-search" value="" class="js-emoji-menu-search emoji-search search-input form-control" placeholder="Search emoji" />
AwardsHandler.prototype.registerEventListener = function registerEventListener(method = 'on', element, ...args) {
element[method].call(element, ...args);
this.eventListeners.push({
element,
args,
});
};
AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) {
if ($addBtn.hasClass('js-note-emoji')) {
$addBtn.closest('.note').find('.js-awards-block').addClass('current');
} else {
$addBtn.closest('.js-awards-block').addClass('current');
}
const $menu = $('.emoji-menu');
const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent();
const $userAuthored = this.isUserAuthored($addBtn);
if ($menu.length) {
if ($menu.is('.is-visible')) {
$addBtn.removeClass('is-active');
$menu.removeClass('is-visible');
$('.js-emoji-menu-search').blur();
} else {
$addBtn.addClass('is-active');
this.positionMenu($menu, $addBtn);
$menu.addClass('is-visible');
$('.js-emoji-menu-search').focus();
}
} else {
$addBtn.addClass('is-loading is-active');
this.createEmojiMenu(() => {
const $createdMenu = $('.emoji-menu');
$addBtn.removeClass('is-loading');
this.positionMenu($createdMenu, $addBtn);
return setTimeout(() => {
$createdMenu.addClass('is-visible');
$('.js-emoji-menu-search').focus();
}, 200);
});
}
$thumbsBtn.toggleClass('disabled', $userAuthored);
};
// Create the emoji menu with the first category of emojis.
// Then render the remaining categories of emojis one by one to avoid jank.
AwardsHandler.prototype.createEmojiMenu = function createEmojiMenu(callback) {
if (this.isCreatingEmojiMenu) {
return;
}
this.isCreatingEmojiMenu = true;
// Render the first category
categoryMap = categoryMap || buildCategoryMap();
const categoryNameKey = Object.keys(categoryMap)[0];
const emojisInCategory = categoryMap[categoryNameKey];
const firstCategory = renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory);
// Render the frequently used
const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
let frequentlyUsedCatgegory = '';
if (frequentlyUsedEmojis.length > 0) {
frequentlyUsedCatgegory = renderCategory('Frequently used', frequentlyUsedEmojis, {
menuListClass: 'frequent-emojis',
});
}
const emojiMenuMarkup = `
<div class="emoji-menu">
<input type="text" name="emoji-menu-search" value="" class="js-emoji-menu-search emoji-search search-input form-control" placeholder="Search emoji" />
<div class="emoji-menu-content">
${frequentlyUsedCatgegory}
${firstCategory}
<div class="emoji-menu-content">
${frequentlyUsedCatgegory}
${firstCategory}
</div>
</div>
</div>
`;
`;
document.body.insertAdjacentHTML('beforeend', emojiMenuMarkup);
document.body.insertAdjacentHTML('beforeend', emojiMenuMarkup);
this.addRemainingEmojiMenuCategories();
this.setupSearch();
if (callback) {
callback();
this.addRemainingEmojiMenuCategories();
this.setupSearch();
if (callback) {
callback();
}
}
};
AwardsHandler
.prototype
.addRemainingEmojiMenuCategories = function addRemainingEmojiMenuCategories() {
addRemainingEmojiMenuCategories() {
if (this.isAddingRemainingEmojiMenuCategories) {
return;
}
this.isAddingRemainingEmojiMenuCategories = true;
categoryMap = categoryMap || buildCategoryMap();
const categoryMap = this.emoji.getEmojiCategoryMap();
// Avoid the jank and render the remaining categories separately
// This will take more time, but makes UI more responsive
@ -220,7 +172,7 @@ AwardsHandler
promiseChain.then(() =>
new Promise((resolve) => {
const emojisInCategory = categoryMap[categoryNameKey];
const categoryMarkup = renderCategory(
const categoryMarkup = this.renderCategory(
categoryLabelMap[categoryNameKey],
emojisInCategory,
);
@ -243,179 +195,186 @@ AwardsHandler
emojiContentElement.insertAdjacentHTML('beforeend', '<p>We encountered an error while adding the remaining categories</p>');
throw new Error(`Error occurred in addRemainingEmojiMenuCategories: ${err.message}`);
});
};
AwardsHandler.prototype.positionMenu = function positionMenu($menu, $addBtn) {
const position = $addBtn.data('position');
// The menu could potentially be off-screen or in a hidden overflow element
// So we position the element absolute in the body
const css = {
top: `${$addBtn.offset().top + $addBtn.outerHeight()}px`,
};
if (position === 'right') {
css.left = `${($addBtn.offset().left - $menu.outerWidth()) + 20}px`;
$menu.addClass('is-aligned-right');
} else {
css.left = `${$addBtn.offset().left}px`;
$menu.removeClass('is-aligned-right');
}
return $menu.css(css);
};
AwardsHandler.prototype.addAward = function addAward(
votesBlock,
awardUrl,
emoji,
checkMutuality,
callback,
) {
const normalizedEmoji = this.normalizeEmojiName(emoji);
const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => {
this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
return typeof callback === 'function' ? callback() : undefined;
});
$('.emoji-menu').removeClass('is-visible');
$('.js-add-award.is-active').removeClass('is-active');
};
AwardsHandler.prototype.addAwardToEmojiBar = function addAwardToEmojiBar(
votesBlock,
emoji,
checkForMutuality,
) {
if (checkForMutuality || checkForMutuality === null) {
this.checkMutuality(votesBlock, emoji);
renderCategory(name, emojiList, opts = {}) {
return `
<h5 class="emoji-menu-title">
${name}
</h5>
<ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}">
${emojiList.map(emojiName => `
<li class="emoji-menu-list-item">
<button class="emoji-menu-btn text-center js-emoji-btn" type="button">
${this.emoji.glEmojiTag(emojiName, {
sprite: true,
})}
</button>
</li>
`).join('\n')}
</ul>
`;
}
this.addEmojiToFrequentlyUsedList(emoji);
const normalizedEmoji = this.normalizeEmojiName(emoji);
const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
if ($emojiButton.length > 0) {
if (this.isActive($emojiButton)) {
this.decrementCounter($emojiButton, normalizedEmoji);
positionMenu($menu, $addBtn) {
const position = $addBtn.data('position');
// The menu could potentially be off-screen or in a hidden overflow element
// So we position the element absolute in the body
const css = {
top: `${$addBtn.offset().top + $addBtn.outerHeight()}px`,
};
if (position === 'right') {
css.left = `${($addBtn.offset().left - $menu.outerWidth()) + 20}px`;
$menu.addClass('is-aligned-right');
} else {
const counter = $emojiButton.find('.js-counter');
counter.text(parseInt(counter.text(), 10) + 1);
$emojiButton.addClass('active');
this.addYouToUserList(votesBlock, normalizedEmoji);
this.animateEmoji($emojiButton);
css.left = `${$addBtn.offset().left}px`;
$menu.removeClass('is-aligned-right');
}
} else {
votesBlock.removeClass('hidden');
this.createEmoji(votesBlock, normalizedEmoji);
}
};
AwardsHandler.prototype.getVotesBlock = function getVotesBlock() {
const currentBlock = $('.js-awards-block.current');
let resultantVotesBlock = currentBlock;
if (currentBlock.length === 0) {
resultantVotesBlock = $('.js-awards-block').eq(0);
return $menu.css(css);
}
return resultantVotesBlock;
};
addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) {
const normalizedEmoji = this.emoji.normalizeEmojiName(emoji);
const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => {
this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
return typeof callback === 'function' ? callback() : undefined;
});
$('.emoji-menu').removeClass('is-visible');
$('.js-add-award.is-active').removeClass('is-active');
}
AwardsHandler.prototype.getAwardUrl = function getAwardUrl() {
return this.getVotesBlock().data('award-url');
};
AwardsHandler.prototype.checkMutuality = function checkMutuality(votesBlock, emoji) {
const awardUrl = this.getAwardUrl();
if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
const mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
const $emojiButton = votesBlock.find(`[data-name="${mutualVote}"]`).parent();
const isAlreadyVoted = $emojiButton.hasClass('active');
if (isAlreadyVoted) {
this.addAward(votesBlock, awardUrl, mutualVote, false);
addAwardToEmojiBar(votesBlock, emoji, checkForMutuality) {
if (checkForMutuality || checkForMutuality === null) {
this.checkMutuality(votesBlock, emoji);
}
this.addEmojiToFrequentlyUsedList(emoji);
const normalizedEmoji = this.emoji.normalizeEmojiName(emoji);
const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
if ($emojiButton.length > 0) {
if (this.isActive($emojiButton)) {
this.decrementCounter($emojiButton, normalizedEmoji);
} else {
const counter = $emojiButton.find('.js-counter');
counter.text(parseInt(counter.text(), 10) + 1);
$emojiButton.addClass('active');
this.addYouToUserList(votesBlock, normalizedEmoji);
this.animateEmoji($emojiButton);
}
} else {
votesBlock.removeClass('hidden');
this.createEmoji(votesBlock, normalizedEmoji);
}
}
};
AwardsHandler.prototype.isActive = function isActive($emojiButton) {
return $emojiButton.hasClass('active');
};
getVotesBlock() {
const currentBlock = $('.js-awards-block.current');
let resultantVotesBlock = currentBlock;
if (currentBlock.length === 0) {
resultantVotesBlock = $('.js-awards-block').eq(0);
}
AwardsHandler.prototype.isUserAuthored = function isUserAuthored($button) {
return $button.hasClass('js-user-authored');
};
return resultantVotesBlock;
}
AwardsHandler.prototype.decrementCounter = function decrementCounter($emojiButton, emoji) {
const counter = $('.js-counter', $emojiButton);
const counterNumber = parseInt(counter.text(), 10);
if (counterNumber > 1) {
counter.text(counterNumber - 1);
this.removeYouFromUserList($emojiButton);
} else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
$emojiButton.tooltip('destroy');
counter.text('0');
this.removeYouFromUserList($emojiButton);
if ($emojiButton.parents('.note').length) {
getAwardUrl() {
return this.getVotesBlock().data('award-url');
}
checkMutuality(votesBlock, emoji) {
const awardUrl = this.getAwardUrl();
if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
const mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
const $emojiButton = votesBlock.find(`[data-name="${mutualVote}"]`).parent();
const isAlreadyVoted = $emojiButton.hasClass('active');
if (isAlreadyVoted) {
this.addAward(votesBlock, awardUrl, mutualVote, false);
}
}
}
isActive($emojiButton) {
return $emojiButton.hasClass('active');
}
isUserAuthored($button) {
return $button.hasClass('js-user-authored');
}
decrementCounter($emojiButton, emoji) {
const counter = $('.js-counter', $emojiButton);
const counterNumber = parseInt(counter.text(), 10);
if (counterNumber > 1) {
counter.text(counterNumber - 1);
this.removeYouFromUserList($emojiButton);
} else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
$emojiButton.tooltip('destroy');
counter.text('0');
this.removeYouFromUserList($emojiButton);
if ($emojiButton.parents('.note').length) {
this.removeEmoji($emojiButton);
}
} else {
this.removeEmoji($emojiButton);
}
} else {
this.removeEmoji($emojiButton);
}
return $emojiButton.removeClass('active');
};
AwardsHandler.prototype.removeEmoji = function removeEmoji($emojiButton) {
$emojiButton.tooltip('destroy');
$emojiButton.remove();
const $votesBlock = this.getVotesBlock();
if ($votesBlock.find('.js-emoji-btn').length === 0) {
$votesBlock.addClass('hidden');
}
};
AwardsHandler.prototype.getAwardTooltip = function getAwardTooltip($awardBlock) {
return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || '';
};
AwardsHandler.prototype.toSentence = function toSentence(list) {
let sentence;
if (list.length <= 2) {
sentence = list.join(' and ');
} else {
sentence = `${list.slice(0, -1).join(', ')}, and ${list[list.length - 1]}`;
return $emojiButton.removeClass('active');
}
return sentence;
};
AwardsHandler.prototype.removeYouFromUserList = function removeYouFromUserList($emojiButton) {
const awardBlock = $emojiButton;
const originalTitle = this.getAwardTooltip(awardBlock);
const authors = originalTitle.split(FROM_SENTENCE_REGEX);
authors.splice(authors.indexOf('You'), 1);
return awardBlock
.closest('.js-emoji-btn')
.removeData('title')
.removeAttr('data-title')
.removeAttr('data-original-title')
.attr('title', this.toSentence(authors))
.tooltip('fixTitle');
};
AwardsHandler.prototype.addYouToUserList = function addYouToUserList(votesBlock, emoji) {
const awardBlock = this.findEmojiIcon(votesBlock, emoji).parent();
const origTitle = this.getAwardTooltip(awardBlock);
let users = [];
if (origTitle) {
users = origTitle.trim().split(FROM_SENTENCE_REGEX);
removeEmoji($emojiButton) {
$emojiButton.tooltip('destroy');
$emojiButton.remove();
const $votesBlock = this.getVotesBlock();
if ($votesBlock.find('.js-emoji-btn').length === 0) {
$votesBlock.addClass('hidden');
}
}
users.unshift('You');
return awardBlock
.attr('title', this.toSentence(users))
.tooltip('fixTitle');
};
AwardsHandler
.prototype
.createAwardButtonForVotesBlock = function createAwardButtonForVotesBlock(votesBlock, emojiName) {
getAwardTooltip($awardBlock) {
return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || '';
}
toSentence(list) {
let sentence;
if (list.length <= 2) {
sentence = list.join(' and ');
} else {
sentence = `${list.slice(0, -1).join(', ')}, and ${list[list.length - 1]}`;
}
return sentence;
}
removeYouFromUserList($emojiButton) {
const awardBlock = $emojiButton;
const originalTitle = this.getAwardTooltip(awardBlock);
const authors = originalTitle.split(FROM_SENTENCE_REGEX);
authors.splice(authors.indexOf('You'), 1);
return awardBlock
.closest('.js-emoji-btn')
.removeData('title')
.removeAttr('data-title')
.removeAttr('data-original-title')
.attr('title', this.toSentence(authors))
.tooltip('fixTitle');
}
addYouToUserList(votesBlock, emoji) {
const awardBlock = this.findEmojiIcon(votesBlock, emoji).parent();
const origTitle = this.getAwardTooltip(awardBlock);
let users = [];
if (origTitle) {
users = origTitle.trim().split(FROM_SENTENCE_REGEX);
}
users.unshift('You');
return awardBlock
.attr('title', this.toSentence(users))
.tooltip('fixTitle');
}
createAwardButtonForVotesBlock(votesBlock, emojiName) {
const buttonHtml = `
<button class="btn award-control js-emoji-btn has-tooltip active" title="You" data-placement="bottom">
${glEmojiTag(emojiName)}
${this.emoji.glEmojiTag(emojiName)}
<span class="award-control-text js-counter">1</span>
</button>
`;
@ -424,144 +383,136 @@ AwardsHandler
this.animateEmoji($emojiButton);
$('.award-control').tooltip();
votesBlock.removeClass('current');
};
AwardsHandler.prototype.animateEmoji = function animateEmoji($emoji) {
const className = 'pulse animated once short';
$emoji.addClass(className);
this.registerEventListener('on', $emoji, animationEndEventString, (e) => {
$(e.currentTarget).removeClass(className);
});
};
AwardsHandler.prototype.createEmoji = function createEmoji(votesBlock, emoji) {
if ($('.emoji-menu').length) {
this.createAwardButtonForVotesBlock(votesBlock, emoji);
}
this.createEmojiMenu(() => {
this.createAwardButtonForVotesBlock(votesBlock, emoji);
});
};
AwardsHandler.prototype.postEmoji = function postEmoji($emojiButton, awardUrl, emoji, callback) {
if (this.isUserAuthored($emojiButton)) {
this.userAuthored($emojiButton);
} else {
$.post(awardUrl, {
name: emoji,
}, (data) => {
if (data.ok) {
callback();
}
}).fail(() => new Flash('Something went wrong on our end.'));
animateEmoji($emoji) {
const className = 'pulse animated once short';
$emoji.addClass(className);
this.registerEventListener('on', $emoji, animationEndEventString, (e) => {
$(e.currentTarget).removeClass(className);
});
}
};
AwardsHandler.prototype.findEmojiIcon = function findEmojiIcon(votesBlock, emoji) {
return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`);
};
createEmoji(votesBlock, emoji) {
if ($('.emoji-menu').length) {
this.createAwardButtonForVotesBlock(votesBlock, emoji);
}
this.createEmojiMenu(() => {
this.createAwardButtonForVotesBlock(votesBlock, emoji);
});
}
AwardsHandler.prototype.userAuthored = function userAuthored($emojiButton) {
const oldTitle = this.getAwardTooltip($emojiButton);
const newTitle = 'You cannot vote on your own issue, MR and note';
gl.utils.updateTooltipTitle($emojiButton, newTitle).tooltip('show');
// Restore tooltip back to award list
return setTimeout(() => {
$emojiButton.tooltip('hide');
gl.utils.updateTooltipTitle($emojiButton, oldTitle);
}, 2800);
};
postEmoji($emojiButton, awardUrl, emoji, callback) {
if (this.isUserAuthored($emojiButton)) {
this.userAuthored($emojiButton);
} else {
$.post(awardUrl, {
name: emoji,
}, (data) => {
if (data.ok) {
callback();
}
}).fail(() => new Flash('Something went wrong on our end.'));
}
}
AwardsHandler.prototype.scrollToAwards = function scrollToAwards() {
const options = {
scrollTop: $('.awards').offset().top - 110,
};
return $('body, html').animate(options, 200);
};
findEmojiIcon(votesBlock, emoji) {
return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`);
}
AwardsHandler.prototype.normalizeEmojiName = function normalizeEmojiName(emoji) {
return Object.prototype.hasOwnProperty.call(this.aliases, emoji) ? this.aliases[emoji] : emoji;
};
userAuthored($emojiButton) {
const oldTitle = this.getAwardTooltip($emojiButton);
const newTitle = 'You cannot vote on your own issue, MR and note';
gl.utils.updateTooltipTitle($emojiButton, newTitle).tooltip('show');
// Restore tooltip back to award list
return setTimeout(() => {
$emojiButton.tooltip('hide');
gl.utils.updateTooltipTitle($emojiButton, oldTitle);
}, 2800);
}
AwardsHandler
.prototype
.addEmojiToFrequentlyUsedList = function addEmojiToFrequentlyUsedList(emoji) {
if (isEmojiNameValid(emoji)) {
scrollToAwards() {
const options = {
scrollTop: $('.awards').offset().top - 110,
};
return $('body, html').animate(options, 200);
}
addEmojiToFrequentlyUsedList(emoji) {
if (this.emoji.isEmojiNameValid(emoji)) {
this.frequentlyUsedEmojis = _.uniq(this.getFrequentlyUsedEmojis().concat(emoji));
Cookies.set('frequently_used_emojis', this.frequentlyUsedEmojis.join(','), { expires: 365 });
}
};
AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmojis() {
return this.frequentlyUsedEmojis || (() => {
const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(','));
this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter(
inputName => isEmojiNameValid(inputName),
);
return this.frequentlyUsedEmojis;
})();
};
AwardsHandler.prototype.setupSearch = function setupSearch() {
const $search = $('.js-emoji-menu-search');
this.registerEventListener('on', $search, 'input', (e) => {
const term = $(e.target).val().trim();
this.searchEmojis(term);
});
const $menu = $('.emoji-menu');
this.registerEventListener('on', $menu, transitionEndEventString, (e) => {
if (e.target === e.currentTarget) {
// Clear the search
this.searchEmojis('');
}
});
};
AwardsHandler.prototype.searchEmojis = function searchEmojis(term) {
const $search = $('.js-emoji-menu-search');
$search.val(term);
// Clean previous search results
$('ul.emoji-menu-search, h5.emoji-search-title').remove();
if (term.length > 0) {
// Generate a search result block
const h5 = $('<h5 class="emoji-search-title"/>').text('Search results');
const foundEmojis = this.findMatchingEmojiElements(term).show();
const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis);
$('.emoji-menu-content ul, .emoji-menu-content h5').hide();
$('.emoji-menu-content').append(h5).append(ul);
} else {
$('.emoji-menu-content').children().show();
}
};
AwardsHandler.prototype.findMatchingEmojiElements = function findMatchingEmojiElements(term) {
const safeTerm = term.toLowerCase();
getFrequentlyUsedEmojis() {
return this.frequentlyUsedEmojis || (() => {
const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(','));
this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter(
inputName => this.emoji.isEmojiNameValid(inputName),
);
const namesMatchingAlias = [];
Object.keys(emojiAliases).forEach((alias) => {
if (alias.indexOf(safeTerm) >= 0) {
namesMatchingAlias.push(emojiAliases[alias]);
return this.frequentlyUsedEmojis;
})();
}
setupSearch() {
const $search = $('.js-emoji-menu-search');
this.registerEventListener('on', $search, 'input', (e) => {
const term = $(e.target).val().trim();
this.searchEmojis(term);
});
const $menu = $('.emoji-menu');
this.registerEventListener('on', $menu, transitionEndEventString, (e) => {
if (e.target === e.currentTarget) {
// Clear the search
this.searchEmojis('');
}
});
}
searchEmojis(term) {
const $search = $('.js-emoji-menu-search');
$search.val(term);
// Clean previous search results
$('ul.emoji-menu-search, h5.emoji-search-title').remove();
if (term.length > 0) {
// Generate a search result block
const h5 = $('<h5 class="emoji-search-title"/>').text('Search results');
const foundEmojis = this.findMatchingEmojiElements(term).show();
const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis);
$('.emoji-menu-content ul, .emoji-menu-content h5').hide();
$('.emoji-menu-content').append(h5).append(ul);
} else {
$('.emoji-menu-content').children().show();
}
});
const $matchingElements = namesMatchingAlias.concat(safeTerm)
.reduce(
($result, searchTerm) =>
$result.add($(`.emoji-menu-list:not(.frequent-emojis) [data-name*="${searchTerm}"]`)),
$([]),
);
return $matchingElements.closest('li').clone();
};
}
AwardsHandler.prototype.destroy = function destroy() {
this.eventListeners.forEach((entry) => {
entry.element.off.call(entry.element, ...entry.args);
});
$('.emoji-menu').remove();
};
findMatchingEmojiElements(query) {
const emojiMatches = this.emoji.filterEmojiNamesByAlias(query);
const $emojiElements = $('.emoji-menu-list:not(.frequent-emojis) [data-name]');
const $matchingElements = $emojiElements
.filter((i, elm) => emojiMatches.indexOf(elm.dataset.name) >= 0);
return $matchingElements.closest('li').clone();
}
export default AwardsHandler;
destroy() {
this.eventListeners.forEach((entry) => {
entry.element.off.call(entry.element, ...entry.args);
});
$('.emoji-menu').remove();
}
}
let awardsHandlerPromise = null;
export default function loadAwardsHandler(reload = false) {
if (!awardsHandlerPromise || reload) {
awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji')
.then(Emoji => new AwardsHandler(Emoji));
}
return awardsHandlerPromise;
}

View file

@ -1,23 +1,8 @@
import autosize from 'vendor/autosize';
$(() => {
const $fields = $('.js-autosize');
document.addEventListener('DOMContentLoaded', () => {
const autosizeEls = document.querySelectorAll('.js-autosize');
$fields.on('autosize:resized', function resized() {
const $field = $(this);
$field.data('height', $field.outerHeight());
});
$fields.on('resize.autosize', function resize() {
const $field = $(this);
if ($field.data('height') !== $field.outerHeight()) {
$field.data('height', $field.outerHeight());
autosize.destroy($field);
$field.css('max-height', window.outerHeight);
}
});
autosize($fields);
autosize.update($fields);
$fields.css('resize', 'vertical');
autosize(autosizeEls);
autosize.update(autosizeEls);
});

View file

@ -1,75 +1,9 @@
import installCustomElements from 'document-register-element';
import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json';
import { getUnicodeSupportMap } from './gl_emoji/unicode_support_map';
import { isEmojiUnicodeSupported } from './gl_emoji/is_emoji_unicode_supported';
import isEmojiUnicodeSupported from '../emoji/support';
installCustomElements(window);
const generatedUnicodeSupportMap = getUnicodeSupportMap();
function emojiImageTag(name, src) {
return `<img class="emoji" title=":${name}:" alt=":${name}:" src="${src}" width="20" height="20" align="absmiddle" />`;
}
function assembleFallbackImageSrc(inputName) {
let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
emojiAliases[inputName] : inputName;
let emojiInfo = emojiMap[name];
// Fallback to question mark for unknown emojis
if (!emojiInfo) {
name = 'grey_question';
emojiInfo = emojiMap[name];
}
const fallbackImageSrc = `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${emojiInfo.digest}.png`;
return fallbackImageSrc;
}
const glEmojiTagDefaults = {
sprite: false,
forceFallback: false,
};
function glEmojiTag(inputName, options) {
const opts = Object.assign({}, glEmojiTagDefaults, options);
let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
emojiAliases[inputName] : inputName;
let emojiInfo = emojiMap[name];
// Fallback to question mark for unknown emojis
if (!emojiInfo) {
name = 'grey_question';
emojiInfo = emojiMap[name];
}
const fallbackImageSrc = assembleFallbackImageSrc(name);
const fallbackSpriteClass = `emoji-${name}`;
const classList = [];
if (opts.forceFallback && opts.sprite) {
classList.push('emoji-icon');
classList.push(fallbackSpriteClass);
}
const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : '';
const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : '';
let contents = emojiInfo.moji;
if (opts.forceFallback && !opts.sprite) {
contents = emojiImageTag(name, fallbackImageSrc);
}
return `
<gl-emoji
${classAttribute}
data-name="${name}"
data-fallback-src="${fallbackImageSrc}"
${fallbackSpriteAttribute}
data-unicode-version="${emojiInfo.unicodeVersion}"
title="${emojiInfo.description}"
>
${contents}
</gl-emoji>
`;
}
function installGlEmojiElement() {
export default function installGlEmojiElement() {
const GlEmojiElementProto = Object.create(HTMLElement.prototype);
GlEmojiElementProto.createdCallback = function createdCallback() {
const emojiUnicode = this.textContent.trim();
@ -88,19 +22,28 @@ function installGlEmojiElement() {
const hasCssSpriteFalback = fallbackSpriteClass && fallbackSpriteClass.length > 0;
if (
emojiUnicode &&
isEmojiUnicode &&
!isEmojiUnicodeSupported(generatedUnicodeSupportMap, emojiUnicode, unicodeVersion)
!isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)
) {
// CSS sprite fallback takes precedence over image fallback
if (hasCssSpriteFalback) {
// IE 11 doesn't like adding multiple at once :(
this.classList.add('emoji-icon');
this.classList.add(fallbackSpriteClass);
} else if (hasImageFallback) {
this.innerHTML = emojiImageTag(name, fallbackSrc);
} else {
const src = assembleFallbackImageSrc(name);
this.innerHTML = emojiImageTag(name, src);
import(/* webpackChunkName: 'emoji' */ '../emoji')
.then(({ emojiImageTag, emojiFallbackImageSrc }) => {
if (hasImageFallback) {
this.innerHTML = emojiImageTag(name, fallbackSrc);
} else {
const src = emojiFallbackImageSrc(name);
this.innerHTML = emojiImageTag(name, src);
}
})
.catch(() => {
// do nothing
});
}
}
};
@ -109,9 +52,3 @@ function installGlEmojiElement() {
prototype: GlEmojiElementProto,
});
}
export {
installGlEmojiElement,
glEmojiTag,
emojiImageTag,
};

View file

@ -1,11 +0,0 @@
import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json';
function isEmojiNameValid(inputName) {
const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
emojiAliases[inputName] : inputName;
return name && emojiMap[name];
}
export default isEmojiNameValid;

View file

@ -1,7 +1,7 @@
import './autosize';
import './bind_in_out';
import './details_behavior';
import { installGlEmojiElement } from './gl_emoji';
import installGlEmojiElement from './gl_emoji';
import './quick_submit';
import './requires_input';
import './toggler_behavior';

View file

@ -40,7 +40,7 @@ $(document).on('keydown.quick_submit', '.js-quick-submit', (e) => {
e.preventDefault();
const $form = $(e.target).closest('form');
const $submitButton = $form.find('input[type=submit], button[type=submit]');
const $submitButton = $form.find('input[type=submit], button[type=submit]').first();
if (!$submitButton.attr('disabled')) {
$submitButton.trigger('click', [e]);

View file

@ -1,3 +1,4 @@
import _ from 'underscore';
import '../commons/bootstrap';
// Requires Input behavior
@ -48,7 +49,9 @@ function hideOrShowHelpBlock(form) {
$(() => {
const $form = $('form.js-requires-input');
$form.requiresInput();
hideOrShowHelpBlock($form);
$('.select2.js-select-namespace').change(() => hideOrShowHelpBlock($form));
if ($form) {
$form.requiresInput();
hideOrShowHelpBlock($form);
$('.select2.js-select-namespace').change(() => hideOrShowHelpBlock($form));
}
});

View file

@ -1,4 +1,3 @@
// Toggle button. Show/hide content inside parent container.
// Button does not change visibility. If button has icon - it changes chevron style.
//

View file

@ -1,5 +1,3 @@
/* global Flash */
import sqljs from 'sql.js';
import { template as _template } from 'underscore';
@ -15,19 +13,27 @@ const PREVIEW_TEMPLATE = _template(`
class BalsamiqViewer {
constructor(viewer) {
this.viewer = viewer;
this.endpoint = this.viewer.dataset.endpoint;
}
loadFile() {
const xhr = new XMLHttpRequest();
loadFile(endpoint) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', this.endpoint, true);
xhr.responseType = 'arraybuffer';
xhr.open('GET', endpoint, true);
xhr.responseType = 'arraybuffer';
xhr.onload = loadEvent => this.fileLoaded(loadEvent, resolve, reject);
xhr.onerror = reject;
xhr.onload = this.renderFile.bind(this);
xhr.onerror = BalsamiqViewer.onError;
xhr.send();
});
}
xhr.send();
fileLoaded(loadEvent, resolve, reject) {
if (loadEvent.target.status !== 200) return reject();
this.renderFile(loadEvent);
return resolve();
}
renderFile(loadEvent) {
@ -103,12 +109,6 @@ class BalsamiqViewer {
static parseTitle(resource) {
return JSON.parse(resource.values[0][2]).name;
}
static onError() {
const flash = new Flash('Balsamiq file could not be loaded.');
return flash;
}
}
export default BalsamiqViewer;

View file

@ -1,6 +1,22 @@
/* global Flash */
import BalsamiqViewer from './balsamiq/balsamiq_viewer';
document.addEventListener('DOMContentLoaded', () => {
const balsamiqViewer = new BalsamiqViewer(document.getElementById('js-balsamiq-viewer'));
balsamiqViewer.loadFile();
});
function onError() {
const flash = new window.Flash('Balsamiq file could not be loaded.');
return flash;
}
function loadBalsamiqFile() {
const viewer = document.getElementById('js-balsamiq-viewer');
if (!(viewer instanceof Element)) return;
const endpoint = viewer.dataset.endpoint;
const balsamiqViewer = new BalsamiqViewer(viewer);
balsamiqViewer.loadFile(endpoint).catch(onError);
}
$(loadBalsamiqFile);

View file

@ -1,9 +1,24 @@
/* eslint-disable func-names, object-shorthand, prefer-arrow-callback */
/* global Dropzone */
import '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants';
function toggleLoading($el, $icon, loading) {
if (loading) {
$el.disable();
$icon.removeClass(HIDDEN_CLASS);
} else {
$el.enable();
$icon.addClass(HIDDEN_CLASS);
}
}
export default class BlobFileDropzone {
constructor(form, method) {
const formDropzone = form.find('.dropzone');
const submitButton = form.find('#submit-all');
const submitButtonLoadingIcon = submitButton.find('.js-loading-icon');
const dropzoneMessage = form.find('.dz-message');
Dropzone.autoDiscover = false;
const dropzone = formDropzone.dropzone({
@ -26,16 +41,24 @@ export default class BlobFileDropzone {
},
init: function () {
this.on('addedfile', function () {
toggleLoading(submitButton, submitButtonLoadingIcon, false);
dropzoneMessage.addClass(HIDDEN_CLASS);
$('.dropzone-alerts').html('').hide();
});
this.on('removedfile', function () {
toggleLoading(submitButton, submitButtonLoadingIcon, false);
dropzoneMessage.removeClass(HIDDEN_CLASS);
});
this.on('success', function (header, response) {
window.location.href = response.filePath;
$('#modal-upload-blob').modal('hide');
window.gl.utils.visitUrl(response.filePath);
});
this.on('maxfilesexceeded', function (file) {
dropzoneMessage.addClass(HIDDEN_CLASS);
this.removeFile(file);
});
this.on('sending', function (file, xhr, formData) {
formData.append('branch_name', form.find('input[name="branch_name"]').val());
formData.append('branch_name', form.find('.js-branch-name').val());
formData.append('create_merge_request', form.find('.js-create-merge-request').val());
formData.append('commit_message', form.find('.js-commit-message').val());
});
@ -48,14 +71,15 @@ export default class BlobFileDropzone {
},
});
const submitButton = form.find('#submit-all')[0];
submitButton.addEventListener('click', function (e) {
submitButton.on('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (dropzone[0].dropzone.getQueuedFiles().length === 0) {
// eslint-disable-next-line no-alert
alert('Please select a file');
return false;
}
toggleLoading(submitButton, submitButtonLoadingIcon, true);
dropzone[0].dropzone.processQueue();
return false;
});

View file

@ -1,88 +0,0 @@
class CreateBranchDropdown {
constructor(el, targetBranchDropdown) {
this.targetBranchDropdown = targetBranchDropdown;
this.el = el;
this.dropdownBack = this.el.closest('.dropdown').querySelector('.dropdown-menu-back');
this.cancelButton = this.el.querySelector('.js-cancel-branch-btn');
this.newBranchField = this.el.querySelector('#new_branch_name');
this.newBranchCreateButton = this.el.querySelector('.js-new-branch-btn');
this.newBranchCreateButton.setAttribute('disabled', '');
this.addBindings();
this.cleanupWrapper = this.cleanup.bind(this);
document.addEventListener('beforeunload', this.cleanupWrapper);
}
cleanup() {
this.cleanBindings();
document.removeEventListener('beforeunload', this.cleanupWrapper);
}
cleanBindings() {
this.newBranchField.removeEventListener('keyup', this.enableBranchCreateButtonWrapper);
this.newBranchField.removeEventListener('change', this.enableBranchCreateButtonWrapper);
this.newBranchField.removeEventListener('keydown', this.handleNewBranchKeydownWrapper);
this.dropdownBack.removeEventListener('click', this.resetFormWrapper);
this.cancelButton.removeEventListener('click', this.handleCancelClickWrapper);
this.newBranchCreateButton.removeEventListener('click', this.createBranchWrapper);
}
addBindings() {
this.enableBranchCreateButtonWrapper = this.enableBranchCreateButton.bind(this);
this.handleNewBranchKeydownWrapper = this.handleNewBranchKeydown.bind(this);
this.resetFormWrapper = this.resetForm.bind(this);
this.handleCancelClickWrapper = this.handleCancelClick.bind(this);
this.createBranchWrapper = this.createBranch.bind(this);
this.newBranchField.addEventListener('keyup', this.enableBranchCreateButtonWrapper);
this.newBranchField.addEventListener('change', this.enableBranchCreateButtonWrapper);
this.newBranchField.addEventListener('keydown', this.handleNewBranchKeydownWrapper);
this.dropdownBack.addEventListener('click', this.resetFormWrapper);
this.cancelButton.addEventListener('click', this.handleCancelClickWrapper);
this.newBranchCreateButton.addEventListener('click', this.createBranchWrapper);
}
handleCancelClick(e) {
e.preventDefault();
e.stopPropagation();
this.resetForm();
this.dropdownBack.click();
}
handleNewBranchKeydown(e) {
const keyCode = e.which;
const ENTER_KEYCODE = 13;
if (keyCode === ENTER_KEYCODE) {
this.createBranch(e);
}
}
enableBranchCreateButton() {
if (this.newBranchField.value !== '') {
this.newBranchCreateButton.removeAttribute('disabled');
} else {
this.newBranchCreateButton.setAttribute('disabled', '');
}
}
resetForm() {
this.newBranchField.value = '';
this.enableBranchCreateButtonWrapper();
}
createBranch(e) {
e.preventDefault();
if (this.newBranchCreateButton.getAttribute('disabled') === '') {
return;
}
const newBranchName = this.newBranchField.value;
this.targetBranchDropdown.setNewBranch(newBranchName);
this.resetForm();
}
}
window.gl = window.gl || {};
gl.CreateBranchDropdown = CreateBranchDropdown;

View file

@ -1,5 +1,3 @@
/* global Api */
export default class FileTemplateSelector {
constructor(mediator) {
this.mediator = mediator;
@ -65,4 +63,3 @@ export default class FileTemplateSelector {
this.reportSelection(opts);
}
}

View file

@ -51,8 +51,9 @@ export default () => {
methods: {
loadFile() {
this.$http.get(el.dataset.endpoint)
.then(response => response.json())
.then((res) => {
this.json = res.json();
this.json = res;
this.loading = false;
})
.catch((e) => {

View file

@ -1,152 +0,0 @@
/* eslint-disable class-methods-use-this */
const SELECT_ITEM_MSG = 'Select';
class TargetBranchDropDown {
constructor(dropdown) {
this.dropdown = dropdown;
this.$dropdown = $(dropdown);
this.fieldName = this.dropdown.getAttribute('data-field-name');
this.form = this.dropdown.closest('form');
this.createDropdown();
}
static bootstrap() {
const dropdowns = document.querySelectorAll('.js-project-branches-dropdown');
[].forEach.call(dropdowns, dropdown => new TargetBranchDropDown(dropdown));
}
createDropdown() {
const self = this;
this.$dropdown.glDropdown({
selectable: true,
filterable: true,
search: {
fields: ['title'],
},
data: (term, callback) => $.ajax({
url: self.dropdown.getAttribute('data-refs-url'),
data: {
ref: self.dropdown.getAttribute('data-ref'),
show_all: true,
},
dataType: 'json',
}).done(refs => callback(self.dropdownData(refs))),
toggleLabel(item, el) {
if (el.is('.is-active')) {
return item.text;
}
return SELECT_ITEM_MSG;
},
clicked(options) {
options.e.preventDefault();
self.onClick.call(self);
},
fieldName: self.fieldName,
});
return new gl.CreateBranchDropdown(this.form.querySelector('.dropdown-new-branch'), this);
}
onClick() {
this.enableSubmit();
this.$dropdown.trigger('change.branch');
}
enableSubmit() {
const submitBtn = this.form.querySelector('[type="submit"]');
if (this.branchInput && this.branchInput.value) {
submitBtn.removeAttribute('disabled');
} else {
submitBtn.setAttribute('disabled', '');
}
}
dropdownData(refs) {
const branchList = this.dropdownItems(refs);
this.cachedRefs = refs;
this.addDefaultBranch(branchList);
this.addNewBranch(branchList);
return { Branches: branchList };
}
dropdownItems(refs) {
return refs.map(this.dropdownItem);
}
dropdownItem(ref) {
return { id: ref, text: ref, title: ref };
}
addDefaultBranch(branchList) {
// when no branch is selected do nothing
if (!this.branchInput) {
return;
}
const branchInputVal = this.branchInput.value;
const currentBranchIndex = this.searchBranch(branchList, branchInputVal);
if (currentBranchIndex === -1) {
this.unshiftBranch(branchList, this.dropdownItem(branchInputVal));
}
}
addNewBranch(branchList) {
if (this.newBranch) {
this.unshiftBranch(branchList, this.newBranch);
}
}
searchBranch(branchList, branchName) {
return _.findIndex(branchList, el => branchName === el.id);
}
unshiftBranch(branchList, branch) {
const branchIndex = this.searchBranch(branchList, branch.id);
if (branchIndex === -1) {
branchList.unshift(branch);
}
}
setNewBranch(newBranchName) {
this.newBranch = this.dropdownItem(newBranchName);
this.refreshData();
this.selectBranch(this.searchBranch(this.glDropdown.fullData.Branches, newBranchName));
}
refreshData() {
this.glDropdown.fullData = this.dropdownData(this.cachedRefs);
this.clearFilter();
}
clearFilter() {
// apply an empty filter in order to refresh the data
this.glDropdown.filter.filter('');
this.dropdown.closest('.dropdown').querySelector('.dropdown-page-one .dropdown-input-field').value = '';
}
selectBranch(index) {
const branch = this.dropdown.closest('.dropdown').querySelectorAll('li a')[index];
if (!branch.classList.contains('is-active')) {
branch.click();
} else {
this.closeDropdown();
}
}
closeDropdown() {
this.dropdown.closest('.dropdown').querySelector('.dropdown-menu-close').click();
}
get branchInput() {
return this.form.querySelector(`input[name="${this.fieldName}"]`);
}
get glDropdown() {
return this.$dropdown.data('glDropdown');
}
}
window.gl = window.gl || {};
gl.TargetBranchDropDown = TargetBranchDropDown;

View file

@ -1,4 +1,4 @@
/* global Api */
import Api from '../../api';
import FileTemplateSelector from '../file_template_selector';

View file

@ -1,4 +1,4 @@
/* global Api */
import Api from '../../api';
import FileTemplateSelector from '../file_template_selector';

View file

@ -1,4 +1,4 @@
/* global Api */
import Api from '../../api';
import FileTemplateSelector from '../file_template_selector';

View file

@ -1,4 +1,4 @@
/* global Api */
import Api from '../../api';
import FileTemplateSelector from '../file_template_selector';

View file

@ -1,17 +1,38 @@
/* global Flash */
export default class BlobViewer {
constructor() {
BlobViewer.initAuxiliaryViewer();
this.initMainViewers();
}
static initAuxiliaryViewer() {
const auxiliaryViewer = document.querySelector('.blob-viewer[data-type="auxiliary"]');
if (!auxiliaryViewer) return;
BlobViewer.loadViewer(auxiliaryViewer);
}
initMainViewers() {
this.$fileHolder = $('.file-holder');
if (!this.$fileHolder.length) return;
this.switcher = document.querySelector('.js-blob-viewer-switcher');
this.switcherBtns = document.querySelectorAll('.js-blob-viewer-switch-btn');
this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn');
this.simpleViewer = document.querySelector('.blob-viewer[data-type="simple"]');
this.richViewer = document.querySelector('.blob-viewer[data-type="rich"]');
this.$fileHolder = $('.file-holder');
let initialViewerName = document.querySelector('.blob-viewer:not(.hidden)').getAttribute('data-type');
this.simpleViewer = this.$fileHolder[0].querySelector('.blob-viewer[data-type="simple"]');
this.richViewer = this.$fileHolder[0].querySelector('.blob-viewer[data-type="rich"]');
this.initBindings();
this.switchToInitialViewer();
}
switchToInitialViewer() {
const initialViewer = this.$fileHolder[0].querySelector('.blob-viewer:not(.hidden)');
let initialViewerName = initialViewer.getAttribute('data-type');
if (this.switcher && location.hash.indexOf('#L') === 0) {
initialViewerName = 'simple';
}
@ -61,41 +82,13 @@ export default class BlobViewer {
$(this.copySourceBtn).tooltip('fixTitle');
}
loadViewer(viewerParam) {
const viewer = viewerParam;
const url = viewer.getAttribute('data-url');
if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) {
return;
}
viewer.setAttribute('data-loading', 'true');
$.ajax({
url,
dataType: 'JSON',
})
.fail(() => new Flash('Error loading source view'))
.done((data) => {
viewer.innerHTML = data.html;
$(viewer).renderGFM();
viewer.setAttribute('data-loaded', 'true');
this.$fileHolder.trigger('highlight:line');
gl.utils.handleLocationHash();
this.toggleCopyButtonState();
});
}
switchToViewer(name) {
const newViewer = document.querySelector(`.blob-viewer[data-type='${name}']`);
const newViewer = this.$fileHolder[0].querySelector(`.blob-viewer[data-type='${name}']`);
if (this.activeViewer === newViewer) return;
const oldButton = document.querySelector('.js-blob-viewer-switch-btn.active');
const newButton = document.querySelector(`.js-blob-viewer-switch-btn[data-viewer='${name}']`);
const oldViewer = document.querySelector(`.blob-viewer:not([data-type='${name}'])`);
const oldViewer = this.$fileHolder[0].querySelector(`.blob-viewer:not([data-type='${name}'])`);
if (oldButton) {
oldButton.classList.remove('active');
@ -116,6 +109,41 @@ export default class BlobViewer {
this.toggleCopyButtonState();
this.loadViewer(newViewer);
BlobViewer.loadViewer(newViewer)
.then((viewer) => {
$(viewer).renderGFM();
this.$fileHolder.trigger('highlight:line');
gl.utils.handleLocationHash();
this.toggleCopyButtonState();
})
.catch(() => new Flash('Error loading viewer'));
}
static loadViewer(viewerParam) {
const viewer = viewerParam;
const url = viewer.getAttribute('data-url');
return new Promise((resolve, reject) => {
if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) {
resolve(viewer);
return;
}
viewer.setAttribute('data-loading', 'true');
$.ajax({
url,
dataType: 'JSON',
})
.fail(reject)
.done((data) => {
viewer.innerHTML = data.html;
viewer.setAttribute('data-loaded', 'true');
resolve(viewer);
});
});
}
}

View file

@ -8,6 +8,7 @@ import BlobFileDropzone from '../blob/blob_file_dropzone';
$(() => {
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');
@ -30,4 +31,8 @@ $(() => {
'.btn-upload-file',
);
}
if (deleteBlobForm.length) {
new NewCommitForm(deleteBlobForm);
}
});

View file

@ -2,27 +2,27 @@
/* global BoardService */
/* global Flash */
import _ from 'underscore';
import Vue from 'vue';
import VueResource from 'vue-resource';
import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub';
require('./models/issue');
require('./models/label');
require('./models/list');
require('./models/milestone');
require('./models/assignee');
require('./stores/boards_store');
require('./stores/modal_store');
require('./services/board_service');
require('./mixins/modal_mixins');
require('./mixins/sortable_default_options');
require('./filters/due_date_filters');
require('./components/board');
require('./components/board_sidebar');
require('./components/new_list_dropdown');
require('./components/modal/index');
require('../vue_shared/vue_resource_interceptor');
import './models/issue';
import './models/label';
import './models/list';
import './models/milestone';
import './models/assignee';
import './stores/boards_store';
import './stores/modal_store';
import './services/board_service';
import './mixins/modal_mixins';
import './mixins/sortable_default_options';
import './filters/due_date_filters';
import './components/board';
import './components/board_sidebar';
import './components/new_list_dropdown';
import './components/modal/index';
import '../vue_shared/vue_resource_interceptor';
Vue.use(VueResource);
@ -71,6 +71,7 @@ $(() => {
gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
this.filterManager = new FilteredSearchBoards(Store.filter, true);
this.filterManager.setup();
// Listen for updateTokens event
eventHub.$on('updateTokens', this.updateTokens);
@ -81,13 +82,16 @@ $(() => {
mounted () {
Store.disabled = this.disabled;
gl.boardService.all()
.then(response => response.json())
.then((resp) => {
resp.json().forEach((board) => {
resp.forEach((board) => {
const list = Store.addList(board, this.defaultAvatar);
if (list.type === 'closed') {
list.position = Infinity;
list.label = { description: 'Shows all closed issues. Moving an issue to this list closes it' };
} else if (list.type === 'backlog') {
list.position = -1;
}
});
@ -95,7 +99,8 @@ $(() => {
Store.addBlankState();
this.loading = false;
}).catch(() => new Flash('An error occurred. Please try again.'));
})
.catch(() => new Flash('An error occurred. Please try again.'));
},
methods: {
updateTokens() {
@ -128,7 +133,7 @@ $(() => {
},
computed: {
disabled() {
return !this.store.lists.filter(list => list.type !== 'blank' && list.type !== 'done').length;
return !this.store.lists.filter(list => !list.preset).length;
},
tooltipTitle() {
if (this.disabled) {

View file

@ -1,11 +1,10 @@
/* eslint-disable comma-dangle, space-before-function-paren, one-var */
/* global Sortable */
import Vue from 'vue';
import AccessorUtilities from '../../lib/utils/accessor';
import boardList from './board_list';
import boardBlankState from './board_blank_state';
require('./board_delete');
require('./board_list');
import './board_delete';
const Store = gl.issueBoards.BoardsStore;
@ -24,6 +23,10 @@ gl.issueBoards.Board = Vue.extend({
disabled: Boolean,
issueLinkBase: String,
rootPath: String,
boardId: {
type: String,
required: true,
},
},
data () {
return {
@ -80,7 +83,16 @@ gl.issueBoards.Board = Vue.extend({
methods: {
showNewIssueForm() {
this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
}
},
toggleExpanded(e) {
if (this.list.isExpandable && !e.target.classList.contains('js-no-trigger-collapse')) {
this.list.isExpanded = !this.list.isExpanded;
if (AccessorUtilities.isLocalStorageAccessSafe()) {
localStorage.setItem(`boards.${this.boardId}.${this.list.type}.expanded`, this.list.isExpanded);
}
}
},
},
mounted () {
this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({
@ -104,4 +116,11 @@ gl.issueBoards.Board = Vue.extend({
this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions);
},
created() {
if (this.list.isExpandable && AccessorUtilities.isLocalStorageAccessSafe()) {
const isCollapsed = localStorage.getItem(`boards.${this.boardId}.${this.list.type}.expanded`) === 'false';
this.list.isExpanded = !isCollapsed;
}
},
});

View file

@ -1,5 +1,6 @@
/* global ListLabel */
import _ from 'underscore';
import Cookies from 'js-cookie';
const Store = gl.issueBoards.BoardsStore;
@ -64,8 +65,9 @@ export default {
// Save the labels
gl.boardService.generateDefaultLists()
.then((resp) => {
resp.json().forEach((listObj) => {
.then(resp => resp.json())
.then((data) => {
data.forEach((listObj) => {
const list = Store.findList('title', listObj.title);
list.id = listObj.id;

View file

@ -1,4 +1,4 @@
require('./issue_card_inner');
import './issue_card_inner';
const Store = gl.issueBoards.BoardsStore;

View file

@ -2,6 +2,7 @@
import boardNewIssue from './board_new_issue';
import boardCard from './board_card';
import eventHub from '../eventhub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
const Store = gl.issueBoards.BoardsStore;
@ -44,6 +45,7 @@ export default {
components: {
boardCard,
boardNewIssue,
loadingIcon,
},
methods: {
listHeight() {
@ -55,6 +57,9 @@ export default {
scrollTop() {
return this.$refs.list.scrollTop + this.listHeight();
},
scrollToTop() {
this.$refs.list.scrollTop = 0;
},
loadNextPage() {
const getIssues = this.list.nextPage();
const loadingDone = () => {
@ -106,6 +111,7 @@ export default {
},
created() {
eventHub.$on(`hide-issue-form-${this.list.id}`, this.toggleForm);
eventHub.$on(`scroll-board-list-${this.list.id}`, this.scrollToTop);
},
mounted() {
const options = gl.issueBoards.getBoardSortableDefaultOptions({
@ -148,6 +154,7 @@ export default {
},
beforeDestroy() {
eventHub.$off(`hide-issue-form-${this.list.id}`, this.toggleForm);
eventHub.$off(`scroll-board-list-${this.list.id}`, this.scrollToTop);
this.$refs.list.removeEventListener('scroll', this.onScroll);
},
template: `
@ -156,14 +163,13 @@ export default {
class="board-list-loading text-center"
aria-label="Loading issues"
v-if="loading">
<i
class="fa fa-spinner fa-spin"
aria-hidden="true">
</i>
<loading-icon />
</div>
<board-new-issue
:list="list"
v-if="list.type !== 'closed' && showIssueForm"/>
<transition name="slide-down">
<board-new-issue
:list="list"
v-if="list.type !== 'closed' && showIssueForm"/>
</transition>
<ul
class="board-list"
v-show="!loading"
@ -184,12 +190,12 @@ export default {
class="board-list-count text-center"
v-if="showCount"
data-id="-1">
<i
class="fa fa-spinner fa-spin"
aria-label="Loading more issues"
aria-hidden="true"
v-show="list.loadingMore">
</i>
<loading-icon
v-show="list.loadingMore"
label="Loading more issues"
/>
<span v-if="list.issues.length === list.issuesSize">
Showing all issues
</span>

View file

@ -17,7 +17,7 @@ export default {
methods: {
submit(e) {
e.preventDefault();
if (this.title.trim() === '') return;
if (this.title.trim() === '') return Promise.resolve();
this.error = false;
@ -29,7 +29,10 @@ export default {
assignees: [],
});
this.list.newIssue(issue)
eventHub.$emit(`scroll-board-list-${this.list.id}`);
this.cancel();
return this.list.newIssue(issue)
.then(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
@ -47,8 +50,6 @@ export default {
// Show error message
this.error = true;
});
this.cancel();
},
cancel() {
this.title = '';
@ -75,6 +76,7 @@ export default {
type="text"
v-model="title"
ref="input"
autocomplete="off"
:id="list.id + '-title'" />
<div class="clearfix prepend-top-10">
<button class="btn btn-success pull-left"

View file

@ -7,11 +7,9 @@
import Vue from 'vue';
import eventHub from '../../sidebar/event_hub';
import AssigneeTitle from '../../sidebar/components/assignees/assignee_title';
import Assignees from '../../sidebar/components/assignees/assignees';
require('./sidebar/remove_issue');
import './sidebar/remove_issue';
const Store = gl.issueBoards.BoardsStore;
@ -34,9 +32,12 @@ gl.issueBoards.BoardSidebar = Vue.extend({
showSidebar () {
return Object.keys(this.issue).length;
},
assigneeId() {
return this.issue.assignee ? this.issue.assignee.id : 0;
}
milestoneTitle() {
return this.issue.milestone ? this.issue.milestone.title : 'No Milestone';
},
canRemove() {
return !this.list.preset;
},
},
watch: {
detail: {
@ -62,18 +63,6 @@ gl.issueBoards.BoardSidebar = Vue.extend({
},
deep: true
},
issue () {
if (this.showSidebar) {
this.$nextTick(() => {
$('.right-sidebar').getNiceScroll(0).doScrollTop(0, 0);
$('.right-sidebar').getNiceScroll().resize();
});
}
this.issue = this.detail.issue;
this.list = this.detail.list;
},
deep: true
},
methods: {
closeSidebar () {

View file

@ -1,4 +1,5 @@
import Vue from 'vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import eventHub from '../eventhub';
const Store = gl.issueBoards.BoardsStore;
@ -38,6 +39,9 @@ gl.issueBoards.IssueCardInner = Vue.extend({
maxCounter: 99,
};
},
components: {
userAvatarLink,
},
computed: {
numberOverLimit() {
return this.issue.assignees.length - this.limitBeforeCounter;
@ -93,9 +97,8 @@ gl.issueBoards.IssueCardInner = Vue.extend({
return `Avatar for ${assignee.name}`;
},
showLabel(label) {
if (!this.list) return true;
return !this.list.label || label.id !== this.list.label.id;
if (!label.id) return false;
return true;
},
filterByLabel(label, e) {
if (!this.updateFilters) return;
@ -146,23 +149,17 @@ gl.issueBoards.IssueCardInner = Vue.extend({
</span>
</h4>
<div class="card-assignee">
<a
class="has-tooltip js-no-trigger"
:href="assigneeUrl(assignee)"
:title="assigneeUrlTitle(assignee)"
<user-avatar-link
v-for="(assignee, index) in issue.assignees"
:key="assignee.id"
v-if="shouldRenderAssignee(index)"
data-container="body"
data-placement="bottom"
>
<img
class="avatar avatar-inline s20"
:src="assignee.avatar"
width="20"
height="20"
:alt="avatarUrlTitle(assignee)"
/>
</a>
class="js-no-trigger"
:link-href="assigneeUrl(assignee)"
:img-alt="avatarUrlTitle(assignee)"
:img-src="assignee.avatar"
:tooltip-text="assigneeUrlTitle(assignee)"
tooltip-placement="bottom"
/>
<span
class="avatar-counter has-tooltip"
:title="assigneeCounterTooltip"

View file

@ -13,6 +13,7 @@ export default {
FilteredSearchContainer.container = this.$el;
this.filteredSearch = new FilteredSearchBoards(this.store);
this.filteredSearch.setup();
this.filteredSearch.removeTokens();
this.filteredSearch.handleInputPlaceholder();
this.filteredSearch.toggleClearSearchButton();

View file

@ -2,8 +2,7 @@
/* global Flash */
import Vue from 'vue';
require('./lists_dropdown');
import './lists_dropdown';
const ModalStore = gl.issueBoards.ModalStore;
@ -27,7 +26,8 @@ gl.issueBoards.ModalFooter = Vue.extend({
},
methods: {
addIssues() {
const list = this.modal.selectedList || this.state.lists[0];
const firstListIndex = 1;
const list = this.modal.selectedList || this.state.lists[firstListIndex];
const selectedIssues = ModalStore.getSelectedIssues();
const issueIds = selectedIssues.map(issue => issue.globalId);

View file

@ -1,7 +1,6 @@
import Vue from 'vue';
import modalFilters from './filters';
require('./tabs');
import './tabs';
const ModalStore = gl.issueBoards.ModalStore;

View file

@ -1,12 +1,12 @@
/* global ListIssue */
import Vue from 'vue';
import queryData from '../../utils/query_data';
require('./header');
require('./list');
require('./footer');
require('./empty_state');
import queryData from '~/boards/utils/query_data';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import './header';
import './list';
import './footer';
import './empty_state';
const ModalStore = gl.issueBoards.ModalStore;
@ -88,9 +88,9 @@ gl.issueBoards.IssuesModal = Vue.extend({
return gl.boardService.getBacklog(queryData(this.filter.path, {
page: this.page,
per: this.perPage,
})).then((res) => {
const data = res.json();
}))
.then(resp => resp.json())
.then((data) => {
if (clearIssues) {
this.issues = [];
}
@ -137,6 +137,7 @@ gl.issueBoards.IssuesModal = Vue.extend({
'modal-list': gl.issueBoards.ModalList,
'modal-footer': gl.issueBoards.ModalFooter,
'empty-state': gl.issueBoards.ModalEmptyState,
loadingIcon,
},
template: `
<div
@ -161,7 +162,7 @@ gl.issueBoards.IssuesModal = Vue.extend({
class="add-issues-list text-center"
v-if="loading || filterLoading">
<div class="add-issues-list-loading">
<i class="fa fa-spinner fa-spin"></i>
<loading-icon />
</div>
</section>
<modal-footer></modal-footer>

View file

@ -1,7 +1,7 @@
/* global ListIssue */
/* global bp */
import Vue from 'vue';
import bp from '../../../breakpoints';
const ModalStore = gl.issueBoards.ModalStore;

View file

@ -11,7 +11,7 @@ gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
},
computed: {
selected() {
return this.modal.selectedList || this.state.lists[0];
return this.modal.selectedList || this.state.lists[1];
},
},
destroyed() {

View file

@ -1,5 +1,6 @@
/* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var,
promise/catch-or-return */
import _ from 'underscore';
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};

View file

@ -46,8 +46,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
},
template: `
<div
class="block list"
v-if="list.type !== 'closed'">
class="block list">
<button
class="btn btn-default btn-block"
type="button"

View file

@ -2,7 +2,7 @@
import FilteredSearchContainer from '../filtered_search/container';
export default class FilteredSearchBoards extends gl.FilteredSearchManager {
constructor(store, updateUrl = false) {
constructor(store, updateUrl = false, cantEdit = []) {
super('boards');
this.store = store;
@ -11,6 +11,7 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
// Issue boards is slightly different, we handle all the requests async
// instead or reloading the page, we just re-fire the list ajax requests
this.isHandledAsync = true;
this.cantEdit = cantEdit;
}
updateObject(path) {
@ -40,4 +41,8 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
// Get the placeholder back if search is empty
this.filteredSearchInput.dispatchEvent(new Event('input'));
}
canEdit(tokenName) {
return this.cantEdit.indexOf(tokenName) === -1;
}
}

View file

@ -12,7 +12,9 @@ class List {
this.position = obj.position;
this.title = obj.title;
this.type = obj.list_type;
this.preset = ['closed', 'blank'].indexOf(this.type) > -1;
this.preset = ['backlog', 'closed', 'blank'].indexOf(this.type) > -1;
this.isExpandable = ['backlog', 'closed'].indexOf(this.type) > -1;
this.isExpanded = true;
this.page = 1;
this.loading = true;
this.loadingMore = false;
@ -38,9 +40,8 @@ class List {
save () {
return gl.boardService.createList(this.label.id)
.then((resp) => {
const data = resp.json();
.then(resp => resp.json())
.then((data) => {
this.id = data.id;
this.type = data.list_type;
this.position = data.position;
@ -89,8 +90,8 @@ class List {
}
return gl.boardService.getIssuesForList(this.id, data)
.then((resp) => {
const data = resp.json();
.then(resp => resp.json())
.then((data) => {
this.loading = false;
this.issuesSize = data.size;
@ -103,13 +104,18 @@ class List {
}
newIssue (issue) {
this.addIssue(issue);
this.addIssue(issue, null, 0);
this.issuesSize += 1;
return gl.boardService.newIssue(this.id, issue)
.then((resp) => {
const data = resp.json();
.then(resp => resp.json())
.then((data) => {
issue.id = data.iid;
if (this.issuesSize > 1) {
const moveBeforeIid = this.issues[1].id;
gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeIid);
}
});
}

View file

@ -23,11 +23,6 @@ class BoardService {
url: bulkUpdatePath,
},
});
Vue.http.interceptors.push((request, next) => {
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
next();
});
}
all () {

View file

@ -1,6 +1,6 @@
/* eslint-disable comma-dangle, space-before-function-paren, one-var, no-shadow, dot-notation, max-len */
/* global List */
import _ from 'underscore';
import Cookies from 'js-cookie';
window.gl = window.gl || {};
@ -22,6 +22,7 @@ gl.issueBoards.BoardsStore = {
create () {
this.state.lists = [];
this.filter.path = gl.utils.getUrlParamsArray().join('&');
this.detail = { issue: {} };
},
addList (listObj, defaultAvatar) {
const list = new List(listObj, defaultAvatar);
@ -31,10 +32,14 @@ gl.issueBoards.BoardsStore = {
},
new (listObj) {
const list = this.addList(listObj);
const backlogList = this.findList('type', 'backlog', 'backlog');
list
.save()
.then(() => {
// Remove any new issues from the backlog
// as they will be visible in the new list
list.issues.forEach(backlogList.removeIssue.bind(backlogList));
this.state.lists = _.sortBy(this.state.lists, 'position');
})
.catch(() => {
@ -47,7 +52,7 @@ gl.issueBoards.BoardsStore = {
},
shouldAddBlankState () {
// Decide whether to add the blank state
return !(this.state.lists.filter(list => list.type !== 'closed')[0]);
return !(this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'closed')[0]);
},
addBlankState () {
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
@ -100,7 +105,7 @@ gl.issueBoards.BoardsStore = {
issueTo.removeLabel(listFrom.label);
}
if (listTo.type === 'closed') {
if (listTo.type === 'closed' && listFrom.type !== 'backlog') {
issueLists.forEach((list) => {
list.removeIssue(issue);
});

View file

@ -0,0 +1,36 @@
const MODAL_SELECTOR = '#modal-delete-branch';
class DeleteModal {
constructor() {
this.$modal = $(MODAL_SELECTOR);
this.$toggleBtns = $(`[data-target="${MODAL_SELECTOR}"]`);
this.$branchName = $('.js-branch-name', this.$modal);
this.$confirmInput = $('.js-delete-branch-input', this.$modal);
this.$deleteBtn = $('.js-delete-branch', this.$modal);
this.bindEvents();
}
bindEvents() {
this.$toggleBtns.on('click', this.setModalData.bind(this));
this.$confirmInput.on('input', this.setDeleteDisabled.bind(this));
}
setModalData(e) {
this.branchName = e.currentTarget.dataset.branchName || '';
this.deletePath = e.currentTarget.dataset.deletePath || '';
this.updateModal();
}
setDeleteDisabled(e) {
this.$deleteBtn.attr('disabled', e.currentTarget.value !== this.branchName);
}
updateModal() {
this.$branchName.text(this.branchName);
this.$confirmInput.val('');
this.$deleteBtn.attr('href', this.deletePath);
this.$deleteBtn.attr('disabled', true);
}
}
export default DeleteModal;

View file

@ -1,66 +1,19 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, quotes, no-shadow, prefer-arrow-callback, prefer-template, consistent-return, no-return-assign, new-parens, no-param-reassign, max-len */
export const breakpoints = {
lg: 1200,
md: 992,
sm: 768,
xs: 0,
};
var Breakpoints = (function() {
var BreakpointInstance, instance;
const BreakpointInstance = {
windowWidth: () => window.innerWidth,
getBreakpointSize() {
const windowWidth = this.windowWidth();
function Breakpoints() {}
const breakpoint = Object.keys(breakpoints).find(key => windowWidth > breakpoints[key]);
instance = null;
return breakpoint;
},
};
BreakpointInstance = (function() {
var BREAKPOINTS;
BREAKPOINTS = ["xs", "sm", "md", "lg"];
function BreakpointInstance() {
this.setup();
}
BreakpointInstance.prototype.setup = function() {
var allDeviceSelector, els;
allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
return ".device-" + breakpoint;
});
if ($(allDeviceSelector.join(",")).length) {
return;
}
// Create all the elements
els = $.map(BREAKPOINTS, function(breakpoint) {
return "<div class='device-" + breakpoint + " visible-" + breakpoint + "'></div>";
});
return $("body").append(els.join(''));
};
BreakpointInstance.prototype.visibleDevice = function() {
var allDeviceSelector;
allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
return ".device-" + breakpoint;
});
return $(allDeviceSelector.join(",")).filter(":visible");
};
BreakpointInstance.prototype.getBreakpointSize = function() {
var $visibleDevice;
$visibleDevice = this.visibleDevice;
// TODO: Consider refactoring in light of turbolinks removal.
// the page refreshed via turbolinks
if (!$visibleDevice().length) {
this.setup();
}
$visibleDevice = this.visibleDevice();
return $visibleDevice.attr("class").split("visible-")[1];
};
return BreakpointInstance;
})();
Breakpoints.get = function() {
return instance != null ? instance : instance = new BreakpointInstance;
};
return Breakpoints;
})();
$(() => { window.bp = Breakpoints.get(); });
window.Breakpoints = Breakpoints;
export default BreakpointInstance;

View file

@ -1,47 +1,38 @@
/* eslint-disable func-names, wrap-iife, no-use-before-define,
consistent-return, prefer-rest-params */
/* global Breakpoints */
import _ from 'underscore';
import bp from './breakpoints';
import { bytesToKiB } from './lib/utils/number_utils';
const bind = function (fn, me) { return function () { return fn.apply(me, arguments); }; };
const AUTO_SCROLL_OFFSET = 75;
const DOWN_BUILD_TRACE = '#down-build-trace';
window.Build = (function () {
Build.timeout = null;
Build.state = null;
function Build(options) {
this.options = options || $('.js-build-options').data();
this.pageUrl = this.options.pageUrl;
this.buildUrl = this.options.buildUrl;
this.buildStatus = this.options.buildStatus;
this.state = this.options.logState;
this.buildStage = this.options.buildStage;
this.$document = $(document);
this.logBytes = 0;
this.hasBeenScrolled = false;
this.updateDropdown = bind(this.updateDropdown, this);
this.updateDropdown = this.updateDropdown.bind(this);
this.getBuildTrace = this.getBuildTrace.bind(this);
this.$body = $('body');
this.$buildTrace = $('#build-trace');
this.$autoScrollContainer = $('.autoscroll-container');
this.$autoScrollStatus = $('#autoscroll-status');
this.$autoScrollStatusText = this.$autoScrollStatus.find('.status-text');
this.$upBuildTrace = $('#up-build-trace');
this.$downBuildTrace = $(DOWN_BUILD_TRACE);
this.$scrollTopBtn = $('#scroll-top');
this.$scrollBottomBtn = $('#scroll-bottom');
this.$buildRefreshAnimation = $('.js-build-refresh');
this.$buildScroll = $('#js-build-scroll');
this.$truncatedInfo = $('.js-truncated-info');
this.$buildTraceOutput = $('.js-build-output');
this.$topBar = $('.js-top-bar');
// Scroll controllers
this.$scrollTopBtn = $('.js-scroll-up');
this.$scrollBottomBtn = $('.js-scroll-down');
clearTimeout(Build.timeout);
// Init breakpoint checker
this.bp = Breakpoints.get();
this.initSidebar();
this.populateJobs(this.buildStage);
@ -56,54 +47,141 @@ window.Build = (function () {
.off('click', '.stage-item')
.on('click', '.stage-item', this.updateDropdown);
this.$document.on('scroll', this.initScrollMonitor.bind(this));
// add event listeners to the scroll buttons
this.$scrollTopBtn
.off('click')
.on('click', this.scrollToTop.bind(this));
this.$scrollBottomBtn
.off('click')
.on('click', this.scrollToBottom.bind(this));
this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
$(window)
.off('scroll')
.on('scroll', () => {
const contentHeight = this.$buildTraceOutput.height();
if (contentHeight > this.windowSize) {
// means the user did not scroll, the content was updated.
this.windowSize = contentHeight;
} else {
// User scrolled
this.hasBeenScrolled = true;
this.toggleScrollAnimation(false);
}
this.scrollThrottled();
});
$(window)
.off('resize.build')
.on('resize.build', this.sidebarOnResize.bind(this));
$('a', this.$buildScroll)
.off('click.stepTrace')
.on('click.stepTrace', this.stepTrace);
.on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100));
this.updateArtifactRemoveDate();
this.initScrollButtonAffix();
this.invokeBuildTrace();
this.initAffixTopArea();
this.getBuildTrace();
}
Build.prototype.initAffixTopArea = function () {
/**
If the browser does not support position sticky, it returns the position as static.
If the browser does support sticky, then we allow the browser to handle it, if not
then we default back to Bootstraps affix
**/
if (this.$topBar.css('position') !== 'static') return;
const offsetTop = this.$buildTrace.offset().top;
this.$topBar.affix({
offset: {
top: offsetTop,
},
});
};
Build.prototype.canScroll = function () {
return $(document).height() > $(window).height();
};
Build.prototype.toggleScroll = function () {
const currentPosition = $(document).scrollTop();
const scrollHeight = $(document).height();
const windowHeight = $(window).height();
if (this.canScroll()) {
if (currentPosition > 0 &&
(scrollHeight - currentPosition !== windowHeight)) {
// User is in the middle of the log
this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, false);
} else if (currentPosition === 0) {
// User is at Top of Build Log
this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, false);
} else if (scrollHeight - currentPosition === windowHeight) {
// User is at the bottom of the build log.
this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, true);
}
} else {
this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, true);
}
};
Build.prototype.scrollDown = function () {
$(document).scrollTop($(document).height());
};
Build.prototype.scrollToBottom = function () {
this.scrollDown();
this.hasBeenScrolled = true;
this.toggleScroll();
};
Build.prototype.scrollToTop = function () {
$(document).scrollTop(0);
this.hasBeenScrolled = true;
this.toggleScroll();
};
Build.prototype.toggleDisableButton = function ($button, disable) {
if (disable && $button.prop('disabled')) return;
$button.prop('disabled', disable);
};
Build.prototype.toggleScrollAnimation = function (toggle) {
this.$scrollBottomBtn.toggleClass('animate', toggle);
};
Build.prototype.initSidebar = function () {
this.$sidebar = $('.js-build-sidebar');
this.$sidebar.niceScroll();
this.$document
.off('click', '.js-sidebar-build-toggle')
.on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
};
Build.prototype.invokeBuildTrace = function () {
return this.getBuildTrace();
};
Build.prototype.getBuildTrace = function () {
return $.ajax({
url: `${this.pageUrl}/trace.json`,
dataType: 'json',
data: {
state: this.state,
},
success: ((log) => {
const $buildContainer = $('.js-build-output');
data: this.state,
})
.done((log) => {
gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
if (log.state) {
this.state = log.state;
}
this.windowSize = this.$buildTraceOutput.height();
if (log.append) {
$buildContainer.append(log.html);
this.$buildTraceOutput.append(log.html);
this.logBytes += log.size;
} else {
$buildContainer.html(log.html);
this.$buildTraceOutput.html(log.html);
this.logBytes = log.size;
}
@ -114,157 +192,62 @@ window.Build = (function () {
const size = bytesToKiB(this.logBytes);
$('.js-truncated-info-size').html(`${size}`);
this.$truncatedInfo.removeClass('hidden');
this.initAffixTruncatedInfo();
} else {
this.$truncatedInfo.addClass('hidden');
}
this.checkAutoscroll();
if (!log.complete) {
if (!this.hasBeenScrolled) {
this.toggleScrollAnimation(true);
} else {
this.toggleScrollAnimation(false);
}
Build.timeout = setTimeout(() => {
this.invokeBuildTrace();
this.getBuildTrace();
}, 4000);
} else {
this.$buildRefreshAnimation.remove();
this.toggleScrollAnimation(false);
}
if (log.status !== this.buildStatus) {
let pageUrl = this.pageUrl;
if (this.$autoScrollStatus.data('state') === 'enabled') {
pageUrl += DOWN_BUILD_TRACE;
}
gl.utils.visitUrl(pageUrl);
gl.utils.visitUrl(this.pageUrl);
}
}),
error: () => {
})
.fail(() => {
this.$buildRefreshAnimation.remove();
return this.initScrollMonitor();
},
});
};
Build.prototype.checkAutoscroll = function () {
if (this.$autoScrollStatus.data('state') === 'enabled') {
return $('html,body').scrollTop(this.$buildTrace.height());
}
// Handle a situation where user started new build
// but never scrolled a page
if (!this.$scrollTopBtn.is(':visible') &&
!this.$scrollBottomBtn.is(':visible') &&
!gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
this.$scrollBottomBtn.show();
}
};
Build.prototype.initScrollButtonAffix = function () {
// Hide everything initially
this.$scrollTopBtn.hide();
this.$scrollBottomBtn.hide();
this.$autoScrollContainer.hide();
};
// Page scroll listener to detect if user has scrolling page
// and handle following cases
// 1) User is at Top of Build Log;
// - Hide Top Arrow button
// - Show Bottom Arrow button
// - Disable Autoscroll and hide indicator (when build is running)
// 2) User is at Bottom of Build Log;
// - Show Top Arrow button
// - Hide Bottom Arrow button
// - Enable Autoscroll and show indicator (when build is running)
// 3) User is somewhere in middle of Build Log;
// - Show Top Arrow button
// - Show Bottom Arrow button
// - Disable Autoscroll and hide indicator (when build is running)
Build.prototype.initScrollMonitor = function () {
if (!gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
!gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
// User is somewhere in middle of Build Log
this.$scrollTopBtn.show();
if (this.buildStatus === 'success' || this.buildStatus === 'failed') { // Check if Build is completed
this.$scrollBottomBtn.show();
} else if (this.$buildRefreshAnimation.is(':visible') &&
!gl.utils.isInViewport(this.$buildRefreshAnimation.get(0))) {
this.$scrollBottomBtn.show();
} else {
this.$scrollBottomBtn.hide();
}
// Hide Autoscroll Status Indicator
if (this.$scrollBottomBtn.is(':visible')) {
this.$autoScrollContainer.hide();
this.$autoScrollStatusText.removeClass('animate');
} else {
this.$autoScrollContainer.css({
top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET,
}).show();
this.$autoScrollStatusText.addClass('animate');
}
} else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
!gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
// User is at Top of Build Log
this.$scrollTopBtn.hide();
this.$scrollBottomBtn.show();
this.$autoScrollContainer.hide();
this.$autoScrollStatusText.removeClass('animate');
} else if ((!gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
gl.utils.isInViewport(this.$downBuildTrace.get(0))) ||
(this.$buildRefreshAnimation.is(':visible') &&
gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)))) {
// User is at Bottom of Build Log
this.$scrollTopBtn.show();
this.$scrollBottomBtn.hide();
// Show and Reposition Autoscroll Status Indicator
this.$autoScrollContainer.css({
top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET,
}).show();
this.$autoScrollStatusText.addClass('animate');
} else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
// Build Log height is small
this.$scrollTopBtn.hide();
this.$scrollBottomBtn.hide();
// Hide Autoscroll Status Indicator
this.$autoScrollContainer.hide();
this.$autoScrollStatusText.removeClass('animate');
}
if (this.buildStatus === 'running' || this.buildStatus === 'pending') {
// Check if Refresh Animation is in Viewport and enable Autoscroll, disable otherwise.
this.$autoScrollStatus.data(
'state',
gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)) ? 'enabled' : 'disabled',
);
}
})
.then(() => {
if (!this.hasBeenScrolled) {
this.scrollDown();
}
})
.then(() => this.toggleScroll());
};
Build.prototype.shouldHideSidebarForViewport = function () {
const bootstrapBreakpoint = this.bp.getBreakpointSize();
const bootstrapBreakpoint = bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
};
Build.prototype.toggleSidebar = function (shouldHide) {
const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
const $toggleButton = $('.js-sidebar-build-toggle-header');
this.$buildScroll.toggleClass('sidebar-expanded', shouldShow)
.toggleClass('sidebar-collapsed', shouldHide);
this.$truncatedInfo.toggleClass('sidebar-expanded', shouldShow)
.toggleClass('sidebar-collapsed', shouldHide);
this.$sidebar.toggleClass('right-sidebar-expanded', shouldShow)
this.$sidebar
.toggleClass('right-sidebar-expanded', shouldShow)
.toggleClass('right-sidebar-collapsed', shouldHide);
this.$topBar
.toggleClass('sidebar-expanded', shouldShow)
.toggleClass('sidebar-collapsed', shouldHide);
if (this.$sidebar.hasClass('right-sidebar-expanded')) {
$toggleButton.addClass('hidden');
} else {
$toggleButton.removeClass('hidden');
}
};
Build.prototype.sidebarOnResize = function () {
@ -301,24 +284,5 @@ window.Build = (function () {
this.populateJobs(stage);
};
Build.prototype.stepTrace = function (e) {
e.preventDefault();
const $currentTarget = $(e.currentTarget);
$.scrollTo($currentTarget.attr('href'), {
offset: 0,
});
};
Build.prototype.initAffixTruncatedInfo = function () {
const offsetTop = this.$buildTrace.offset().top;
this.$truncatedInfo.affix({
offset: {
top: offsetTop,
},
});
};
return Build;
})();

View file

@ -2,7 +2,7 @@
$(function() {
$('.reveal-variables').off('click').on('click', function() {
$('.js-build').toggle().niceScroll();
$('.js-build-variables').toggle();
$(this).hide();
});
});

View file

@ -0,0 +1,97 @@
import DropLab from './droplab/drop_lab';
import ISetter from './droplab/plugins/input_setter';
// Todo: Remove this when fixing issue in input_setter plugin
const InputSetter = Object.assign({}, ISetter);
class CloseReopenReportToggle {
constructor(opts = {}) {
this.dropdownTrigger = opts.dropdownTrigger;
this.dropdownList = opts.dropdownList;
this.button = opts.button;
}
initDroplab() {
this.reopenItem = this.dropdownList.querySelector('.reopen-item');
this.closeItem = this.dropdownList.querySelector('.close-item');
this.droplab = new DropLab();
const config = this.setConfig();
this.droplab.init(this.dropdownTrigger, this.dropdownList, [InputSetter], config);
}
updateButton(isClosed) {
this.toggleButtonType(isClosed);
this.button.blur();
}
toggleButtonType(isClosed) {
const [showItem, hideItem] = this.getButtonTypes(isClosed);
showItem.classList.remove('hidden');
showItem.classList.add('droplab-item-selected');
hideItem.classList.add('hidden');
hideItem.classList.remove('droplab-item-selected');
showItem.click();
}
getButtonTypes(isClosed) {
return isClosed ? [this.reopenItem, this.closeItem] : [this.closeItem, this.reopenItem];
}
setDisable(shouldDisable = true) {
if (shouldDisable) {
this.button.setAttribute('disabled', 'true');
this.dropdownTrigger.setAttribute('disabled', 'true');
} else {
this.button.removeAttribute('disabled');
this.dropdownTrigger.removeAttribute('disabled');
}
}
setConfig() {
const config = {
InputSetter: [
{
input: this.button,
valueAttribute: 'data-text',
inputAttribute: 'data-value',
},
{
input: this.button,
valueAttribute: 'data-text',
inputAttribute: 'title',
},
{
input: this.button,
valueAttribute: 'data-button-class',
inputAttribute: 'class',
},
{
input: this.dropdownTrigger,
valueAttribute: 'data-toggle-class',
inputAttribute: 'class',
},
{
input: this.button,
valueAttribute: 'data-url',
inputAttribute: 'href',
},
{
input: this.button,
valueAttribute: 'data-method',
inputAttribute: 'data-method',
},
],
};
return config;
}
}
export default CloseReopenReportToggle;

View file

@ -1,5 +1,8 @@
import DropLab from './droplab/drop_lab';
import InputSetter from './droplab/plugins/input_setter';
import ISetter from './droplab/plugins/input_setter';
// Todo: Remove this when fixing issue in input_setter plugin
const InputSetter = Object.assign({}, ISetter);
class CommentTypeToggle {
constructor(opts = {}) {

View file

@ -1,29 +1,43 @@
/* eslint-disable no-param-reassign */
import Vue from 'vue';
import VueResource from 'vue-resource';
import CommitPipelinesTable from './pipelines_table';
Vue.use(VueResource);
import commitPipelinesTable from './pipelines_table.vue';
/**
* Commits View > Pipelines Tab > Pipelines Table.
*
* Renders Pipelines table in pipelines tab in the commits show view.
* Used in:
* - Commit details View > Pipelines Tab > Pipelines Table.
* - Merge Request details View > Pipelines Tab > Pipelines Table.
* - New Merge Request View > Pipelines Tab > Pipelines Table.
*/
// export for use in merge_request_tabs.js (TODO: remove this hack)
const CommitPipelinesTable = Vue.extend(commitPipelinesTable);
// export for use in merge_request_tabs.js (TODO: remove this hack when we understand how to load
// vue.js in merge_request_tabs.js)
window.gl = window.gl || {};
window.gl.CommitPipelinesTable = CommitPipelinesTable;
$(() => {
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
document.addEventListener('DOMContentLoaded', () => {
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable().$mount();
pipelineTableViewEl.appendChild(gl.commits.pipelines.PipelinesTableBundle.$el);
if (pipelineTableViewEl) {
// Update MR and Commits tabs
pipelineTableViewEl.addEventListener('update-pipelines-count', (event) => {
if (event.detail.pipelines &&
event.detail.pipelines.count &&
event.detail.pipelines.count.all) {
const badge = document.querySelector('.js-pipelines-mr-count');
badge.textContent = event.detail.pipelines.count.all;
}
});
if (pipelineTableViewEl.dataset.disableInitialization === undefined) {
const table = new CommitPipelinesTable({
propsData: {
endpoint: pipelineTableViewEl.dataset.endpoint,
helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
},
}).$mount();
pipelineTableViewEl.appendChild(table.$el);
}
}
});

View file

@ -1,179 +0,0 @@
import Vue from 'vue';
import Visibility from 'visibilityjs';
import PipelinesTableComponent from '../../vue_shared/components/pipelines_table';
import PipelinesService from '../../pipelines/services/pipelines_service';
import PipelineStore from '../../pipelines/stores/pipelines_store';
import eventHub from '../../pipelines/event_hub';
import EmptyState from '../../pipelines/components/empty_state.vue';
import ErrorState from '../../pipelines/components/error_state.vue';
import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor';
import Poll from '../../lib/utils/poll';
/**
*
* Uses `pipelines-table-component` to render Pipelines table with an API call.
* Endpoint is provided in HTML and passed as `endpoint`.
* We need a store to store the received environemnts.
* We need a service to communicate with the server.
*
* Necessary SVG in the table are provided as props. This should be refactored
* as soon as we have Webpack and can load them directly into JS files.
*/
export default Vue.component('pipelines-table', {
components: {
'pipelines-table-component': PipelinesTableComponent,
'error-state': ErrorState,
'empty-state': EmptyState,
},
/**
* Accesses the DOM to provide the needed data.
* Returns the necessary props to render `pipelines-table-component` component.
*
* @return {Object}
*/
data() {
const store = new PipelineStore();
return {
endpoint: null,
helpPagePath: null,
store,
state: store.state,
isLoading: false,
hasError: false,
isMakingRequest: false,
updateGraphDropdown: false,
};
},
computed: {
shouldRenderErrorState() {
return this.hasError && !this.isLoading;
},
shouldRenderEmptyState() {
return !this.state.pipelines.length &&
!this.isLoading &&
!this.hasError;
},
shouldRenderTable() {
return !this.isLoading &&
this.state.pipelines.length > 0 &&
!this.hasError;
},
},
/**
* When the component is about to be mounted, tell the service to fetch the data
*
* A request to fetch the pipelines will be made.
* In case of a successfull response we will store the data in the provided
* store, in case of a failed response we need to warn the user.
*
*/
beforeMount() {
const element = document.querySelector('#commit-pipeline-table-view');
this.endpoint = element.dataset.endpoint;
this.helpPagePath = element.dataset.helpPagePath;
this.service = new PipelinesService(this.endpoint);
this.poll = new Poll({
resource: this.service,
method: 'getPipelines',
successCallback: this.successCallback,
errorCallback: this.errorCallback,
notificationCallback: this.setIsMakingRequest,
});
if (!Visibility.hidden()) {
this.isLoading = true;
this.poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
beforeDestroyed() {
eventHub.$off('refreshPipelines');
},
destroyed() {
this.poll.stop();
},
methods: {
fetchPipelines() {
this.isLoading = true;
return this.service.getPipelines()
.then(response => this.successCallback(response))
.catch(() => this.errorCallback());
},
successCallback(resp) {
const response = resp.json();
// depending of the endpoint the response can either bring a `pipelines` key or not.
const pipelines = response.pipelines || response;
this.store.storePipelines(pipelines);
this.isLoading = false;
this.updateGraphDropdown = true;
},
errorCallback() {
this.hasError = true;
this.isLoading = false;
this.updateGraphDropdown = false;
},
setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest;
if (isMakingRequest) {
this.updateGraphDropdown = false;
}
},
},
template: `
<div class="content-list pipelines">
<div
class="realtime-loading"
v-if="isLoading">
<i
class="fa fa-spinner fa-spin"
aria-hidden="true" />
</div>
<empty-state
v-if="shouldRenderEmptyState"
:help-page-path="helpPagePath" />
<error-state v-if="shouldRenderErrorState" />
<div
class="table-holder"
v-if="shouldRenderTable">
<pipelines-table-component
:pipelines="state.pipelines"
:service="service"
:update-graph-dropdown="updateGraphDropdown"
/>
</div>
</div>
`,
});

View file

@ -0,0 +1,101 @@
<script>
import PipelinesService from '../../pipelines/services/pipelines_service';
import PipelineStore from '../../pipelines/stores/pipelines_store';
import pipelinesMixin from '../../pipelines/mixins/pipelines';
export default {
props: {
endpoint: {
type: String,
required: true,
},
helpPagePath: {
type: String,
required: true,
},
},
mixins: [
pipelinesMixin,
],
data() {
const store = new PipelineStore();
return {
store,
state: store.state,
};
},
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;
},
},
created() {
this.service = new PipelinesService(this.endpoint);
},
methods: {
successCallback(resp) {
return resp.json().then((response) => {
// depending of the endpoint the response can either bring a `pipelines` key or not.
const pipelines = response.pipelines || response;
this.setCommonData(pipelines);
const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
detail: {
pipelines: response,
},
});
// notifiy to update the count in tabs
if (this.$el.parentElement) {
this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
}
});
},
},
};
</script>
<template>
<div class="content-list pipelines">
<loading-icon
label="Loading pipelines"
size="3"
v-if="isLoading"
/>
<empty-state
v-if="shouldRenderEmptyState"
:help-page-path="helpPagePath"
/>
<error-state
v-if="shouldRenderErrorState"
/>
<div
class="table-holder"
v-if="shouldRenderTable">
<pipelines-table-component
:pipelines="state.pipelines"
:update-graph-dropdown="updateGraphDropdown"
/>
</div>
</div>
</template>

View file

@ -7,6 +7,8 @@ window.CommitsList = (function() {
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");
@ -14,9 +16,9 @@ window.CommitsList = (function() {
return false;
}
});
Pager.init(limit, false, false, function() {
gl.utils.localTimeAgo($('.js-timeago'));
});
Pager.init(parseInt(limit, 10), false, false, this.processCommits);
this.content = $("#commits-list");
this.searchField = $("#commits-search");
this.lastSearch = this.searchField.val();
@ -62,5 +64,34 @@ window.CommitsList = (function() {
});
};
// Prepare loaded data.
CommitsList.processCommits = (data) => {
let processedData = data;
const $processedData = $(processedData);
const $commitsHeadersLast = CommitsList.$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');
let commitsCount;
// If commits headers show the same date,
// remove the last header and change the previous one.
if (lastShownDay === loadedShownDayFirst) {
// Last shown commits count under the last commits header.
commitsCount = $commitsHeadersLast.nextUntil('li.js-commit-header').find('li.commit').length;
// Remove duplicate of commits header.
processedData = $processedData.not(`li.js-commit-header[data-day="${loadedShownDayFirst}"]`);
// Update commits count in the previous commits header.
commitsCount += Number($(processedData).nextUntil('li.js-commit-header').first().find('li.commit').length);
$commitsHeadersLast.find('span.commits-count').text(`${commitsCount} ${gl.text.pluralize('commit', commitsCount)}`);
}
gl.utils.localTimeAgo($processedData.find('.js-timeago'));
return processedData;
};
return CommitsList;
})();

View file

@ -3,11 +3,13 @@ import $ from 'jquery';
// bootstrap jQuery plugins
import 'bootstrap-sass/assets/javascripts/bootstrap/affix';
import 'bootstrap-sass/assets/javascripts/bootstrap/alert';
import 'bootstrap-sass/assets/javascripts/bootstrap/button';
import 'bootstrap-sass/assets/javascripts/bootstrap/dropdown';
import 'bootstrap-sass/assets/javascripts/bootstrap/modal';
import 'bootstrap-sass/assets/javascripts/bootstrap/tab';
import 'bootstrap-sass/assets/javascripts/bootstrap/transition';
import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip';
import 'bootstrap-sass/assets/javascripts/bootstrap/popover';
// custom jQuery functions
$.fn.extend({

View file

@ -1,3 +1,4 @@
import 'underscore';
import './polyfills';
import './jquery';
import './bootstrap';

View file

@ -6,6 +6,5 @@ import 'vendor/jquery.endless-scroll';
import 'vendor/jquery.caret';
import 'vendor/jquery.atwho';
import 'vendor/jquery.scrollTo';
import 'vendor/jquery.nicescroll';
import 'vendor/jquery.waitforimages';
import 'select2/select2';

View file

@ -1,5 +1,6 @@
// ECMAScript polyfills
import 'core-js/fn/array/find';
import 'core-js/fn/array/find-index';
import 'core-js/fn/array/from';
import 'core-js/fn/array/includes';
import 'core-js/fn/object/assign';

View file

@ -1,6 +1,7 @@
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
require('./lib/utils/common_utils');
import _ from 'underscore';
import './lib/utils/common_utils';
import { placeholderImage } from './lazy_loader';
const gfmRules = {
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
@ -18,12 +19,12 @@ const gfmRules = {
},
},
TaskListFilter: {
'input[type=checkbox].task-list-item-checkbox'(el, text) {
'input[type=checkbox].task-list-item-checkbox'(el) {
return `[${el.checked ? 'x' : ' '}]`;
},
},
ReferenceFilter: {
'.tooltip'(el, text) {
'.tooltip'(el) {
return '';
},
'a.gfm:not([data-link=true])'(el, text) {
@ -39,15 +40,15 @@ const gfmRules = {
},
},
TableOfContentsFilter: {
'ul.section-nav'(el, text) {
'ul.section-nav'(el) {
return '[[_TOC_]]';
},
},
EmojiFilter: {
'img.emoji'(el, text) {
'img.emoji'(el) {
return el.getAttribute('alt');
},
'gl-emoji'(el, text) {
'gl-emoji'(el) {
return `:${el.getAttribute('data-name')}:`;
},
},
@ -56,14 +57,19 @@ const gfmRules = {
return text;
},
},
ImageLazyLoadFilter: {
'img'(el, text) {
return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
},
},
VideoLinkFilter: {
'.video-container'(el, text) {
'.video-container'(el) {
const videoEl = el.querySelector('video');
if (!videoEl) return false;
return CopyAsGFM.nodeToGFM(videoEl);
},
'video'(el, text) {
'video'(el) {
return `![${el.dataset.title}](${el.getAttribute('src')})`;
},
},
@ -74,19 +80,19 @@ const gfmRules = {
'code.code.math[data-math-style=inline]'(el, text) {
return `$\`${text}\`$`;
},
'span.katex-display span.katex-mathml'(el, text) {
'span.katex-display span.katex-mathml'(el) {
const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
if (!mathAnnotation) return false;
return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``;
},
'span.katex-mathml'(el, text) {
'span.katex-mathml'(el) {
const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
if (!mathAnnotation) return false;
return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`;
},
'span.katex-html'(el, text) {
'span.katex-html'(el) {
// We don't want to include the content of this element in the copied text.
return '';
},
@ -95,7 +101,7 @@ const gfmRules = {
},
},
SanitizationFilter: {
'a[name]:not([href]):empty'(el, text) {
'a[name]:not([href]):empty'(el) {
return el.outerHTML;
},
'dl'(el, text) {
@ -143,7 +149,7 @@ const gfmRules = {
},
},
MarkdownFilter: {
'br'(el, text) {
'br'(el) {
// Two spaces at the end of a line are turned into a BR
return ' ';
},
@ -162,8 +168,10 @@ const gfmRules = {
'blockquote'(el, text) {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
},
'img'(el, text) {
return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
'img'(el) {
const imageSrc = el.src;
const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : (el.dataset.src || '');
return `![${el.getAttribute('alt')}](${imageUrl})`;
},
'a.anchor'(el, text) {
// Don't render a Markdown link for the anchor link inside a heading
@ -222,10 +230,10 @@ const gfmRules = {
'sup'(el, text) {
return `^${text}`;
},
'hr'(el, text) {
'hr'(el) {
return '-----';
},
'table'(el, text) {
'table'(el) {
const theadEl = el.querySelector('thead');
const tbodyEl = el.querySelector('tbody');
if (!theadEl || !tbodyEl) return false;
@ -233,11 +241,11 @@ const gfmRules = {
const theadText = CopyAsGFM.nodeToGFM(theadEl);
const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl);
return theadText + tbodyText;
return [theadText, tbodyText].join('\n');
},
'thead'(el, text) {
const cells = _.map(el.querySelectorAll('th'), (cell) => {
let chars = CopyAsGFM.nodeToGFM(cell).trim().length + 2;
let chars = CopyAsGFM.nodeToGFM(cell).length + 2;
let before = '';
let after = '';
@ -262,10 +270,15 @@ const gfmRules = {
return before + middle + after;
});
return `${text}|${cells.join('|')}|`;
const separatorRow = `|${cells.join('|')}|`;
return [text, separatorRow].join('\n');
},
'tr'(el, text) {
const cells = _.map(el.querySelectorAll('td, th'), cell => CopyAsGFM.nodeToGFM(cell).trim());
'tr'(el) {
const cellEls = el.querySelectorAll('td, th');
if (cellEls.length === 0) return false;
const cells = _.map(cellEls, cell => CopyAsGFM.nodeToGFM(cell));
return `| ${cells.join(' | ')} |`;
},
},
@ -273,12 +286,12 @@ const gfmRules = {
class CopyAsGFM {
constructor() {
$(document).on('copy', '.md, .wiki', (e) => { this.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { this.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
$(document).on('paste', '.js-gfm-input', this.pasteGFM.bind(this));
$(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
$(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM);
}
copyAsGFM(e, transformer) {
static copyAsGFM(e, transformer) {
const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return;
@ -292,26 +305,59 @@ class CopyAsGFM {
e.stopPropagation();
clipboardData.setData('text/plain', el.textContent);
clipboardData.setData('text/x-gfm', CopyAsGFM.nodeToGFM(el));
clipboardData.setData('text/x-gfm', this.nodeToGFM(el));
}
pasteGFM(e) {
static pasteGFM(e) {
const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return;
const text = clipboardData.getData('text/plain');
const gfm = clipboardData.getData('text/x-gfm');
if (!gfm) return;
e.preventDefault();
window.gl.utils.insertText(e.target, gfm);
window.gl.utils.insertText(e.target, (textBefore, textAfter) => {
// If the text before the cursor contains an odd number of backticks,
// we are either inside an inline code span that starts with 1 backtick
// or a code block that starts with 3 backticks.
// This logic still holds when there are one or more _closed_ code spans
// or blocks that will have 2 or 6 backticks.
// This will break down when the actual code block contains an uneven
// number of backticks, but this is a rare edge case.
const backtickMatch = textBefore.match(/`/g);
const insideCodeBlock = backtickMatch && (backtickMatch.length % 2) === 1;
if (insideCodeBlock) {
return text;
}
return gfm;
});
}
static transformGFMSelection(documentFragment) {
// If the documentFragment contains more than just Markdown, don't copy as GFM.
if (documentFragment.querySelector('.md, .wiki')) return null;
const gfmEls = documentFragment.querySelectorAll('.md, .wiki');
switch (gfmEls.length) {
case 0: {
return documentFragment;
}
case 1: {
return gfmEls[0];
}
default: {
const allGfmEl = document.createElement('div');
return documentFragment;
for (let i = 0; i < gfmEls.length; i += 1) {
const lineEl = gfmEls[i];
allGfmEl.appendChild(lineEl);
allGfmEl.appendChild(document.createTextNode('\n\n'));
}
return allGfmEl;
}
}
}
static transformCodeSelection(documentFragment) {
@ -343,7 +389,7 @@ class CopyAsGFM {
return codeEl;
}
static nodeToGFM(node) {
static nodeToGFM(node, respectWhitespaceParam = false) {
if (node.nodeType === Node.COMMENT_NODE) {
return '';
}
@ -352,7 +398,9 @@ class CopyAsGFM {
return node.textContent;
}
const text = this.innerGFM(node);
const respectWhitespace = respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE');
const text = this.innerGFM(node, respectWhitespace);
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
return text;
@ -366,7 +414,17 @@ class CopyAsGFM {
if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue;
const result = func(node, text);
let result;
if (func.length === 2) {
// if `func` takes 2 arguments, it depends on text.
// if there is no text, we don't need to generate GFM for this node.
if (text.length === 0) continue;
result = func(node, text);
} else {
result = func(node);
}
if (result === false) continue;
return result;
@ -376,7 +434,7 @@ class CopyAsGFM {
return text;
}
static innerGFM(parentNode) {
static innerGFM(parentNode, respectWhitespace = false) {
const nodes = parentNode.childNodes;
const clonedParentNode = parentNode.cloneNode(true);
@ -386,13 +444,19 @@ class CopyAsGFM {
const node = nodes[i];
const clonedNode = clonedNodes[i];
const text = this.nodeToGFM(node);
const text = this.nodeToGFM(node, respectWhitespace);
// `clonedNode.replaceWith(text)` is not yet widely supported
clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode);
}
return clonedParentNode.innerText || clonedParentNode.textContent;
let nodeText = clonedParentNode.innerText || clonedParentNode.textContent;
if (!respectWhitespace) {
nodeText = nodeText.trim();
}
return nodeText;
}
}

View file

@ -1,5 +1,5 @@
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-param-reassign, wrap-iife, max-len */
/* global Api */
import Api from './api';
class CreateLabelDropdown {
constructor ($el, namespacePath, projectPath) {

View file

@ -1,6 +1,7 @@
/* eslint-disable no-param-reassign */
import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
@ -10,6 +11,9 @@ global.cycleAnalytics.StageCodeComponent = Vue.extend({
items: Array,
stage: Object,
},
components: {
userAvatarImage,
},
template: `
<div>
<div class="events-description">
@ -19,7 +23,8 @@ global.cycleAnalytics.StageCodeComponent = Vue.extend({
<ul class="stage-event-list">
<li v-for="mergeRequest in items" class="stage-event-item">
<div class="item-details">
<img class="avatar" :src="mergeRequest.author.avatarUrl">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
<h5 class="item-title merge-merquest-title">
<a :href="mergeRequest.url">
{{ mergeRequest.title }}

View file

@ -1,6 +1,6 @@
/* eslint-disable no-param-reassign */
import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
@ -10,6 +10,9 @@ global.cycleAnalytics.StageIssueComponent = Vue.extend({
items: Array,
stage: Object,
},
components: {
userAvatarImage,
},
template: `
<div>
<div class="events-description">
@ -19,7 +22,8 @@ global.cycleAnalytics.StageIssueComponent = Vue.extend({
<ul class="stage-event-list">
<li v-for="issue in items" class="stage-event-item">
<div class="item-details">
<img class="avatar" :src="issue.author.avatarUrl">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="issue.author.avatarUrl"/>
<h5 class="item-title issue-title">
<a class="issue-title" :href="issue.url">
{{ issue.title }}

View file

@ -1,5 +1,6 @@
/* eslint-disable no-param-reassign */
import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconCommit from '../svg/icon_commit.svg';
const global = window.gl || (window.gl = {});
@ -10,11 +11,12 @@ global.cycleAnalytics.StagePlanComponent = Vue.extend({
items: Array,
stage: Object,
},
components: {
userAvatarImage,
},
data() {
return { iconCommit };
},
template: `
<div>
<div class="events-description">
@ -24,7 +26,8 @@ global.cycleAnalytics.StagePlanComponent = Vue.extend({
<ul class="stage-event-list">
<li v-for="commit in items" class="stage-event-item">
<div class="item-details item-conmmit-component">
<img class="avatar" :src="commit.author.avatarUrl">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="commit.author.avatarUrl"/>
<h5 class="item-title commit-title">
<a :href="commit.commitUrl">
{{ commit.title }}

View file

@ -1,6 +1,6 @@
/* eslint-disable no-param-reassign */
import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
@ -10,6 +10,9 @@ global.cycleAnalytics.StageProductionComponent = Vue.extend({
items: Array,
stage: Object,
},
components: {
userAvatarImage,
},
template: `
<div>
<div class="events-description">
@ -19,7 +22,8 @@ global.cycleAnalytics.StageProductionComponent = Vue.extend({
<ul class="stage-event-list">
<li v-for="issue in items" class="stage-event-item">
<div class="item-details">
<img class="avatar" :src="issue.author.avatarUrl">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="issue.author.avatarUrl"/>
<h5 class="item-title issue-title">
<a class="issue-title" :href="issue.url">
{{ issue.title }}

View file

@ -1,6 +1,6 @@
/* eslint-disable no-param-reassign */
import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
@ -10,6 +10,9 @@ global.cycleAnalytics.StageReviewComponent = Vue.extend({
items: Array,
stage: Object,
},
components: {
userAvatarImage,
},
template: `
<div>
<div class="events-description">
@ -19,7 +22,8 @@ global.cycleAnalytics.StageReviewComponent = Vue.extend({
<ul class="stage-event-list">
<li v-for="mergeRequest in items" class="stage-event-item">
<div class="item-details">
<img class="avatar" :src="mergeRequest.author.avatarUrl">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
<h5 class="item-title merge-merquest-title">
<a :href="mergeRequest.url">
{{ mergeRequest.title }}

View file

@ -1,5 +1,6 @@
/* eslint-disable no-param-reassign */
import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconBranch from '../svg/icon_branch.svg';
const global = window.gl || (window.gl = {});
@ -13,6 +14,9 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({
data() {
return { iconBranch };
},
components: {
userAvatarImage,
},
template: `
<div>
<div class="events-description">
@ -22,13 +26,14 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({
<ul class="stage-event-list">
<li v-for="build in items" class="stage-event-item item-build-component">
<div class="item-details">
<img class="avatar" :src="build.author.avatarUrl">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="build.author.avatarUrl"/>
<h5 class="item-title">
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<i class="fa fa-code-fork"></i>
<a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
<a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch">${iconBranch}</span>
<a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
</h5>
<span>
<a :href="build.url" class="build-date">{{ build.date }}</a>

View file

@ -29,9 +29,9 @@ global.cycleAnalytics.StageTestComponent = Vue.extend({
&middot;
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<i class="fa fa-code-fork"></i>
<a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
<a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch">${iconBranch}</span>
<a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
</h5>
<span>
<a :href="build.url" class="issue-date">

View file

@ -4,18 +4,16 @@ import Vue from 'vue';
import Cookies from 'js-cookie';
import Translate from '../vue_shared/translate';
import LimitWarningComponent from './components/limit_warning_component';
require('./components/stage_code_component');
require('./components/stage_issue_component');
require('./components/stage_plan_component');
require('./components/stage_production_component');
require('./components/stage_review_component');
require('./components/stage_staging_component');
require('./components/stage_test_component');
require('./components/total_time_component');
require('./cycle_analytics_service');
require('./cycle_analytics_store');
require('./default_event_objects');
import './components/stage_code_component';
import './components/stage_issue_component';
import './components/stage_plan_component';
import './components/stage_production_component';
import './components/stage_review_component';
import './components/stage_staging_component';
import './components/stage_test_component';
import './components/total_time_component';
import './cycle_analytics_service';
import './cycle_analytics_store';
Vue.use(Translate);
@ -94,7 +92,7 @@ $(() => {
});
},
selectDefaultStage() {
const stage = this.state.stages.first();
const stage = this.state.stages[0];
this.selectStage(stage);
},
selectStage(stage) {

View file

@ -1,8 +1,8 @@
/* eslint-disable no-param-reassign */
import { __ } from '../locale';
require('../lib/utils/text_utility');
const DEFAULT_EVENT_OBJECTS = require('./default_event_objects');
import { __ } from '../locale';
import '../lib/utils/text_utility';
import DEFAULT_EVENT_OBJECTS from './default_event_objects';
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};

View file

@ -1,4 +1,4 @@
module.exports = {
export default {
issue: {
created_at: '',
url: '',

View file

@ -1,5 +1,6 @@
<script>
import eventHub from '../eventhub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
data() {
@ -22,6 +23,11 @@
default: 'btn-default',
},
},
components: {
loadingIcon,
},
methods: {
doAction() {
this.isLoading = true;
@ -44,11 +50,6 @@
:disabled="isLoading"
@click="doAction">
{{ text }}
<i
v-if="isLoading"
class="fa fa-spinner fa-spin"
aria-hidden="true"
aria-label="Loading">
</i>
<loading-icon v-if="isLoading" />
</button>
</template>

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