diff --git a/.codeclimate.yml b/.codeclimate.yml
index 216ecf43beb..8699a903f2a 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -10,12 +10,6 @@ engines:
- javascript
exclude_paths:
- "lib/api/v3/*"
- eslint:
- enabled: true
- channel: "eslint-4"
- rubocop:
- enabled: true
- channel: "gitlab-rubocop-0-52-1"
ratings:
paths:
- Gemfile.lock
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 70f41e4dc98..4659722854e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.16-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6"
+image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.7-golang-1.9-git-2.17-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6"
.dedicated-runner: &dedicated-runner
retry: 1
@@ -6,7 +6,7 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git
- gitlab-org
.default-cache: &default-cache
- key: "ruby-2.3.6-with-yarn"
+ key: "ruby-2.3.7-with-yarn"
paths:
- vendor/ruby
- .yarn-cache/
@@ -78,6 +78,19 @@ stages:
- mysql:latest
- redis:alpine
+.rails5-variables: &rails5-variables
+ script:
+ - export RAILS5=${RAILS5}
+ - export BUNDLE_GEMFILE=${BUNDLE_GEMFILE}
+
+.rails5: &rails5
+ allow_failure: true
+ only:
+ - /rails5/
+ variables:
+ BUNDLE_GEMFILE: "Gemfile.rails5"
+ RAILS5: "true"
+
# 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
@@ -118,6 +131,7 @@ stages:
<<: *dedicated-runner
<<: *except-docs-and-qa
<<: *pull-cache
+ <<: *rails5-variables
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
@@ -148,14 +162,23 @@ stages:
<<: *rspec-metadata
<<: *use-pg
+.rspec-metadata-pg-rails5: &rspec-metadata-pg-rails5
+ <<: *rspec-metadata-pg
+ <<: *rails5
+
.rspec-metadata-mysql: &rspec-metadata-mysql
<<: *rspec-metadata
<<: *use-mysql
+.rspec-metadata-mysql-rails5: &rspec-metadata-mysql-rails5
+ <<: *rspec-metadata-mysql
+ <<: *rails5
+
.spinach-metadata: &spinach-metadata
<<: *dedicated-runner
<<: *except-docs-and-qa
<<: *pull-cache
+ <<: *rails5-variables
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
@@ -179,10 +202,18 @@ stages:
<<: *spinach-metadata
<<: *use-pg
+.spinach-metadata-pg-rails5: &spinach-metadata-pg-rails5
+ <<: *spinach-metadata-pg
+ <<: *rails5
+
.spinach-metadata-mysql: &spinach-metadata-mysql
<<: *spinach-metadata
<<: *use-mysql
+.spinach-metadata-mysql-rails5: &spinach-metadata-mysql-rails5
+ <<: *spinach-metadata-mysql
+ <<: *rails5
+
.only-canonical-masters: &only-canonical-masters
only:
- master@gitlab-org/gitlab-ce
@@ -264,8 +295,18 @@ package-and-qa:
stage: build
cache: {}
when: manual
+ variables:
+ GIT_STRATEGY: none
+ retry: 0
+ before_script:
+ # We need to download the script rather than clone the repo since the
+ # package-and-qa job will not be able to run when the branch gets
+ # deleted (when merging the MR).
+ - apk add --update openssl
+ - wget https://gitlab.com/$CI_PROJECT_PATH/raw/$CI_COMMIT_SHA/scripts/trigger-build-omnibus
+ - chmod 755 trigger-build-omnibus
script:
- - scripts/trigger-build-omnibus
+ - ./trigger-build-omnibus
only:
- //@gitlab-org/gitlab-ce
- //@gitlab-org/gitlab-ee
@@ -458,6 +499,70 @@ spinach-pg 1 2: *spinach-metadata-pg
spinach-mysql 0 2: *spinach-metadata-mysql
spinach-mysql 1 2: *spinach-metadata-mysql
+rspec-pg-rails5 0 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 1 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 2 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 3 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 4 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 5 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 6 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 7 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 8 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 9 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 10 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 11 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 12 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 13 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 14 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 15 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 16 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 17 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 18 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 19 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 20 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 21 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 22 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 23 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 24 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 25 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 26 28: *rspec-metadata-pg-rails5
+rspec-pg-rails5 27 28: *rspec-metadata-pg-rails5
+
+rspec-mysql-rails5 0 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 1 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 2 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 3 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 4 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 5 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 6 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 7 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 8 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 9 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 10 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 11 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 12 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 13 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 14 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 15 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 16 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 17 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 18 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 19 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 20 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 21 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 22 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 23 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 24 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 25 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 26 28: *rspec-metadata-mysql-rails5
+rspec-mysql-rails5 27 28: *rspec-metadata-mysql-rails5
+
+spinach-pg-rails5 0 2: *spinach-metadata-pg-rails5
+spinach-pg-rails5 1 2: *spinach-metadata-pg-rails5
+
+spinach-mysql-rails5 0 2: *spinach-metadata-mysql-rails5
+spinach-mysql-rails5 1 2: *spinach-metadata-mysql-rails5
+
static-analysis:
<<: *dedicated-no-docs-no-db-pull-cache-job
dependencies:
@@ -466,7 +571,7 @@ static-analysis:
script:
- scripts/static-analysis
cache:
- key: "ruby-2.3.6-with-yarn-and-rubocop"
+ key: "ruby-2.3.7-with-yarn-and-rubocop"
paths:
- vendor/ruby
- .yarn-cache/
@@ -608,21 +713,23 @@ karma:
codequality:
<<: *dedicated-no-docs-no-db-pull-cache-job
- image: docker:latest
+ image: docker:stable
+ allow_failure: true
+ # gitlab-org runners set `privileged: false` but we need to have it set to true
+ # since we're using Docker in Docker
+ tags: []
before_script: []
services:
- docker:dind
variables:
SETUP_DB: "false"
DOCKER_DRIVER: overlay2
- CODECLIMATE_FORMAT: json
cache: {}
dependencies: []
script:
- - apk update && apk add jq
- - ./scripts/codequality analyze -f json > raw_codeclimate.json || true
- # The following line keeps only the fields used in the MR widget, reducing the JSON artifact size
- - jq -c 'map({check_name,description,fingerprint,location})' raw_codeclimate.json > codeclimate.json
+ # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
+ - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
+ - docker run --env SOURCE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
artifacts:
paths: [codeclimate.json]
expire_in: 1 week
@@ -655,7 +762,13 @@ qa:selectors:
- bundle exec bin/qa Test::Sanity::Selectors
coverage:
- <<: *dedicated-no-docs-no-db-pull-cache-job
+ # Don't include dedicated-no-docs-no-db-pull-cache-job here since we need to
+ # download artifacts from all the rspec jobs instead of from setup-test-env only
+ <<: *dedicated-runner
+ <<: *except-docs-and-qa
+ <<: *pull-cache
+ variables:
+ SETUP_DB: "false"
stage: post-test
script:
- bundle exec scripts/merge-simplecov
diff --git a/.gitlab/merge_request_templates/Database Changes.md b/.gitlab/merge_request_templates/Database Changes.md
index 8302b3b30c7..68bc0fd1c7f 100644
--- a/.gitlab/merge_request_templates/Database Changes.md
+++ b/.gitlab/merge_request_templates/Database Changes.md
@@ -33,13 +33,16 @@ When removing columns, tables, indexes or other structures:
## General Checklist
-- [ ] [Changelog entry](https://docs.gitlab.com/ce/development/changelog.html) added, if necessary
-- [ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)
+- [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary
+- [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/doc_styleguide.html)
- [ ] API support added
- [ ] Tests added for this feature/bug
- Review
- [ ] Has been reviewed by Backend
- [ ] Has been reviewed by Database
-- [ ] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
-- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
+- [ ] Conform by the [merge request performance guides](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)
+- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides)
- [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
+- [ ] Internationalization required/considered
+- [ ] If paid feature, have we considered GitLab.com plan and how it works for groups and is there a design for promoting it to users who aren't on the correct plan
+- [ ] End-to-end tests pass (`package-qa` manual pipeline job)
diff --git a/.ruby-version b/.ruby-version
index e75da3e63d6..00355e29d11 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.3.6
+2.3.7
diff --git a/.scss-lint.yml b/.scss-lint.yml
index dcd4cac780a..180d377d6f8 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -59,6 +59,8 @@ linters:
# Reports when you define the same property twice in a single rule set.
DuplicateProperty:
enabled: true
+ ignore_consecutive:
+ - cursor
# Separate rule, function, and mixin declarations with empty lines.
EmptyLineBetweenBlocks:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8a90a7fcdc2..d56c86523f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,32 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 10.6.4 (2018-04-09)
+
+### Fixed (8 changes, 1 of them is from the community)
+
+- Correct copy text for the promote milestone and label modals. !17726
+- Avoid validation errors when running the Pages domain verification service. !17992
+- Fix autolinking URLs containing ampersands. !18045
+- Fix exceptions raised when migrating pipeline stages in the background. !18076
+- Work around Prometheus Helm chart name changes to fix integration. !18206 (joshlambert)
+- Don't show Jump to Discussion button on Issues.
+- Fix listing commit branch/tags that contain special characters.
+- Fix 404 in group boards when moving issue between lists.
+
+### Performance (1 change)
+
+- Free open file descriptors and libgit2 buffers in UpdatePagesService.
+
+
+## 10.6.3 (2018-04-03)
+
+### Security (2 changes)
+
+- Fix XSS on diff view stored on filenames.
+- Adds confidential notes channel for Slack/Mattermost.
+
+
## 10.6.2 (2018-03-29)
### Fixed (2 changes, 1 of them is from the community)
@@ -191,7 +217,6 @@ entry.
- Enable privileged mode for GitLab Runner. !17528
- Expose GITLAB_FEATURES as CI/CD variable (fixes #40994).
- Upgrade GitLab Workhorse to 4.0.0.
-- Allow CI/CD Jobs being grouped on version strings.
- Add discussions API for Issues and Snippets.
- Add one group board to Libre.
- Add support for filtering by source and target branch to merge requests API.
@@ -218,6 +243,14 @@ entry.
- Use host URL to build JIRA remote link icon.
+## 10.5.7 (2018-04-03)
+
+### Security (2 changes)
+
+- Fix XSS on diff view stored on filenames.
+- Adds confidential notes channel for Slack/Mattermost.
+
+
## 10.5.6 (2018-03-16)
### Security (2 changes)
@@ -485,6 +518,14 @@ entry.
- Adds empty state illustration for pending job.
+## 10.4.7 (2018-04-03)
+
+### Security (2 changes)
+
+- Fix XSS on diff view stored on filenames.
+- Adds confidential notes channel for Slack/Mattermost.
+
+
## 10.4.6 (2018-03-16)
### Security (2 changes)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 36545ad338e..5f8cbfdb7d7 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.92.0
+0.95.0
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 39e898a4f95..a3df0a6959e 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-0.7.1
+0.8.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 21c8c7b46b8..a8a18875682 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-7.1.1
+7.1.2
diff --git a/Gemfile b/Gemfile
index a1e43700260..647138cd90f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,7 +6,6 @@ end
gem_versions = {}
gem_versions['activerecord_sane_schema_dumper'] = rails5? ? '1.0' : '0.2'
gem_versions['default_value_for'] = rails5? ? '~> 3.0.5' : '~> 3.0.0'
-gem_versions['html-pipeline'] = rails5? ? '~> 2.6.0' : '~> 1.11.0'
gem_versions['rails'] = rails5? ? '5.0.6' : '4.2.10'
gem_versions['rails-i18n'] = rails5? ? '~> 5.1' : '~> 4.0.9'
# --- The end of special code for migrating to Rails 5.0 ---
@@ -136,7 +135,7 @@ gem 'unf', '~> 0.1.4'
gem 'seed-fu', '~> 2.3.7'
# Markdown and HTML processing
-gem 'html-pipeline', gem_versions['html-pipeline']
+gem 'html-pipeline', '~> 2.7.1'
gem 'deckar01-task_list', '2.0.0'
gem 'gitlab-markup', '~> 1.6.2'
gem 'redcarpet', '~> 3.4'
@@ -310,7 +309,7 @@ end
group :development do
gem 'foreman', '~> 0.84.0'
- gem 'brakeman', '~> 3.6.0', require: false
+ gem 'brakeman', '~> 4.2', require: false
gem 'letter_opener_web', '~> 1.3.0'
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
@@ -385,7 +384,8 @@ group :test do
gem 'email_spec', '~> 1.6.0'
gem 'json-schema', '~> 2.8.0'
gem 'webmock', '~> 2.3.2'
- gem 'test_after_commit', '~> 1.1'
+ gem 'rails-controller-testing' if rails5? # Rails5 only gem.
+ gem 'test_after_commit', '~> 1.1' unless rails5? # Remove this gem when migrated to rails 5.0. It's been integrated to rails 5.0.
gem 'sham_rack', '~> 1.3.6'
gem 'concurrent-ruby', '~> 1.0.5'
gem 'test-prof', '~> 0.2.5'
@@ -422,7 +422,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.91.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.94.0', require: 'gitaly'
gem 'grpc', '~> 1.10.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed
@@ -441,3 +441,5 @@ gem 'grape_logging', '~> 1.7'
# Asset synchronization
gem 'asset_sync', '~> 2.2.0'
+
+gem 'goldiloader', '~> 2.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index bb510f6b539..76e1a17155f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -95,7 +95,7 @@ GEM
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
bootstrap_form (2.7.0)
- brakeman (3.6.1)
+ brakeman (4.2.1)
browser (2.2.0)
builder (3.2.3)
bullet (5.5.1)
@@ -120,7 +120,7 @@ GEM
activesupport (>= 4.0.0)
mime-types (>= 1.16)
cause (0.1)
- charlock_holmes (0.7.5)
+ charlock_holmes (0.7.6)
childprocess (0.7.0)
ffi (~> 1.0, >= 1.0.11)
chronic (0.10.2)
@@ -290,7 +290,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly-proto (0.91.0)
+ gitaly-proto (0.94.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (5.3.3)
@@ -320,6 +320,9 @@ GEM
rubyntlm (~> 0.5)
globalid (0.4.1)
activesupport (>= 4.2.0)
+ goldiloader (2.0.1)
+ activerecord (>= 4.2, < 5.2)
+ activesupport (>= 4.2, < 5.2)
gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1)
gollum-lib (4.2.7)
@@ -399,9 +402,9 @@ GEM
hipchat (1.5.2)
httparty
mimemagic
- html-pipeline (1.11.0)
+ html-pipeline (2.7.1)
activesupport (>= 2)
- nokogiri (~> 1.4)
+ nokogiri (>= 1.4)
html2text (0.2.0)
nokogiri (~> 1.6)
htmlentities (4.3.4)
@@ -587,7 +590,7 @@ GEM
orm_adapter (0.5.0)
os (0.9.6)
parallel (1.12.1)
- parser (2.5.0.3)
+ parser (2.5.0.5)
ast (~> 2.4.0)
parslet (1.5.0)
blankslate (~> 2.0)
@@ -1012,7 +1015,7 @@ DEPENDENCIES
binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0)
bootstrap_form (~> 2.7.0)
- brakeman (~> 3.6.0)
+ brakeman (~> 4.2)
browser (~> 2.2)
bullet (~> 5.5.0)
bundler-audit (~> 0.5.0)
@@ -1061,12 +1064,13 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 0.91.0)
+ gitaly-proto (~> 0.94.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
gitlab-styles (~> 2.3)
gitlab_omniauth-ldap (~> 2.0.4)
+ goldiloader (~> 2.0)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0)
@@ -1083,7 +1087,7 @@ DEPENDENCIES
hashie-forbidden_attributes
health_check (~> 2.6.0)
hipchat (~> 1.5.0)
- html-pipeline (~> 1.11.0)
+ html-pipeline (~> 2.7.1)
html2text
httparty (~> 0.13.3)
influxdb (~> 0.2)
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
index 86a22dbe550..03fe5f2ed26 100644
--- a/Gemfile.rails5.lock
+++ b/Gemfile.rails5.lock
@@ -60,7 +60,7 @@ GEM
faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.0)
asciidoctor (1.5.6.1)
- asciidoctor-plantuml (0.0.7)
+ asciidoctor-plantuml (0.0.8)
asciidoctor (~> 1.5)
asset_sync (2.2.0)
activemodel (>= 4.1.0)
@@ -97,7 +97,7 @@ GEM
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
bootstrap_form (2.7.0)
- brakeman (3.6.2)
+ brakeman (4.2.1)
browser (2.5.3)
builder (3.2.3)
bullet (5.5.1)
@@ -144,6 +144,7 @@ GEM
connection_pool (2.2.1)
crack (0.4.3)
safe_yaml (~> 1.0.0)
+ crass (1.0.3)
creole (0.5.0)
css_parser (1.6.0)
addressable
@@ -244,10 +245,11 @@ GEM
builder
excon (~> 0.58)
formatador (~> 0.2)
- fog-google (0.6.0)
+ fog-google (1.3.3)
fog-core
fog-json
fog-xml
+ google-api-client (~> 0.19.1)
fog-json (1.0.2)
fog-core (~> 1.0)
multi_json (~> 1.10)
@@ -289,7 +291,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly-proto (0.88.0)
+ gitaly-proto (0.94.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (5.3.3)
@@ -398,7 +400,7 @@ GEM
hipchat (1.5.4)
httparty
mimemagic
- html-pipeline (2.6.0)
+ html-pipeline (2.7.1)
activesupport (>= 2)
nokogiri (>= 1.4)
html2text (0.2.1)
@@ -484,7 +486,8 @@ GEM
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
- loofah (2.0.3)
+ loofah (2.2.2)
+ crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.0)
mini_mime (>= 0.1.1)
@@ -527,8 +530,8 @@ GEM
omniauth (1.8.1)
hashie (>= 3.4.6, < 3.6.0)
rack (>= 1.6.2, < 3)
- omniauth-auth0 (1.4.2)
- omniauth-oauth2 (~> 1.1)
+ omniauth-auth0 (2.0.0)
+ omniauth-oauth2 (~> 1.4)
omniauth-authentiq (0.3.1)
omniauth-oauth2 (~> 1.3, >= 1.3.1)
omniauth-azure-oauth2 (0.0.9)
@@ -551,6 +554,9 @@ GEM
jwt (>= 1.5)
omniauth (>= 1.1.1)
omniauth-oauth2 (>= 1.5)
+ omniauth-jwt (0.0.2)
+ jwt
+ omniauth (~> 1.1)
omniauth-kerberos (0.3.0)
omniauth-multipassword
timfel-krb5-auth (~> 0.8)
@@ -569,9 +575,9 @@ GEM
ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0)
- omniauth-twitter (1.2.1)
- json (~> 1.3)
+ omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
+ rack
omniauth_crowd (2.2.3)
activesupport
nokogiri (>= 1.4.4)
@@ -581,7 +587,7 @@ GEM
orm_adapter (0.5.0)
os (0.9.6)
parallel (1.12.1)
- parser (2.5.0.4)
+ parser (2.5.0.5)
ast (~> 2.4.0)
parslet (1.5.0)
blankslate (~> 2.0)
@@ -672,6 +678,10 @@ GEM
bundler (>= 1.3.0)
railties (= 5.0.6)
sprockets-rails (>= 2.0.0)
+ rails-controller-testing (1.0.2)
+ actionpack (~> 5.x, >= 5.0.1)
+ actionview (~> 5.x, >= 5.0.1)
+ activesupport (~> 5.x)
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
rails-dom-testing (2.0.3)
@@ -808,7 +818,7 @@ GEM
rubyzip (1.2.1)
rufus-scheduler (3.4.2)
et-orbi (~> 1.0)
- rugged (0.26.0)
+ rugged (0.27.0)
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
@@ -907,8 +917,6 @@ GEM
sysexits (1.2.0)
temple (0.7.7)
test-prof (0.2.5)
- test_after_commit (1.1.0)
- activerecord (>= 3.2)
text (1.3.1)
thin (1.7.2)
daemons (~> 1.0, >= 1.0.9)
@@ -995,8 +1003,8 @@ DEPENDENCIES
akismet (~> 2.0)
allocations (~> 1.0)
asana (~> 0.6.0)
- asciidoctor (~> 1.5.2)
- asciidoctor-plantuml (= 0.0.7)
+ asciidoctor (~> 1.5.6)
+ asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.2.0)
attr_encrypted (~> 3.0.0)
awesome_print (~> 1.2.0)
@@ -1009,7 +1017,7 @@ DEPENDENCIES
binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0)
bootstrap_form (~> 2.7.0)
- brakeman (~> 3.6.0)
+ brakeman (~> 4.2)
browser (~> 2.2)
bullet (~> 5.5.0)
bundler-audit (~> 0.5.0)
@@ -1044,9 +1052,9 @@ DEPENDENCIES
flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0)
fog-aliyun (~> 0.2.0)
- fog-aws (~> 2.0)
+ fog-aws (~> 2.0.1)
fog-core (~> 1.44)
- fog-google (~> 0.5)
+ fog-google (~> 1.3.3)
fog-local (~> 0.3)
fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1)
@@ -1058,7 +1066,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 0.88.0)
+ gitaly-proto (~> 0.94.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
@@ -1080,7 +1088,7 @@ DEPENDENCIES
hashie-forbidden_attributes
health_check (~> 2.6.0)
hipchat (~> 1.5.0)
- html-pipeline (~> 2.6.0)
+ html-pipeline (~> 2.7.1)
html2text
httparty (~> 0.13.3)
influxdb (~> 0.2)
@@ -1095,7 +1103,7 @@ DEPENDENCIES
license_finder (~> 3.1)
licensee (~> 8.9)
lograge (~> 0.5)
- loofah (~> 2.0.3)
+ loofah (~> 2.2)
mail_room (~> 0.9.1)
method_source (~> 0.8)
minitest (~> 5.7.0)
@@ -1107,19 +1115,20 @@ DEPENDENCIES
oauth2 (~> 1.4)
octokit (~> 4.8)
omniauth (~> 1.8)
- omniauth-auth0 (~> 1.4.1)
+ omniauth-auth0 (~> 2.0.0)
omniauth-authentiq (~> 0.3.1)
omniauth-azure-oauth2 (~> 0.0.9)
omniauth-cas3 (~> 1.1.4)
omniauth-facebook (~> 4.0.0)
omniauth-github (~> 1.1.1)
omniauth-gitlab (~> 1.0.2)
- omniauth-google-oauth2 (~> 0.5.2)
+ omniauth-google-oauth2 (~> 0.5.3)
+ omniauth-jwt (~> 0.0.2)
omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.2.0)
- omniauth-twitter (~> 1.2.0)
+ omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12)
peek (~> 1.0.1)
@@ -1140,6 +1149,7 @@ DEPENDENCIES
rack-oauth2 (~> 1.2.1)
rack-proxy (~> 0.6.0)
rails (= 5.0.6)
+ rails-controller-testing
rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 5.1)
rainbow (~> 2.2)
@@ -1169,7 +1179,7 @@ DEPENDENCIES
ruby-prof (~> 0.17.0)
ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4)
- rugged (~> 0.26.0)
+ rugged (~> 0.27)
sanitize (~> 2.0)
sass-rails (~> 5.0.6)
scss_lint (~> 0.56.0)
@@ -1197,7 +1207,6 @@ DEPENDENCIES
state_machines-activerecord (~> 0.5.1)
sys-filesystem (~> 1.1.6)
test-prof (~> 0.2.5)
- test_after_commit (~> 1.1)
thin (~> 1.7.0)
timecop (~> 0.8.0)
toml-rb (~> 1.0.0)
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index cbcefb2c18f..8ad3d18b302 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -10,6 +10,9 @@ const Api = {
projectsPath: '/api/:version/projects.json',
projectPath: '/api/:version/projects/:id',
projectLabelsPath: '/:namespace_path/:project_path/labels',
+ mergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
+ mergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
+ mergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
groupLabelsPath: '/groups/:namespace_path/-/labels',
licensePath: '/api/:version/templates/licenses/:key',
gitignorePath: '/api/:version/templates/gitignores/:key',
@@ -22,25 +25,27 @@ const Api = {
createBranchPath: '/api/:version/projects/:id/repository/branches',
group(groupId, callback) {
- const url = Api.buildUrl(Api.groupPath)
- .replace(':id', groupId);
- return axios.get(url)
- .then(({ data }) => {
- callback(data);
+ const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
+ return axios.get(url).then(({ data }) => {
+ callback(data);
- return data;
- });
+ return data;
+ });
},
// Return groups list. Filtered by query
groups(query, options, callback = $.noop) {
const url = Api.buildUrl(Api.groupsPath);
- return axios.get(url, {
- params: Object.assign({
- search: query,
- per_page: 20,
- }, options),
- })
+ return axios
+ .get(url, {
+ params: Object.assign(
+ {
+ search: query,
+ per_page: 20,
+ },
+ options,
+ ),
+ })
.then(({ data }) => {
callback(data);
@@ -51,12 +56,13 @@ const Api = {
// Return namespaces list. Filtered by query
namespaces(query, callback) {
const url = Api.buildUrl(Api.namespacesPath);
- return axios.get(url, {
- params: {
- search: query,
- per_page: 20,
- },
- })
+ return axios
+ .get(url, {
+ params: {
+ search: query,
+ per_page: 20,
+ },
+ })
.then(({ data }) => callback(data));
},
@@ -73,9 +79,10 @@ const Api = {
defaults.membership = true;
}
- return axios.get(url, {
- params: Object.assign(defaults, options),
- })
+ return axios
+ .get(url, {
+ params: Object.assign(defaults, options),
+ })
.then(({ data }) => {
callback(data);
@@ -85,8 +92,32 @@ const Api = {
// Return single project
project(projectPath) {
- const url = Api.buildUrl(Api.projectPath)
- .replace(':id', encodeURIComponent(projectPath));
+ const url = Api.buildUrl(Api.projectPath).replace(':id', encodeURIComponent(projectPath));
+
+ return axios.get(url);
+ },
+
+ // Return Merge Request for project
+ mergeRequest(projectPath, mergeRequestId) {
+ const url = Api.buildUrl(Api.mergeRequestPath)
+ .replace(':id', encodeURIComponent(projectPath))
+ .replace(':mrid', mergeRequestId);
+
+ return axios.get(url);
+ },
+
+ mergeRequestChanges(projectPath, mergeRequestId) {
+ const url = Api.buildUrl(Api.mergeRequestChangesPath)
+ .replace(':id', encodeURIComponent(projectPath))
+ .replace(':mrid', mergeRequestId);
+
+ return axios.get(url);
+ },
+
+ mergeRequestVersions(projectPath, mergeRequestId) {
+ const url = Api.buildUrl(Api.mergeRequestVersionsPath)
+ .replace(':id', encodeURIComponent(projectPath))
+ .replace(':mrid', mergeRequestId);
return axios.get(url);
},
@@ -102,30 +133,30 @@ const Api = {
url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
}
- return axios.post(url, {
- label: data,
- })
+ return axios
+ .post(url, {
+ label: data,
+ })
.then(res => callback(res.data))
.catch(e => callback(e.response.data));
},
// Return group projects list. Filtered by query
groupProjects(groupId, query, callback) {
- const url = Api.buildUrl(Api.groupProjectsPath)
- .replace(':id', groupId);
- return axios.get(url, {
- params: {
- search: query,
- per_page: 20,
- },
- })
+ const url = Api.buildUrl(Api.groupProjectsPath).replace(':id', groupId);
+ return axios
+ .get(url, {
+ params: {
+ search: query,
+ per_page: 20,
+ },
+ })
.then(({ data }) => callback(data));
},
commitMultiple(id, data) {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
- const url = Api.buildUrl(Api.commitPath)
- .replace(':id', encodeURIComponent(id));
+ const url = Api.buildUrl(Api.commitPath).replace(':id', encodeURIComponent(id));
return axios.post(url, JSON.stringify(data), {
headers: {
'Content-Type': 'application/json; charset=utf-8',
@@ -136,39 +167,34 @@ const Api = {
branchSingle(id, branch) {
const url = Api.buildUrl(Api.branchSinglePath)
.replace(':id', encodeURIComponent(id))
- .replace(':branch', branch);
+ .replace(':branch', encodeURIComponent(branch));
return axios.get(url);
},
// Return text for a specific license
licenseText(key, data, callback) {
- const url = Api.buildUrl(Api.licensePath)
- .replace(':key', key);
- return axios.get(url, {
- params: data,
- })
+ const url = Api.buildUrl(Api.licensePath).replace(':key', key);
+ return axios
+ .get(url, {
+ params: data,
+ })
.then(res => callback(res.data));
},
gitignoreText(key, callback) {
- const url = Api.buildUrl(Api.gitignorePath)
- .replace(':key', key);
- return axios.get(url)
- .then(({ data }) => callback(data));
+ const url = Api.buildUrl(Api.gitignorePath).replace(':key', key);
+ return axios.get(url).then(({ data }) => callback(data));
},
gitlabCiYml(key, callback) {
- const url = Api.buildUrl(Api.gitlabCiYmlPath)
- .replace(':key', key);
- return axios.get(url)
- .then(({ data }) => callback(data));
+ const url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key);
+ return axios.get(url).then(({ data }) => callback(data));
},
dockerfileYml(key, callback) {
const url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
- return axios.get(url)
- .then(({ data }) => callback(data));
+ return axios.get(url).then(({ data }) => callback(data));
},
issueTemplate(namespacePath, projectPath, key, type, callback) {
@@ -177,7 +203,8 @@ const Api = {
.replace(':type', type)
.replace(':project_path', projectPath)
.replace(':namespace_path', namespacePath);
- return axios.get(url)
+ return axios
+ .get(url)
.then(({ data }) => callback(null, data))
.catch(callback);
},
@@ -185,10 +212,13 @@ const Api = {
users(query, options) {
const url = Api.buildUrl(this.usersPath);
return axios.get(url, {
- params: Object.assign({
- search: query,
- per_page: 20,
- }, options),
+ params: Object.assign(
+ {
+ search: query,
+ per_page: 20,
+ },
+ options,
+ ),
});
},
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 6da33a26e58..976d32abe9b 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -4,7 +4,8 @@ import $ from 'jquery';
import _ from 'underscore';
import Cookies from 'js-cookie';
import { __ } from './locale';
-import { isInIssuePage, isInMRPage, hasVueMRDiscussionsCookie, updateTooltipTitle } from './lib/utils/common_utils';
+import { updateTooltipTitle } from './lib/utils/common_utils';
+import { isInVueNoteablePage } from './lib/utils/dom_utils';
import flash from './flash';
import axios from './lib/utils/axios_utils';
@@ -243,7 +244,7 @@ class AwardsHandler {
addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) {
const isMainAwardsBlock = votesBlock.closest('.js-noteable-awards').length;
- if (this.isInVueNoteablePage() && !isMainAwardsBlock) {
+ if (isInVueNoteablePage() && !isMainAwardsBlock) {
const id = votesBlock.attr('id').replace('note_', '');
this.hideMenuElement($('.emoji-menu'));
@@ -295,16 +296,8 @@ class AwardsHandler {
}
}
- isVueMRDiscussions() {
- return isInMRPage() && hasVueMRDiscussionsCookie() && !$('#diffs').is(':visible');
- }
-
- isInVueNoteablePage() {
- return isInIssuePage() || this.isVueMRDiscussions();
- }
-
getVotesBlock() {
- if (this.isInVueNoteablePage()) {
+ if (isInVueNoteablePage()) {
const $el = $('.js-add-award.is-active').closest('.note.timeline-entry');
if ($el.length) {
diff --git a/app/assets/javascripts/badges/components/badge.vue b/app/assets/javascripts/badges/components/badge.vue
new file mode 100644
index 00000000000..6e6cb31e3ac
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge.vue
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ s__('Badges|No badge image') }}
+
+
+
+
+
+
diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue
new file mode 100644
index 00000000000..ae942b2c1a7
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge_form.vue
@@ -0,0 +1,219 @@
+
+
+
+
+
diff --git a/app/assets/javascripts/badges/components/badge_list.vue b/app/assets/javascripts/badges/components/badge_list.vue
new file mode 100644
index 00000000000..ca7197e1e0f
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge_list.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+ {{ s__('Badges|Your badges') }}
+ {{ badges.length }}
+
+
+
+ {{ s__('Badges|This group has no badges') }}
+ {{ s__('Badges|This project has no badges') }}
+
+
+
+
+
+
diff --git a/app/assets/javascripts/badges/components/badge_list_row.vue b/app/assets/javascripts/badges/components/badge_list_row.vue
new file mode 100644
index 00000000000..af062bdf8c6
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge_list_row.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+
{{ badge.linkUrl }}
+
+ {{ badgeKindText }}
+
+
+
+
diff --git a/app/assets/javascripts/badges/components/badge_settings.vue b/app/assets/javascripts/badges/components/badge_settings.vue
new file mode 100644
index 00000000000..83f78394238
--- /dev/null
+++ b/app/assets/javascripts/badges/components/badge_settings.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
diff --git a/app/assets/javascripts/badges/constants.js b/app/assets/javascripts/badges/constants.js
new file mode 100644
index 00000000000..8fbe3db5ef1
--- /dev/null
+++ b/app/assets/javascripts/badges/constants.js
@@ -0,0 +1,2 @@
+export const GROUP_BADGE = 'group';
+export const PROJECT_BADGE = 'project';
diff --git a/app/assets/javascripts/badges/empty_badge.js b/app/assets/javascripts/badges/empty_badge.js
new file mode 100644
index 00000000000..49a9b5e1be8
--- /dev/null
+++ b/app/assets/javascripts/badges/empty_badge.js
@@ -0,0 +1,7 @@
+export default () => ({
+ imageUrl: '',
+ isDeleting: false,
+ linkUrl: '',
+ renderedImageUrl: '',
+ renderedLinkUrl: '',
+});
diff --git a/app/assets/javascripts/badges/store/actions.js b/app/assets/javascripts/badges/store/actions.js
new file mode 100644
index 00000000000..5542278b3e0
--- /dev/null
+++ b/app/assets/javascripts/badges/store/actions.js
@@ -0,0 +1,167 @@
+import axios from '~/lib/utils/axios_utils';
+import types from './mutation_types';
+
+export const transformBackendBadge = badge => ({
+ id: badge.id,
+ imageUrl: badge.image_url,
+ kind: badge.kind,
+ linkUrl: badge.link_url,
+ renderedImageUrl: badge.rendered_image_url,
+ renderedLinkUrl: badge.rendered_link_url,
+ isDeleting: false,
+});
+
+export default {
+ requestNewBadge({ commit }) {
+ commit(types.REQUEST_NEW_BADGE);
+ },
+ receiveNewBadge({ commit }, newBadge) {
+ commit(types.RECEIVE_NEW_BADGE, newBadge);
+ },
+ receiveNewBadgeError({ commit }) {
+ commit(types.RECEIVE_NEW_BADGE_ERROR);
+ },
+ addBadge({ dispatch, state }) {
+ const newBadge = state.badgeInAddForm;
+ const endpoint = state.apiEndpointUrl;
+ dispatch('requestNewBadge');
+ return axios
+ .post(endpoint, {
+ image_url: newBadge.imageUrl,
+ link_url: newBadge.linkUrl,
+ })
+ .catch(error => {
+ dispatch('receiveNewBadgeError');
+ throw error;
+ })
+ .then(res => {
+ dispatch('receiveNewBadge', transformBackendBadge(res.data));
+ });
+ },
+ requestDeleteBadge({ commit }, badgeId) {
+ commit(types.REQUEST_DELETE_BADGE, badgeId);
+ },
+ receiveDeleteBadge({ commit }, badgeId) {
+ commit(types.RECEIVE_DELETE_BADGE, badgeId);
+ },
+ receiveDeleteBadgeError({ commit }, badgeId) {
+ commit(types.RECEIVE_DELETE_BADGE_ERROR, badgeId);
+ },
+ deleteBadge({ dispatch, state }, badge) {
+ const badgeId = badge.id;
+ dispatch('requestDeleteBadge', badgeId);
+ const endpoint = `${state.apiEndpointUrl}/${badgeId}`;
+ return axios
+ .delete(endpoint)
+ .catch(error => {
+ dispatch('receiveDeleteBadgeError', badgeId);
+ throw error;
+ })
+ .then(() => {
+ dispatch('receiveDeleteBadge', badgeId);
+ });
+ },
+
+ editBadge({ commit }, badge) {
+ commit(types.START_EDITING, badge);
+ },
+
+ requestLoadBadges({ commit }, data) {
+ commit(types.REQUEST_LOAD_BADGES, data);
+ },
+ receiveLoadBadges({ commit }, badges) {
+ commit(types.RECEIVE_LOAD_BADGES, badges);
+ },
+ receiveLoadBadgesError({ commit }) {
+ commit(types.RECEIVE_LOAD_BADGES_ERROR);
+ },
+
+ loadBadges({ dispatch, state }, data) {
+ dispatch('requestLoadBadges', data);
+ const endpoint = state.apiEndpointUrl;
+ return axios
+ .get(endpoint)
+ .catch(error => {
+ dispatch('receiveLoadBadgesError');
+ throw error;
+ })
+ .then(res => {
+ dispatch('receiveLoadBadges', res.data.map(transformBackendBadge));
+ });
+ },
+
+ requestRenderedBadge({ commit }) {
+ commit(types.REQUEST_RENDERED_BADGE);
+ },
+ receiveRenderedBadge({ commit }, renderedBadge) {
+ commit(types.RECEIVE_RENDERED_BADGE, renderedBadge);
+ },
+ receiveRenderedBadgeError({ commit }) {
+ commit(types.RECEIVE_RENDERED_BADGE_ERROR);
+ },
+
+ renderBadge({ dispatch, state }) {
+ const badge = state.isEditing ? state.badgeInEditForm : state.badgeInAddForm;
+ const { linkUrl, imageUrl } = badge;
+ if (!linkUrl || linkUrl.trim() === '' || !imageUrl || imageUrl.trim() === '') {
+ return Promise.resolve(badge);
+ }
+
+ dispatch('requestRenderedBadge');
+
+ const parameters = [
+ `link_url=${encodeURIComponent(linkUrl)}`,
+ `image_url=${encodeURIComponent(imageUrl)}`,
+ ].join('&');
+ const renderEndpoint = `${state.apiEndpointUrl}/render?${parameters}`;
+ return axios
+ .get(renderEndpoint)
+ .catch(error => {
+ dispatch('receiveRenderedBadgeError');
+ throw error;
+ })
+ .then(res => {
+ dispatch('receiveRenderedBadge', transformBackendBadge(res.data));
+ });
+ },
+
+ requestUpdatedBadge({ commit }) {
+ commit(types.REQUEST_UPDATED_BADGE);
+ },
+ receiveUpdatedBadge({ commit }, updatedBadge) {
+ commit(types.RECEIVE_UPDATED_BADGE, updatedBadge);
+ },
+ receiveUpdatedBadgeError({ commit }) {
+ commit(types.RECEIVE_UPDATED_BADGE_ERROR);
+ },
+
+ saveBadge({ dispatch, state }) {
+ const badge = state.badgeInEditForm;
+ const endpoint = `${state.apiEndpointUrl}/${badge.id}`;
+ dispatch('requestUpdatedBadge');
+ return axios
+ .put(endpoint, {
+ image_url: badge.imageUrl,
+ link_url: badge.linkUrl,
+ })
+ .catch(error => {
+ dispatch('receiveUpdatedBadgeError');
+ throw error;
+ })
+ .then(res => {
+ dispatch('receiveUpdatedBadge', transformBackendBadge(res.data));
+ });
+ },
+
+ stopEditing({ commit }) {
+ commit(types.STOP_EDITING);
+ },
+
+ updateBadgeInForm({ commit }, badge) {
+ commit(types.UPDATE_BADGE_IN_FORM, badge);
+ },
+
+ updateBadgeInModal({ commit }, badge) {
+ commit(types.UPDATE_BADGE_IN_MODAL, badge);
+ },
+};
diff --git a/app/assets/javascripts/badges/store/index.js b/app/assets/javascripts/badges/store/index.js
new file mode 100644
index 00000000000..7a5df403a0e
--- /dev/null
+++ b/app/assets/javascripts/badges/store/index.js
@@ -0,0 +1,13 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import createState from './state';
+import actions from './actions';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+ state: createState(),
+ actions,
+ mutations,
+});
diff --git a/app/assets/javascripts/badges/store/mutation_types.js b/app/assets/javascripts/badges/store/mutation_types.js
new file mode 100644
index 00000000000..d73f91b6005
--- /dev/null
+++ b/app/assets/javascripts/badges/store/mutation_types.js
@@ -0,0 +1,21 @@
+export default {
+ RECEIVE_DELETE_BADGE: 'RECEIVE_DELETE_BADGE',
+ RECEIVE_DELETE_BADGE_ERROR: 'RECEIVE_DELETE_BADGE_ERROR',
+ RECEIVE_LOAD_BADGES: 'RECEIVE_LOAD_BADGES',
+ RECEIVE_LOAD_BADGES_ERROR: 'RECEIVE_LOAD_BADGES_ERROR',
+ RECEIVE_NEW_BADGE: 'RECEIVE_NEW_BADGE',
+ RECEIVE_NEW_BADGE_ERROR: 'RECEIVE_NEW_BADGE_ERROR',
+ RECEIVE_RENDERED_BADGE: 'RECEIVE_RENDERED_BADGE',
+ RECEIVE_RENDERED_BADGE_ERROR: 'RECEIVE_RENDERED_BADGE_ERROR',
+ RECEIVE_UPDATED_BADGE: 'RECEIVE_UPDATED_BADGE',
+ RECEIVE_UPDATED_BADGE_ERROR: 'RECEIVE_UPDATED_BADGE_ERROR',
+ REQUEST_DELETE_BADGE: 'REQUEST_DELETE_BADGE',
+ REQUEST_LOAD_BADGES: 'REQUEST_LOAD_BADGES',
+ REQUEST_NEW_BADGE: 'REQUEST_NEW_BADGE',
+ REQUEST_RENDERED_BADGE: 'REQUEST_RENDERED_BADGE',
+ REQUEST_UPDATED_BADGE: 'REQUEST_UPDATED_BADGE',
+ START_EDITING: 'START_EDITING',
+ STOP_EDITING: 'STOP_EDITING',
+ UPDATE_BADGE_IN_FORM: 'UPDATE_BADGE_IN_FORM',
+ UPDATE_BADGE_IN_MODAL: 'UPDATE_BADGE_IN_MODAL',
+};
diff --git a/app/assets/javascripts/badges/store/mutations.js b/app/assets/javascripts/badges/store/mutations.js
new file mode 100644
index 00000000000..bd84e68c00f
--- /dev/null
+++ b/app/assets/javascripts/badges/store/mutations.js
@@ -0,0 +1,158 @@
+import types from './mutation_types';
+import { PROJECT_BADGE } from '../constants';
+
+const reorderBadges = badges =>
+ badges.sort((a, b) => {
+ if (a.kind !== b.kind) {
+ return a.kind === PROJECT_BADGE ? 1 : -1;
+ }
+
+ return a.id - b.id;
+ });
+
+export default {
+ [types.RECEIVE_NEW_BADGE](state, newBadge) {
+ Object.assign(state, {
+ badgeInAddForm: null,
+ badges: reorderBadges(state.badges.concat(newBadge)),
+ isSaving: false,
+ renderedBadge: null,
+ });
+ },
+ [types.RECEIVE_NEW_BADGE_ERROR](state) {
+ Object.assign(state, {
+ isSaving: false,
+ });
+ },
+ [types.REQUEST_NEW_BADGE](state) {
+ Object.assign(state, {
+ isSaving: true,
+ });
+ },
+
+ [types.RECEIVE_UPDATED_BADGE](state, updatedBadge) {
+ const badges = state.badges.map(badge => {
+ if (badge.id === updatedBadge.id) {
+ return updatedBadge;
+ }
+ return badge;
+ });
+ Object.assign(state, {
+ badgeInEditForm: null,
+ badges,
+ isEditing: false,
+ isSaving: false,
+ renderedBadge: null,
+ });
+ },
+ [types.RECEIVE_UPDATED_BADGE_ERROR](state) {
+ Object.assign(state, {
+ isSaving: false,
+ });
+ },
+ [types.REQUEST_UPDATED_BADGE](state) {
+ Object.assign(state, {
+ isSaving: true,
+ });
+ },
+
+ [types.RECEIVE_LOAD_BADGES](state, badges) {
+ Object.assign(state, {
+ badges: reorderBadges(badges),
+ isLoading: false,
+ });
+ },
+ [types.RECEIVE_LOAD_BADGES_ERROR](state) {
+ Object.assign(state, {
+ isLoading: false,
+ });
+ },
+ [types.REQUEST_LOAD_BADGES](state, data) {
+ Object.assign(state, {
+ kind: data.kind, // project or group
+ apiEndpointUrl: data.apiEndpointUrl,
+ docsUrl: data.docsUrl,
+ isLoading: true,
+ });
+ },
+
+ [types.RECEIVE_DELETE_BADGE](state, badgeId) {
+ const badges = state.badges.filter(badge => badge.id !== badgeId);
+ Object.assign(state, {
+ badges,
+ });
+ },
+ [types.RECEIVE_DELETE_BADGE_ERROR](state, badgeId) {
+ const badges = state.badges.map(badge => {
+ if (badge.id === badgeId) {
+ return {
+ ...badge,
+ isDeleting: false,
+ };
+ }
+
+ return badge;
+ });
+ Object.assign(state, {
+ badges,
+ });
+ },
+ [types.REQUEST_DELETE_BADGE](state, badgeId) {
+ const badges = state.badges.map(badge => {
+ if (badge.id === badgeId) {
+ return {
+ ...badge,
+ isDeleting: true,
+ };
+ }
+
+ return badge;
+ });
+ Object.assign(state, {
+ badges,
+ });
+ },
+
+ [types.RECEIVE_RENDERED_BADGE](state, renderedBadge) {
+ Object.assign(state, { isRendering: false, renderedBadge });
+ },
+ [types.RECEIVE_RENDERED_BADGE_ERROR](state) {
+ Object.assign(state, { isRendering: false });
+ },
+ [types.REQUEST_RENDERED_BADGE](state) {
+ Object.assign(state, { isRendering: true });
+ },
+
+ [types.START_EDITING](state, badge) {
+ Object.assign(state, {
+ badgeInEditForm: { ...badge },
+ isEditing: true,
+ renderedBadge: { ...badge },
+ });
+ },
+ [types.STOP_EDITING](state) {
+ Object.assign(state, {
+ badgeInEditForm: null,
+ isEditing: false,
+ renderedBadge: null,
+ });
+ },
+
+ [types.UPDATE_BADGE_IN_FORM](state, badge) {
+ if (state.isEditing) {
+ Object.assign(state, {
+ badgeInEditForm: badge,
+ });
+ } else {
+ Object.assign(state, {
+ badgeInAddForm: badge,
+ });
+ }
+ },
+
+ [types.UPDATE_BADGE_IN_MODAL](state, badge) {
+ Object.assign(state, {
+ badgeInModal: badge,
+ });
+ },
+};
diff --git a/app/assets/javascripts/badges/store/state.js b/app/assets/javascripts/badges/store/state.js
new file mode 100644
index 00000000000..43413aeb5bb
--- /dev/null
+++ b/app/assets/javascripts/badges/store/state.js
@@ -0,0 +1,13 @@
+export default () => ({
+ apiEndpointUrl: null,
+ badgeInAddForm: null,
+ badgeInEditForm: null,
+ badgeInModal: null,
+ badges: [],
+ docsUrl: null,
+ renderedBadge: null,
+ isEditing: false,
+ isLoading: false,
+ isRendering: false,
+ isSaving: false,
+});
diff --git a/app/assets/javascripts/blob/file_template_mediator.js b/app/assets/javascripts/blob/file_template_mediator.js
index 030ca1907e5..ff1cbcad145 100644
--- a/app/assets/javascripts/blob/file_template_mediator.js
+++ b/app/assets/javascripts/blob/file_template_mediator.js
@@ -94,7 +94,7 @@ export default class FileTemplateMediator {
const hash = urlPieces[1];
if (hash === 'preview') {
this.hideTemplateSelectorMenu();
- } else if (hash === 'editor') {
+ } else if (hash === 'editor' && !this.typeSelector.isHidden()) {
this.showTemplateSelectorMenu();
}
});
diff --git a/app/assets/javascripts/blob/file_template_selector.js b/app/assets/javascripts/blob/file_template_selector.js
index e52cf249f3a..02228434a29 100644
--- a/app/assets/javascripts/blob/file_template_selector.js
+++ b/app/assets/javascripts/blob/file_template_selector.js
@@ -32,6 +32,10 @@ export default class FileTemplateSelector {
}
}
+ isHidden() {
+ return this.$wrapper.hasClass('hidden');
+ }
+
getToggleText() {
return this.$dropdownToggleText.text();
}
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
index 3cffd91716a..bea818010a4 100644
--- a/app/assets/javascripts/boards/components/board.js
+++ b/app/assets/javascripts/boards/components/board.js
@@ -5,7 +5,7 @@ import Sortable from 'vendor/Sortable';
import Vue from 'vue';
import AccessorUtilities from '../../lib/utils/accessor';
import boardList from './board_list.vue';
-import boardBlankState from './board_blank_state';
+import BoardBlankState from './board_blank_state.vue';
import './board_delete';
const Store = gl.issueBoards.BoardsStore;
@@ -18,7 +18,7 @@ gl.issueBoards.Board = Vue.extend({
components: {
boardList,
'board-delete': gl.issueBoards.BoardDelete,
- boardBlankState,
+ BoardBlankState,
},
props: {
list: Object,
diff --git a/app/assets/javascripts/boards/components/board_blank_state.js b/app/assets/javascripts/boards/components/board_blank_state.vue
similarity index 61%
rename from app/assets/javascripts/boards/components/board_blank_state.js
rename to app/assets/javascripts/boards/components/board_blank_state.vue
index 72db626d3c7..2049eeb9c30 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.js
+++ b/app/assets/javascripts/boards/components/board_blank_state.vue
@@ -1,42 +1,11 @@
+
+
+
+
+
+ Add the following default lists to your Issue Board with one click:
+
+
+ -
+
+
+ {{ label.title }}
+
+
+
+ Starting out with the default set of lists will get you
+ right on the way to making the most of your board.
+
+
+
+
+
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index a44969272a1..c4ee4f6c855 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -60,10 +60,6 @@ gl.issueBoards.BoardSidebar = Vue.extend({
this.issue = this.detail.issue;
this.list = this.detail.list;
-
- this.$nextTick(() => {
- this.endpoint = this.$refs.assigneeDropdown.dataset.issueUpdate;
- });
},
deep: true
},
@@ -91,7 +87,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({
saveAssignees () {
this.loadingAssignees = true;
- gl.issueBoards.BoardsStore.detail.issue.update(this.endpoint)
+ gl.issueBoards.BoardsStore.detail.issue.update()
.then(() => {
this.loadingAssignees = false;
})
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js
index 8aee5b23c76..84fe9b1288a 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.js
+++ b/app/assets/javascripts/boards/components/issue_card_inner.js
@@ -68,15 +68,6 @@ gl.issueBoards.IssueCardInner = Vue.extend({
return this.issue.assignees.length > this.numberOverLimit;
},
- cardUrl() {
- let baseUrl = this.issueLinkBase;
-
- if (this.groupId && this.issue.project) {
- baseUrl = this.issueLinkBase.replace(':project_path', this.issue.project.path);
- }
-
- return `${baseUrl}/${this.issue.iid}`;
- },
issueId() {
if (this.issue.iid) {
return `#${this.issue.iid}`;
@@ -153,13 +144,13 @@ gl.issueBoards.IssueCardInner = Vue.extend({
/>
{{ issue.title }}
- {{issue.project.path}}{{ issueId }}
+ {{ issue.referencePath }}
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.js b/app/assets/javascripts/boards/components/modal/empty_state.js
index e571b11a83d..9e37f95cdd6 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.js
+++ b/app/assets/javascripts/boards/components/modal/empty_state.js
@@ -1,9 +1,9 @@
import Vue from 'vue';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
+import modalMixin from '../../mixins/modal_mixins';
gl.issueBoards.ModalEmptyState = Vue.extend({
- mixins: [gl.issueBoards.ModalMixins],
+ mixins: [modalMixin],
data() {
return ModalStore.store;
},
diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.js
index 03cd7ef65cb..9735e0ddacc 100644
--- a/app/assets/javascripts/boards/components/modal/footer.js
+++ b/app/assets/javascripts/boards/components/modal/footer.js
@@ -3,11 +3,11 @@ import Flash from '../../../flash';
import { __ } from '../../../locale';
import './lists_dropdown';
import { pluralize } from '../../../lib/utils/text_utility';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
+import modalMixin from '../../mixins/modal_mixins';
gl.issueBoards.ModalFooter = Vue.extend({
- mixins: [gl.issueBoards.ModalMixins],
+ mixins: [modalMixin],
data() {
return {
modal: ModalStore.store,
diff --git a/app/assets/javascripts/boards/components/modal/header.js b/app/assets/javascripts/boards/components/modal/header.js
index 31f59d295bf..67c29ebca72 100644
--- a/app/assets/javascripts/boards/components/modal/header.js
+++ b/app/assets/javascripts/boards/components/modal/header.js
@@ -1,11 +1,11 @@
import Vue from 'vue';
import modalFilters from './filters';
import './tabs';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
+import modalMixin from '../../mixins/modal_mixins';
gl.issueBoards.ModalHeader = Vue.extend({
- mixins: [gl.issueBoards.ModalMixins],
+ mixins: [modalMixin],
props: {
projectId: {
type: Number,
diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js
index d825ff38587..3083b3e4405 100644
--- a/app/assets/javascripts/boards/components/modal/index.js
+++ b/app/assets/javascripts/boards/components/modal/index.js
@@ -7,8 +7,7 @@ import './header';
import './list';
import './footer';
import './empty_state';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
gl.issueBoards.IssuesModal = Vue.extend({
props: {
diff --git a/app/assets/javascripts/boards/components/modal/list.js b/app/assets/javascripts/boards/components/modal/list.js
index 7c62134b3a3..6b04a6c7a6c 100644
--- a/app/assets/javascripts/boards/components/modal/list.js
+++ b/app/assets/javascripts/boards/components/modal/list.js
@@ -2,8 +2,7 @@
import Vue from 'vue';
import bp from '../../../breakpoints';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
gl.issueBoards.ModalList = Vue.extend({
props: {
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.js b/app/assets/javascripts/boards/components/modal/lists_dropdown.js
index 4684ea76647..e644de2d4fc 100644
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.js
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.js
@@ -1,6 +1,5 @@
import Vue from 'vue';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
data() {
diff --git a/app/assets/javascripts/boards/components/modal/tabs.js b/app/assets/javascripts/boards/components/modal/tabs.js
index 3e5d08e3d75..b6465a88e5e 100644
--- a/app/assets/javascripts/boards/components/modal/tabs.js
+++ b/app/assets/javascripts/boards/components/modal/tabs.js
@@ -1,9 +1,9 @@
import Vue from 'vue';
-
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../../stores/modal_store';
+import modalMixin from '../../mixins/modal_mixins';
gl.issueBoards.ModalTabs = Vue.extend({
- mixins: [gl.issueBoards.ModalMixins],
+ mixins: [modalMixin],
data() {
return ModalStore.store;
},
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
index 09c683ff621..0a0820ec5fd 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
@@ -17,14 +17,10 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
type: Object,
required: true,
},
- issueUpdate: {
- type: String,
- required: true,
- },
},
computed: {
updateUrl() {
- return this.issueUpdate.replace(':project_path', this.issue.project.path);
+ return this.issue.path;
},
},
methods: {
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index fb40b9f5565..70367c4f711 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -6,6 +6,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
constructor(store, updateUrl = false, cantEdit = []) {
super({
page: 'boards',
+ isGroupDecendent: true,
stateFiltersSelector: '.issues-state-filters',
});
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 8b1c14c04ff..a6f8681cfac 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -17,9 +17,9 @@ import './models/milestone';
import './models/project';
import './models/assignee';
import './stores/boards_store';
-import './stores/modal_store';
+import ModalStore from './stores/modal_store';
import BoardService from './services/board_service';
-import './mixins/modal_mixins';
+import modalMixin from './mixins/modal_mixins';
import './mixins/sortable_default_options';
import './filters/due_date_filters';
import './components/board';
@@ -31,7 +31,6 @@ import '~/vue_shared/vue_resource_interceptor'; // eslint-disable-line import/fi
export default () => {
const $boardApp = document.getElementById('board-app');
const Store = gl.issueBoards.BoardsStore;
- const ModalStore = gl.issueBoards.ModalStore;
window.gl = window.gl || {};
@@ -176,7 +175,7 @@ export default () => {
gl.IssueBoardsModalAddBtn = new Vue({
el: document.getElementById('js-add-issues-btn'),
- mixins: [gl.issueBoards.ModalMixins],
+ mixins: [modalMixin],
data() {
return {
modal: ModalStore.store,
diff --git a/app/assets/javascripts/boards/mixins/modal_mixins.js b/app/assets/javascripts/boards/mixins/modal_mixins.js
index 2b0a1aaa89f..6c97e1629bf 100644
--- a/app/assets/javascripts/boards/mixins/modal_mixins.js
+++ b/app/assets/javascripts/boards/mixins/modal_mixins.js
@@ -1,6 +1,6 @@
-const ModalStore = gl.issueBoards.ModalStore;
+import ModalStore from '../stores/modal_store';
-gl.issueBoards.ModalMixins = {
+export default {
methods: {
toggleModal(toggle) {
ModalStore.store.showAddIssuesModal = toggle;
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 4c5079efc8b..b381d48d625 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -23,6 +23,8 @@ class ListIssue {
};
this.isLoading = {};
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
+ this.referencePath = obj.reference_path;
+ this.path = obj.real_path;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
this.milestone_id = obj.milestone_id;
this.project_id = obj.project_id;
@@ -98,7 +100,7 @@ class ListIssue {
this.isLoading[key] = value;
}
- update (url) {
+ update () {
const data = {
issue: {
milestone_id: this.milestone ? this.milestone.id : null,
@@ -113,7 +115,7 @@ class ListIssue {
}
const projectPath = this.project ? this.project.path : '';
- return Vue.http.patch(url.replace(':project_path', projectPath), data);
+ return Vue.http.patch(`${this.path}.json`, data);
}
}
diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js
index d78d4701974..7c90597f77c 100644
--- a/app/assets/javascripts/boards/services/board_service.js
+++ b/app/assets/javascripts/boards/services/board_service.js
@@ -19,7 +19,7 @@ export default class BoardService {
}
static generateIssuePath(boardId, id) {
- return `${gon.relative_url_root}/-/boards/${boardId ? `/${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
+ return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
}
all() {
diff --git a/app/assets/javascripts/boards/stores/modal_store.js b/app/assets/javascripts/boards/stores/modal_store.js
index 4fdc925c825..a4220cd840d 100644
--- a/app/assets/javascripts/boards/stores/modal_store.js
+++ b/app/assets/javascripts/boards/stores/modal_store.js
@@ -1,6 +1,3 @@
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
class ModalStore {
constructor() {
this.store = {
@@ -95,4 +92,4 @@ class ModalStore {
}
}
-gl.issueBoards.ModalStore = new ModalStore();
+export default new ModalStore();
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
index 466a5b5d635..24d63b99a29 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
@@ -55,22 +55,20 @@
},
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);
+ // depending of the endpoint the response can either bring a `pipelines` key or not.
+ const pipelines = resp.data.pipelines || resp.data;
+ 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);
- }
+ const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
+ detail: {
+ pipelines: resp.data,
+ },
});
+
+ // notifiy to update the count in tabs
+ if (this.$el.parentElement) {
+ this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
+ }
},
},
};
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index e6390f0855b..d7e1de18d09 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -26,8 +26,8 @@ export default class FilteredSearchDropdownManager {
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page;
this.groupsOnly = isGroup;
- this.groupAncestor = isGroupAncestor;
- this.isGroupDecendent = isGroupDecendent;
+ this.includeAncestorGroups = isGroupAncestor;
+ this.includeDescendantGroups = isGroupDecendent;
this.setupMapping();
@@ -108,7 +108,19 @@ export default class FilteredSearchDropdownManager {
}
getLabelsEndpoint() {
- const endpoint = `${this.baseEndpoint}/labels.json`;
+ let endpoint = `${this.baseEndpoint}/labels.json?`;
+
+ if (this.groupsOnly) {
+ endpoint = `${endpoint}only_group_labels=true&`;
+ }
+
+ if (this.includeAncestorGroups) {
+ endpoint = `${endpoint}include_ancestor_groups=true&`;
+ }
+
+ if (this.includeDescendantGroups) {
+ endpoint = `${endpoint}include_descendant_groups=true`;
+ }
return endpoint;
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 71b7e80335b..cf5ba1e1771 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -21,7 +21,7 @@ export default class FilteredSearchManager {
constructor({
page,
isGroup = false,
- isGroupAncestor = false,
+ isGroupAncestor = true,
isGroupDecendent = false,
filteredSearchTokenKeys = FilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters',
@@ -86,6 +86,7 @@ export default class FilteredSearchManager {
page: this.page,
isGroup: this.isGroup,
isGroupAncestor: this.isGroupAncestor,
+ isGroupDecendent: this.isGroupDecendent,
filteredSearchTokenKeys: this.filteredSearchTokenKeys,
});
diff --git a/app/assets/javascripts/ide/components/changed_file_icon.vue b/app/assets/javascripts/ide/components/changed_file_icon.vue
index 0c54c992e51..037e3efb4ce 100644
--- a/app/assets/javascripts/ide/components/changed_file_icon.vue
+++ b/app/assets/javascripts/ide/components/changed_file_icon.vue
@@ -1,25 +1,25 @@
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
index 18934af004a..560cdd941cd 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
@@ -1,38 +1,36 @@
diff --git a/app/assets/javascripts/ide/components/editor_mode_dropdown.vue b/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
index 170347881e0..0c44a755f56 100644
--- a/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
+++ b/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
@@ -1,31 +1,44 @@
@@ -43,7 +56,10 @@
}"
data-toggle="dropdown"
>
-
+
+ {{ mergeReviewLine }}
+
+
{{ __('Editing') }}
@@ -57,6 +73,29 @@
-
+
-
-
- |
- {{ seriesMetricLabel(index, series) }} |
+
+ {{ series.track }} {{ seriesMetricLabel(index, series) }} |
{{ seriesMetricValue(series) }}
|
diff --git a/app/assets/javascripts/monitoring/components/graph/legend.vue b/app/assets/javascripts/monitoring/components/graph/legend.vue
index a7a058a9203..da9280cf1f1 100644
--- a/app/assets/javascripts/monitoring/components/graph/legend.vue
+++ b/app/assets/javascripts/monitoring/components/graph/legend.vue
@@ -1,204 +1,72 @@
-
-
-
-
-
- {{ yAxisLabel }}
-
-
-
- Time
-
-
-
+
+
-
-
+ {{ series.trackName }}
+
+
+
- {{ createSeriesString(index, series) }}
-
-
- {{ legendTitle }} {{ formatMetricUsage(series) }}
-
-
-
-
+ v-if="timeSeries.length > 1">
+
+
+ {{ legendTitle }} series {{ index + 1 }}
+
+ |
+
+
+ {{ legendTitle }}
+
+ |
+
+
+
+
+ |
+
+
+
+
diff --git a/app/assets/javascripts/monitoring/components/graph/track_info.vue b/app/assets/javascripts/monitoring/components/graph/track_info.vue
new file mode 100644
index 00000000000..ec1c2222af9
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/graph/track_info.vue
@@ -0,0 +1,29 @@
+
+
+
+
+ {{ track.metricTag }}
+
+ {{ summaryMetrics }}
+
+
+
diff --git a/app/assets/javascripts/monitoring/components/graph/track_line.vue b/app/assets/javascripts/monitoring/components/graph/track_line.vue
new file mode 100644
index 00000000000..79b322e2e42
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/graph/track_line.vue
@@ -0,0 +1,36 @@
+
+
+
+
+ |
+
+
diff --git a/app/assets/javascripts/monitoring/stores/monitoring_store.js b/app/assets/javascripts/monitoring/stores/monitoring_store.js
index 854636e9a89..535c415cd6d 100644
--- a/app/assets/javascripts/monitoring/stores/monitoring_store.js
+++ b/app/assets/javascripts/monitoring/stores/monitoring_store.js
@@ -1,7 +1,7 @@
import _ from 'underscore';
function sortMetrics(metrics) {
- return _.chain(metrics).sortBy('weight').sortBy('title').value();
+ return _.chain(metrics).sortBy('title').sortBy('weight').value();
}
function normalizeMetrics(metrics) {
diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
index b5b8e3c255d..8a93c7e6bae 100644
--- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js
+++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
@@ -1,10 +1,21 @@
import _ from 'underscore';
import { scaleLinear, scaleTime } from 'd3-scale';
import { line, area, curveLinear } from 'd3-shape';
-import { extent, max } from 'd3-array';
+import { extent, max, sum } from 'd3-array';
import { timeMinute } from 'd3-time';
+import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
-const d3 = { scaleLinear, scaleTime, line, area, curveLinear, extent, max, timeMinute };
+const d3 = {
+ scaleLinear,
+ scaleTime,
+ line,
+ area,
+ curveLinear,
+ extent,
+ max,
+ timeMinute,
+ sum,
+};
const defaultColorPalette = {
blue: ['#1f78d1', '#8fbce8'],
@@ -20,6 +31,8 @@ const defaultStyleOrder = ['solid', 'dashed', 'dotted'];
function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle) {
let usedColors = [];
+ let renderCanary = false;
+ const timeSeriesParsed = [];
function pickColor(name) {
let pick;
@@ -38,16 +51,23 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
return defaultColorPalette[pick];
}
- return query.result.map((timeSeries, timeSeriesNumber) => {
+ query.result.forEach((timeSeries, timeSeriesNumber) => {
let metricTag = '';
let lineColor = '';
let areaColor = '';
+ let shouldRenderLegend = true;
+ const timeSeriesValues = timeSeries.values.map(d => d.value);
+ const maximumValue = d3.max(timeSeriesValues);
+ const accum = d3.sum(timeSeriesValues);
+ const trackName = capitalizeFirstCharacter(query.track ? query.track : 'Stable');
- const timeSeriesScaleX = d3.scaleTime()
- .range([0, graphWidth - 70]);
+ if (trackName === 'Canary') {
+ renderCanary = true;
+ }
- const timeSeriesScaleY = d3.scaleLinear()
- .range([graphHeight - graphHeightOffset, 0]);
+ const timeSeriesScaleX = d3.scaleTime().range([0, graphWidth - 70]);
+
+ const timeSeriesScaleY = d3.scaleLinear().range([graphHeight - graphHeightOffset, 0]);
timeSeriesScaleX.domain(xDom);
timeSeriesScaleX.ticks(d3.timeMinute, 60);
@@ -55,13 +75,15 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
const defined = d => !isNaN(d.value) && d.value != null;
- const lineFunction = d3.line()
+ const lineFunction = d3
+ .line()
.defined(defined)
.curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate
.x(d => timeSeriesScaleX(d.time))
.y(d => timeSeriesScaleY(d.value));
- const areaFunction = d3.area()
+ const areaFunction = d3
+ .area()
.defined(defined)
.curve(d3.curveLinear)
.x(d => timeSeriesScaleX(d.time))
@@ -69,38 +91,62 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
.y1(d => timeSeriesScaleY(d.value));
const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]];
- const seriesCustomizationData = query.series != null &&
- _.findWhere(query.series[0].when, { value: timeSeriesMetricLabel });
+ const seriesCustomizationData =
+ query.series != null && _.findWhere(query.series[0].when, { value: timeSeriesMetricLabel });
if (seriesCustomizationData) {
metricTag = seriesCustomizationData.value || timeSeriesMetricLabel;
[lineColor, areaColor] = pickColor(seriesCustomizationData.color);
+ shouldRenderLegend = false;
} else {
metricTag = timeSeriesMetricLabel || query.label || `series ${timeSeriesNumber + 1}`;
[lineColor, areaColor] = pickColor();
+ if (timeSeriesParsed.length > 1) {
+ shouldRenderLegend = false;
+ }
}
- if (query.track) {
- metricTag += ` - ${query.track}`;
+ if (!shouldRenderLegend) {
+ if (!timeSeriesParsed[0].tracksLegend) {
+ timeSeriesParsed[0].tracksLegend = [];
+ }
+ timeSeriesParsed[0].tracksLegend.push({
+ max: maximumValue,
+ average: accum / timeSeries.values.length,
+ lineStyle,
+ lineColor,
+ metricTag,
+ });
}
- return {
+ timeSeriesParsed.push({
linePath: lineFunction(timeSeries.values),
areaPath: areaFunction(timeSeries.values),
timeSeriesScaleX,
values: timeSeries.values,
+ max: maximumValue,
+ average: accum / timeSeries.values.length,
lineStyle,
lineColor,
areaColor,
metricTag,
- };
+ trackName,
+ shouldRenderLegend,
+ renderCanary,
+ });
});
+
+ return timeSeriesParsed;
}
export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) {
- const allValues = queries.reduce((allQueryResults, query) => allQueryResults.concat(
- query.result.reduce((allResults, result) => allResults.concat(result.values), []),
- ), []);
+ const allValues = queries.reduce(
+ (allQueryResults, query) =>
+ allQueryResults.concat(
+ query.result.reduce((allResults, result) => allResults.concat(result.values), []),
+ ),
+ [],
+ );
const xDom = d3.extent(allValues, d => d.time);
const yDom = [0, d3.max(allValues.map(d => d.value))];
diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js
index 096c4ef5f31..e3c5bf06b3d 100644
--- a/app/assets/javascripts/mr_notes/index.js
+++ b/app/assets/javascripts/mr_notes/index.js
@@ -13,8 +13,11 @@ export default function initMrNotes() {
data() {
const notesDataset = document.getElementById('js-vue-mr-discussions')
.dataset;
+ const noteableData = JSON.parse(notesDataset.noteableData);
+ noteableData.noteableType = notesDataset.noteableType;
+
return {
- noteableData: JSON.parse(notesDataset.noteableData),
+ noteableData,
currentUserData: JSON.parse(notesDataset.currentUserData),
notesData: JSON.parse(notesDataset.notesData),
};
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index b0573510ff9..ac70ddb3ff4 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1190,12 +1190,12 @@ export default class Notes {
addForm = false;
let lineTypeSelector = '';
rowCssToAdd =
- ' | |
';
+ ' | |
';
// In parallel view, look inside the correct left/right pane
if (this.isParallelView()) {
lineTypeSelector = `.${lineType}`;
rowCssToAdd =
- ' | | | |
';
+ ' | | | |
';
}
const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
let notesContent = targetRow.find(notesContentSelector);
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 90dcafd75b7..648fa6ff804 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -99,6 +99,10 @@ export default {
'js-note-target-reopen': !this.isOpen,
};
},
+ supportQuickActions() {
+ // Disable quick actions support for Epics
+ return this.noteableType !== constants.EPIC_NOTEABLE_TYPE;
+ },
markdownDocsPath() {
return this.getNotesData.markdownDocsPath;
},
@@ -355,7 +359,7 @@ Please check your network connection and try again.`;
name="note[note]"
class="note-textarea js-vue-comment-form
js-gfm-input js-autosize markdown-area js-vue-textarea"
- data-supports-quick-actions="true"
+ :data-supports-quick-actions="supportQuickActions"
aria-label="Description"
v-model="note"
ref="textarea"
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index e0f883a8e08..476b15aca4a 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -258,9 +258,7 @@ Please check your network connection and try again.`;
:key="note.id"
/>
-
+
{
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
+ isGroupDecendent: true,
});
projectSelect();
});
diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js
index a5cc1f34b63..1600faa3611 100644
--- a/app/assets/javascripts/pages/groups/merge_requests/index.js
+++ b/app/assets/javascripts/pages/groups/merge_requests/index.js
@@ -5,6 +5,7 @@ import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
+ isGroupDecendent: true,
});
projectSelect();
});
diff --git a/app/assets/javascripts/pages/groups/settings/badges/index.js b/app/assets/javascripts/pages/groups/settings/badges/index.js
new file mode 100644
index 00000000000..74e96ee4a8f
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/settings/badges/index.js
@@ -0,0 +1,10 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import { GROUP_BADGE } from '~/badges/constants';
+import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
+
+Vue.use(Translate);
+
+document.addEventListener('DOMContentLoaded', () => {
+ mountBadgeSettings(GROUP_BADGE);
+});
diff --git a/app/assets/javascripts/pages/projects/settings/badges/index/index.js b/app/assets/javascripts/pages/projects/settings/badges/index/index.js
new file mode 100644
index 00000000000..30469550866
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/badges/index/index.js
@@ -0,0 +1,10 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import { PROJECT_BADGE } from '~/badges/constants';
+import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
+
+Vue.use(Translate);
+
+document.addEventListener('DOMContentLoaded', () => {
+ mountBadgeSettings(PROJECT_BADGE);
+});
diff --git a/app/assets/javascripts/pages/projects/settings/repository/create_deploy_token/index.js b/app/assets/javascripts/pages/projects/settings/repository/create_deploy_token/index.js
new file mode 100644
index 00000000000..ffc84dc106b
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/repository/create_deploy_token/index.js
@@ -0,0 +1,3 @@
+import initForm from '../form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/settings/repository/form.js b/app/assets/javascripts/pages/projects/settings/repository/form.js
new file mode 100644
index 00000000000..a5c17ab322c
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/repository/form.js
@@ -0,0 +1,19 @@
+/* eslint-disable no-new */
+
+import ProtectedTagCreate from '~/protected_tags/protected_tag_create';
+import ProtectedTagEditList from '~/protected_tags/protected_tag_edit_list';
+import initSettingsPanels from '~/settings_panels';
+import initDeployKeys from '~/deploy_keys';
+import ProtectedBranchCreate from '~/protected_branches/protected_branch_create';
+import ProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list';
+import DueDateSelectors from '~/due_date_select';
+
+export default () => {
+ new ProtectedTagCreate();
+ new ProtectedTagEditList();
+ initDeployKeys();
+ initSettingsPanels();
+ new ProtectedBranchCreate(); // eslint-disable-line no-new
+ new ProtectedBranchEditList(); // eslint-disable-line no-new
+ new DueDateSelectors();
+};
diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
index 788d86d1192..ffc84dc106b 100644
--- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
@@ -1,17 +1,3 @@
-/* eslint-disable no-new */
+import initForm from '../form';
-import ProtectedTagCreate from '~/protected_tags/protected_tag_create';
-import ProtectedTagEditList from '~/protected_tags/protected_tag_edit_list';
-import initSettingsPanels from '~/settings_panels';
-import initDeployKeys from '~/deploy_keys';
-import ProtectedBranchCreate from '~/protected_branches/protected_branch_create';
-import ProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list';
-
-document.addEventListener('DOMContentLoaded', () => {
- new ProtectedTagCreate();
- new ProtectedTagEditList();
- initDeployKeys();
- initSettingsPanels();
- new ProtectedBranchCreate(); // eslint-disable-line no-new
- new ProtectedBranchEditList(); // eslint-disable-line no-new
-});
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/shared/mount_badge_settings.js b/app/assets/javascripts/pages/shared/mount_badge_settings.js
new file mode 100644
index 00000000000..1397c0834ff
--- /dev/null
+++ b/app/assets/javascripts/pages/shared/mount_badge_settings.js
@@ -0,0 +1,24 @@
+import Vue from 'vue';
+import BadgeSettings from '~/badges/components/badge_settings.vue';
+import store from '~/badges/store';
+
+export default kind => {
+ const badgeSettingsElement = document.getElementById('badge-settings');
+
+ store.dispatch('loadBadges', {
+ kind,
+ apiEndpointUrl: badgeSettingsElement.dataset.apiEndpointUrl,
+ docsUrl: badgeSettingsElement.dataset.docsUrl,
+ });
+
+ return new Vue({
+ el: badgeSettingsElement,
+ store,
+ components: {
+ BadgeSettings,
+ },
+ render(createElement) {
+ return createElement(BadgeSettings);
+ },
+ });
+};
diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue
index d7effb27bff..e99d949801f 100644
--- a/app/assets/javascripts/pipelines/components/graph/action_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue
@@ -1,60 +1,72 @@
-
-
+
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index ab84711d4a2..ac9ce7e47d6 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -1,54 +1,59 @@
@@ -70,6 +75,7 @@
:key="stage.name"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"
+ :action-disabled="actionDisabled"
/>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue
index 9b136573135..c6e5ae6df41 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue
@@ -1,95 +1,102 @@
@@ -100,6 +107,7 @@
:title="tooltipText"
:class="cssClassJobName"
data-container="body"
+ data-html="true"
class="js-pipeline-graph-job-link"
>
@@ -115,6 +123,7 @@
class="js-job-component-tooltip"
:title="tooltipText"
:class="cssClassJobName"
+ data-html="true"
data-container="body"
>
@@ -129,7 +138,7 @@
:tooltip-text="status.action.title"
:link="status.action.path"
:action-icon="status.action.icon"
- :action-method="status.action.method"
+ :button-disabled="actionDisabled"
/>
- import jobComponent from './job_component.vue';
- import dropdownJobComponent from './dropdown_job_component.vue';
+import JobComponent from './job_component.vue';
+import DropdownJobComponent from './dropdown_job_component.vue';
- export default {
- components: {
- jobComponent,
- dropdownJobComponent,
- },
- props: {
- title: {
- type: String,
- required: true,
- },
-
- jobs: {
- type: Array,
- required: true,
- },
-
- isFirstColumn: {
- type: Boolean,
- required: false,
- default: false,
- },
-
- stageConnectorClass: {
- type: String,
- required: false,
- default: '',
- },
+export default {
+ components: {
+ JobComponent,
+ DropdownJobComponent,
+ },
+ props: {
+ title: {
+ type: String,
+ required: true,
},
- methods: {
- firstJob(list) {
- return list[0];
- },
-
- jobId(job) {
- return `ci-badge-${job.name}`;
- },
-
- buildConnnectorClass(index) {
- return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
- },
+ jobs: {
+ type: Array,
+ required: true,
},
- };
+
+ isFirstColumn: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+
+ stageConnectorClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ actionDisabled: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+
+ methods: {
+ firstJob(list) {
+ return list[0];
+ },
+
+ jobId(job) {
+ return `ci-badge-${job.name}`;
+ },
+
+ buildConnnectorClass(index) {
+ return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
+ },
+ },
+};
this.state.pageInfo.perPage;
+ this.state.pageInfo.total > this.state.pageInfo.perPage
+ );
},
emptyTabMessage() {
@@ -229,15 +224,13 @@
},
methods: {
successCallback(resp) {
- return resp.json().then((response) => {
- // Because we are polling & the user is interacting verify if the response received
- // matches the last request made
- if (_.isEqual(parseQueryStringIntoObject(resp.url.split('?')[1]), this.requestData)) {
- this.store.storeCount(response.count);
- this.store.storePagination(resp.headers);
- this.setCommonData(response.pipelines);
- }
- });
+ // Because we are polling & the user is interacting verify if the response received
+ // matches the last request made
+ if (_.isEqual(resp.config.params, this.requestData)) {
+ this.store.storeCount(resp.data.count);
+ this.store.storePagination(resp.headers);
+ this.setCommonData(resp.data.pipelines);
+ }
},
/**
* Handles URL and query parameter changes.
@@ -251,8 +244,9 @@
this.updateInternalState(parameters);
// fetch new data
- return this.service.getPipelines(this.requestData)
- .then((response) => {
+ return this.service
+ .getPipelines(this.requestData)
+ .then(response => {
this.isLoading = false;
this.successCallback(response);
@@ -271,13 +265,11 @@
handleResetRunnersCache(endpoint) {
this.isResetCacheButtonLoading = true;
- this.service.postAction(endpoint)
+ this.service
+ .postAction(endpoint)
.then(() => {
this.isResetCacheButtonLoading = false;
- createFlash(
- s__('Pipelines|Project cache successfully reset.'),
- 'notice',
- );
+ createFlash(s__('Pipelines|Project cache successfully reset.'), 'notice');
})
.catch(() => {
this.isResetCacheButtonLoading = false;
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index 6b26708148c..900eb7855f4 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -25,13 +25,36 @@ export default () => {
data() {
return {
mediator,
+ actionDisabled: null,
};
},
+ created() {
+ eventHub.$on('graphAction', this.postAction);
+ },
+ beforeDestroy() {
+ eventHub.$off('graphAction', this.postAction);
+ },
+ methods: {
+ postAction(action) {
+ this.actionDisabled = action;
+
+ this.mediator.service.postAction(action)
+ .then(() => {
+ this.mediator.refreshPipeline();
+ this.actionDisabled = null;
+ })
+ .catch(() => {
+ this.actionDisabled = null;
+ Flash(__('An error occurred while making the request.'));
+ });
+ },
+ },
render(createElement) {
return createElement('pipeline-graph', {
props: {
isLoading: this.mediator.state.isLoading,
pipeline: this.mediator.store.state.pipeline,
+ actionDisabled: this.actionDisabled,
},
});
},
diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediator.js b/app/assets/javascripts/pipelines/pipeline_details_mediator.js
index 10f238fe73b..621969cd622 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_mediator.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_mediator.js
@@ -52,8 +52,11 @@ export default class pipelinesMediator {
}
refreshPipeline() {
- this.service.getPipeline()
+ this.poll.stop();
+
+ return this.service.getPipeline()
.then(response => this.successCallback(response))
- .catch(() => this.errorCallback());
+ .catch(() => this.errorCallback())
+ .finally(() => this.poll.restart());
}
}
diff --git a/app/assets/javascripts/pipelines/services/pipelines_service.js b/app/assets/javascripts/pipelines/services/pipelines_service.js
index 47736fc5f42..001286f5d52 100644
--- a/app/assets/javascripts/pipelines/services/pipelines_service.js
+++ b/app/assets/javascripts/pipelines/services/pipelines_service.js
@@ -1,35 +1,27 @@
-/* eslint-disable class-methods-use-this */
-import Vue from 'vue';
-import VueResource from 'vue-resource';
-import '../../vue_shared/vue_resource_interceptor';
-
-Vue.use(VueResource);
+import axios from '../../lib/utils/axios_utils';
export default class PipelinesService {
-
/**
- * Commits and merge request endpoints need to be requested with `.json`.
- *
- * The url provided to request the pipelines in the new merge request
- * page already has `.json`.
- *
- * @param {String} root
- */
+ * Commits and merge request endpoints need to be requested with `.json`.
+ *
+ * The url provided to request the pipelines in the new merge request
+ * page already has `.json`.
+ *
+ * @param {String} root
+ */
constructor(root) {
- let endpoint;
-
if (root.indexOf('.json') === -1) {
- endpoint = `${root}.json`;
+ this.endpoint = `${root}.json`;
} else {
- endpoint = root;
+ this.endpoint = root;
}
-
- this.pipelines = Vue.resource(endpoint);
}
getPipelines(data = {}) {
const { scope, page } = data;
- return this.pipelines.get({ scope, page });
+ return axios.get(this.endpoint, {
+ params: { scope, page },
+ });
}
/**
@@ -38,7 +30,8 @@ export default class PipelinesService {
* @param {String} endpoint
* @return {Promise}
*/
+ // eslint-disable-next-line class-methods-use-this
postAction(endpoint) {
- return Vue.http.post(`${endpoint}.json`);
+ return axios.post(`${endpoint}.json`);
}
}
diff --git a/app/assets/javascripts/profile/account/components/update_username.vue b/app/assets/javascripts/profile/account/components/update_username.vue
new file mode 100644
index 00000000000..e5de3f69b01
--- /dev/null
+++ b/app/assets/javascripts/profile/account/components/update_username.vue
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/profile/account/index.js b/app/assets/javascripts/profile/account/index.js
index 84049a1f0b7..59c13e1a042 100644
--- a/app/assets/javascripts/profile/account/index.js
+++ b/app/assets/javascripts/profile/account/index.js
@@ -1,10 +1,25 @@
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
+import UpdateUsername from './components/update_username.vue';
import deleteAccountModal from './components/delete_account_modal.vue';
export default () => {
Vue.use(Translate);
+ const updateUsernameElement = document.getElementById('update-username');
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: updateUsernameElement,
+ components: {
+ UpdateUsername,
+ },
+ render(createElement) {
+ return createElement('update-username', {
+ props: { ...updateUsernameElement.dataset },
+ });
+ },
+ });
+
const deleteAccountButton = document.getElementById('delete-account-button');
const deleteAccountModalEl = document.getElementById('delete-account-modal');
// eslint-disable-next-line no-new
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index 7dd3e9858c6..2da022fde63 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -233,21 +233,21 @@ export default class SearchAutocomplete {
const issueItems = [
{
text: 'Issues assigned to me',
- url: `${issuesPath}/?assignee_username=${userName}`,
+ url: `${issuesPath}/?assignee_id=${userId}`,
},
{
text: "Issues I've created",
- url: `${issuesPath}/?author_username=${userName}`,
+ url: `${issuesPath}/?author_id=${userId}`,
},
];
const mergeRequestItems = [
{
text: 'Merge requests assigned to me',
- url: `${mrPath}/?assignee_username=${userName}`,
+ url: `${mrPath}/?assignee_id=${userId}`,
},
{
text: "Merge requests I've created",
- url: `${mrPath}/?author_username=${userName}`,
+ url: `${mrPath}/?author_id=${userId}`,
},
];
diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
index 8a86c409b62..ceb02309959 100644
--- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
@@ -1,59 +1,73 @@