Merge remote-tracking branch 'upstream/master' into use-update-runner-service

* upstream/master: (488 commits)
  Remove duplicate CHANGELOG.md entries for 8.16.5
  Update CHANGELOG.md for 8.14.9
  Update CHANGELOG.md for 8.15.6
  #27631: Add missing top-area div to activity header page
  Update CHANGELOG.md for 8.16.5
  Update CHANGELOG.md for 8.16.5
  Update CHANGELOG.md for 8.16.5
  Fix yarn lock and package.json mismatch caused by MR 9133
  sync yarn.lock with recent changes to package.json
  Add changelog
  Fix z index bugs
  Add Links to Branches in Calendar Activity
  SidekiqStatus need to be qualified in some cases
  Replace static fixture for behaviors/requires_input_spec.js (!9162)
  API: Consolidate /projects endpoint
  Add MySQL info in install requirements
  Fix timezone on issue boards due date
  Use Gitlab::Database.with_connection_pool from !9192
  Disconnect the pool after done
  Use threads directly, introduce pool later:
  ...
This commit is contained in:
Lin Jen-Shin 2017-02-15 16:13:14 +08:00
commit a065ee341c
855 changed files with 17792 additions and 15257 deletions

View File

@ -12,12 +12,18 @@
"localStorage": false "localStorage": false
}, },
"plugins": [ "plugins": [
"filenames" "filenames",
"import"
], ],
"settings": {
"import/resolver": {
"webpack": {
"config": "./config/webpack.config.js"
}
}
},
"rules": { "rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"], "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"],
"no-multiple-empty-lines": ["error", { "max": 1 }], "no-multiple-empty-lines": ["error", { "max": 1 }]
"import/no-extraneous-dependencies": "off",
"import/no-unresolved": "off"
} }
} }

View File

@ -1,3 +1,4 @@
*.erb *.erb
lib/gitlab/sanitizers/svg/whitelist.rb lib/gitlab/sanitizers/svg/whitelist.rb
lib/gitlab/diff/position_tracer.rb lib/gitlab/diff/position_tracer.rb
app/policies/project_policy.rb

View File

@ -107,7 +107,10 @@ setup-test-env:
<<: *dedicated-runner <<: *dedicated-runner
stage: prepare stage: prepare
script: script:
- npm install - node --version
- yarn --version
- yarn install --pure-lockfile
- yarn check # ensure that yarn.lock matches package.json
- bundle exec rake gitlab:assets:compile - bundle exec rake gitlab:assets:compile
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
artifacts: artifacts:
@ -163,64 +166,7 @@ spinach 7 10: *spinach-knapsack
spinach 8 10: *spinach-knapsack spinach 8 10: *spinach-knapsack
spinach 9 10: *spinach-knapsack spinach 9 10: *spinach-knapsack
# Execute all testing suites against Ruby 2.1
.ruby-21: &ruby-21
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.1-git-2.7-phantomjs-2.1"
<<: *use-db
only:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- master@gitlab/gitlabhq
- master@gitlab/gitlab-ee
cache:
key: "ruby21"
paths:
- vendor/ruby
.rspec-knapsack-ruby21: &rspec-knapsack-ruby21
<<: *rspec-knapsack
<<: *dedicated-runner
<<: *ruby-21
.spinach-knapsack-ruby21: &spinach-knapsack-ruby21
<<: *spinach-knapsack
<<: *dedicated-runner
<<: *ruby-21
rspec 0 20 ruby21: *rspec-knapsack-ruby21
rspec 1 20 ruby21: *rspec-knapsack-ruby21
rspec 2 20 ruby21: *rspec-knapsack-ruby21
rspec 3 20 ruby21: *rspec-knapsack-ruby21
rspec 4 20 ruby21: *rspec-knapsack-ruby21
rspec 5 20 ruby21: *rspec-knapsack-ruby21
rspec 6 20 ruby21: *rspec-knapsack-ruby21
rspec 7 20 ruby21: *rspec-knapsack-ruby21
rspec 8 20 ruby21: *rspec-knapsack-ruby21
rspec 9 20 ruby21: *rspec-knapsack-ruby21
rspec 10 20 ruby21: *rspec-knapsack-ruby21
rspec 11 20 ruby21: *rspec-knapsack-ruby21
rspec 12 20 ruby21: *rspec-knapsack-ruby21
rspec 13 20 ruby21: *rspec-knapsack-ruby21
rspec 14 20 ruby21: *rspec-knapsack-ruby21
rspec 15 20 ruby21: *rspec-knapsack-ruby21
rspec 16 20 ruby21: *rspec-knapsack-ruby21
rspec 17 20 ruby21: *rspec-knapsack-ruby21
rspec 18 20 ruby21: *rspec-knapsack-ruby21
rspec 19 20 ruby21: *rspec-knapsack-ruby21
spinach 0 10 ruby21: *spinach-knapsack-ruby21
spinach 1 10 ruby21: *spinach-knapsack-ruby21
spinach 2 10 ruby21: *spinach-knapsack-ruby21
spinach 3 10 ruby21: *spinach-knapsack-ruby21
spinach 4 10 ruby21: *spinach-knapsack-ruby21
spinach 5 10 ruby21: *spinach-knapsack-ruby21
spinach 6 10 ruby21: *spinach-knapsack-ruby21
spinach 7 10 ruby21: *spinach-knapsack-ruby21
spinach 8 10 ruby21: *spinach-knapsack-ruby21
spinach 9 10 ruby21: *spinach-knapsack-ruby21
# Other generic tests # Other generic tests
.ruby-static-analysis: &ruby-static-analysis .ruby-static-analysis: &ruby-static-analysis
variables: variables:
SIMPLECOV: "false" SIMPLECOV: "false"
@ -243,6 +189,7 @@ rubocop:
rake haml_lint: *exec rake haml_lint: *exec
rake scss_lint: *exec rake scss_lint: *exec
rake config_lint: *exec
rake brakeman: *exec rake brakeman: *exec
rake flay: *exec rake flay: *exec
license_finder: *exec license_finder: *exec
@ -302,13 +249,12 @@ karma:
<<: *use-db <<: *use-db
<<: *dedicated-runner <<: *dedicated-runner
script: script:
- npm link istanbul
- bundle exec rake karma - bundle exec rake karma
artifacts: artifacts:
name: coverage-javascript name: coverage-javascript
expire_in: 31d expire_in: 31d
paths: paths:
- coverage-javascript/default/ - coverage-javascript/
lint-doc: lint-doc:
stage: test stage: test
@ -381,11 +327,9 @@ lint:javascript:
paths: paths:
- node_modules/ - node_modules/
stage: test stage: test
image: "node:7.1" before_script: []
before_script:
- npm install
script: script:
- npm --silent run eslint - yarn run eslint
lint:javascript:report: lint:javascript:report:
<<: *dedicated-runner <<: *dedicated-runner
@ -393,12 +337,10 @@ lint:javascript:report:
paths: paths:
- node_modules/ - node_modules/
stage: post-test stage: post-test
image: "node:7.1" before_script: []
before_script:
- npm install
script: script:
- find app/ spec/ -name '*.js' -or -name '*.js.es6' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files - find app/ spec/ -name '*.js' -or -name '*.js.es6' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
- npm --silent run eslint-report || true # ignore exit code - yarn run eslint-report || true # ignore exit code
artifacts: artifacts:
name: eslint-report name: eslint-report
expire_in: 31d expire_in: 31d
@ -450,9 +392,9 @@ pages:
script: script:
- mv public/ .public/ - mv public/ .public/
- mkdir public/ - mkdir public/
- mv coverage public/coverage-ruby - mv coverage/ public/coverage-ruby/ || true
- mv coverage-javascript/default/ public/coverage-javascript/ - mv coverage-javascript/ public/coverage-javascript/ || true
- mv eslint-report.html public/ - mv eslint-report.html public/ || true
artifacts: artifacts:
paths: paths:
- public - public

View File

@ -6,14 +6,14 @@
(How one can reproduce the issue - this is very important) (How one can reproduce the issue - this is very important)
### Expected behavior ### What is the current *bug* behavior?
(What you should see instead)
### Actual behavior
(What actually happens) (What actually happens)
### What is the expected *correct* behavior?
(What you should see instead)
### Relevant logs and/or screenshots ### Relevant logs and/or screenshots
(Paste any relevant logs - please use code blocks (```) to format console output, (Paste any relevant logs - please use code blocks (```) to format console output,
@ -23,6 +23,14 @@ logs, and code as it's very hard to read otherwise.)
(If you are reporting a bug on GitLab.com, write: This bug happens on GitLab.com) (If you are reporting a bug on GitLab.com, write: This bug happens on GitLab.com)
#### Results of GitLab environment info
(For installations with omnibus-gitlab package run and paste the output of:
`sudo gitlab-rake gitlab:env:info`)
(For installations from source run and paste the output of:
`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
#### Results of GitLab application Check #### Results of GitLab application Check
(For installations with omnibus-gitlab package run and paste the output of: (For installations with omnibus-gitlab package run and paste the output of:
@ -33,14 +41,6 @@ logs, and code as it's very hard to read otherwise.)
(we will only investigate if the tests are passing) (we will only investigate if the tests are passing)
#### Results of GitLab environment info
(For installations with omnibus-gitlab package run and paste the output of:
`sudo gitlab-rake gitlab:env:info`)
(For installations from source run and paste the output of:
`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
### Possible fixes ### Possible fixes
(If you can, link to the line of code that might be responsible for the problem) (If you can, link to the line of code that might be responsible for the problem)

View File

@ -5,7 +5,7 @@ require:
inherit_from: .rubocop_todo.yml inherit_from: .rubocop_todo.yml
AllCops: AllCops:
TargetRubyVersion: 2.1 TargetRubyVersion: 2.3
# Cop names are not d§splayed in offense messages by default. Change behavior # Cop names are not d§splayed in offense messages by default. Change behavior
# by overriding DisplayCopNames, or by giving the -D/--display-cop-names # by overriding DisplayCopNames, or by giving the -D/--display-cop-names
# option. # option.
@ -31,8 +31,7 @@ AllCops:
- 'lib/gitlab/seeder.rb' - 'lib/gitlab/seeder.rb'
- 'generator_templates/**/*' - 'generator_templates/**/*'
# Style #######################################################################
##################### Style ##################################
# Check indentation of private/protected visibility modifiers. # Check indentation of private/protected visibility modifiers.
Style/AccessModifierIndentation: Style/AccessModifierIndentation:
@ -340,6 +339,10 @@ Style/OpMethod:
Style/ParenthesesAroundCondition: Style/ParenthesesAroundCondition:
Enabled: true Enabled: true
# Checks for an obsolete RuntimeException argument in raise/fail.
Style/RedundantException:
Enabled: true
# Checks for parentheses that seem not to serve any purpose. # Checks for parentheses that seem not to serve any purpose.
Style/RedundantParentheses: Style/RedundantParentheses:
Enabled: true Enabled: true
@ -471,7 +474,7 @@ Style/WhileUntilModifier:
Style/WordArray: Style/WordArray:
Enabled: false Enabled: false
#################### Metrics ################################ # Metrics #####################################################################
# A calculated magnitude based on number of assignments, # A calculated magnitude based on number of assignments,
# branches, and conditions. # branches, and conditions.
@ -516,8 +519,7 @@ Metrics/PerceivedComplexity:
Enabled: true Enabled: true
Max: 18 Max: 18
# Lint ########################################################################
#################### Lint ################################
# Checks for useless access modifiers. # Checks for useless access modifiers.
Lint/UselessAccessModifier: Lint/UselessAccessModifier:
@ -570,6 +572,10 @@ Lint/ElseLayout:
Lint/EmptyEnsure: Lint/EmptyEnsure:
Enabled: true Enabled: true
# Checks for the presence of `when` branches without a body.
Lint/EmptyWhen:
Enabled: true
# Align ends correctly. # Align ends correctly.
Lint/EndAlignment: Lint/EndAlignment:
Enabled: true Enabled: true
@ -679,8 +685,7 @@ Lint/UselessSetterCall:
Lint/Void: Lint/Void:
Enabled: true Enabled: true
# Performance #################################################################
##################### Performance ############################
# Use `casecmp` rather than `downcase ==`. # Use `casecmp` rather than `downcase ==`.
Performance/Casecmp: Performance/Casecmp:
@ -718,8 +723,7 @@ Performance/StringReplacement:
Performance/TimesMap: Performance/TimesMap:
Enabled: true Enabled: true
# Rails #######################################################################
##################### Rails ##################################
# Enables Rails cops. # Enables Rails cops.
Rails: Rails:
@ -767,12 +771,16 @@ Rails/ReadWriteAttribute:
Rails/ScopeArgs: Rails/ScopeArgs:
Enabled: true Enabled: true
##################### RSpec ################################## # RSpec #######################################################################
# Check that instances are not being stubbed globally. # Check that instances are not being stubbed globally.
RSpec/AnyInstance: RSpec/AnyInstance:
Enabled: false Enabled: false
# Check for expectations where `be(...)` can replace `eql(...)`.
RSpec/BeEql:
Enabled: false
# Check that the first argument to the top level describe is the tested class or # Check that the first argument to the top level describe is the tested class or
# module. # module.
RSpec/DescribeClass: RSpec/DescribeClass:
@ -801,6 +809,10 @@ RSpec/ExampleWording:
not: does not not: does not
IgnoredWords: [] IgnoredWords: []
# Checks for `expect(...)` calls containing literal values.
RSpec/ExpectActual:
Enabled: true
# Checks the file and folder naming of the spec file. # Checks the file and folder naming of the spec file.
RSpec/FilePath: RSpec/FilePath:
Enabled: false Enabled: false
@ -828,3 +840,9 @@ RSpec/NotToNot:
# Prefer using verifying doubles over normal doubles. # Prefer using verifying doubles over normal doubles.
RSpec/VerifiedDoubles: RSpec/VerifiedDoubles:
Enabled: false Enabled: false
# Custom ######################################################################
# Disallow the `git` and `github` arguments in the Gemfile.
GemFetcher:
Enabled: true

View File

@ -21,10 +21,6 @@ Lint/AmbiguousRegexpLiteral:
Lint/AssignmentInCondition: Lint/AssignmentInCondition:
Enabled: false Enabled: false
# Offense count: 1
Lint/EmptyWhen:
Enabled: false
# Offense count: 20 # Offense count: 20
Lint/HandleExceptions: Lint/HandleExceptions:
Enabled: false Enabled: false
@ -80,19 +76,11 @@ Performance/RedundantMatch:
Performance/RedundantMerge: Performance/RedundantMerge:
Enabled: false Enabled: false
# Offense count: 7
RSpec/BeEql:
Enabled: false
# Offense count: 15 # Offense count: 15
# Configuration parameters: CustomIncludeMethods. # Configuration parameters: CustomIncludeMethods.
RSpec/EmptyExampleGroup: RSpec/EmptyExampleGroup:
Enabled: false Enabled: false
# Offense count: 24
RSpec/ExpectActual:
Enabled: false
# Offense count: 58 # Offense count: 58
# Configuration parameters: EnforcedStyle, SupportedStyles. # Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: implicit, each, example # SupportedStyles: implicit, each, example
@ -424,11 +412,6 @@ Style/RaiseArgs:
Style/RedundantBegin: Style/RedundantBegin:
Enabled: false Enabled: false
# Offense count: 1
# Cop supports --auto-correct.
Style/RedundantException:
Enabled: false
# Offense count: 29 # Offense count: 29
# Cop supports --auto-correct. # Cop supports --auto-correct.
Style/RedundantFreeze: Style/RedundantFreeze:

View File

@ -2,6 +2,13 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 8.16.5 (2017-02-14)
- Patch Asciidocs rendering to block XSS.
- Fix XSS vulnerability in SVG attachments.
- Prevent the GitHub importer from assigning labels and comments to merge requests or issues belonging to other projects.
- Patch XSS vulnerability in RDOC support.
## 8.16.4 (2017-02-02) ## 8.16.4 (2017-02-02)
- Support non-ASCII characters in GFM autocomplete. !8729 - Support non-ASCII characters in GFM autocomplete. !8729
@ -174,6 +181,13 @@ entry.
- Add margin to markdown math blocks. - Add margin to markdown math blocks.
- Add hover state to MR comment reply button. - Add hover state to MR comment reply button.
## 8.15.6 (2017-02-14)
- Patch Asciidocs rendering to block XSS.
- Fix XSS vulnerability in SVG attachments.
- Prevent the GitHub importer from assigning labels and comments to merge requests or issues belonging to other projects.
- Patch XSS vulnerability in RDOC support.
## 8.15.4 (2017-01-09) ## 8.15.4 (2017-01-09)
- Make successful pipeline emails off for watchers. !8176 - Make successful pipeline emails off for watchers. !8176
@ -437,6 +451,13 @@ entry.
- Whitelist next project names: help, ci, admin, search. !8227 - Whitelist next project names: help, ci, admin, search. !8227
- Adds back CSS for progress-bars. !8237 - Adds back CSS for progress-bars. !8237
## 8.14.9 (2017-02-14)
- Patch Asciidocs rendering to block XSS.
- Fix XSS vulnerability in SVG attachments.
- Prevent the GitHub importer from assigning labels and comments to merge requests or issues belonging to other projects.
- Patch XSS vulnerability in RDOC support.
## 8.14.8 (2017-01-25) ## 8.14.8 (2017-01-25)
- Accept environment variables from the `pre-receive` script. !7967 - Accept environment variables from the `pre-receive` script. !7967

View File

@ -15,6 +15,7 @@
- [Issue weight](#issue-weight) - [Issue weight](#issue-weight)
- [Regression issues](#regression-issues) - [Regression issues](#regression-issues)
- [Technical debt](#technical-debt) - [Technical debt](#technical-debt)
- [Stewardship](#stewardship)
- [Merge requests](#merge-requests) - [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines) - [Merge request guidelines](#merge-request-guidelines)
- [Contribution acceptance criteria](#contribution-acceptance-criteria) - [Contribution acceptance criteria](#contribution-acceptance-criteria)
@ -230,6 +231,21 @@ for a release by the appropriate person.
Make sure to mention the merge request that the `technical debt` issue is Make sure to mention the merge request that the `technical debt` issue is
associated with in the description of the issue. associated with in the description of the issue.
### Stewardship
For issues related to the open source stewardship of GitLab,
there is the ~"stewardship" label.
This label is to be used for issues in which the stewardship of GitLab
is a topic of discussion. For instance if GitLab Inc. is planning to remove
features from GitLab CE to make exclusive in GitLab EE, related issues
would be labelled with ~"stewardship".
A recent example of this was the issue for
[bringing the time tracking API to GitLab CE][time-tracking-issue].
[time-tracking-issue]: https://gitlab.com/gitlab-org/gitlab-ce/issues/25517#note_20019084
## Merge requests ## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests, We welcome merge requests with fixes and improvements to GitLab code, tests,
@ -414,7 +430,7 @@ merge request:
1. [Newlines styleguide][newlines-styleguide] 1. [Newlines styleguide][newlines-styleguide]
1. [Testing](doc/development/testing.md) 1. [Testing](doc/development/testing.md)
1. [JavaScript (ES6)](https://github.com/airbnb/javascript) 1. [JavaScript (ES6)](https://github.com/airbnb/javascript)
1. [JavaScript (ES5)](https://github.com/airbnb/javascript/tree/master/es5) 1. [JavaScript (ES5)](https://github.com/airbnb/javascript/tree/es5-deprecated/es5)
1. [SCSS styleguide][scss-styleguide] 1. [SCSS styleguide][scss-styleguide]
1. [Shell commands](doc/development/shell_commands.md) created by GitLab 1. [Shell commands](doc/development/shell_commands.md) created by GitLab
contributors to enhance security contributors to enhance security

View File

@ -1 +1 @@
0.2.4 0.3.0

View File

@ -29,6 +29,7 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.2' gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.4.1' gem 'omniauth-google-oauth2', '~> 0.4.1'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.7.0' gem 'omniauth-saml', '~> 1.7.0'
gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0'
@ -284,7 +285,7 @@ group :development, :test do
gem 'rspec-retry', '~> 0.4.5' gem 'rspec-retry', '~> 0.4.5'
gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2' gem 'spinach-rerun-reporter', '~> 0.0.2'
gem 'rspec_profiling' gem 'rspec_profiling', '~> 0.0.5'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0' gem 'minitest', '~> 5.7.0'

View File

@ -483,6 +483,8 @@ GEM
omniauth-oauth2 (1.3.1) omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0) oauth2 (~> 1.0)
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-oauth2-generic (0.2.2)
omniauth-oauth2 (~> 1.0)
omniauth-saml (1.7.0) omniauth-saml (1.7.0)
omniauth (~> 1.3) omniauth (~> 1.3)
ruby-saml (~> 1.4) ruby-saml (~> 1.4)
@ -638,7 +640,7 @@ GEM
rspec-retry (0.4.5) rspec-retry (0.4.5)
rspec-core rspec-core
rspec-support (3.5.0) rspec-support (3.5.0)
rspec_profiling (0.0.4) rspec_profiling (0.0.5)
activerecord activerecord
pg pg
rails rails
@ -738,7 +740,7 @@ GEM
actionpack (>= 4.0) actionpack (>= 4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
sqlite3 (1.3.11) sqlite3 (1.3.13)
stackprof (0.2.10) stackprof (0.2.10)
state_machines (0.4.0) state_machines (0.4.0)
state_machines-activemodel (0.4.0) state_machines-activemodel (0.4.0)
@ -931,6 +933,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.2) omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.4.1) omniauth-google-oauth2 (~> 0.4.1)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.7.0) omniauth-saml (~> 1.7.0)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
@ -962,7 +965,7 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.5.0) rspec-rails (~> 3.5.0)
rspec-retry (~> 0.4.5) rspec-retry (~> 0.4.5)
rspec_profiling rspec_profiling (~> 0.0.5)
rubocop (~> 0.46.0) rubocop (~> 0.46.0)
rubocop-rspec (~> 1.9.1) rubocop-rspec (~> 1.9.1)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
@ -1011,4 +1014,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.14.2 1.14.3

View File

@ -1,4 +1,4 @@
Copyright (c) 2011-2016 GitLab B.V. Copyright (c) 2011-2017 GitLab B.V.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -33,7 +33,7 @@ core team members will mention this person.
### Merge request coaching ### Merge request coaching
Several people from the [GitLab team][team] are helping community members to get Several people from the [GitLab team][team] are helping community members to get
their contributions accepted by meeting our [Definition of done][CONTRIBUTING.md#definition-of-done]. their contributions accepted by meeting our [Definition of done](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#definition-of-done).
What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/. What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/.
@ -59,67 +59,85 @@ star, smile, etc.). Some good tips about code reviews can be found in our
## Feature Freeze ## Feature Freeze
On the 7th of each month, the stable branches for the upcoming release will After the 7th (Pacific Standard Time Zone) of each month, RC1 of the upcoming release is created and deployed to GitLab.com and the stable branch for this release is frozen, which means master is no longer merged into it.
be frozen for major changes. Merge requests may still be merged into master Merge requests may still be merged into master during this period,
during this period. By freezing the stable branches prior to a release there's but they will go into the _next_ release, unless they are manually cherry-picked into the stable branch.
no need to worry about last minute merge requests potentially breaking a lot of By freezing the stable branches 2 weeks prior to a release, we reduce the risk of a last minute merge request potentially breaking things.
things.
What is considered to be a major change is determined on a case by case basis as Once the stable branch is frozen, only fixes for regressions (bugs introduced in that same release)
this definition depends very much on the context of changes. For example, a 5 and security issues will be cherry-picked into the stable branch.
line change might have a big impact on the entire application. Ultimately the Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch.
decision will be made by the maintainers and the release managers. These fixes will be released in the next RC (before the 22nd) or patch release (after the 22nd).
If you think a merge request should go into the upcoming release even though it does not meet these requirements,
you can ask for an exception to be made. Exceptions require sign-off from 3 people besides the developer:
1. a Release Manager
2. an Engineering Lead
3. an Engineering Director, the VP of Engineering, or the CTO
You can find who is who on the [team page](https://about.gitlab.com/team/).
Whether an exception is made is determined by weighing the benefit and urgency of the change
(how important it is to the company that this is released _right now_ instead of in a month)
against the potential negative impact
(things breaking without enough time to comfortably find and fix them before the release on the 22nd).
When in doubt, we err on the side of _not_ cherry-picking.
For example, it is likely that an exception will be made for a trivial 1-5 line performance improvement
(e.g. adding a database index or adding `includes` to a query), but not for a new feature, no matter how relatively small or thoroughly tested.
During the feature freeze all merge requests that are meant to go into the upcoming During the feature freeze all merge requests that are meant to go into the upcoming
release should have the correct milestone assigned _and_ have the label release should have the correct milestone assigned _and_ have the label
~"Pick into Stable" set. Merge requests without a milestone and this label will ~"Pick into Stable" set, so that release managers can find and pick them.
Merge requests without a milestone and this label will
not be merged into any stable branches. not be merged into any stable branches.
## Copy & paste responses ## Copy & paste responses
### Improperly formatted issue ### Improperly formatted issue
Thanks for the issue report. Please reformat your issue to conform to the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). Thanks for the issue report. Please reformat your issue to conform to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Issue report for old version ### Issue report for old version
Thanks for the issue report but we only support issues for the latest stable version of GitLab. I'm closing this issue but if you still experience this problem in the latest stable version, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). Thanks for the issue report but we only support issues for the latest stable version of GitLab. I'm closing this issue but if you still experience this problem in the latest stable version, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Support requests and configuration questions ### Support requests and configuration questions
Thanks for your interest in GitLab. We don't use the issue tracker for support Thanks for your interest in GitLab. We don't use the issue tracker for support
requests and configuration questions. Please check our requests and configuration questions. Please check our
\[getting help\]\(https://about.gitlab.com/getting-help/) page to see all of the available [getting help](https://about.gitlab.com/getting-help/) page to see all of the available
support options. Also, have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) support options. Also, have a look at the [contribution guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md)
for more information. for more information.
### Code format ### Code format
Please use ``` to format console output, logs, and code as it's very hard to read otherwise. Please use \`\`\` to format console output, logs, and code as it's very hard to read otherwise.
### Issue fixed in newer version ### Issue fixed in newer version
Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please [upgrade](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Improperly formatted merge request ### Improperly formatted merge request
Thanks for your interest in improving the GitLab codebase! Please update your merge request according to the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-request-guidelines). Thanks for your interest in improving the GitLab codebase! Please update your merge request according to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-request-guidelines).
### Inactivity close of an issue ### Inactivity close of an issue
It's been at least 2 weeks (and a new release) since we heard from you. I'm closing this issue but if you still experience this problem, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). It's been at least 2 weeks (and a new release) since we heard from you. I'm closing this issue but if you still experience this problem, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Inactivity close of a merge request ### Inactivity close of a merge request
This merge request has been closed because a request for more information has not been reacted to for more than 2 weeks. If you respond and conform to the merge request guidelines in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-requests) we will reopen this merge request. This merge request has been closed because a request for more information has not been reacted to for more than 2 weeks. If you respond and conform to the merge request guidelines in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-requests) we will reopen this merge request.
### Accepting merge requests ### Accepting merge requests
Is there an issue on the Is there an issue on the
\[issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues) that is [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) that is
similar to this? Could you please link it here? similar to this? Could you please link it here?
Please be aware that new functionality that is not marked Please be aware that new functionality that is not marked
\[accepting merge requests\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=Accepting+Merge+Requests) [accepting merge requests](https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=Accepting+Merge+Requests)
might not make it into GitLab. might not make it into GitLab.
### Only accepting merge requests with green tests ### Only accepting merge requests with green tests
@ -134,7 +152,7 @@ rebase with master to see if that solves the issue.
We are currently in the process of closing down the issue tracker on GitHub, to We are currently in the process of closing down the issue tracker on GitHub, to
prevent duplication with the GitLab.com issue tracker. prevent duplication with the GitLab.com issue tracker.
Since this is an older issue I'll be closing this for now. If you think this is Since this is an older issue I'll be closing this for now. If you think this is
still an issue I encourage you to open it on the \[GitLab.com issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues). still an issue I encourage you to open it on the [GitLab.com issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues).
[team]: https://about.gitlab.com/team/ [team]: https://about.gitlab.com/team/
[contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria [contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria

View File

@ -11,7 +11,7 @@
licensePath: "/api/:version/templates/licenses/:key", licensePath: "/api/:version/templates/licenses/:key",
gitignorePath: "/api/:version/templates/gitignores/:key", gitignorePath: "/api/:version/templates/gitignores/:key",
gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key", gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
dockerfilePath: "/api/:version/dockerfiles/:key", dockerfilePath: "/api/:version/templates/dockerfiles/:key",
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key", issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
group: function(group_id, callback) { group: function(group_id, callback) {
var url = Api.buildUrl(Api.groupPath) var url = Api.buildUrl(Api.groupPath)

View File

@ -10,7 +10,6 @@ function requireAll(context) { return context.keys().map(context); }
window.$ = window.jQuery = require('jquery'); window.$ = window.jQuery = require('jquery');
require('jquery-ui/ui/autocomplete'); require('jquery-ui/ui/autocomplete');
require('jquery-ui/ui/datepicker');
require('jquery-ui/ui/draggable'); require('jquery-ui/ui/draggable');
require('jquery-ui/ui/effect-highlight'); require('jquery-ui/ui/effect-highlight');
require('jquery-ui/ui/sortable'); require('jquery-ui/ui/sortable');
@ -21,7 +20,7 @@ require('vendor/jquery.waitforimages');
require('vendor/jquery.caret'); require('vendor/jquery.caret');
require('vendor/jquery.atwho'); require('vendor/jquery.atwho');
require('vendor/jquery.scrollTo'); require('vendor/jquery.scrollTo');
window.Cookies = require('vendor/js.cookie'); window.Cookies = require('js-cookie');
require('./autosave'); require('./autosave');
require('bootstrap/js/affix'); require('bootstrap/js/affix');
require('bootstrap/js/alert'); require('bootstrap/js/alert');
@ -35,8 +34,10 @@ require('bootstrap/js/transition');
require('bootstrap/js/tooltip'); require('bootstrap/js/tooltip');
require('bootstrap/js/popover'); require('bootstrap/js/popover');
require('select2/select2.js'); require('select2/select2.js');
window.Pikaday = require('pikaday');
window._ = require('underscore'); window._ = require('underscore');
window.Dropzone = require('dropzone'); window.Dropzone = require('dropzone');
window.Sortable = require('vendor/Sortable');
require('mousetrap'); require('mousetrap');
require('mousetrap/plugins/pause/mousetrap-pause'); require('mousetrap/plugins/pause/mousetrap-pause');
require('./shortcuts'); require('./shortcuts');
@ -55,8 +56,7 @@ requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/)); requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/)); requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/));
require('vendor/fuzzaldrin-plus'); require('vendor/fuzzaldrin-plus');
window.ES6Promise = require('vendor/es6-promise.auto'); require('es6-promise').polyfill();
window.ES6Promise.polyfill();
(function () { (function () {
document.addEventListener('beforeunload', function () { document.addEventListener('beforeunload', function () {
@ -247,5 +247,7 @@ window.ES6Promise.polyfill();
new Aside(); new Aside();
// bind sidebar events // bind sidebar events
new gl.Sidebar(); new gl.Sidebar();
gl.utils.initTimeagoTimeout();
}); });
}).call(this); }).call(this);

View File

@ -6,7 +6,6 @@ function requireAll(context) { return context.keys().map(context); }
window.Vue = require('vue'); window.Vue = require('vue');
window.Vue.use(require('vue-resource')); window.Vue.use(require('vue-resource'));
window.Sortable = require('vendor/Sortable');
requireAll(require.context('./models', true, /^\.\/.*\.(js|es6)$/)); requireAll(require.context('./models', true, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./stores', true, /^\.\/.*\.(js|es6)$/)); requireAll(require.context('./stores', true, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./services', true, /^\.\/.*\.(js|es6)$/)); requireAll(require.context('./services', true, /^\.\/.*\.(js|es6)$/));
@ -16,7 +15,7 @@ require('./components/board');
require('./components/board_sidebar'); require('./components/board_sidebar');
require('./components/new_list_dropdown'); require('./components/new_list_dropdown');
require('./components/modal/index'); require('./components/modal/index');
require('./vue_resource_interceptor'); require('../vue_shared/vue_resource_interceptor');
$(() => { $(() => {
const $boardApp = document.getElementById('board-app'); const $boardApp = document.getElementById('board-app');

View File

@ -1,6 +1,7 @@
/* global Vue */ /* global Vue */
/* global dateFormat */
Vue.filter('due-date', (value) => { Vue.filter('due-date', (value) => {
const date = new Date(value); const date = new Date(value);
return $.datepicker.formatDate('M d, yy', date); return dateFormat(date, 'mmm d, yyyy', true);
}); });

View File

@ -1,10 +0,0 @@
/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars */
/* global Vue */
Vue.http.interceptors.push((request, next) => {
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
next(function (response) {
Vue.activeResources -= 1;
});
});

View File

@ -67,16 +67,8 @@
Build.prototype.initSidebar = function() { Build.prototype.initSidebar = function() {
this.$sidebar = $('.js-build-sidebar'); this.$sidebar = $('.js-build-sidebar');
this.sidebarTranslationLimits = {
min: $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()
};
this.sidebarTranslationLimits.max = this.sidebarTranslationLimits.min + $('.scrolling-tabs-container').outerHeight();
this.$sidebar.css({
top: this.sidebarTranslationLimits.max
});
this.$sidebar.niceScroll(); this.$sidebar.niceScroll();
this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
this.$document.off('scroll.translateSidebar').on('scroll.translateSidebar', this.translateSidebar.bind(this));
}; };
Build.prototype.location = function() { Build.prototype.location = function() {
@ -231,14 +223,6 @@
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm'; return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
}; };
Build.prototype.translateSidebar = function(e) {
var newPosition = this.sidebarTranslationLimits.max - (document.body.scrollTop || document.documentElement.scrollTop);
if (newPosition < this.sidebarTranslationLimits.min) newPosition = this.sidebarTranslationLimits.min;
this.$sidebar.css({
top: newPosition
});
};
Build.prototype.toggleSidebar = function(shouldHide) { Build.prototype.toggleSidebar = function(shouldHide) {
var shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined; var shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
this.$buildScroll.toggleClass('sidebar-expanded', shouldShow) this.$buildScroll.toggleClass('sidebar-expanded', shouldShow)
@ -285,7 +269,7 @@
e.preventDefault(); e.preventDefault();
$currentTarget = $(e.currentTarget); $currentTarget = $(e.currentTarget);
$.scrollTo($currentTarget.attr('href'), { $.scrollTo($currentTarget.attr('href'), {
offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()) offset: 0
}); });
}; };

View File

@ -0,0 +1,26 @@
/* eslint-disable no-new, no-param-reassign */
/* global Vue, CommitsPipelineStore, PipelinesService, Flash */
window.Vue = require('vue');
require('./pipelines_table');
/**
* Commits View > Pipelines Tab > Pipelines Table.
* Merge Request View > Pipelines Tab > Pipelines Table.
*
* Renders Pipelines table in pipelines tab in the commits show view.
* Renders Pipelines table in pipelines tab in the merge request show view.
*/
$(() => {
window.gl = window.gl || {};
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
if (gl.commits.PipelinesTableBundle) {
gl.commits.PipelinesTableBundle.$destroy(true);
}
gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView({
el: document.querySelector('#commit-pipeline-table-view'),
});
});

View File

@ -0,0 +1,44 @@
/* globals Vue */
/* eslint-disable no-unused-vars, no-param-reassign */
/**
* Pipelines service.
*
* Used to fetch the data used to render the pipelines table.
* Uses Vue.Resource
*/
class PipelinesService {
/**
* FIXME: The url provided to request the pipelines in the new merge request
* page already has `.json`.
* This should be fixed when the endpoint is improved.
*
* @param {String} root
*/
constructor(root) {
let endpoint;
if (root.indexOf('.json') === -1) {
endpoint = `${root}.json`;
} else {
endpoint = root;
}
this.pipelines = Vue.resource(endpoint);
}
/**
* Given the root param provided when the class is initialized, will
* make a GET request.
*
* @return {Promise}
*/
all() {
return this.pipelines.get();
}
}
window.gl = window.gl || {};
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
gl.commits.pipelines.PipelinesService = PipelinesService;

View File

@ -0,0 +1,50 @@
/* eslint-disable no-underscore-dangle*/
/**
* Pipelines' Store for commits view.
*
* Used to store the Pipelines rendered in the commit view in the pipelines table.
*/
class PipelinesStore {
constructor() {
this.state = {};
this.state.pipelines = [];
}
storePipelines(pipelines = []) {
this.state.pipelines = pipelines;
return pipelines;
}
/**
* Once the data is received we will start the time ago loops.
*
* Everytime a request is made like retry or cancel a pipeline, every 10 seconds we
* update the time to show how long as passed.
*
*/
startTimeAgoLoops() {
const startTimeLoops = () => {
this.timeLoopInterval = setInterval(() => {
this.$children[0].$children.reduce((acc, component) => {
const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
acc.push(timeAgoComponent);
return acc;
}, []).forEach(e => e.changeTime());
}, 10000);
};
startTimeLoops();
const removeIntervals = () => clearInterval(this.timeLoopInterval);
const startIntervals = () => startTimeLoops();
gl.VueRealtimeListener(removeIntervals, startIntervals);
}
}
window.gl = window.gl || {};
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
gl.commits.pipelines.PipelinesStore = PipelinesStore;

View File

@ -0,0 +1,107 @@
/* eslint-disable no-new, no-param-reassign */
/* global Vue, CommitsPipelineStore, PipelinesService, Flash */
window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor');
require('../../vue_shared/components/pipelines_table');
require('../../vue_realtime_listener/index');
require('./pipelines_service');
require('./pipelines_store');
/**
*
* Uses `pipelines-table-component` to render Pipelines table with an API call.
* Endpoint is provided in HTML and passed as `endpoint`.
* We need a store to store the received environemnts.
* We need a service to communicate with the server.
*
* Necessary SVG in the table are provided as props. This should be refactored
* as soon as we have Webpack and can load them directly into JS files.
*/
(() => {
window.gl = window.gl || {};
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
gl.commits.pipelines.PipelinesTableView = Vue.component('pipelines-table', {
components: {
'pipelines-table-component': gl.pipelines.PipelinesTableComponent,
},
/**
* Accesses the DOM to provide the needed data.
* Returns the necessary props to render `pipelines-table-component` component.
*
* @return {Object}
*/
data() {
const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset;
const svgsData = document.querySelector('.pipeline-svgs').dataset;
const store = new gl.commits.pipelines.PipelinesStore();
// Transform svgs DOMStringMap to a plain Object.
const svgsObject = gl.utils.DOMStringMapToObject(svgsData);
return {
endpoint: pipelinesTableData.endpoint,
svgs: svgsObject,
store,
state: store.state,
isLoading: false,
};
},
/**
* When the component is created the service to fetch the data will be
* initialized with the correct endpoint.
*
* A request to fetch the pipelines will be made.
* In case of a successfull response we will store the data in the provided
* store, in case of a failed response we need to warn the user.
*
*/
created() {
const pipelinesService = new gl.commits.pipelines.PipelinesService(this.endpoint);
this.isLoading = true;
return pipelinesService.all()
.then(response => response.json())
.then((json) => {
this.store.storePipelines(json);
this.store.startTimeAgoLoops.call(this, Vue);
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
new Flash('An error occurred while fetching the pipelines, please reload the page again.', 'alert');
});
},
template: `
<div>
<div class="pipelines realtime-loading" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
</div>
<div class="blank-state blank-state-no-icon"
v-if="!isLoading && state.pipelines.length === 0">
<h2 class="blank-state-title js-blank-state-title">
No pipelines to show
</h2>
</div>
<div class="table-holder pipelines"
v-if="!isLoading && state.pipelines.length > 0">
<pipelines-table-component
:pipelines="state.pipelines"
:svgs="svgs">
</pipelines-table-component>
</div>
</div>
`,
});
})();

View File

@ -91,6 +91,9 @@ require('./lib/utils/common_utils');
}, },
}, },
SanitizationFilter: { SanitizationFilter: {
'a[name]:not([href]):empty'(el, text) {
return el.outerHTML;
},
'dl'(el, text) { 'dl'(el, text) {
let lines = text.trim().split('\n'); let lines = text.trim().split('\n');
// Add two spaces to the front of subsequent list items lines, // Add two spaces to the front of subsequent list items lines,

View File

@ -13,6 +13,12 @@
<div> <div>
<div class="events-description"> <div class="events-description">
{{ stage.description }} {{ stage.description }}
<span v-if="items.length === 50" class="events-info pull-right">
<i class="fa fa-warning has-tooltip"
title="Limited to showing 50 events at most"
data-placement="top"></i>
Showing 50 events
</span>
</div> </div>
<ul class="stage-event-list"> <ul class="stage-event-list">
<li v-for="commit in items" class="stage-event-item"> <li v-for="commit in items" class="stage-event-item">

View File

@ -3,7 +3,7 @@
/* global Flash */ /* global Flash */
window.Vue = require('vue'); window.Vue = require('vue');
window.Cookies = require('vendor/js.cookie'); window.Cookies = require('js-cookie');
function requireAll(context) { return context.keys().map(context); } function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('./svg', false, /^\.\/.*\.(js|es6)$/)); requireAll(require.context('./svg', false, /^\.\/.*\.(js|es6)$/));

View File

@ -76,7 +76,7 @@ require('./lib/utils/url_utility');
const diffFile = diffTitle.closest('.diff-file'); const diffFile = diffTitle.closest('.diff-file');
const nothingHereBlock = $('.nothing-here-block:visible', diffFile); const nothingHereBlock = $('.nothing-here-block:visible', diffFile);
if (nothingHereBlock.length) { if (nothingHereBlock.length) {
const clickTarget = $('.file-title, .click-to-expand', diffFile); const clickTarget = $('.js-file-title, .click-to-expand', diffFile);
diffFile.data('singleFileDiff').toggleDiff(clickTarget, () => { diffFile.data('singleFileDiff').toggleDiff(clickTarget, () => {
this.highlighSelectedLine(); this.highlighSelectedLine();
if (cb) cb(); if (cb) cb();

View File

@ -1,6 +1,6 @@
/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, max-len */ /* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, max-len */
/* global Vue */
/* global CommentsStore */ /* global CommentsStore */
const Vue = require('vue');
(() => { (() => {
const CommentAndResolveBtn = Vue.extend({ const CommentAndResolveBtn = Vue.extend({
@ -9,13 +9,11 @@
}, },
data() { data() {
return { return {
textareaIsEmpty: true textareaIsEmpty: true,
discussion: {},
}; };
}, },
computed: { computed: {
discussion: function () {
return CommentsStore.state[this.discussionId];
},
showButton: function () { showButton: function () {
if (this.discussion) { if (this.discussion) {
return this.discussion.isResolvable(); return this.discussion.isResolvable();
@ -42,6 +40,9 @@
} }
} }
}, },
created() {
this.discussion = CommentsStore.state[this.discussionId];
},
mounted: function () { mounted: function () {
const $textarea = $(`#new-discussion-note-form-${this.discussionId} .note-textarea`); const $textarea = $(`#new-discussion-note-form-${this.discussionId} .note-textarea`);
this.textareaIsEmpty = $textarea.val() === ''; this.textareaIsEmpty = $textarea.val() === '';

View File

@ -1,7 +1,7 @@
/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, guard-for-in, no-restricted-syntax, one-var, space-before-function-paren, no-lonely-if, no-continue, brace-style, max-len, quotes */ /* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, guard-for-in, no-restricted-syntax, one-var, space-before-function-paren, no-lonely-if, no-continue, brace-style, max-len, quotes */
/* global Vue */
/* global DiscussionMixins */ /* global DiscussionMixins */
/* global CommentsStore */ /* global CommentsStore */
const Vue = require('vue');
(() => { (() => {
const JumpToDiscussion = Vue.extend({ const JumpToDiscussion = Vue.extend({
@ -12,12 +12,10 @@
data: function () { data: function () {
return { return {
discussions: CommentsStore.state, discussions: CommentsStore.state,
discussion: {},
}; };
}, },
computed: { computed: {
discussion: function () {
return this.discussions[this.discussionId];
},
allResolved: function () { allResolved: function () {
return this.unresolvedDiscussionCount === 0; return this.unresolvedDiscussionCount === 0;
}, },
@ -183,10 +181,13 @@
} }
$.scrollTo($target, { $.scrollTo($target, {
offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()) offset: 0
}); });
} }
} },
created() {
this.discussion = this.discussions[this.discussionId];
},
}); });
Vue.component('jump-to-discussion', JumpToDiscussion); Vue.component('jump-to-discussion', JumpToDiscussion);

View File

@ -1,8 +1,8 @@
/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, no-new, max-len */ /* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, no-new, max-len */
/* global Vue */
/* global CommentsStore */ /* global CommentsStore */
/* global ResolveService */ /* global ResolveService */
/* global Flash */ /* global Flash */
const Vue = require('vue');
(() => { (() => {
const ResolveBtn = Vue.extend({ const ResolveBtn = Vue.extend({
@ -10,14 +10,14 @@
noteId: Number, noteId: Number,
discussionId: String, discussionId: String,
resolved: Boolean, resolved: Boolean,
projectPath: String,
canResolve: Boolean, canResolve: Boolean,
resolvedBy: String resolvedBy: String
}, },
data: function () { data: function () {
return { return {
discussions: CommentsStore.state, discussions: CommentsStore.state,
loading: false loading: false,
note: {},
}; };
}, },
watch: { watch: {
@ -30,13 +30,6 @@
discussion: function () { discussion: function () {
return this.discussions[this.discussionId]; return this.discussions[this.discussionId];
}, },
note: function () {
if (this.discussion) {
return this.discussion.getNote(this.noteId);
} else {
return undefined;
}
},
buttonText: function () { buttonText: function () {
if (this.isResolved) { if (this.isResolved) {
return `Resolved by ${this.resolvedByName}`; return `Resolved by ${this.resolvedByName}`;
@ -73,10 +66,10 @@
if (this.isResolved) { if (this.isResolved) {
promise = ResolveService promise = ResolveService
.unresolve(this.projectPath, this.noteId); .unresolve(this.noteId);
} else { } else {
promise = ResolveService promise = ResolveService
.resolve(this.projectPath, this.noteId); .resolve(this.noteId);
} }
promise.then((response) => { promise.then((response) => {
@ -106,6 +99,8 @@
}, },
created: function () { created: function () {
CommentsStore.create(this.discussionId, this.noteId, this.canResolve, this.resolved, this.resolvedBy); CommentsStore.create(this.discussionId, this.noteId, this.canResolve, this.resolved, this.resolvedBy);
this.note = this.discussion.getNote(this.noteId);
} }
}); });

View File

@ -1,7 +1,7 @@
/* eslint-disable comma-dangle, object-shorthand, func-names, no-param-reassign */ /* eslint-disable comma-dangle, object-shorthand, func-names, no-param-reassign */
/* global Vue */
/* global DiscussionMixins */ /* global DiscussionMixins */
/* global CommentsStore */ /* global CommentsStore */
const Vue = require('vue');
((w) => { ((w) => {
w.ResolveCount = Vue.extend({ w.ResolveCount = Vue.extend({

View File

@ -1,25 +1,22 @@
/* eslint-disable object-shorthand, func-names, space-before-function-paren, comma-dangle, no-else-return, quotes, max-len */ /* eslint-disable object-shorthand, func-names, space-before-function-paren, comma-dangle, no-else-return, quotes, max-len */
/* global Vue */
/* global CommentsStore */ /* global CommentsStore */
/* global ResolveService */ /* global ResolveService */
const Vue = require('vue');
(() => { (() => {
const ResolveDiscussionBtn = Vue.extend({ const ResolveDiscussionBtn = Vue.extend({
props: { props: {
discussionId: String, discussionId: String,
mergeRequestId: Number, mergeRequestId: Number,
projectPath: String,
canResolve: Boolean, canResolve: Boolean,
}, },
data: function() { data: function() {
return { return {
discussions: CommentsStore.state discussion: {},
}; };
}, },
computed: { computed: {
discussion: function () {
return this.discussions[this.discussionId];
},
showButton: function () { showButton: function () {
if (this.discussion) { if (this.discussion) {
return this.discussion.isResolvable(); return this.discussion.isResolvable();
@ -51,11 +48,13 @@
}, },
methods: { methods: {
resolve: function () { resolve: function () {
ResolveService.toggleResolveForDiscussion(this.projectPath, this.mergeRequestId, this.discussionId); ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId);
} }
}, },
created: function () { created: function () {
CommentsStore.createDiscussion(this.discussionId, this.canResolve); CommentsStore.createDiscussion(this.discussionId, this.canResolve);
this.discussion = CommentsStore.state[this.discussionId];
} }
}); });

View File

@ -3,6 +3,7 @@
/* global ResolveCount */ /* global ResolveCount */
function requireAll(context) { return context.keys().map(context); } function requireAll(context) { return context.keys().map(context); }
const Vue = require('vue');
requireAll(require.context('./models', false, /^\.\/.*\.(js|es6)$/)); requireAll(require.context('./models', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./stores', false, /^\.\/.*\.(js|es6)$/)); requireAll(require.context('./stores', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./services', false, /^\.\/.*\.(js|es6)$/)); requireAll(require.context('./services', false, /^\.\/.*\.(js|es6)$/));
@ -10,11 +11,14 @@ requireAll(require.context('./mixins', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./components', false, /^\.\/.*\.(js|es6)$/)); requireAll(require.context('./components', false, /^\.\/.*\.(js|es6)$/));
$(() => { $(() => {
const projectPath = document.querySelector('.merge-request').dataset.projectPath;
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn'; const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn';
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.diffNoteApps = {}; window.gl.diffNoteApps = {};
window.ResolveService = new gl.DiffNotesResolveServiceClass(projectPath);
gl.diffNotesCompileComponents = () => { gl.diffNotesCompileComponents = () => {
const $components = $(COMPONENT_SELECTOR).filter(function () { const $components = $(COMPONENT_SELECTOR).filter(function () {
return $(this).closest('resolve-count').length !== 1; return $(this).closest('resolve-count').length !== 1;

View File

@ -1,45 +1,37 @@
/* eslint-disable class-methods-use-this, one-var, camelcase, no-new, comma-dangle, no-param-reassign, max-len */ /* eslint-disable class-methods-use-this, one-var, camelcase, no-new, comma-dangle, no-param-reassign, max-len */
/* global Vue */
/* global Flash */ /* global Flash */
/* global CommentsStore */ /* global CommentsStore */
((w) => { const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
require('../../vue_shared/vue_resource_interceptor');
(() => {
window.gl = window.gl || {};
class ResolveServiceClass { class ResolveServiceClass {
constructor() { constructor(root) {
this.noteResource = Vue.resource('notes{/noteId}/resolve'); this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`);
this.discussionResource = Vue.resource('merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve'); this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`);
} }
setCSRF() { resolve(noteId) {
Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken();
}
prepareRequest(root) {
this.setCSRF();
Vue.http.options.root = root;
}
resolve(projectPath, noteId) {
this.prepareRequest(projectPath);
return this.noteResource.save({ noteId }, {}); return this.noteResource.save({ noteId }, {});
} }
unresolve(projectPath, noteId) { unresolve(noteId) {
this.prepareRequest(projectPath);
return this.noteResource.delete({ noteId }, {}); return this.noteResource.delete({ noteId }, {});
} }
toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId) { toggleResolveForDiscussion(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId]; const discussion = CommentsStore.state[discussionId];
const isResolved = discussion.isResolved(); const isResolved = discussion.isResolved();
let promise; let promise;
if (isResolved) { if (isResolved) {
promise = this.unResolveAll(projectPath, mergeRequestId, discussionId); promise = this.unResolveAll(mergeRequestId, discussionId);
} else { } else {
promise = this.resolveAll(projectPath, mergeRequestId, discussionId); promise = this.resolveAll(mergeRequestId, discussionId);
} }
promise.then((response) => { promise.then((response) => {
@ -62,11 +54,9 @@
}); });
} }
resolveAll(projectPath, mergeRequestId, discussionId) { resolveAll(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId]; const discussion = CommentsStore.state[discussionId];
this.prepareRequest(projectPath);
discussion.loading = true; discussion.loading = true;
return this.discussionResource.save({ return this.discussionResource.save({
@ -75,11 +65,9 @@
}, {}); }, {});
} }
unResolveAll(projectPath, mergeRequestId, discussionId) { unResolveAll(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId]; const discussion = CommentsStore.state[discussionId];
this.prepareRequest(projectPath);
discussion.loading = true; discussion.loading = true;
return this.discussionResource.delete({ return this.discussionResource.delete({
@ -89,5 +77,5 @@
} }
} }
w.ResolveService = new ResolveServiceClass(); gl.DiffNotesResolveServiceClass = ResolveServiceClass;
})(window); })();

View File

@ -19,7 +19,6 @@
/* global UsersSelect */ /* global UsersSelect */
/* global GroupAvatar */ /* global GroupAvatar */
/* global LineHighlighter */ /* global LineHighlighter */
/* global ShortcutsBlob */
/* global ProjectFork */ /* global ProjectFork */
/* global BuildArtifacts */ /* global BuildArtifacts */
/* global GroupsSelect */ /* global GroupsSelect */
@ -36,6 +35,8 @@
/* global Labels */ /* global Labels */
/* global Shortcuts */ /* global Shortcuts */
const ShortcutsBlob = require('./shortcuts_blob');
(function() { (function() {
var Dispatcher; var Dispatcher;
@ -96,6 +97,7 @@
break; break;
case 'projects:milestones:new': case 'projects:milestones:new':
case 'projects:milestones:edit': case 'projects:milestones:edit':
case 'projects:milestones:update':
new ZenMode(); new ZenMode();
new gl.DueDateSelectors(); new gl.DueDateSelectors();
new gl.GLForm($('.milestone-form')); new gl.GLForm($('.milestone-form'));
@ -162,7 +164,7 @@
case 'projects:commit:pipelines': case 'projects:commit:pipelines':
new gl.MiniPipelineGraph({ new gl.MiniPipelineGraph({
container: '.js-pipeline-table', container: '.js-pipeline-table',
}); }).bindEvents();
break; break;
case 'projects:commits:show': case 'projects:commits:show':
case 'projects:activity': case 'projects:activity':
@ -225,7 +227,12 @@
case 'projects:blame:show': case 'projects:blame:show':
new LineHighlighter(); new LineHighlighter();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new ShortcutsBlob(true); const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
const fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
new ShortcutsBlob({
skipResetBindings: true,
fileBlobPermalinkUrl,
});
break; break;
case 'groups:labels:new': case 'groups:labels:new':
case 'groups:labels:edit': case 'groups:labels:edit':
@ -259,7 +266,7 @@
new gl.ProtectedBranchCreate(); new gl.ProtectedBranchCreate();
new gl.ProtectedBranchEditList(); new gl.ProtectedBranchEditList();
break; break;
case 'projects:variables:index': case 'projects:ci_cd:show':
new gl.ProjectVariables(); new gl.ProjectVariables();
break; break;
case 'ci:lints:create': case 'ci:lints:create':

View File

@ -78,8 +78,8 @@ require('../window')(function(w){
}, },
destroy: function() { destroy: function() {
if (this.listTemplate) { var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]'); if (this.listTemplate && dynamicList) {
dynamicList.outerHTML = this.listTemplate; dynamicList.outerHTML = this.listTemplate;
} }
} }

View File

@ -1,4 +1,6 @@
/* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, no-unused-vars, no-underscore-dangle, no-new, max-len, no-sequences, no-unused-expressions, no-param-reassign */ /* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, no-unused-vars, no-underscore-dangle, no-new, max-len, no-sequences, no-unused-expressions, no-param-reassign */
/* global dateFormat */
/* global Pikaday */
(function(global) { (function(global) {
class DueDateSelect { class DueDateSelect {
@ -25,11 +27,14 @@
this.initGlDropdown(); this.initGlDropdown();
this.initRemoveDueDate(); this.initRemoveDueDate();
this.initDatePicker(); this.initDatePicker();
this.initStopPropagation();
} }
initGlDropdown() { initGlDropdown() {
this.$dropdown.glDropdown({ this.$dropdown.glDropdown({
opened: () => {
const calendar = this.$datePicker.data('pikaday');
calendar.show();
},
hidden: () => { hidden: () => {
this.$selectbox.hide(); this.$selectbox.hide();
this.$value.css('display', ''); this.$value.css('display', '');
@ -38,25 +43,37 @@
} }
initDatePicker() { initDatePicker() {
this.$datePicker.datepicker({ const $dueDateInput = $(`input[name='${this.fieldName}']`);
dateFormat: 'yy-mm-dd',
defaultDate: $("input[name='" + this.fieldName + "']").val(), const calendar = new Pikaday({
altField: "input[name='" + this.fieldName + "']", field: $dueDateInput.get(0),
onSelect: () => { theme: 'gitlab-theme',
format: 'YYYY-MM-DD',
onSelect: (dateText) => {
const formattedDate = dateFormat(new Date(dateText), 'yyyy-mm-dd');
$dueDateInput.val(formattedDate);
if (this.$dropdown.hasClass('js-issue-boards-due-date')) { if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
gl.issueBoards.BoardsStore.detail.issue.dueDate = $(`input[name='${this.fieldName}']`).val(); gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val();
this.updateIssueBoardIssue(); this.updateIssueBoardIssue();
} else { } else {
return this.saveDueDate(true); this.saveDueDate(true);
} }
} }
}); });
this.$datePicker.append(calendar.el);
this.$datePicker.data('pikaday', calendar);
} }
initRemoveDueDate() { initRemoveDueDate() {
this.$block.on('click', '.js-remove-due-date', (e) => { this.$block.on('click', '.js-remove-due-date', (e) => {
const calendar = this.$datePicker.data('pikaday');
e.preventDefault(); e.preventDefault();
calendar.setDate(null);
if (this.$dropdown.hasClass('js-issue-boards-due-date')) { if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
gl.issueBoards.BoardsStore.detail.issue.dueDate = ''; gl.issueBoards.BoardsStore.detail.issue.dueDate = '';
this.updateIssueBoardIssue(); this.updateIssueBoardIssue();
@ -67,12 +84,6 @@
}); });
} }
initStopPropagation() {
$(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', (e) => {
return e.stopImmediatePropagation();
});
}
saveDueDate(isDropdown) { saveDueDate(isDropdown) {
this.parseSelectedDate(); this.parseSelectedDate();
this.prepSelectedDate(); this.prepSelectedDate();
@ -86,7 +97,7 @@
// Construct Date object manually to avoid buggy dateString support within Date constructor // Construct Date object manually to avoid buggy dateString support within Date constructor
const dateArray = this.rawSelectedDate.split('-').map(v => parseInt(v, 10)); const dateArray = this.rawSelectedDate.split('-').map(v => parseInt(v, 10));
const dateObj = new Date(dateArray[0], dateArray[1] - 1, dateArray[2]); const dateObj = new Date(dateArray[0], dateArray[1] - 1, dateArray[2]);
this.displayedDate = $.datepicker.formatDate('M d, yy', dateObj); this.displayedDate = dateFormat(dateObj, 'mmm d, yyyy');
} else { } else {
this.displayedDate = 'No due date'; this.displayedDate = 'No due date';
} }
@ -153,14 +164,24 @@
} }
initMilestoneDatePicker() { initMilestoneDatePicker() {
$('.datepicker').datepicker({ $('.datepicker').each(function() {
dateFormat: 'yy-mm-dd' const $datePicker = $(this);
const calendar = new Pikaday({
field: $datePicker.get(0),
theme: 'gitlab-theme',
format: 'YYYY-MM-DD',
onSelect(dateText) {
$datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
}
});
$datePicker.data('pikaday', calendar);
}); });
$('.js-clear-due-date,.js-clear-start-date').on('click', (e) => { $('.js-clear-due-date,.js-clear-start-date').on('click', (e) => {
e.preventDefault(); e.preventDefault();
const datepicker = $(e.target).siblings('.datepicker'); const calendar = $(e.target).siblings('.datepicker').data('pikaday');
$.datepicker._clearDate(datepicker); calendar.setDate(null);
}); });
} }

View File

@ -2,9 +2,9 @@
/* global timeago */ /* global timeago */
window.Vue = require('vue'); window.Vue = require('vue');
window.timeago = require('vendor/timeago'); window.timeago = require('timeago.js');
require('../../lib/utils/text_utility'); require('../../lib/utils/text_utility');
require('../../vue_common_component/commit'); require('../../vue_shared/components/commit');
require('./environment_actions'); require('./environment_actions');
require('./environment_external_url'); require('./environment_external_url');
require('./environment_stop'); require('./environment_stop');
@ -147,12 +147,12 @@ require('./environment_terminal_button');
}, },
/** /**
* Returns the value of the `stoppable?` key provided in the response. * Returns the value of the `stop_action?` key provided in the response.
* *
* @returns {Boolean} * @returns {Boolean}
*/ */
isStoppable() { hasStopAction() {
return this.model['stoppable?']; return this.model['stop_action?'];
}, },
/** /**
@ -508,7 +508,7 @@ require('./environment_terminal_button');
</external-url-component> </external-url-component>
</div> </div>
<div v-if="isStoppable && canCreateDeployment" <div v-if="hasStopAction && canCreateDeployment"
class="inline js-stop-component-container"> class="inline js-stop-component-container">
<stop-component <stop-component
:stop-url="model.stop_path"> :stop-url="model.stop_path">

View File

@ -1,8 +1,7 @@
window.Vue = require('vue'); window.Vue = require('vue');
require('./stores/environments_store'); require('./stores/environments_store');
require('./components/environment'); require('./components/environment');
require('./vue_resource_interceptor'); require('../vue_shared/vue_resource_interceptor');
$(() => { $(() => {
window.gl = window.gl || {}; window.gl = window.gl || {};

View File

@ -1,12 +0,0 @@
/* global Vue */
Vue.http.interceptors.push((request, next) => {
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
next((response) => {
if (typeof response.data === 'string') {
response.data = JSON.parse(response.data); // eslint-disable-line
}
Vue.activeResources--; // eslint-disable-line
});
});

View File

@ -8,7 +8,7 @@ require('./filtered_search_dropdown');
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.config = { this.config = {
droplabAjaxFilter: { droplabAjaxFilter: {
endpoint: '/autocomplete/users.json', endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
searchKey: 'search', searchKey: 'search',
params: { params: {
per_page: 20, per_page: 20,

View File

@ -4,7 +4,7 @@
class FilteredSearchDropdown { class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) { constructor(droplab, dropdown, input, filter) {
this.droplab = droplab; this.droplab = droplab;
this.hookId = input.getAttribute('data-id'); this.hookId = input && input.getAttribute('data-id');
this.input = input; this.input = input;
this.filter = filter; this.filter = filter;
this.dropdown = dropdown; this.dropdown = dropdown;

View File

@ -2,7 +2,8 @@
(() => { (() => {
class FilteredSearchDropdownManager { class FilteredSearchDropdownManager {
constructor() { constructor(baseEndpoint = '') {
this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = gl.FilteredSearchTokenizer; this.tokenizer = gl.FilteredSearchTokenizer;
this.filteredSearchInput = document.querySelector('.filtered-search'); this.filteredSearchInput = document.querySelector('.filtered-search');
@ -38,13 +39,13 @@
milestone: { milestone: {
reference: null, reference: null,
gl: 'DropdownNonUser', gl: 'DropdownNonUser',
extraArguments: ['milestones.json', '%'], extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'],
element: document.querySelector('#js-dropdown-milestone'), element: document.querySelector('#js-dropdown-milestone'),
}, },
label: { label: {
reference: null, reference: null,
gl: 'DropdownNonUser', gl: 'DropdownNonUser',
extraArguments: ['labels.json', '~'], extraArguments: [`${this.baseEndpoint}/labels.json`, '~'],
element: document.querySelector('#js-dropdown-label'), element: document.querySelector('#js-dropdown-label'),
}, },
hint: { hint: {

View File

@ -6,7 +6,7 @@
if (this.filteredSearchInput) { if (this.filteredSearchInput) {
this.tokenizer = gl.FilteredSearchTokenizer; this.tokenizer = gl.FilteredSearchTokenizer;
this.dropdownManager = new gl.FilteredSearchDropdownManager(); this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '');
this.bindEvents(); this.bindEvents();
this.loadSearchParamsFromURL(); this.loadSearchParamsFromURL();

View File

@ -47,9 +47,11 @@
} }
// Only filter asynchronously only if option remote is set // Only filter asynchronously only if option remote is set
if (this.options.remote) { if (this.options.remote) {
$inputContainer.parent().addClass('is-loading');
clearTimeout(timeout); clearTimeout(timeout);
return timeout = setTimeout(function() { return timeout = setTimeout(function() {
return this.options.query(this.input.val(), function(data) { return this.options.query(this.input.val(), function(data) {
$inputContainer.parent().removeClass('is-loading');
return this.options.callback(data); return this.options.callback(data);
}.bind(this)); }.bind(this));
}.bind(this), 250); }.bind(this), 250);
@ -437,7 +439,7 @@
} }
}; };
GitLabDropdown.prototype.opened = function() { GitLabDropdown.prototype.opened = function(e) {
var contentHtml; var contentHtml;
this.resetRows(); this.resetRows();
this.addArrowKeyEvent(); this.addArrowKeyEvent();
@ -457,6 +459,10 @@
this.positionMenuAbove(); this.positionMenuAbove();
} }
if (this.options.opened) {
this.options.opened.call(this, e);
}
return this.dropdown.trigger('shown.gl.dropdown'); return this.dropdown.trigger('shown.gl.dropdown');
}; };

View File

@ -3,6 +3,8 @@
/* global UsersSelect */ /* global UsersSelect */
/* global ZenMode */ /* global ZenMode */
/* global Autosave */ /* global Autosave */
/* global dateFormat */
/* global Pikaday */
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
@ -13,7 +15,7 @@
IssuableForm.prototype.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i; IssuableForm.prototype.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i;
function IssuableForm(form) { function IssuableForm(form) {
var $issuableDueDate; var $issuableDueDate, calendar;
this.form = form; this.form = form;
this.toggleWip = bind(this.toggleWip, this); this.toggleWip = bind(this.toggleWip, this);
this.renderWipExplanation = bind(this.renderWipExplanation, this); this.renderWipExplanation = bind(this.renderWipExplanation, this);
@ -35,12 +37,14 @@
this.initMoveDropdown(); this.initMoveDropdown();
$issuableDueDate = $('#issuable-due-date'); $issuableDueDate = $('#issuable-due-date');
if ($issuableDueDate.length) { if ($issuableDueDate.length) {
$('.datepicker').datepicker({ calendar = new Pikaday({
dateFormat: 'yy-mm-dd', field: $issuableDueDate.get(0),
onSelect: function(dateText, inst) { theme: 'gitlab-theme',
return $issuableDueDate.val(dateText); format: 'YYYY-MM-DD',
onSelect: function(dateText) {
$issuableDueDate.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
} }
}).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $issuableDueDate.val())); });
} }
} }

View File

@ -1,5 +1,6 @@
/* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */ /* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */
/* global Flash */ /* global Flash */
/* global Sortable */
((global) => { ((global) => {
class LabelManager { class LabelManager {
@ -9,11 +10,12 @@
this.otherLabels = otherLabels || $('.js-other-labels'); this.otherLabels = otherLabels || $('.js-other-labels');
this.errorMessage = 'Unable to update label prioritization at this time'; this.errorMessage = 'Unable to update label prioritization at this time';
this.emptyState = document.querySelector('#js-priority-labels-empty-state'); this.emptyState = document.querySelector('#js-priority-labels-empty-state');
this.prioritizedLabels.sortable({ this.sortable = Sortable.create(this.prioritizedLabels.get(0), {
items: 'li', filter: '.empty-message',
placeholder: 'list-placeholder', forceFallback: true,
axis: 'y', fallbackClass: 'is-dragging',
update: this.onPrioritySortUpdate.bind(this) dataIdAttr: 'data-id',
onUpdate: this.onPrioritySortUpdate.bind(this),
}); });
this.bindEvents(); this.bindEvents();
} }
@ -51,13 +53,13 @@
$target = this.otherLabels; $target = this.otherLabels;
$from = this.prioritizedLabels; $from = this.prioritizedLabels;
} }
if ($from.find('li').length === 1) { $label.detach().appendTo($target);
if ($from.find('li').length) {
$from.find('.empty-message').removeClass('hidden'); $from.find('.empty-message').removeClass('hidden');
} }
if (!$target.find('li').length) { if ($target.find('> li:not(.empty-message)').length) {
$target.find('.empty-message').addClass('hidden'); $target.find('.empty-message').addClass('hidden');
} }
$label.detach().appendTo($target);
// Return if we are not persisting state // Return if we are not persisting state
if (!persistState) { if (!persistState) {
return; return;
@ -101,8 +103,12 @@
getSortedLabelsIds() { getSortedLabelsIds() {
const sortedIds = []; const sortedIds = [];
this.prioritizedLabels.find('li').each(function() { this.prioritizedLabels.find('> li').each(function() {
sortedIds.push($(this).data('id')); const id = $(this).data('id');
if (id) {
sortedIds.push(id);
}
}); });
return sortedIds; return sortedIds;
} }

View File

@ -69,27 +69,21 @@
var hash = w.gl.utils.getLocationHash(); var hash = w.gl.utils.getLocationHash();
if (!hash) return; if (!hash) return;
var navbar = document.querySelector('.navbar-gitlab'); // This is required to handle non-unicode characters in hash
var subnav = document.querySelector('.layout-nav'); hash = decodeURIComponent(hash);
var fixedTabs = document.querySelector('.js-tabs-affix');
var adjustment = 0;
if (navbar) adjustment -= navbar.offsetHeight;
if (subnav) adjustment -= subnav.offsetHeight;
// scroll to user-generated markdown anchor if we cannot find a match // scroll to user-generated markdown anchor if we cannot find a match
if (document.getElementById(hash) === null) { if (document.getElementById(hash) === null) {
var target = document.getElementById('user-content-' + hash); var target = document.getElementById('user-content-' + hash);
if (target && target.scrollIntoView) { if (target && target.scrollIntoView) {
target.scrollIntoView(true); target.scrollIntoView(true);
window.scrollBy(0, adjustment);
} }
} else { } else {
// only adjust for fixedTabs when not targeting user-generated content // only adjust for fixedTabs when not targeting user-generated content
var fixedTabs = document.querySelector('.js-tabs-affix');
if (fixedTabs) { if (fixedTabs) {
adjustment -= fixedTabs.offsetHeight; window.scrollBy(0, -fixedTabs.offsetHeight);
} }
window.scrollBy(0, adjustment);
} }
}; };
@ -134,14 +128,20 @@
return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey; return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
}; };
gl.utils.isMetaClick = function(e) {
// Identify following special clicks
// 1) Cmd + Click on Mac (e.metaKey)
// 2) Ctrl + Click on PC (e.ctrlKey)
// 3) Middle-click or Mouse Wheel Click (e.which is 2)
return e.metaKey || e.ctrlKey || e.which === 2;
};
gl.utils.scrollToElement = function($el) { gl.utils.scrollToElement = function($el) {
var top = $el.offset().top; var top = $el.offset().top;
gl.navBarHeight = gl.navBarHeight || $('.navbar-gitlab').height();
gl.navLinksHeight = gl.navLinksHeight || $('.nav-links').height();
gl.mrTabsHeight = gl.mrTabsHeight || $('.merge-request-tabs').height(); gl.mrTabsHeight = gl.mrTabsHeight || $('.merge-request-tabs').height();
return $('body, html').animate({ return $('body, html').animate({
scrollTop: top - (gl.navBarHeight + gl.navLinksHeight + gl.mrTabsHeight) scrollTop: top - (gl.mrTabsHeight)
}, 200); }, 200);
}; };
@ -230,5 +230,16 @@
return upperCaseHeaders; return upperCaseHeaders;
}; };
/**
* Transforms a DOMStringMap into a plain object.
*
* @param {DOMStringMap} DOMStringMapObject
* @returns {Object}
*/
w.gl.utils.DOMStringMapToObject = DOMStringMapObject => Object.keys(DOMStringMapObject).reduce((acc, element) => {
acc[element] = DOMStringMapObject[element];
return acc;
}, {});
})(window); })(window);
}).call(this); }).call(this);

View File

@ -1,101 +0,0 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, comma-dangle, no-unused-expressions, prefer-template, max-len */
/* global timeago */
/* global dateFormat */
window.timeago = require('vendor/timeago');
window.dateFormat = require('vendor/date.format');
(function() {
(function(w) {
var base;
if (w.gl == null) {
w.gl = {};
}
if ((base = w.gl).utils == null) {
base.utils = {};
}
w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
w.gl.utils.formatDate = function(datetime) {
return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
};
w.gl.utils.getDayName = function(date) {
return this.days[date.getDay()];
};
w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago) {
if (setTimeago == null) {
setTimeago = true;
}
$timeagoEls.filter(':not([data-timeago-rendered])').each(function() {
var $el = $(this);
$el.attr('title', gl.utils.formatDate($el.attr('datetime')));
if (setTimeago) {
// Recreate with custom template
$el.tooltip({
template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
});
}
$el.attr('data-timeago-rendered', true);
gl.utils.renderTimeago($el);
});
};
w.gl.utils.getTimeago = function() {
var locale = function(number, index) {
return [
['less than a minute ago', 'a while'],
['less than a minute ago', 'in %s seconds'],
['about a minute ago', 'in 1 minute'],
['%s minutes ago', 'in %s minutes'],
['about an hour ago', 'in 1 hour'],
['about %s hours ago', 'in %s hours'],
['a day ago', 'in 1 day'],
['%s days ago', 'in %s days'],
['a week ago', 'in 1 week'],
['%s weeks ago', 'in %s weeks'],
['a month ago', 'in 1 month'],
['%s months ago', 'in %s months'],
['a year ago', 'in 1 year'],
['%s years ago', 'in %s years']
][index];
};
timeago.register('gl_en', locale);
return timeago();
};
w.gl.utils.timeFor = function(time, suffix, expiredLabel) {
var timefor;
if (!time) {
return '';
}
suffix || (suffix = 'remaining');
expiredLabel || (expiredLabel = 'Past due');
timefor = gl.utils.getTimeago().format(time).replace('in', '');
if (timefor.indexOf('ago') > -1) {
timefor = expiredLabel;
} else {
timefor = timefor.trim() + ' ' + suffix;
}
return timefor;
};
w.gl.utils.renderTimeago = function($element) {
var timeagoInstance = gl.utils.getTimeago();
timeagoInstance.render($element, 'gl_en');
};
w.gl.utils.getDayDifference = function(a, b) {
var millisecondsPerDay = 1000 * 60 * 60 * 24;
var date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
return Math.floor((date2 - date1) / millisecondsPerDay);
};
})(window);
}).call(this);

View File

@ -0,0 +1,126 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, comma-dangle, no-unused-expressions, prefer-template, max-len */
/* global timeago */
/* global dateFormat */
window.timeago = require('timeago.js');
window.dateFormat = require('vendor/date.format');
(function() {
(function(w) {
var base;
var timeagoInstance;
if (w.gl == null) {
w.gl = {};
}
if ((base = w.gl).utils == null) {
base.utils = {};
}
w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
w.gl.utils.formatDate = function(datetime) {
return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
};
w.gl.utils.getDayName = function(date) {
return this.days[date.getDay()];
};
w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago = true) {
$timeagoEls.each((i, el) => {
el.setAttribute('title', gl.utils.formatDate(el.getAttribute('datetime')));
if (setTimeago) {
// Recreate with custom template
$(el).tooltip({
template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
});
}
el.classList.add('js-timeago-render');
});
gl.utils.renderTimeago($timeagoEls);
};
w.gl.utils.getTimeago = function() {
var locale;
if (!timeagoInstance) {
locale = function(number, index) {
return [
['less than a minute ago', 'a while'],
['less than a minute ago', 'in %s seconds'],
['about a minute ago', 'in 1 minute'],
['%s minutes ago', 'in %s minutes'],
['about an hour ago', 'in 1 hour'],
['about %s hours ago', 'in %s hours'],
['a day ago', 'in 1 day'],
['%s days ago', 'in %s days'],
['a week ago', 'in 1 week'],
['%s weeks ago', 'in %s weeks'],
['a month ago', 'in 1 month'],
['%s months ago', 'in %s months'],
['a year ago', 'in 1 year'],
['%s years ago', 'in %s years']
][index];
};
timeago.register('gl_en', locale);
timeagoInstance = timeago();
}
return timeagoInstance;
};
w.gl.utils.timeFor = function(time, suffix, expiredLabel) {
var timefor;
if (!time) {
return '';
}
suffix || (suffix = 'remaining');
expiredLabel || (expiredLabel = 'Past due');
timefor = gl.utils.getTimeago().format(time).replace('in', '');
if (timefor.indexOf('ago') > -1) {
timefor = expiredLabel;
} else {
timefor = timefor.trim() + ' ' + suffix;
}
return timefor;
};
w.gl.utils.cachedTimeagoElements = [];
w.gl.utils.renderTimeago = function($els) {
if (!$els && !w.gl.utils.cachedTimeagoElements.length) {
w.gl.utils.cachedTimeagoElements = [].slice.call(document.querySelectorAll('.js-timeago-render'));
} else if ($els) {
w.gl.utils.cachedTimeagoElements = w.gl.utils.cachedTimeagoElements.concat($els.toArray());
}
w.gl.utils.cachedTimeagoElements.forEach(gl.utils.updateTimeagoText);
};
w.gl.utils.updateTimeagoText = function(el) {
const timeago = gl.utils.getTimeago();
const formattedDate = timeago.format(el.getAttribute('datetime'), 'gl_en');
if (el.textContent !== formattedDate) {
el.textContent = formattedDate;
}
};
w.gl.utils.initTimeagoTimeout = function() {
gl.utils.renderTimeago();
gl.utils.timeagoTimeout = setTimeout(gl.utils.initTimeagoTimeout, 1000);
};
w.gl.utils.getDayDifference = function(a, b) {
var millisecondsPerDay = 1000 * 60 * 60 * 24;
var date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
return Math.floor((date2 - date1) / millisecondsPerDay);
};
})(window);
}).call(this);

View File

@ -1,3 +1,5 @@
/* global Pikaday */
/* global dateFormat */
(() => { (() => {
// Add datepickers to all `js-access-expiration-date` elements. If those elements are // Add datepickers to all `js-access-expiration-date` elements. If those elements are
// children of an element with the `clearable-input` class, and have a sibling // children of an element with the `clearable-input` class, and have a sibling
@ -11,21 +13,34 @@
} }
const inputs = $(selector); const inputs = $(selector);
inputs.datepicker({ inputs.each((i, el) => {
dateFormat: 'yy-mm-dd', const $input = $(el);
minDate: 1,
onSelect: function onSelect() { const calendar = new Pikaday({
$(this).trigger('change'); field: $input.get(0),
toggleClearInput.call(this); theme: 'gitlab-theme',
}, format: 'YYYY-MM-DD',
minDate: new Date(),
onSelect(dateText) {
$input.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
$input.trigger('change');
toggleClearInput.call($input);
},
});
$input.data('pikaday', calendar);
}); });
inputs.next('.js-clear-input').on('click', function clicked(event) { inputs.next('.js-clear-input').on('click', function clicked(event) {
event.preventDefault(); event.preventDefault();
const input = $(this).closest('.clearable-input').find(selector); const input = $(this).closest('.clearable-input').find(selector);
input.datepicker('setDate', null) const calendar = input.data('pikaday');
.trigger('change');
calendar.setDate(null);
input.trigger('change');
toggleClearInput.call(input); toggleClearInput.call(input);
}); });

View File

@ -115,8 +115,8 @@ require('./merge_request_tabs');
e.preventDefault(); e.preventDefault();
textarea.val(textarea.data('messageWithDescription')); textarea.val(textarea.data('messageWithDescription'));
$('p.js-with-description-hint').hide(); $('.js-with-description-hint').hide();
$('p.js-without-description-hint').show(); $('.js-without-description-hint').show();
}); });
$(document).on('click', 'a.js-without-description-link', function(e) { $(document).on('click', 'a.js-without-description-link', function(e) {
@ -124,8 +124,8 @@ require('./merge_request_tabs');
e.preventDefault(); e.preventDefault();
textarea.val(textarea.data('messageWithoutDescription')); textarea.val(textarea.data('messageWithoutDescription'));
$('p.js-with-description-hint').show(); $('.js-with-description-hint').show();
$('p.js-without-description-hint').hide(); $('.js-without-description-hint').hide();
}); });
}; };

View File

@ -4,7 +4,7 @@
/* global Flash */ /* global Flash */
require('./breakpoints'); require('./breakpoints');
window.Cookies = require('vendor/js.cookie'); window.Cookies = require('js-cookie');
require('./flash'); require('./flash');
/* eslint-disable max-len */ /* eslint-disable max-len */
@ -61,7 +61,6 @@ require('./flash');
constructor({ action, setUrl, stubLocation } = {}) { constructor({ action, setUrl, stubLocation } = {}) {
this.diffsLoaded = false; this.diffsLoaded = false;
this.pipelinesLoaded = false;
this.commitsLoaded = false; this.commitsLoaded = false;
this.fixedLayoutPref = null; this.fixedLayoutPref = null;
@ -83,12 +82,18 @@ require('./flash');
$(document) $(document)
.on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown) .on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
.on('click', '.js-show-tab', this.showTab); .on('click', '.js-show-tab', this.showTab);
$('.merge-request-tabs a[data-toggle="tab"]')
.on('click', this.clickTab);
} }
unbindEvents() { unbindEvents() {
$(document) $(document)
.off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown) .off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
.off('click', '.js-show-tab', this.showTab); .off('click', '.js-show-tab', this.showTab);
$('.merge-request-tabs a[data-toggle="tab"]')
.off('click', this.clickTab);
} }
showTab(e) { showTab(e) {
@ -96,6 +101,14 @@ require('./flash');
this.activateTab($(e.target).data('action')); this.activateTab($(e.target).data('action'));
} }
clickTab(e) {
if (e.target && gl.utils.isMetaClick(e)) {
const targetLink = e.target.getAttribute('href');
e.stopImmediatePropagation();
window.open(targetLink, '_blank');
}
}
tabShown(e) { tabShown(e) {
const $target = $(e.target); const $target = $(e.target);
const action = $target.data('action'); const action = $target.data('action');
@ -112,14 +125,9 @@ require('./flash');
if (this.diffViewType() === 'parallel') { if (this.diffViewType() === 'parallel') {
this.expandViewContainer(); this.expandViewContainer();
} }
const navBarHeight = $('.navbar-gitlab').outerHeight();
$.scrollTo('.merge-request-details .merge-request-tabs', { $.scrollTo('.merge-request-details .merge-request-tabs', {
offset: -navBarHeight, offset: 0,
}); });
} else if (action === 'pipelines') {
this.loadPipelines($target.attr('href'));
this.expandView();
this.resetViewContainer();
} else { } else {
this.expandView(); this.expandView();
this.resetViewContainer(); this.resetViewContainer();
@ -131,11 +139,7 @@ require('./flash');
scrollToElement(container) { scrollToElement(container) {
if (location.hash) { if (location.hash) {
const offset = 0 - ( const offset = -$('.js-tabs-affix').outerHeight();
$('.navbar-gitlab').outerHeight() +
$('.layout-nav').outerHeight() +
$('.js-tabs-affix').outerHeight()
);
const $el = $(`${container} ${location.hash}:not(.match)`); const $el = $(`${container} ${location.hash}:not(.match)`);
if ($el.length) { if ($el.length) {
$.scrollTo($el[0], { offset }); $.scrollTo($el[0], { offset });
@ -244,25 +248,6 @@ require('./flash');
}); });
} }
loadPipelines(source) {
if (this.pipelinesLoaded) {
return;
}
this.ajaxGet({
url: `${source}.json`,
success: (data) => {
$('#pipelines').html(data.html);
gl.utils.localTimeAgo($('.js-timeago', '#pipelines'));
this.pipelinesLoaded = true;
this.scrollToElement('#pipelines');
new gl.MiniPipelineGraph({
container: '.js-pipeline-table',
});
},
});
}
// Show or hide the loading spinner // Show or hide the loading spinner
// //
// status - Boolean, true to show, false to hide // status - Boolean, true to show, false to hide
@ -340,14 +325,12 @@ require('./flash');
if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return; if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return;
const $diffTabs = $('#diff-notes-app'); const $diffTabs = $('#diff-notes-app');
const $fixedNav = $('.navbar-fixed-top');
const $layoutNav = $('.layout-nav');
$tabs.off('affix.bs.affix affix-top.bs.affix') $tabs.off('affix.bs.affix affix-top.bs.affix')
.affix({ .affix({
offset: { offset: {
top: () => ( top: () => (
$diffTabs.offset().top - $tabs.height() - $fixedNav.height() - $layoutNav.height() $diffTabs.offset().top - $tabs.height()
), ),
}, },
}) })

View File

@ -51,6 +51,8 @@ require('./smart_interval');
this.getCIStatus(false); this.getCIStatus(false);
this.retrieveSuccessIcon(); this.retrieveSuccessIcon();
this.initMiniPipelineGraph();
this.ciStatusInterval = new global.SmartInterval({ this.ciStatusInterval = new global.SmartInterval({
callback: this.getCIStatus.bind(this, true), callback: this.getCIStatus.bind(this, true),
startingInterval: 10000, startingInterval: 10000,
@ -66,6 +68,7 @@ require('./smart_interval');
incrementByFactorOf: 15000, incrementByFactorOf: 15000,
immediateExecution: true, immediateExecution: true,
}); });
notifyPermissions(); notifyPermissions();
} }
@ -151,7 +154,7 @@ require('./smart_interval');
return $.getJSON(this.opts.ci_status_url, (function(_this) { return $.getJSON(this.opts.ci_status_url, (function(_this) {
return function(data) { return function(data) {
var message, status, title; var message, status, title;
if (data.status === '') { if (!data.status) {
return; return;
} }
if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
@ -236,17 +239,20 @@ require('./smart_interval');
case "failed": case "failed":
case "canceled": case "canceled":
case "not_found": case "not_found":
return this.setMergeButtonClass('btn-danger'); this.setMergeButtonClass('btn-danger');
break;
case "running": case "running":
return this.setMergeButtonClass('btn-info'); this.setMergeButtonClass('btn-info');
break;
case "success": case "success":
case "success_with_warnings": case "success_with_warnings":
return this.setMergeButtonClass('btn-create'); this.setMergeButtonClass('btn-create');
} }
} else { } else {
$('.ci_widget.ci-error').show(); $('.ci_widget.ci-error').show();
return this.setMergeButtonClass('btn-danger'); this.setMergeButtonClass('btn-danger');
} }
this.initMiniPipelineGraph();
}; };
MergeRequestWidget.prototype.showCICoverage = function(coverage) { MergeRequestWidget.prototype.showCICoverage = function(coverage) {
@ -269,6 +275,12 @@ require('./smart_interval');
$('.js-commit-link').text(`#${id}`).attr('href', [commitsUrl, id].join('/')); $('.js-commit-link').text(`#${id}`).attr('href', [commitsUrl, id].join('/'));
}; };
MergeRequestWidget.prototype.initMiniPipelineGraph = function() {
new gl.MiniPipelineGraph({
container: '.js-pipeline-inline-mr-widget-graph:visible',
}).bindEvents();
};
return MergeRequestWidget; return MergeRequestWidget;
})(); })();
})(window.gl || (window.gl = {})); })(window.gl || (window.gl = {}));

View File

@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-use-before-define, camelcase, quotes, object-shorthand, no-shadow, no-unused-vars, comma-dangle, no-var, prefer-template, no-underscore-dangle, consistent-return, one-var, one-var-declaration-per-line, default-case, prefer-arrow-callback, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-use-before-define, camelcase, quotes, object-shorthand, no-shadow, no-unused-vars, comma-dangle, no-var, prefer-template, no-underscore-dangle, consistent-return, one-var, one-var-declaration-per-line, default-case, prefer-arrow-callback, max-len */
/* global Flash */ /* global Flash */
/* global Sortable */
(function() { (function() {
this.Milestone = (function() { this.Milestone = (function() {
@ -8,11 +9,9 @@
type: "PUT", type: "PUT",
url: issue_url, url: issue_url,
data: data, data: data,
success: (function(_this) { success: function(_data) {
return function(_data) { return Milestone.successCallback(_data, li);
return _this.successCallback(_data, li); },
};
})(this),
error: function(data) { error: function(data) {
return new Flash("Issue update failed", 'alert'); return new Flash("Issue update failed", 'alert');
}, },
@ -27,11 +26,9 @@
type: "PUT", type: "PUT",
url: sort_issues_url, url: sort_issues_url,
data: data, data: data,
success: (function(_this) { success: function(_data) {
return function(_data) { return Milestone.successCallback(_data);
return _this.successCallback(_data); },
};
})(this),
error: function() { error: function() {
return new Flash("Issues update failed", 'alert'); return new Flash("Issues update failed", 'alert');
}, },
@ -46,11 +43,9 @@
type: "PUT", type: "PUT",
url: sort_mr_url, url: sort_mr_url,
data: data, data: data,
success: (function(_this) { success: function(_data) {
return function(_data) { return Milestone.successCallback(_data);
return _this.successCallback(_data); },
};
})(this),
error: function(data) { error: function(data) {
return new Flash("Issue update failed", 'alert'); return new Flash("Issue update failed", 'alert');
}, },
@ -63,11 +58,9 @@
type: "PUT", type: "PUT",
url: merge_request_url, url: merge_request_url,
data: data, data: data,
success: (function(_this) { success: function(_data) {
return function(_data) { return Milestone.successCallback(_data, li);
return _this.successCallback(_data, li); },
};
})(this),
error: function(data) { error: function(data) {
return new Flash("Issue update failed", 'alert'); return new Flash("Issue update failed", 'alert');
}, },
@ -81,65 +74,30 @@
img_tag = $('<img/>'); img_tag = $('<img/>');
img_tag.attr('src', data.assignee.avatar_url); img_tag.attr('src', data.assignee.avatar_url);
img_tag.addClass('avatar s16'); img_tag.addClass('avatar s16');
$(element).find('.assignee-icon').html(img_tag); $(element).find('.assignee-icon img').replaceWith(img_tag);
} else { } else {
$(element).find('.assignee-icon').html(''); $(element).find('.assignee-icon').empty();
} }
return $(element).effect('highlight'); return $(element).effect('highlight');
}; };
function Milestone() { function Milestone() {
var oldMouseStart; var oldMouseStart;
oldMouseStart = $.ui.sortable.prototype._mouseStart;
$.ui.sortable.prototype._mouseStart = function(event, overrideHandle, noActivation) {
this._trigger("beforeStart", event, this._uiHash());
return oldMouseStart.apply(this, [event, overrideHandle, noActivation]);
};
this.bindIssuesSorting(); this.bindIssuesSorting();
this.bindMergeRequestSorting(); this.bindMergeRequestSorting();
this.bindTabsSwitching(); this.bindTabsSwitching();
} }
Milestone.prototype.bindIssuesSorting = function() { Milestone.prototype.bindIssuesSorting = function() {
return $("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable({ $('#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed').each(function (i, el) {
connectWith: ".issues-sortable-list", this.createSortable(el, {
dropOnEmpty: true, group: 'issue-list',
items: "li:not(.ui-sort-disabled)", listEls: $('.issues-sortable-list'),
beforeStart: function(event, ui) { fieldName: 'issue',
return $(".issues-sortable-list").css("min-height", ui.item.outerHeight()); sortCallback: Milestone.sortIssues,
}, updateCallback: Milestone.updateIssue,
stop: function(event, ui) { });
return $(".issues-sortable-list").css("min-height", "0px"); }.bind(this));
},
update: function(event, ui) {
var data;
// Prevents sorting from container which element has been removed.
if ($(this).find(ui.item).length > 0) {
data = $(this).sortable("serialize");
return Milestone.sortIssues(data);
}
},
receive: function(event, ui) {
var data, issue_id, issue_url, new_state;
new_state = $(this).data('state');
issue_id = ui.item.data('iid');
issue_url = ui.item.data('url');
data = (function() {
switch (new_state) {
case 'ongoing':
return "issue[assignee_id]=" + gon.current_user_id;
case 'unassigned':
return "issue[assignee_id]=";
case 'closed':
return "issue[state_event]=close";
}
})();
if ($(ui.sender).data('state') === "closed") {
data += "&issue[state_event]=reopen";
}
return Milestone.updateIssue(ui.item, issue_url, data);
}
}).disableSelection();
}; };
Milestone.prototype.bindTabsSwitching = function() { Milestone.prototype.bindTabsSwitching = function() {
@ -154,42 +112,62 @@
}; };
Milestone.prototype.bindMergeRequestSorting = function() { Milestone.prototype.bindMergeRequestSorting = function() {
return $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable({ $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").each(function (i, el) {
connectWith: ".merge_requests-sortable-list", this.createSortable(el, {
dropOnEmpty: true, group: 'merge-request-list',
items: "li:not(.ui-sort-disabled)", listEls: $(".merge_requests-sortable-list:not(#merge_requests-list-merged)"),
beforeStart: function(event, ui) { fieldName: 'merge_request',
return $(".merge_requests-sortable-list").css("min-height", ui.item.outerHeight()); sortCallback: Milestone.sortMergeRequests,
updateCallback: Milestone.updateMergeRequest,
});
}.bind(this));
};
Milestone.prototype.createSortable = function(el, opts) {
return Sortable.create(el, {
group: opts.group,
filter: '.is-disabled',
forceFallback: true,
onStart: function(e) {
opts.listEls.css('min-height', e.item.offsetHeight);
}, },
stop: function(event, ui) { onEnd: function () {
return $(".merge_requests-sortable-list").css("min-height", "0px"); opts.listEls.css("min-height", "0px");
}, },
update: function(event, ui) { onUpdate: function(e) {
var data; var ids = this.toArray(),
data = $(this).sortable("serialize"); data;
return Milestone.sortMergeRequests(data);
if (ids.length) {
data = ids.map(function(id) {
return 'sortable_' + opts.fieldName + '[]=' + id;
}).join('&');
opts.sortCallback(data);
}
}, },
receive: function(event, ui) { onAdd: function (e) {
var data, merge_request_id, merge_request_url, new_state; var data, issuableId, issuableUrl, newState;
new_state = $(this).data('state'); newState = e.to.dataset.state;
merge_request_id = ui.item.data('iid'); issuableUrl = e.item.dataset.url;
merge_request_url = ui.item.data('url');
data = (function() { data = (function() {
switch (new_state) { switch (newState) {
case 'ongoing': case 'ongoing':
return "merge_request[assignee_id]=" + gon.current_user_id; return opts.fieldName + '[assignee_id]=' + gon.current_user_id;
case 'unassigned': case 'unassigned':
return "merge_request[assignee_id]="; return opts.fieldName + '[assignee_id]=';
case 'closed': case 'closed':
return "merge_request[state_event]=close"; return opts.fieldName + '[state_event]=close';
} }
})(); })();
if ($(ui.sender).data('state') === "closed") { if (e.from.dataset.state === 'closed') {
data += "&merge_request[state_event]=reopen"; data += '&' + opts.fieldName + '[state_event]=reopen';
} }
return Milestone.updateMergeRequest(ui.item, merge_request_url, data);
opts.updateCallback(e.item, issuableUrl, data);
this.options.onUpdate.call(this, e);
} }
}).disableSelection(); });
}; };
return Milestone; return Milestone;

View File

@ -21,8 +21,6 @@
this.container = opts.container || ''; this.container = opts.container || '';
this.dropdownListSelector = '.js-builds-dropdown-container'; this.dropdownListSelector = '.js-builds-dropdown-container';
this.getBuildsList = this.getBuildsList.bind(this); this.getBuildsList = this.getBuildsList.bind(this);
this.bindEvents();
} }
/** /**
@ -30,7 +28,7 @@
* All dropdown events are fired at the .dropdown-menu's parent element. * All dropdown events are fired at the .dropdown-menu's parent element.
*/ */
bindEvents() { bindEvents() {
$(this.container).on('shown.bs.dropdown', this.getBuildsList); $(document).on('shown.bs.dropdown', this.container, this.getBuildsList);
} }
/** /**

View File

@ -455,7 +455,7 @@ require('vendor/task_list');
var mergeRequestId = $form.data('noteable-iid'); var mergeRequestId = $form.data('noteable-iid');
if (ResolveService != null) { if (ResolveService != null) {
ResolveService.toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId); ResolveService.toggleResolveForDiscussion(mergeRequestId, discussionId);
} }
} }

View File

@ -25,6 +25,7 @@
bindEvents() { bindEvents() {
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm); $('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
$('#user_notification_email').on('change', this.submitForm); $('#user_notification_email').on('change', this.submitForm);
$('#user_notified_of_own_activity').on('change', this.submitForm);
$('.update-username').on('ajax:before', this.beforeUpdateUsername); $('.update-username').on('ajax:before', this.beforeUpdateUsername);
$('.update-username').on('ajax:complete', this.afterUpdateUsername); $('.update-username').on('ajax:complete', this.afterUpdateUsername);
$('.update-notifications').on('ajax:success', this.onUpdateNotifs); $('.update-notifications').on('ajax:success', this.onUpdateNotifs);

View File

@ -1,29 +0,0 @@
/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, consistent-return */
/* global Shortcuts */
/* global Mousetrap */
require('./shortcuts');
(function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
this.ShortcutsBlob = (function(superClass) {
extend(ShortcutsBlob, superClass);
function ShortcutsBlob(skipResetBindings) {
ShortcutsBlob.__super__.constructor.call(this, skipResetBindings);
Mousetrap.bind('y', ShortcutsBlob.copyToClipboard);
}
ShortcutsBlob.copyToClipboard = function() {
var clipboardButton;
clipboardButton = $('.btn-clipboard');
if (clipboardButton) {
return clipboardButton.click();
}
};
return ShortcutsBlob;
})(Shortcuts);
}).call(this);

View File

@ -0,0 +1,29 @@
/* global Mousetrap */
/* global Shortcuts */
require('./shortcuts');
const defaults = {
skipResetBindings: false,
fileBlobPermalinkUrl: null,
};
class ShortcutsBlob extends Shortcuts {
constructor(opts) {
const options = Object.assign({}, defaults, opts);
super(options.skipResetBindings);
this.options = options;
Mousetrap.bind('y', this.moveToFilePermalink.bind(this));
}
moveToFilePermalink() {
if (this.options.fileBlobPermalinkUrl) {
const hash = gl.utils.getLocationHash();
const hashUrlString = hash ? `#${hash}` : '';
gl.utils.visitUrl(`${this.options.fileBlobPermalinkUrl}${hashUrlString}`);
}
}
}
module.exports = ShortcutsBlob;

View File

@ -1,14 +1,12 @@
/* eslint-disable arrow-parens, class-methods-use-this, no-param-reassign */ /* eslint-disable arrow-parens, class-methods-use-this, no-param-reassign */
/* global Cookies */ /* global Cookies */
((global) => { (() => {
let singleton;
const pinnedStateCookie = 'pin_nav'; const pinnedStateCookie = 'pin_nav';
const sidebarBreakpoint = 1024; const sidebarBreakpoint = 1024;
const pageSelector = '.page-with-sidebar'; const pageSelector = '.page-with-sidebar';
const navbarSelector = '.navbar-fixed-top'; const navbarSelector = '.navbar-gitlab';
const sidebarWrapperSelector = '.sidebar-wrapper'; const sidebarWrapperSelector = '.sidebar-wrapper';
const sidebarContentSelector = '.nav-sidebar'; const sidebarContentSelector = '.nav-sidebar';
@ -23,11 +21,12 @@
class Sidebar { class Sidebar {
constructor() { constructor() {
if (!singleton) { if (!Sidebar.singleton) {
singleton = this; Sidebar.singleton = this;
singleton.init(); Sidebar.singleton.init();
} }
return singleton;
return Sidebar.singleton;
} }
init() { init() {
@ -36,13 +35,16 @@
window.innerWidth >= sidebarBreakpoint && window.innerWidth >= sidebarBreakpoint &&
$(pageSelector).hasClass(expandedPageClass) $(pageSelector).hasClass(expandedPageClass)
); );
$(window).on('resize', () => this.setSidebarHeight());
$(document) $(document)
.on('click', sidebarToggleSelector, () => this.toggleSidebar()) .on('click', sidebarToggleSelector, () => this.toggleSidebar())
.on('click', pinnedToggleSelector, () => this.togglePinnedState()) .on('click', pinnedToggleSelector, () => this.togglePinnedState())
.on('click', 'html, body', (e) => this.handleClickEvent(e)) .on('click', 'html, body, a, button', (e) => this.handleClickEvent(e))
.on('DOMContentLoaded', () => this.renderState()) .on('DOMContentLoaded', () => this.renderState())
.on('scroll', () => this.setSidebarHeight())
.on('todo:toggle', (e, count) => this.updateTodoCount(count)); .on('todo:toggle', (e, count) => this.updateTodoCount(count));
this.renderState(); this.renderState();
this.setSidebarHeight();
} }
handleClickEvent(e) { handleClickEvent(e) {
@ -65,6 +67,16 @@
this.renderState(); this.renderState();
} }
setSidebarHeight() {
const $navHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight();
const diff = $navHeight - $('body').scrollTop();
if (diff > 0) {
$('.js-right-sidebar').outerHeight($(window).height() - diff);
} else {
$('.js-right-sidebar').outerHeight('100%');
}
}
togglePinnedState() { togglePinnedState() {
this.isPinned = !this.isPinned; this.isPinned = !this.isPinned;
if (!this.isPinned) { if (!this.isPinned) {
@ -88,10 +100,12 @@
$pinnedToggle.attr('title', tooltipText).tooltip('fixTitle').tooltip(tooltipState); $pinnedToggle.attr('title', tooltipText).tooltip('fixTitle').tooltip(tooltipState);
if (this.isExpanded) { if (this.isExpanded) {
setTimeout(() => $(sidebarContentSelector).niceScroll().updateScrollBar(), 200); const sidebarContent = $(sidebarContentSelector);
setTimeout(() => { sidebarContent.niceScroll().updateScrollBar(); }, 200);
} }
} }
} }
global.Sidebar = Sidebar; window.gl = window.gl || {};
})(window.gl || (window.gl = {})); gl.Sidebar = Sidebar;
})();

View File

@ -33,13 +33,13 @@
this.$toggleIcon.addClass('fa-caret-down'); this.$toggleIcon.addClass('fa-caret-down');
} }
$('.file-title, .click-to-expand', this.file).on('click', (function (e) { $('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
this.toggleDiff($(e.target)); this.toggleDiff($(e.target));
}).bind(this)); }).bind(this));
} }
SingleFileDiff.prototype.toggleDiff = function($target, cb) { SingleFileDiff.prototype.toggleDiff = function($target, cb) {
if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return; if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
this.isOpen = !this.isOpen; this.isOpen = !this.isOpen;
if (!this.isOpen && !this.hasError) { if (!this.isOpen && !this.hasError) {
this.content.hide(); this.content.hide();

View File

@ -50,14 +50,15 @@
return ( return (
children[target.index] || children[target.index] ||
children[target.index === 'first' ? 0 : -1] || children[target.index === 'first' ? 0 : -1] ||
children[target.index === 'last' ? children.length - 1 : -1] children[target.index === 'last' ? children.length - 1 : -1] ||
el
); );
} }
function getRect(el) { function getRect(el) {
var rect = el.getBoundingClientRect(); var rect = el.getBoundingClientRect();
var width = rect.right - rect.left; var width = rect.right - rect.left;
var height = rect.bottom - rect.top; var height = rect.bottom - rect.top + 10;
return { return {
x: rect.left, x: rect.left,

View File

@ -146,14 +146,26 @@
} }
goToTodoUrl(e) { goToTodoUrl(e) {
const todoLink = $(this).data('url'); const todoLink = this.dataset.url;
let targetLink = e.target.getAttribute('href');
if (e.target.tagName === 'IMG') { // See if clicked target was Avatar
targetLink = e.target.parentElement.getAttribute('href'); // Parent of Avatar is link
}
if (!todoLink) { if (!todoLink) {
return; return;
} }
// Allow Meta-Click or Mouse3-click to open in a new tab
if (e.metaKey || e.which === 2) { if (gl.utils.isMetaClick(e)) {
e.preventDefault(); e.preventDefault();
return window.open(todoLink, '_blank'); // Meta-Click on username leads to different URL than todoLink.
// Turbolinks can resolve that URL, but window.open requires URL manually.
if (targetLink !== todoLink) {
return window.open(targetLink, '_blank');
} else {
return window.open(todoLink, '_blank');
}
} else { } else {
return gl.utils.visitUrl(todoLink); return gl.utils.visitUrl(todoLink);
} }

View File

@ -1,41 +1,36 @@
/* eslint-disable no-param-reassign */
/* global Vue, VueResource, gl */ /* global Vue, VueResource, gl */
window.Vue = require('vue'); window.Vue = require('vue');
window.Vue.use(require('vue-resource')); window.Vue.use(require('vue-resource'));
require('../vue_common_component/commit'); require('../lib/utils/common_utils');
require('../vue_pagination/index'); require('../vue_shared/vue_resource_interceptor');
require('../boards/vue_resource_interceptor');
require('./status');
require('./store');
require('./pipeline_url');
require('./stage');
require('./stages');
require('./pipeline_actions');
require('./time_ago');
require('./pipelines'); require('./pipelines');
(() => { $(() => new Vue({
const project = document.querySelector('.pipelines'); el: document.querySelector('.vue-pipelines-index'),
const entry = document.querySelector('.vue-pipelines-index');
const svgs = document.querySelector('.pipeline-svgs');
if (!entry) return null; data() {
return new Vue({ const project = document.querySelector('.pipelines');
el: entry, const svgs = document.querySelector('.pipeline-svgs').dataset;
data: {
// Transform svgs DOMStringMap to a plain Object.
const svgsObject = gl.utils.DOMStringMapToObject(svgs);
return {
scope: project.dataset.url, scope: project.dataset.url,
store: new gl.PipelineStore(), store: new gl.PipelineStore(),
svgs: svgs.dataset, svgs: svgsObject,
}, };
components: { },
'vue-pipelines': gl.VuePipelines, components: {
}, 'vue-pipelines': gl.VuePipelines,
template: ` },
<vue-pipelines template: `
:scope='scope' <vue-pipelines
:store='store' :scope='scope'
:svgs='svgs' :store='store'
> :svgs='svgs'
</vue-pipelines> >
`, </vue-pipelines>
}); `,
})(); }));

View File

@ -50,9 +50,9 @@
<button <button
v-if='artifacts' v-if='artifacts'
class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download" class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
data-toggle="dropdown"
title="Artifacts" title="Artifacts"
data-placement="top" data-placement="top"
data-toggle="dropdown"
aria-label="Artifacts" aria-label="Artifacts"
> >
<i class="fa fa-download" aria-hidden="true"></i> <i class="fa fa-download" aria-hidden="true"></i>
@ -81,8 +81,7 @@
data-placement="top" data-placement="top"
data-toggle="dropdown" data-toggle="dropdown"
:href='pipeline.retry_path' :href='pipeline.retry_path'
aria-label="Retry" aria-label="Retry">
>
<i class="fa fa-repeat" aria-hidden="true"></i> <i class="fa fa-repeat" aria-hidden="true"></i>
</a> </a>
<a <a
@ -94,8 +93,7 @@
data-placement="top" data-placement="top"
data-toggle="dropdown" data-toggle="dropdown"
:href='pipeline.cancel_path' :href='pipeline.cancel_path'
aria-label="Cancel" aria-label="Cancel">
>
<i class="fa fa-remove" aria-hidden="true"></i> <i class="fa fa-remove" aria-hidden="true"></i>
</a> </a>
</div> </div>

View File

@ -1,19 +1,19 @@
/* global Vue, gl */ /* global Vue, gl */
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
window.Vue = require('vue');
require('../vue_shared/components/table_pagination');
require('./store');
require('../vue_shared/components/pipelines_table');
((gl) => { ((gl) => {
gl.VuePipelines = Vue.extend({ gl.VuePipelines = Vue.extend({
components: { components: {
runningPipeline: gl.VueRunningPipeline, 'gl-pagination': gl.VueGlPagination,
pipelineActions: gl.VuePipelineActions, 'pipelines-table-component': gl.pipelines.PipelinesTableComponent,
stages: gl.VueStages,
commit: gl.CommitComponent,
pipelineUrl: gl.VuePipelineUrl,
pipelineHead: gl.VuePipelineHead,
glPagination: gl.VueGlPagination,
statusScope: gl.VueStatusScope,
timeAgo: gl.VueTimeAgo,
}, },
data() { data() {
return { return {
pipelines: [], pipelines: [],
@ -38,87 +38,29 @@
change(pagenum, apiScope) { change(pagenum, apiScope) {
gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`); gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`);
}, },
author(pipeline) {
if (!pipeline.commit) return { avatar_url: '', web_url: '', username: '' };
if (pipeline.commit.author) return pipeline.commit.author;
return {
avatar_url: pipeline.commit.author_gravatar_url,
web_url: `mailto:${pipeline.commit.author_email}`,
username: pipeline.commit.author_name,
};
},
ref(pipeline) {
const { ref } = pipeline;
return { name: ref.name, tag: ref.tag, ref_url: ref.path };
},
commitTitle(pipeline) {
return pipeline.commit ? pipeline.commit.title : '';
},
commitSha(pipeline) {
return pipeline.commit ? pipeline.commit.short_id : '';
},
commitUrl(pipeline) {
return pipeline.commit ? pipeline.commit.commit_path : '';
},
match(string) {
return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase());
},
}, },
template: ` template: `
<div> <div>
<div class="pipelines realtime-loading" v-if='pipelines.length < 1'>
<i class="fa fa-spinner fa-spin"></i>
</div>
<div class="table-holder" v-if='pipelines.length'>
<table class="table ci-table">
<thead>
<tr>
<th class="pipeline-status">Status</th>
<th class="pipeline-info">Pipeline</th>
<th class="pipeline-commit">Commit</th>
<th class="pipeline-stages">Stages</th>
<th class="pipeline-date"></th>
<th class="pipeline-actions hidden-xs"></th>
</tr>
</thead>
<tbody>
<tr class="commit" v-for='pipeline in pipelines'>
<status-scope
:pipeline='pipeline'
:match='match'
:svgs='svgs'
>
</status-scope>
<pipeline-url :pipeline='pipeline'></pipeline-url>
<td>
<commit
:commit-icon-svg='svgs.commitIconSvg'
:author='author(pipeline)'
:tag="pipeline.ref.tag"
:title='commitTitle(pipeline)'
:commit-ref='ref(pipeline)'
:short-sha='commitSha(pipeline)'
:commit-url='commitUrl(pipeline)'
>
</commit>
</td>
<stages
:pipeline='pipeline'
:svgs='svgs'
:match='match'
>
</stages>
<time-ago :pipeline='pipeline' :svgs='svgs'></time-ago>
<pipeline-actions :pipeline='pipeline' :svgs='svgs'></pipeline-actions>
</tr>
</tbody>
</table>
</div>
<div class="pipelines realtime-loading" v-if='pageRequest'> <div class="pipelines realtime-loading" v-if='pageRequest'>
<i class="fa fa-spinner fa-spin"></i> <i class="fa fa-spinner fa-spin"></i>
</div> </div>
<div class="blank-state blank-state-no-icon"
v-if="!pageRequest && pipelines.length === 0">
<h2 class="blank-state-title js-blank-state-title">
No pipelines to show
</h2>
</div>
<div class="table-holder" v-if='!pageRequest && pipelines.length'>
<pipelines-table-component
:pipelines='pipelines'
:svgs='svgs'>
</pipelines-table-component>
</div>
<gl-pagination <gl-pagination
v-if='pageInfo.total > pageInfo.perPage' v-if='!pageRequest && pipelines.length && pageInfo.total > pageInfo.perPage'
:pagenum='pagenum' :pagenum='pagenum'
:change='change' :change='change'
:count='count.all' :count='count.all'

View File

@ -15,7 +15,7 @@
required: true, required: true,
}, },
svgs: { svgs: {
type: DOMStringMap, type: Object,
required: true, required: true,
}, },
match: { match: {

View File

@ -1,21 +0,0 @@
/* global Vue, gl */
/* eslint-disable no-param-reassign */
((gl) => {
gl.VueStages = Vue.extend({
components: {
'vue-stage': gl.VueStage,
},
props: ['pipeline', 'svgs', 'match'],
template: `
<td class="stage-cell">
<div
class="stage-container dropdown js-mini-pipeline-graph"
v-for='stage in pipeline.details.stages'
>
<vue-stage :stage='stage' :svgs='svgs' :match='match'></vue-stage>
</div>
</td>
`,
});
})(window.gl || (window.gl = {}));

View File

@ -20,6 +20,7 @@ require('../vue_realtime_listener');
gl.PipelineStore = class { gl.PipelineStore = class {
fetchDataLoop(Vue, pageNum, url, apiScope) { fetchDataLoop(Vue, pageNum, url, apiScope) {
this.pageRequest = true;
const updatePipelineNums = (count) => { const updatePipelineNums = (count) => {
const { all } = count; const { all } = count;
const running = count.running_or_pending; const running = count.running_or_pending;
@ -41,16 +42,18 @@ require('../vue_realtime_listener');
this.pageRequest = false; this.pageRequest = false;
}, () => { }, () => {
this.pageRequest = false; this.pageRequest = false;
return new Flash('Something went wrong on our end.'); return new Flash('An error occurred while fetching the pipelines, please reload the page again.');
}); });
goFetch(); goFetch();
const startTimeLoops = () => { const startTimeLoops = () => {
this.timeLoopInterval = setInterval(() => { this.timeLoopInterval = setInterval(() => {
this.$children this.$children[0].$children.reduce((acc, component) => {
.filter(e => e.$options._componentTag === 'time-ago') const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
.forEach(e => e.changeTime()); acc.push(timeAgoComponent);
return acc;
}, []).forEach(e => e.changeTime());
}, 10000); }, 10000);
}; };

View File

@ -1,6 +1,9 @@
/* global Vue, gl */ /* global Vue, gl */
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
window.Vue = require('vue');
require('../lib/utils/datetime_utility');
((gl) => { ((gl) => {
gl.VueTimeAgo = Vue.extend({ gl.VueTimeAgo = Vue.extend({
data() { data() {

View File

@ -14,5 +14,16 @@
window.addEventListener('focus', startIntervals); window.addEventListener('focus', startIntervals);
window.addEventListener('blur', removeIntervals); window.addEventListener('blur', removeIntervals);
document.addEventListener('beforeunload', removeAll); document.addEventListener('beforeunload', removeAll);
// add removeAll methods to stack
const stack = gl.VueRealtimeListener.reset;
gl.VueRealtimeListener.reset = () => {
gl.VueRealtimeListener.reset = stack;
removeAll();
stack();
};
}; };
// remove all event listeners and intervals
gl.VueRealtimeListener.reset = () => undefined; // noop
})(window.gl || (window.gl = {})); })(window.gl || (window.gl = {}));

View File

@ -1,7 +1,5 @@
/* global Vue */ /* global Vue */
window.Vue = require('vue');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};

View File

@ -0,0 +1,61 @@
/* eslint-disable no-param-reassign */
/* global Vue */
require('./pipelines_table_row');
/**
* Pipelines Table Component.
*
* Given an array of objects, renders a table.
*/
(() => {
window.gl = window.gl || {};
gl.pipelines = gl.pipelines || {};
gl.pipelines.PipelinesTableComponent = Vue.component('pipelines-table-component', {
props: {
pipelines: {
type: Array,
required: true,
default: () => ([]),
},
/**
* TODO: Remove this when we have webpack.
*/
svgs: {
type: Object,
required: true,
default: () => ({}),
},
},
components: {
'pipelines-table-row-component': gl.pipelines.PipelinesTableRowComponent,
},
template: `
<table class="table ci-table">
<thead>
<tr>
<th class="js-pipeline-status pipeline-status">Status</th>
<th class="js-pipeline-info pipeline-info">Pipeline</th>
<th class="js-pipeline-commit pipeline-commit">Commit</th>
<th class="js-pipeline-stages pipeline-stages">Stages</th>
<th class="js-pipeline-date pipeline-date"></th>
<th class="js-pipeline-actions pipeline-actions hidden-xs"></th>
</tr>
</thead>
<tbody>
<template v-for="model in pipelines"
v-bind:model="model">
<tr is="pipelines-table-row-component"
:pipeline="model"
:svgs="svgs"></tr>
</template>
</tbody>
</table>
`,
});
})();

View File

@ -0,0 +1,234 @@
/* eslint-disable no-param-reassign */
/* global Vue */
require('../../vue_pipelines_index/status');
require('../../vue_pipelines_index/pipeline_url');
require('../../vue_pipelines_index/stage');
require('../../vue_pipelines_index/pipeline_actions');
require('../../vue_pipelines_index/time_ago');
require('./commit');
/**
* Pipeline table row.
*
* Given the received object renders a table row in the pipelines' table.
*/
(() => {
window.gl = window.gl || {};
gl.pipelines = gl.pipelines || {};
gl.pipelines.PipelinesTableRowComponent = Vue.component('pipelines-table-row-component', {
props: {
pipeline: {
type: Object,
required: true,
default: () => ({}),
},
/**
* TODO: Remove this when we have webpack;
*/
svgs: {
type: Object,
required: true,
default: () => ({}),
},
},
components: {
'commit-component': gl.CommitComponent,
'pipeline-actions': gl.VuePipelineActions,
'dropdown-stage': gl.VueStage,
'pipeline-url': gl.VuePipelineUrl,
'status-scope': gl.VueStatusScope,
'time-ago': gl.VueTimeAgo,
},
computed: {
/**
* If provided, returns the commit tag.
* Needed to render the commit component column.
*
* This field needs a lot of verification, because of different possible cases:
*
* 1. person who is an author of a commit might be a GitLab user
* 2. if person who is an author of a commit is a GitLab user he/she can have a GitLab avatar
* 3. If GitLab user does not have avatar he/she might have a Gravatar
* 4. If committer is not a GitLab User he/she can have a Gravatar
* 5. We do not have consistent API object in this case
* 6. We should improve API and the code
*
* @returns {Object|Undefined}
*/
commitAuthor() {
let commitAuthorInformation;
// 1. person who is an author of a commit might be a GitLab user
if (this.pipeline &&
this.pipeline.commit &&
this.pipeline.commit.author) {
// 2. if person who is an author of a commit is a GitLab user
// he/she can have a GitLab avatar
if (this.pipeline.commit.author.avatar_url) {
commitAuthorInformation = this.pipeline.commit.author;
// 3. If GitLab user does not have avatar he/she might have a Gravatar
} else if (this.pipeline.commit.author_gravatar_url) {
commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, {
avatar_url: this.pipeline.commit.author_gravatar_url,
});
}
}
// 4. If committer is not a GitLab User he/she can have a Gravatar
if (this.pipeline &&
this.pipeline.commit) {
commitAuthorInformation = {
avatar_url: this.pipeline.commit.author_gravatar_url,
web_url: `mailto:${this.pipeline.commit.author_email}`,
username: this.pipeline.commit.author_name,
};
}
return commitAuthorInformation;
},
/**
* If provided, returns the commit tag.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitTag() {
if (this.pipeline.ref &&
this.pipeline.ref.tag) {
return this.pipeline.ref.tag;
}
return undefined;
},
/**
* If provided, returns the commit ref.
* Needed to render the commit component column.
*
* Matches `path` prop sent in the API to `ref_url` prop needed
* in the commit component.
*
* @returns {Object|Undefined}
*/
commitRef() {
if (this.pipeline.ref) {
return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
if (prop === 'path') {
accumulator.ref_url = this.pipeline.ref[prop];
} else {
accumulator[prop] = this.pipeline.ref[prop];
}
return accumulator;
}, {});
}
return undefined;
},
/**
* If provided, returns the commit url.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitUrl() {
if (this.pipeline.commit &&
this.pipeline.commit.commit_path) {
return this.pipeline.commit.commit_path;
}
return undefined;
},
/**
* If provided, returns the commit short sha.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitShortSha() {
if (this.pipeline.commit &&
this.pipeline.commit.short_id) {
return this.pipeline.commit.short_id;
}
return undefined;
},
/**
* If provided, returns the commit title.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitTitle() {
if (this.pipeline.commit &&
this.pipeline.commit.title) {
return this.pipeline.commit.title;
}
return undefined;
},
},
methods: {
/**
* FIXME: This should not be in this component but in the components that
* need this function.
*
* Used to render SVGs in the following components:
* - status-scope
* - dropdown-stage
*
* @param {String} string
* @return {String}
*/
match(string) {
return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase());
},
},
template: `
<tr class="commit">
<status-scope
:pipeline="pipeline"
:svgs="svgs"
:match="match">
</status-scope>
<pipeline-url :pipeline="pipeline"></pipeline-url>
<td>
<commit-component
:tag="commitTag"
:commit-ref="commitRef"
:commit-url="commitUrl"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"
:commit-icon-svg="svgs.commitIconSvg">
</commit-component>
</td>
<td class="stage-cell">
<div class="stage-container dropdown js-mini-pipeline-graph"
v-if="pipeline.details.stages.length > 0"
v-for="stage in pipeline.details.stages">
<dropdown-stage
:stage="stage"
:svgs="svgs"
:match="match">
</dropdown-stage>
</div>
</td>
<time-ago :pipeline="pipeline" :svgs="svgs"></time-ago>
<pipeline-actions :pipeline="pipeline" :svgs="svgs"></pipeline-actions>
</tr>
`,
});
})();

View File

@ -0,0 +1,23 @@
/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars,
no-param-reassign, no-plusplus */
/* global Vue */
Vue.http.interceptors.push((request, next) => {
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
next((response) => {
if (typeof response.data === 'string') {
response.data = JSON.parse(response.data);
}
Vue.activeResources--;
});
});
Vue.http.interceptors.push((request, next) => {
// needed in order to not break the tests.
if ($.rails) {
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
}
next();
});

View File

@ -2,7 +2,6 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory * This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope. * the top of the compiled file, but it's generally better to create a new file per style scope.
*= require jquery-ui/datepicker
*= require jquery-ui/autocomplete *= require jquery-ui/autocomplete
*= require jquery.atwho *= require jquery.atwho
*= require select2 *= require select2
@ -19,6 +18,8 @@
* directory. * directory.
*/ */
@import "../../../node_modules/pikaday/scss/pikaday";
/* /*
* GitLab UI framework * GitLab UI framework
*/ */

View File

@ -128,8 +128,7 @@
.note-action-button .link-highlight, .note-action-button .link-highlight,
.toolbar-btn, .toolbar-btn,
.dropdown-toggle-caret, .dropdown-toggle-caret {
.fa:not(.fa-bell) {
@include transition(color); @include transition(color);
} }

View File

@ -28,6 +28,8 @@
.avatar { .avatar {
@extend .avatar-circle; @extend .avatar-circle;
@include transition-property(none);
width: 40px; width: 40px;
height: 40px; height: 40px;
padding: 0; padding: 0;

View File

@ -9,6 +9,8 @@
} }
.user-calendar-activities { .user-calendar-activities {
direction: ltr;
.str-truncated { .str-truncated {
max-width: 70%; max-width: 70%;
} }
@ -43,3 +45,56 @@
float: right; float: right;
font-size: 12px; font-size: 12px;
} }
.pika-single.gitlab-theme {
.pika-label {
color: $gl-text-color-secondary;
font-size: 14px;
font-weight: normal;
}
th {
padding: 2px 0;
color: $note-disabled-comment-color;
font-weight: normal;
text-transform: lowercase;
border-top: 1px solid $calendar-border-color;
}
abbr {
cursor: default;
}
td {
border: 1px solid $calendar-border-color;
&:first-child {
border-left: 0;
}
&:last-child {
border-right: 0;
}
}
.pika-day {
border-radius: 0;
background-color: $white-light;
text-align: center;
}
.is-today {
.pika-day {
color: inherit;
font-weight: normal;
}
}
.is-selected .pika-day,
.pika-day:hover,
.is-today .pika-day:hover {
background: $gl-primary;
color: $white-light;
box-shadow: none;
}
}

View File

@ -253,6 +253,8 @@ li.note {
.progress { .progress {
margin-bottom: 0; margin-bottom: 0;
margin-top: 4px; margin-top: 4px;
box-shadow: none;
background-color: $border-gray-light;
} }
} }

View File

@ -125,7 +125,6 @@
top: 100%; top: 100%;
left: 0; left: 0;
z-index: 9; z-index: 9;
max-width: 280px;
min-width: 240px; min-width: 240px;
margin-top: 2px; margin-top: 2px;
margin-bottom: 0; margin-bottom: 0;
@ -137,6 +136,10 @@
border-radius: $border-radius-base; border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color; box-shadow: 0 2px 4px $dropdown-shadow-color;
.filtered-search-input-container & {
max-width: 280px;
}
&.is-loading { &.is-loading {
.dropdown-content { .dropdown-content {
display: none; display: none;
@ -502,119 +505,16 @@
max-height: 230px; max-height: 230px;
} }
.ui-widget { .pika-single {
table { position: relative!important;
margin: 0; top: 0!important;
} border: 0;
box-shadow: none;
&.ui-datepicker-inline {
padding: 0 10px;
border: 0;
width: 100%;
}
.ui-datepicker-header {
padding: 0 8px 10px;
border: 0;
.ui-icon {
background: none;
font-size: 20px;
text-indent: 0;
&::before {
display: block;
position: relative;
top: -2px;
color: $dropdown-title-btn-color;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
}
.ui-datepicker-calendar {
.ui-state-hover,
.ui-state-active {
color: $white-light;
border: 0;
}
}
.ui-datepicker-prev,
.ui-datepicker-next {
top: 0;
height: 15px;
cursor: pointer;
&:hover {
background-color: transparent;
border: 0;
.ui-icon::before {
color: $md-link-color;
}
}
}
.ui-datepicker-prev {
left: 0;
.ui-icon::before {
content: '\f104';
text-align: left;
}
}
.ui-datepicker-next {
right: 0;
.ui-icon::before {
content: '\f105';
text-align: right;
}
}
td {
padding: 0;
border: 1px solid $calendar-border-color;
&:first-child {
border-left: 0;
}
&:last-child {
border-right: 0;
}
a {
line-height: 17px;
border: 0;
border-radius: 0;
}
}
.ui-datepicker-title {
color: $gl-text-color;
font-size: 14px;
line-height: 1;
font-weight: normal;
}
} }
th { .pika-lendar {
padding: 2px 0; margin-top: -5px;
color: $note-disabled-comment-color; margin-bottom: 0;
font-weight: normal;
text-transform: lowercase;
border-top: 1px solid $calendar-border-color;
}
.ui-datepicker-unselectable {
background-color: $gray-light;
} }
} }

View File

@ -231,3 +231,46 @@ span.idiff {
} }
} }
} }
.file-title-flex-parent {
display: flex;
align-items: center;
justify-content: space-between;
background-color: $gray-light;
border-bottom: 1px solid $border-color;
padding: 5px $gl-padding;
margin: 0;
border-radius: 3px 3px 0 0;
.file-header-content {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 30px;
position: relative;
}
.btn-clipboard {
position: absolute;
right: 0;
}
a {
color: $gl-text-color;
}
small {
margin: 0 10px 0 0;
}
.file-actions {
white-space: nowrap;
.btn {
padding: 0 10px;
font-size: 13px;
line-height: 28px;
display: inline-block;
}
}
}

View File

@ -222,6 +222,10 @@ header {
float: right; float: right;
border-top: none; border-top: none;
@media (min-width: $screen-md-min) {
padding: 0;
}
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
float: none; float: none;
} }
@ -272,7 +276,7 @@ header {
.header-user { .header-user {
.dropdown-menu-nav { .dropdown-menu-nav {
width: 140px; min-width: 140px;
margin-top: -5px; margin-top: -5px;
} }
} }

View File

@ -2,42 +2,6 @@
font-family: $regular_font; font-family: $regular_font;
font-size: $font-size-base; font-size: $font-size-base;
&.ui-datepicker,
&.ui-datepicker-inline {
border: 1px solid $jq-ui-border;
padding: 10px;
width: 270px;
.ui-datepicker-header {
background: $white-light;
border-color: $jq-ui-border;
.ui-datepicker-prev,
.ui-datepicker-next {
top: 4px;
}
.ui-datepicker-prev {
left: 2px;
}
.ui-datepicker-next {
right: 2px;
}
.ui-state-hover {
background: transparent;
border: 0;
cursor: pointer;
}
}
.ui-datepicker-calendar td a {
padding: 5px;
text-align: center;
}
}
&.ui-autocomplete { &.ui-autocomplete {
border-color: $jq-ui-border; border-color: $jq-ui-border;
padding: 0; padding: 0;
@ -59,25 +23,4 @@
border: 0; border: 0;
background: transparent; background: transparent;
} }
.ui-datepicker-calendar {
.ui-state-active,
.ui-state-hover,
.ui-state-focus {
border: 1px solid $gl-primary;
background: $gl-primary;
color: $white-light;
}
}
}
.ui-sortable-handle {
cursor: move;
cursor: -webkit-grab;
cursor: -moz-grab;
&:active {
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
} }

View File

@ -307,3 +307,7 @@ ul.controls {
} }
} }
} }
ul.indent-list {
padding: 10px 0 0 30px;
}

View File

@ -283,10 +283,7 @@
} }
.layout-nav { .layout-nav {
position: fixed;
top: $header-height;
width: 100%; width: 100%;
z-index: 11;
background: $gray-light; background: $gray-light;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
transition: padding $sidebar-transition-duration; transition: padding $sidebar-transition-duration;
@ -419,15 +416,20 @@
} }
.page-with-layout-nav { .page-with-layout-nav {
margin-top: $header-height + 2;
.right-sidebar { .right-sidebar {
top: ($header-height * 2) + 2; top: ($header-height * 2) + 2;
} }
.build-sidebar {
top: ($header-height * 3) + 3;
&.affix {
top: 0;
}
}
} }
.activities { .activities {
.nav-block { .nav-block {
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;

View File

@ -6,8 +6,22 @@
.pagination { .pagination {
padding: 0; padding: 0;
a {
cursor: pointer;
}
.separator,
.separator:hover {
a {
cursor: default;
background-color: $gray-light;
padding: $gl-vert-padding;
}
}
} }
.gap, .gap,
.gap:hover { .gap:hover {
background-color: $gray-light; background-color: $gray-light;

View File

@ -1,5 +1,5 @@
.page-with-sidebar { .page-with-sidebar {
padding: $header-height 0 25px; padding-bottom: 25px;
transition: padding $sidebar-transition-duration; transition: padding $sidebar-transition-duration;
&.page-sidebar-pinned { &.page-sidebar-pinned {
@ -208,7 +208,9 @@ header.header-sidebar-pinned {
padding-right: 0; padding-right: 0;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-right: $sidebar_collapsed_width; .content-wrapper {
padding-right: $sidebar_collapsed_width;
}
.merge-request-tabs-holder.affix { .merge-request-tabs-holder.affix {
right: $sidebar_collapsed_width; right: $sidebar_collapsed_width;
@ -234,7 +236,9 @@ header.header-sidebar-pinned {
} }
@media (min-width: $screen-md-min) { @media (min-width: $screen-md-min) {
padding-right: $gutter_width; .content-wrapper {
padding-right: $gutter_width;
}
&:not(.with-overlay) .merge-request-tabs-holder.affix { &:not(.with-overlay) .merge-request-tabs-holder.affix {
right: $gutter_width; right: $gutter_width;
@ -252,4 +256,9 @@ header.header-sidebar-pinned {
.right-sidebar { .right-sidebar {
border-left: 1px solid $border-color; border-left: 1px solid $border-color;
&.affix {
position: fixed;
top: 0;
}
} }

View File

@ -138,6 +138,13 @@ pre {
margin: 0; margin: 0;
} }
blockquote {
color: $gl-grayish-blue;
padding: 0 0 0 15px;
margin: 0;
border-left: 3px solid $white-dark;
}
span.highlight_word { span.highlight_word {
background-color: $highlighted-highlight-word !important; background-color: $highlighted-highlight-word !important;
} }

View File

@ -298,12 +298,8 @@
.issue-boards-sidebar { .issue-boards-sidebar {
&.right-sidebar { &.right-sidebar {
top: 153px; top: 0;
bottom: 0; bottom: 0;
@media (min-width: $screen-sm-min) {
top: 220px;
}
} }
.issuable-sidebar-header { .issuable-sidebar-header {

View File

@ -159,7 +159,6 @@
.commit-row-description { .commit-row-description {
font-size: 14px; font-size: 14px;
border-left: 1px solid $white-normal;
padding: 10px 15px; padding: 10px 15px;
margin: 10px 0; margin: 10px 0;
background: $gray-light; background: $gray-light;

View File

@ -284,7 +284,11 @@
.events-description { .events-description {
line-height: 65px; line-height: 65px;
padding-left: $gl-padding; padding: 0 $gl-padding;
}
.events-info {
color: $gl-text-color-secondary;
} }
} }

View File

@ -34,9 +34,14 @@
} }
} }
.file-title { .file-title,
.file-title-flex-parent {
cursor: pointer; cursor: pointer;
a:hover {
text-decoration: none;
}
&:hover { &:hover {
background-color: $gray-normal; background-color: $gray-normal;
} }

View File

@ -41,7 +41,6 @@
word-wrap: break-word; word-wrap: break-word;
.md { .md {
color: $gl-grayish-blue;
font-size: $gl-font-size; font-size: $gl-font-size;
.label { .label {

View File

@ -189,11 +189,10 @@
} }
.right-sidebar { .right-sidebar {
position: fixed; position: absolute;
top: $header-height; top: $header-height;
bottom: 0; bottom: 0;
right: 0; right: 0;
z-index: 10;
transition: width .3s; transition: width .3s;
background: $gray-light; background: $gray-light;
padding: 10px 20px; padding: 10px 20px;
@ -461,8 +460,19 @@
.issuable-list { .issuable-list {
li { li {
.issue-box {
display: -webkit-flex;
display: flex;
}
.issue-info-container {
-webkit-flex: 1;
flex: 1;
padding-right: $gl-padding;
}
.issue-check { .issue-check {
float: left;
padding-right: $gl-padding; padding-right: $gl-padding;
margin-bottom: 10px; margin-bottom: 10px;
min-width: 15px; min-width: 15px;

View File

@ -1,6 +1,6 @@
.issues-list { .issues-list {
.issue { .issue {
padding: 10px $gl-padding; padding: 10px 0 10px $gl-padding;
position: relative; position: relative;
.title { .title {
@ -148,3 +148,7 @@ ul.related-merge-requests > li {
border: 1px solid $border-gray-normal; border: 1px solid $border-gray-normal;
} }
} }
.recaptcha {
margin-bottom: 30px;
}

View File

@ -116,6 +116,22 @@
} }
.manage-labels-list { .manage-labels-list {
> li:not(.empty-message) {
background-color: $white-light;
cursor: move;
cursor: -webkit-grab;
cursor: -moz-grab;
&:active {
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
&.sortable-ghost {
opacity: 0.3;
}
}
.btn-action { .btn-action {
color: $gl-text-color; color: $gl-text-color;
@ -259,3 +275,8 @@
} }
} }
} }
.label-link {
display: inline-block;
vertical-align: text-top;
}

View File

@ -80,19 +80,28 @@
.ci_widget { .ci_widget {
border-bottom: 1px solid $well-inner-border; border-bottom: 1px solid $well-inner-border;
color: $gl-text-color; color: $gl-text-color;
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
i,
svg {
margin-right: 8px;
}
svg { svg {
margin-right: 4px;
position: relative; position: relative;
top: 1px; top: 1px;
overflow: visible; overflow: visible;
} }
&.ci-success_with_warnings { & > span {
padding-right: 4px;
}
i { @media (max-width: $screen-xs-max) {
color: $gl-warning; flex-wrap: wrap;
}
} }
} }
@ -102,6 +111,43 @@
padding: $gl-padding; padding: $gl-padding;
} }
.mr-widget-pipeline-graph {
flex-shrink: 0;
.dropdown-menu {
margin-top: 11px;
}
.ci-action-icon-wrapper {
line-height: 16px;
}
@media (min-width: $screen-sm-min) {
.stage-cell {
padding: 0 4px;
}
}
@media (max-width: $screen-xs-max) {
order: 1;
margin-top: $gl-padding-top;
border-radius: 3px;
background-color: $white-light;
border: 1px solid $gray-darker;
width: 100%;
text-align: center;
.dropdown-menu {
margin-left: -97.5px;
}
.arrow-up::before,
.arrow-up::after, {
margin-left: 97.5px;
}
}
}
.normal { .normal {
color: $gl-text-color; color: $gl-text-color;
} }
@ -223,8 +269,15 @@
.mr-list { .mr-list {
.merge-request { .merge-request {
padding: 10px 15px; padding: 10px 0 10px 15px;
position: relative; position: relative;
display: -webkit-flex;
display: flex;
.issue-info-container {
-webkit-flex: 1;
flex: 1;
}
.merge-request-title { .merge-request-title {
margin-bottom: 2px; margin-bottom: 2px;
@ -430,7 +483,7 @@
background-color: $white-light; background-color: $white-light;
&.affix { &.affix {
top: 100px; top: 0;
left: 0; left: 0;
z-index: 10; z-index: 10;
transition: right .15s; transition: right .15s;

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