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; | ||||
|       return Promise.resolve(viewer); | ||||
|     } | ||||
| 
 | ||||
|     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); | ||||
|   } | ||||
| 
 | ||||
|   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); | ||||
| 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; | ||||
|           }), | ||||
|     ); | ||||
|   } | ||||
| }; | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| <script> | ||||
| import { s__, sprintf } from '../../locale'; | ||||
| import eventHub from '../event_hub'; | ||||
| import loadingButton from '../../vue_shared/components/loading_button.vue'; | ||||
| import { | ||||
|   /* 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, | ||||
|  | @ -12,9 +13,12 @@ import { | |||
|     REQUEST_LOADING, | ||||
|     REQUEST_SUCCESS, | ||||
|     REQUEST_FAILURE, | ||||
| } from '../constants'; | ||||
|   } from '../constants'; | ||||
| 
 | ||||
| export default { | ||||
|   export default { | ||||
|     components: { | ||||
|       loadingButton, | ||||
|     }, | ||||
|     props: { | ||||
|       id: { | ||||
|         type: String, | ||||
|  | @ -49,9 +53,6 @@ export default { | |||
|         required: false, | ||||
|       }, | ||||
|     }, | ||||
|   components: { | ||||
|     loadingButton, | ||||
|   }, | ||||
|     computed: { | ||||
|       rowJsClass() { | ||||
|         return `js-cluster-application-row-${this.id}`; | ||||
|  | @ -66,7 +67,8 @@ export default { | |||
|         // 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) || | ||||
|         return (this.status !== APPLICATION_INSTALLABLE | ||||
|           && this.status !== APPLICATION_ERROR) || | ||||
|           this.requestStatus === REQUEST_LOADING || | ||||
|           this.requestStatus === REQUEST_SUCCESS; | ||||
|       }, | ||||
|  | @ -78,7 +80,8 @@ export default { | |||
|           this.status === APPLICATION_ERROR | ||||
|         ) { | ||||
|           label = s__('ClusterIntegration|Install'); | ||||
|       } else if (this.status === APPLICATION_SCHEDULED || this.status === APPLICATION_INSTALLING) { | ||||
|         } else if (this.status === APPLICATION_SCHEDULED || | ||||
|           this.status === APPLICATION_INSTALLING) { | ||||
|           label = s__('ClusterIntegration|Installing'); | ||||
|         } else if (this.status === APPLICATION_INSTALLED) { | ||||
|           label = s__('ClusterIntegration|Installed'); | ||||
|  | @ -87,7 +90,8 @@ export default { | |||
|         return label; | ||||
|       }, | ||||
|       hasError() { | ||||
|       return this.status === APPLICATION_ERROR || this.requestStatus === REQUEST_FAILURE; | ||||
|         return this.status === APPLICATION_ERROR || | ||||
|         this.requestStatus === REQUEST_FAILURE; | ||||
|       }, | ||||
|       generalErrorDescription() { | ||||
|         return sprintf( | ||||
|  | @ -102,7 +106,7 @@ export default { | |||
|         eventHub.$emit('installApplication', this.id); | ||||
|       }, | ||||
|     }, | ||||
| }; | ||||
|   }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|  |  | |||
|  | @ -1,9 +1,12 @@ | |||
| <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 { | ||||
|   export default { | ||||
|     components: { | ||||
|       applicationRow, | ||||
|     }, | ||||
|     props: { | ||||
|       applications: { | ||||
|         type: Object, | ||||
|  | @ -13,15 +16,15 @@ export 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}')), { | ||||
|           _.escape(s__(`ClusterIntegration|Install applications on your cluster. | ||||
|   Read more about %{helpLink}`)), | ||||
|           { | ||||
|             helpLink: `<a href="${this.helpPath}"> | ||||
|               ${_.escape(s__('ClusterIntegration|installing applications'))} | ||||
|             </a>`, | ||||
|  | @ -43,11 +46,15 @@ export default { | |||
|         )); | ||||
| 
 | ||||
|         const extraCostParagraph = sprintf( | ||||
|         _.escape(s__('ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}')), { | ||||
|           _.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|GKE pricing'))} | ||||
|           </a>`, | ||||
|               ${_.escape(s__('ClusterIntegration|check the pricing here'))}</a>`, | ||||
|           }, | ||||
|           false, | ||||
|         ); | ||||
|  | @ -69,16 +76,18 @@ export default { | |||
|       }, | ||||
|       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>`, | ||||
|           _.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> | ||||
|  | @ -126,7 +135,10 @@ export default { | |||
|           :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 --> | ||||
|         <!-- | ||||
|           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(); | ||||
|  | @ -110,7 +111,8 @@ | |||
| 
 | ||||
|     <div | ||||
|       class="table-holder" | ||||
|       v-if="shouldRenderTable"> | ||||
|       v-if="shouldRenderTable" | ||||
|     > | ||||
|       <pipelines-table-component | ||||
|         :pipelines="state.pipelines" | ||||
|         :update-graph-dropdown="updateGraphDropdown" | ||||
|  |  | |||
|  | @ -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,28 +44,29 @@ 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', | ||||
|       }); | ||||
|   }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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() { | ||||
|   static sendAjax(url, loading, target, params) { | ||||
|     const $target = $(target); | ||||
| 
 | ||||
|     loading.show(); | ||||
|         return $target.empty(); | ||||
|       }, | ||||
|       success: function(html) { | ||||
|     $target.empty(); | ||||
| 
 | ||||
|     return axios.get(url, { | ||||
|       params, | ||||
|     }).then(({ data }) => { | ||||
|       loading.hide(); | ||||
|         $target.html(html); | ||||
|         var className = '.' + $target[0].className.replace(' ', '.'); | ||||
|       $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,13 +75,10 @@ export default class CreateMergeRequestDropdown { | |||
|   } | ||||
| 
 | ||||
|   checkAbilityToCreateBranch() { | ||||
|     return $.ajax({ | ||||
|       type: 'GET', | ||||
|       dataType: 'json', | ||||
|       url: this.canCreatePath, | ||||
|       beforeSend: () => this.setUnavailableButtonState(), | ||||
|     }) | ||||
|     .done((data) => { | ||||
|     this.setUnavailableButtonState(); | ||||
| 
 | ||||
|     axios.get(this.canCreatePath) | ||||
|       .then(({ data }) => { | ||||
|         this.setUnavailableButtonState(false); | ||||
| 
 | ||||
|         if (data.can_create_branch) { | ||||
|  | @ -94,39 +93,34 @@ export default class CreateMergeRequestDropdown { | |||
|         } else if (data.has_related_branch) { | ||||
|           this.hide(); | ||||
|         } | ||||
|     }).fail(() => { | ||||
|       }) | ||||
|       .catch(() => { | ||||
|         this.unavailable(); | ||||
|         this.disable(); | ||||
|       new Flash('Failed to check if a new branch can be created.'); | ||||
|         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.isCreatingBranch = true; | ||||
| 
 | ||||
|     return axios.post(this.createBranchPath) | ||||
|       .then(({ data }) => { | ||||
|         this.branchCreated = true; | ||||
|         window.location.href = data.url; | ||||
|       }) | ||||
|     .fail(() => new Flash('Failed to create a branch for this issue. Please try again.')); | ||||
|       .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.isCreatingMergeRequest = true; | ||||
| 
 | ||||
|     return axios.post(this.createMrPath) | ||||
|       .then(({ data }) => { | ||||
|         this.mergeRequestCreated = true; | ||||
|         window.location.href = data.url; | ||||
|       }) | ||||
|     .fail(() => new Flash('Failed to create Merge Request. Please try again.')); | ||||
|       .catch(() => Flash('Failed to create Merge Request. Please try again.')); | ||||
|   } | ||||
| 
 | ||||
|   disable() { | ||||
|  | @ -199,18 +193,8 @@ 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) => { | ||||
|     return axios.get(this.refsPath + ref) | ||||
|       .then(({ data }) => { | ||||
|         const branches = data[Object.keys(data)[0]]; | ||||
|         const tags = data[Object.keys(data)[1]]; | ||||
|         let result; | ||||
|  | @ -223,13 +207,17 @@ export default class CreateMergeRequestDropdown { | |||
|           this.suggestedRef = result; | ||||
|         } | ||||
| 
 | ||||
|         this.isGettingRef = false; | ||||
| 
 | ||||
|         return this.updateInputState(target, ref, result); | ||||
|       }) | ||||
|     .fail(() => { | ||||
|       .catch(() => { | ||||
|         this.unavailable(); | ||||
|         this.disable(); | ||||
|         new Flash('Failed to get ref.'); | ||||
| 
 | ||||
|         this.isGettingRef = false; | ||||
| 
 | ||||
|         return false; | ||||
|       }); | ||||
|   } | ||||
|  | @ -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,10 +17,30 @@ | |||
| <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> | ||||
|       -- | ||||
|  |  | |||
|  | @ -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,24 +236,20 @@ 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; | ||||
|     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(); | ||||
|       }); | ||||
|   }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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,12 +126,6 @@ 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'; | ||||
| 
 | ||||
|  | @ -145,11 +140,10 @@ class DueDateSelect { | |||
|     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(() => { | ||||
|     $('.js-remove-due-date-holder').toggleClass('hidden', selectedDateValue.length); | ||||
| 
 | ||||
|     return axios.put(this.issueUpdateURL, this.datePayload) | ||||
|       .then(() => { | ||||
|         if (isDropdown) { | ||||
|           this.$dropdown.trigger('loaded.gl.dropdown'); | ||||
|           this.$dropdown.dropdown('toggle'); | ||||
|  |  | |||
|  | @ -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); | ||||
|  | @ -64,7 +63,7 @@ | |||
|       <table-pagination | ||||
|         v-if="pagination && pagination.totalPages > 1" | ||||
|         :change="onChangePage" | ||||
|         :pageInfo="pagination" | ||||
|         :page-info="pagination" | ||||
|       /> | ||||
|     </div> | ||||
|   </div> | ||||
|  |  | |||
|  | @ -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,18 +1,10 @@ | |||
| <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'; | ||||
| 
 | ||||
| export default { | ||||
|   props: { | ||||
|     actions: { | ||||
|       type: Array, | ||||
|       required: false, | ||||
|       default: () => [], | ||||
|     }, | ||||
|   }, | ||||
|   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 { | ||||
|     directives: { | ||||
|       tooltip, | ||||
|     }, | ||||
|  | @ -20,6 +12,13 @@ export default { | |||
|     components: { | ||||
|       loadingIcon, | ||||
|     }, | ||||
|     props: { | ||||
|       actions: { | ||||
|         type: Array, | ||||
|         required: false, | ||||
|         default: () => [], | ||||
|       }, | ||||
|     }, | ||||
| 
 | ||||
|     data() { | ||||
|       return { | ||||
|  | @ -49,7 +48,7 @@ export default { | |||
|         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,11 +1,14 @@ | |||
| <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 { | ||||
|   export default { | ||||
|     directives: { | ||||
|       tooltip, | ||||
|     }, | ||||
|     props: { | ||||
|       externalUrl: { | ||||
|         type: String, | ||||
|  | @ -13,16 +16,12 @@ export default { | |||
|       }, | ||||
|     }, | ||||
| 
 | ||||
|   directives: { | ||||
|     tooltip, | ||||
|   }, | ||||
| 
 | ||||
|     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,25 +1,25 @@ | |||
| <script> | ||||
| import Timeago from 'timeago.js'; | ||||
| import _ from 'underscore'; | ||||
| import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; | ||||
| import { humanize } from '../../lib/utils/text_utility'; | ||||
| import ActionsComponent from './environment_actions.vue'; | ||||
| import ExternalUrlComponent from './environment_external_url.vue'; | ||||
| import StopComponent from './environment_stop.vue'; | ||||
| import RollbackComponent from './environment_rollback.vue'; | ||||
| import TerminalButtonComponent from './environment_terminal_button.vue'; | ||||
| import MonitoringButtonComponent from './environment_monitoring.vue'; | ||||
| import CommitComponent from '../../vue_shared/components/commit.vue'; | ||||
| import eventHub from '../event_hub'; | ||||
|   import Timeago from 'timeago.js'; | ||||
|   import _ from 'underscore'; | ||||
|   import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; | ||||
|   import { humanize } from '../../lib/utils/text_utility'; | ||||
|   import ActionsComponent from './environment_actions.vue'; | ||||
|   import ExternalUrlComponent from './environment_external_url.vue'; | ||||
|   import StopComponent from './environment_stop.vue'; | ||||
|   import RollbackComponent from './environment_rollback.vue'; | ||||
|   import TerminalButtonComponent from './environment_terminal_button.vue'; | ||||
|   import MonitoringButtonComponent from './environment_monitoring.vue'; | ||||
|   import CommitComponent from '../../vue_shared/components/commit.vue'; | ||||
|   import eventHub from '../event_hub'; | ||||
| 
 | ||||
| /** | ||||
|   /** | ||||
|   * Envrionment Item Component | ||||
|   * | ||||
|   * Renders a table row for each environment. | ||||
|   */ | ||||
| const timeagoInstance = new Timeago(); | ||||
|   const timeagoInstance = new Timeago(); | ||||
| 
 | ||||
| export default { | ||||
|   export default { | ||||
|     components: { | ||||
|       userAvatarLink, | ||||
|       'commit-component': CommitComponent, | ||||
|  | @ -287,7 +287,8 @@ export default { | |||
|         if (this.model && | ||||
|           this.model.last_deployment && | ||||
|           this.model.last_deployment.deployable) { | ||||
|         return `${this.model.last_deployment.deployable.name} #${this.model.last_deployment.deployable.id}`; | ||||
|           const deployable = this.model.last_deployment.deployable; | ||||
|           return `${deployable.name} #${deployable.id}`; | ||||
|         } | ||||
|         return ''; | ||||
|       }, | ||||
|  | @ -417,7 +418,7 @@ export default { | |||
|         eventHub.$emit('toggleFolder', this.model); | ||||
|       }, | ||||
|     }, | ||||
| }; | ||||
|   }; | ||||
| </script> | ||||
| <template> | ||||
|   <div | ||||
|  | @ -427,18 +428,22 @@ export default { | |||
|       'folder-row': model.isFolder, | ||||
|     }" | ||||
|     role="row"> | ||||
|     <div class="table-section section-10" role="gridcell"> | ||||
|     <div | ||||
|       class="table-section section-10" | ||||
|       role="gridcell" | ||||
|     > | ||||
|       <div | ||||
|         v-if="!model.isFolder" | ||||
|         class="table-mobile-header" | ||||
|         role="rowheader"> | ||||
|         {{s__("Environments|Environment")}} | ||||
|         role="rowheader" | ||||
|       > | ||||
|         {{ s__("Environments|Environment") }} | ||||
|       </div> | ||||
|       <a | ||||
|         v-if="!model.isFolder" | ||||
|         class="environment-name flex-truncate-parent table-mobile-content" | ||||
|         :href="environmentPath"> | ||||
|         <span class="flex-truncate-child">{{model.name}}</span> | ||||
|         <span class="flex-truncate-child">{{ model.name }}</span> | ||||
|       </a> | ||||
|       <span | ||||
|         v-else | ||||
|  | @ -450,32 +455,40 @@ export default { | |||
|           <i | ||||
|             v-show="model.isOpen" | ||||
|             class="fa fa-caret-down" | ||||
|             aria-hidden="true" /> | ||||
|             aria-hidden="true" | ||||
|           > | ||||
|           </i> | ||||
|           <i | ||||
|             v-show="!model.isOpen" | ||||
|             class="fa fa-caret-right" | ||||
|             aria-hidden="true"/> | ||||
|             aria-hidden="true" | ||||
|           > | ||||
|           </i> | ||||
|         </span> | ||||
| 
 | ||||
|         <span class="folder-icon"> | ||||
|           <i | ||||
|             class="fa fa-folder" | ||||
|             aria-hidden="true" /> | ||||
|             aria-hidden="true"> | ||||
|           </i> | ||||
|         </span> | ||||
| 
 | ||||
|         <span> | ||||
|           {{model.folderName}} | ||||
|           {{ model.folderName }} | ||||
|         </span> | ||||
| 
 | ||||
|         <span class="badge"> | ||||
|           {{model.size}} | ||||
|           {{ model.size }} | ||||
|         </span> | ||||
|       </span> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="table-section section-10 deployment-column hidden-xs hidden-sm" role="gridcell"> | ||||
|     <div | ||||
|       class="table-section section-10 deployment-column hidden-xs hidden-sm" | ||||
|       role="gridcell" | ||||
|     > | ||||
|       <span v-if="shouldRenderDeploymentID"> | ||||
|         {{deploymentInternalId}} | ||||
|         {{ deploymentInternalId }} | ||||
|       </span> | ||||
| 
 | ||||
|       <span v-if="!model.isFolder && deploymentHasUser"> | ||||
|  | @ -490,22 +503,29 @@ export default { | |||
|       </span> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="table-section section-15 hidden-xs hidden-sm" role="gridcell"> | ||||
|     <div | ||||
|       class="table-section section-15 hidden-xs hidden-sm" | ||||
|       role="gridcell" | ||||
|     > | ||||
|       <a | ||||
|         v-if="shouldRenderBuildName" | ||||
|         class="build-link flex-truncate-parent" | ||||
|         :href="buildPath"> | ||||
|         <span class="flex-truncate-child">{{buildName}}</span> | ||||
|         :href="buildPath" | ||||
|       > | ||||
|         <span class="flex-truncate-child">{{ buildName }}</span> | ||||
|       </a> | ||||
|     </div> | ||||
| 
 | ||||
|     <div | ||||
|       v-if="!model.isFolder" | ||||
|       class="table-section section-25" role="gridcell"> | ||||
|       class="table-section section-25" | ||||
|       role="gridcell" | ||||
|     > | ||||
|       <div | ||||
|         role="rowheader" | ||||
|         class="table-mobile-header"> | ||||
|         {{s__("Environments|Commit")}} | ||||
|         class="table-mobile-header" | ||||
|       > | ||||
|         {{ s__("Environments|Commit") }} | ||||
|       </div> | ||||
|       <div | ||||
|         v-if="hasLastDeploymentKey" | ||||
|  | @ -521,22 +541,24 @@ export default { | |||
|       <div | ||||
|         v-if="!hasLastDeploymentKey" | ||||
|         class="commit-title table-mobile-content"> | ||||
|         {{s__("Environments|No deployments yet")}} | ||||
|         {{ s__("Environments|No deployments yet") }} | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div | ||||
|       v-if="!model.isFolder" | ||||
|       class="table-section section-10" role="gridcell"> | ||||
|       class="table-section section-10" | ||||
|       role="gridcell" | ||||
|     > | ||||
|       <div | ||||
|         role="rowheader" | ||||
|         class="table-mobile-header"> | ||||
|         {{s__("Environments|Updated")}} | ||||
|         {{ s__("Environments|Updated") }} | ||||
|       </div> | ||||
|       <span | ||||
|         v-if="canShowDate" | ||||
|         class="environment-created-date-timeago table-mobile-content"> | ||||
|         {{createdDate}} | ||||
|         {{ createdDate }} | ||||
|       </span> | ||||
|     </div> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,10 +1,14 @@ | |||
| <script> | ||||
| /** | ||||
|   /** | ||||
|   * Renders the Monitoring (Metrics) link in environments table. | ||||
|   */ | ||||
| import tooltip from '../../vue_shared/directives/tooltip'; | ||||
|   import tooltip from '../../vue_shared/directives/tooltip'; | ||||
| 
 | ||||
|   export default { | ||||
|     directives: { | ||||
|       tooltip, | ||||
|     }, | ||||
| 
 | ||||
| export default { | ||||
|     props: { | ||||
|       monitoringUrl: { | ||||
|         type: String, | ||||
|  | @ -12,16 +16,12 @@ export default { | |||
|       }, | ||||
|     }, | ||||
| 
 | ||||
|   directives: { | ||||
|     tooltip, | ||||
|   }, | ||||
| 
 | ||||
|     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,14 +1,18 @@ | |||
| <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'; | ||||
|   import eventHub from '../event_hub'; | ||||
|   import loadingIcon from '../../vue_shared/components/loading_icon.vue'; | ||||
| 
 | ||||
|   export default { | ||||
|     components: { | ||||
|       loadingIcon, | ||||
|     }, | ||||
| 
 | ||||
| export default { | ||||
|     props: { | ||||
|       retryUrl: { | ||||
|         type: String, | ||||
|  | @ -21,10 +25,6 @@ export default { | |||
|       }, | ||||
|     }, | ||||
| 
 | ||||
|   components: { | ||||
|     loadingIcon, | ||||
|   }, | ||||
| 
 | ||||
|     data() { | ||||
|       return { | ||||
|         isLoading: false, | ||||
|  | @ -38,20 +38,21 @@ export default { | |||
|         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,13 +1,21 @@ | |||
| <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'; | ||||
|   import eventHub from '../event_hub'; | ||||
|   import loadingIcon from '../../vue_shared/components/loading_icon.vue'; | ||||
|   import tooltip from '../../vue_shared/directives/tooltip'; | ||||
| 
 | ||||
|   export default { | ||||
|     components: { | ||||
|       loadingIcon, | ||||
|     }, | ||||
| 
 | ||||
|     directives: { | ||||
|       tooltip, | ||||
|     }, | ||||
| 
 | ||||
| export default { | ||||
|     props: { | ||||
|       stopUrl: { | ||||
|         type: String, | ||||
|  | @ -15,20 +23,12 @@ export default { | |||
|       }, | ||||
|     }, | ||||
| 
 | ||||
|   directives: { | ||||
|     tooltip, | ||||
|   }, | ||||
| 
 | ||||
|     data() { | ||||
|       return { | ||||
|         isLoading: false, | ||||
|       }; | ||||
|     }, | ||||
| 
 | ||||
|   components: { | ||||
|     loadingIcon, | ||||
|   }, | ||||
| 
 | ||||
|     computed: { | ||||
|       title() { | ||||
|         return 'Stop'; | ||||
|  | @ -47,7 +47,7 @@ export default { | |||
|         } | ||||
|       }, | ||||
|     }, | ||||
| }; | ||||
|   }; | ||||
| </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,12 +1,16 @@ | |||
| <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'; | ||||
|   import terminalIconSvg from 'icons/_icon_terminal.svg'; | ||||
|   import tooltip from '../../vue_shared/directives/tooltip'; | ||||
| 
 | ||||
|   export default { | ||||
|     directives: { | ||||
|       tooltip, | ||||
|     }, | ||||
| 
 | ||||
| export default { | ||||
|     props: { | ||||
|       terminalPath: { | ||||
|         type: String, | ||||
|  | @ -15,10 +19,6 @@ export default { | |||
|       }, | ||||
|     }, | ||||
| 
 | ||||
|   directives: { | ||||
|     tooltip, | ||||
|   }, | ||||
| 
 | ||||
|     data() { | ||||
|       return { | ||||
|         terminalIconSvg, | ||||
|  | @ -30,7 +30,7 @@ export default { | |||
|         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); | ||||
|  | @ -99,11 +100,13 @@ | |||
| 
 | ||||
|       <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> | ||||
|  |  | |||
|  | @ -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,10 +40,11 @@ | |||
|   <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 | ||||
|  |  | |||
|  | @ -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); | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     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(); | ||||
|     if (this.options.beforeSend) { | ||||
|       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
 | ||||
|     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(() => { | ||||
|     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> | ||||
|  |  | |||
|  | @ -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,9 +1,9 @@ | |||
| <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 { | ||||
|   export default { | ||||
|     components: { | ||||
|       tablePagination, | ||||
|     }, | ||||
|  | @ -33,15 +33,16 @@ export default { | |||
|         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,10 +1,15 @@ | |||
| <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 { | ||||
|   export default { | ||||
|     components: { | ||||
|       icon, | ||||
|       timeAgoTooltip, | ||||
|  | @ -33,7 +38,7 @@ export default { | |||
|         return this.item.type === ITEM_TYPE.GROUP; | ||||
|       }, | ||||
|     }, | ||||
| }; | ||||
|   }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|  |  | |||
|  | @ -1,8 +1,14 @@ | |||
| <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 { | ||||
|   export default { | ||||
|     components: { | ||||
|       icon, | ||||
|     }, | ||||
|     directives: { | ||||
|       tooltip, | ||||
|     }, | ||||
|     props: { | ||||
|       title: { | ||||
|         type: String, | ||||
|  | @ -35,18 +41,12 @@ export default { | |||
|         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)); | ||||
|   } | ||||
| } | ||||
|  |  | |||