Merge commit '8af23def1d6' into object-storage-ee-to-ce-backport
3
.babelrc
|
|
@ -8,7 +8,8 @@
|
|||
"plugins": [
|
||||
["istanbul", {
|
||||
"exclude": [
|
||||
"spec/javascripts/**/*"
|
||||
"spec/javascripts/**/*",
|
||||
"app/assets/javascripts/locale/**/app.js"
|
||||
]
|
||||
}],
|
||||
["transform-define", {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
---
|
||||
engines:
|
||||
brakeman:
|
||||
enabled: true
|
||||
bundler-audit:
|
||||
enabled: true
|
||||
duplication:
|
||||
|
|
@ -13,7 +11,8 @@ engines:
|
|||
exclude_paths:
|
||||
- "lib/api/v3/*"
|
||||
eslint:
|
||||
enabled: true
|
||||
# eslint-plugin-vue is locked to version 2 in codeclimate, we need version 4
|
||||
enabled: false
|
||||
rubocop:
|
||||
enabled: true
|
||||
channel: "gitlab-rubocop-0-52"
|
||||
|
|
|
|||
23
.eslintrc
|
|
@ -4,15 +4,19 @@
|
|||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "airbnb-base",
|
||||
"extends": [
|
||||
"airbnb-base",
|
||||
"plugin:vue/recommended"
|
||||
],
|
||||
"globals": {
|
||||
"__webpack_public_path__": true,
|
||||
"_": false,
|
||||
"gl": false,
|
||||
"gon": false,
|
||||
"localStorage": false
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"plugins": [
|
||||
"filenames",
|
||||
"import",
|
||||
|
|
@ -20,7 +24,7 @@
|
|||
"promise"
|
||||
],
|
||||
"settings": {
|
||||
"html/html-extensions": [".html", ".html.raw", ".vue"],
|
||||
"html/html-extensions": [".html", ".html.raw"],
|
||||
"import/resolver": {
|
||||
"webpack": {
|
||||
"config": "./config/webpack.config.js"
|
||||
|
|
@ -32,6 +36,15 @@
|
|||
"import/no-commonjs": "error",
|
||||
"no-multiple-empty-lines": ["error", { "max": 1 }],
|
||||
"promise/catch-or-return": "error",
|
||||
"no-underscore-dangle": ["error", { "allow": ["__"]}]
|
||||
"no-underscore-dangle": ["error", { "allow": ["__"]}],
|
||||
"vue/html-self-closing": ["error", {
|
||||
"html": {
|
||||
"void": "always",
|
||||
"normal": "never",
|
||||
"component": "always"
|
||||
},
|
||||
"svg": "always",
|
||||
"math": "always"
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
*.swp
|
||||
*.mo
|
||||
*.edit.po
|
||||
*.rej
|
||||
.DS_Store
|
||||
.bundle
|
||||
.chef
|
||||
|
|
|
|||
171
.gitlab-ci.yml
|
|
@ -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-235-with-yarn"
|
||||
key: "ruby-2.3.6-with-yarn"
|
||||
paths:
|
||||
- vendor/ruby
|
||||
- .yarn-cache/
|
||||
|
|
@ -61,6 +61,9 @@ stages:
|
|||
|
||||
.use-pg: &use-pg
|
||||
services:
|
||||
# As of Jan 2018, we don't have a strong reason to upgrade to 9.6 for CI yet,
|
||||
# so using the least common denominator ensures backwards compatibility
|
||||
# (as many users are still using 9.2).
|
||||
- postgres:9.2
|
||||
- redis:alpine
|
||||
|
||||
|
|
@ -287,7 +290,7 @@ flaky-examples-check:
|
|||
- scripts/merge-reports ${NEW_FLAKY_SPECS_REPORT} rspec_flaky/new_*_*.json
|
||||
- scripts/detect-new-flaky-examples $NEW_FLAKY_SPECS_REPORT
|
||||
|
||||
setup-test-env:
|
||||
compile-assets:
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs
|
||||
<<: *use-pg
|
||||
|
|
@ -298,82 +301,94 @@ setup-test-env:
|
|||
- node --version
|
||||
- yarn install --frozen-lockfile --cache-folder .yarn-cache
|
||||
- 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:
|
||||
- node_modules
|
||||
- public/assets
|
||||
|
||||
setup-test-env:
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs
|
||||
<<: *use-pg
|
||||
stage: prepare
|
||||
cache:
|
||||
<<: *default-cache
|
||||
script:
|
||||
- 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:
|
||||
- tmp/tests
|
||||
- config/secrets.yml
|
||||
|
||||
rspec-pg 0 26: *rspec-metadata-pg
|
||||
rspec-pg 1 26: *rspec-metadata-pg
|
||||
rspec-pg 2 26: *rspec-metadata-pg
|
||||
rspec-pg 3 26: *rspec-metadata-pg
|
||||
rspec-pg 4 26: *rspec-metadata-pg
|
||||
rspec-pg 5 26: *rspec-metadata-pg
|
||||
rspec-pg 6 26: *rspec-metadata-pg
|
||||
rspec-pg 7 26: *rspec-metadata-pg
|
||||
rspec-pg 8 26: *rspec-metadata-pg
|
||||
rspec-pg 9 26: *rspec-metadata-pg
|
||||
rspec-pg 10 26: *rspec-metadata-pg
|
||||
rspec-pg 11 26: *rspec-metadata-pg
|
||||
rspec-pg 12 26: *rspec-metadata-pg
|
||||
rspec-pg 13 26: *rspec-metadata-pg
|
||||
rspec-pg 14 26: *rspec-metadata-pg
|
||||
rspec-pg 15 26: *rspec-metadata-pg
|
||||
rspec-pg 16 26: *rspec-metadata-pg
|
||||
rspec-pg 17 26: *rspec-metadata-pg
|
||||
rspec-pg 18 26: *rspec-metadata-pg
|
||||
rspec-pg 19 26: *rspec-metadata-pg
|
||||
rspec-pg 20 26: *rspec-metadata-pg
|
||||
rspec-pg 21 26: *rspec-metadata-pg
|
||||
rspec-pg 22 26: *rspec-metadata-pg
|
||||
rspec-pg 23 26: *rspec-metadata-pg
|
||||
rspec-pg 24 26: *rspec-metadata-pg
|
||||
rspec-pg 25 26: *rspec-metadata-pg
|
||||
rspec-pg 0 27: *rspec-metadata-pg
|
||||
rspec-pg 1 27: *rspec-metadata-pg
|
||||
rspec-pg 2 27: *rspec-metadata-pg
|
||||
rspec-pg 3 27: *rspec-metadata-pg
|
||||
rspec-pg 4 27: *rspec-metadata-pg
|
||||
rspec-pg 5 27: *rspec-metadata-pg
|
||||
rspec-pg 6 27: *rspec-metadata-pg
|
||||
rspec-pg 7 27: *rspec-metadata-pg
|
||||
rspec-pg 8 27: *rspec-metadata-pg
|
||||
rspec-pg 9 27: *rspec-metadata-pg
|
||||
rspec-pg 10 27: *rspec-metadata-pg
|
||||
rspec-pg 11 27: *rspec-metadata-pg
|
||||
rspec-pg 12 27: *rspec-metadata-pg
|
||||
rspec-pg 13 27: *rspec-metadata-pg
|
||||
rspec-pg 14 27: *rspec-metadata-pg
|
||||
rspec-pg 15 27: *rspec-metadata-pg
|
||||
rspec-pg 16 27: *rspec-metadata-pg
|
||||
rspec-pg 17 27: *rspec-metadata-pg
|
||||
rspec-pg 18 27: *rspec-metadata-pg
|
||||
rspec-pg 19 27: *rspec-metadata-pg
|
||||
rspec-pg 20 27: *rspec-metadata-pg
|
||||
rspec-pg 21 27: *rspec-metadata-pg
|
||||
rspec-pg 22 27: *rspec-metadata-pg
|
||||
rspec-pg 23 27: *rspec-metadata-pg
|
||||
rspec-pg 24 27: *rspec-metadata-pg
|
||||
rspec-pg 25 27: *rspec-metadata-pg
|
||||
rspec-pg 26 27: *rspec-metadata-pg
|
||||
|
||||
rspec-mysql 0 26: *rspec-metadata-mysql
|
||||
rspec-mysql 1 26: *rspec-metadata-mysql
|
||||
rspec-mysql 2 26: *rspec-metadata-mysql
|
||||
rspec-mysql 3 26: *rspec-metadata-mysql
|
||||
rspec-mysql 4 26: *rspec-metadata-mysql
|
||||
rspec-mysql 5 26: *rspec-metadata-mysql
|
||||
rspec-mysql 6 26: *rspec-metadata-mysql
|
||||
rspec-mysql 7 26: *rspec-metadata-mysql
|
||||
rspec-mysql 8 26: *rspec-metadata-mysql
|
||||
rspec-mysql 9 26: *rspec-metadata-mysql
|
||||
rspec-mysql 10 26: *rspec-metadata-mysql
|
||||
rspec-mysql 11 26: *rspec-metadata-mysql
|
||||
rspec-mysql 12 26: *rspec-metadata-mysql
|
||||
rspec-mysql 13 26: *rspec-metadata-mysql
|
||||
rspec-mysql 14 26: *rspec-metadata-mysql
|
||||
rspec-mysql 15 26: *rspec-metadata-mysql
|
||||
rspec-mysql 16 26: *rspec-metadata-mysql
|
||||
rspec-mysql 17 26: *rspec-metadata-mysql
|
||||
rspec-mysql 18 26: *rspec-metadata-mysql
|
||||
rspec-mysql 19 26: *rspec-metadata-mysql
|
||||
rspec-mysql 20 26: *rspec-metadata-mysql
|
||||
rspec-mysql 21 26: *rspec-metadata-mysql
|
||||
rspec-mysql 22 26: *rspec-metadata-mysql
|
||||
rspec-mysql 23 26: *rspec-metadata-mysql
|
||||
rspec-mysql 24 26: *rspec-metadata-mysql
|
||||
rspec-mysql 25 26: *rspec-metadata-mysql
|
||||
rspec-mysql 0 27: *rspec-metadata-mysql
|
||||
rspec-mysql 1 27: *rspec-metadata-mysql
|
||||
rspec-mysql 2 27: *rspec-metadata-mysql
|
||||
rspec-mysql 3 27: *rspec-metadata-mysql
|
||||
rspec-mysql 4 27: *rspec-metadata-mysql
|
||||
rspec-mysql 5 27: *rspec-metadata-mysql
|
||||
rspec-mysql 6 27: *rspec-metadata-mysql
|
||||
rspec-mysql 7 27: *rspec-metadata-mysql
|
||||
rspec-mysql 8 27: *rspec-metadata-mysql
|
||||
rspec-mysql 9 27: *rspec-metadata-mysql
|
||||
rspec-mysql 10 27: *rspec-metadata-mysql
|
||||
rspec-mysql 11 27: *rspec-metadata-mysql
|
||||
rspec-mysql 12 27: *rspec-metadata-mysql
|
||||
rspec-mysql 13 27: *rspec-metadata-mysql
|
||||
rspec-mysql 14 27: *rspec-metadata-mysql
|
||||
rspec-mysql 15 27: *rspec-metadata-mysql
|
||||
rspec-mysql 16 27: *rspec-metadata-mysql
|
||||
rspec-mysql 17 27: *rspec-metadata-mysql
|
||||
rspec-mysql 18 27: *rspec-metadata-mysql
|
||||
rspec-mysql 19 27: *rspec-metadata-mysql
|
||||
rspec-mysql 20 27: *rspec-metadata-mysql
|
||||
rspec-mysql 21 27: *rspec-metadata-mysql
|
||||
rspec-mysql 22 27: *rspec-metadata-mysql
|
||||
rspec-mysql 23 27: *rspec-metadata-mysql
|
||||
rspec-mysql 24 27: *rspec-metadata-mysql
|
||||
rspec-mysql 25 27: *rspec-metadata-mysql
|
||||
rspec-mysql 26 27: *rspec-metadata-mysql
|
||||
|
||||
spinach-pg 0 4: *spinach-metadata-pg
|
||||
spinach-pg 1 4: *spinach-metadata-pg
|
||||
spinach-pg 2 4: *spinach-metadata-pg
|
||||
spinach-pg 3 4: *spinach-metadata-pg
|
||||
spinach-pg 0 3: *spinach-metadata-pg
|
||||
spinach-pg 1 3: *spinach-metadata-pg
|
||||
spinach-pg 2 3: *spinach-metadata-pg
|
||||
|
||||
spinach-mysql 0 4: *spinach-metadata-mysql
|
||||
spinach-mysql 1 4: *spinach-metadata-mysql
|
||||
spinach-mysql 2 4: *spinach-metadata-mysql
|
||||
spinach-mysql 3 4: *spinach-metadata-mysql
|
||||
spinach-mysql 0 3: *spinach-metadata-mysql
|
||||
spinach-mysql 1 3: *spinach-metadata-mysql
|
||||
spinach-mysql 2 3: *spinach-metadata-mysql
|
||||
|
||||
# Static analysis jobs
|
||||
.ruby-static-analysis: &ruby-static-analysis
|
||||
<<: *pull-cache
|
||||
variables:
|
||||
SIMPLECOV: "false"
|
||||
SETUP_DB: "false"
|
||||
|
|
@ -394,6 +409,12 @@ static-analysis:
|
|||
stage: test
|
||||
script:
|
||||
- scripts/static-analysis
|
||||
cache:
|
||||
key: "ruby-2.3.6-with-yarn-and-rubocop"
|
||||
paths:
|
||||
- vendor/ruby
|
||||
- .yarn-cache/
|
||||
- tmp/rubocop_cache
|
||||
|
||||
# Documentation checks:
|
||||
# - Check validity of relative links
|
||||
|
|
@ -604,6 +625,7 @@ codequality:
|
|||
paths: [codeclimate.json]
|
||||
|
||||
sast:
|
||||
<<: *except-docs
|
||||
image: registry.gitlab.com/gitlab-org/gl-sast:latest
|
||||
before_script: []
|
||||
script:
|
||||
|
|
@ -623,6 +645,18 @@ qa:internal:
|
|||
- bundle install
|
||||
- bundle exec rspec
|
||||
|
||||
qa:selectors:
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs
|
||||
stage: test
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
services: []
|
||||
script:
|
||||
- cd qa/
|
||||
- bundle install
|
||||
- bundle exec bin/qa Test::Sanity::Selectors
|
||||
|
||||
coverage:
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
|
|
@ -648,6 +682,7 @@ lint:javascript:report:
|
|||
<<: *pull-cache
|
||||
stage: post-test
|
||||
dependencies:
|
||||
- compile-assets
|
||||
- setup-test-env
|
||||
before_script: []
|
||||
script:
|
||||
|
|
@ -688,8 +723,6 @@ pages:
|
|||
cache gems:
|
||||
<<: *dedicated-runner
|
||||
<<: *pull-cache
|
||||
only:
|
||||
- tags
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
script:
|
||||
|
|
@ -700,12 +733,14 @@ cache gems:
|
|||
only:
|
||||
- master@gitlab-org/gitlab-ce
|
||||
- master@gitlab-org/gitlab-ee
|
||||
- tags
|
||||
|
||||
gitlab_git_test:
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
<<: *pull-cache
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
before_script: []
|
||||
cache: {}
|
||||
script:
|
||||
- spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
|
||||
|
|
|
|||
|
|
@ -11,4 +11,6 @@ See the guidelines: http://docs.gitlab.com/ce/development/doc_styleguide.html#ch
|
|||
- [ ] Make sure the old link is not removed and has its contents replaced with a link to the new location.
|
||||
- [ ] Make sure internal links pointing to the document in question are not broken.
|
||||
- [ ] Search and replace any links referring to old docs in GitLab Rails app, specifically under the `app/views/` directory.
|
||||
- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ee/development/doc_styleguide.html#redirections-for-pages-with-disqus-comments) to the new document if there are any Disqus comments on the old document thread.
|
||||
- [ ] If working on CE, submit an MR to EE with the changes as well.
|
||||
- [ ] Ping one of the technical writers for review.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ AllCops:
|
|||
- 'bin/**/*'
|
||||
- 'generator_templates/**/*'
|
||||
- 'builds/**/*'
|
||||
CacheRootDirectory: tmp
|
||||
|
||||
# Gitlab ###################################################################
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,26 @@
|
|||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config`
|
||||
# on 2017-12-14 12:04:26 +0100 using RuboCop version 0.52.0.
|
||||
# on 2018-01-18 18:23:26 +0100 using RuboCop version 0.52.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: 174
|
||||
# Offense count: 181
|
||||
Capybara/CurrentPathExpectation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 951
|
||||
# Offense count: 956
|
||||
Capybara/FeatureMethods:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 24
|
||||
# Offense count: 23
|
||||
FactoryBot/DynamicAttributeDefinedStatically:
|
||||
Exclude:
|
||||
- 'spec/factories/broadcast_messages.rb'
|
||||
- 'spec/factories/ci/builds.rb'
|
||||
- 'spec/factories/ci/runners.rb'
|
||||
- 'spec/factories/clusters/applications/helm.rb'
|
||||
- 'spec/factories/clusters/applications/ingress.rb'
|
||||
- 'spec/factories/clusters/platforms/kubernetes.rb'
|
||||
- 'spec/factories/emails.rb'
|
||||
- 'spec/factories/gpg_keys.rb'
|
||||
|
|
@ -33,40 +32,31 @@ FactoryBot/DynamicAttributeDefinedStatically:
|
|||
- 'spec/factories/todos.rb'
|
||||
- 'spec/factories/uploads.rb'
|
||||
|
||||
# Offense count: 65
|
||||
# Offense count: 167
|
||||
# Cop supports --auto-correct.
|
||||
Layout/EmptyLinesAroundArguments:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 249
|
||||
# Offense count: 253
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
|
||||
Layout/ExtraSpacing:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 82
|
||||
# Offense count: 83
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: special_inside_parentheses, consistent, align_brackets
|
||||
Layout/IndentArray:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 239
|
||||
# Offense count: 237
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: special_inside_parentheses, consistent, align_braces
|
||||
Layout/IndentHash:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 15
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: .
|
||||
# SupportedStyles: space, no_space
|
||||
# SupportedStylesForEmptyBraces: space, no_space
|
||||
Layout/SpaceBeforeBlockBraces:
|
||||
EnforcedStyle: space
|
||||
EnforcedStyleForEmptyBraces: space
|
||||
|
||||
# Offense count: 11
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AllowForAlignment.
|
||||
|
|
@ -97,7 +87,7 @@ Layout/SpaceInsideArrayLiteralBrackets:
|
|||
Exclude:
|
||||
- 'spec/lib/gitlab/import_export/relation_factory_spec.rb'
|
||||
|
||||
# Offense count: 323
|
||||
# Offense count: 327
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
|
||||
# SupportedStyles: space, no_space
|
||||
|
|
@ -105,7 +95,7 @@ Layout/SpaceInsideArrayLiteralBrackets:
|
|||
Layout/SpaceInsideBlockBraces:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 146
|
||||
# Offense count: 156
|
||||
# Cop supports --auto-correct.
|
||||
Layout/SpaceInsideParens:
|
||||
Enabled: false
|
||||
|
|
@ -118,7 +108,7 @@ Layout/SpaceInsidePercentLiteralDelimiters:
|
|||
- 'lib/gitlab/health_checks/fs_shards_check.rb'
|
||||
- 'spec/lib/gitlab/health_checks/fs_shards_check_spec.rb'
|
||||
|
||||
# Offense count: 25
|
||||
# Offense count: 26
|
||||
Lint/DuplicateMethods:
|
||||
Exclude:
|
||||
- 'app/models/application_setting.rb'
|
||||
|
|
@ -144,7 +134,7 @@ Lint/InterpolationCheck:
|
|||
- 'spec/features/users_spec.rb'
|
||||
- 'spec/services/quick_actions/interpret_service_spec.rb'
|
||||
|
||||
# Offense count: 198
|
||||
# Offense count: 206
|
||||
# Configuration parameters: MaximumRangeSize.
|
||||
Lint/MissingCopEnableDirective:
|
||||
Enabled: false
|
||||
|
|
@ -185,6 +175,12 @@ Lint/UriEscapeUnescape:
|
|||
- 'spec/requests/api/issues_spec.rb'
|
||||
- 'spec/requests/api/v3/issues_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
||||
# URISchemes: http, https
|
||||
Metrics/LineLength:
|
||||
Max: 1310
|
||||
|
||||
# Offense count: 2
|
||||
Naming/ConstantName:
|
||||
Exclude:
|
||||
|
|
@ -202,13 +198,13 @@ Naming/HeredocDelimiterCase:
|
|||
- 'spec/support/repo_helpers.rb'
|
||||
- 'spec/support/seed_repo.rb'
|
||||
|
||||
# Offense count: 101
|
||||
# Offense count: 112
|
||||
# Configuration parameters: Blacklist.
|
||||
# Blacklist: END, (?-mix:EO[A-Z]{1})
|
||||
Naming/HeredocDelimiterNaming:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 28
|
||||
# Offense count: 27
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AutoCorrect.
|
||||
Performance/HashEachMethods:
|
||||
|
|
@ -225,21 +221,27 @@ Performance/UriDefaultParser:
|
|||
Exclude:
|
||||
- 'lib/gitlab/url_sanitizer.rb'
|
||||
|
||||
# Offense count: 3745
|
||||
# Offense count: 3821
|
||||
# Configuration parameters: Prefixes.
|
||||
# Prefixes: when, with, without
|
||||
RSpec/ContextWording:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 291
|
||||
# Offense count: 293
|
||||
RSpec/EmptyLineAfterFinalLet:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 180
|
||||
# Offense count: 188
|
||||
RSpec/EmptyLineAfterSubject:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 220
|
||||
# Offense count: 258
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: method_call, block
|
||||
RSpec/ExpectChange:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 221
|
||||
RSpec/ExpectInHook:
|
||||
Enabled: false
|
||||
|
||||
|
|
@ -304,7 +306,7 @@ RSpec/OverwritingSetup:
|
|||
- 'spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb'
|
||||
- 'spec/services/notes/quick_actions_service_spec.rb'
|
||||
|
||||
# Offense count: 917
|
||||
# Offense count: 965
|
||||
# Configuration parameters: Strict, EnforcedStyle.
|
||||
# SupportedStyles: inflected, explicit
|
||||
RSpec/PredicateMatcher:
|
||||
|
|
@ -314,13 +316,13 @@ RSpec/PredicateMatcher:
|
|||
RSpec/RepeatedExample:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 132
|
||||
# Offense count: 140
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: and_return, block
|
||||
RSpec/ReturnFromStub:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 105
|
||||
# Offense count: 112
|
||||
RSpec/ScatteredLet:
|
||||
Enabled: false
|
||||
|
||||
|
|
@ -340,10 +342,6 @@ RSpec/SharedContext:
|
|||
Exclude:
|
||||
- 'spec/features/admin/admin_groups_spec.rb'
|
||||
|
||||
# Offense count: 90
|
||||
RSpec/SingleLineHook:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 5
|
||||
RSpec/VoidExpect:
|
||||
Exclude:
|
||||
|
|
@ -353,23 +351,23 @@ RSpec/VoidExpect:
|
|||
- 'spec/models/ci/runner_spec.rb'
|
||||
- 'spec/services/users/destroy_service_spec.rb'
|
||||
|
||||
# Offense count: 40
|
||||
# Offense count: 41
|
||||
# Configuration parameters: Include.
|
||||
# Include: db/migrate/*.rb
|
||||
Rails/CreateTableWithTimestamps:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 149
|
||||
# Offense count: 155
|
||||
Rails/FilePath:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 119
|
||||
# Offense count: 121
|
||||
# Configuration parameters: Include.
|
||||
# Include: app/models/**/*.rb
|
||||
Rails/HasManyOrHasOneDependent:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 113
|
||||
# Offense count: 157
|
||||
# Configuration parameters: Include.
|
||||
# Include: app/models/**/*.rb
|
||||
Rails/InverseOf:
|
||||
|
|
@ -399,12 +397,6 @@ Rails/Presence:
|
|||
- 'lib/gitlab/git/hook.rb'
|
||||
- 'lib/gitlab/github_import/importer/releases_importer.rb'
|
||||
|
||||
# Offense count: 14
|
||||
# Cop supports --auto-correct.
|
||||
Rails/RedundantReceiverInWithOptions:
|
||||
Exclude:
|
||||
- 'config/initializers/doorkeeper_openid_connect.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# Configuration parameters: Include.
|
||||
# Include: db/migrate/*.rb
|
||||
|
|
@ -412,7 +404,7 @@ Rails/ReversibleMigration:
|
|||
Exclude:
|
||||
- 'db/migrate/20160824103857_drop_unused_ci_tables.rb'
|
||||
|
||||
# Offense count: 430
|
||||
# Offense count: 446
|
||||
# Configuration parameters: Blacklist.
|
||||
# Blacklist: decrement!, decrement_counter, increment!, increment_counter, toggle!, touch, update_all, update_attribute, update_column, update_columns, update_counters
|
||||
Rails/SkipsModelValidations:
|
||||
|
|
@ -439,7 +431,7 @@ Security/YAMLLoad:
|
|||
- 'spec/models/clusters/platforms/kubernetes_spec.rb'
|
||||
- 'spec/models/project_services/kubernetes_service_spec.rb'
|
||||
|
||||
# Offense count: 63
|
||||
# Offense count: 64
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: percent_q, bare_percent
|
||||
|
|
@ -506,7 +498,7 @@ Style/EmptyLiteral:
|
|||
- 'spec/requests/api/jobs_spec.rb'
|
||||
- 'spec/support/chat_slash_commands_shared_examples.rb'
|
||||
|
||||
# Offense count: 98
|
||||
# Offense count: 102
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: compact, expanded
|
||||
|
|
@ -523,35 +515,28 @@ Style/EvalWithLocation:
|
|||
Exclude:
|
||||
- 'app/models/service.rb'
|
||||
|
||||
# Offense count: 52
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: Autocorrect, EnforcedStyle.
|
||||
# SupportedStyles: module_function, extend_self
|
||||
Style/ExtendSelf:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 34
|
||||
# Offense count: 35
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: format, sprintf, percent
|
||||
Style/FormatString:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 371
|
||||
# Offense count: 384
|
||||
# Configuration parameters: MinBodyLength.
|
||||
Style/GuardClause:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 21
|
||||
# Offense count: 22
|
||||
Style/IfInsideElse:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 781
|
||||
# Offense count: 809
|
||||
# Cop supports --auto-correct.
|
||||
Style/IfUnlessModifier:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 71
|
||||
# Offense count: 75
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: line_count_dependent, lambda, literal
|
||||
|
|
@ -573,7 +558,7 @@ Style/LineEndConcatenation:
|
|||
Style/MethodCallWithoutArgsParentheses:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 17
|
||||
# Offense count: 18
|
||||
Style/MethodMissing:
|
||||
Enabled: false
|
||||
|
||||
|
|
@ -599,28 +584,28 @@ Style/MultilineIfModifier:
|
|||
- 'lib/api/commit_statuses.rb'
|
||||
- 'lib/gitlab/ci/trace.rb'
|
||||
|
||||
# Offense count: 23
|
||||
# Offense count: 25
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: Whitelist.
|
||||
# Whitelist: be, be_a, be_an, be_between, be_falsey, be_kind_of, be_instance_of, be_truthy, be_within, eq, eql, end_with, include, match, raise_error, respond_to, start_with
|
||||
Style/NestedParenthesizedCalls:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 20
|
||||
# Offense count: 19
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, MinBodyLength.
|
||||
# SupportedStyles: skip_modifier_ifs, always
|
||||
Style/Next:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 58
|
||||
# Offense count: 61
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedOctalStyle.
|
||||
# SupportedOctalStyles: zero_with_o, zero_only
|
||||
Style/NumericLiteralPrefix:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 112
|
||||
# Offense count: 114
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AutoCorrect, EnforcedStyle.
|
||||
# SupportedStyles: predicate, comparison
|
||||
|
|
@ -641,7 +626,7 @@ Style/OrAssignment:
|
|||
Style/ParallelAssignment:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 891
|
||||
# Offense count: 917
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: PreferredDelimiters.
|
||||
Style/PercentLiteralDelimiters:
|
||||
|
|
@ -663,14 +648,14 @@ Style/PerlBackrefs:
|
|||
- 'lib/gitlab/search_results.rb'
|
||||
- 'lib/gitlab/sherlock/query.rb'
|
||||
|
||||
# Offense count: 82
|
||||
# Offense count: 87
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: compact, exploded
|
||||
Style/RaiseArgs:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 8
|
||||
# Offense count: 9
|
||||
# Cop supports --auto-correct.
|
||||
Style/RedundantBegin:
|
||||
Exclude:
|
||||
|
|
@ -689,7 +674,7 @@ Style/RedundantConditional:
|
|||
Exclude:
|
||||
- 'lib/system_check/helpers.rb'
|
||||
|
||||
# Offense count: 58
|
||||
# Offense count: 57
|
||||
# Cop supports --auto-correct.
|
||||
Style/RedundantFreeze:
|
||||
Enabled: false
|
||||
|
|
@ -709,31 +694,33 @@ Style/RedundantReturn:
|
|||
- 'lib/gitlab/utils.rb'
|
||||
- 'lib/google_api/auth.rb'
|
||||
|
||||
# Offense count: 454
|
||||
# Offense count: 460
|
||||
# Cop supports --auto-correct.
|
||||
Style/RedundantSelf:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 140
|
||||
# Offense count: 142
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, AllowInnerSlashes.
|
||||
# SupportedStyles: slashes, percent_r, mixed
|
||||
Style/RegexpLiteral:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
EnforcedStyle: mixed
|
||||
AllowInnerSlashes: false
|
||||
|
||||
# Offense count: 35
|
||||
# Offense count: 36
|
||||
# Cop supports --auto-correct.
|
||||
Style/RescueModifier:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 105
|
||||
# Offense count: 107
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: implicit, explicit
|
||||
Style/RescueStandardError:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 91
|
||||
# Offense count: 92
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: ConvertCodeThatCanStartToReturnNil.
|
||||
Style/SafeNavigation:
|
||||
|
|
@ -778,7 +765,7 @@ Style/StderrPuts:
|
|||
Style/StringLiteralsInInterpolation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 99
|
||||
# Offense count: 106
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: IgnoredMethods.
|
||||
# IgnoredMethods: respond_to, define_method
|
||||
|
|
@ -837,7 +824,7 @@ Style/UnlessElse:
|
|||
- 'lib/tasks/gitlab/check.rake'
|
||||
- 'spec/features/issues/award_emoji_spec.rb'
|
||||
|
||||
# Offense count: 30
|
||||
# Offense count: 31
|
||||
# Cop supports --auto-correct.
|
||||
Style/UnneededInterpolation:
|
||||
Enabled: false
|
||||
|
|
@ -856,7 +843,7 @@ Style/ZeroLengthPredicate:
|
|||
- 'lib/extracts_path.rb'
|
||||
- 'lib/gitlab/git/repository.rb'
|
||||
|
||||
# Offense count: 22050
|
||||
# Offense count: 22840
|
||||
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
||||
# URISchemes: http, https
|
||||
Metrics/LineLength:
|
||||
|
|
|
|||
3527
CHANGELOG.md
|
|
@ -104,9 +104,13 @@ the remaining issues on the GitHub issue tracker.
|
|||
|
||||
## I want to contribute!
|
||||
|
||||
If you want to contribute to GitLab, [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight] is a great place to start. Issues with a lower weight (1 or 2) are deemed suitable for beginners.
|
||||
These issues will be of reasonable size and challenge, for anyone to start
|
||||
contributing to GitLab.
|
||||
If you want to contribute to GitLab [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight]
|
||||
is a great place to start. Issues with a lower weight (1 or 2) are deemed
|
||||
suitable for beginners. These issues will be of reasonable size and challenge,
|
||||
for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
|
||||
learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
|
||||
please consider we favor
|
||||
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
|
||||
|
||||
## Workflow labels
|
||||
|
||||
|
|
@ -167,7 +171,7 @@ Assigning a team label makes sure issues get the attention of the appropriate
|
|||
people.
|
||||
|
||||
The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Edge,
|
||||
~Geo, ~Gitaly, ~Platform, ~Prometheus, ~Release, and ~"UX".
|
||||
~Geo, ~Gitaly, ~Platform, ~Monitoring, ~Release, and ~"UX".
|
||||
|
||||
The descriptions on the [labels page][labels-page] explain what falls under the
|
||||
responsibility of each team.
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0.66.0
|
||||
0.77.0
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
5.10.3
|
||||
6.0.2
|
||||
|
|
|
|||
26
Gemfile
|
|
@ -70,6 +70,10 @@ gem 'net-ldap'
|
|||
# Git Wiki
|
||||
# Required manually in config/initializers/gollum.rb to control load order
|
||||
gem 'gollum-lib', '~> 4.2', require: false
|
||||
|
||||
# Before updating this gem, check if
|
||||
# https://github.com/gollum/rugged_adapter/pull/28 has been merged.
|
||||
# If it has, then remove the monkey patch for tree_entry in config/initializers/gollum.rb
|
||||
gem 'gollum-rugged_adapter', '~> 0.4.4', require: false
|
||||
|
||||
# Language detection
|
||||
|
|
@ -78,7 +82,7 @@ gem 'github-linguist', '~> 4.7.0', require: 'linguist'
|
|||
# API
|
||||
gem 'grape', '~> 1.0'
|
||||
gem 'grape-entity', '~> 0.6.0'
|
||||
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
|
||||
gem 'rack-cors', '~> 1.0.0', require: 'rack/cors'
|
||||
|
||||
# Disable strong_params so that Mash does not respond to :permitted?
|
||||
gem 'hashie-forbidden_attributes'
|
||||
|
|
@ -111,7 +115,7 @@ gem 'google-api-client', '~> 0.13.6'
|
|||
gem 'unf', '~> 0.1.4'
|
||||
|
||||
# Seed data
|
||||
gem 'seed-fu', '2.3.6' # Upgrade to > 2.3.7 once https://github.com/mbleigh/seed-fu/issues/123 is solved
|
||||
gem 'seed-fu', '~> 2.3.7'
|
||||
|
||||
# Markdown and HTML processing
|
||||
gem 'html-pipeline', '~> 1.11.0'
|
||||
|
|
@ -128,7 +132,7 @@ gem 'asciidoctor-plantuml', '0.0.7'
|
|||
gem 'rouge', '~> 2.0'
|
||||
gem 'truncato', '~> 0.7.9'
|
||||
gem 'bootstrap_form', '~> 2.7.0'
|
||||
gem 'nokogiri', '~> 1.8.1'
|
||||
gem 'nokogiri', '~> 1.8.2'
|
||||
|
||||
# Diffs
|
||||
gem 'diffy', '~> 3.1.0'
|
||||
|
|
@ -229,6 +233,9 @@ gem 'charlock_holmes', '~> 0.7.5'
|
|||
# Faster JSON
|
||||
gem 'oj', '~> 2.17.4'
|
||||
|
||||
# Faster blank
|
||||
gem 'fast_blank'
|
||||
|
||||
# Parse time & duration
|
||||
gem 'chronic', '~> 0.10.2'
|
||||
gem 'chronic_duration', '~> 0.10.6'
|
||||
|
|
@ -318,7 +325,7 @@ group :development, :test do
|
|||
gem 'spinach-rerun-reporter', '~> 0.0.2'
|
||||
gem 'rspec_profiling', '~> 0.0.5'
|
||||
gem 'rspec-set', '~> 0.1.3'
|
||||
gem 'rspec-parameterized'
|
||||
gem 'rspec-parameterized', require: false
|
||||
|
||||
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
|
||||
gem 'minitest', '~> 5.7.0'
|
||||
|
|
@ -334,10 +341,10 @@ group :development, :test do
|
|||
gem 'spring-commands-rspec', '~> 1.0.4'
|
||||
gem 'spring-commands-spinach', '~> 1.1.0'
|
||||
|
||||
gem 'gitlab-styles', '~> 2.2.0', require: false
|
||||
gem 'gitlab-styles', '~> 2.3', require: false
|
||||
# Pin these dependencies, otherwise a new rule could break the CI pipelines
|
||||
gem 'rubocop', '~> 0.52.0'
|
||||
gem 'rubocop-rspec', '~> 1.20.1'
|
||||
gem 'rubocop', '~> 0.52.1'
|
||||
gem 'rubocop-rspec', '~> 1.22.1'
|
||||
|
||||
gem 'scss_lint', '~> 0.56.0', require: false
|
||||
gem 'haml_lint', '~> 0.26.0', require: false
|
||||
|
|
@ -381,9 +388,6 @@ gem 'ruby-prof', '~> 0.16.2'
|
|||
# OAuth
|
||||
gem 'oauth2', '~> 1.4'
|
||||
|
||||
# Soft deletion
|
||||
gem 'paranoia', '~> 2.3.1'
|
||||
|
||||
# Health check
|
||||
gem 'health_check', '~> 2.6.0'
|
||||
|
||||
|
|
@ -402,7 +406,7 @@ group :ed25519 do
|
|||
end
|
||||
|
||||
# Gitaly GRPC client
|
||||
gem 'gitaly-proto', '~> 0.64.0', require: 'gitaly'
|
||||
gem 'gitaly-proto', '~> 0.83.0', require: 'gitaly'
|
||||
|
||||
gem 'toml-rb', '~> 0.3.15', require: false
|
||||
|
||||
|
|
|
|||
48
Gemfile.lock
|
|
@ -207,6 +207,7 @@ GEM
|
|||
faraday_middleware-multi_json (0.0.6)
|
||||
faraday_middleware
|
||||
multi_json
|
||||
fast_blank (1.0.0)
|
||||
fast_gettext (1.4.0)
|
||||
ffaker (2.4.0)
|
||||
ffi (1.9.18)
|
||||
|
|
@ -284,7 +285,7 @@ GEM
|
|||
po_to_json (>= 1.0.0)
|
||||
rails (>= 3.2.0)
|
||||
gherkin-ruby (0.3.2)
|
||||
gitaly-proto (0.64.0)
|
||||
gitaly-proto (0.83.0)
|
||||
google-protobuf (~> 3.1)
|
||||
grpc (~> 1.0)
|
||||
github-linguist (4.7.6)
|
||||
|
|
@ -303,7 +304,7 @@ GEM
|
|||
mime-types (>= 1.16)
|
||||
posix-spawn (~> 0.3)
|
||||
gitlab-markup (1.6.3)
|
||||
gitlab-styles (2.2.0)
|
||||
gitlab-styles (2.3.2)
|
||||
rubocop (~> 0.51)
|
||||
rubocop-gitlab-security (~> 0.1.0)
|
||||
rubocop-rspec (~> 1.19)
|
||||
|
|
@ -339,7 +340,9 @@ GEM
|
|||
mime-types (~> 3.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
google-protobuf (3.4.1.1)
|
||||
google-protobuf (3.5.1.1)
|
||||
googleapis-common-protos-types (1.0.1)
|
||||
google-protobuf (~> 3.0)
|
||||
googleauth (0.5.3)
|
||||
faraday (~> 0.12)
|
||||
jwt (~> 1.4)
|
||||
|
|
@ -366,9 +369,10 @@ GEM
|
|||
rake
|
||||
grape_logging (1.7.0)
|
||||
grape
|
||||
grpc (1.4.5)
|
||||
grpc (1.8.3)
|
||||
google-protobuf (~> 3.1)
|
||||
googleauth (~> 0.5.1)
|
||||
googleapis-common-protos-types (~> 1.0.0)
|
||||
googleauth (>= 0.5.1, < 0.7)
|
||||
haml (4.0.7)
|
||||
tilt
|
||||
haml_lint (0.26.0)
|
||||
|
|
@ -509,7 +513,7 @@ GEM
|
|||
net-ldap (0.16.0)
|
||||
net-ssh (4.1.0)
|
||||
netrc (0.11.0)
|
||||
nokogiri (1.8.1)
|
||||
nokogiri (1.8.2)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
numerizer (0.1.1)
|
||||
oauth (0.5.1)
|
||||
|
|
@ -579,9 +583,7 @@ GEM
|
|||
rubypants (~> 0.2)
|
||||
orm_adapter (0.5.0)
|
||||
os (0.9.6)
|
||||
parallel (1.12.0)
|
||||
paranoia (2.3.1)
|
||||
activerecord (>= 4.0, < 5.2)
|
||||
parallel (1.12.1)
|
||||
parser (2.4.0.2)
|
||||
ast (~> 2.3)
|
||||
parslet (1.5.0)
|
||||
|
|
@ -651,7 +653,7 @@ GEM
|
|||
rack (>= 0.4)
|
||||
rack-attack (4.4.1)
|
||||
rack
|
||||
rack-cors (0.4.0)
|
||||
rack-cors (1.0.2)
|
||||
rack-oauth2 (1.2.3)
|
||||
activesupport (>= 2.3)
|
||||
attr_required (>= 0.0.5)
|
||||
|
|
@ -784,7 +786,7 @@ GEM
|
|||
pg
|
||||
rails
|
||||
sqlite3
|
||||
rubocop (0.52.0)
|
||||
rubocop (0.52.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.4.0.2, < 3.0)
|
||||
powerpack (~> 0.1)
|
||||
|
|
@ -793,8 +795,8 @@ GEM
|
|||
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||
rubocop-gitlab-security (0.1.1)
|
||||
rubocop (>= 0.51)
|
||||
rubocop-rspec (1.20.1)
|
||||
rubocop (>= 0.51.0)
|
||||
rubocop-rspec (1.22.1)
|
||||
rubocop (>= 0.52.1)
|
||||
ruby-fogbugz (0.2.1)
|
||||
crack (~> 0.4)
|
||||
ruby-prof (0.16.2)
|
||||
|
|
@ -830,7 +832,7 @@ GEM
|
|||
rake (>= 0.9, < 13)
|
||||
sass (~> 3.5.3)
|
||||
securecompare (1.0.0)
|
||||
seed-fu (2.3.6)
|
||||
seed-fu (2.3.7)
|
||||
activerecord (>= 3.1)
|
||||
activesupport (>= 3.1)
|
||||
select2-rails (3.5.9.3)
|
||||
|
|
@ -1033,6 +1035,7 @@ DEPENDENCIES
|
|||
email_spec (~> 1.6.0)
|
||||
factory_bot_rails (~> 4.8.2)
|
||||
faraday (~> 0.12)
|
||||
fast_blank
|
||||
ffaker (~> 2.4)
|
||||
flay (~> 2.8.0)
|
||||
flipper (~> 0.11.0)
|
||||
|
|
@ -1053,11 +1056,11 @@ DEPENDENCIES
|
|||
gettext (~> 3.2.2)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.2.0)
|
||||
gitaly-proto (~> 0.64.0)
|
||||
gitaly-proto (~> 0.83.0)
|
||||
github-linguist (~> 4.7.0)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab-markup (~> 1.6.2)
|
||||
gitlab-styles (~> 2.2.0)
|
||||
gitlab-styles (~> 2.3)
|
||||
gitlab_omniauth-ldap (~> 2.0.4)
|
||||
gollum-lib (~> 4.2)
|
||||
gollum-rugged_adapter (~> 0.4.4)
|
||||
|
|
@ -1097,7 +1100,7 @@ DEPENDENCIES
|
|||
mysql2 (~> 0.4.10)
|
||||
net-ldap
|
||||
net-ssh (~> 4.1.0)
|
||||
nokogiri (~> 1.8.1)
|
||||
nokogiri (~> 1.8.2)
|
||||
oauth2 (~> 1.4)
|
||||
octokit (~> 4.6.2)
|
||||
oj (~> 2.17.4)
|
||||
|
|
@ -1117,7 +1120,6 @@ DEPENDENCIES
|
|||
omniauth-twitter (~> 1.2.0)
|
||||
omniauth_crowd (~> 2.2.0)
|
||||
org-ruby (~> 0.9.12)
|
||||
paranoia (~> 2.3.1)
|
||||
peek (~> 1.0.1)
|
||||
peek-gc (~> 0.0.2)
|
||||
peek-host (~> 1.0.0)
|
||||
|
|
@ -1133,7 +1135,7 @@ DEPENDENCIES
|
|||
pry-byebug (~> 3.4.1)
|
||||
pry-rails (~> 0.3.4)
|
||||
rack-attack (~> 4.4.1)
|
||||
rack-cors (~> 0.4.0)
|
||||
rack-cors (~> 1.0.0)
|
||||
rack-oauth2 (~> 1.2.1)
|
||||
rack-proxy (~> 0.6.0)
|
||||
rails (= 4.2.10)
|
||||
|
|
@ -1160,8 +1162,8 @@ DEPENDENCIES
|
|||
rspec-retry (~> 0.4.5)
|
||||
rspec-set (~> 0.1.3)
|
||||
rspec_profiling (~> 0.0.5)
|
||||
rubocop (~> 0.52.0)
|
||||
rubocop-rspec (~> 1.20.1)
|
||||
rubocop (~> 0.52.1)
|
||||
rubocop-rspec (~> 1.22.1)
|
||||
ruby-fogbugz (~> 0.2.1)
|
||||
ruby-prof (~> 0.16.2)
|
||||
ruby_parser (~> 3.8)
|
||||
|
|
@ -1170,7 +1172,7 @@ DEPENDENCIES
|
|||
sanitize (~> 2.0)
|
||||
sass-rails (~> 5.0.6)
|
||||
scss_lint (~> 0.56.0)
|
||||
seed-fu (= 2.3.6)
|
||||
seed-fu (~> 2.3.7)
|
||||
select2-rails (~> 3.5.9)
|
||||
selenium-webdriver (~> 3.5)
|
||||
sentry-raven (~> 2.5.3)
|
||||
|
|
@ -1212,4 +1214,4 @@ DEPENDENCIES
|
|||
wikicloth (= 0.8.1)
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.0
|
||||
1.16.1
|
||||
|
|
|
|||
30
PROCESS.md
|
|
@ -85,7 +85,8 @@ These types of merge requests for the upcoming release need special consideratio
|
|||
and a dedicated team with front-end, back-end, and UX.
|
||||
* **Small features**: any other feature request.
|
||||
|
||||
**Large features** must be with a maintainer **by the 1st**. This means that:
|
||||
It is strongly recommended that **large features** be with a maintainer **by the
|
||||
1st**. This means that:
|
||||
|
||||
* There is a merge request (even if it's WIP).
|
||||
* The person (or people, if it needs a frontend and backend maintainer) who will
|
||||
|
|
@ -100,14 +101,37 @@ The maintainer can also choose to assign a reviewer to perform an initial
|
|||
review, but this way the maintainer is unlikely to be surprised by receiving an
|
||||
MR later in the cycle.
|
||||
|
||||
**Small features** must be with a reviewer (not necessarily maintainer) **by the
|
||||
3rd**.
|
||||
It is strongly recommended that **small features** be with a reviewer (not
|
||||
necessarily a maintainer) **by the 3rd**.
|
||||
|
||||
Most merge requests from the community do not have a specific release
|
||||
target. However, if one does and falls into either of the above categories, it's
|
||||
the reviewer's responsibility to manage the above communication and assignment
|
||||
on behalf of the community member.
|
||||
|
||||
#### What happens if these deadlines are missed?
|
||||
|
||||
If a small or large feature is _not_ with a maintainer or reviewer by the
|
||||
recommended date, this does _not_ mean that maintainers or reviewers will refuse
|
||||
to review or merge it, or that the feature will definitely not make it in before
|
||||
the feature freeze.
|
||||
|
||||
However, with every day that passes without review, it will become more likely
|
||||
that the feature will slip, because maintainers and reviewers may not have
|
||||
enough time to do a thorough review, and developers may not have enough time to
|
||||
adequately address any feedback that may come back.
|
||||
|
||||
A maintainer or reviewer may also determine that it will not be possible to
|
||||
finish the current scope of the feature in time, but that it is possible to
|
||||
reduce the scope so that something can still ship this month, with the remaining
|
||||
scope moving to the next release. The sooner this decision is made, in
|
||||
conversation with the Product Manager and developer, the more time there is to
|
||||
extract that which is now out of scope, and to finish that which remains in scope.
|
||||
|
||||
For these reasons, it is strongly recommended to follow the guidelines above,
|
||||
to maximize the chances of your feature making it in before the feature freeze,
|
||||
and to prevent any last minute surprises.
|
||||
|
||||
### On the 7th
|
||||
|
||||
Merge requests should still be complete, following the
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"iconCount":189,"spriteSize":85766,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","bookmark","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder-o-open","folder-o","folder-open","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-external","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","podcast","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","staged","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","unstaged","user","users","volume-up","warning","work"]}
|
||||
{"iconCount":189,"spriteSize":85900,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","bookmark","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder-o","folder-open","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-external","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","podcast","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","staged","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_notfound","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","unstaged","user","users","volume-up","warning","work"]}
|
||||
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 80 80"><g fill="none" fill-rule="evenodd"><path fill="#EEE" d="M44.242 59.348c-3.7 1.576-7.3 1.994-10.902.84a7.002 7.002 0 0 1-9.085-.699l-4.243-4.243a7 7 0 0 1-.238-9.649c-.701-3.024-.419-6.083.646-9.206l-6.287-2.426a5.6 5.6 0 0 1-2.274-8.824l8.233-9.811a5.6 5.6 0 0 1 6.306-1.625l8.045 3.105c.772-.797 1.564-1.6 2.374-2.41C44.841 6.376 55.265 2.135 68.09 1.677a10 10 0 0 1 1.119.023c5.507.42 9.63 5.226 9.209 10.733-.935 12.225-5.373 22.309-13.315 30.25a410.76 410.76 0 0 1-1.661 1.653l3.247 8.412a5.6 5.6 0 0 1-1.625 6.306l-9.81 8.233a5.6 5.6 0 0 1-8.825-2.274l-2.186-5.665zm-22.92-26.923l10.406-12.402-6.822-2.633a1.6 1.6 0 0 0-1.801.464l-8.233 9.811a1.6 1.6 0 0 0 .65 2.521l5.8 2.239zm26.646 25.4l2.239 5.8a1.6 1.6 0 0 0 2.521.649l9.81-8.232a1.6 1.6 0 0 0 .465-1.802l-2.633-6.822-12.402 10.406zm-19.69-5.627c8.751 8.752 16.065 5.587 33.995-12.343 7.25-7.25 11.292-16.433 12.155-27.727a6 6 0 0 0-6.196-6.454c-11.846.423-21.303 4.271-28.586 11.554-17.03 17.03-20.414 25.924-11.368 34.97z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M52.54 28.376a4 4 0 1 0 5.656-5.657 4 4 0 0 0-5.657 5.657zm-2.83 2.829A8 8 0 1 1 61.025 19.89a8 8 0 0 1-11.313 11.314z"/><path fill="#FEE1D3" d="M15.063 54.54a2 2 0 0 1 0 2.828L3.749 68.68A2 2 0 1 1 .92 65.853l11.314-11.314a2 2 0 0 1 2.829 0zm9.899 9.899a2 2 0 0 1 0 2.828l-8.485 8.485a2 2 0 1 1-2.829-2.828l8.486-8.485a2 2 0 0 1 2.828 0z"/><path fill="#FDC4A8" d="M20.012 59.489a2 2 0 0 1 0 2.828L4.456 77.874a2 2 0 0 1-2.829-2.829L17.184 59.49a2 2 0 0 1 2.828 0z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 80 80"><g fill="none" fill-rule="evenodd" transform="translate(7 3)"><path fill="#EEE" fill-rule="nonzero" d="M54 18a2 2 0 1 1 0-4h4c.843 0 1.675.105 2.48.31a2 2 0 1 1-.99 3.876A6.015 6.015 0 0 0 58 18h-4zm9.735 4.228a2 2 0 0 1 3.822-1.18A10 10 0 0 1 68 24v3.513a2 2 0 1 1-4 0V24c0-.61-.09-1.204-.265-1.772zM64 35.513a2 2 0 1 1 4 0v6a2 2 0 1 1-4 0v-6zm0 14a2 2 0 1 1 4 0v6a2 2 0 1 1-4 0v-6zm0 14a2 2 0 1 1 4 0V66a9.97 9.97 0 0 1-.963 4.286 2 2 0 1 1-3.613-1.716A5.969 5.969 0 0 0 64 66v-2.487zm-5.255 8.441a2 2 0 1 1 .49 3.97c-.401.05-.806.075-1.218.076h-5.042a2 2 0 1 1 0-4h5.038c.246 0 .49-.016.732-.046zM44.975 72a2 2 0 1 1 0 4h-6a2 2 0 1 1 0-4h6zm-14 0a2 2 0 1 1 0 4H26c-.429 0-.855-.027-1.276-.08a2 2 0 0 1 .506-3.969c.254.033.51.049.77.049h4.975zm-10.438-3.514a2 2 0 1 1-3.64 1.66A9.97 9.97 0 0 1 16 66v-2.538a2 2 0 1 1 4 0V66c0 .871.185 1.713.537 2.486zM8 2a6 6 0 0 0-6 6v42a6 6 0 0 0 6 6h32a6 6 0 0 0 6-6V8a6 6 0 0 0-6-6H8zm0-4h32c5.523 0 10 4.477 10 10v42c0 5.523-4.477 10-10 10H8C2.477 60-2 55.523-2 50V8C-2 2.477 2.477-2 8-2z"/><rect width="10" height="4" x="8" y="16" fill="#EFEDF8" rx="2"/><rect width="10" height="4" x="21" y="16" fill="#6B4FBB" rx="2"/><rect width="10" height="4" x="8" y="32" fill="#E1DBF1" rx="2"/><rect width="6" height="4" x="34" y="16" fill="#EFEDF8" rx="2"/><rect width="6" height="4" x="8" y="24" fill="#6B4FBB" rx="2"/><rect width="6" height="4" x="17" y="24" fill="#EFEDF8" rx="2"/><rect width="6" height="4" x="21" y="32" fill="#6B4FBB" rx="2"/><rect width="6" height="4" x="8" y="40" fill="#6B4FBB" rx="2"/><rect width="6" height="4" x="17" y="40" fill="#EFEDF8" rx="2"/><rect width="6" height="4" x="26" y="40" fill="#C3B8E3" rx="2"/><rect width="10" height="4" x="26" y="24" fill="#C3B8E3" rx="2"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 80 80"><g fill="none" fill-rule="evenodd" transform="translate(0 3)"><path fill="#EEE" fill-rule="nonzero" d="M40.843 5.864a2 2 0 1 1 .348-3.985l5.977.523a2 2 0 1 1-.348 3.985l-5.977-.523zm13.946 1.22a2 2 0 1 1 .349-3.985l5.977.523a2 2 0 1 1-.348 3.985l-5.978-.523zm13.947 1.22a2 2 0 1 1 .349-3.984 11.952 11.952 0 0 1 6.655 2.75 2 2 0 1 1-2.569 3.066 7.953 7.953 0 0 0-4.435-1.832zm7.28 7.357a2 2 0 1 1 3.99-.301c.048.639.045 1.283-.01 1.934l-.385 4.4a2 2 0 1 1-3.985-.349l.384-4.395c.037-.433.039-.863.007-1.29zm-1.088 13.654a2 2 0 0 1 3.985.348l-.523 5.978a2 2 0 1 1-3.984-.349l.522-5.977zm-1.22 13.947a2 2 0 1 1 3.985.348l-.523 5.977a2 2 0 1 1-3.985-.348l.523-5.977zM72.305 56.7a2 2 0 0 1 3.79 1.282 11.995 11.995 0 0 1-4.253 5.81 2 2 0 0 1-2.373-3.22 7.996 7.996 0 0 0 2.836-3.872zm-9.054 5.33a2 2 0 1 1-.349 3.985l-5.977-.522a2 2 0 1 1 .349-3.985l5.977.523zM32.793 10.675a2 2 0 1 1-3.675-1.579 12.02 12.02 0 0 1 4.696-5.456 2 2 0 0 1 2.112 3.397 8.02 8.02 0 0 0-3.133 3.638z"/><rect width="48" height="58" x="2" y="14" fill="#FAFAFA" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M12 16a8 8 0 0 0-8 8v38a8 8 0 0 0 8 8h28a8 8 0 0 0 8-8V24a8 8 0 0 0-8-8H12zm0-4h28c6.627 0 12 5.373 12 12v38c0 6.627-5.373 12-12 12H12C5.373 74 0 68.627 0 62V24c0-6.627 5.373-12 12-12z"/><rect width="24" height="4" x="11" y="30" fill="#E5E5E5" rx="2"/><rect width="30" height="4" x="11" y="41" fill="#E5E5E5" rx="2"/><rect width="20" height="4" x="11" y="52" fill="#E5E5E5" rx="2"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="430" height="200" viewBox="0 0 430 200"><g fill="none" fill-rule="evenodd"><g transform="translate(138 65)"><path fill="#E5E5E5" fill-rule="nonzero" d="M35 70a2 2 0 1 1 0-4c2.542 0 5.042-.305 7.463-.904a2 2 0 1 1 .96 3.884A35.075 35.075 0 0 1 35 70zm18.21-5.105a2 2 0 1 1-2.083-3.414 31.143 31.143 0 0 0 5.896-4.664 2 2 0 1 1 2.842 2.815 35.143 35.143 0 0 1-6.654 5.263zM66.106 51.06a2 2 0 0 1-3.552-1.838 30.77 30.77 0 0 0 2.612-7.042 2 2 0 1 1 3.892.922 34.77 34.77 0 0 1-2.952 7.958zm3.816-18.433a2 2 0 1 1-3.991.268 30.873 30.873 0 0 0-1.407-7.38 2 2 0 0 1 3.808-1.223 34.873 34.873 0 0 1 1.59 8.335zm-6.346-17.842a2 2 0 0 1-3.264 2.312 31.188 31.188 0 0 0-5.054-5.564 2 2 0 0 1 2.615-3.027 35.188 35.188 0 0 1 5.703 6.279zM48.895 2.867a2 2 0 0 1-1.59 3.67 30.758 30.758 0 0 0-7.206-2.12 2 2 0 1 1 .653-3.946 34.758 34.758 0 0 1 8.143 2.396zM30.263.318a2 2 0 0 1 .537 3.964c-2.505.339-4.94.98-7.266 1.907a2 2 0 1 1-1.48-3.716A34.774 34.774 0 0 1 30.263.318zM12.907 7.853a2 2 0 0 1 2.527 3.1 31.196 31.196 0 0 0-5.213 5.416 2 2 0 0 1-3.196-2.406 35.196 35.196 0 0 1 5.882-6.11zM1.99 23.343a2 2 0 0 1 3.772 1.331 30.82 30.82 0 0 0-1.619 7.337 2 2 0 1 1-3.982-.38 34.82 34.82 0 0 1 1.829-8.289zM.719 42.086a2 2 0 1 1 3.917-.806 30.757 30.757 0 0 0 2.4 7.118 2 2 0 1 1-3.605 1.73 34.757 34.757 0 0 1-2.713-8.042zM9.393 58.86a2 2 0 0 1 2.926-2.728 31.167 31.167 0 0 0 5.751 4.841 2 2 0 1 1-2.187 3.349 35.167 35.167 0 0 1-6.49-5.462zm16.245 9.873a2 2 0 1 1 1.067-3.855 30.979 30.979 0 0 0 7.434 1.11 2 2 0 1 1-.11 3.998 34.979 34.979 0 0 1-8.391-1.253z"/><circle cx="35" cy="35" r="16" stroke="#E1DBF1" stroke-width="4"/><path fill="#6B4FBB" d="M37 33h5a2 2 0 1 1 0 4h-7a2 2 0 0 1-2-2v-8a2 2 0 1 1 4 0v6z"/></g><g transform="translate(247 30)"><rect width="116" height="135" y="5" fill="#F9F9F9" rx="10"/><rect width="116" height="134" x="5" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="10"/><g transform="translate(23 23)"><rect width="16" height="4" fill="#E1DBF1" rx="2"/><rect width="16" height="4" x="32" y="12" fill="#E1DBF1" rx="2"/><rect width="16" height="4" x="44" fill="#EEE" rx="2"/><rect width="16" height="4" x="12" y="24" fill="#E1DBF1" rx="2"/><rect width="16" height="4" x="64" y="36" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="20" fill="#FEE1D3" rx="2"/><rect width="8" height="4" x="32" y="36" fill="#FC6D26" rx="2"/><rect width="8" height="4" x="52" y="12" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="64" fill="#FEF0E8" rx="2"/><rect width="12" height="4" x="16" y="48" fill="#E1DBF1" rx="2"/><rect width="8" height="4" x="44" y="36" fill="#FC6D26" rx="2"/><rect width="4" height="4" x="56" y="36" fill="#E1DBF1" rx="2"/><rect width="4" height="4" x="64" y="60" fill="#E1DBF1" rx="2"/><rect width="4" height="4" x="72" y="60" fill="#FC6D26" rx="2"/><rect width="8" height="4" x="32" fill="#FC6D26" rx="2"/><rect width="28" height="4" y="36" fill="#EEE" rx="2"/><rect width="28" height="4" x="44" y="48" fill="#EEE" rx="2"/><rect width="28" height="4" x="32" y="60" fill="#EFEDF8" rx="2"/><rect width="28" height="4" y="12" fill="#6B4FBB" rx="2"/><rect width="28" height="4" x="32" y="24" fill="#C3B8E3" rx="2"/><rect width="8" height="4" y="24" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="32" y="48" fill="#6B4FBB" rx="2"/><rect width="12" height="4" y="48" fill="#FC6D26" rx="2"/><rect width="12" height="4" y="60" fill="#FEF0E8" rx="2"/><rect width="12" height="4" x="16" y="60" fill="#FEF0E8" rx="2"/></g><g transform="translate(23 95)"><rect width="16" height="4" fill="#EFEDF8" rx="2"/><rect width="16" height="4" x="18" y="12" fill="#FC6D26" rx="2"/><rect width="16" height="4" x="44" fill="#6B4FBB" rx="2"/><rect width="8" height="4" x="20" fill="#FEE1D3" rx="2"/><rect width="8" height="4" x="38" y="12" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="64" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="32" fill="#FC6D26" rx="2"/><rect width="14" height="4" y="12" fill="#EEE" rx="2"/></g></g><path fill="#FC6D26" fill-rule="nonzero" d="M81 119c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19zm0-4c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15zm-5-20a2 2 0 0 1 2 2v6a2 2 0 1 1-4 0v-6a2 2 0 0 1 2-2zm10 0a2 2 0 0 1 2 2v6a2 2 0 1 1-4 0v-6a2 2 0 0 1 2-2z"/><path fill="#E5E5E5" fill-rule="nonzero" d="M108 102c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2zm14 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2zm93 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2zm14 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 3.9 KiB |
|
|
@ -1,9 +1,9 @@
|
|||
import $ from 'jquery';
|
||||
import _ from 'underscore';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
|
||||
const Api = {
|
||||
groupsPath: '/api/:version/groups.json',
|
||||
groupPath: '/api/:version/groups/:id.json',
|
||||
groupPath: '/api/:version/groups/:id',
|
||||
namespacesPath: '/api/:version/namespaces.json',
|
||||
groupProjectsPath: '/api/:version/groups/:id/projects.json',
|
||||
projectsPath: '/api/:version/projects.json',
|
||||
|
|
@ -23,42 +23,44 @@ const Api = {
|
|||
group(groupId, callback) {
|
||||
const url = Api.buildUrl(Api.groupPath)
|
||||
.replace(':id', groupId);
|
||||
return $.ajax({
|
||||
url,
|
||||
dataType: 'json',
|
||||
})
|
||||
.done(group => callback(group));
|
||||
return axios.get(url)
|
||||
.then(({ data }) => {
|
||||
callback(data);
|
||||
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
// Return groups list. Filtered by query
|
||||
groups(query, options, callback) {
|
||||
const url = Api.buildUrl(Api.groupsPath);
|
||||
return $.ajax({
|
||||
url,
|
||||
data: Object.assign({
|
||||
return axios.get(url, {
|
||||
params: Object.assign({
|
||||
search: query,
|
||||
per_page: 20,
|
||||
}, options),
|
||||
dataType: 'json',
|
||||
})
|
||||
.done(groups => callback(groups));
|
||||
.then(({ data }) => {
|
||||
callback(data);
|
||||
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
// Return namespaces list. Filtered by query
|
||||
namespaces(query, callback) {
|
||||
const url = Api.buildUrl(Api.namespacesPath);
|
||||
return $.ajax({
|
||||
url,
|
||||
data: {
|
||||
return axios.get(url, {
|
||||
params: {
|
||||
search: query,
|
||||
per_page: 20,
|
||||
},
|
||||
dataType: 'json',
|
||||
}).done(namespaces => callback(namespaces));
|
||||
})
|
||||
.then(({ data }) => callback(data));
|
||||
},
|
||||
|
||||
// Return projects list. Filtered by query
|
||||
projects(query, options, callback) {
|
||||
projects(query, options, callback = _.noop) {
|
||||
const url = Api.buildUrl(Api.projectsPath);
|
||||
const defaults = {
|
||||
search: query,
|
||||
|
|
@ -70,12 +72,14 @@ const Api = {
|
|||
defaults.membership = true;
|
||||
}
|
||||
|
||||
return $.ajax({
|
||||
url,
|
||||
data: Object.assign(defaults, options),
|
||||
dataType: 'json',
|
||||
return axios.get(url, {
|
||||
params: Object.assign(defaults, options),
|
||||
})
|
||||
.done(projects => callback(projects));
|
||||
.then(({ data }) => {
|
||||
callback(data);
|
||||
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
// Return single project
|
||||
|
|
@ -97,41 +101,34 @@ const Api = {
|
|||
url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
|
||||
}
|
||||
|
||||
return $.ajax({
|
||||
url,
|
||||
type: 'POST',
|
||||
data: { label: data },
|
||||
dataType: 'json',
|
||||
return axios.post(url, {
|
||||
label: data,
|
||||
})
|
||||
.done(label => callback(label))
|
||||
.fail(message => callback(message.responseJSON));
|
||||
.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 $.ajax({
|
||||
url,
|
||||
data: {
|
||||
return axios.get(url, {
|
||||
params: {
|
||||
search: query,
|
||||
per_page: 20,
|
||||
},
|
||||
dataType: 'json',
|
||||
})
|
||||
.done(projects => callback(projects));
|
||||
.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));
|
||||
return this.wrapAjaxCall({
|
||||
url,
|
||||
type: 'POST',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
data: JSON.stringify(data),
|
||||
dataType: 'json',
|
||||
return axios.post(url, JSON.stringify(data), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -140,65 +137,57 @@ const Api = {
|
|||
.replace(':id', encodeURIComponent(id))
|
||||
.replace(':branch', branch);
|
||||
|
||||
return this.wrapAjaxCall({
|
||||
url,
|
||||
type: 'GET',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
dataType: 'json',
|
||||
});
|
||||
return axios.get(url);
|
||||
},
|
||||
|
||||
// Return text for a specific license
|
||||
licenseText(key, data, callback) {
|
||||
const url = Api.buildUrl(Api.licensePath)
|
||||
.replace(':key', key);
|
||||
return $.ajax({
|
||||
url,
|
||||
data,
|
||||
return axios.get(url, {
|
||||
params: data,
|
||||
})
|
||||
.done(license => callback(license));
|
||||
.then(res => callback(res.data));
|
||||
},
|
||||
|
||||
gitignoreText(key, callback) {
|
||||
const url = Api.buildUrl(Api.gitignorePath)
|
||||
.replace(':key', key);
|
||||
return $.get(url, gitignore => callback(gitignore));
|
||||
return axios.get(url)
|
||||
.then(({ data }) => callback(data));
|
||||
},
|
||||
|
||||
gitlabCiYml(key, callback) {
|
||||
const url = Api.buildUrl(Api.gitlabCiYmlPath)
|
||||
.replace(':key', key);
|
||||
return $.get(url, file => callback(file));
|
||||
return axios.get(url)
|
||||
.then(({ data }) => callback(data));
|
||||
},
|
||||
|
||||
dockerfileYml(key, callback) {
|
||||
const url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
|
||||
$.get(url, callback);
|
||||
return axios.get(url)
|
||||
.then(({ data }) => callback(data));
|
||||
},
|
||||
|
||||
issueTemplate(namespacePath, projectPath, key, type, callback) {
|
||||
const url = Api.buildUrl(Api.issuableTemplatePath)
|
||||
.replace(':key', key)
|
||||
.replace(':key', encodeURIComponent(key))
|
||||
.replace(':type', type)
|
||||
.replace(':project_path', projectPath)
|
||||
.replace(':namespace_path', namespacePath);
|
||||
$.ajax({
|
||||
url,
|
||||
dataType: 'json',
|
||||
})
|
||||
.done(file => callback(null, file))
|
||||
.fail(callback);
|
||||
return axios.get(url)
|
||||
.then(({ data }) => callback(null, data))
|
||||
.catch(callback);
|
||||
},
|
||||
|
||||
users(query, options) {
|
||||
const url = Api.buildUrl(this.usersPath);
|
||||
return Api.wrapAjaxCall({
|
||||
url,
|
||||
data: Object.assign({
|
||||
return axios.get(url, {
|
||||
params: Object.assign({
|
||||
search: query,
|
||||
per_page: 20,
|
||||
}, options),
|
||||
dataType: 'json',
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -209,20 +198,6 @@ const Api = {
|
|||
}
|
||||
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);
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default Api;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
import _ from 'underscore';
|
||||
import Cookies from 'js-cookie';
|
||||
import { __ } from './locale';
|
||||
import { isInIssuePage, updateTooltipTitle } from './lib/utils/common_utils';
|
||||
import Flash from './flash';
|
||||
import flash from './flash';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
|
||||
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
|
||||
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
|
||||
|
|
@ -441,13 +443,15 @@ class AwardsHandler {
|
|||
if (this.isUserAuthored($emojiButton)) {
|
||||
this.userAuthored($emojiButton);
|
||||
} else {
|
||||
$.post(awardUrl, {
|
||||
axios.post(awardUrl, {
|
||||
name: emoji,
|
||||
}, (data) => {
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.ok) {
|
||||
callback();
|
||||
}
|
||||
}).fail(() => new Flash('Something went wrong on our end.'));
|
||||
})
|
||||
.catch(() => flash(__('Something went wrong on our end.')));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -299,6 +299,13 @@ const gfmRules = {
|
|||
|
||||
export class CopyAsGFM {
|
||||
constructor() {
|
||||
// iOS currently does not support clipboardData.setData(). This bug should
|
||||
// be fixed in iOS 12, but for now we'll disable this for all iOS browsers
|
||||
// ref: https://trac.webkit.org/changeset/222228/webkit
|
||||
const userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) || '';
|
||||
const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
|
||||
if (isIOS) return;
|
||||
|
||||
$(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);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import installGlEmojiElement from './gl_emoji';
|
|||
import './quick_submit';
|
||||
import './requires_input';
|
||||
import './toggler_behavior';
|
||||
import '../preview_markdown';
|
||||
|
||||
installGlEmojiElement();
|
||||
initCopyAsGFM();
|
||||
|
|
|
|||
|
|
@ -2,18 +2,19 @@ import { n__ } from '../locale';
|
|||
import { convertPermissionToBoolean } from '../lib/utils/common_utils';
|
||||
|
||||
export default class SecretValues {
|
||||
constructor(container) {
|
||||
constructor({
|
||||
container,
|
||||
valueSelector = '.js-secret-value',
|
||||
placeholderSelector = '.js-secret-value-placeholder',
|
||||
}) {
|
||||
this.container = container;
|
||||
this.valueSelector = valueSelector;
|
||||
this.placeholderSelector = placeholderSelector;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.values = this.container.querySelectorAll('.js-secret-value');
|
||||
this.placeholders = this.container.querySelectorAll('.js-secret-value-placeholder');
|
||||
this.revealButton = this.container.querySelector('.js-secret-value-reveal-button');
|
||||
|
||||
this.revealText = n__('Reveal value', 'Reveal values', this.values.length);
|
||||
this.hideText = n__('Hide value', 'Hide values', this.values.length);
|
||||
|
||||
const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus);
|
||||
this.updateDom(isRevealed);
|
||||
|
||||
|
|
@ -28,15 +29,17 @@ export default class SecretValues {
|
|||
}
|
||||
|
||||
updateDom(isRevealed) {
|
||||
this.values.forEach((value) => {
|
||||
const values = this.container.querySelectorAll(this.valueSelector);
|
||||
values.forEach((value) => {
|
||||
value.classList.toggle('hide', !isRevealed);
|
||||
});
|
||||
|
||||
this.placeholders.forEach((placeholder) => {
|
||||
const placeholders = this.container.querySelectorAll(this.placeholderSelector);
|
||||
placeholders.forEach((placeholder) => {
|
||||
placeholder.classList.toggle('hide', isRevealed);
|
||||
});
|
||||
|
||||
this.revealButton.textContent = isRevealed ? this.hideText : this.revealText;
|
||||
this.revealButton.textContent = isRevealed ? n__('Hide value', 'Hide values', values.length) : n__('Reveal value', 'Reveal values', values.length);
|
||||
this.revealButton.dataset.secretRevealStatus = isRevealed;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import { visitUrl } from '../lib/utils/url_utility';
|
|||
import { HIDDEN_CLASS } from '../lib/utils/constants';
|
||||
import csrf from '../lib/utils/csrf';
|
||||
|
||||
Dropzone.autoDiscover = false;
|
||||
|
||||
function toggleLoading($el, $icon, loading) {
|
||||
if (loading) {
|
||||
$el.disable();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ export default () => {
|
|||
|
||||
new Vue({
|
||||
el,
|
||||
components: {
|
||||
notebookLab,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
error: false,
|
||||
|
|
@ -16,8 +19,41 @@ export default () => {
|
|||
json: {},
|
||||
};
|
||||
},
|
||||
components: {
|
||||
notebookLab,
|
||||
mounted() {
|
||||
if (gon.katex_css_url) {
|
||||
const katexStyles = document.createElement('link');
|
||||
katexStyles.setAttribute('rel', 'stylesheet');
|
||||
katexStyles.setAttribute('href', gon.katex_css_url);
|
||||
document.head.appendChild(katexStyles);
|
||||
}
|
||||
|
||||
if (gon.katex_js_url) {
|
||||
const katexScript = document.createElement('script');
|
||||
katexScript.addEventListener('load', () => {
|
||||
this.loadFile();
|
||||
});
|
||||
katexScript.setAttribute('src', gon.katex_js_url);
|
||||
document.head.appendChild(katexScript);
|
||||
} else {
|
||||
this.loadFile();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadFile() {
|
||||
axios.get(el.dataset.endpoint)
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
this.json = data;
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e.status !== 200) {
|
||||
this.loadError = true;
|
||||
}
|
||||
|
||||
this.error = true;
|
||||
});
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div class="container-fluid md prepend-top-default append-bottom-default">
|
||||
|
|
@ -46,41 +82,5 @@ export default () => {
|
|||
</p>
|
||||
</div>
|
||||
`,
|
||||
methods: {
|
||||
loadFile() {
|
||||
axios.get(el.dataset.endpoint)
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
this.json = data;
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e.status !== 200) {
|
||||
this.loadError = true;
|
||||
}
|
||||
|
||||
this.error = true;
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (gon.katex_css_url) {
|
||||
const katexStyles = document.createElement('link');
|
||||
katexStyles.setAttribute('rel', 'stylesheet');
|
||||
katexStyles.setAttribute('href', gon.katex_css_url);
|
||||
document.head.appendChild(katexStyles);
|
||||
}
|
||||
|
||||
if (gon.katex_js_url) {
|
||||
const katexScript = document.createElement('script');
|
||||
katexScript.addEventListener('load', () => {
|
||||
this.loadFile();
|
||||
});
|
||||
katexScript.setAttribute('src', gon.katex_js_url);
|
||||
document.head.appendChild(katexScript);
|
||||
} else {
|
||||
this.loadFile();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ export default () => {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
components: {
|
||||
pdfLab,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
error: false,
|
||||
|
|
@ -15,9 +18,6 @@ export default () => {
|
|||
pdf: el.dataset.endpoint,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
pdfLab,
|
||||
},
|
||||
methods: {
|
||||
onLoad() {
|
||||
this.loading = false;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import Flash from '../../flash';
|
||||
import { handleLocationHash } from '../../lib/utils/common_utils';
|
||||
import axios from '../../lib/utils/axios_utils';
|
||||
|
||||
export default class BlobViewer {
|
||||
constructor() {
|
||||
|
|
@ -127,25 +128,18 @@ export default class BlobViewer {
|
|||
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;
|
||||
}
|
||||
if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) {
|
||||
return Promise.resolve(viewer);
|
||||
}
|
||||
|
||||
viewer.setAttribute('data-loading', 'true');
|
||||
viewer.setAttribute('data-loading', 'true');
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
dataType: 'JSON',
|
||||
})
|
||||
.fail(reject)
|
||||
.done((data) => {
|
||||
return axios.get(url)
|
||||
.then(({ data }) => {
|
||||
viewer.innerHTML = data.html;
|
||||
viewer.setAttribute('data-loaded', 'true');
|
||||
|
||||
resolve(viewer);
|
||||
return viewer;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
/* global ace */
|
||||
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import TemplateSelectorMediator from '../blob/file_template_mediator';
|
||||
|
||||
export default class EditBlob {
|
||||
|
|
@ -56,12 +59,14 @@ export default class EditBlob {
|
|||
|
||||
if (paneId === '#preview') {
|
||||
this.$toggleButton.hide();
|
||||
return $.post(currentLink.data('preview-url'), {
|
||||
axios.post(currentLink.data('preview-url'), {
|
||||
content: this.editor.getValue(),
|
||||
}, (response) => {
|
||||
currentPane.empty().append(response);
|
||||
return currentPane.renderGFM();
|
||||
});
|
||||
})
|
||||
.then(({ data }) => {
|
||||
currentPane.empty().append(data);
|
||||
currentPane.renderGFM();
|
||||
})
|
||||
.catch(() => createFlash(__('An error occurred previewing the blob')));
|
||||
}
|
||||
|
||||
this.$toggleButton.show();
|
||||
|
|
|
|||
|
|
@ -171,19 +171,14 @@ $(() => {
|
|||
});
|
||||
|
||||
gl.IssueBoardsModalAddBtn = new Vue({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
el: document.getElementById('js-add-issues-btn'),
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
data() {
|
||||
return {
|
||||
modal: ModalStore.store,
|
||||
store: Store.state,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
disabled() {
|
||||
this.updateTooltip();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
disabled() {
|
||||
if (!this.store) {
|
||||
|
|
@ -199,6 +194,14 @@ $(() => {
|
|||
return '';
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
disabled() {
|
||||
this.updateTooltip();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.updateTooltip();
|
||||
},
|
||||
methods: {
|
||||
updateTooltip() {
|
||||
const $tooltip = $(this.$refs.addIssuesButton);
|
||||
|
|
@ -217,9 +220,6 @@ $(() => {
|
|||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.updateTooltip();
|
||||
},
|
||||
template: `
|
||||
<div class="board-extra-actions">
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable comma-dangle, space-before-function-paren, one-var */
|
||||
/* global Sortable */
|
||||
import Sortable from 'vendor/Sortable';
|
||||
import Vue from 'vue';
|
||||
import AccessorUtilities from '../../lib/utils/accessor';
|
||||
import boardList from './board_list';
|
||||
|
|
|
|||
|
|
@ -10,12 +10,30 @@ export default {
|
|||
'issue-card-inner': gl.issueBoards.IssueCardInner,
|
||||
},
|
||||
props: {
|
||||
list: Object,
|
||||
issue: Object,
|
||||
issueLinkBase: String,
|
||||
disabled: Boolean,
|
||||
index: Number,
|
||||
rootPath: String,
|
||||
list: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
issue: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
issueLinkBase: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
rootPath: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -54,8 +72,13 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<li class="card"
|
||||
:class="{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }"
|
||||
<li
|
||||
class="card"
|
||||
:class="{
|
||||
'user-can-drag': !disabled && issue.id,
|
||||
'is-disabled': disabled || !issue.id,
|
||||
'is-active': issueDetailVisible
|
||||
}"
|
||||
:index="index"
|
||||
:data-issue-id="issue.id"
|
||||
@mousedown="mouseDown"
|
||||
|
|
@ -66,6 +89,7 @@ export default {
|
|||
:issue="issue"
|
||||
:issue-link-base="issueLinkBase"
|
||||
:root-path="rootPath"
|
||||
:update-filters="true" />
|
||||
:update-filters="true"
|
||||
/>
|
||||
</li>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* global Sortable */
|
||||
import Sortable from 'vendor/Sortable';
|
||||
import boardNewIssue from './board_new_issue';
|
||||
import boardCard from './board_card.vue';
|
||||
import eventHub from '../eventhub';
|
||||
|
|
@ -187,7 +187,7 @@ export default {
|
|||
<li
|
||||
class="board-list-count text-center"
|
||||
v-if="showCount"
|
||||
data-id="-1">
|
||||
data-issue-id="-1">
|
||||
|
||||
<loading-icon
|
||||
v-show="list.loadingMore"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export default (path, extraData) => path.split('&').reduce((dataParam, filterPar
|
|||
const paramSplit = filterParam.split('=');
|
||||
const paramKeyNormalized = paramSplit[0].replace('[]', '');
|
||||
const isArray = paramSplit[0].indexOf('[]');
|
||||
const value = decodeURIComponent(paramSplit[1]).replace(/\+/g, ' ');
|
||||
const value = decodeURIComponent(paramSplit[1].replace(/\+/g, ' '));
|
||||
|
||||
if (isArray !== -1) {
|
||||
if (!data[paramKeyNormalized]) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
import ClustersService from './services/clusters_service';
|
||||
import ClustersStore from './stores/clusters_store';
|
||||
import applications from './components/applications.vue';
|
||||
import setupToggleButtons from '../toggle_buttons';
|
||||
|
||||
/**
|
||||
* Cluster page has 2 separate parts:
|
||||
|
|
@ -48,12 +49,9 @@ export default class Clusters {
|
|||
installPrometheusEndpoint: installPrometheusPath,
|
||||
});
|
||||
|
||||
this.toggle = this.toggle.bind(this);
|
||||
this.installApplication = this.installApplication.bind(this);
|
||||
this.showToken = this.showToken.bind(this);
|
||||
|
||||
this.toggleButton = document.querySelector('.js-toggle-cluster');
|
||||
this.toggleInput = document.querySelector('.js-toggle-input');
|
||||
this.errorContainer = document.querySelector('.js-cluster-error');
|
||||
this.successContainer = document.querySelector('.js-cluster-success');
|
||||
this.creatingContainer = document.querySelector('.js-cluster-creating');
|
||||
|
|
@ -63,6 +61,7 @@ export default class Clusters {
|
|||
this.tokenField = document.querySelector('.js-cluster-token');
|
||||
|
||||
initSettingsPanels();
|
||||
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
|
||||
this.initApplications();
|
||||
|
||||
if (this.store.state.status !== 'created') {
|
||||
|
|
@ -101,13 +100,11 @@ export default class Clusters {
|
|||
}
|
||||
|
||||
addListeners() {
|
||||
this.toggleButton.addEventListener('click', this.toggle);
|
||||
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
|
||||
eventHub.$on('installApplication', this.installApplication);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
this.toggleButton.removeEventListener('click', this.toggle);
|
||||
if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken);
|
||||
eventHub.$off('installApplication', this.installApplication);
|
||||
}
|
||||
|
|
@ -151,11 +148,6 @@ export default class Clusters {
|
|||
this.updateContainer(prevStatus, this.store.state.status, this.store.state.statusReason);
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.toggleButton.classList.toggle('is-checked');
|
||||
this.toggleInput.setAttribute('value', this.toggleButton.classList.contains('is-checked').toString());
|
||||
}
|
||||
|
||||
showToken() {
|
||||
const type = this.tokenField.getAttribute('type');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,58 +1,20 @@
|
|||
import Flash from '../flash';
|
||||
import { s__ } from '../locale';
|
||||
import setupToggleButtons from '../toggle_buttons';
|
||||
import ClustersService from './services/clusters_service';
|
||||
/**
|
||||
* Toggles loading and disabled classes.
|
||||
* @param {HTMLElement} button
|
||||
*/
|
||||
const toggleLoadingButton = (button) => {
|
||||
if (button.getAttribute('disabled')) {
|
||||
button.removeAttribute('disabled');
|
||||
} else {
|
||||
button.setAttribute('disabled', true);
|
||||
|
||||
export default () => {
|
||||
const clusterList = document.querySelector('.js-clusters-list');
|
||||
// The empty state won't have a clusterList
|
||||
if (clusterList) {
|
||||
setupToggleButtons(
|
||||
document.querySelector('.js-clusters-list'),
|
||||
(value, toggle) =>
|
||||
ClustersService.updateCluster(toggle.dataset.endpoint, { cluster: { enabled: value } })
|
||||
.catch((err) => {
|
||||
Flash(s__('ClusterIntegration|Something went wrong on our end.'));
|
||||
throw err;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
button.classList.toggle('is-loading');
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles checked class for the given button
|
||||
* @param {HTMLElement} button
|
||||
*/
|
||||
const toggleValue = (button) => {
|
||||
button.classList.toggle('is-checked');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles toggle buttons in the cluster's table.
|
||||
*
|
||||
* When the user clicks the toggle button for each cluster, it:
|
||||
* - toggles the button
|
||||
* - shows a loading and disables button
|
||||
* - Makes a put request to the given endpoint
|
||||
* Once we receive the response, either:
|
||||
* 1) Show updated status in case of successfull response
|
||||
* 2) Show initial status in case of failed response
|
||||
*/
|
||||
export default function setClusterTableToggles() {
|
||||
document.querySelectorAll('.js-toggle-cluster-list')
|
||||
.forEach(button => button.addEventListener('click', (e) => {
|
||||
const toggleButton = e.currentTarget;
|
||||
const endpoint = toggleButton.getAttribute('data-endpoint');
|
||||
|
||||
toggleValue(toggleButton);
|
||||
toggleLoadingButton(toggleButton);
|
||||
|
||||
const value = toggleButton.classList.contains('is-checked');
|
||||
|
||||
ClustersService.updateCluster(endpoint, { cluster: { enabled: value } })
|
||||
.then(() => {
|
||||
toggleLoadingButton(toggleButton);
|
||||
})
|
||||
.catch(() => {
|
||||
toggleLoadingButton(toggleButton);
|
||||
toggleValue(toggleButton);
|
||||
Flash(s__('ClusterIntegration|Something went wrong on our end.'));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,108 +1,112 @@
|
|||
<script>
|
||||
import { s__, sprintf } from '../../locale';
|
||||
import eventHub from '../event_hub';
|
||||
import loadingButton from '../../vue_shared/components/loading_button.vue';
|
||||
import {
|
||||
APPLICATION_NOT_INSTALLABLE,
|
||||
APPLICATION_SCHEDULED,
|
||||
APPLICATION_INSTALLABLE,
|
||||
APPLICATION_INSTALLING,
|
||||
APPLICATION_INSTALLED,
|
||||
APPLICATION_ERROR,
|
||||
REQUEST_LOADING,
|
||||
REQUEST_SUCCESS,
|
||||
REQUEST_FAILURE,
|
||||
} from '../constants';
|
||||
/* eslint-disable vue/require-default-prop */
|
||||
import { s__, sprintf } from '../../locale';
|
||||
import eventHub from '../event_hub';
|
||||
import loadingButton from '../../vue_shared/components/loading_button.vue';
|
||||
import {
|
||||
APPLICATION_NOT_INSTALLABLE,
|
||||
APPLICATION_SCHEDULED,
|
||||
APPLICATION_INSTALLABLE,
|
||||
APPLICATION_INSTALLING,
|
||||
APPLICATION_INSTALLED,
|
||||
APPLICATION_ERROR,
|
||||
REQUEST_LOADING,
|
||||
REQUEST_SUCCESS,
|
||||
REQUEST_FAILURE,
|
||||
} from '../constants';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
export default {
|
||||
components: {
|
||||
loadingButton,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
titleLink: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
statusReason: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
requestStatus: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
requestReason: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
titleLink: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
statusReason: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
requestStatus: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
requestReason: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
loadingButton,
|
||||
},
|
||||
computed: {
|
||||
rowJsClass() {
|
||||
return `js-cluster-application-row-${this.id}`;
|
||||
},
|
||||
installButtonLoading() {
|
||||
return !this.status ||
|
||||
this.status === APPLICATION_SCHEDULED ||
|
||||
this.status === APPLICATION_INSTALLING ||
|
||||
this.requestStatus === REQUEST_LOADING;
|
||||
},
|
||||
installButtonDisabled() {
|
||||
// Avoid the potential for the real-time data to say APPLICATION_INSTALLABLE but
|
||||
// we already made a request to install and are just waiting for the real-time
|
||||
// to sync up.
|
||||
return (this.status !== APPLICATION_INSTALLABLE && this.status !== APPLICATION_ERROR) ||
|
||||
this.requestStatus === REQUEST_LOADING ||
|
||||
this.requestStatus === REQUEST_SUCCESS;
|
||||
},
|
||||
installButtonLabel() {
|
||||
let label;
|
||||
if (
|
||||
this.status === APPLICATION_NOT_INSTALLABLE ||
|
||||
this.status === APPLICATION_INSTALLABLE ||
|
||||
this.status === APPLICATION_ERROR
|
||||
) {
|
||||
label = s__('ClusterIntegration|Install');
|
||||
} else if (this.status === APPLICATION_SCHEDULED || this.status === APPLICATION_INSTALLING) {
|
||||
label = s__('ClusterIntegration|Installing');
|
||||
} else if (this.status === APPLICATION_INSTALLED) {
|
||||
label = s__('ClusterIntegration|Installed');
|
||||
}
|
||||
computed: {
|
||||
rowJsClass() {
|
||||
return `js-cluster-application-row-${this.id}`;
|
||||
},
|
||||
installButtonLoading() {
|
||||
return !this.status ||
|
||||
this.status === APPLICATION_SCHEDULED ||
|
||||
this.status === APPLICATION_INSTALLING ||
|
||||
this.requestStatus === REQUEST_LOADING;
|
||||
},
|
||||
installButtonDisabled() {
|
||||
// Avoid the potential for the real-time data to say APPLICATION_INSTALLABLE but
|
||||
// we already made a request to install and are just waiting for the real-time
|
||||
// to sync up.
|
||||
return (this.status !== APPLICATION_INSTALLABLE
|
||||
&& this.status !== APPLICATION_ERROR) ||
|
||||
this.requestStatus === REQUEST_LOADING ||
|
||||
this.requestStatus === REQUEST_SUCCESS;
|
||||
},
|
||||
installButtonLabel() {
|
||||
let label;
|
||||
if (
|
||||
this.status === APPLICATION_NOT_INSTALLABLE ||
|
||||
this.status === APPLICATION_INSTALLABLE ||
|
||||
this.status === APPLICATION_ERROR
|
||||
) {
|
||||
label = s__('ClusterIntegration|Install');
|
||||
} else if (this.status === APPLICATION_SCHEDULED ||
|
||||
this.status === APPLICATION_INSTALLING) {
|
||||
label = s__('ClusterIntegration|Installing');
|
||||
} else if (this.status === APPLICATION_INSTALLED) {
|
||||
label = s__('ClusterIntegration|Installed');
|
||||
}
|
||||
|
||||
return label;
|
||||
return label;
|
||||
},
|
||||
hasError() {
|
||||
return this.status === APPLICATION_ERROR ||
|
||||
this.requestStatus === REQUEST_FAILURE;
|
||||
},
|
||||
generalErrorDescription() {
|
||||
return sprintf(
|
||||
s__('ClusterIntegration|Something went wrong while installing %{title}'), {
|
||||
title: this.title,
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
hasError() {
|
||||
return this.status === APPLICATION_ERROR || this.requestStatus === REQUEST_FAILURE;
|
||||
methods: {
|
||||
installClicked() {
|
||||
eventHub.$emit('installApplication', this.id);
|
||||
},
|
||||
},
|
||||
generalErrorDescription() {
|
||||
return sprintf(
|
||||
s__('ClusterIntegration|Something went wrong while installing %{title}'), {
|
||||
title: this.title,
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
installClicked() {
|
||||
eventHub.$emit('installApplication', this.id);
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -1,84 +1,93 @@
|
|||
<script>
|
||||
import _ from 'underscore';
|
||||
import { s__, sprintf } from '../../locale';
|
||||
import applicationRow from './application_row.vue';
|
||||
import _ from 'underscore';
|
||||
import { s__, sprintf } from '../../locale';
|
||||
import applicationRow from './application_row.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
applications: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
export default {
|
||||
components: {
|
||||
applicationRow,
|
||||
},
|
||||
helpPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
props: {
|
||||
applications: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
helpPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
applicationRow,
|
||||
},
|
||||
computed: {
|
||||
generalApplicationDescription() {
|
||||
return sprintf(
|
||||
_.escape(s__('ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}')), {
|
||||
helpLink: `<a href="${this.helpPath}">
|
||||
${_.escape(s__('ClusterIntegration|installing applications'))}
|
||||
</a>`,
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
helmTillerDescription() {
|
||||
return _.escape(s__(
|
||||
`ClusterIntegration|Helm streamlines installing and managing Kubernets applications.
|
||||
Tiller runs inside of your Kubernetes Cluster, and manages
|
||||
releases of your charts.`,
|
||||
));
|
||||
},
|
||||
ingressDescription() {
|
||||
const descriptionParagraph = _.escape(s__(
|
||||
`ClusterIntegration|Ingress gives you a way to route requests to services based on the
|
||||
request host or path, centralizing a number of services into a single entrypoint.`,
|
||||
));
|
||||
computed: {
|
||||
generalApplicationDescription() {
|
||||
return sprintf(
|
||||
_.escape(s__(`ClusterIntegration|Install applications on your cluster.
|
||||
Read more about %{helpLink}`)),
|
||||
{
|
||||
helpLink: `<a href="${this.helpPath}">
|
||||
${_.escape(s__('ClusterIntegration|installing applications'))}
|
||||
</a>`,
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
helmTillerDescription() {
|
||||
return _.escape(s__(
|
||||
`ClusterIntegration|Helm streamlines installing and managing Kubernets applications.
|
||||
Tiller runs inside of your Kubernetes Cluster, and manages
|
||||
releases of your charts.`,
|
||||
));
|
||||
},
|
||||
ingressDescription() {
|
||||
const descriptionParagraph = _.escape(s__(
|
||||
`ClusterIntegration|Ingress gives you a way to route requests to services based on the
|
||||
request host or path, centralizing a number of services into a single entrypoint.`,
|
||||
));
|
||||
|
||||
const extraCostParagraph = sprintf(
|
||||
_.escape(s__('ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}')), {
|
||||
boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
|
||||
pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
|
||||
${_.escape(s__('ClusterIntegration|GKE pricing'))}
|
||||
</a>`,
|
||||
},
|
||||
false,
|
||||
);
|
||||
const extraCostParagraph = sprintf(
|
||||
_.escape(s__(
|
||||
`ClusterIntegration|%{boldNotice} This will add some extra resources
|
||||
like a load balancer, which may incur additional costs depending on
|
||||
the hosting provider Kubernetes is installed on. If you are using GKE,
|
||||
you can %{pricingLink}.`,
|
||||
)), {
|
||||
boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
|
||||
pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
|
||||
${_.escape(s__('ClusterIntegration|check the pricing here'))}</a>`,
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
return `
|
||||
<p>
|
||||
${descriptionParagraph}
|
||||
</p>
|
||||
<p class="append-bottom-0">
|
||||
${extraCostParagraph}
|
||||
</p>
|
||||
`;
|
||||
return `
|
||||
<p>
|
||||
${descriptionParagraph}
|
||||
</p>
|
||||
<p class="append-bottom-0">
|
||||
${extraCostParagraph}
|
||||
</p>
|
||||
`;
|
||||
},
|
||||
gitlabRunnerDescription() {
|
||||
return _.escape(s__(
|
||||
`ClusterIntegration|GitLab Runner is the open source project that is used to run your jobs
|
||||
and send the results back to GitLab.`,
|
||||
));
|
||||
},
|
||||
prometheusDescription() {
|
||||
return sprintf(
|
||||
_.escape(s__(`ClusterIntegration|Prometheus is an open-source monitoring system
|
||||
with %{gitlabIntegrationLink} to monitor deployed applications.`)),
|
||||
{
|
||||
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
|
||||
target="_blank" rel="noopener noreferrer">
|
||||
${_.escape(s__('ClusterIntegration|GitLab Integration'))}</a>`,
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
},
|
||||
gitlabRunnerDescription() {
|
||||
return _.escape(s__(
|
||||
`ClusterIntegration|GitLab Runner is the open source project that is used to run your jobs
|
||||
and send the results back to GitLab.`,
|
||||
));
|
||||
},
|
||||
prometheusDescription() {
|
||||
return sprintf(
|
||||
_.escape(s__('ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications.')), {
|
||||
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html", target="_blank" rel="noopener noreferrer">
|
||||
${_.escape(s__('ClusterIntegration|Gitlab Integration'))}
|
||||
</a>`,
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -107,26 +116,29 @@ export default {
|
|||
:request-reason="applications.helm.requestReason"
|
||||
/>
|
||||
<application-row
|
||||
id="ingress"
|
||||
:title="applications.ingress.title"
|
||||
title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
|
||||
:description="ingressDescription"
|
||||
:status="applications.ingress.status"
|
||||
:status-reason="applications.ingress.statusReason"
|
||||
:request-status="applications.ingress.requestStatus"
|
||||
:request-reason="applications.ingress.requestReason"
|
||||
/>
|
||||
<application-row
|
||||
id="prometheus"
|
||||
:title="applications.prometheus.title"
|
||||
title-link="https://prometheus.io/docs/introduction/overview/"
|
||||
:description="prometheusDescription"
|
||||
:status="applications.prometheus.status"
|
||||
:status-reason="applications.prometheus.statusReason"
|
||||
:request-status="applications.prometheus.requestStatus"
|
||||
:request-reason="applications.prometheus.requestReason"
|
||||
/>
|
||||
<!-- NOTE: Don't forget to update `clusters.scss` min-height for this block and uncomment `application_spec` tests -->
|
||||
id="ingress"
|
||||
:title="applications.ingress.title"
|
||||
title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
|
||||
:description="ingressDescription"
|
||||
:status="applications.ingress.status"
|
||||
:status-reason="applications.ingress.statusReason"
|
||||
:request-status="applications.ingress.requestStatus"
|
||||
:request-reason="applications.ingress.requestReason"
|
||||
/>
|
||||
<application-row
|
||||
id="prometheus"
|
||||
:title="applications.prometheus.title"
|
||||
title-link="https://prometheus.io/docs/introduction/overview/"
|
||||
:description="prometheusDescription"
|
||||
:status="applications.prometheus.status"
|
||||
:status-reason="applications.prometheus.statusReason"
|
||||
:request-status="applications.prometheus.requestStatus"
|
||||
:request-reason="applications.prometheus.requestReason"
|
||||
/>
|
||||
<!--
|
||||
NOTE: Don't forget to update `clusters.scss`
|
||||
min-height for this block and uncomment `application_spec` tests
|
||||
-->
|
||||
<!-- Add GitLab Runner row, all other plumbing is complete -->
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export default class ImageFile {
|
|||
});
|
||||
return [maxWidth, maxHeight];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
views = {
|
||||
'two-up': function() {
|
||||
return $('.two-up.view .wrap', this.file).each((function(_this) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
import pipelinesMixin from '../../pipelines/mixins/pipelines';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
pipelinesMixin,
|
||||
],
|
||||
|
||||
props: {
|
||||
endpoint: {
|
||||
type: String,
|
||||
|
|
@ -31,9 +35,6 @@
|
|||
default: 'child',
|
||||
},
|
||||
},
|
||||
mixins: [
|
||||
pipelinesMixin,
|
||||
],
|
||||
|
||||
data() {
|
||||
const store = new PipelineStore();
|
||||
|
|
@ -95,28 +96,29 @@
|
|||
label="Loading pipelines"
|
||||
size="3"
|
||||
v-if="isLoading"
|
||||
/>
|
||||
/>
|
||||
|
||||
<empty-state
|
||||
v-if="shouldRenderEmptyState"
|
||||
:help-page-path="helpPagePath"
|
||||
:empty-state-svg-path="emptyStateSvgPath"
|
||||
/>
|
||||
/>
|
||||
|
||||
<error-state
|
||||
v-if="shouldRenderErrorState"
|
||||
:error-state-svg-path="errorStateSvgPath"
|
||||
/>
|
||||
/>
|
||||
|
||||
<div
|
||||
class="table-holder"
|
||||
v-if="shouldRenderTable">
|
||||
v-if="shouldRenderTable"
|
||||
>
|
||||
<pipelines-table-component
|
||||
:pipelines="state.pipelines"
|
||||
:update-graph-dropdown="updateGraphDropdown"
|
||||
:auto-devops-help-path="autoDevopsHelpPath"
|
||||
:view-type="viewType"
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
/* global Flash */
|
||||
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import { n__, s__ } from './locale';
|
||||
|
||||
export function getHeaderText(childElementCount, mergeRequestCount) {
|
||||
if (childElementCount === 0) {
|
||||
return `${mergeRequestCount} ${n__('merge request', 'merge requests', mergeRequestCount)}`;
|
||||
}
|
||||
return ',';
|
||||
}
|
||||
|
||||
export function createHeader(childElementCount, mergeRequestCount) {
|
||||
const headerText = getHeaderText(childElementCount, mergeRequestCount);
|
||||
|
||||
return $('<span />', {
|
||||
class: 'append-right-5',
|
||||
text: headerText,
|
||||
});
|
||||
}
|
||||
|
||||
export function createLink(mergeRequest) {
|
||||
return $('<a />', {
|
||||
class: 'append-right-5',
|
||||
href: mergeRequest.path,
|
||||
text: `!${mergeRequest.iid}`,
|
||||
});
|
||||
}
|
||||
|
||||
export function createTitle(mergeRequest) {
|
||||
return $('<span />', {
|
||||
text: mergeRequest.title,
|
||||
});
|
||||
}
|
||||
|
||||
export function createItem(mergeRequest) {
|
||||
const $item = $('<span />');
|
||||
const $link = createLink(mergeRequest);
|
||||
const $title = createTitle(mergeRequest);
|
||||
$item.append($link);
|
||||
$item.append($title);
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
export function createContent(mergeRequests) {
|
||||
const $content = $('<span />');
|
||||
|
||||
if (mergeRequests.length === 0) {
|
||||
$content.text(s__('Commits|No related merge requests found'));
|
||||
} else {
|
||||
mergeRequests.forEach((mergeRequest) => {
|
||||
const $header = createHeader($content.children().length, mergeRequests.length);
|
||||
const $item = createItem(mergeRequest);
|
||||
$content.append($header);
|
||||
$content.append($item);
|
||||
});
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
export function fetchCommitMergeRequests() {
|
||||
const $container = $('.merge-requests');
|
||||
|
||||
axios.get($container.data('projectCommitPath'))
|
||||
.then((response) => {
|
||||
const $content = createContent(response.data);
|
||||
|
||||
$container.html($content);
|
||||
})
|
||||
.catch(() => Flash(s__('Commits|An error occurred while fetching merge requests data.')));
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
import { pluralize } from './lib/utils/text_utility';
|
||||
import { localTimeAgo } from './lib/utils/datetime_utility';
|
||||
import Pager from './pager';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
|
||||
export default (function () {
|
||||
const CommitsList = {};
|
||||
|
|
@ -43,29 +44,30 @@ export default (function () {
|
|||
CommitsList.filterResults = function () {
|
||||
const form = $('.commits-search-form');
|
||||
const search = CommitsList.searchField.val();
|
||||
if (search === CommitsList.lastSearch) return;
|
||||
if (search === CommitsList.lastSearch) return Promise.resolve();
|
||||
const commitsUrl = form.attr('action') + '?' + form.serialize();
|
||||
CommitsList.content.fadeTo('fast', 0.5);
|
||||
return $.ajax({
|
||||
type: 'GET',
|
||||
url: form.attr('action'),
|
||||
data: form.serialize(),
|
||||
complete: function () {
|
||||
return CommitsList.content.fadeTo('fast', 1.0);
|
||||
},
|
||||
success: function (data) {
|
||||
const params = form.serializeArray().reduce((acc, obj) => Object.assign(acc, {
|
||||
[obj.name]: obj.value,
|
||||
}), {});
|
||||
|
||||
return axios.get(form.attr('action'), {
|
||||
params,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
CommitsList.lastSearch = search;
|
||||
CommitsList.content.html(data.html);
|
||||
return history.replaceState({
|
||||
page: commitsUrl,
|
||||
CommitsList.content.fadeTo('fast', 1.0);
|
||||
|
||||
// Change url so if user reload a page - search results are saved
|
||||
history.replaceState({
|
||||
page: commitsUrl,
|
||||
}, document.title, commitsUrl);
|
||||
},
|
||||
error: function () {
|
||||
})
|
||||
.catch(() => {
|
||||
CommitsList.content.fadeTo('fast', 1.0);
|
||||
CommitsList.lastSearch = null;
|
||||
},
|
||||
dataType: 'json',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Prepare loaded data.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
|
||||
import { localTimeAgo } from './lib/utils/datetime_utility';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
|
||||
export default class Compare {
|
||||
constructor(opts) {
|
||||
|
|
@ -41,17 +42,14 @@ export default class Compare {
|
|||
}
|
||||
|
||||
getTargetProject() {
|
||||
return $.ajax({
|
||||
url: this.opts.targetProjectUrl,
|
||||
data: {
|
||||
target_project_id: $("input[name='merge_request[target_project_id]']").val()
|
||||
$('.mr_target_commit').empty();
|
||||
|
||||
return axios.get(this.opts.targetProjectUrl, {
|
||||
params: {
|
||||
target_project_id: $("input[name='merge_request[target_project_id]']").val(),
|
||||
},
|
||||
beforeSend: function() {
|
||||
return $('.mr_target_commit').empty();
|
||||
},
|
||||
success: function(html) {
|
||||
return $('.js-target-branch-dropdown .dropdown-content').html(html);
|
||||
}
|
||||
}).then(({ data }) => {
|
||||
$('.js-target-branch-dropdown .dropdown-content').html(data);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -68,22 +66,19 @@ export default class Compare {
|
|||
});
|
||||
}
|
||||
|
||||
static sendAjax(url, loading, target, data) {
|
||||
var $target;
|
||||
$target = $(target);
|
||||
return $.ajax({
|
||||
url: url,
|
||||
data: data,
|
||||
beforeSend: function() {
|
||||
loading.show();
|
||||
return $target.empty();
|
||||
},
|
||||
success: function(html) {
|
||||
loading.hide();
|
||||
$target.html(html);
|
||||
var className = '.' + $target[0].className.replace(' ', '.');
|
||||
localTimeAgo($('.js-timeago', className));
|
||||
}
|
||||
static sendAjax(url, loading, target, params) {
|
||||
const $target = $(target);
|
||||
|
||||
loading.show();
|
||||
$target.empty();
|
||||
|
||||
return axios.get(url, {
|
||||
params,
|
||||
}).then(({ data }) => {
|
||||
loading.hide();
|
||||
$target.html(data);
|
||||
const className = '.' + $target[0].className.replace(' ', '.');
|
||||
localTimeAgo($('.js-timeago', className));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */
|
||||
import { __ } from './locale';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import flash from './flash';
|
||||
|
||||
export default function initCompareAutocomplete() {
|
||||
$('.js-compare-dropdown').each(function() {
|
||||
|
|
@ -10,15 +13,14 @@ export default function initCompareAutocomplete() {
|
|||
const $filterInput = $('input[type="search"]', $dropdownContainer);
|
||||
$dropdown.glDropdown({
|
||||
data: function(term, callback) {
|
||||
return $.ajax({
|
||||
url: $dropdown.data('refs-url'),
|
||||
data: {
|
||||
axios.get($dropdown.data('refsUrl'), {
|
||||
params: {
|
||||
ref: $dropdown.data('ref'),
|
||||
search: term,
|
||||
}
|
||||
}).done(function(refs) {
|
||||
return callback(refs);
|
||||
});
|
||||
},
|
||||
}).then(({ data }) => {
|
||||
callback(data);
|
||||
}).catch(() => flash(__('Error fetching refs')));
|
||||
},
|
||||
selectable: true,
|
||||
filterable: true,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
import _ from 'underscore';
|
||||
|
||||
export default class CreateItemDropdown {
|
||||
/**
|
||||
* @param {Object} options containing
|
||||
* `$dropdown` target element
|
||||
* `onSelect` event callback
|
||||
* $dropdown must be an element created using `dropdown_tag()` rails helper
|
||||
*/
|
||||
constructor(options) {
|
||||
this.defaultToggleLabel = options.defaultToggleLabel;
|
||||
this.fieldName = options.fieldName;
|
||||
this.onSelect = options.onSelect || (() => {});
|
||||
this.getDataOption = options.getData;
|
||||
this.createNewItemFromValueOption = options.createNewItemFromValue;
|
||||
this.$dropdown = options.$dropdown;
|
||||
this.$dropdownContainer = this.$dropdown.parent();
|
||||
this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
|
||||
this.$createButton = this.$dropdownContainer.find('.js-dropdown-create-new-item');
|
||||
|
||||
this.buildDropdown();
|
||||
this.bindEvents();
|
||||
|
||||
// Hide footer
|
||||
this.toggleFooter(true);
|
||||
}
|
||||
|
||||
buildDropdown() {
|
||||
this.$dropdown.glDropdown({
|
||||
data: this.getData.bind(this),
|
||||
filterable: true,
|
||||
remote: false,
|
||||
search: {
|
||||
fields: ['text'],
|
||||
},
|
||||
selectable: true,
|
||||
toggleLabel(selected) {
|
||||
return (selected && 'id' in selected) ? _.escape(selected.title) : this.defaultToggleLabel;
|
||||
},
|
||||
fieldName: this.fieldName,
|
||||
text(item) {
|
||||
return _.escape(item.text);
|
||||
},
|
||||
id(item) {
|
||||
return _.escape(item.id);
|
||||
},
|
||||
onFilter: this.toggleCreateNewButton.bind(this),
|
||||
clicked: (options) => {
|
||||
options.e.preventDefault();
|
||||
this.onSelect();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
clearDropdown() {
|
||||
this.$dropdownContainer.find('.dropdown-content').html('');
|
||||
this.$dropdownContainer.find('.dropdown-input-field').val('');
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.$createButton.on('click', this.onClickCreateWildcard.bind(this));
|
||||
}
|
||||
|
||||
onClickCreateWildcard(e) {
|
||||
e.preventDefault();
|
||||
|
||||
this.refreshData();
|
||||
this.$dropdown.data('glDropdown').selectRowAtIndex();
|
||||
}
|
||||
|
||||
refreshData() {
|
||||
// Refresh the dropdown's data, which ends up calling `getData`
|
||||
this.$dropdown.data('glDropdown').remote.execute();
|
||||
}
|
||||
|
||||
getData(term, callback) {
|
||||
this.getDataOption(term, (data = []) => {
|
||||
// Ensure the selected item isn't already in the data to avoid duplicates
|
||||
const alreadyHasSelectedItem = this.selectedItem && data.some(item =>
|
||||
item.id === this.selectedItem.id,
|
||||
);
|
||||
|
||||
let uniqueData = data;
|
||||
if (!alreadyHasSelectedItem) {
|
||||
uniqueData = data.concat(this.selectedItem || []);
|
||||
}
|
||||
|
||||
callback(uniqueData);
|
||||
});
|
||||
}
|
||||
|
||||
createNewItemFromValue(newValue) {
|
||||
if (this.createNewItemFromValueOption) {
|
||||
return this.createNewItemFromValueOption(newValue);
|
||||
}
|
||||
|
||||
return {
|
||||
title: newValue,
|
||||
id: newValue,
|
||||
text: newValue,
|
||||
};
|
||||
}
|
||||
|
||||
toggleCreateNewButton(newValue) {
|
||||
if (newValue) {
|
||||
this.selectedItem = this.createNewItemFromValue(newValue);
|
||||
|
||||
this.$dropdownContainer
|
||||
.find('.js-dropdown-create-new-item code')
|
||||
.text(newValue);
|
||||
}
|
||||
|
||||
this.toggleFooter(!newValue);
|
||||
}
|
||||
|
||||
toggleFooter(toggleState) {
|
||||
this.$dropdownFooter.toggleClass('hidden', toggleState);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
/* eslint-disable no-new */
|
||||
import _ from 'underscore';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import Flash from './flash';
|
||||
import DropLab from './droplab/drop_lab';
|
||||
import ISetter from './droplab/plugins/input_setter';
|
||||
|
|
@ -73,60 +75,52 @@ export default class CreateMergeRequestDropdown {
|
|||
}
|
||||
|
||||
checkAbilityToCreateBranch() {
|
||||
return $.ajax({
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
url: this.canCreatePath,
|
||||
beforeSend: () => this.setUnavailableButtonState(),
|
||||
})
|
||||
.done((data) => {
|
||||
this.setUnavailableButtonState(false);
|
||||
this.setUnavailableButtonState();
|
||||
|
||||
if (data.can_create_branch) {
|
||||
this.available();
|
||||
this.enable();
|
||||
axios.get(this.canCreatePath)
|
||||
.then(({ data }) => {
|
||||
this.setUnavailableButtonState(false);
|
||||
|
||||
if (!this.droplabInitialized) {
|
||||
this.droplabInitialized = true;
|
||||
this.initDroplab();
|
||||
this.bindEvents();
|
||||
if (data.can_create_branch) {
|
||||
this.available();
|
||||
this.enable();
|
||||
|
||||
if (!this.droplabInitialized) {
|
||||
this.droplabInitialized = true;
|
||||
this.initDroplab();
|
||||
this.bindEvents();
|
||||
}
|
||||
} else if (data.has_related_branch) {
|
||||
this.hide();
|
||||
}
|
||||
} else if (data.has_related_branch) {
|
||||
this.hide();
|
||||
}
|
||||
}).fail(() => {
|
||||
this.unavailable();
|
||||
this.disable();
|
||||
new Flash('Failed to check if a new branch can be created.');
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.unavailable();
|
||||
this.disable();
|
||||
Flash('Failed to check if a new branch can be created.');
|
||||
});
|
||||
}
|
||||
|
||||
createBranch() {
|
||||
return $.ajax({
|
||||
method: 'POST',
|
||||
dataType: 'json',
|
||||
url: this.createBranchPath,
|
||||
beforeSend: () => (this.isCreatingBranch = true),
|
||||
})
|
||||
.done((data) => {
|
||||
this.branchCreated = true;
|
||||
window.location.href = data.url;
|
||||
})
|
||||
.fail(() => new Flash('Failed to create a branch for this issue. Please try again.'));
|
||||
this.isCreatingBranch = true;
|
||||
|
||||
return axios.post(this.createBranchPath)
|
||||
.then(({ data }) => {
|
||||
this.branchCreated = true;
|
||||
window.location.href = data.url;
|
||||
})
|
||||
.catch(() => Flash('Failed to create a branch for this issue. Please try again.'));
|
||||
}
|
||||
|
||||
createMergeRequest() {
|
||||
return $.ajax({
|
||||
method: 'POST',
|
||||
dataType: 'json',
|
||||
url: this.createMrPath,
|
||||
beforeSend: () => (this.isCreatingMergeRequest = true),
|
||||
})
|
||||
.done((data) => {
|
||||
this.mergeRequestCreated = true;
|
||||
window.location.href = data.url;
|
||||
})
|
||||
.fail(() => new Flash('Failed to create Merge Request. Please try again.'));
|
||||
this.isCreatingMergeRequest = true;
|
||||
|
||||
return axios.post(this.createMrPath)
|
||||
.then(({ data }) => {
|
||||
this.mergeRequestCreated = true;
|
||||
window.location.href = data.url;
|
||||
})
|
||||
.catch(() => Flash('Failed to create Merge Request. Please try again.'));
|
||||
}
|
||||
|
||||
disable() {
|
||||
|
|
@ -199,39 +193,33 @@ export default class CreateMergeRequestDropdown {
|
|||
getRef(ref, target = 'all') {
|
||||
if (!ref) return false;
|
||||
|
||||
return $.ajax({
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
url: this.refsPath + ref,
|
||||
beforeSend: () => {
|
||||
this.isGettingRef = true;
|
||||
},
|
||||
})
|
||||
.always(() => {
|
||||
this.isGettingRef = false;
|
||||
})
|
||||
.done((data) => {
|
||||
const branches = data[Object.keys(data)[0]];
|
||||
const tags = data[Object.keys(data)[1]];
|
||||
let result;
|
||||
return axios.get(this.refsPath + ref)
|
||||
.then(({ data }) => {
|
||||
const branches = data[Object.keys(data)[0]];
|
||||
const tags = data[Object.keys(data)[1]];
|
||||
let result;
|
||||
|
||||
if (target === 'branch') {
|
||||
result = CreateMergeRequestDropdown.findByValue(branches, ref);
|
||||
} else {
|
||||
result = CreateMergeRequestDropdown.findByValue(branches, ref, true) ||
|
||||
CreateMergeRequestDropdown.findByValue(tags, ref, true);
|
||||
this.suggestedRef = result;
|
||||
}
|
||||
if (target === 'branch') {
|
||||
result = CreateMergeRequestDropdown.findByValue(branches, ref);
|
||||
} else {
|
||||
result = CreateMergeRequestDropdown.findByValue(branches, ref, true) ||
|
||||
CreateMergeRequestDropdown.findByValue(tags, ref, true);
|
||||
this.suggestedRef = result;
|
||||
}
|
||||
|
||||
return this.updateInputState(target, ref, result);
|
||||
})
|
||||
.fail(() => {
|
||||
this.unavailable();
|
||||
this.disable();
|
||||
new Flash('Failed to get ref.');
|
||||
this.isGettingRef = false;
|
||||
|
||||
return false;
|
||||
});
|
||||
return this.updateInputState(target, ref, result);
|
||||
})
|
||||
.catch(() => {
|
||||
this.unavailable();
|
||||
this.disable();
|
||||
new Flash('Failed to get ref.');
|
||||
|
||||
this.isGettingRef = false;
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
getTargetData(target) {
|
||||
|
|
@ -331,12 +319,12 @@ export default class CreateMergeRequestDropdown {
|
|||
xhr = this.createBranch();
|
||||
}
|
||||
|
||||
xhr.fail(() => {
|
||||
xhr.catch(() => {
|
||||
this.isCreatingMergeRequest = false;
|
||||
this.isCreatingBranch = false;
|
||||
});
|
||||
|
||||
xhr.always(() => this.enable());
|
||||
this.enable();
|
||||
});
|
||||
|
||||
this.disable();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,28 +26,34 @@
|
|||
class="js-ca-dismiss-button dismiss-button"
|
||||
type="button"
|
||||
:aria-label="__('Dismiss Cycle Analytics introduction box')"
|
||||
@click="dismissOverviewDialog">
|
||||
@click="dismissOverviewDialog"
|
||||
>
|
||||
<i
|
||||
class="fa fa-times"
|
||||
aria-hidden="true">
|
||||
</i>
|
||||
</button>
|
||||
<div class="svg-container" v-html="iconCycleAnalyticsSplash">
|
||||
<div
|
||||
class="svg-container"
|
||||
v-html="iconCycleAnalyticsSplash"
|
||||
>
|
||||
</div>
|
||||
<div class="inner-content">
|
||||
<h4>
|
||||
{{__('Introducing Cycle Analytics')}}
|
||||
{{ __('Introducing Cycle Analytics') }}
|
||||
</h4>
|
||||
<p>
|
||||
{{ __('Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.') }}
|
||||
{{ __(`Cycle Analytics gives an overview
|
||||
of how much time it takes to go from idea to production in your project.`) }}
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
:href="documentationLink"
|
||||
target="_blank"
|
||||
rel="nofollow"
|
||||
class="btn">
|
||||
{{__('Read more')}}
|
||||
class="btn"
|
||||
>
|
||||
{{ __('Read more') }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,25 +2,34 @@
|
|||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
props: {
|
||||
count: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<span v-if="count === 50" class="events-info pull-right">
|
||||
<span
|
||||
v-if="count === 50"
|
||||
class="events-info pull-right"
|
||||
>
|
||||
<i
|
||||
class="fa fa-warning"
|
||||
v-tooltip
|
||||
aria-hidden="true"
|
||||
:title="n__('Limited to showing %d event at most', 'Limited to showing %d events at most', 50)"
|
||||
data-placement="top"></i>
|
||||
:title="n__(
|
||||
'Limited to showing %d event at most',
|
||||
'Limited to showing %d events at most',
|
||||
50
|
||||
)"
|
||||
data-placement="top"
|
||||
>
|
||||
</i>
|
||||
{{ n__('Showing %d event', 'Showing %d events', 50) }}
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -4,15 +4,21 @@
|
|||
import totalTime from './total_time_component.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
components: {
|
||||
userAvatarImage,
|
||||
limitWarning,
|
||||
totalTime,
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
stage: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
|
@ -22,28 +28,44 @@
|
|||
<limit-warning :count="items.length" />
|
||||
</div>
|
||||
<ul class="stage-event-list">
|
||||
<li v-for="mergeRequest in items" class="stage-event-item">
|
||||
<li
|
||||
v-for="(mergeRequest, i) in items"
|
||||
:key="i"
|
||||
class="stage-event-item"
|
||||
>
|
||||
<div class="item-details">
|
||||
<!-- FIXME: Pass an alt attribute here for accessibility -->
|
||||
<user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
|
||||
<user-avatar-image :img-src="mergeRequest.author.avatarUrl" />
|
||||
<h5 class="item-title merge-merquest-title">
|
||||
<a :href="mergeRequest.url">
|
||||
{{ mergeRequest.title }}
|
||||
</a>
|
||||
</h5>
|
||||
<a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
|
||||
<a
|
||||
:href="mergeRequest.url"
|
||||
class="issue-link">
|
||||
!{{ mergeRequest.iid }}
|
||||
</a>
|
||||
·
|
||||
<span>
|
||||
{{ s__('OpenedNDaysAgo|Opened') }}
|
||||
<a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
|
||||
<a
|
||||
:href="mergeRequest.url"
|
||||
class="issue-date">
|
||||
{{ mergeRequest.createdAt }}
|
||||
</a>
|
||||
</span>
|
||||
<span>
|
||||
{{ s__('ByAuthor|by') }}
|
||||
<a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
|
||||
<a
|
||||
:href="mergeRequest.author.webUrl"
|
||||
class="issue-author-link">
|
||||
{{ mergeRequest.author.name }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="item-time">
|
||||
<total-time :time="mergeRequest.totalTime"></total-time>
|
||||
<total-time :time="mergeRequest.totalTime" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -4,15 +4,21 @@
|
|||
import totalTime from './total_time_component.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
components: {
|
||||
userAvatarImage,
|
||||
limitWarning,
|
||||
totalTime,
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
stage: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
|
@ -25,30 +31,43 @@
|
|||
<li
|
||||
v-for="(issue, i) in items"
|
||||
:key="i"
|
||||
class="stage-event-item">
|
||||
class="stage-event-item"
|
||||
>
|
||||
<div class="item-details">
|
||||
<!-- 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">
|
||||
<a
|
||||
class="issue-title"
|
||||
:href="issue.url"
|
||||
>
|
||||
{{ issue.title }}
|
||||
</a>
|
||||
</h5>
|
||||
<a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
|
||||
<a
|
||||
:href="issue.url"
|
||||
class="issue-link"
|
||||
>#{{ issue.iid }}</a>
|
||||
·
|
||||
<span>
|
||||
{{ s__('OpenedNDaysAgo|Opened') }}
|
||||
<a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
|
||||
<a
|
||||
:href="issue.url"
|
||||
class="issue-date"
|
||||
>{{ issue.createdAt }}</a>
|
||||
</span>
|
||||
<span>
|
||||
{{ s__('ByAuthor|by') }}
|
||||
<a :href="issue.author.webUrl" class="issue-author-link">
|
||||
<a
|
||||
:href="issue.author.webUrl"
|
||||
class="issue-author-link"
|
||||
>
|
||||
{{ issue.author.name }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="item-time">
|
||||
<total-time :time="issue.totalTime"/>
|
||||
<total-time :time="issue.totalTime" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -5,15 +5,21 @@
|
|||
import totalTime from './total_time_component.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
components: {
|
||||
userAvatarImage,
|
||||
totalTime,
|
||||
limitWarning,
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
stage: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
iconCommit() {
|
||||
return iconCommit;
|
||||
|
|
@ -31,10 +37,11 @@
|
|||
<li
|
||||
v-for="(commit, i) in items"
|
||||
:key="i"
|
||||
class="stage-event-item">
|
||||
class="stage-event-item"
|
||||
>
|
||||
<div class="item-details item-conmmit-component">
|
||||
<!-- FIXME: Pass an alt attribute here for accessibility -->
|
||||
<user-avatar-image :img-src="commit.author.avatarUrl"/>
|
||||
<user-avatar-image :img-src="commit.author.avatarUrl" />
|
||||
<h5 class="item-title commit-title">
|
||||
<a :href="commit.commitUrl">
|
||||
{{ commit.title }}
|
||||
|
|
@ -42,10 +49,20 @@
|
|||
</h5>
|
||||
<span>
|
||||
{{ s__('FirstPushedBy|First') }}
|
||||
<span class="commit-icon" v-html="iconCommit"></span>
|
||||
<a :href="commit.commitUrl" class="commit-hash-link commit-sha">{{ commit.shortSha }}</a>
|
||||
<span
|
||||
class="commit-icon"
|
||||
v-html="iconCommit"
|
||||
>
|
||||
</span>
|
||||
<a
|
||||
:href="commit.commitUrl"
|
||||
class="commit-hash-link commit-sha"
|
||||
>{{ commit.shortSha }}</a>
|
||||
{{ s__('FirstPushedBy|pushed by') }}
|
||||
<a :href="commit.author.webUrl" class="commit-author-link">
|
||||
<a
|
||||
:href="commit.author.webUrl"
|
||||
class="commit-author-link"
|
||||
>
|
||||
{{ commit.author.name }}
|
||||
</a>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -5,16 +5,22 @@
|
|||
import icon from '../../vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
components: {
|
||||
userAvatarImage,
|
||||
totalTime,
|
||||
limitWarning,
|
||||
icon,
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
stage: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
|
@ -27,7 +33,8 @@
|
|||
<li
|
||||
v-for="(mergeRequest, i) in items"
|
||||
:key="i"
|
||||
class="stage-event-item">
|
||||
class="stage-event-item"
|
||||
>
|
||||
<div class="item-details">
|
||||
<!-- FIXME: Pass an alt attribute here for accessibility -->
|
||||
<user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
|
||||
|
|
@ -36,34 +43,52 @@
|
|||
{{ mergeRequest.title }}
|
||||
</a>
|
||||
</h5>
|
||||
<a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
|
||||
<a
|
||||
:href="mergeRequest.url"
|
||||
class="issue-link"
|
||||
>!{{ mergeRequest.iid }}</a>
|
||||
·
|
||||
<span>
|
||||
{{ s__('OpenedNDaysAgo|Opened') }}
|
||||
<a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
|
||||
<a
|
||||
:href="mergeRequest.url"
|
||||
class="issue-date"
|
||||
>{{ mergeRequest.createdAt }}</a>
|
||||
</span>
|
||||
<span>
|
||||
{{ s__('ByAuthor|by') }}
|
||||
<a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
|
||||
<a
|
||||
:href="mergeRequest.author.webUrl"
|
||||
class="issue-author-link"
|
||||
>{{ mergeRequest.author.name }}</a>
|
||||
</span>
|
||||
<template v-if="mergeRequest.state === 'closed'">
|
||||
<span class="merge-request-state">
|
||||
<i class="fa fa-ban"></i>
|
||||
<i
|
||||
class="fa fa-ban"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
{{ mergeRequest.state.toUpperCase() }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="merge-request-branch" v-if="mergeRequest.branch">
|
||||
<span
|
||||
class="merge-request-branch"
|
||||
v-if="mergeRequest.branch"
|
||||
>
|
||||
<icon
|
||||
name="fork"
|
||||
:size="16">
|
||||
</icon>
|
||||
<a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
|
||||
:size="16"
|
||||
/>
|
||||
<a :href="mergeRequest.branch.url">
|
||||
{{ mergeRequest.branch.name }}
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="item-time">
|
||||
<total-time :time="mergeRequest.totalTime"/>
|
||||
<total-time :time="mergeRequest.totalTime" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -6,16 +6,22 @@
|
|||
import icon from '../../vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
components: {
|
||||
userAvatarImage,
|
||||
totalTime,
|
||||
limitWarning,
|
||||
icon,
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
stage: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
iconBranch() {
|
||||
return iconBranch;
|
||||
|
|
@ -33,30 +39,58 @@
|
|||
<li
|
||||
v-for="(build, i) in items"
|
||||
class="stage-event-item item-build-component"
|
||||
:key="i">
|
||||
:key="i"
|
||||
>
|
||||
<div class="item-details">
|
||||
<!-- 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>
|
||||
<a
|
||||
:href="build.url"
|
||||
class="pipeline-id"
|
||||
>
|
||||
#{{ build.id }}
|
||||
</a>
|
||||
<icon
|
||||
name="fork"
|
||||
:size="16">
|
||||
</icon>
|
||||
<a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
|
||||
<span class="icon-branch" v-html="iconBranch"></span>
|
||||
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
|
||||
:size="16"
|
||||
/>
|
||||
<a
|
||||
:href="build.branch.url"
|
||||
class="ref-name"
|
||||
>
|
||||
{{ build.branch.name }}
|
||||
</a>
|
||||
<span
|
||||
class="icon-branch"
|
||||
v-html="iconBranch"
|
||||
>
|
||||
</span>
|
||||
<a
|
||||
:href="build.commitUrl"
|
||||
class="commit-sha"
|
||||
>
|
||||
{{ build.shortSha }}
|
||||
</a>
|
||||
</h5>
|
||||
<span>
|
||||
<a :href="build.url" class="build-date">{{ build.date }}</a>
|
||||
<a
|
||||
:href="build.url"
|
||||
class="build-date"
|
||||
>
|
||||
{{ build.date }}
|
||||
</a>
|
||||
{{ s__('ByAuthor|by') }}
|
||||
<a :href="build.author.webUrl" class="issue-author-link">
|
||||
<a
|
||||
:href="build.author.webUrl"
|
||||
class="issue-author-link"
|
||||
>
|
||||
{{ build.author.name }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="item-time">
|
||||
<total-time :time="build.totalTime"/>
|
||||
<total-time :time="build.totalTime" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -6,15 +6,21 @@
|
|||
import icon from '../../vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
components: {
|
||||
totalTime,
|
||||
limitWarning,
|
||||
icon,
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
stage: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
iconBuildStatus() {
|
||||
return iconBuildStatus;
|
||||
|
|
@ -35,29 +41,59 @@
|
|||
<li
|
||||
v-for="(build, i) in items"
|
||||
:key="i"
|
||||
class="stage-event-item item-build-component">
|
||||
class="stage-event-item item-build-component"
|
||||
>
|
||||
<div class="item-details">
|
||||
<h5 class="item-title">
|
||||
<span class="icon-build-status" v-html="iconBuildStatus"></span>
|
||||
<a :href="build.url" class="item-build-name">{{ build.name }}</a>
|
||||
<span
|
||||
class="icon-build-status"
|
||||
v-html="iconBuildStatus"
|
||||
>
|
||||
</span>
|
||||
<a
|
||||
:href="build.url"
|
||||
class="item-build-name"
|
||||
>
|
||||
{{ build.name }}
|
||||
</a>
|
||||
·
|
||||
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
|
||||
<a
|
||||
:href="build.url"
|
||||
class="pipeline-id"
|
||||
>
|
||||
#{{ build.id }}
|
||||
</a>
|
||||
<icon
|
||||
name="fork"
|
||||
:size="16">
|
||||
</icon>
|
||||
<a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
|
||||
<span class="icon-branch" v-html="iconBranch"></span>
|
||||
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
|
||||
:size="16"
|
||||
/>
|
||||
<a
|
||||
:href="build.branch.url"
|
||||
class="ref-name"
|
||||
>
|
||||
{{ build.branch.name }}
|
||||
</a>
|
||||
<span
|
||||
class="icon-branch"
|
||||
v-html="iconBranch"
|
||||
>
|
||||
</span>
|
||||
<a
|
||||
:href="build.commitUrl"
|
||||
class="commit-sha">
|
||||
{{ build.shortSha }}
|
||||
</a>
|
||||
</h5>
|
||||
<span>
|
||||
<a :href="build.url" class="issue-date">
|
||||
<a
|
||||
:href="build.url"
|
||||
class="issue-date">
|
||||
{{ build.date }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="item-time">
|
||||
<total-time :time="build.totalTime"/>
|
||||
<total-time :time="build.totalTime" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -17,13 +17,33 @@
|
|||
<template>
|
||||
<span class="total-time">
|
||||
<template v-if="hasData">
|
||||
<template v-if="time.days">{{ time.days }} <span>{{ n__('day', 'days', time.days) }}</span></template>
|
||||
<template v-if="time.hours">{{ time.hours }} <span>{{ n__('Time|hr', 'Time|hrs', time.hours) }}</span></template>
|
||||
<template v-if="time.mins && !time.days">{{ time.mins }} <span>{{ n__('Time|min', 'Time|mins', time.mins) }}</span></template>
|
||||
<template v-if="time.seconds && hasData === 1 || time.seconds === 0">{{ time.seconds }} <span>{{ s__('Time|s') }}</span></template>
|
||||
<template v-if="time.days">
|
||||
{{ time.days }}
|
||||
<span>
|
||||
{{ n__('day', 'days', time.days) }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="time.hours">
|
||||
{{ time.hours }}
|
||||
<span>
|
||||
{{ n__('Time|hr', 'Time|hrs', time.hours) }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="time.mins && !time.days">
|
||||
{{ time.mins }}
|
||||
<span>
|
||||
{{ n__('Time|min', 'Time|mins', time.mins) }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="time.seconds && hasData === 1 || time.seconds === 0">
|
||||
{{ time.seconds }}
|
||||
<span>
|
||||
{{ s__('Time|s') }}
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
--
|
||||
</template>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,16 @@ $(() => {
|
|||
gl.cycleAnalyticsApp = new Vue({
|
||||
el: '#cycle-analytics',
|
||||
name: 'CycleAnalytics',
|
||||
components: {
|
||||
banner,
|
||||
'stage-issue-component': stageComponent,
|
||||
'stage-plan-component': stagePlanComponent,
|
||||
'stage-code-component': stageCodeComponent,
|
||||
'stage-test-component': stageTestComponent,
|
||||
'stage-review-component': stageReviewComponent,
|
||||
'stage-staging-component': stageStagingComponent,
|
||||
'stage-production-component': stageComponent,
|
||||
},
|
||||
data() {
|
||||
const cycleAnalyticsEl = document.querySelector('#cycle-analytics');
|
||||
const cycleAnalyticsService = new CycleAnalyticsService({
|
||||
|
|
@ -43,16 +53,6 @@ $(() => {
|
|||
return this.store.currentActiveStage();
|
||||
},
|
||||
},
|
||||
components: {
|
||||
banner,
|
||||
'stage-issue-component': stageComponent,
|
||||
'stage-plan-component': stagePlanComponent,
|
||||
'stage-code-component': stageCodeComponent,
|
||||
'stage-test-component': stageTestComponent,
|
||||
'stage-review-component': stageReviewComponent,
|
||||
'stage-staging-component': stageStagingComponent,
|
||||
'stage-production-component': stageComponent,
|
||||
},
|
||||
created() {
|
||||
this.fetchCycleAnalyticsData();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,10 +3,8 @@
|
|||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
};
|
||||
components: {
|
||||
loadingIcon,
|
||||
},
|
||||
props: {
|
||||
deployKey: {
|
||||
|
|
@ -23,11 +21,16 @@
|
|||
default: 'btn-default',
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
loadingIcon,
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
text() {
|
||||
return `${this.type.charAt(0).toUpperCase()}${this.type.slice(1)}`;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
doAction() {
|
||||
this.isLoading = true;
|
||||
|
|
@ -37,11 +40,6 @@
|
|||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
text() {
|
||||
return `${this.type.charAt(0).toUpperCase()}${this.type.slice(1)}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,9 @@
|
|||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
store: new DeployKeysStore(),
|
||||
};
|
||||
components: {
|
||||
keysPanel,
|
||||
loadingIcon,
|
||||
},
|
||||
props: {
|
||||
endpoint: {
|
||||
|
|
@ -19,6 +17,12 @@
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
store: new DeployKeysStore(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasKeys() {
|
||||
return Object.keys(this.keys).length;
|
||||
|
|
@ -27,9 +31,20 @@
|
|||
return this.store.keys;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
keysPanel,
|
||||
loadingIcon,
|
||||
created() {
|
||||
this.service = new DeployKeysService(this.endpoint);
|
||||
|
||||
eventHub.$on('enable.key', this.enableKey);
|
||||
eventHub.$on('remove.key', this.disableKey);
|
||||
eventHub.$on('disable.key', this.disableKey);
|
||||
},
|
||||
mounted() {
|
||||
this.fetchKeys();
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off('enable.key', this.enableKey);
|
||||
eventHub.$off('remove.key', this.disableKey);
|
||||
eventHub.$off('disable.key', this.disableKey);
|
||||
},
|
||||
methods: {
|
||||
fetchKeys() {
|
||||
|
|
@ -59,21 +74,6 @@
|
|||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.service = new DeployKeysService(this.endpoint);
|
||||
|
||||
eventHub.$on('enable.key', this.enableKey);
|
||||
eventHub.$on('remove.key', this.disableKey);
|
||||
eventHub.$on('disable.key', this.disableKey);
|
||||
},
|
||||
mounted() {
|
||||
this.fetchKeys();
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off('enable.key', this.enableKey);
|
||||
eventHub.$off('remove.key', this.disableKey);
|
||||
eventHub.$off('disable.key', this.disableKey);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -87,6 +87,7 @@
|
|||
<div v-else-if="hasKeys">
|
||||
<keys-panel
|
||||
title="Enabled deploy keys for this project"
|
||||
class="qa-project-deploy-keys"
|
||||
:keys="keys.enabled_keys"
|
||||
:store="store"
|
||||
:endpoint="endpoint"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
<script>
|
||||
import actionBtn from './action_btn.vue';
|
||||
import { getTimeago } from '../../lib/utils/datetime_utility';
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
actionBtn,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
props: {
|
||||
deployKey: {
|
||||
type: Object,
|
||||
|
|
@ -17,9 +24,6 @@
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
actionBtn,
|
||||
},
|
||||
computed: {
|
||||
timeagoDate() {
|
||||
return getTimeago().format(this.deployKey.created_at);
|
||||
|
|
@ -32,6 +36,9 @@
|
|||
isEnabled(id) {
|
||||
return this.store.findEnabledKey(id) !== undefined;
|
||||
},
|
||||
tooltipTitle(project) {
|
||||
return project.can_push ? 'Write access allowed' : 'Read access only';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -46,26 +53,29 @@
|
|||
</i>
|
||||
</div>
|
||||
<div class="deploy-key-content key-list-item-info">
|
||||
<strong class="title">
|
||||
<strong class="title qa-key-title">
|
||||
{{ deployKey.title }}
|
||||
</strong>
|
||||
<div class="description">
|
||||
<div class="description qa-key-fingerprint">
|
||||
{{ deployKey.fingerprint }}
|
||||
</div>
|
||||
<div
|
||||
v-if="deployKey.can_push"
|
||||
class="write-access-allowed"
|
||||
>
|
||||
Write access allowed
|
||||
</div>
|
||||
</div>
|
||||
<div class="deploy-key-content prepend-left-default deploy-key-projects">
|
||||
<a
|
||||
v-for="project in deployKey.projects"
|
||||
v-for="(deployKeysProject, i) in deployKey.deploy_keys_projects"
|
||||
:key="i"
|
||||
class="label deploy-project-label"
|
||||
:href="project.full_path"
|
||||
:href="deployKeysProject.project.full_path"
|
||||
:title="tooltipTitle(deployKeysProject)"
|
||||
v-tooltip
|
||||
>
|
||||
{{ project.full_name }}
|
||||
{{ deployKeysProject.project.full_name }}
|
||||
<i
|
||||
v-if="!deployKeysProject.can_push"
|
||||
aria-hidden="true"
|
||||
class="fa fa-lock"
|
||||
>
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="deploy-key-content">
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
import key from './key.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
key,
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
|
|
@ -25,9 +28,6 @@
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
key,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -37,12 +37,14 @@
|
|||
{{ title }}
|
||||
({{ keys.length }})
|
||||
</h5>
|
||||
<ul class="well-list"
|
||||
<ul
|
||||
class="well-list"
|
||||
v-if="keys.length"
|
||||
>
|
||||
<li
|
||||
v-for="deployKey in keys"
|
||||
:key="deployKey.id">
|
||||
:key="deployKey.id"
|
||||
>
|
||||
<key
|
||||
:deploy-key="deployKey"
|
||||
:store="store"
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ import deployKeysApp from './components/app.vue';
|
|||
|
||||
document.addEventListener('DOMContentLoaded', () => new Vue({
|
||||
el: document.getElementById('js-deploy-keys'),
|
||||
components: {
|
||||
deployKeysApp,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
endpoint: this.$options.el.dataset.endpoint,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
deployKeysApp,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('deploy-keys-app', {
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ import Dropzone from 'dropzone';
|
|||
import _ from 'underscore';
|
||||
import './preview_markdown';
|
||||
import csrf from './lib/utils/csrf';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
|
||||
Dropzone.autoDiscover = false;
|
||||
|
||||
export default function dropzoneInput(form) {
|
||||
const divHover = '<div class="div-dropzone-hover"></div>';
|
||||
|
|
@ -233,25 +236,21 @@ export default function dropzoneInput(form) {
|
|||
uploadFile = (item, filename) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', item, filename);
|
||||
return $.ajax({
|
||||
url: uploadsPath,
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
dataType: 'json',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: csrf.headers,
|
||||
beforeSend: () => {
|
||||
showSpinner();
|
||||
return closeAlertMessage();
|
||||
},
|
||||
success: (e, text, response) => {
|
||||
const md = response.responseJSON.link.markdown;
|
||||
|
||||
showSpinner();
|
||||
closeAlertMessage();
|
||||
|
||||
axios.post(uploadsPath, formData)
|
||||
.then(({ data }) => {
|
||||
const md = data.link.markdown;
|
||||
|
||||
insertToTextArea(filename, md);
|
||||
},
|
||||
error: response => showError(response.responseJSON.message),
|
||||
complete: () => closeSpinner(),
|
||||
});
|
||||
closeSpinner();
|
||||
})
|
||||
.catch((e) => {
|
||||
showError(e.response.data.message);
|
||||
closeSpinner();
|
||||
});
|
||||
};
|
||||
|
||||
updateAttachingMessage = (files, messageContainer) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/* global dateFormat */
|
||||
|
||||
import Pikaday from 'pikaday';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
|
||||
|
||||
class DueDateSelect {
|
||||
|
|
@ -125,37 +126,30 @@ class DueDateSelect {
|
|||
}
|
||||
|
||||
submitSelectedDate(isDropdown) {
|
||||
return $.ajax({
|
||||
type: 'PUT',
|
||||
url: this.issueUpdateURL,
|
||||
data: this.datePayload,
|
||||
dataType: 'json',
|
||||
beforeSend: () => {
|
||||
const selectedDateValue = this.datePayload[this.abilityName].due_date;
|
||||
const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value';
|
||||
const selectedDateValue = this.datePayload[this.abilityName].due_date;
|
||||
const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value';
|
||||
|
||||
this.$loading.removeClass('hidden').fadeIn();
|
||||
this.$loading.removeClass('hidden').fadeIn();
|
||||
|
||||
if (isDropdown) {
|
||||
this.$dropdown.trigger('loading.gl.dropdown');
|
||||
this.$selectbox.hide();
|
||||
}
|
||||
|
||||
this.$value.css('display', '');
|
||||
this.$valueContent.html(`<span class='${displayedDateStyle}'>${this.displayedDate}</span>`);
|
||||
this.$sidebarValue.html(this.displayedDate);
|
||||
|
||||
$('.js-remove-due-date-holder').toggleClass('hidden', selectedDateValue.length);
|
||||
|
||||
return axios.put(this.issueUpdateURL, this.datePayload)
|
||||
.then(() => {
|
||||
if (isDropdown) {
|
||||
this.$dropdown.trigger('loading.gl.dropdown');
|
||||
this.$selectbox.hide();
|
||||
this.$dropdown.trigger('loaded.gl.dropdown');
|
||||
this.$dropdown.dropdown('toggle');
|
||||
}
|
||||
|
||||
this.$value.css('display', '');
|
||||
this.$valueContent.html(`<span class='${displayedDateStyle}'>${this.displayedDate}</span>`);
|
||||
this.$sidebarValue.html(this.displayedDate);
|
||||
|
||||
return selectedDateValue.length ?
|
||||
$('.js-remove-due-date-holder').removeClass('hidden') :
|
||||
$('.js-remove-due-date-holder').addClass('hidden');
|
||||
},
|
||||
}).done(() => {
|
||||
if (isDropdown) {
|
||||
this.$dropdown.trigger('loaded.gl.dropdown');
|
||||
this.$dropdown.dropdown('toggle');
|
||||
}
|
||||
return this.$loading.fadeOut();
|
||||
});
|
||||
return this.$loading.fadeOut();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@
|
|||
import environmentTable from '../components/environments_table.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
environmentTable,
|
||||
loadingIcon,
|
||||
tablePagination,
|
||||
},
|
||||
props: {
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
|
|
@ -26,12 +31,6 @@
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
environmentTable,
|
||||
loadingIcon,
|
||||
tablePagination,
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChangePage(page) {
|
||||
this.$emit('onChangePage', page);
|
||||
|
|
@ -47,7 +46,7 @@
|
|||
label="Loading environments"
|
||||
v-if="isLoading"
|
||||
size="3"
|
||||
/>
|
||||
/>
|
||||
|
||||
<slot name="emptyState"></slot>
|
||||
|
||||
|
|
@ -59,13 +58,13 @@
|
|||
:environments="environments"
|
||||
:can-create-deployment="canCreateDeployment"
|
||||
:can-read-environment="canReadEnvironment"
|
||||
/>
|
||||
/>
|
||||
|
||||
<table-pagination
|
||||
v-if="pagination && pagination.totalPages > 1"
|
||||
:change="onChangePage"
|
||||
:pageInfo="pagination"
|
||||
/>
|
||||
:page-info="pagination"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'environmentsEmptyState',
|
||||
name: 'EnvironmentsEmptyState',
|
||||
props: {
|
||||
newPath: {
|
||||
type: String,
|
||||
|
|
@ -21,21 +21,23 @@
|
|||
<div class="blank-state-row">
|
||||
<div class="blank-state-center">
|
||||
<h2 class="blank-state-title js-blank-state-title">
|
||||
{{s__("Environments|You don't have any environments right now.")}}
|
||||
{{ s__("Environments|You don't have any environments right now.") }}
|
||||
</h2>
|
||||
<p class="blank-state-text">
|
||||
{{s__("Environments|Environments are places where code gets deployed, such as staging or production.")}}
|
||||
{{ s__(`Environments|Environments are places where
|
||||
code gets deployed, such as staging or production.`) }}
|
||||
<br />
|
||||
<a :href="helpPath">
|
||||
{{s__("Environments|Read more about environments")}}
|
||||
{{ s__("Environments|Read more about environments") }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<a
|
||||
v-if="canCreateEnvironment"
|
||||
:href="newPath"
|
||||
class="btn btn-create js-new-environment-button">
|
||||
{{s__("Environments|New environment")}}
|
||||
class="btn btn-create js-new-environment-button"
|
||||
>
|
||||
{{ s__("Environments|New environment") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,55 +1,54 @@
|
|||
<script>
|
||||
import playIconSvg from 'icons/_icon_play.svg';
|
||||
import eventHub from '../event_hub';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
import playIconSvg from 'icons/_icon_play.svg';
|
||||
import eventHub from '../event_hub';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
actions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
|
||||
components: {
|
||||
loadingIcon,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
playIconSvg,
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
title() {
|
||||
return 'Deploy to...';
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClickAction(endpoint) {
|
||||
this.isLoading = true;
|
||||
|
||||
eventHub.$emit('postAction', endpoint);
|
||||
export default {
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
|
||||
isActionDisabled(action) {
|
||||
if (action.playable === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !action.playable;
|
||||
components: {
|
||||
loadingIcon,
|
||||
},
|
||||
},
|
||||
};
|
||||
props: {
|
||||
actions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
playIconSvg,
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
title() {
|
||||
return 'Deploy to...';
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClickAction(endpoint) {
|
||||
this.isLoading = true;
|
||||
|
||||
eventHub.$emit('postAction', endpoint);
|
||||
},
|
||||
|
||||
isActionDisabled(action) {
|
||||
if (action.playable === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !action.playable;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
|
|
@ -63,27 +62,33 @@ export default {
|
|||
data-toggle="dropdown"
|
||||
:title="title"
|
||||
:aria-label="title"
|
||||
:disabled="isLoading">
|
||||
:disabled="isLoading"
|
||||
>
|
||||
<span>
|
||||
<span v-html="playIconSvg"></span>
|
||||
<i
|
||||
class="fa fa-caret-down"
|
||||
aria-hidden="true"/>
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
<loading-icon v-if="isLoading" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu dropdown-menu-align-right">
|
||||
<li v-for="action in actions">
|
||||
<li
|
||||
v-for="(action, i) in actions"
|
||||
:key="i">
|
||||
<button
|
||||
type="button"
|
||||
class="js-manual-action-link no-btn btn"
|
||||
@click="onClickAction(action.play_path)"
|
||||
:class="{ disabled: isActionDisabled(action) }"
|
||||
:disabled="isActionDisabled(action)">
|
||||
:disabled="isActionDisabled(action)"
|
||||
>
|
||||
<span v-html="playIconSvg"></span>
|
||||
<span>
|
||||
{{action.name}}
|
||||
{{ action.name }}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -1,28 +1,27 @@
|
|||
<script>
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
import { s__ } from '../../locale';
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
import { s__ } from '../../locale';
|
||||
|
||||
/**
|
||||
* Renders the external url link in environments table.
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
externalUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
/**
|
||||
* Renders the external url link in environments table.
|
||||
*/
|
||||
export default {
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
},
|
||||
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
|
||||
computed: {
|
||||
title() {
|
||||
return s__('Environments|Open');
|
||||
props: {
|
||||
externalUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
computed: {
|
||||
title() {
|
||||
return s__('Environments|Open');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<a
|
||||
|
|
@ -33,9 +32,12 @@ export default {
|
|||
rel="noopener noreferrer nofollow"
|
||||
:title="title"
|
||||
:aria-label="title"
|
||||
:href="externalUrl">
|
||||
:href="externalUrl"
|
||||
>
|
||||
<i
|
||||
class="fa fa-external-link"
|
||||
aria-hidden="true" />
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
</a>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
<script>
|
||||
/**
|
||||
* Renders the Monitoring (Metrics) link in environments table.
|
||||
*/
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
/**
|
||||
* Renders the Monitoring (Metrics) link in environments table.
|
||||
*/
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
monitoringUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
export default {
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
},
|
||||
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
|
||||
computed: {
|
||||
title() {
|
||||
return 'Monitoring';
|
||||
props: {
|
||||
monitoringUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
computed: {
|
||||
title() {
|
||||
return 'Monitoring';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<a
|
||||
|
|
@ -31,10 +31,12 @@ export default {
|
|||
rel="noopener noreferrer nofollow"
|
||||
:href="monitoringUrl"
|
||||
:title="title"
|
||||
:aria-label="title">
|
||||
:aria-label="title"
|
||||
>
|
||||
<i
|
||||
class="fa fa-area-chart"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
>
|
||||
</i>
|
||||
</a>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,57 +1,58 @@
|
|||
<script>
|
||||
/**
|
||||
* Renders Rollback or Re deploy button in environments table depending
|
||||
* of the provided property `isLastDeployment`.
|
||||
*
|
||||
* Makes a post request when the button is clicked.
|
||||
*/
|
||||
import eventHub from '../event_hub';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
/**
|
||||
* Renders Rollback or Re deploy button in environments table depending
|
||||
* of the provided property `isLastDeployment`.
|
||||
*
|
||||
* Makes a post request when the button is clicked.
|
||||
*/
|
||||
import eventHub from '../event_hub';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
retryUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
export default {
|
||||
components: {
|
||||
loadingIcon,
|
||||
},
|
||||
|
||||
isLastDeployment: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
props: {
|
||||
retryUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
isLastDeployment: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
loadingIcon,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClick() {
|
||||
this.isLoading = true;
|
||||
|
||||
eventHub.$emit('postAction', this.retryUrl);
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
methods: {
|
||||
onClick() {
|
||||
this.isLoading = true;
|
||||
|
||||
eventHub.$emit('postAction', this.retryUrl);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="btn hidden-xs hidden-sm"
|
||||
@click="onClick"
|
||||
:disabled="isLoading">
|
||||
:disabled="isLoading"
|
||||
>
|
||||
|
||||
<span v-if="isLastDeployment">
|
||||
{{s__("Environments|Re-deploy")}}
|
||||
{{ s__("Environments|Re-deploy") }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{s__("Environments|Rollback")}}
|
||||
{{ s__("Environments|Rollback") }}
|
||||
</span>
|
||||
|
||||
<loading-icon v-if="isLoading" />
|
||||
|
|
|
|||
|
|
@ -1,53 +1,53 @@
|
|||
<script>
|
||||
/**
|
||||
* Renders the stop "button" that allows stop an environment.
|
||||
* Used in environments table.
|
||||
*/
|
||||
import eventHub from '../event_hub';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
/**
|
||||
* Renders the stop "button" that allows stop an environment.
|
||||
* Used in environments table.
|
||||
*/
|
||||
import eventHub from '../event_hub';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
stopUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
export default {
|
||||
components: {
|
||||
loadingIcon,
|
||||
},
|
||||
},
|
||||
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
|
||||
components: {
|
||||
loadingIcon,
|
||||
},
|
||||
|
||||
computed: {
|
||||
title() {
|
||||
return 'Stop';
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClick() {
|
||||
// eslint-disable-next-line no-alert
|
||||
if (confirm('Are you sure you want to stop this environment?')) {
|
||||
this.isLoading = true;
|
||||
|
||||
$(this.$el).tooltip('destroy');
|
||||
|
||||
eventHub.$emit('postAction', this.stopUrl);
|
||||
}
|
||||
props: {
|
||||
stopUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
title() {
|
||||
return 'Stop';
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClick() {
|
||||
// eslint-disable-next-line no-alert
|
||||
if (confirm('Are you sure you want to stop this environment?')) {
|
||||
this.isLoading = true;
|
||||
|
||||
$(this.$el).tooltip('destroy');
|
||||
|
||||
eventHub.$emit('postAction', this.stopUrl);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<button
|
||||
|
|
@ -58,10 +58,13 @@ export default {
|
|||
@click="onClick"
|
||||
:disabled="isLoading"
|
||||
:title="title"
|
||||
:aria-label="title">
|
||||
:aria-label="title"
|
||||
>
|
||||
<i
|
||||
class="fa fa-stop stop-env-icon"
|
||||
aria-hidden="true" />
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
<loading-icon v-if="isLoading" />
|
||||
</button>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,36 +1,36 @@
|
|||
<script>
|
||||
/**
|
||||
* Renders a terminal button to open a web terminal.
|
||||
* Used in environments table.
|
||||
*/
|
||||
import terminalIconSvg from 'icons/_icon_terminal.svg';
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
/**
|
||||
* Renders a terminal button to open a web terminal.
|
||||
* Used in environments table.
|
||||
*/
|
||||
import terminalIconSvg from 'icons/_icon_terminal.svg';
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
terminalPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
export default {
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
},
|
||||
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
terminalIconSvg,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
title() {
|
||||
return 'Terminal';
|
||||
props: {
|
||||
terminalPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
data() {
|
||||
return {
|
||||
terminalIconSvg,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
title() {
|
||||
return 'Terminal';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<a
|
||||
|
|
@ -40,6 +40,7 @@ export default {
|
|||
:title="title"
|
||||
:aria-label="title"
|
||||
:href="terminalPath"
|
||||
v-html="terminalIconSvg">
|
||||
v-html="terminalIconSvg"
|
||||
>
|
||||
</a>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,15 @@
|
|||
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
emptyState,
|
||||
},
|
||||
|
||||
mixins: [
|
||||
CIPaginationMixin,
|
||||
environmentsMixin,
|
||||
],
|
||||
|
||||
props: {
|
||||
endpoint: {
|
||||
type: String,
|
||||
|
|
@ -37,14 +46,6 @@
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
emptyState,
|
||||
},
|
||||
|
||||
mixins: [
|
||||
CIPaginationMixin,
|
||||
environmentsMixin,
|
||||
],
|
||||
|
||||
created() {
|
||||
eventHub.$on('toggleFolder', this.toggleFolder);
|
||||
|
|
@ -95,15 +96,17 @@
|
|||
:tabs="tabs"
|
||||
@onChangeTab="onChangeTab"
|
||||
scope="environments"
|
||||
/>
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="canCreateEnvironment && !isLoading"
|
||||
class="nav-controls">
|
||||
class="nav-controls"
|
||||
>
|
||||
<a
|
||||
:href="newEnvironmentPath"
|
||||
class="btn btn-create">
|
||||
{{s__("Environments|New environment")}}
|
||||
class="btn btn-create"
|
||||
>
|
||||
{{ s__("Environments|New environment") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -116,13 +119,13 @@
|
|||
:can-read-environment="canReadEnvironment"
|
||||
@onChangePage="onChangePage"
|
||||
>
|
||||
<empty-state
|
||||
<empty-state
|
||||
slot="emptyState"
|
||||
v-if="!isLoading && state.environments.length === 0"
|
||||
:new-path="newEnvironmentPath"
|
||||
:help-path="helpPagePath"
|
||||
:can-create-environment="canCreateEnvironment"
|
||||
/>
|
||||
/>
|
||||
</container>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -30,63 +30,96 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
folderUrl(model) {
|
||||
return `${window.location.pathname}/folders/${model.folderName}`;
|
||||
},
|
||||
shouldRenderFolderContent(env) {
|
||||
return env.isFolder &&
|
||||
env.isOpen &&
|
||||
env.children &&
|
||||
env.children.length > 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="ci-table" role="grid">
|
||||
<div class="gl-responsive-table-row table-row-header" role="row">
|
||||
<div class="table-section section-10 environments-name" role="columnheader">
|
||||
{{s__("Environments|Environment")}}
|
||||
<div
|
||||
class="ci-table"
|
||||
role="grid"
|
||||
>
|
||||
<div
|
||||
class="gl-responsive-table-row table-row-header"
|
||||
role="row"
|
||||
>
|
||||
<div
|
||||
class="table-section section-10 environments-name"
|
||||
role="columnheader"
|
||||
>
|
||||
{{ s__("Environments|Environment") }}
|
||||
</div>
|
||||
<div class="table-section section-10 environments-deploy" role="columnheader">
|
||||
{{s__("Environments|Deployment")}}
|
||||
<div
|
||||
class="table-section section-10 environments-deploy"
|
||||
role="columnheader"
|
||||
>
|
||||
{{ s__("Environments|Deployment") }}
|
||||
</div>
|
||||
<div class="table-section section-15 environments-build" role="columnheader">
|
||||
{{s__("Environments|Job")}}
|
||||
<div
|
||||
class="table-section section-15 environments-build"
|
||||
role="columnheader"
|
||||
>
|
||||
{{ s__("Environments|Job") }}
|
||||
</div>
|
||||
<div class="table-section section-25 environments-commit" role="columnheader">
|
||||
{{s__("Environments|Commit")}}
|
||||
<div
|
||||
class="table-section section-25 environments-commit"
|
||||
role="columnheader"
|
||||
>
|
||||
{{ s__("Environments|Commit") }}
|
||||
</div>
|
||||
<div class="table-section section-10 environments-date" role="columnheader">
|
||||
{{s__("Environments|Updated")}}
|
||||
<div
|
||||
class="table-section section-10 environments-date"
|
||||
role="columnheader"
|
||||
>
|
||||
{{ s__("Environments|Updated") }}
|
||||
</div>
|
||||
</div>
|
||||
<template
|
||||
v-for="model in environments"
|
||||
v-bind:model="model">
|
||||
v-for="(model, i) in environments"
|
||||
:model="model">
|
||||
<div
|
||||
is="environment-item"
|
||||
:model="model"
|
||||
:can-create-deployment="canCreateDeployment"
|
||||
:can-read-environment="canReadEnvironment"
|
||||
/>
|
||||
:key="i"
|
||||
/>
|
||||
|
||||
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
|
||||
<div v-if="model.isLoadingFolderContent">
|
||||
<template
|
||||
v-if="shouldRenderFolderContent(model)"
|
||||
>
|
||||
<div
|
||||
v-if="model.isLoadingFolderContent"
|
||||
:key="`loading-item-${i}`">
|
||||
<loading-icon size="2" />
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div
|
||||
is="environment-item"
|
||||
v-for="children in model.children"
|
||||
v-for="(children, index) in model.children"
|
||||
:model="children"
|
||||
:can-create-deployment="canCreateDeployment"
|
||||
:can-read-environment="canReadEnvironment"
|
||||
/>
|
||||
:key="`env-item-${i}-${index}`"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div :key="`sub-div-${i}`">
|
||||
<div class="text-center prepend-top-10">
|
||||
<a
|
||||
:href="folderUrl(model)"
|
||||
class="btn btn-default">
|
||||
{{s__("Environments|Show all")}}
|
||||
class="btn btn-default"
|
||||
>
|
||||
{{ s__("Environments|Show all") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@
|
|||
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
environmentsMixin,
|
||||
CIPaginationMixin,
|
||||
],
|
||||
props: {
|
||||
endpoint: {
|
||||
type: String,
|
||||
|
|
@ -25,10 +29,6 @@
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
mixins: [
|
||||
environmentsMixin,
|
||||
CIPaginationMixin,
|
||||
],
|
||||
methods: {
|
||||
successCallback(resp) {
|
||||
this.saveData(resp);
|
||||
|
|
@ -40,17 +40,18 @@
|
|||
<div :class="cssContainerClass">
|
||||
<div
|
||||
class="top-area"
|
||||
v-if="!isLoading">
|
||||
v-if="!isLoading"
|
||||
>
|
||||
|
||||
<h4 class="js-folder-name environments-folder-name">
|
||||
{{s__("Environments|Environments")}} / <b>{{folderName}}</b>
|
||||
{{ s__("Environments|Environments") }} / <b>{{ folderName }}</b>
|
||||
</h4>
|
||||
|
||||
<tabs
|
||||
:tabs="tabs"
|
||||
@onChangeTab="onChangeTab"
|
||||
scope="environments"
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<container
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Common code between environmets app and folder view
|
||||
*/
|
||||
|
||||
import _ from 'underscore';
|
||||
import Visibility from 'visibilityjs';
|
||||
import Poll from '../../lib/utils/poll';
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import _ from 'underscore';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
|
||||
/**
|
||||
* Makes search request for content when user types a value in the search input.
|
||||
|
|
@ -54,32 +55,26 @@ export default class FilterableList {
|
|||
this.listFilterElement.removeEventListener('input', this.debounceFilter);
|
||||
}
|
||||
|
||||
filterResults(queryData) {
|
||||
filterResults(params) {
|
||||
if (this.isBusy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$(this.listHolderElement).fadeTo(250, 0.5);
|
||||
|
||||
return $.ajax({
|
||||
url: this.getFilterEndpoint(),
|
||||
data: queryData,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
context: this,
|
||||
complete: this.onFilterComplete,
|
||||
beforeSend: () => {
|
||||
this.isBusy = true;
|
||||
},
|
||||
success: (response, textStatus, xhr) => {
|
||||
this.onFilterSuccess(response, xhr, queryData);
|
||||
},
|
||||
});
|
||||
this.isBusy = true;
|
||||
|
||||
return axios.get(this.getFilterEndpoint(), {
|
||||
params,
|
||||
}).then((res) => {
|
||||
this.onFilterSuccess(res, params);
|
||||
this.onFilterComplete();
|
||||
}).catch(() => this.onFilterComplete());
|
||||
}
|
||||
|
||||
onFilterSuccess(response, xhr, queryData) {
|
||||
if (response.html) {
|
||||
this.listHolderElement.innerHTML = response.html;
|
||||
onFilterSuccess(response, queryData) {
|
||||
if (response.data.html) {
|
||||
this.listHolderElement.innerHTML = response.data.html;
|
||||
}
|
||||
|
||||
// Change url so if user reload a page - search results are saved
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import _ from 'underscore';
|
||||
import DropLab from '~/droplab/drop_lab';
|
||||
import FilteredSearchContainer from './container';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import _ from 'underscore';
|
||||
import { visitUrl } from '../lib/utils/url_utility';
|
||||
import Flash from '../flash';
|
||||
import FilteredSearchContainer from './container';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import _ from 'underscore';
|
||||
import AjaxCache from '../lib/utils/ajax_cache';
|
||||
import Flash from '../flash';
|
||||
import FilteredSearchContainer from './container';
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ class RecentSearchesRoot {
|
|||
const state = this.store.state;
|
||||
this.vm = new Vue({
|
||||
el: this.wrapperElement,
|
||||
components: {
|
||||
'recent-searches-dropdown-content': RecentSearchesDropdownContent,
|
||||
},
|
||||
data() { return state; },
|
||||
template: `
|
||||
<recent-searches-dropdown-content
|
||||
|
|
@ -40,9 +43,6 @@ class RecentSearchesRoot {
|
|||
:allowed-keys="allowedKeys"
|
||||
/>
|
||||
`,
|
||||
components: {
|
||||
'recent-searches-dropdown-content': RecentSearchesDropdownContent,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ const hideFlash = (flashEl, fadeTransition = true) => {
|
|||
|
||||
flashEl.addEventListener('transitionend', () => {
|
||||
flashEl.remove();
|
||||
if (document.body.classList.contains('flash-shown')) document.body.classList.remove('flash-shown');
|
||||
}, {
|
||||
once: true,
|
||||
passive: true,
|
||||
|
|
@ -64,6 +65,7 @@ const createFlash = function createFlash(
|
|||
parent = document,
|
||||
actionConfig = null,
|
||||
fadeTransition = true,
|
||||
addBodyClass = false,
|
||||
) {
|
||||
const flashContainer = parent.querySelector('.flash-container');
|
||||
|
||||
|
|
@ -86,6 +88,8 @@ const createFlash = function createFlash(
|
|||
|
||||
flashContainer.style.display = 'block';
|
||||
|
||||
if (addBodyClass) document.body.classList.add('flash-shown');
|
||||
|
||||
return flashContainer;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -118,14 +118,14 @@ export const showSubLevelItems = (el) => {
|
|||
moveSubItemsToPosition(el, subItems);
|
||||
};
|
||||
|
||||
export const mouseEnterTopItems = (el) => {
|
||||
export const mouseEnterTopItems = (el, timeout = getHideSubItemsInterval()) => {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
timeoutId = setTimeout(() => {
|
||||
if (currentOpenMenu) hideMenu(currentOpenMenu);
|
||||
|
||||
showSubLevelItems(el);
|
||||
}, getHideSubItemsInterval());
|
||||
}, timeout);
|
||||
};
|
||||
|
||||
export const mouseLeaveTopItem = (el) => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
/* global fuzzaldrinPlus */
|
||||
import _ from 'underscore';
|
||||
import fuzzaldrinPlus from 'fuzzaldrin-plus';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import { visitUrl } from './lib/utils/url_utility';
|
||||
import { isObject } from './lib/utils/type_utility';
|
||||
|
||||
|
|
@ -212,25 +213,17 @@ GitLabDropdownRemote = (function() {
|
|||
};
|
||||
|
||||
GitLabDropdownRemote.prototype.fetchData = function() {
|
||||
return $.ajax({
|
||||
url: this.dataEndpoint,
|
||||
dataType: this.options.dataType,
|
||||
beforeSend: (function(_this) {
|
||||
return function() {
|
||||
if (_this.options.beforeSend) {
|
||||
return _this.options.beforeSend();
|
||||
}
|
||||
};
|
||||
})(this),
|
||||
success: (function(_this) {
|
||||
return function(data) {
|
||||
if (_this.options.success) {
|
||||
return _this.options.success(data);
|
||||
}
|
||||
};
|
||||
})(this)
|
||||
});
|
||||
// Fetch the data through ajax if the data is a string
|
||||
if (this.options.beforeSend) {
|
||||
this.options.beforeSend();
|
||||
}
|
||||
|
||||
// Fetch the data through ajax if the data is a string
|
||||
return axios.get(this.dataEndpoint)
|
||||
.then(({ data }) => {
|
||||
if (this.options.success) {
|
||||
return this.options.success(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return GitLabDropdownRemote;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import flash from '../flash';
|
||||
import { __ } from '../locale';
|
||||
import axios from '../lib/utils/axios_utils';
|
||||
import ContributorsStatGraph from './stat_graph_contributors';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: document.querySelector('.js-graphs-show').dataset.projectGraphPath,
|
||||
dataType: 'json',
|
||||
success(data) {
|
||||
const url = document.querySelector('.js-graphs-show').dataset.projectGraphPath;
|
||||
|
||||
axios.get(url)
|
||||
.then(({ data }) => {
|
||||
const graph = new ContributorsStatGraph();
|
||||
graph.init(data);
|
||||
|
||||
|
|
@ -16,6 +18,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
|
||||
$('.stat-graph').fadeIn();
|
||||
$('.loading-graph').hide();
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => flash(__('Error fetching contributors data.')));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import axios from './lib/utils/axios_utils';
|
||||
import flash from './flash';
|
||||
import { __ } from './locale';
|
||||
|
||||
export default class GroupLabelSubscription {
|
||||
constructor(container) {
|
||||
const $container = $(container);
|
||||
|
|
@ -13,14 +17,12 @@ export default class GroupLabelSubscription {
|
|||
event.preventDefault();
|
||||
|
||||
const url = this.$unsubscribeButtons.attr('data-url');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url,
|
||||
}).done(() => {
|
||||
this.toggleSubscriptionButtons();
|
||||
this.$unsubscribeButtons.removeAttr('data-url');
|
||||
});
|
||||
axios.post(url)
|
||||
.then(() => {
|
||||
this.toggleSubscriptionButtons();
|
||||
this.$unsubscribeButtons.removeAttr('data-url');
|
||||
})
|
||||
.catch(() => flash(__('There was an error when unsubscribing from this label.')));
|
||||
}
|
||||
|
||||
subscribe(event) {
|
||||
|
|
@ -31,12 +33,9 @@ export default class GroupLabelSubscription {
|
|||
|
||||
this.$unsubscribeButtons.attr('data-url', url);
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url,
|
||||
}).done(() => {
|
||||
this.toggleSubscriptionButtons();
|
||||
});
|
||||
axios.post(url)
|
||||
.then(() => this.toggleSubscriptionButtons())
|
||||
.catch(() => flash(__('There was an error when subscribing to this label.')));
|
||||
}
|
||||
|
||||
toggleSubscriptionButtons() {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
<script>
|
||||
/* global Flash */
|
||||
|
||||
import { s__ } from '~/locale';
|
||||
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
|
||||
import modal from '~/vue_shared/components/modal.vue';
|
||||
import { getParameterByName } from '~/lib/utils/common_utils';
|
||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||
|
||||
import eventHub from '../event_hub';
|
||||
import { getParameterByName } from '../../lib/utils/common_utils';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import { COMMON_STR } from '../constants';
|
||||
import { mergeUrlParams } from '../../lib/utils/url_utility';
|
||||
import groupsComponent from './groups.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
loadingIcon,
|
||||
modal,
|
||||
groupsComponent,
|
||||
},
|
||||
props: {
|
||||
|
|
@ -32,6 +36,10 @@ export default {
|
|||
isLoading: true,
|
||||
isSearchEmpty: false,
|
||||
searchEmptyMessage: '',
|
||||
showModal: false,
|
||||
groupLeaveConfirmationMessage: '',
|
||||
targetGroup: null,
|
||||
targetParentGroup: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -42,6 +50,26 @@ export default {
|
|||
return this.store.getPaginationInfo();
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.searchEmptyMessage = this.hideProjects ?
|
||||
COMMON_STR.GROUP_SEARCH_EMPTY : COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
|
||||
|
||||
eventHub.$on('fetchPage', this.fetchPage);
|
||||
eventHub.$on('toggleChildren', this.toggleChildren);
|
||||
eventHub.$on('showLeaveGroupModal', this.showLeaveGroupModal);
|
||||
eventHub.$on('updatePagination', this.updatePagination);
|
||||
eventHub.$on('updateGroups', this.updateGroups);
|
||||
},
|
||||
mounted() {
|
||||
this.fetchAllGroups();
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off('fetchPage', this.fetchPage);
|
||||
eventHub.$off('toggleChildren', this.toggleChildren);
|
||||
eventHub.$off('showLeaveGroupModal', this.showLeaveGroupModal);
|
||||
eventHub.$off('updatePagination', this.updatePagination);
|
||||
eventHub.$off('updateGroups', this.updateGroups);
|
||||
},
|
||||
methods: {
|
||||
fetchGroups({ parentId, page, filterGroupsBy, sortBy, archived, updatePagination }) {
|
||||
return this.service.getGroups(parentId, page, filterGroupsBy, sortBy, archived)
|
||||
|
|
@ -121,14 +149,23 @@ export default {
|
|||
parentGroup.isOpen = false;
|
||||
}
|
||||
},
|
||||
leaveGroup(group, parentGroup) {
|
||||
const targetGroup = group;
|
||||
targetGroup.isBeingRemoved = true;
|
||||
this.service.leaveGroup(targetGroup.leavePath)
|
||||
showLeaveGroupModal(group, parentGroup) {
|
||||
this.targetGroup = group;
|
||||
this.targetParentGroup = parentGroup;
|
||||
this.showModal = true;
|
||||
this.groupLeaveConfirmationMessage = s__(`GroupsTree|Are you sure you want to leave the "${group.fullName}" group?`);
|
||||
},
|
||||
hideLeaveGroupModal() {
|
||||
this.showModal = false;
|
||||
},
|
||||
leaveGroup() {
|
||||
this.showModal = false;
|
||||
this.targetGroup.isBeingRemoved = true;
|
||||
this.service.leaveGroup(this.targetGroup.leavePath)
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
$.scrollTo(0);
|
||||
this.store.removeGroup(targetGroup, parentGroup);
|
||||
this.store.removeGroup(this.targetGroup, this.targetParentGroup);
|
||||
Flash(res.notice, 'notice');
|
||||
})
|
||||
.catch((err) => {
|
||||
|
|
@ -137,7 +174,7 @@ export default {
|
|||
message = COMMON_STR.LEAVE_FORBIDDEN;
|
||||
}
|
||||
Flash(message);
|
||||
targetGroup.isBeingRemoved = false;
|
||||
this.targetGroup.isBeingRemoved = false;
|
||||
});
|
||||
},
|
||||
updatePagination(headers) {
|
||||
|
|
@ -152,26 +189,6 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.searchEmptyMessage = this.hideProjects ?
|
||||
COMMON_STR.GROUP_SEARCH_EMPTY : COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
|
||||
|
||||
eventHub.$on('fetchPage', this.fetchPage);
|
||||
eventHub.$on('toggleChildren', this.toggleChildren);
|
||||
eventHub.$on('leaveGroup', this.leaveGroup);
|
||||
eventHub.$on('updatePagination', this.updatePagination);
|
||||
eventHub.$on('updateGroups', this.updateGroups);
|
||||
},
|
||||
mounted() {
|
||||
this.fetchAllGroups();
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off('fetchPage', this.fetchPage);
|
||||
eventHub.$off('toggleChildren', this.toggleChildren);
|
||||
eventHub.$off('leaveGroup', this.leaveGroup);
|
||||
eventHub.$off('updatePagination', this.updatePagination);
|
||||
eventHub.$off('updateGroups', this.updateGroups);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -190,5 +207,14 @@ export default {
|
|||
:search-empty-message="searchEmptyMessage"
|
||||
:page-info="pageInfo"
|
||||
/>
|
||||
<modal
|
||||
v-show="showModal"
|
||||
:primary-button-label="__('Leave')"
|
||||
kind="warning"
|
||||
:title="__('Are you sure?')"
|
||||
:text="groupLeaveConfirmationMessage"
|
||||
@cancel="hideLeaveGroupModal"
|
||||
@submit="leaveGroup"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,11 @@ export default {
|
|||
return this.parentGroup.childrenCount > MAX_CHILDREN_COUNT;
|
||||
},
|
||||
moreChildrenStats() {
|
||||
return n__('One more item', '%d more items', this.parentGroup.childrenCount - this.parentGroup.children.length);
|
||||
return n__(
|
||||
'One more item',
|
||||
'%d more items',
|
||||
this.parentGroup.childrenCount - this.parentGroup.children.length,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -43,8 +47,9 @@ export default {
|
|||
<i
|
||||
class="fa fa-external-link"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{moreChildrenStats}}
|
||||
>
|
||||
</i>
|
||||
{{ moreChildrenStats }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export default {
|
|||
:id="groupDomId"
|
||||
:class="rowClass"
|
||||
class="group-row"
|
||||
>
|
||||
>
|
||||
<div
|
||||
class="group-row-contents"
|
||||
:class="{ 'project-row-contents': !isGroup }">
|
||||
|
|
@ -88,7 +88,8 @@ export default {
|
|||
:item="group"
|
||||
/>
|
||||
<div
|
||||
class="folder-toggle-wrap">
|
||||
class="folder-toggle-wrap"
|
||||
>
|
||||
<item-caret
|
||||
:is-group-open="group.isOpen"
|
||||
/>
|
||||
|
|
@ -113,13 +114,14 @@ export default {
|
|||
<identicon
|
||||
v-else
|
||||
size-class="s24"
|
||||
:entity-id=group.id
|
||||
:entity-id="group.id"
|
||||
:entity-name="group.name"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="title namespace-title">
|
||||
class="title namespace-title"
|
||||
>
|
||||
<a
|
||||
v-tooltip
|
||||
:href="group.relativePath"
|
||||
|
|
@ -135,7 +137,7 @@ export default {
|
|||
v-if="group.permission"
|
||||
class="user-access-role"
|
||||
>
|
||||
{{group.permission}}
|
||||
{{ group.permission }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -1,47 +1,48 @@
|
|||
<script>
|
||||
import tablePagination from '~/vue_shared/components/table_pagination.vue';
|
||||
import eventHub from '../event_hub';
|
||||
import { getParameterByName } from '../../lib/utils/common_utils';
|
||||
import tablePagination from '~/vue_shared/components/table_pagination.vue';
|
||||
import eventHub from '../event_hub';
|
||||
import { getParameterByName } from '../../lib/utils/common_utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
tablePagination,
|
||||
},
|
||||
props: {
|
||||
groups: {
|
||||
type: Array,
|
||||
required: true,
|
||||
export default {
|
||||
components: {
|
||||
tablePagination,
|
||||
},
|
||||
pageInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
props: {
|
||||
groups: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
pageInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
searchEmpty: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
searchEmptyMessage: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
searchEmpty: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
methods: {
|
||||
change(page) {
|
||||
const filterGroupsParam = getParameterByName('filter_groups');
|
||||
const sortParam = getParameterByName('sort');
|
||||
const archivedParam = getParameterByName('archived');
|
||||
eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam, archivedParam);
|
||||
},
|
||||
},
|
||||
searchEmptyMessage: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
change(page) {
|
||||
const filterGroupsParam = getParameterByName('filter_groups');
|
||||
const sortParam = getParameterByName('sort');
|
||||
const archivedParam = getParameterByName('archived');
|
||||
eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam, archivedParam);
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="groups-list-tree-container">
|
||||
<div
|
||||
v-if="searchEmpty"
|
||||
class="has-no-search-results">
|
||||
{{searchEmptyMessage}}
|
||||
class="has-no-search-results"
|
||||
>
|
||||
{{ searchEmptyMessage }}
|
||||
</div>
|
||||
<group-folder
|
||||
v-if="!searchEmpty"
|
||||
|
|
@ -50,7 +51,7 @@ export default {
|
|||
<table-pagination
|
||||
v-if="!searchEmpty"
|
||||
:change="change"
|
||||
:pageInfo="pageInfo"
|
||||
:page-info="pageInfo"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
<script>
|
||||
import { s__ } from '~/locale';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import modal from '~/vue_shared/components/modal.vue';
|
||||
import eventHub from '../event_hub';
|
||||
import { COMMON_STR } from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
modal,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
|
|
@ -25,11 +22,6 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modalStatus: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
leaveBtnTitle() {
|
||||
return COMMON_STR.LEAVE_BTN_TITLE;
|
||||
|
|
@ -37,17 +29,10 @@ export default {
|
|||
editBtnTitle() {
|
||||
return COMMON_STR.EDIT_BTN_TITLE;
|
||||
},
|
||||
leaveConfirmationMessage() {
|
||||
return s__(`GroupsTree|Are you sure you want to leave the "${this.group.fullName}" group?`);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onLeaveGroup() {
|
||||
this.modalStatus = true;
|
||||
},
|
||||
leaveGroup() {
|
||||
this.modalStatus = false;
|
||||
eventHub.$emit('leaveGroup', this.group, this.parentGroup);
|
||||
eventHub.$emit('showLeaveGroupModal', this.group, this.parentGroup);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -78,14 +63,5 @@ export default {
|
|||
class="leave-group btn no-expand">
|
||||
<icon name="leave"/>
|
||||
</a>
|
||||
<modal
|
||||
v-show="modalStatus"
|
||||
:primary-button-label="__('Leave')"
|
||||
kind="warning"
|
||||
:title="__('Are you sure?')"
|
||||
:text="__('Are you sure you want to leave this group?')"
|
||||
:body="leaveConfirmationMessage"
|
||||
@submit="leaveGroup"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
import icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
props: {
|
||||
isGroupOpen: {
|
||||
type: Boolean,
|
||||
|
|
@ -9,9 +12,6 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
computed: {
|
||||
iconClass() {
|
||||
return this.isGroupOpen ? 'angle-down' : 'angle-right';
|
||||
|
|
|
|||
|
|
@ -1,39 +1,44 @@
|
|||
<script>
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import { ITEM_TYPE, VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE, PROJECT_VISIBILITY_TYPE } from '../constants';
|
||||
import itemStatsValue from './item_stats_value.vue';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import {
|
||||
ITEM_TYPE,
|
||||
VISIBILITY_TYPE_ICON,
|
||||
GROUP_VISIBILITY_TYPE,
|
||||
PROJECT_VISIBILITY_TYPE,
|
||||
} from '../constants';
|
||||
import itemStatsValue from './item_stats_value.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
timeAgoTooltip,
|
||||
itemStatsValue,
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
timeAgoTooltip,
|
||||
itemStatsValue,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
visibilityIcon() {
|
||||
return VISIBILITY_TYPE_ICON[this.item.visibility];
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
visibilityTooltip() {
|
||||
if (this.item.type === ITEM_TYPE.GROUP) {
|
||||
return GROUP_VISIBILITY_TYPE[this.item.visibility];
|
||||
}
|
||||
return PROJECT_VISIBILITY_TYPE[this.item.visibility];
|
||||
computed: {
|
||||
visibilityIcon() {
|
||||
return VISIBILITY_TYPE_ICON[this.item.visibility];
|
||||
},
|
||||
visibilityTooltip() {
|
||||
if (this.item.type === ITEM_TYPE.GROUP) {
|
||||
return GROUP_VISIBILITY_TYPE[this.item.visibility];
|
||||
}
|
||||
return PROJECT_VISIBILITY_TYPE[this.item.visibility];
|
||||
},
|
||||
isProject() {
|
||||
return this.item.type === ITEM_TYPE.PROJECT;
|
||||
},
|
||||
isGroup() {
|
||||
return this.item.type === ITEM_TYPE.GROUP;
|
||||
},
|
||||
},
|
||||
isProject() {
|
||||
return this.item.type === ITEM_TYPE.PROJECT;
|
||||
},
|
||||
isGroup() {
|
||||
return this.item.type === ITEM_TYPE.GROUP;
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -1,52 +1,52 @@
|
|||
<script>
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
cssClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
iconName: {
|
||||
type: String,
|
||||
required: true,
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
cssClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
iconName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
tooltipPlacement: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'bottom',
|
||||
},
|
||||
/**
|
||||
* value could either be number or string
|
||||
* as `memberCount` is always passed as string
|
||||
* while `subgroupCount` & `projectCount`
|
||||
* are always number
|
||||
*/
|
||||
value: {
|
||||
type: [Number, String],
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
tooltipPlacement: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'bottom',
|
||||
computed: {
|
||||
isValuePresent() {
|
||||
return this.value !== '';
|
||||
},
|
||||
},
|
||||
/**
|
||||
* value could either be number or string
|
||||
* as `memberCount` is always passed as string
|
||||
* while `subgroupCount` & `projectCount`
|
||||
* are always number
|
||||
*/
|
||||
value: {
|
||||
type: [Number, String],
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
computed: {
|
||||
isValuePresent() {
|
||||
return this.value !== '';
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -57,12 +57,12 @@ export default {
|
|||
:class="cssClass"
|
||||
:title="title"
|
||||
>
|
||||
<icon :name="iconName"/>
|
||||
<icon :name="iconName" />
|
||||
<span
|
||||
v-if="isValuePresent"
|
||||
class="stat-value"
|
||||
>
|
||||
{{value}}
|
||||
{{ value }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import FilterableList from '~/filterable_list';
|
||||
import eventHub from './event_hub';
|
||||
import { getParameterByName } from '../lib/utils/common_utils';
|
||||
import { normalizeHeaders, getParameterByName } from '../lib/utils/common_utils';
|
||||
|
||||
export default class GroupFilterableList extends FilterableList {
|
||||
constructor({ form, filter, holder, filterEndpoint, pagePath, dropdownSel, filterInputField }) {
|
||||
|
|
@ -94,23 +94,14 @@ export default class GroupFilterableList extends FilterableList {
|
|||
this.form.querySelector(`[name="${this.filterInputField}"]`).value = '';
|
||||
}
|
||||
|
||||
onFilterSuccess(data, xhr, queryData) {
|
||||
onFilterSuccess(res, queryData) {
|
||||
const currentPath = this.getPagePath(queryData);
|
||||
|
||||
const paginationData = {
|
||||
'X-Per-Page': xhr.getResponseHeader('X-Per-Page'),
|
||||
'X-Page': xhr.getResponseHeader('X-Page'),
|
||||
'X-Total': xhr.getResponseHeader('X-Total'),
|
||||
'X-Total-Pages': xhr.getResponseHeader('X-Total-Pages'),
|
||||
'X-Next-Page': xhr.getResponseHeader('X-Next-Page'),
|
||||
'X-Prev-Page': xhr.getResponseHeader('X-Prev-Page'),
|
||||
};
|
||||
|
||||
window.history.replaceState({
|
||||
page: currentPath,
|
||||
}, document.title, currentPath);
|
||||
|
||||
eventHub.$emit('updateGroups', data, Object.prototype.hasOwnProperty.call(queryData, this.filterInputField));
|
||||
eventHub.$emit('updatePagination', paginationData);
|
||||
eventHub.$emit('updateGroups', res.data, Object.prototype.hasOwnProperty.call(queryData, this.filterInputField));
|
||||
eventHub.$emit('updatePagination', normalizeHeaders(res.headers));
|
||||
}
|
||||
}
|
||||
|
|
|
|||