Commit 2a5f9709 authored by Filipa Lacerda's avatar Filipa Lacerda

[ci skip] Merge branch 'master' into 42568-pipeline-empty-state

* master: (156 commits)
  Make issue boards height calculation clearer and remove magic numbers
  Rename `package-qa` in docs
  Copyedit CI services docs
  Fix dropzone project show
  Prettify notes.
  Fix "A copy of Gitlab::Metrics::Methods" have been removed error
  Add Jupyter Notebook docs
  Fixed profile notifications not being editable
  Rename manual job to `package-and-qa`
  Update vendored gitlab-ci.yml template for auto-devops
  Docs: refactor doc general guidelines and style guidelines
  Use Gitaly 0.91.0
  Add version available info to integrity check rake task docs
  Adds the option to override project description on export via API and fixes the project description not being imported
  Extract constant for LfsPointerFile::VERSION_LINE
  Setup Faraday middleware before adapter
  Revert "Merge branch 'update-httparty' into 'master'"
  Update Code Quality example documentation
  Fix timeouts loading /admin/projects page
  OpenShift install docs: Recommend "add-scc-to-user" over "edit scc"
  ...
parents 024a0b83 e1739e47
...@@ -35,8 +35,14 @@ ...@@ -35,8 +35,14 @@
"import/no-commonjs": "error", "import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }], "no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error", "promise/catch-or-return": "error",
"no-underscore-dangle": ["error", { "allow": ["__", "_links"]}], "no-underscore-dangle": ["error", { "allow": ["__", "_links"] }],
"vue/html-self-closing": ["error", { "no-mixed-operators": 0,
"space-before-function-paren": 0,
"curly": 0,
"arrow-parens": 0,
"vue/html-self-closing": [
"error",
{
"html": { "html": {
"void": "always", "void": "always",
"normal": "never", "normal": "never",
...@@ -44,6 +50,7 @@ ...@@ -44,6 +50,7 @@
}, },
"svg": "always", "svg": "always",
"math": "always" "math": "always"
}] }
]
} }
} }
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
eslint-report.html eslint-report.html
/.gitlab_shell_secret /.gitlab_shell_secret
.idea .idea
/.vscode/*
/.rbenv-version /.rbenv-version
.rbx/ .rbx/
/.ruby-gemset /.ruby-gemset
......
...@@ -257,7 +257,7 @@ stages: ...@@ -257,7 +257,7 @@ stages:
## ##
# Trigger a package build in omnibus-gitlab repository # Trigger a package build in omnibus-gitlab repository
# #
package-qa: package-and-qa:
<<: *dedicated-runner <<: *dedicated-runner
image: ruby:2.4-alpine image: ruby:2.4-alpine
before_script: [] before_script: []
......
{
"singleQuote": true,
"trailingComma": "all"
}
...@@ -2,6 +2,20 @@ ...@@ -2,6 +2,20 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 10.5.5 (2018-03-15)
### Fixed (3 changes)
- Fix missing uploads after group transfer. !17658
- Fix code and wiki search results when filename is non-ASCII.
- Remove double caching of Repository#empty?.
### Performance (2 changes)
- Adding missing indexes on taggings table.
- Add index on section_name_id on ci_build_trace_sections table.
## 10.5.4 (2018-03-08) ## 10.5.4 (2018-03-08)
### Fixed (11 changes) ### Fixed (11 changes)
......
...@@ -34,7 +34,7 @@ gem 'omniauth-gitlab', '~> 1.0.2' ...@@ -34,7 +34,7 @@ gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.5.2' gem 'omniauth-google-oauth2', '~> 0.5.2'
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-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.7.0' gem 'omniauth-saml', '~> 1.10.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'
gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth_crowd', '~> 2.2.0'
...@@ -104,7 +104,7 @@ gem 'carrierwave', '~> 1.2' ...@@ -104,7 +104,7 @@ gem 'carrierwave', '~> 1.2'
gem 'dropzonejs-rails', '~> 0.7.1' gem 'dropzonejs-rails', '~> 0.7.1'
# for backups # for backups
gem 'fog-aws', '~> 1.4' gem 'fog-aws', '~> 2.0'
gem 'fog-core', '~> 1.44' gem 'fog-core', '~> 1.44'
gem 'fog-google', '~> 0.5' gem 'fog-google', '~> 0.5'
gem 'fog-local', '~> 0.3' gem 'fog-local', '~> 0.3'
...@@ -152,7 +152,7 @@ end ...@@ -152,7 +152,7 @@ end
gem 'state_machines-activerecord', '~> 0.4.0' gem 'state_machines-activerecord', '~> 0.4.0'
# Issue tags # Issue tags
gem 'acts-as-taggable-on', '~> 4.0' gem 'acts-as-taggable-on', '~> 5.0'
# Background jobs # Background jobs
gem 'sidekiq', '~> 5.0' gem 'sidekiq', '~> 5.0'
...@@ -208,7 +208,7 @@ gem 'asana', '~> 0.6.0' ...@@ -208,7 +208,7 @@ gem 'asana', '~> 0.6.0'
gem 'ruby-fogbugz', '~> 0.2.1' gem 'ruby-fogbugz', '~> 0.2.1'
# Kubernetes integration # Kubernetes integration
gem 'kubeclient', '~> 2.2.0' gem 'kubeclient', '~> 3.0'
# d3 # d3
gem 'd3_rails', '~> 3.5.0' gem 'd3_rails', '~> 3.5.0'
...@@ -257,14 +257,13 @@ gem 'font-awesome-rails', '~> 4.7' ...@@ -257,14 +257,13 @@ gem 'font-awesome-rails', '~> 4.7'
gem 'gemojione', '~> 3.3' gem 'gemojione', '~> 3.3'
gem 'gon', '~> 6.1.0' gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.3.1'
gem 'request_store', '~> 1.3' gem 'request_store', '~> 1.3'
gem 'select2-rails', '~> 3.5.9' gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1' gem 'virtus', '~> 1.0.1'
gem 'base32', '~> 0.3.0' gem 'base32', '~> 0.3.0'
# Sentry integration # Sentry integration
gem 'sentry-raven', '~> 2.5.3' gem 'sentry-raven', '~> 2.7'
gem 'premailer-rails', '~> 1.9.7' gem 'premailer-rails', '~> 1.9.7'
...@@ -300,7 +299,7 @@ group :metrics do ...@@ -300,7 +299,7 @@ group :metrics do
end end
group :development do group :development do
gem 'foreman', '~> 0.78.0' gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 3.6.0', require: false gem 'brakeman', '~> 3.6.0', require: false
gem 'letter_opener_web', '~> 1.3.0' gem 'letter_opener_web', '~> 1.3.0'
...@@ -360,7 +359,7 @@ group :development, :test do ...@@ -360,7 +359,7 @@ group :development, :test do
gem 'benchmark-ips', '~> 2.3.0', require: false gem 'benchmark-ips', '~> 2.3.0', require: false
gem 'license_finder', '~> 3.1', require: false gem 'license_finder', '~> 3.1', require: false
gem 'knapsack', '~> 1.11.0' gem 'knapsack', '~> 1.16'
gem 'activerecord_sane_schema_dumper', '0.2' gem 'activerecord_sane_schema_dumper', '0.2'
...@@ -381,14 +380,14 @@ group :test do ...@@ -381,14 +380,14 @@ group :test do
gem 'test-prof', '~> 0.2.5' gem 'test-prof', '~> 0.2.5'
end end
gem 'octokit', '~> 4.6.2' gem 'octokit', '~> 4.8'
gem 'mail_room', '~> 0.9.1' gem 'mail_room', '~> 0.9.1'
gem 'email_reply_trimmer', '~> 0.1' gem 'email_reply_trimmer', '~> 0.1'
gem 'html2text' gem 'html2text'
gem 'ruby-prof', '~> 0.16.2' gem 'ruby-prof', '~> 0.17.0'
# OAuth # OAuth
gem 'oauth2', '~> 1.4' gem 'oauth2', '~> 1.4'
...@@ -401,7 +400,7 @@ gem 'vmstat', '~> 2.3.0' ...@@ -401,7 +400,7 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6' gem 'sys-filesystem', '~> 1.1.6'
# SSH host key support # SSH host key support
gem 'net-ssh', '~> 4.1.0' gem 'net-ssh', '~> 4.2.0'
gem 'sshkey', '~> 1.9.0' gem 'sshkey', '~> 1.9.0'
# Required for ED25519 SSH host key support # Required for ED25519 SSH host key support
...@@ -421,9 +420,9 @@ gem 'google-protobuf', '= 3.5.1' ...@@ -421,9 +420,9 @@ gem 'google-protobuf', '= 3.5.1'
gem 'toml-rb', '~> 1.0.0', require: false gem 'toml-rb', '~> 1.0.0', require: false
# Feature toggles # Feature toggles
gem 'flipper', '~> 0.11.0' gem 'flipper', '~> 0.13.0'
gem 'flipper-active_record', '~> 0.11.0' gem 'flipper-active_record', '~> 0.13.0'
gem 'flipper-active_support_cache_store', '~> 0.11.0' gem 'flipper-active_support_cache_store', '~> 0.13.0'
# Structured logging # Structured logging
gem 'lograge', '~> 0.5' gem 'lograge', '~> 0.5'
......
...@@ -40,8 +40,8 @@ GEM ...@@ -40,8 +40,8 @@ GEM
minitest (~> 5.1) minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4) thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1) tzinfo (~> 1.1)
acts-as-taggable-on (4.0.0) acts-as-taggable-on (5.0.0)
activerecord (>= 4.0) activerecord (>= 4.2.8)
adamantium (0.2.0) adamantium (0.2.0)
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
memoizable (~> 0.4.0) memoizable (~> 0.4.0)
...@@ -174,7 +174,7 @@ GEM ...@@ -174,7 +174,7 @@ GEM
diff-lcs (1.3) diff-lcs (1.3)
diffy (3.1.0) diffy (3.1.0)
docile (1.1.5) docile (1.1.5)
domain_name (0.5.20161021) domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.2.6) doorkeeper (4.2.6)
railties (>= 4.2) railties (>= 4.2)
...@@ -194,7 +194,7 @@ GEM ...@@ -194,7 +194,7 @@ GEM
et-orbi (1.0.3) et-orbi (1.0.3)
tzinfo tzinfo
eventmachine (1.0.8) eventmachine (1.0.8)
excon (0.57.1) excon (0.60.0)
execjs (2.6.0) execjs (2.6.0)
expression_parser (0.9.0) expression_parser (0.9.0)
factory_bot (4.8.2) factory_bot (4.8.2)
...@@ -218,13 +218,13 @@ GEM ...@@ -218,13 +218,13 @@ GEM
path_expander (~> 1.0) path_expander (~> 1.0)
ruby_parser (~> 3.0) ruby_parser (~> 3.0)
sexp_processor (~> 4.0) sexp_processor (~> 4.0)
flipper (0.11.0) flipper (0.13.0)
flipper-active_record (0.11.0) flipper-active_record (0.13.0)
activerecord (>= 3.2, < 6) activerecord (>= 3.2, < 6)
flipper (~> 0.11.0) flipper (~> 0.13.0)
flipper-active_support_cache_store (0.11.0) flipper-active_support_cache_store (0.13.0)
activesupport (>= 3.2, < 6) activesupport (>= 3.2, < 6)
flipper (~> 0.11.0) flipper (~> 0.13.0)
flowdock (0.7.1) flowdock (0.7.1)
httparty (~> 0.7) httparty (~> 0.7)
multi_json multi_json
...@@ -233,14 +233,14 @@ GEM ...@@ -233,14 +233,14 @@ GEM
fog-json (~> 1.0) fog-json (~> 1.0)
ipaddress (~> 0.8) ipaddress (~> 0.8)
xml-simple (~> 1.1) xml-simple (~> 1.1)
fog-aws (1.4.0) fog-aws (2.0.1)
fog-core (~> 1.38) fog-core (~> 1.38)
fog-json (~> 1.0) fog-json (~> 1.0)
fog-xml (~> 0.1) fog-xml (~> 0.1)
ipaddress (~> 0.8) ipaddress (~> 0.8)
fog-core (1.44.3) fog-core (1.45.0)
builder builder
excon (~> 0.49) excon (~> 0.58)
formatador (~> 0.2) formatador (~> 0.2)
fog-google (0.5.3) fog-google (0.5.3)
fog-core fog-core
...@@ -265,7 +265,7 @@ GEM ...@@ -265,7 +265,7 @@ GEM
nokogiri (>= 1.5.11, < 2.0.0) nokogiri (>= 1.5.11, < 2.0.0)
font-awesome-rails (4.7.0.1) font-awesome-rails (4.7.0.1)
railties (>= 3.2, < 5.1) railties (>= 3.2, < 5.1)
foreman (0.78.0) foreman (0.84.0)
thor (~> 0.19.1) thor (~> 0.19.1)
formatador (0.2.5) formatador (0.2.5)
fuubar (2.2.0) fuubar (2.2.0)
...@@ -388,7 +388,7 @@ GEM ...@@ -388,7 +388,7 @@ GEM
thor thor
tilt tilt
hashdiff (0.3.4) hashdiff (0.3.4)
hashie (3.5.6) hashie (3.5.7)
hashie-forbidden_attributes (0.1.1) hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0) hashie (>= 3.0)
health_check (2.6.0) health_check (2.6.0)
...@@ -402,20 +402,20 @@ GEM ...@@ -402,20 +402,20 @@ GEM
html2text (0.2.0) html2text (0.2.0)
nokogiri (~> 1.6) nokogiri (~> 1.6)
htmlentities (4.3.4) htmlentities (4.3.4)
http (0.9.8) http (2.2.2)
addressable (~> 2.3) addressable (~> 2.3)
http-cookie (~> 1.0) http-cookie (~> 1.0)
http-form_data (~> 1.0.1) http-form_data (~> 1.0.1)
http_parser.rb (~> 0.6.0) http_parser.rb (~> 0.6.0)
http-cookie (1.0.3) http-cookie (1.0.3)
domain_name (~> 0.5) domain_name (~> 0.5)
http-form_data (1.0.1) http-form_data (1.0.3)
http_parser.rb (0.6.0) http_parser.rb (0.6.0)
httparty (0.13.7) httparty (0.13.7)
json (~> 1.8) json (~> 1.8)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpclient (2.8.2) httpclient (2.8.2)
i18n (0.9.1) i18n (0.9.5)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
ice_nine (0.11.2) ice_nine (0.11.2)
influxdb (0.2.3) influxdb (0.2.3)
...@@ -427,10 +427,6 @@ GEM ...@@ -427,10 +427,6 @@ GEM
multipart-post multipart-post
oauth (~> 0.5, >= 0.5.0) oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2) jquery-atwho-rails (1.3.2)
jquery-rails (4.3.1)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (1.8.6) json (1.8.6)
json-jwt (1.7.2) json-jwt (1.7.2)
activesupport activesupport
...@@ -454,13 +450,13 @@ GEM ...@@ -454,13 +450,13 @@ GEM
kaminari-core (= 1.0.1) kaminari-core (= 1.0.1)
kaminari-core (1.0.1) kaminari-core (1.0.1)
kgio (2.10.0) kgio (2.10.0)
knapsack (1.11.0) knapsack (1.16.0)
rake rake
timecop (>= 0.1.0) timecop (>= 0.1.0)
kubeclient (2.2.0) kubeclient (3.0.0)
http (= 0.9.8) http (~> 2.2.2)
recursive-open-struct (= 1.0.0) recursive-open-struct (~> 1.0.4)
rest-client rest-client (~> 2.0)
launchy (2.4.3) launchy (2.4.3)
addressable (~> 2.3) addressable (~> 2.3)
letter_opener (1.4.1) letter_opener (1.4.1)
...@@ -513,7 +509,7 @@ GEM ...@@ -513,7 +509,7 @@ GEM
mustermann (~> 1.0.0) mustermann (~> 1.0.0)
mysql2 (0.4.10) mysql2 (0.4.10)
net-ldap (0.16.0) net-ldap (0.16.0)
net-ssh (4.1.0) net-ssh (4.2.0)
netrc (0.11.0) netrc (0.11.0)
nokogiri (1.8.2) nokogiri (1.8.2)
mini_portile2 (~> 2.3.0) mini_portile2 (~> 2.3.0)
...@@ -525,12 +521,12 @@ GEM ...@@ -525,12 +521,12 @@ GEM
multi_json (~> 1.3) multi_json (~> 1.3)
multi_xml (~> 0.5) multi_xml (~> 0.5)
rack (>= 1.2, < 3) rack (>= 1.2, < 3)
octokit (4.6.2) octokit (4.8.0)
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.5) oj (2.17.5)
omniauth (1.4.2) omniauth (1.4.3)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (>= 1.0, < 3) rack (>= 1.6.2, < 3)
omniauth-auth0 (1.4.1) omniauth-auth0 (1.4.1)
omniauth-oauth2 (~> 1.1) omniauth-oauth2 (~> 1.1)
omniauth-authentiq (0.3.1) omniauth-authentiq (0.3.1)
...@@ -569,9 +565,9 @@ GEM ...@@ -569,9 +565,9 @@ GEM
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-oauth2-generic (0.2.2) omniauth-oauth2-generic (0.2.2)
omniauth-oauth2 (~> 1.0) omniauth-oauth2 (~> 1.0)
omniauth-saml (1.7.0) omniauth-saml (1.10.0)
omniauth (~> 1.3) omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.4) ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1) omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0) omniauth (>= 1.0.0)
omniauth-twitter (1.2.1) omniauth-twitter (1.2.1)
...@@ -650,7 +646,7 @@ GEM ...@@ -650,7 +646,7 @@ GEM
pry (>= 0.9.10) pry (>= 0.9.10)
public_suffix (3.0.2) public_suffix (3.0.2)
pyu-ruby-sasl (0.0.3.3) pyu-ruby-sasl (0.0.3.3)
rack (1.6.8) rack (1.6.9)
rack-accept (0.4.5) rack-accept (0.4.5)
rack (>= 0.4) rack (>= 0.4)
rack-attack (4.4.1) rack-attack (4.4.1)
...@@ -713,7 +709,7 @@ GEM ...@@ -713,7 +709,7 @@ GEM
re2 (1.1.1) re2 (1.1.1)
recaptcha (3.0.0) recaptcha (3.0.0)
json json
recursive-open-struct (1.0.0) recursive-open-struct (1.0.5)
redcarpet (3.4.0) redcarpet (3.4.0)
redis (3.3.5) redis (3.3.5)
redis-actionpack (5.0.2) redis-actionpack (5.0.2)
...@@ -741,7 +737,7 @@ GEM ...@@ -741,7 +737,7 @@ GEM
request_store (1.3.1) request_store (1.3.1)
responders (2.3.0) responders (2.3.0)
railties (>= 4.2.0, < 5.1) railties (>= 4.2.0, < 5.1)
rest-client (2.0.0) rest-client (2.0.2)
http-cookie (>= 1.0.2, < 2.0) http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0) mime-types (>= 1.16, < 4.0)
netrc (~> 0.8) netrc (~> 0.8)
...@@ -803,9 +799,9 @@ GEM ...@@ -803,9 +799,9 @@ GEM
i18n i18n
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-prof (0.16.2) ruby-prof (0.17.0)
ruby-progressbar (1.9.0) ruby-progressbar (1.9.0)
ruby-saml (1.4.1) ruby-saml (1.7.2)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
ruby_parser (3.9.0) ruby_parser (3.9.0)
sexp_processor (~> 4.1) sexp_processor (~> 4.1)
...@@ -844,7 +840,7 @@ GEM ...@@ -844,7 +840,7 @@ GEM
selenium-webdriver (3.5.0) selenium-webdriver (3.5.0)
childprocess (~> 0.5) childprocess (~> 0.5)
rubyzip (~> 1.0) rubyzip (~> 1.0)
sentry-raven (2.5.3) sentry-raven (2.7.2)
faraday (>= 0.7.6, < 1.0) faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.9.0) sexp_processor (4.9.0)
...@@ -933,7 +929,7 @@ GEM ...@@ -933,7 +929,7 @@ GEM
truncato (0.7.10) truncato (0.7.10)
htmlentities (~> 4.3.1) htmlentities (~> 4.3.1)
nokogiri (~> 1.8.0, >= 1.7.0) nokogiri (~> 1.8.0, >= 1.7.0)
tzinfo (1.2.4) tzinfo (1.2.5)
thread_safe (~> 0.1) thread_safe (~> 0.1)
u2f (0.2.1) u2f (0.2.1)
uber (0.1.0) uber (0.1.0)
...@@ -942,7 +938,7 @@ GEM ...@@ -942,7 +938,7 @@ GEM
json (>= 1.8.0) json (>= 1.8.0)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.4) unf_ext (0.0.7.5)
unicode-display_width (1.3.0) unicode-display_width (1.3.0)
unicorn (5.1.0) unicorn (5.1.0)
kgio (~> 2.6) kgio (~> 2.6)
...@@ -994,7 +990,7 @@ DEPENDENCIES ...@@ -994,7 +990,7 @@ DEPENDENCIES
RedCloth (~> 4.3.2) RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0) ace-rails-ap (~> 4.1.0)
activerecord_sane_schema_dumper (= 0.2) activerecord_sane_schema_dumper (= 0.2)
acts-as-taggable-on (~> 4.0) acts-as-taggable-on (~> 5.0)
addressable (~> 2.5.2) addressable (~> 2.5.2)
akismet (~> 2.0) akismet (~> 2.0)
allocations (~> 1.0) allocations (~> 1.0)
...@@ -1044,18 +1040,18 @@ DEPENDENCIES ...@@ -1044,18 +1040,18 @@ DEPENDENCIES
fast_blank fast_blank
ffaker (~> 2.4) ffaker (~> 2.4)
flay (~> 2.10.0) flay (~> 2.10.0)
flipper (~> 0.11.0) flipper (~> 0.13.0)
flipper-active_record (~> 0.11.0) flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.11.0) flipper-active_support_cache_store (~> 0.13.0)
fog-aliyun (~> 0.2.0) fog-aliyun (~> 0.2.0)
fog-aws (~> 1.4) fog-aws (~> 2.0)
fog-core (~> 1.44) fog-core (~> 1.44)
fog-google (~> 0.5) fog-google (~> 0.5)
fog-local (~> 0.3) fog-local (~> 0.3)
fog-openstack (~> 0.1) fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1) fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7) font-awesome-rails (~> 4.7)
foreman (~> 0.78.0) foreman (~> 0.84.0)
fuubar (~> 2.2.0) fuubar (~> 2.2.0)
gemnasium-gitlab-service (~> 0.2) gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.3) gemojione (~> 3.3)
...@@ -1090,12 +1086,11 @@ DEPENDENCIES ...@@ -1090,12 +1086,11 @@ DEPENDENCIES
influxdb (~> 0.2) influxdb (~> 0.2)
jira-ruby (~> 1.4) jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2) jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 4.3.1)
json-schema (~> 2.8.0) json-schema (~> 2.8.0)
jwt (~> 1.5.6) jwt (~> 1.5.6)
kaminari (~> 1.0) kaminari (~> 1.0)
knapsack (~> 1.11.0) knapsack (~> 1.16)
kubeclient (~> 2.2.0) kubeclient (~> 3.0)
letter_opener_web (~> 1.3.0) letter_opener_web (~> 1.3.0)
license_finder (~> 3.1) license_finder (~> 3.1)
licensee (~> 8.7.0) licensee (~> 8.7.0)
...@@ -1107,10 +1102,10 @@ DEPENDENCIES ...@@ -1107,10 +1102,10 @@ DEPENDENCIES
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10) mysql2 (~> 0.4.10)
net-ldap net-ldap
net-ssh (~> 4.1.0) net-ssh (~> 4.2.0)
nokogiri (~> 1.8.2) nokogiri (~> 1.8.2)
oauth2 (~> 1.4) oauth2 (~> 1.4)
octokit (~> 4.6.2) octokit (~> 4.8)
oj (~> 2.17.4) oj (~> 2.17.4)
omniauth (~> 1.4.2) omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1) omniauth-auth0 (~> 1.4.1)
...@@ -1123,7 +1118,7 @@ DEPENDENCIES ...@@ -1123,7 +1118,7 @@ DEPENDENCIES
omniauth-google-oauth2 (~> 0.5.2) omniauth-google-oauth2 (~> 0.5.2)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2) omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.7.0) omniauth-saml (~> 1.10.0)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
...@@ -1173,7 +1168,7 @@ DEPENDENCIES ...@@ -1173,7 +1168,7 @@ DEPENDENCIES
rubocop (~> 0.52.1) rubocop (~> 0.52.1)
rubocop-rspec (~> 1.22.1) rubocop-rspec (~> 1.22.1)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2) ruby-prof (~> 0.17.0)
ruby_parser (~> 3.8) ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4) rufus-scheduler (~> 3.4)
rugged (~> 0.26.0) rugged (~> 0.26.0)
...@@ -1183,7 +1178,7 @@ DEPENDENCIES ...@@ -1183,7 +1178,7 @@ DEPENDENCIES
seed-fu (~> 2.3.7) seed-fu (~> 2.3.7)
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5) selenium-webdriver (~> 3.5)
sentry-raven (~> 2.5.3) sentry-raven (~> 2.7)
settingslogic (~> 2.0.9) settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6) sham_rack (~> 1.3.6)
shoulda-matchers (~> 3.1.2) shoulda-matchers (~> 3.1.2)
......
...@@ -132,9 +132,8 @@ class GfmAutoComplete { ...@@ -132,9 +132,8 @@ class GfmAutoComplete {
callbacks: { callbacks: {
...this.getDefaultCallbacks(), ...this.getDefaultCallbacks(),
matcher(flag, subtext) { matcher(flag, subtext) {
const relevantText = subtext.trim().split(/\s/).pop();
const regexp = new RegExp(`(?:[^${glRegexp.unicodeLetters}0-9:]|\n|^):([^:]*)$`, 'gi'); const regexp = new RegExp(`(?:[^${glRegexp.unicodeLetters}0-9:]|\n|^):([^:]*)$`, 'gi');
const match = regexp.exec(relevantText); const match = regexp.exec(subtext);
return match && match.length ? match[1] : null; return match && match.length ? match[1] : null;
}, },
......
...@@ -73,6 +73,7 @@ export default class MergeRequestTabs { ...@@ -73,6 +73,7 @@ export default class MergeRequestTabs {
constructor({ action, setUrl, stubLocation } = {}) { constructor({ action, setUrl, stubLocation } = {}) {
const mergeRequestTabs = document.querySelector('.js-tabs-affix'); const mergeRequestTabs = document.querySelector('.js-tabs-affix');
const navbar = document.querySelector('.navbar-gitlab'); const navbar = document.querySelector('.navbar-gitlab');
const peek = document.getElementById('peek');
const paddingTop = 16; const paddingTop = 16;
this.diffsLoaded = false; this.diffsLoaded = false;
...@@ -86,6 +87,10 @@ export default class MergeRequestTabs { ...@@ -86,6 +87,10 @@ export default class MergeRequestTabs {
this.showTab = this.showTab.bind(this); this.showTab = this.showTab.bind(this);
this.stickyTop = navbar ? navbar.offsetHeight - paddingTop : 0; this.stickyTop = navbar ? navbar.offsetHeight - paddingTop : 0;
if (peek) {
this.stickyTop += peek.offsetHeight;
}
if (mergeRequestTabs) { if (mergeRequestTabs) {
this.stickyTop += mergeRequestTabs.offsetHeight; this.stickyTop += mergeRequestTabs.offsetHeight;
} }
......
...@@ -209,6 +209,7 @@ ...@@ -209,6 +209,7 @@
const xAxis = d3.axisBottom() const xAxis = d3.axisBottom()
.scale(axisXScale) .scale(axisXScale)
.ticks(this.graphWidth / 120)
.tickFormat(timeScaleFormat); .tickFormat(timeScaleFormat);
const yAxis = d3.axisLeft() const yAxis = d3.axisLeft()
......
...@@ -4,13 +4,15 @@ import discussionCounter from '../notes/components/discussion_counter.vue'; ...@@ -4,13 +4,15 @@ import discussionCounter from '../notes/components/discussion_counter.vue';
import store from '../notes/stores'; import store from '../notes/stores';
export default function initMrNotes() { export default function initMrNotes() {
new Vue({ // eslint-disable-line // eslint-disable-next-line no-new
new Vue({
el: '#js-vue-mr-discussions', el: '#js-vue-mr-discussions',
components: { components: {
notesApp, notesApp,
}, },
data() { data() {
const notesDataset = document.getElementById('js-vue-mr-discussions').dataset; const notesDataset = document.getElementById('js-vue-mr-discussions')
.dataset;
return { return {
noteableData: JSON.parse(notesDataset.noteableData), noteableData: JSON.parse(notesDataset.noteableData),
currentUserData: JSON.parse(notesDataset.currentUserData), currentUserData: JSON.parse(notesDataset.currentUserData),
...@@ -28,7 +30,8 @@ export default function initMrNotes() { ...@@ -28,7 +30,8 @@ export default function initMrNotes() {
}, },
}); });
new Vue({ // eslint-disable-line // eslint-disable-next-line no-new
new Vue({
el: '#js-vue-discussion-counter', el: '#js-vue-discussion-counter',
components: { components: {
discussionCounter, discussionCounter,
......
This diff is collapsed.
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import _ from 'underscore'; import _ from 'underscore';
import Autosize from 'autosize'; import Autosize from 'autosize';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import Flash from '../../flash'; import Flash from '../../flash';
import Autosave from '../../autosave'; import Autosave from '../../autosave';
import TaskList from '../../task_list'; import TaskList from '../../task_list';
import { capitalizeFirstCharacter, convertToCamelCase } from '../../lib/utils/text_utility'; import {
import * as constants from '../constants'; capitalizeFirstCharacter,
import eventHub from '../event_hub'; convertToCamelCase,
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue'; } from '../../lib/utils/text_utility';
import markdownField from '../../vue_shared/components/markdown/field.vue'; import * as constants from '../constants';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import eventHub from '../event_hub';
import loadingButton from '../../vue_shared/components/loading_button.vue'; import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue'; import markdownField from '../../vue_shared/components/markdown/field.vue';
import discussionLockedWidget from './discussion_locked_widget.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import issuableStateMixin from '../mixins/issuable_state'; import loadingButton from '../../vue_shared/components/loading_button.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import discussionLockedWidget from './discussion_locked_widget.vue';
import issuableStateMixin from '../mixins/issuable_state';
export default { export default {
name: 'CommentForm', name: 'CommentForm',
components: { components: {
issueWarning, issueWarning,
...@@ -28,9 +31,7 @@ ...@@ -28,9 +31,7 @@
userAvatarLink, userAvatarLink,
loadingButton, loadingButton,
}, },
mixins: [ mixins: [issuableStateMixin],
issuableStateMixin,
],
props: { props: {
noteableType: { noteableType: {
type: String, type: String,
...@@ -53,6 +54,7 @@ ...@@ -53,6 +54,7 @@
'getNotesData', 'getNotesData',
'openState', 'openState',
]), ]),
...mapState(['isToggleStateButtonLoading']),
noteableDisplayName() { noteableDisplayName() {
return this.noteableType.replace(/_/g, ' '); return this.noteableType.replace(/_/g, ' ');
}, },
...@@ -60,10 +62,15 @@ ...@@ -60,10 +62,15 @@
return this.getUserData.id; return this.getUserData.id;
}, },
commentButtonTitle() { commentButtonTitle() {
return this.noteType === constants.COMMENT ? 'Comment' : 'Start discussion'; return this.noteType === constants.COMMENT
? 'Comment'
: 'Start discussion';
}, },
isOpen() { isOpen() {
return this.openState === constants.OPENED || this.openState === constants.REOPENED; return (
this.openState === constants.OPENED ||
this.openState === constants.REOPENED
);
}, },
canCreateNote() { canCreateNote() {
return this.getNoteableData.current_user.can_create_note; return this.getNoteableData.current_user.can_create_note;
...@@ -72,23 +79,17 @@ ...@@ -72,23 +79,17 @@
const openOrClose = this.isOpen ? 'close' : 'reopen'; const openOrClose = this.isOpen ? 'close' : 'reopen';
if (this.note.length) { if (this.note.length) {
return sprintf( return sprintf(__('%{actionText} & %{openOrClose} %{noteable}'), {
__('%{actionText} & %{openOrClose} %{noteable}'),
{
actionText: this.commentButtonTitle, actionText: this.commentButtonTitle,
openOrClose, openOrClose,
noteable: this.noteableDisplayName, noteable: this.noteableDisplayName,
}, });
);
} }
return sprintf( return sprintf(__('%{openOrClose} %{noteable}'), {
__('%{openOrClose} %{noteable}'),
{
openOrClose: capitalizeFirstCharacter(openOrClose), openOrClose: capitalizeFirstCharacter(openOrClose),
noteable: this.noteableDisplayName, noteable: this.noteableDisplayName,
}, });
);
}, },
actionButtonClassNames() { actionButtonClassNames() {
return { return {
...@@ -128,7 +129,9 @@ ...@@ -128,7 +129,9 @@
mounted() { mounted() {
// jQuery is needed here because it is a custom event being dispatched with jQuery. // jQuery is needed here because it is a custom event being dispatched with jQuery.
$(document).on('issuable:change', (e, isClosed) => { $(document).on('issuable:change', (e, isClosed) => {
this.toggleIssueLocalState(isClosed ? constants.CLOSED : constants.REOPENED); this.toggleIssueLocalState(
isClosed ? constants.CLOSED : constants.REOPENED,
);
}); });
this.initAutoSave(); this.initAutoSave();
...@@ -143,6 +146,7 @@ ...@@ -143,6 +146,7 @@
'closeIssue', 'closeIssue',
'reopenIssue', 'reopenIssue',
'toggleIssueLocalState', 'toggleIssueLocalState',
'toggleStateButtonLoading',
]), ]),
setIsSubmitButtonDisabled(note, isSubmitting) { setIsSubmitButtonDisabled(note, isSubmitting) {
if (!_.isEmpty(note) && !isSubmitting) { if (!_.isEmpty(note) && !isSubmitting) {
...@@ -170,13 +174,14 @@ ...@@ -170,13 +174,14 @@
if (this.noteType === constants.DISCUSSION) { if (this.noteType === constants.DISCUSSION) {
noteData.data.note.type = constants.DISCUSSION_NOTE; noteData.data.note.type = constants.DISCUSSION_NOTE;
} }
this.note = ''; // Empty textarea while being requested. Repopulate in catch this.note = ''; // Empty textarea while being requested. Repopulate in catch
this.resizeTextarea(); this.resizeTextarea();
this.stopPolling(); this.stopPolling();
this.saveNote(noteData) this.saveNote(noteData)
.then((res) => { .then(res => {
this.isSubmitting = false; this.enableButton();
this.restartPolling(); this.restartPolling();
if (res.errors) { if (res.errors) {
...@@ -198,10 +203,9 @@ ...@@ -198,10 +203,9 @@
} }
}) })
.catch(() => { .catch(() => {
this.isSubmitting = false; this.enableButton();
this.discard(false); this.discard(false);
const msg = const msg = `Your comment could not be submitted!
`Your comment could not be submitted!
Please check your network connection and try again.`; Please check your network connection and try again.`;
Flash(msg, 'alert', this.$el); Flash(msg, 'alert', this.$el);
this.note = noteData.data.note.note; // Restore textarea content. this.note = noteData.data.note.note; // Restore textarea content.
...@@ -220,9 +224,12 @@ Please check your network connection and try again.`; ...@@ -220,9 +224,12 @@ Please check your network connection and try again.`;
.then(() => this.enableButton()) .then(() => this.enableButton())
.catch(() => { .catch(() => {
this.enableButton(); this.enableButton();
this.toggleStateButtonLoading(false);
Flash( Flash(
sprintf( sprintf(
__('Something went wrong while closing the %{issuable}. Please try again later'), __(
'Something went wrong while closing the %{issuable}. Please try again later',
),
{ issuable: this.noteableDisplayName }, { issuable: this.noteableDisplayName },
), ),
); );
...@@ -232,9 +239,12 @@ Please check your network connection and try again.`; ...@@ -232,9 +239,12 @@ Please check your network connection and try again.`;
.then(() => this.enableButton()) .then(() => this.enableButton())
.catch(() => { .catch(() => {
this.enableButton(); this.enableButton();
this.toggleStateButtonLoading(false);
Flash( Flash(
sprintf( sprintf(
__('Something went wrong while reopening the %{issuable}. Please try again later'), __(
'Something went wrong while reopening the %{issuable}. Please try again later',
),
{ issuable: this.noteableDisplayName }, { issuable: this.noteableDisplayName },
), ),
); );
...@@ -271,12 +281,15 @@ Please check your network connection and try again.`; ...@@ -271,12 +281,15 @@ Please check your network connection and try again.`;
}, },
initAutoSave() { initAutoSave() {
if (this.isLoggedIn) { if (this.isLoggedIn) {
const noteableType = capitalizeFirstCharacter(convertToCamelCase(this.noteableType)); const noteableType = capitalizeFirstCharacter(
convertToCamelCase(this.noteableType),
this.autosave = new Autosave(
$(this.$refs.textarea),
['Note', noteableType, this.getNoteableData.id],
); );
this.autosave = new Autosave($(this.$refs.textarea), [
'Note',
noteableType,
this.getNoteableData.id,
]);
} }
}, },
initTaskList() { initTaskList() {
...@@ -292,7 +305,7 @@ Please check your network connection and try again.`; ...@@ -292,7 +305,7 @@ Please check your network connection and try again.`;
}); });
}, },
}, },
}; };
</script> </script>
<template> <template>
...@@ -419,13 +432,13 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown" ...@@ -419,13 +432,13 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
<loading-button <loading-button
v-if="canUpdateIssue" v-if="canUpdateIssue"
:loading="isSubmitting" :loading="isToggleStateButtonLoading"
@click="handleSave(true)" @click="handleSave(true)"
:container-class="[ :container-class="[
actionButtonClassNames, actionButtonClassNames,
'btn btn-comment btn-comment-and-close js-action-button' 'btn btn-comment btn-comment-and-close js-action-button'
]" ]"
:disabled="isSubmitting" :disabled="isToggleStateButtonLoading || isSubmitting"
:label="issueActionButtonTitle" :label="issueActionButtonTitle"
/> />
......
<script> <script>
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
export default { export default {
components: { components: {
ClipboardButton, ClipboardButton,
Icon, Icon,
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
return this.diffFile.discussionPath ? 'a' : 'span'; return this.diffFile.discussionPath ? 'a' : 'span';
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import syntaxHighlight from '~/syntax_highlight'; import syntaxHighlight from '~/syntax_highlight';
import imageDiffHelper from '~/image_diff/helpers/index'; import imageDiffHelper from '~/image_diff/helpers/index';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import DiffFileHeader from './diff_file_header.vue'; import DiffFileHeader from './diff_file_header.vue';
export default { export default {
components: { components: {
DiffFileHeader, DiffFileHeader,
}, },
...@@ -37,7 +37,11 @@ ...@@ -37,7 +37,11 @@
if (this.isImageDiff) { if (this.isImageDiff) {
const canCreateNote = false; const canCreateNote = false;
const renderCommentBadge = true; const renderCommentBadge = true;
imageDiffHelper.initImageDiff(this.$refs.fileHolder, canCreateNote, renderCommentBadge); imageDiffHelper.initImageDiff(
this.$refs.fileHolder,
canCreateNote,
renderCommentBadge,
);
} else { } else {
const fileHolder = $(this.$refs.fileHolder); const fileHolder = $(this.$refs.fileHolder);
this.$nextTick(() => { this.$nextTick(() => {
...@@ -50,7 +54,7 @@ ...@@ -50,7 +54,7 @@
return html.outerHTML ? 'tr' : 'template'; return html.outerHTML ? 'tr' : 'template';
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import resolveSvg from 'icons/_icon_resolve_discussion.svg'; import resolveSvg from 'icons/_icon_resolve_discussion.svg';
import resolvedSvg from 'icons/_icon_status_success_solid.svg'; import resolvedSvg from 'icons/_icon_status_success_solid.svg';
import mrIssueSvg from 'icons/_icon_mr_issue.svg'; import mrIssueSvg from 'icons/_icon_mr_issue.svg';
import nextDiscussionSvg from 'icons/_next_discussion.svg'; import nextDiscussionSvg from 'icons/_next_discussion.svg';
import { pluralize } from '../../lib/utils/text_utility'; import { pluralize } from '../../lib/utils/text_utility';
import { scrollToElement } from '../../lib/utils/common_utils'; import { scrollToElement } from '../../lib/utils/common_utils';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
directives: { directives: {
tooltip, tooltip,
}, },
...@@ -49,7 +49,9 @@ ...@@ -49,7 +49,9 @@
}, },
methods: { methods: {
jumpToFirstDiscussion() { jumpToFirstDiscussion() {
const el = document.querySelector(`[data-discussion-id="${this.firstUnresolvedDiscussionId}"]`); const el = document.querySelector(
`[data-discussion-id="${this.firstUnresolvedDiscussionId}"]`,
);
const activeTab = window.mrTabs.currentAction; const activeTab = window.mrTabs.currentAction;
if (activeTab === 'commits' || activeTab === 'pipelines') { if (activeTab === 'commits' || activeTab === 'pipelines') {
...@@ -61,7 +63,7 @@ ...@@ -61,7 +63,7 @@
} }
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import Issuable from '~/vue_shared/mixins/issuable'; import Issuable from '~/vue_shared/mixins/issuable';
export default { export default {
components: { components: {
Icon, Icon,
}, },
mixins: [ mixins: [Issuable],
Issuable, };
],
};
</script> </script>
<template> <template>
......
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg'; import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
import emojiSmile from 'icons/_emoji_smile.svg'; import emojiSmile from 'icons/_emoji_smile.svg';
import emojiSmiley from 'icons/_emoji_smiley.svg'; import emojiSmiley from 'icons/_emoji_smiley.svg';
import editSvg from 'icons/_icon_pencil.svg'; import editSvg from 'icons/_icon_pencil.svg';
import resolveDiscussionSvg from 'icons/_icon_resolve_discussion.svg'; import resolveDiscussionSvg from 'icons/_icon_resolve_discussion.svg';
import resolvedDiscussionSvg from 'icons/_icon_status_success_solid.svg'; import resolvedDiscussionSvg from 'icons/_icon_status_success_solid.svg';
import ellipsisSvg from 'icons/_ellipsis_v.svg'; import ellipsisSvg from 'icons/_ellipsis_v.svg';
import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
export default { export default {
name: 'NoteActions', name: 'NoteActions',
directives: { directives: {
tooltip, tooltip,
...@@ -70,9 +70,7 @@ ...@@ -70,9 +70,7 @@
}, },
}, },
computed: { computed: {
...mapGetters([ ...mapGetters(['getUserDataByProp']),
'getUserDataByProp',
]),
shouldShowActionsDropdown() { shouldShowActionsDropdown() {
return this.currentUserId && (this.canEdit || this.canReportAsAbuse); return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
}, },
...@@ -115,7 +113,7 @@ ...@@ -115,7 +113,7 @@
this.$emit('handleResolve'); this.$emit('handleResolve');
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
export default { export default {
name: 'NoteAttachment', name: 'NoteAttachment',
props: { props: {
attachment: { attachment: {
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
required: true, required: true,
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg'; import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
import emojiSmile from 'icons/_emoji_smile.svg'; import emojiSmile from 'icons/_emoji_smile.svg';
import emojiSmiley from 'icons/_emoji_smiley.svg'; import emojiSmiley from 'icons/_emoji_smiley.svg';
import Flash from '../../flash'; import Flash from '../../flash';
import { glEmojiTag } from '../../emoji'; import { glEmojiTag } from '../../emoji';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
directives: { directives: {
tooltip, tooltip,
}, },
...@@ -30,9 +30,7 @@ ...@@ -30,9 +30,7 @@
}, },
}, },
computed: { computed: {
...mapGetters([ ...mapGetters(['getUserData']),
'getUserData',
]),
// `this.awards` is an array with emojis but they are not grouped by emoji name. See below. // `this.awards` is an array with emojis but they are not grouped by emoji name. See below.
// [ { name: foo, user: user1 }, { name: bar, user: user1 }, { name: foo, user: user2 } ] // [ { name: foo, user: user1 }, { name: bar, user: user1 }, { name: foo, user: user2 } ]
// This method will group emojis by their name as an Object. See below. // This method will group emojis by their name as an Object. See below.
...@@ -79,9 +77,7 @@ ...@@ -79,9 +77,7 @@
this.emojiSmiley = emojiSmiley; this.emojiSmiley = emojiSmiley;
}, },
methods: { methods: {
...mapActions([ ...mapActions(['toggleAwardRequest']),
'toggleAwardRequest',
]),
getAwardHTML(name) { getAwardHTML(name) {
return glEmojiTag(name); return glEmojiTag(name);
}, },
...@@ -96,30 +92,43 @@ ...@@ -96,30 +92,43 @@
const restrictedEmojis = ['thumbsup', 'thumbsdown']; const restrictedEmojis = ['thumbsup', 'thumbsdown'];
// Users can not add :+1: and :-1: to their own notes // Users can not add :+1: and :-1: to their own notes
if (this.getUserData.id === this.noteAuthorId && restrictedEmojis.indexOf(awardName) > -1) { if (
this.getUserData.id === this.noteAuthorId &&
restrictedEmojis.indexOf(awardName) > -1
) {
isAllowed = false; isAllowed = false;
} }
return this.getUserData.id && isAllowed; return this.getUserData.id && isAllowed;
}, },
hasReactionByCurrentUser(awardList) { hasReactionByCurrentUser(awardList) {
return awardList.filter(award => award.user.id === this.getUserData.id).length; return awardList.filter(award => award.user.id === this.getUserData.id)
.length;
}, },
awardTitle(awardsList) { awardTitle(awardsList) {
const hasReactionByCurrentUser = this.hasReactionByCurrentUser(awardsList); const hasReactionByCurrentUser = this.hasReactionByCurrentUser(
awardsList,
);
const TOOLTIP_NAME_COUNT = hasReactionByCurrentUser ? 9 : 10; const TOOLTIP_NAME_COUNT = hasReactionByCurrentUser ? 9 : 10;
let awardList = awardsList; let awardList = awardsList;
// Filter myself from list if I am awarded. // Filter myself from list if I am awarded.
if (hasReactionByCurrentUser) { if (hasReactionByCurrentUser) {
awardList = awardList.filter(award => award.user.id !== this.getUserData.id); awardList = awardList.filter(
award => award.user.id !== this.getUserData.id,
);
} }
// Get only 9-10 usernames to show in tooltip text. // Get only 9-10 usernames to show in tooltip text.
const namesToShow = awardList.slice(0, TOOLTIP_NAME_COUNT).map(award => award.user.name); const namesToShow = awardList
.slice(0, TOOLTIP_NAME_COUNT)
.map(award => award.user.name);
// Get the remaining list to use in `and x more` text. // Get the remaining list to use in `and x more` text.
const remainingAwardList = awardList.slice(TOOLTIP_NAME_COUNT, awardList.length); const remainingAwardList = awardList.slice(
TOOLTIP_NAME_COUNT,
awardList.length,
);
// Add myself to the begining of the list so title will start with You. // Add myself to the begining of the list so title will start with You.
if (hasReactionByCurrentUser) { if (hasReactionByCurrentUser) {
...@@ -130,14 +139,17 @@ ...@@ -130,14 +139,17 @@
// We have 10+ awarded user, join them with comma and add `and x more`. // We have 10+ awarded user, join them with comma and add `and x more`.
if (remainingAwardList.length) { if (remainingAwardList.length) {
title = `${namesToShow.join(', ')}, and ${remainingAwardList.length} more.`; title = `${namesToShow.join(', ')}, and ${
remainingAwardList.length
} more.`;
} else if (namesToShow.length > 1) { } else if (namesToShow.length > 1) {
// Join all names with comma but not the last one, it will be added with and text. // Join all names with comma but not the last one, it will be added with and text.
title = namesToShow.slice(0, namesToShow.length - 1).join(', '); title = namesToShow.slice(0, namesToShow.length - 1).join(', ');
// If we have more than 2 users we need an extra comma before and text. // If we have more than 2 users we need an extra comma before and text.
title += namesToShow.length > 2 ? ',' : ''; title += namesToShow.length > 2 ? ',' : '';
title += ` and ${namesToShow.slice(-1)}`; // Append and text title += ` and ${namesToShow.slice(-1)}`; // Append and text
} else { // We have only 2 users so join them with and. } else {
// We have only 2 users so join them with and.
title = namesToShow.join(' and '); title = namesToShow.join(' and ');
} }
...@@ -169,11 +181,12 @@ ...@@ -169,11 +181,12 @@
awardName: parsedName, awardName: parsedName,
}; };
this.toggleAwardRequest(data) this.toggleAwardRequest(data).catch(() =>
.catch(() => Flash('Something went wrong on our end.')); Flash('Something went wrong on our end.'),
);
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import noteEditedText from './note_edited_text.vue'; import noteEditedText from './note_edited_text.vue';
import noteAwardsList from './note_awards_list.vue'; import noteAwardsList from './note_awards_list.vue';
import noteAttachment from './note_attachment.vue'; import noteAttachment from './note_attachment.vue';
import noteForm from './note_form.vue'; import noteForm from './note_form.vue';
import TaskList from '../../task_list'; import TaskList from '../../task_list';
import autosave from '../mixins/autosave'; import autosave from '../mixins/autosave';
export default { export default {
components: { components: {
noteEditedText, noteEditedText,
noteAwardsList, noteAwardsList,
noteAttachment, noteAttachment,
noteForm, noteForm,
}, },
mixins: [ mixins: [autosave],
autosave,
],
props: { props: {
note: { note: {
type: Object, type: Object,
...@@ -77,7 +75,7 @@ ...@@ -77,7 +75,7 @@
this.$emit('cancelFormEdition', shouldConfirm, isDirty); this.$emit('cancelFormEdition', shouldConfirm, isDirty);
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue'; import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
export default { export default {
name: 'EditedNoteText', name: 'EditedNoteText',
components: { components: {
timeAgoTooltip, timeAgoTooltip,
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
default: 'edited-text', default: 'edited-text',
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue'; import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue'; import markdownField from '../../vue_shared/components/markdown/field.vue';
import issuableStateMixin from '../mixins/issuable_state'; import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable'; import resolvable from '../mixins/resolvable';
export default { export default {
name: 'IssueNoteForm', name: 'IssueNoteForm',
components: { components: {
issueWarning, issueWarning,
markdownField, markdownField,
}, },
mixins: [ mixins: [issuableStateMixin, resolvable],
issuableStateMixin,
resolvable,
],
props: { props: {
noteBody: { noteBody: {
type: String, type: String,
...@@ -69,7 +66,9 @@ ...@@ -69,7 +66,9 @@
return this.getNotesDataByProp('markdownDocsPath'); return this.getNotesDataByProp('markdownDocsPath');
}, },
quickActionsDocsPath() { quickActionsDocsPath() {
return !this.isEditing ? this.getNotesDataByProp('quickActionsDocsPath') : undefined; return !this.isEditing
? this.getNotesDataByProp('quickActionsDocsPath')
: undefined;
}, },
currentUserId() { currentUserId() {
return this.getUserDataByProp('id'); return this.getUserDataByProp('id');
...@@ -91,24 +90,29 @@ ...@@ -91,24 +90,29 @@
this.$refs.textarea.focus(); this.$refs.textarea.focus();
}, },
methods: { methods: {
...mapActions([ ...mapActions(['toggleResolveNote']),
'toggleResolveNote',
]),
handleUpdate(shouldResolve) { handleUpdate(shouldResolve) {
const beforeSubmitDiscussionState = this.discussionResolved; const beforeSubmitDiscussionState = this.discussionResolved;
this.isSubmitting = true; this.isSubmitting = true;
this.$emit('handleFormUpdate', this.updatedNoteBody, this.$refs.editNoteForm, () => { this.$emit(
'handleFormUpdate',
this.updatedNoteBody,
this.$refs.editNoteForm,
() => {
this.isSubmitting = false; this.isSubmitting = false;
if (shouldResolve) { if (shouldResolve) {
this.resolveHandler(beforeSubmitDiscussionState); this.resolveHandler(beforeSubmitDiscussionState);
} }
}); },
);
}, },
editMyLastNote() { editMyLastNote() {
if (this.updatedNoteBody === '') { if (this.updatedNoteBody === '') {
const lastNoteInDiscussion = this.getDiscussionLastNote(this.updatedNoteBody); const lastNoteInDiscussion = this.getDiscussionLastNote(
this.updatedNoteBody,
);
if (lastNoteInDiscussion) { if (lastNoteInDiscussion) {
eventHub.$emit('enterEditMode', { eventHub.$emit('enterEditMode', {
...@@ -119,10 +123,14 @@ ...@@ -119,10 +123,14 @@
}, },
cancelHandler(shouldConfirm = false) { cancelHandler(shouldConfirm = false) {
// Sends information about confirm message and if the textarea has changed // Sends information about confirm message and if the textarea has changed
this.$emit('cancelFormEdition', shouldConfirm, this.noteBody !== this.updatedNoteBody); this.$emit(
'cancelFormEdition',
shouldConfirm,
this.noteBody !== this.updatedNoteBody,
);
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue'; import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
export default { export default {
components: { components: {
timeAgoTooltip, timeAgoTooltip,
}, },
...@@ -49,9 +49,7 @@ ...@@ -49,9 +49,7 @@
}, },
}, },
methods: { methods: {
...mapActions([ ...mapActions(['setTargetNoteHash']),
'setTargetNoteHash',
]),
handleToggle() { handleToggle() {
this.$emit('toggleHandler'); this.$emit('toggleHandler');
}, },
...@@ -59,7 +57,7 @@ ...@@ -59,7 +57,7 @@
this.setTargetNoteHash(this.noteTimestampLink); this.setTargetNoteHash(this.noteTimestampLink);
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
export default { export default {
computed: { computed: {
...mapGetters([ ...mapGetters(['getNotesDataByProp']),
'getNotesDataByProp',
]),
registerLink() { registerLink() {
return this.getNotesDataByProp('registerPath'); return this.getNotesDataByProp('registerPath');
}, },
...@@ -13,7 +11,7 @@ ...@@ -13,7 +11,7 @@
return this.getNotesDataByProp('newSessionPath'); return this.getNotesDataByProp('newSessionPath');
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import resolveDiscussionsSvg from 'icons/_icon_mr_issue.svg'; import resolveDiscussionsSvg from 'icons/_icon_mr_issue.svg';
import nextDiscussionsSvg from 'icons/_next_discussion.svg'; import nextDiscussionsSvg from 'icons/_next_discussion.svg';
import Flash from '../../flash'; import Flash from '../../flash';
import { SYSTEM_NOTE } from '../constants'; import { SYSTEM_NOTE } from '../constants';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteableNote from './noteable_note.vue'; import noteableNote from './noteable_note.vue';
import noteHeader from './note_header.vue'; import noteHeader from './note_header.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue'; import noteSignedOutWidget from './note_signed_out_widget.vue';
import noteEditedText from './note_edited_text.vue'; import noteEditedText from './note_edited_text.vue';
import noteForm from './note_form.vue'; import noteForm from './note_form.vue';
import diffWithNote from './diff_with_note.vue'; import diffWithNote from './diff_with_note.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue'; import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue'; import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import autosave from '../mixins/autosave'; import autosave from '../mixins/autosave';
import noteable from '../mixins/noteable'; import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable'; import resolvable from '../mixins/resolvable';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import { scrollToElement } from '../../lib/utils/common_utils'; import { scrollToElement } from '../../lib/utils/common_utils';
export default { export default {
components: { components: {
noteableNote, noteableNote,
diffWithNote, diffWithNote,
...@@ -34,11 +34,7 @@ ...@@ -34,11 +34,7 @@
directives: { directives: {
tooltip, tooltip,
}, },
mixins: [ mixins: [autosave, noteable, resolvable],
autosave,
noteable,
resolvable,
],
props: { props: {
note: { note: {
type: Object, type: Object,
...@@ -99,7 +95,9 @@ ...@@ -99,7 +95,9 @@
return this.unresolvedDiscussions.length > 0; return this.unresolvedDiscussions.length > 0;
}, },
wrapperComponent() { wrapperComponent() {
return (this.discussion.diffDiscussion && this.discussion.diffFile) ? diffWithNote : 'div'; return this.discussion.diffDiscussion && this.discussion.diffFile
? diffWithNote
: 'div';
}, },
wrapperClass() { wrapperClass() {
return this.isDiffDiscussion ? '' : 'panel panel-default'; return this.isDiffDiscussion ? '' : 'panel panel-default';
...@@ -151,8 +149,10 @@ ...@@ -151,8 +149,10 @@
}, },
cancelReplyForm(shouldConfirm) { cancelReplyForm(shouldConfirm) {
if (shouldConfirm && this.$refs.noteForm.isDirty) { if (shouldConfirm && this.$refs.noteForm.isDirty) {
const msg = 'Are you sure you want to cancel creating this comment?';
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
if (!confirm('Are you sure you want to cancel creating this comment?')) { if (!confirm(msg)) {
return; return;
} }
} }
...@@ -178,7 +178,7 @@ ...@@ -178,7 +178,7 @@
this.resetAutoSave(); this.resetAutoSave();
callback(); callback();
}) })
.catch((err) => { .catch(err => {
this.removePlaceholderNotes(); this.removePlaceholderNotes();
this.isReplying = true; this.isReplying = true;
this.$nextTick(() => { this.$nextTick(() => {
...@@ -204,7 +204,7 @@ Please check your network connection and try again.`; ...@@ -204,7 +204,7 @@ Please check your network connection and try again.`;
} }
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import { escape } from 'underscore'; import { escape } from 'underscore';
import Flash from '../../flash'; import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue'; import noteHeader from './note_header.vue';
import noteActions from './note_actions.vue'; import noteActions from './note_actions.vue';
import noteBody from './note_body.vue'; import noteBody from './note_body.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import noteable from '../mixins/noteable'; import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable'; import resolvable from '../mixins/resolvable';
export default { export default {
components: { components: {
userAvatarLink, userAvatarLink,
noteHeader, noteHeader,
noteActions, noteActions,
noteBody, noteBody,
}, },
mixins: [ mixins: [noteable, resolvable],
noteable,
resolvable,
],
props: { props: {
note: { note: {
type: Object, type: Object,
...@@ -37,10 +34,7 @@ ...@@ -37,10 +34,7 @@
}; };
}, },
computed: { computed: {
...mapGetters([ ...mapGetters(['targetNoteHash', 'getUserData']),
'targetNoteHash',
'getUserData',
]),
author() { author() {
return this.note.author; return this.note.author;
}, },
...@@ -53,7 +47,9 @@ ...@@ -53,7 +47,9 @@
}; };
}, },
canReportAsAbuse() { canReportAsAbuse() {
return this.note.report_abuse_path && this.author.id !== this.getUserData.id; return (
this.note.report_abuse_path && this.author.id !== this.getUserData.id
);
}, },
noteAnchorId() { noteAnchorId() {
return `note_${this.note.id}`; return `note_${this.note.id}`;
...@@ -89,7 +85,9 @@ ...@@ -89,7 +85,9 @@
this.isDeleting = false; this.isDeleting = false;
}) })
.catch(() => { .catch(() => {
Flash('Something went wrong while deleting your note. Please try again.'); Flash(
'Something went wrong while deleting your note. Please try again.',
);
this.isDeleting = false; this.isDeleting = false;
}); });
} }
...@@ -120,7 +118,8 @@ ...@@ -120,7 +118,8 @@
this.isRequesting = false; this.isRequesting = false;
this.isEditing = true; this.isEditing = true;
this.$nextTick(() => { this.$nextTick(() => {
const msg = 'Something went wrong while editing your comment. Please try again.'; const msg =
'Something went wrong while editing your comment. Please try again.';
Flash(msg, 'alert', this.$el); Flash(msg, 'alert', this.$el);
this.recoverNoteContent(noteText); this.recoverNoteContent(noteText);
callback(); callback();
...@@ -130,7 +129,8 @@ ...@@ -130,7 +129,8 @@
formCancelHandler(shouldConfirm, isDirty) { formCancelHandler(shouldConfirm, isDirty) {
if (shouldConfirm && isDirty) { if (shouldConfirm && isDirty) {
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
if (!confirm('Are you sure you want to cancel editing this comment?')) return; if (!confirm('Are you sure you want to cancel editing this comment?'))
return;
} }
this.$refs.noteBody.resetAutoSave(); this.$refs.noteBody.resetAutoSave();
if (this.oldContent) { if (this.oldContent) {
...@@ -146,7 +146,7 @@ ...@@ -146,7 +146,7 @@
this.$refs.noteBody.$refs.noteForm.note.note = noteText; this.$refs.noteBody.$refs.noteForm.note.note = noteText;
}, },
}, },
}; };
</script> </script>
<template> <template>
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import { getLocationHash } from '../../lib/utils/url_utility'; import { getLocationHash } from '../../lib/utils/url_utility';
import Flash from '../../flash'; import Flash from '../../flash';
import store from '../stores/'; import store from '../stores/';
import * as constants from '../constants'; import * as constants from '../constants';
import noteableNote from './noteable_note.vue'; import noteableNote from './noteable_note.vue';
import noteableDiscussion from './noteable_discussion.vue'; import noteableDiscussion from './noteable_discussion.vue';
import systemNote from '../../vue_shared/components/notes/system_note.vue'; import systemNote from '../../vue_shared/components/notes/system_note.vue';
import commentForm from './comment_form.vue'; import commentForm from './comment_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue'; import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue'; import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue'; import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue';
export default { export default {
name: 'NotesApp', name: 'NotesApp',
components: { components: {
noteableNote, noteableNote,
...@@ -47,16 +47,14 @@ ...@@ -47,16 +47,14 @@
}; };
}, },
computed: { computed: {
...mapGetters([ ...mapGetters(['notes', 'getNotesDataByProp', 'discussionCount']),
'notes',
'getNotesDataByProp',
'discussionCount',
]),
noteableType() { noteableType() {
// FIXME -- @fatihacet Get this from JSON data. // FIXME -- @fatihacet Get this from JSON data.
const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE } = constants; const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE } = constants;
return this.noteableData.merge_params ? MERGE_REQUEST_NOTEABLE_TYPE : ISSUE_NOTEABLE_TYPE; return this.noteableData.merge_params
? MERGE_REQUEST_NOTEABLE_TYPE
: ISSUE_NOTEABLE_TYPE;
}, },
allNotes() { allNotes() {
if (this.isLoading) { if (this.isLoading) {
...@@ -79,9 +77,11 @@ ...@@ -79,9 +77,11 @@
const parentElement = this.$el.parentElement; const parentElement = this.$el.parentElement;
if (parentElement && if (
parentElement.classList.contains('js-vue-notes-event')) { parentElement &&
parentElement.addEventListener('toggleAward', (event) => { parentElement.classList.contains('js-vue-notes-event')
) {
parentElement.addEventListener('toggleAward', event => {
const { awardName, noteId } = event.detail; const { awardName, noteId } = event.detail;
this.actionToggleAward({ awardName, noteId }); this.actionToggleAward({ awardName, noteId });
}); });
...@@ -131,7 +131,9 @@ ...@@ -131,7 +131,9 @@
.then(() => this.checkLocationHash()) .then(() => this.checkLocationHash())
.catch(() => { .catch(() => {
this.isLoading = false; this.isLoading = false;
Flash('Something went wrong while fetching comments. Please try again.'); Flash(
'Something went wrong while fetching comments. Please try again.',
);
}); });
}, },
initPolling() { initPolling() {
...@@ -154,7 +156,7 @@ ...@@ -154,7 +156,7 @@
} }
}, },
}, },
}; };
</script> </script>
<template> <template>
......
import Vue from 'vue'; import Vue from 'vue';
import notesApp from './components/notes_app.vue'; import notesApp from './components/notes_app.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({ document.addEventListener(
'DOMContentLoaded',
() =>
new Vue({
el: '#js-vue-notes', el: '#js-vue-notes',
components: { components: {
notesApp, notesApp,
...@@ -9,13 +12,17 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ ...@@ -9,13 +12,17 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
data() { data() {
const notesDataset = document.getElementById('js-vue-notes').dataset; const notesDataset = document.getElementById('js-vue-notes').dataset;
const parsedUserData = JSON.parse(notesDataset.currentUserData); const parsedUserData = JSON.parse(notesDataset.currentUserData);
const currentUserData = parsedUserData ? { let currentUserData = {};
if (parsedUserData) {
currentUserData = {
id: parsedUserData.id, id: parsedUserData.id,
name: parsedUserData.name, name: parsedUserData.name,
username: parsedUserData.username, username: parsedUserData.username,
avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url, avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url,
path: parsedUserData.path, path: parsedUserData.path,
} : {}; };
}
return { return {
noteableData: JSON.parse(notesDataset.noteableData), noteableData: JSON.parse(notesDataset.noteableData),
...@@ -32,4 +39,5 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ ...@@ -32,4 +39,5 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
}, },
}); });
}, },
})); }),
);
...@@ -5,7 +5,11 @@ import { capitalizeFirstCharacter } from '../../lib/utils/text_utility'; ...@@ -5,7 +5,11 @@ import { capitalizeFirstCharacter } from '../../lib/utils/text_utility';
export default { export default {
methods: { methods: {
initAutoSave(noteableType) { initAutoSave(noteableType) {
this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), ['Note', capitalizeFirstCharacter(noteableType), this.note.id]); this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), [
'Note',
capitalizeFirstCharacter(noteableType),
this.note.id,
]);
}, },
resetAutoSave() { resetAutoSave() {
this.autosave.reset(); this.autosave.reset();
......
...@@ -12,7 +12,8 @@ export default { ...@@ -12,7 +12,8 @@ export default {
discussionResolved() { discussionResolved() {
const { notes, resolved } = this.note; const { notes, resolved } = this.note;
if (notes) { // Decide resolved state using store. Only valid for discussions. if (notes) {
// Decide resolved state using store. Only valid for discussions.
return notes.every(note => note.resolved && !note.system); return notes.every(note => note.resolved && !note.system);
} }
...@@ -26,7 +27,9 @@ export default { ...@@ -26,7 +27,9 @@ export default {
return __('Comment and resolve discussion'); return __('Comment and resolve discussion');
} }
return this.discussionResolved ? __('Unresolve discussion') : __('Resolve discussion'); return this.discussionResolved
? __('Unresolve discussion')
: __('Resolve discussion');
}, },
}, },
methods: { methods: {
...@@ -42,7 +45,9 @@ export default { ...@@ -42,7 +45,9 @@ export default {
}) })
.catch(() => { .catch(() => {
this.isResolving = false; this.isResolving = false;
const msg = __('Something went wrong while resolving this discussion. Please try again.'); const msg = __(
'Something went wrong while resolving this discussion. Please try again.',
);
Flash(msg, 'alert', this.$el); Flash(msg, 'alert', this.$el);
}); });
}, },
......
...@@ -22,7 +22,9 @@ export default { ...@@ -22,7 +22,9 @@ export default {
}, },
toggleResolveNote(endpoint, isResolved) { toggleResolveNote(endpoint, isResolved) {
const { RESOLVE_NOTE_METHOD_NAME, UNRESOLVE_NOTE_METHOD_NAME } = constants; const { RESOLVE_NOTE_METHOD_NAME, UNRESOLVE_NOTE_METHOD_NAME } = constants;
const method = isResolved ? UNRESOLVE_NOTE_METHOD_NAME : RESOLVE_NOTE_METHOD_NAME; const method = isResolved
? UNRESOLVE_NOTE_METHOD_NAME
: RESOLVE_NOTE_METHOD_NAME;
return Vue.http[method](endpoint); return Vue.http[method](endpoint);
}, },
......
...@@ -12,47 +12,57 @@ import { isInViewport, scrollToElement } from '../../lib/utils/common_utils'; ...@@ -12,47 +12,57 @@ import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
let eTagPoll; let eTagPoll;
export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data); export const setNotesData = ({ commit }, data) =>
export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data); commit(types.SET_NOTES_DATA, data);
export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data); export const setNoteableData = ({ commit }, data) =>
export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data); commit(types.SET_NOTEABLE_DATA, data);
export const setInitialNotes = ({ commit }, data) => commit(types.SET_INITIAL_NOTES, data); export const setUserData = ({ commit }, data) =>
export const setTargetNoteHash = ({ commit }, data) => commit(types.SET_TARGET_NOTE_HASH, data); commit(types.SET_USER_DATA, data);
export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data); export const setLastFetchedAt = ({ commit }, data) =>
commit(types.SET_LAST_FETCHED_AT, data);
export const fetchNotes = ({ commit }, path) => service export const setInitialNotes = ({ commit }, data) =>
commit(types.SET_INITIAL_NOTES, data);
export const setTargetNoteHash = ({ commit }, data) =>
commit(types.SET_TARGET_NOTE_HASH, data);
export const toggleDiscussion = ({ commit }, data) =>
commit(types.TOGGLE_DISCUSSION, data);
export const fetchNotes = ({ commit }, path) =>
service
.fetchNotes(path) .fetchNotes(path)
.then(res => res.json()) .then(res => res.json())
.then((res) => { .then(res => {
commit(types.SET_INITIAL_NOTES, res); commit(types.SET_INITIAL_NOTES, res);
}); });
export const deleteNote = ({ commit }, note) => service export const deleteNote = ({ commit }, note) =>
.deleteNote(note.path) service.deleteNote(note.path).then(() => {
.then(() => {
commit(types.DELETE_NOTE, note); commit(types.DELETE_NOTE, note);
}); });
export const updateNote = ({ commit }, { endpoint, note }) => service export const updateNote = ({ commit }, { endpoint, note }) =>
service
.updateNote(endpoint, note) .updateNote(endpoint, note)
.then(res => res.json()) .then(res => res.json())
.then((res) => { .then(res => {
commit(types.UPDATE_NOTE, res); commit(types.UPDATE_NOTE, res);
}); });
export const replyToDiscussion = ({ commit }, { endpoint, data }) => service export const replyToDiscussion = ({ commit }, { endpoint, data }) =>
service
.replyToDiscussion(endpoint, data) .replyToDiscussion(endpoint, data)
.then(res => res.json()) .then(res => res.json())
.then((res) => { .then(res => {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res); commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
return res; return res;
}); });
export const createNewNote = ({ commit }, { endpoint, data }) => service export const createNewNote = ({ commit }, { endpoint, data }) =>
service
.createNewNote(endpoint, data) .createNewNote(endpoint, data)
.then(res => res.json()) .then(res => res.json())
.then((res) => { .then(res => {
if (!res.errors) { if (!res.errors) {
commit(types.ADD_NEW_NOTE, res); commit(types.ADD_NEW_NOTE, res);
} }
...@@ -62,36 +72,55 @@ export const createNewNote = ({ commit }, { endpoint, data }) => service ...@@ -62,36 +72,55 @@ export const createNewNote = ({ commit }, { endpoint, data }) => service
export const removePlaceholderNotes = ({ commit }) => export const removePlaceholderNotes = ({ commit }) =>
commit(types.REMOVE_PLACEHOLDER_NOTES); commit(types.REMOVE_PLACEHOLDER_NOTES);
export const toggleResolveNote = ({ commit }, { endpoint, isResolved, discussion }) => service export const toggleResolveNote = (
{ commit },
{ endpoint, isResolved, discussion },
) =>
service
.toggleResolveNote(endpoint, isResolved) .toggleResolveNote(endpoint, isResolved)
.then(res => res.json()) .then(res => res.json())
.then((res) => { .then(res => {
const mutationType = discussion ? types.UPDATE_DISCUSSION : types.UPDATE_NOTE; const mutationType = discussion
? types.UPDATE_DISCUSSION
: types.UPDATE_NOTE;
commit(mutationType, res); commit(mutationType, res);
}); });
export const closeIssue = ({ commit, dispatch, state }) => service export const closeIssue = ({ commit, dispatch, state }) => {
dispatch('toggleStateButtonLoading', true);
return service
.toggleIssueState(state.notesData.closePath) .toggleIssueState(state.notesData.closePath)
.then(res => res.json()) .then(res => res.json())
.then((data) => { .then(data => {
commit(types.CLOSE_ISSUE); commit(types.CLOSE_ISSUE);
dispatch('emitStateChangedEvent', data); dispatch('emitStateChangedEvent', data);
dispatch('toggleStateButtonLoading', false);
}); });
};
export const reopenIssue = ({ commit, dispatch, state }) => service export const reopenIssue = ({ commit, dispatch, state }) => {
dispatch('toggleStateButtonLoading', true);
return service
.toggleIssueState(state.notesData.reopenPath) .toggleIssueState(state.notesData.reopenPath)
.then(res => res.json()) .then(res => res.json())
.then((data) => { .then(data => {
commit(types.REOPEN_ISSUE); commit(types.REOPEN_ISSUE);
dispatch('emitStateChangedEvent', data); dispatch('emitStateChangedEvent', data);
dispatch('toggleStateButtonLoading', false);
}); });
};
export const toggleStateButtonLoading = ({ commit }, value) =>
commit(types.TOGGLE_STATE_BUTTON_LOADING, value);
export const emitStateChangedEvent = ({ commit, getters }, data) => { export const emitStateChangedEvent = ({ commit, getters }, data) => {
const event = new CustomEvent('issuable_vue_app:change', { detail: { const event = new CustomEvent('issuable_vue_app:change', {
detail: {
data, data,
isClosed: getters.openState === constants.CLOSED, isClosed: getters.openState === constants.CLOSED,
} }); },
});
document.dispatchEvent(event); document.dispatchEvent(event);
}; };
...@@ -133,8 +162,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { ...@@ -133,8 +162,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
}); });
} }
return dispatch(methodToDispatch, noteData) return dispatch(methodToDispatch, noteData).then(res => {
.then((res) => {
const { errors } = res; const { errors } = res;
const commandsChanges = res.commands_changes; const commandsChanges = res.commands_changes;
...@@ -150,8 +178,11 @@ export const saveNote = ({ commit, dispatch }, noteData) => { ...@@ -150,8 +178,11 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
const votesBlock = $('.js-awards-block').eq(0); const votesBlock = $('.js-awards-block').eq(0);
loadAwardsHandler() loadAwardsHandler()
.then((awardsHandler) => { .then(awardsHandler => {
awardsHandler.addAwardToEmojiBar(votesBlock, commandsChanges.emoji_award); awardsHandler.addAwardToEmojiBar(
votesBlock,
commandsChanges.emoji_award,
);
awardsHandler.scrollToAwards(); awardsHandler.scrollToAwards();
}) })
.catch(() => { .catch(() => {
...@@ -163,7 +194,10 @@ export const saveNote = ({ commit, dispatch }, noteData) => { ...@@ -163,7 +194,10 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
}); });
} }
if (commandsChanges.spend_time != null || commandsChanges.time_estimate != null) { if (
commandsChanges.spend_time != null ||
commandsChanges.time_estimate != null
) {
sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', res); sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', res);
} }
} }
...@@ -181,11 +215,17 @@ const pollSuccessCallBack = (resp, commit, state, getters) => { ...@@ -181,11 +215,17 @@ const pollSuccessCallBack = (resp, commit, state, getters) => {
if (resp.notes && resp.notes.length) { if (resp.notes && resp.notes.length) {
const { notesById } = getters; const { notesById } = getters;
resp.notes.forEach((note) => { resp.notes.forEach(note => {
if (notesById[note.id]) { if (notesById[note.id]) {
commit(types.UPDATE_NOTE, note); commit(types.UPDATE_NOTE, note);
} else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) { } else if (
const discussion = utils.findNoteObjectById(state.notes, note.discussion_id); note.type === constants.DISCUSSION_NOTE ||
note.type === constants.DIFF_NOTE
) {
const discussion = utils.findNoteObjectById(
state.notes,
note.discussion_id,
);
if (discussion) { if (discussion) {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note); commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note);
...@@ -208,9 +248,12 @@ export const poll = ({ commit, state, getters }) => { ...@@ -208,9 +248,12 @@ export const poll = ({ commit, state, getters }) => {
resource: service, resource: service,
method: 'poll', method: 'poll',
data: state, data: state,
successCallback: resp => resp.json() successCallback: resp =>
resp
.json()
.then(data => pollSuccessCallBack(data, commit, state, getters)), .then(data => pollSuccessCallBack(data, commit, state, getters)),
errorCallback: () => Flash('Something went wrong while fetching latest comments.'), errorCallback: () =>
Flash('Something went wrong while fetching latest comments.'),
}); });
if (!Visibility.hidden()) { if (!Visibility.hidden()) {
...@@ -237,15 +280,22 @@ export const restartPolling = () => { ...@@ -237,15 +280,22 @@ export const restartPolling = () => {
}; };
export const fetchData = ({ commit, state, getters }) => { export const fetchData = ({ commit, state, getters }) => {
const requestData = { endpoint: state.notesData.notesPath, lastFetchedAt: state.lastFetchedAt }; const requestData = {
endpoint: state.notesData.notesPath,
lastFetchedAt: state.lastFetchedAt,
};
service.poll(requestData) service
.poll(requestData)
.then(resp => resp.json) .then(resp => resp.json)
.then(data => pollSuccessCallBack(data, commit, state, getters)) .then(data => pollSuccessCallBack(data, commit, state, getters))
.catch(() => Flash('Something went wrong while fetching latest comments.')); .catch(() => Flash('Something went wrong while fetching latest comments.'));
}; };
export const toggleAward = ({ commit, state, getters, dispatch }, { awardName, noteId }) => { export const toggleAward = (
{ commit, state, getters, dispatch },
{ awardName, noteId },
) => {
commit(types.TOGGLE_AWARD, { awardName, note: getters.notesById[noteId] }); commit(types.TOGGLE_AWARD, { awardName, note: getters.notesById[noteId] });
}; };
......
...@@ -11,27 +11,31 @@ export const getNoteableDataByProp = state => prop => state.noteableData[prop]; ...@@ -11,27 +11,31 @@ export const getNoteableDataByProp = state => prop => state.noteableData[prop];
export const openState = state => state.noteableData.state; export const openState = state => state.noteableData.state;
export const getUserData = state => state.userData || {}; export const getUserData = state => state.userData || {};
export const getUserDataByProp = state => prop => state.userData && state.userData[prop]; export const getUserDataByProp = state => prop =>
state.userData && state.userData[prop];
export const notesById = state => state.notes.reduce((acc, note) => { export const notesById = state =>
state.notes.reduce((acc, note) => {
note.notes.every(n => Object.assign(acc, { [n.id]: n })); note.notes.every(n => Object.assign(acc, { [n.id]: n }));
return acc; return acc;
}, {}); }, {});
const reverseNotes = array => array.slice(0).reverse(); const reverseNotes = array => array.slice(0).reverse();
const isLastNote = (note, state) => !note.system && const isLastNote = (note, state) =>
state.userData && note.author && !note.system &&
state.userData &&
note.author &&
note.author.id === state.userData.id; note.author.id === state.userData.id;
export const getCurrentUserLastNote = state => _.flatten( export const getCurrentUserLastNote = state =>
reverseNotes(state.notes) _.flatten(
.map(note => reverseNotes(note.notes)), reverseNotes(state.notes).map(note => reverseNotes(note.notes)),
).find(el => isLastNote(el, state)); ).find(el => isLastNote(el, state));
export const getDiscussionLastNote = state => discussion => reverseNotes(discussion.notes) export const getDiscussionLastNote = state => discussion =>
.find(el => isLastNote(el, state)); reverseNotes(discussion.notes).find(el => isLastNote(el, state));
export const discussionCount = (state) => { export const discussionCount = state => {
const discussions = state.notes.filter(n => !n.individual_note); const discussions = state.notes.filter(n => !n.individual_note);
return discussions.length; return discussions.length;
...@@ -43,10 +47,10 @@ export const unresolvedDiscussions = (state, getters) => { ...@@ -43,10 +47,10 @@ export const unresolvedDiscussions = (state, getters) => {
return state.notes.filter(n => !n.individual_note && !resolvedMap[n.id]); return state.notes.filter(n => !n.individual_note && !resolvedMap[n.id]);
}; };
export const resolvedDiscussionsById = (state) => { export const resolvedDiscussionsById = state => {
const map = {}; const map = {};
state.notes.forEach((n) => { state.notes.forEach(n => {
if (n.notes) { if (n.notes) {
const resolved = n.notes.every(note => note.resolved && !note.system); const resolved = n.notes.every(note => note.resolved && !note.system);
......
...@@ -12,6 +12,9 @@ export default new Vuex.Store({ ...@@ -12,6 +12,9 @@ export default new Vuex.Store({
targetNoteHash: null, targetNoteHash: null,
lastFetchedAt: null, lastFetchedAt: null,
// View layer
isToggleStateButtonLoading: false,
// holds endpoints and permissions provided through haml // holds endpoints and permissions provided through haml
notesData: {}, notesData: {},
userData: {}, userData: {},
......
...@@ -17,3 +17,4 @@ export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION'; ...@@ -17,3 +17,4 @@ export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION';
// Issue // Issue
export const CLOSE_ISSUE = 'CLOSE_ISSUE'; export const CLOSE_ISSUE = 'CLOSE_ISSUE';
export const REOPEN_ISSUE = 'REOPEN_ISSUE'; export const REOPEN_ISSUE = 'REOPEN_ISSUE';
export const TOGGLE_STATE_BUTTON_LOADING = 'TOGGLE_STATE_BUTTON_LOADING';
...@@ -7,7 +7,7 @@ export default { ...@@ -7,7 +7,7 @@ export default {
[types.ADD_NEW_NOTE](state, note) { [types.ADD_NEW_NOTE](state, note) {
const { discussion_id, type } = note; const { discussion_id, type } = note;
const [exists] = state.notes.filter(n => n.id === note.discussion_id); const [exists] = state.notes.filter(n => n.id === note.discussion_id);
const isDiscussion = (type === constants.DISCUSSION_NOTE); const isDiscussion = type === constants.DISCUSSION_NOTE;
if (!exists) { if (!exists) {
const noteData = { const noteData = {
...@@ -63,13 +63,15 @@ export default { ...@@ -63,13 +63,15 @@ export default {
const note = notes[i]; const note = notes[i];
const children = note.notes; const children = note.notes;
if (children.length && !note.individual_note) { // remove placeholder from discussions if (children.length && !note.individual_note) {
// remove placeholder from discussions
for (let j = children.length - 1; j >= 0; j -= 1) { for (let j = children.length - 1; j >= 0; j -= 1) {
if (children[j].isPlaceholderNote) { if (children[j].isPlaceholderNote) {
children.splice(j, 1); children.splice(j, 1);
} }
} }
} else if (note.isPlaceholderNote) { // remove placeholders from state root } else if (note.isPlaceholderNote) {
// remove placeholders from state root
notes.splice(i, 1); notes.splice(i, 1);
} }
} }
...@@ -89,10 +91,10 @@ export default { ...@@ -89,10 +91,10 @@ export default {
[types.SET_INITIAL_NOTES](state, notesData) { [types.SET_INITIAL_NOTES](state, notesData) {
const notes = []; const notes = [];
notesData.forEach((note) => { notesData.forEach(note => {
// To support legacy notes, should be very rare case. // To support legacy notes, should be very rare case.
if (note.individual_note && note.notes.length > 1) { if (note.individual_note && note.notes.length > 1) {
note.notes.forEach((n) => { note.notes.forEach(n => {
notes.push({ notes.push({
...note, ...note,
notes: [n], // override notes array to only have one item to mimick individual_note notes: [n], // override notes array to only have one item to mimick individual_note
...@@ -103,7 +105,7 @@ export default { ...@@ -103,7 +105,7 @@ export default {
notes.push({ notes.push({
...note, ...note,
expanded: (oldNote ? oldNote.expanded : note.expanded), expanded: oldNote ? oldNote.expanded : note.expanded,
}); });
} }
}); });
...@@ -128,7 +130,9 @@ export default { ...@@ -128,7 +130,9 @@ export default {
notesArr.push({ notesArr.push({
individual_note: true, individual_note: true,
isPlaceholderNote: true, isPlaceholderNote: true,
placeholderType: data.isSystemNote ? constants.SYSTEM_NOTE : constants.NOTE, placeholderType: data.isSystemNote
? constants.SYSTEM_NOTE
: constants.NOTE,
notes: [ notes: [
{ {
body: data.noteBody, body: data.noteBody,
...@@ -141,12 +145,16 @@ export default { ...@@ -141,12 +145,16 @@ export default {
const { awardName, note } = data; const { awardName, note } = data;
const { id, name, username } = state.userData; const { id, name, username } = state.userData;
const hasEmojiAwardedByCurrentUser = note.award_emoji const hasEmojiAwardedByCurrentUser = note.award_emoji.filter(
.filter(emoji => emoji.name === data.awardName && emoji.user.id === id); emoji => emoji.name === data.awardName && emoji.user.id === id,
);
if (hasEmojiAwardedByCurrentUser.length) { if (hasEmojiAwardedByCurrentUser.length) {
// If current user has awarded this emoji, remove it. // If current user has awarded this emoji, remove it.
note.award_emoji.splice(note.award_emoji.indexOf(hasEmojiAwardedByCurrentUser[0]), 1); note.award_emoji.splice(
note.award_emoji.indexOf(hasEmojiAwardedByCurrentUser[0]),
1,
);
} else { } else {
note.award_emoji.push({ note.award_emoji.push({
name: awardName, name: awardName,
...@@ -199,4 +207,8 @@ export default { ...@@ -199,4 +207,8 @@ export default {
[types.REOPEN_ISSUE](state) { [types.REOPEN_ISSUE](state) {
Object.assign(state.noteableData, { state: constants.REOPENED }); Object.assign(state.noteableData, { state: constants.REOPENED });
}, },
[types.TOGGLE_STATE_BUTTON_LOADING](state, value) {
Object.assign(state, { isToggleStateButtonLoading: value });
},
}; };
...@@ -2,13 +2,15 @@ import AjaxCache from '~/lib/utils/ajax_cache'; ...@@ -2,13 +2,15 @@ import AjaxCache from '~/lib/utils/ajax_cache';
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm; const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
export const findNoteObjectById = (notes, id) => notes.filter(n => n.id === id)[0]; export const findNoteObjectById = (notes, id) =>
notes.filter(n => n.id === id)[0];
export const getQuickActionText = (note) => { export const getQuickActionText = note => {
let text = 'Applying command'; let text = 'Applying command';
const quickActions = AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || []; const quickActions =
AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || [];
const executedCommands = quickActions.filter((command) => { const executedCommands = quickActions.filter(command => {
const commandRegex = new RegExp(`/${command.name}`); const commandRegex = new RegExp(`/${command.name}`);
return commandRegex.test(note); return commandRegex.test(note);
}); });
...@@ -27,4 +29,5 @@ export const getQuickActionText = (note) => { ...@@ -27,4 +29,5 @@ export const getQuickActionText = (note) => {
export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note); export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note);
export const stripQuickActions = note => note.replace(REGEX_QUICK_ACTIONS, '').trim(); export const stripQuickActions = note =>
note.replace(REGEX_QUICK_ACTIONS, '').trim();
import NotificationsForm from '../../../../notifications_form';
import notificationsDropdown from '../../../../notifications_dropdown';
document.addEventListener('DOMContentLoaded', () => {
new NotificationsForm(); // eslint-disable-line no-new
notificationsDropdown();
});
import Vue from 'vue';
import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import BlobViewer from '~/blob/viewer/index'; import BlobViewer from '~/blob/viewer/index';
import initBlob from '~/pages/projects/init_blob'; import initBlob from '~/pages/projects/init_blob';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new BlobViewer(); // eslint-disable-line no-new new BlobViewer(); // eslint-disable-line no-new
initBlob(); initBlob();
const CommitPipelineStatusEl = document.querySelector('.js-commit-pipeline-status');
const statusLink = document.querySelector('.commit-actions .ci-status-link');
if (statusLink) {
statusLink.remove();
// eslint-disable-next-line no-new
new Vue({
el: CommitPipelineStatusEl,
components: {
commitPipelineStatus,
},
render(createElement) {
return createElement('commit-pipeline-status', {
props: {
endpoint: CommitPipelineStatusEl.dataset.endpoint,
},
});
},
});
}
}); });
import $ from 'jquery'; import $ from 'jquery';
import initBlob from '~/blob_edit/blob_bundle';
import ShortcutsNavigation from '~/shortcuts_navigation'; import ShortcutsNavigation from '~/shortcuts_navigation';
import NotificationsForm from '~/notifications_form'; import NotificationsForm from '~/notifications_form';
import UserCallout from '~/user_callout'; import UserCallout from '~/user_callout';
...@@ -19,10 +20,22 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -19,10 +20,22 @@ document.addEventListener('DOMContentLoaded', () => {
className: 'js-autodevops-banner', className: 'js-autodevops-banner',
}); });
if ($('#tree-slider').length) new TreeView(); // eslint-disable-line no-new // Project show page loads different overview content based on user preferences
if ($('.blob-viewer').length) new BlobViewer(); // eslint-disable-line no-new const treeSlider = document.querySelector('#tree-slider');
if ($('.project-show-activity').length) new Activities(); // eslint-disable-line no-new if (treeSlider) {
$('#tree-slider').waitForImages(() => { new TreeView(); // eslint-disable-line no-new
initBlob();
}
if (document.querySelector('.blob-viewer')) {
new BlobViewer(); // eslint-disable-line no-new
}
if (document.querySelector('.project-show-activity')) {
new Activities(); // eslint-disable-line no-new
}
$(treeSlider).waitForImages(() => {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
}); });
}); });
...@@ -14,8 +14,6 @@ export default class PerformanceBar { ...@@ -14,8 +14,6 @@ export default class PerformanceBar {
init(opts) { init(opts) {
const $container = $(opts.container); const $container = $(opts.container);
this.$sqlProfileLink = $container.find('.js-toggle-modal-peek-sql');
this.$sqlProfileModal = $container.find('#modal-peek-pg-queries');
this.$lineProfileLink = $container.find('.js-toggle-modal-peek-line-profile'); this.$lineProfileLink = $container.find('.js-toggle-modal-peek-line-profile');
this.$lineProfileModal = $('#modal-peek-line-profile'); this.$lineProfileModal = $('#modal-peek-line-profile');
this.initEventListeners(); this.initEventListeners();
...@@ -23,7 +21,6 @@ export default class PerformanceBar { ...@@ -23,7 +21,6 @@ export default class PerformanceBar {
} }
initEventListeners() { initEventListeners() {
this.$sqlProfileLink.on('click', () => this.handleSQLProfileLink());
this.$lineProfileLink.on('click', e => this.handleLineProfileLink(e)); this.$lineProfileLink.on('click', e => this.handleLineProfileLink(e));
$(document).on('click', '.js-lineprof-file', PerformanceBar.toggleLineProfileFile); $(document).on('click', '.js-lineprof-file', PerformanceBar.toggleLineProfileFile);
} }
...@@ -36,10 +33,6 @@ export default class PerformanceBar { ...@@ -36,10 +33,6 @@ export default class PerformanceBar {
} }
} }
handleSQLProfileLink() {
PerformanceBar.toggleModal(this.$sqlProfileModal);
}
handleLineProfileLink(e) { handleLineProfileLink(e) {
const lineProfilerParameter = getParameterValues('lineprofiler'); const lineProfilerParameter = getParameterValues('lineprofiler');
const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`); const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`);
......
<script>
import timeagoMixin from '../../vue_shared/mixins/timeago';
import tooltip from '../../vue_shared/directives/tooltip';
import LoadingButton from '../../vue_shared/components/loading_button.vue';
import { visitUrl } from '../../lib/utils/url_utility';
import createFlash from '../../flash';
import MemoryUsage from './memory_usage.vue';
import StatusIcon from './mr_widget_status_icon.vue';
import MRWidgetService from '../services/mr_widget_service';
export default {
name: 'Deployment',
components: {
LoadingButton,
MemoryUsage,
StatusIcon,
},
directives: {
tooltip,
},
mixins: [
timeagoMixin,
],
props: {
deployment: {
type: Object,
required: true,
},
},
data() {
return {
isStopping: false,
};
},
computed: {
deployTimeago() {
return this.timeFormated(this.deployment.deployed_at);
},
hasExternalUrls() {
return !!(this.deployment.external_url && this.deployment.external_url_formatted);
},
hasDeploymentTime() {
return !!(this.deployment.deployed_at && this.deployment.deployed_at_formatted);
},
hasDeploymentMeta() {
return !!(this.deployment.url && this.deployment.name);
},
hasMetrics() {
return !!(this.deployment.metrics_url);
},
},
methods: {
stopEnvironment() {
const msg = 'Are you sure you want to stop this environment?';
const isConfirmed = confirm(msg); // eslint-disable-line
if (isConfirmed) {
this.isStopping = true;
MRWidgetService.stopEnvironment(this.deployment.stop_url)
.then(res => res.data)
.then((data) => {
if (data.redirect_url) {
visitUrl(data.redirect_url);
}
this.isStopping = false;
})
.catch(() => {
createFlash('Something went wrong while stopping this environment. Please try again.');
this.isStopping = false;
});
}
},
},
};
</script>
<template>
<div class="mr-widget-heading deploy-heading">
<div class="ci-widget media">
<div class="ci-status-icon ci-status-icon-success">
<span class="js-icon-link icon-link">
<status-icon status="success" />
</span>
</div>
<div class="media-body">
<div class="deploy-body">
<template v-if="hasDeploymentMeta">
<span>
Deployed to
</span>
<a
:href="deployment.url"
target="_blank"
rel="noopener noreferrer nofollow"
class="deploy-link js-deploy-meta"
>
{{ deployment.name }}
</a>
</template>
<template v-if="hasExternalUrls">
<span>
on
</span>
<a
:href="deployment.external_url"
target="_blank"
rel="noopener noreferrer nofollow"
class="deploy-link js-deploy-url"
>
<i
class="fa fa-external-link"
aria-hidden="true"
>
</i>
{{ deployment.external_url_formatted }}
</a>
</template>
<span
v-if="hasDeploymentTime"
v-tooltip
:title="deployment.deployed_at_formatted"
class="js-deploy-time"
>
{{ deployTimeago }}
</span>
<loading-button
v-if="deployment.stop_url"
container-class="btn btn-default btn-xs prepend-left-default"
label="Stop environment"
:loading="isStopping"
@click="stopEnvironment"
/>
</div>
<memory-usage
v-if="hasMetrics"
:metrics-url="deployment.metrics_url"
:metrics-monitoring-url="deployment.metrics_monitoring_url"
/>
</div>
</div>
</div>
</template>
import { getTimeago } from '~/lib/utils/datetime_utility';
import { visitUrl } from '../../lib/utils/url_utility';
import Flash from '../../flash';
import MemoryUsage from './memory_usage.vue';
import StatusIcon from './mr_widget_status_icon.vue';
import MRWidgetService from '../services/mr_widget_service';
export default {
name: 'MRWidgetDeployment',
props: {
mr: { type: Object, required: true },
service: { type: Object, required: true },
},
components: {
MemoryUsage,
StatusIcon,
},
methods: {
formatDate(date) {
return getTimeago().format(date);
},
hasExternalUrls(deployment = {}) {
return deployment.external_url && deployment.external_url_formatted;
},
hasDeploymentTime(deployment = {}) {
return deployment.deployed_at && deployment.deployed_at_formatted;
},
hasDeploymentMeta(deployment = {}) {
return deployment.url && deployment.name;
},
stopEnvironment(deployment) {
const msg = 'Are you sure you want to stop this environment?';
const isConfirmed = confirm(msg); // eslint-disable-line
if (isConfirmed) {
MRWidgetService.stopEnvironment(deployment.stop_url)
.then(res => res.data)
.then((data) => {
if (data.redirect_url) {
visitUrl(data.redirect_url);
}
})
.catch(() => {
new Flash('Something went wrong while stopping this environment. Please try again.'); // eslint-disable-line
});
}
},
},
template: `
<div class="mr-widget-heading deploy-heading">
<div v-for="deployment in mr.deployments">
<div class="ci-widget media">
<div class="ci-status-icon ci-status-icon-success">
<span class="js-icon-link icon-link">
<status-icon status="success" />
</span>
</div>
<div class="media-body space-children">
<span>
<span
v-if="hasDeploymentMeta(deployment)">
Deployed to
</span>
<a
v-if="hasDeploymentMeta(deployment)"
:href="deployment.url"
target="_blank"
rel="noopener noreferrer nofollow"
class="js-deploy-meta inline">
{{deployment.name}}
</a>
<span
v-if="hasExternalUrls(deployment)">
on
</span>
<a
v-if="hasExternalUrls(deployment)"
:href="deployment.external_url"
target="_blank"
rel="noopener noreferrer nofollow"
class="js-deploy-url inline">
<i
class="fa fa-external-link"
aria-hidden="true" />
{{deployment.external_url_formatted}}
</a>
<span
v-if="hasDeploymentTime(deployment)"
:data-title="deployment.deployed_at_formatted"
class="js-deploy-time"
data-toggle="tooltip"
data-placement="top">
{{formatDate(deployment.deployed_at)}}
</span>
</span>
<button
type="button"
v-if="deployment.stop_url"
@click="stopEnvironment(deployment)"
class="btn btn-default btn-xs">
Stop environment
</button>
<memory-usage
v-if="deployment.metrics_url"
:metrics-url="deployment.metrics_url"
:metrics-monitoring-url="deployment.metrics_monitoring_url"
/>
</div>
</div>
</div>
</div>
`,
};
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetUnresolvedDiscussions',
props: {
mr: { type: Object, required: true },
},
components: {
statusIcon,
},
template: `
<div class="mr-widget-body media">
<status-icon status="warning" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
There are unresolved discussions. Please resolve these discussions
</span>
<a
v-if="mr.createIssueToResolveDiscussionsPath"
:href="mr.createIssueToResolveDiscussionsPath"
class="btn btn-default btn-xs js-create-issue">
Create an issue to resolve them later
</a>
</div>
</div>
`,
};
<script>
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'UnresolvedDiscussions',
components: {
statusIcon,
},
props: {
mr: { type: Object, required: true },
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="warning"
:show-disabled-button="true"
/>
<div class="media-body space-children">
<span class="bold">
There are unresolved discussions. Please resolve these discussions
</span>
<a
v-if="mr.createIssueToResolveDiscussionsPath"
:href="mr.createIssueToResolveDiscussionsPath"
class="btn btn-default btn-xs js-create-issue">
Create an issue to resolve them later
</a>
</div>
</div>
</template>
...@@ -14,7 +14,7 @@ export { default as SmartInterval } from '~/smart_interval'; ...@@ -14,7 +14,7 @@ export { default as SmartInterval } from '~/smart_interval';
export { default as WidgetHeader } from './components/mr_widget_header.vue'; export { default as WidgetHeader } from './components/mr_widget_header.vue';
export { default as WidgetMergeHelp } from './components/mr_widget_merge_help.vue'; export { default as WidgetMergeHelp } from './components/mr_widget_merge_help.vue';
export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue'; export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue';
export { default as WidgetDeployment } from './components/mr_widget_deployment'; export { default as Deployment } from './components/deployment.vue';
export { default as WidgetMaintainerEdit } from './components/mr_widget_maintainer_edit.vue'; export { default as WidgetMaintainerEdit } from './components/mr_widget_maintainer_edit.vue';
export { default as WidgetRelatedLinks } from './components/mr_widget_related_links.vue'; export { default as WidgetRelatedLinks } from './components/mr_widget_related_links.vue';
export { default as MergedState } from './components/states/mr_widget_merged.vue'; export { default as MergedState } from './components/states/mr_widget_merged.vue';
...@@ -29,7 +29,7 @@ export { default as MissingBranchState } from './components/states/mr_widget_mis ...@@ -29,7 +29,7 @@ export { default as MissingBranchState } from './components/states/mr_widget_mis
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue'; export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue';
export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge'; export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge';
export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch'; export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch';
export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions'; export { default as UnresolvedDiscussionsState } from './components/states/unresolved_discussions.vue';
export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue'; export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue';
export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed'; export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed';
export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds.vue'; export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds.vue';
......
...@@ -5,7 +5,7 @@ import { ...@@ -5,7 +5,7 @@ import {
WidgetHeader, WidgetHeader,
WidgetMergeHelp, WidgetMergeHelp,
WidgetPipeline, WidgetPipeline,
WidgetDeployment, Deployment,
WidgetMaintainerEdit, WidgetMaintainerEdit,
WidgetRelatedLinks, WidgetRelatedLinks,
MergedState, MergedState,
...@@ -67,9 +67,6 @@ export default { ...@@ -67,9 +67,6 @@ export default {
shouldRenderRelatedLinks() { shouldRenderRelatedLinks() {
return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState; return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState;
}, },
shouldRenderDeployments() {
return this.mr.deployments.length;
},
shouldRenderSourceBranchRemovalStatus() { shouldRenderSourceBranchRemovalStatus() {
return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch && return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch &&
(!this.mr.isNothingToMergeState && !this.mr.isMergedState); (!this.mr.isNothingToMergeState && !this.mr.isMergedState);
...@@ -216,7 +213,7 @@ export default { ...@@ -216,7 +213,7 @@ export default {
'mr-widget-header': WidgetHeader, 'mr-widget-header': WidgetHeader,
'mr-widget-merge-help': WidgetMergeHelp, 'mr-widget-merge-help': WidgetMergeHelp,
'mr-widget-pipeline': WidgetPipeline, 'mr-widget-pipeline': WidgetPipeline,
'mr-widget-deployment': WidgetDeployment, Deployment,
'mr-widget-maintainer-edit': WidgetMaintainerEdit, 'mr-widget-maintainer-edit': WidgetMaintainerEdit,
'mr-widget-related-links': WidgetRelatedLinks, 'mr-widget-related-links': WidgetRelatedLinks,
'mr-widget-merged': MergedState, 'mr-widget-merged': MergedState,
...@@ -250,10 +247,11 @@ export default { ...@@ -250,10 +247,11 @@ export default {
:ci-status="mr.ciStatus" :ci-status="mr.ciStatus"
:has-ci="mr.hasCI" :has-ci="mr.hasCI"
/> />
<mr-widget-deployment <deployment
v-if="shouldRenderDeployments" v-for="deployment in mr.deployments"
:mr="mr" :key="deployment.id"
:service="service" /> :deployment="deployment"
/>
<div class="mr-widget-section"> <div class="mr-widget-section">
<component <component
:is="componentName" :is="componentName"
......
...@@ -9,7 +9,8 @@ ...@@ -9,7 +9,8 @@
padding-left: $contextual-sidebar-width; padding-left: $contextual-sidebar-width;
} }
.issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header { .issues-bulk-update.right-sidebar.right-sidebar-expanded
.issuable-sidebar-header {
padding: 10px 0 15px; padding: 10px 0 15px;
} }
} }
...@@ -61,7 +62,8 @@ ...@@ -61,7 +62,8 @@
} }
.nav-sidebar { .nav-sidebar {
transition: width $sidebar-transition-duration, left $sidebar-transition-duration; transition: width $sidebar-transition-duration,
left $sidebar-transition-duration;
position: fixed; position: fixed;
z-index: 400; z-index: 400;
width: $contextual-sidebar-width; width: $contextual-sidebar-width;
...@@ -234,7 +236,7 @@ ...@@ -234,7 +236,7 @@
border-radius: 0 3px 3px 0; border-radius: 0 3px 3px 0;
&::before { &::before {
content: ""; content: '';
position: absolute; position: absolute;
top: -30px; top: -30px;
bottom: -30px; bottom: -30px;
...@@ -305,7 +307,6 @@ ...@@ -305,7 +307,6 @@
} }
} }
// Collapsed nav // Collapsed nav
.toggle-sidebar-button, .toggle-sidebar-button,
...@@ -454,18 +455,3 @@ ...@@ -454,18 +455,3 @@
z-index: 300; z-index: 300;
} }
} }
// Make issue boards full-height now that sub-nav is gone
.boards-list {
height: calc(100vh - #{$header-height});
@media (min-width: $screen-sm-min) {
height: calc(100vh - 180px);
}
}
.with-performance-bar .boards-list {
height: calc(100vh - #{$header-height} - #{$performance-bar-height});
}
...@@ -184,7 +184,6 @@ ...@@ -184,7 +184,6 @@
} }
.container-fluid { .container-fluid {
.navbar-nav { .navbar-nav {
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
display: -webkit-flex; display: -webkit-flex;
...@@ -337,7 +336,7 @@ ...@@ -337,7 +336,7 @@
.breadcrumbs { .breadcrumbs {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
min-height: 48px; min-height: $breadcrumb-min-height;
color: $gl-text-color; color: $gl-text-color;
} }
...@@ -466,7 +465,7 @@ ...@@ -466,7 +465,7 @@
padding: 0 5px; padding: 0 5px;
line-height: 12px; line-height: 12px;
border-radius: 7px; border-radius: 7px;
box-shadow: 0 1px 0 rgba($gl-header-color, .2); box-shadow: 0 1px 0 rgba($gl-header-color, 0.2);
&.issues-count { &.issues-count {
background-color: $green-500; background-color: $green-500;
......
...@@ -17,8 +17,6 @@ ...@@ -17,8 +17,6 @@
*/ */
@mixin markdown-table { @mixin markdown-table {
width: auto; width: auto;
display: block;
overflow-x: auto;
} }
/* /*
......
...@@ -5,9 +5,9 @@ $grid-size: 8px; ...@@ -5,9 +5,9 @@ $grid-size: 8px;
$gutter_collapsed_width: 62px; $gutter_collapsed_width: 62px;
$gutter_width: 290px; $gutter_width: 290px;
$gutter_inner_width: 250px; $gutter_inner_width: 250px;
$sidebar-transition-duration: .3s; $sidebar-transition-duration: 0.3s;
$sidebar-breakpoint: 1024px; $sidebar-breakpoint: 1024px;
$default-transition-duration: .15s; $default-transition-duration: 0.15s;
$contextual-sidebar-width: 220px; $contextual-sidebar-width: 220px;
$contextual-sidebar-collapsed-width: 50px; $contextual-sidebar-collapsed-width: 50px;
...@@ -129,7 +129,6 @@ $theme-green-800: #145d33; ...@@ -129,7 +129,6 @@ $theme-green-800: #145d33;
$theme-green-900: #0d4524; $theme-green-900: #0d4524;
$theme-green-950: #072d16; $theme-green-950: #072d16;
$black: #000; $black: #000;
$black-transparent: rgba(0, 0, 0, 0.3); $black-transparent: rgba(0, 0, 0, 0.3);
$almost-black: #242424; $almost-black: #242424;
...@@ -163,7 +162,7 @@ $gl-text-color-secondary: #707070; ...@@ -163,7 +162,7 @@ $gl-text-color-secondary: #707070;
$gl-text-color-tertiary: #949494; $gl-text-color-tertiary: #949494;
$gl-text-color-quaternary: #d6d6d6; $gl-text-color-quaternary: #d6d6d6;
$gl-text-color-inverted: rgba(255, 255, 255, 1); $gl-text-color-inverted: rgba(255, 255, 255, 1);
$gl-text-color-secondary-inverted: rgba(255, 255, 255, .85); $gl-text-color-secondary-inverted: rgba(255, 255, 255, 0.85);
$gl-text-color-disabled: #919191; $gl-text-color-disabled: #919191;
$gl-text-green: $green-600; $gl-text-green: $green-600;
$gl-text-green-hover: $green-700; $gl-text-green-hover: $green-700;
...@@ -262,6 +261,7 @@ $highlight-changes-color: rgb(235, 255, 232); ...@@ -262,6 +261,7 @@ $highlight-changes-color: rgb(235, 255, 232);
$performance-bar-height: 35px; $performance-bar-height: 35px;
$flash-height: 52px; $flash-height: 52px;
$context-header-height: 60px; $context-header-height: 60px;
$breadcrumb-min-height: 48px;
/* /*
* Common component specific colors * Common component specific colors
...@@ -296,7 +296,7 @@ $tanuki-yellow: #fca326; ...@@ -296,7 +296,7 @@ $tanuki-yellow: #fca326;
*/ */
$gl-primary: $blue-500; $gl-primary: $blue-500;
$gl-success: $green-500; $gl-success: $green-500;
$gl-success-focus: rgba($gl-success, .4); $gl-success-focus: rgba($gl-success, 0.4);
$gl-info: $blue-500; $gl-info: $blue-500;
$gl-warning: $orange-500; $gl-warning: $orange-500;
$gl-danger: $red-500; $gl-danger: $red-500;
...@@ -331,8 +331,11 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%); ...@@ -331,8 +331,11 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%);
/* /*
* Fonts * Fonts
*/ */
$monospace_font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; $monospace_font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas',
$regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
/* /*
* Dropdowns * Dropdowns
...@@ -343,16 +346,16 @@ $dropdown-max-height: 312px; ...@@ -343,16 +346,16 @@ $dropdown-max-height: 312px;
$dropdown-vertical-offset: 4px; $dropdown-vertical-offset: 4px;
$dropdown-link-color: #555; $dropdown-link-color: #555;
$dropdown-link-hover-bg: $row-hover; $dropdown-link-hover-bg: $row-hover;
$dropdown-empty-row-bg: rgba(#000, .04); $dropdown-empty-row-bg: rgba(#000, 0.04);
$dropdown-border-color: $border-color; $dropdown-border-color: $border-color;
$dropdown-shadow-color: rgba(#000, .1); $dropdown-shadow-color: rgba(#000, 0.1);
$dropdown-divider-color: rgba(#000, .1); $dropdown-divider-color: rgba(#000, 0.1);
$dropdown-title-btn-color: #bfbfbf; $dropdown-title-btn-color: #bfbfbf;
$dropdown-input-color: #555; $dropdown-input-color: #555;
$dropdown-input-fa-color: #c7c7c7; $dropdown-input-fa-color: #c7c7c7;
$dropdown-input-focus-border: $focus-border-color; $dropdown-input-focus-border: $focus-border-color;
$dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4); $dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, 0.4);
$dropdown-loading-bg: rgba(#fff, .6); $dropdown-loading-bg: rgba(#fff, 0.6);
$dropdown-chevron-size: 10px; $dropdown-chevron-size: 10px;
$dropdown-toggle-active-border-color: darken($border-color, 14%); $dropdown-toggle-active-border-color: darken($border-color, 14%);
$dropdown-item-hover-bg: $gray-darker; $dropdown-item-hover-bg: $gray-darker;
...@@ -367,9 +370,9 @@ $dropdown-hover-color: $blue-400; ...@@ -367,9 +370,9 @@ $dropdown-hover-color: $blue-400;
/* /*
* Contextual Sidebar * Contextual Sidebar
*/ */
$link-active-background: rgba(0, 0, 0, .04); $link-active-background: rgba(0, 0, 0, 0.04);
$link-hover-background: rgba(0, 0, 0, .06); $link-hover-background: rgba(0, 0, 0, 0.06);
$inactive-badge-background: rgba(0, 0, 0, .08); $inactive-badge-background: rgba(0, 0, 0, 0.08);
/* /*
* Buttons * Buttons
...@@ -397,14 +400,14 @@ $status-icon-margin: $gl-btn-padding; ...@@ -397,14 +400,14 @@ $status-icon-margin: $gl-btn-padding;
/* /*
* Award emoji * Award emoji
*/ */
$award-emoji-menu-shadow: rgba(0, 0, 0, .175); $award-emoji-menu-shadow: rgba(0, 0, 0, 0.175);
$award-emoji-positive-add-bg: #fed159; $award-emoji-positive-add-bg: #fed159;
$award-emoji-positive-add-lines: #bb9c13; $award-emoji-positive-add-lines: #bb9c13;
/* /*
* Search Box * Search Box
*/ */
$search-input-border-color: rgba($blue-400, .8); $search-input-border-color: rgba($blue-400, 0.8);
$search-input-focus-shadow-color: $dropdown-input-focus-shadow; $search-input-focus-shadow-color: $dropdown-input-focus-shadow;
$search-input-width: 220px; $search-input-width: 220px;
$location-badge-active-bg: $blue-500; $location-badge-active-bg: $blue-500;
...@@ -429,7 +432,7 @@ $zen-control-color: #555; ...@@ -429,7 +432,7 @@ $zen-control-color: #555;
* Calendar * Calendar
*/ */
$calendar-hover-bg: #ecf3fe; $calendar-hover-bg: #ecf3fe;
$calendar-border-color: rgba(#000, .1); $calendar-border-color: rgba(#000, 0.1);
$calendar-user-contrib-text: #959494; $calendar-user-contrib-text: #959494;
/* /*
...@@ -452,6 +455,17 @@ $ci-skipped-color: #888; ...@@ -452,6 +455,17 @@ $ci-skipped-color: #888;
*/ */
$issue-boards-font-size: 14px; $issue-boards-font-size: 14px;
$issue-boards-card-shadow: rgba(186, 186, 186, 0.5); $issue-boards-card-shadow: rgba(186, 186, 186, 0.5);
/*
The following heights are used in boards.scss and are used for calculation of the board height.
They probably should be derived in a smarter way.
*/
$issue-boards-filter-height: 68px;
$issue-boards-breadcrumbs-height-xs: 63px;
$issue-board-list-difference-xs: $header-height +
$issue-boards-breadcrumbs-height-xs;
$issue-board-list-difference-sm: $header-height + $breadcrumb-min-height;
$issue-board-list-difference-md: $issue-board-list-difference-sm +
$issue-boards-filter-height;
/* /*
* Avatar * Avatar
...@@ -567,14 +581,14 @@ $label-padding: 7px; ...@@ -567,14 +581,14 @@ $label-padding: 7px;
$label-padding-modal: 10px; $label-padding-modal: 10px;
$label-gray-bg: #f8fafc; $label-gray-bg: #f8fafc;
$label-inverse-bg: #333; $label-inverse-bg: #333;
$label-remove-border: rgba(0, 0, 0, .1); $label-remove-border: rgba(0, 0, 0, 0.1);
$label-border-radius: 100px; $label-border-radius: 100px;
/* /*
* Animation * Animation
*/ */
$fade-in-duration: 200ms; $fade-in-duration: 200ms;
$fade-mask-transition-duration: .1s; $fade-mask-transition-duration: 0.1s;
$fade-mask-transition-curve: ease-in-out; $fade-mask-transition-curve: ease-in-out;
/* /*
...@@ -642,7 +656,6 @@ $stat-graph-selection-stroke: #333; ...@@ -642,7 +656,6 @@ $stat-graph-selection-stroke: #333;
$select2-drop-shadow1: rgba(76, 86, 103, 0.247059); $select2-drop-shadow1: rgba(76, 86, 103, 0.247059);
$select2-drop-shadow2: rgba(31, 37, 50, 0.317647); $select2-drop-shadow2: rgba(31, 37, 50, 0.317647);
/* /*
* Todo * Todo
*/ */
...@@ -679,7 +692,6 @@ CI variable lists ...@@ -679,7 +692,6 @@ CI variable lists
*/ */
$ci-variable-remove-button-width: calc(1em + #{2 * $gl-padding}); $ci-variable-remove-button-width: calc(1em + #{2 * $gl-padding});
/* /*
Filtered Search Filtered Search
*/ */
...@@ -706,7 +718,14 @@ Repo editor ...@@ -706,7 +718,14 @@ Repo editor
*/ */
$repo-editor-grey: #f6f7f9; $repo-editor-grey: #f6f7f9;
$repo-editor-grey-darker: #e9ebee; $repo-editor-grey-darker: #e9ebee;
$repo-editor-linear-gradient: linear-gradient(to right, $repo-editor-grey 0%, $repo-editor-grey-darker, 20%, $repo-editor-grey 40%, $repo-editor-grey 100%); $repo-editor-linear-gradient: linear-gradient(
to right,
$repo-editor-grey 0%,
$repo-editor-grey-darker,
20%,
$repo-editor-grey 40%,
$repo-editor-grey 100%
);
/* /*
Performance Bar Performance Bar
...@@ -717,8 +736,8 @@ $perf-bar-staging: #291430; ...@@ -717,8 +736,8 @@ $perf-bar-staging: #291430;
$perf-bar-development: #4c1210; $perf-bar-development: #4c1210;
$perf-bar-bucket-bg: #111; $perf-bar-bucket-bg: #111;
$perf-bar-bucket-color: #ccc; $perf-bar-bucket-color: #ccc;
$perf-bar-bucket-box-shadow-from: rgba($white-light, .2); $perf-bar-bucket-box-shadow-from: rgba($white-light, 0.2);
$perf-bar-bucket-box-shadow-to: rgba($black, .25); $perf-bar-bucket-box-shadow-to: rgba($black, 0.25);
/* /*
Issuable warning Issuable warning
......
@import "./issues/issue_count_badge"; @import './issues/issue_count_badge';
[v-cloak] { [v-cloak] {
display: none; display: none;
...@@ -72,22 +72,37 @@ ...@@ -72,22 +72,37 @@
} }
.boards-list { .boards-list {
height: calc(100vh - 105px); height: calc(100vh - #{$issue-board-list-difference-xs});
width: 100%; width: 100%;
padding-top: 25px; padding: $gl-padding ($gl-padding / 2);
padding-bottom: 25px;
padding-right: ($gl-padding / 2);
padding-left: ($gl-padding / 2);
overflow-x: scroll; overflow-x: scroll;
white-space: nowrap; white-space: nowrap;
min-height: 200px;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
height: calc(100vh - #{$issue-board-list-difference-sm});
}
@media (min-width: $screen-md-min) {
height: calc(100vh - #{$issue-board-list-difference-md});
}
.with-performance-bar & {
height: calc(
100vh - #{$issue-board-list-difference-xs} - #{$performance-bar-height}
);
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
height: calc(100vh - 90px); height: calc(
100vh - #{$issue-board-list-difference-sm} - #{$performance-bar-height}
);
} }
@media (min-width: $screen-md-min) { @media (min-width: $screen-md-min) {
height: calc(100vh - 160px); height: calc(
min-height: 475px; 100vh - #{$issue-board-list-difference-md} - #{$performance-bar-height}
);
}
} }
} }
...@@ -473,7 +488,7 @@ ...@@ -473,7 +488,7 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
background-color: rgba($black, .3); background-color: rgba($black, 0.3);
z-index: 9999; z-index: 9999;
} }
...@@ -490,7 +505,7 @@ ...@@ -490,7 +505,7 @@
padding: 25px 15px 0; padding: 25px 15px 0;
background-color: $white-light; background-color: $white-light;
border-radius: $border-radius-default; border-radius: $border-radius-default;
box-shadow: 0 2px 12px rgba($black, .5); box-shadow: 0 2px 12px rgba($black, 0.5);
.empty-state { .empty-state {
display: -webkit-flex; display: -webkit-flex;
...@@ -568,7 +583,7 @@ ...@@ -568,7 +583,7 @@
.card { .card {
border: 1px solid $border-gray-dark; border: 1px solid $border-gray-dark;
box-shadow: 0 1px 2px rgba($issue-boards-card-shadow, .3); box-shadow: 0 1px 2px rgba($issue-boards-card-shadow, 0.3);
cursor: pointer; cursor: pointer;
} }
} }
......
...@@ -194,8 +194,6 @@ ...@@ -194,8 +194,6 @@
.commit-actions { .commit-actions {
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
font-size: 0;
.fa-spinner { .fa-spinner {
font-size: 12px; font-size: 12px;
} }
...@@ -204,7 +202,7 @@ ...@@ -204,7 +202,7 @@
.ci-status-link { .ci-status-link {
display: inline-block; display: inline-block;
position: relative; position: relative;
top: 1px; top: 2px;
} }
.btn-clipboard, .btn-clipboard,
...@@ -226,7 +224,7 @@ ...@@ -226,7 +224,7 @@
.ci-status-icon { .ci-status-icon {
position: relative; position: relative;
top: 1px; top: 2px;
} }
} }
......
...@@ -718,6 +718,8 @@ ...@@ -718,6 +718,8 @@
} }
.mr-memory-usage { .mr-memory-usage {
width: 100%;
p.usage-info-loading .usage-info-load-spinner { p.usage-info-loading .usage-info-load-spinner {
margin-right: 10px; margin-right: 10px;
font-size: 16px; font-size: 16px;
...@@ -727,3 +729,36 @@ ...@@ -727,3 +729,36 @@
.fork-sprite { .fork-sprite {
margin-right: -5px; margin-right: -5px;
} }
.deploy-heading {
.media-body {
min-width: 0;
}
}
.deploy-body {
display: flex;
flex-wrap: wrap;
@media (min-width: $screen-xs) {
flex-wrap: nowrap;
white-space: nowrap;
}
> *:not(:last-child) {
margin-right: .3em;
}
}
.deploy-link {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 100px;
max-width: 150px;
@media (min-width: $screen-xs) {
min-width: 0;
max-width: 100%;
}
}
...@@ -16,7 +16,7 @@ ul.notes { ...@@ -16,7 +16,7 @@ ul.notes {
.note-created-ago, .note-created-ago,
.note-updated-at { .note-updated-at {
white-space: nowrap; white-space: normal;
} }
.discussion-body { .discussion-body {
......
...@@ -180,6 +180,11 @@ ul.wiki-pages-list.content-list { ...@@ -180,6 +180,11 @@ ul.wiki-pages-list.content-list {
} }
} }
.wiki-holder {
overflow-x: auto;
overflow-y: hidden;
}
.wiki { .wiki {
table { table {
@include markdown-table; @include markdown-table;
......
...@@ -19,6 +19,12 @@ class Projects::DiscussionsController < Projects::ApplicationController ...@@ -19,6 +19,12 @@ class Projects::DiscussionsController < Projects::ApplicationController
render_discussion render_discussion
end end
def show
render json: {
discussion_html: view_to_html_string('discussions/_diff_with_notes', discussion: discussion, expanded: true)
}
end
private private
def render_discussion def render_discussion
......
...@@ -16,8 +16,7 @@ class Admin::ProjectsFinder ...@@ -16,8 +16,7 @@ class Admin::ProjectsFinder
items = by_archived(items) items = by_archived(items)
items = by_personal(items) items = by_personal(items)
items = by_name(items) items = by_name(items)
items = sort(items) sort(items).page(params[:page])
items.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page])
end end
private private
......
...@@ -2,9 +2,4 @@ module JavascriptHelper ...@@ -2,9 +2,4 @@ module JavascriptHelper
def page_specific_javascript_tag(js) def page_specific_javascript_tag(js)
javascript_include_tag asset_path(js) javascript_include_tag asset_path(js)
end end
# deprecated; use webpack_bundle_tag directly instead
def page_specific_javascript_bundle_tag(bundle)
webpack_bundle_tag(bundle)
end
end end
...@@ -347,15 +347,15 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -347,15 +347,15 @@ class ApplicationSetting < ActiveRecord::Base
end end
def home_page_url_column_exists? def home_page_url_column_exists?
ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url) ::Gitlab::Database.cached_column_exists?(:application_settings, :home_page_url)
end end
def help_page_support_url_column_exists? def help_page_support_url_column_exists?
ActiveRecord::Base.connection.column_exists?(:application_settings, :help_page_support_url) ::Gitlab::Database.cached_column_exists?(:application_settings, :help_page_support_url)
end end
def sidekiq_throttling_column_exists? def sidekiq_throttling_column_exists?
ActiveRecord::Base.connection.column_exists?(:application_settings, :sidekiq_throttling_enabled) ::Gitlab::Database.cached_column_exists?(:application_settings, :sidekiq_throttling_enabled)
end end
def domain_whitelist_raw def domain_whitelist_raw
......
...@@ -41,12 +41,12 @@ module Ci ...@@ -41,12 +41,12 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) } scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) } scope :ignore_failures, ->() { where(allow_failure: false) }
scope :with_artifacts, ->() do scope :with_artifacts_archive, ->() do
where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)', where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)',
'', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id')) '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
end end
scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + [:manual]) } scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + [:manual]) }
scope :ref_protected, -> { where(protected: true) } scope :ref_protected, -> { where(protected: true) }
...@@ -252,23 +252,23 @@ module Ci ...@@ -252,23 +252,23 @@ module Ci
# All variables, including those dependent on environment, which could # All variables, including those dependent on environment, which could
# contain unexpanded variables. # contain unexpanded variables.
def variables(environment: persisted_environment) def variables(environment: persisted_environment)
variables = predefined_variables collection = Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables += project.predefined_variables variables.concat(predefined_variables)
variables += pipeline.predefined_variables variables.concat(project.predefined_variables)
variables += runner.predefined_variables if runner variables.concat(pipeline.predefined_variables)
variables += project.container_registry_variables variables.concat(runner.predefined_variables) if runner
variables += project.deployment_variables if has_environment? variables.concat(project.deployment_variables(environment: environment)) if has_environment?
variables += project.auto_devops_variables variables.concat(yaml_variables)
variables += yaml_variables variables.concat(user_variables)
variables += user_variables variables.concat(project.group.secret_variables_for(ref, project)) if project.group
variables += project.group.secret_variables_for(ref, project).map(&:to_runner_variable) if project.group variables.concat(secret_variables(environment: environment))
variables += secret_variables(environment: environment) variables.concat(trigger_request.user_variables) if trigger_request
variables += trigger_request.user_variables if trigger_request variables.concat(pipeline.variables)
variables += pipeline.variables.map(&:to_runner_variable) variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline.pipeline_schedule
variables += pipeline.pipeline_schedule.job_variables if pipeline.pipeline_schedule variables.concat(persisted_environment_variables) if environment
variables += persisted_environment_variables if environment end
variables collection.to_runner_variables
end end
def features def features
...@@ -430,14 +430,14 @@ module Ci ...@@ -430,14 +430,14 @@ module Ci
end end
def user_variables def user_variables
return [] if user.blank? Gitlab::Ci::Variables::Collection.new.tap do |variables|
return variables if user.blank?
[ variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s)
{ key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, variables.append(key: 'GITLAB_USER_EMAIL', value: user.email)
{ key: 'GITLAB_USER_EMAIL', value: user.email, public: true }, variables.append(key: 'GITLAB_USER_LOGIN', value: user.username)
{ key: 'GITLAB_USER_LOGIN', value: user.username, public: true }, variables.append(key: 'GITLAB_USER_NAME', value: user.name)
{ key: 'GITLAB_USER_NAME', value: user.name, public: true } end
]
end end
def secret_variables(environment: persisted_environment) def secret_variables(environment: persisted_environment)
...@@ -540,60 +540,57 @@ module Ci ...@@ -540,60 +540,57 @@ module Ci
CI_REGISTRY_USER = 'gitlab-ci-token'.freeze CI_REGISTRY_USER = 'gitlab-ci-token'.freeze
def predefined_variables def predefined_variables
variables = [ Gitlab::Ci::Variables::Collection.new.tap do |variables|
{ key: 'CI', value: 'true', public: true }, variables.append(key: 'CI', value: 'true')
{ key: 'GITLAB_CI', value: 'true', public: true }, variables.append(key: 'GITLAB_CI', value: 'true')
{ key: 'GITLAB_FEATURES', value: project.namespace.features.join(','), public: true }, variables.append(key: 'GITLAB_FEATURES', value: project.namespace.features.join(','))
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true }, variables.append(key: 'CI_SERVER_NAME', value: 'GitLab')
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true }, variables.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION)
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }, variables.append(key: 'CI_SERVER_REVISION', value: Gitlab::REVISION)
{ key: 'CI_JOB_ID', value: id.to_s, public: true }, variables.append(key: 'CI_JOB_ID', value: id.to_s)
{ key: 'CI_JOB_NAME', value: name, public: true }, variables.append(key: 'CI_JOB_NAME', value: name)
{ key: 'CI_JOB_STAGE', value: stage, public: true }, variables.append(key: 'CI_JOB_STAGE', value: stage)
{ key: 'CI_JOB_TOKEN', value: token, public: false }, variables.append(key: 'CI_JOB_TOKEN', value: token, public: false)
{ key: 'CI_COMMIT_SHA', value: sha, public: true }, variables.append(key: 'CI_COMMIT_SHA', value: sha)
{ key: 'CI_COMMIT_REF_NAME', value: ref, public: true }, variables.append(key: 'CI_COMMIT_REF_NAME', value: ref)
{ key: 'CI_COMMIT_REF_SLUG', value: ref_slug, public: true }, variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug)
{ key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER, public: true }, variables.append(key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER)
{ key: 'CI_REGISTRY_PASSWORD', value: token, public: false }, variables.append(key: 'CI_REGISTRY_PASSWORD', value: token, public: false)
{ key: 'CI_REPOSITORY_URL', value: repo_url, public: false } variables.append(key: 'CI_REPOSITORY_URL', value: repo_url, public: false)
] variables.append(key: "CI_COMMIT_TAG", value: ref) if tag?
variables.append(key: "CI_PIPELINE_TRIGGERED", value: 'true') if trigger_request
variables << { key: "CI_COMMIT_TAG", value: ref, public: true } if tag? variables.append(key: "CI_JOB_MANUAL", value: 'true') if action?
variables << { key: "CI_PIPELINE_TRIGGERED", value: 'true', public: true } if trigger_request
variables << { key: "CI_JOB_MANUAL", value: 'true', public: true } if action?
variables.concat(legacy_variables) variables.concat(legacy_variables)
end end
end
def persisted_environment_variables def persisted_environment_variables
return [] unless persisted_environment Gitlab::Ci::Variables::Collection.new.tap do |variables|
return variables unless persisted_environment
variables = persisted_environment.predefined_variables variables.concat(persisted_environment.predefined_variables)
# Here we're passing unexpanded environment_url for runner to expand, # Here we're passing unexpanded environment_url for runner to expand,
# and we need to make sure that CI_ENVIRONMENT_NAME and # and we need to make sure that CI_ENVIRONMENT_NAME and
# CI_ENVIRONMENT_SLUG so on are available for the URL be expanded. # CI_ENVIRONMENT_SLUG so on are available for the URL be expanded.
variables << { key: 'CI_ENVIRONMENT_URL', value: environment_url, public: true } if environment_url variables.append(key: 'CI_ENVIRONMENT_URL', value: environment_url) if environment_url
end
variables
end end
def legacy_variables def legacy_variables
variables = [ Gitlab::Ci::Variables::Collection.new.tap do |variables|
{ key: 'CI_BUILD_ID', value: id.to_s, public: true }, variables.append(key: 'CI_BUILD_ID', value: id.to_s)
{ key: 'CI_BUILD_TOKEN', value: token, public: false }, variables.append(key: 'CI_BUILD_TOKEN', value: token, public: false)
{ key: 'CI_BUILD_REF', value: sha, public: true }, variables.append(key: 'CI_BUILD_REF', value: sha)
{ key: 'CI_BUILD_BEFORE_SHA', value: before_sha, public: true }, variables.append(key: 'CI_BUILD_BEFORE_SHA', value: before_sha)
{ key: 'CI_BUILD_REF_NAME', value: ref, public: true }, variables.append(key: 'CI_BUILD_REF_NAME', value: ref)
{ key: 'CI_BUILD_REF_SLUG', value: ref_slug, public: true }, variables.append(key: 'CI_BUILD_REF_SLUG', value: ref_slug)
{ key: 'CI_BUILD_NAME', value: name, public: true }, variables.append(key: 'CI_BUILD_NAME', value: name)
{ key: 'CI_BUILD_STAGE', value: stage, public: true } variables.append(key: 'CI_BUILD_STAGE', value: stage)
] variables.append(key: "CI_BUILD_TAG", value: ref) if tag?
variables.append(key: "CI_BUILD_TRIGGERED", value: 'true') if trigger_request
variables << { key: "CI_BUILD_TAG", value: ref, public: true } if tag? variables.append(key: "CI_BUILD_MANUAL", value: 'true') if action?
variables << { key: "CI_BUILD_TRIGGERED", value: 'true', public: true } if trigger_request end
variables << { key: "CI_BUILD_MANUAL", value: 'true', public: true } if action?
variables
end end
def environment_url def environment_url
......
...@@ -473,11 +473,10 @@ module Ci ...@@ -473,11 +473,10 @@ module Ci
end end
def predefined_variables def predefined_variables
[ Gitlab::Ci::Variables::Collection.new
{ key: 'CI_PIPELINE_ID', value: id.to_s, public: true }, .append(key: 'CI_PIPELINE_ID', value: id.to_s)
{ key: 'CI_CONFIG_PATH', value: ci_yaml_file_path, public: true }, .append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path)
{ key: 'CI_PIPELINE_SOURCE', value: source.to_s, public: true } .append(key: 'CI_PIPELINE_SOURCE', value: source.to_s)
]
end end
def queued_duration def queued_duration
...@@ -514,7 +513,7 @@ module Ci ...@@ -514,7 +513,7 @@ module Ci
# We purposely cast the builds to an Array here. Because we always use the # We purposely cast the builds to an Array here. Because we always use the
# rows if there are more than 0 this prevents us from having to run two # rows if there are more than 0 this prevents us from having to run two
# queries: one to get the count and one to get the rows. # queries: one to get the count and one to get the rows.
@latest_builds_with_artifacts ||= builds.latest.with_artifacts.to_a @latest_builds_with_artifacts ||= builds.latest.with_artifacts_archive.to_a
end end
private private
......
...@@ -132,11 +132,10 @@ module Ci ...@@ -132,11 +132,10 @@ module Ci
end end
def predefined_variables def predefined_variables
[ Gitlab::Ci::Variables::Collection.new
{ key: 'CI_RUNNER_ID', value: id.to_s, public: true }, .append(key: 'CI_RUNNER_ID', value: id.to_s)
{ key: 'CI_RUNNER_DESCRIPTION', value: description, public: true }, .append(key: 'CI_RUNNER_DESCRIPTION', value: description)
{ key: 'CI_RUNNER_TAGS', value: tag_list.to_s, public: true } .append(key: 'CI_RUNNER_TAGS', value: tag_list.to_s)
]
end end
def tick_runner_queue def tick_runner_queue
......
...@@ -56,19 +56,19 @@ module Clusters ...@@ -56,19 +56,19 @@ module Clusters
def predefined_variables def predefined_variables
config = YAML.dump(kubeconfig) config = YAML.dump(kubeconfig)
variables = [ Gitlab::Ci::Variables::Collection.new.tap do |variables|
{ key: 'KUBE_URL', value: api_url, public: true }, variables
{ key: 'KUBE_TOKEN', value: token, public: false }, .append(key: 'KUBE_URL', value: api_url)
{ key: 'KUBE_NAMESPACE', value: actual_namespace, public: true }, .append(key: 'KUBE_TOKEN', value: token, public: false)
{ key: 'KUBECONFIG', value: config, public: false, file: true } .append(key: 'KUBE_NAMESPACE', value: actual_namespace)
] .append(key: 'KUBECONFIG', value: config, public: false, file: true)
if ca_pem.present? if ca_pem.present?
variables << { key: 'KUBE_CA_PEM', value: ca_pem, public: true }
variables << { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
end
variables variables
.append(key: 'KUBE_CA_PEM', value: ca_pem)
.append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true)
end
end
end end
# Constructs a list of terminals from the reactive cache # Constructs a list of terminals from the reactive cache
...@@ -134,7 +134,7 @@ module Clusters ...@@ -134,7 +134,7 @@ module Clusters
kubeclient = build_kubeclient! kubeclient = build_kubeclient!
kubeclient.get_pods(namespace: actual_namespace).as_json kubeclient.get_pods(namespace: actual_namespace).as_json
rescue KubeException => err rescue Kubeclient::HttpError => err
raise err unless err.error_code == 404 raise err unless err.error_code == 404
[] []
......
...@@ -7,28 +7,23 @@ module Storage ...@@ -7,28 +7,23 @@ module Storage
raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry') raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry')
end end
expires_full_path_cache parent_was = if parent_changed? && parent_id_was.present?
Namespace.find(parent_id_was) # raise NotFound early if needed
# Move the namespace directory in all storage paths used by member projects end
repository_storage_paths.each do |repository_storage_path|
# Ensure old directory exists before moving it
gitlab_shell.add_namespace(repository_storage_path, full_path_was)
# Ensure new directory exists before moving it (if there's a parent)
gitlab_shell.add_namespace(repository_storage_path, parent.full_path) if parent
unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path)
Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}" expires_full_path_cache
# if we cannot move namespace directory we should rollback move_repositories
# db changes in order to prevent out of sync between db and fs
raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
end
end
if parent_changed?
former_parent_full_path = parent_was&.full_path
parent_full_path = parent&.full_path
Gitlab::UploadsTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
Gitlab::PagesTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
else
Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path) Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path)
Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path) Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
end
remove_exports! remove_exports!
...@@ -57,6 +52,26 @@ module Storage ...@@ -57,6 +52,26 @@ module Storage
private private
def move_repositories
# Move the namespace directory in all storage paths used by member projects
repository_storage_paths.each do |repository_storage_path|
# Ensure old directory exists before moving it
gitlab_shell.add_namespace(repository_storage_path, full_path_was)
# Ensure new directory exists before moving it (if there's a parent)
gitlab_shell.add_namespace(repository_storage_path, parent.full_path) if parent
unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path)
Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}"
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
end
end
end
def old_repository_storage_paths def old_repository_storage_paths
@old_repository_storage_paths ||= repository_storage_paths @old_repository_storage_paths ||= repository_storage_paths
end end
......
...@@ -65,10 +65,9 @@ class Environment < ActiveRecord::Base ...@@ -65,10 +65,9 @@ class Environment < ActiveRecord::Base
end end
def predefined_variables def predefined_variables
[ Gitlab::Ci::Variables::Collection.new
{ key: 'CI_ENVIRONMENT_NAME', value: name, public: true }, .append(key: 'CI_ENVIRONMENT_NAME', value: name)
{ key: 'CI_ENVIRONMENT_SLUG', value: slug, public: true } .append(key: 'CI_ENVIRONMENT_SLUG', value: slug)
]
end end
def recently_updated_on_branch?(ref) def recently_updated_on_branch?(ref)
......
...@@ -55,7 +55,7 @@ class Member < ActiveRecord::Base ...@@ -55,7 +55,7 @@ class Member < ActiveRecord::Base
scope :active_without_invites, -> do scope :active_without_invites, -> do
left_join_users left_join_users
.where(users: { state: 'active' }) .where(users: { state: 'active' })
.where(requested_at: nil) .non_request
.reorder(nil) .reorder(nil)
end end
......
...@@ -579,9 +579,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -579,9 +579,10 @@ class MergeRequest < ActiveRecord::Base
return unless open? return unless open?
old_diff_refs = self.diff_refs old_diff_refs = self.diff_refs
new_diff = create_merge_request_diff
MergeRequests::MergeRequestDiffCacheService.new.execute(self, new_diff)
create_merge_request_diff
MergeRequests::MergeRequestDiffCacheService.new.execute(self)
new_diff_refs = self.diff_refs new_diff_refs = self.diff_refs
update_diff_discussion_positions( update_diff_discussion_positions(
......
...@@ -542,7 +542,7 @@ class Project < ActiveRecord::Base ...@@ -542,7 +542,7 @@ class Project < ActiveRecord::Base
latest_pipeline = pipelines.latest_successful_for(ref) latest_pipeline = pipelines.latest_successful_for(ref)
if latest_pipeline if latest_pipeline
latest_pipeline.builds.latest.with_artifacts latest_pipeline.builds.latest.with_artifacts_archive
else else
builds.none builds.none
end end
...@@ -1083,7 +1083,7 @@ class Project < ActiveRecord::Base ...@@ -1083,7 +1083,7 @@ class Project < ActiveRecord::Base
# Forked import is handled asynchronously # Forked import is handled asynchronously
return if forked? && !force return if forked? && !force
if gitlab_shell.add_repository(repository_storage, disk_path) if gitlab_shell.create_repository(repository_storage, disk_path)
repository.after_create repository.after_create
true true
else else
...@@ -1519,8 +1519,8 @@ class Project < ActiveRecord::Base ...@@ -1519,8 +1519,8 @@ class Project < ActiveRecord::Base
@errors = original_errors @errors = original_errors
end end
def add_export_job(current_user:) def add_export_job(current_user:, params: {})
job_id = ProjectExportWorker.perform_async(current_user.id, self.id) job_id = ProjectExportWorker.perform_async(current_user.id, self.id, params)
if job_id if job_id
Rails.logger.info "Export job started for project ID #{self.id} with job ID #{job_id}" Rails.logger.info "Export job started for project ID #{self.id} with job ID #{job_id}"
...@@ -1572,29 +1572,30 @@ class Project < ActiveRecord::Base ...@@ -1572,29 +1572,30 @@ class Project < ActiveRecord::Base
end end
def predefined_variables def predefined_variables
[ visibility = Gitlab::VisibilityLevel.string_level(visibility_level)
{ key: 'CI_PROJECT_ID', value: id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: path, public: true }, Gitlab::Ci::Variables::Collection.new
{ key: 'CI_PROJECT_PATH', value: full_path, public: true }, .append(key: 'CI_PROJECT_ID', value: id.to_s)
{ key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug, public: true }, .append(key: 'CI_PROJECT_NAME', value: path)
{ key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path, public: true }, .append(key: 'CI_PROJECT_PATH', value: full_path)
{ key: 'CI_PROJECT_URL', value: web_url, public: true }, .append(key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug)
{ key: 'CI_PROJECT_VISIBILITY', value: Gitlab::VisibilityLevel.string_level(visibility_level), public: true } .append(key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path)
] .append(key: 'CI_PROJECT_URL', value: web_url)
.append(key: 'CI_PROJECT_VISIBILITY', value: visibility)
.concat(container_registry_variables)
.concat(auto_devops_variables)
end end
def container_registry_variables def container_registry_variables
return [] unless Gitlab.config.registry.enabled Gitlab::Ci::Variables::Collection.new.tap do |variables|
return variables unless Gitlab.config.registry.enabled
variables = [ variables.append(key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port)
{ key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port, public: true }
]
if container_registry_enabled? if container_registry_enabled?
variables << { key: 'CI_REGISTRY_IMAGE', value: container_registry_url, public: true } variables.append(key: 'CI_REGISTRY_IMAGE', value: container_registry_url)
end
end end
variables
end end
def secret_variables_for(ref:, environment: nil) def secret_variables_for(ref:, environment: nil)
...@@ -1614,16 +1615,14 @@ class Project < ActiveRecord::Base ...@@ -1614,16 +1615,14 @@ class Project < ActiveRecord::Base
end end
end end
def deployment_variables def deployment_variables(environment: nil)
return [] unless deployment_platform deployment_platform(environment: environment)&.predefined_variables || []
deployment_platform.predefined_variables
end end
def auto_devops_variables def auto_devops_variables
return [] unless auto_devops_enabled? return [] unless auto_devops_enabled?
(auto_devops || build_auto_devops)&.variables (auto_devops || build_auto_devops)&.predefined_variables
end end
def append_or_update_attribute(name, value) def append_or_update_attribute(name, value)
......
...@@ -14,9 +14,12 @@ class ProjectAutoDevops < ActiveRecord::Base ...@@ -14,9 +14,12 @@ class ProjectAutoDevops < ActiveRecord::Base
domain.present? || instance_domain.present? domain.present? || instance_domain.present?
end end
def variables def predefined_variables
variables = [] Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables << { key: 'AUTO_DEVOPS_DOMAIN', value: domain.presence || instance_domain, public: true } if has_domain? if has_domain?
variables variables.append(key: 'AUTO_DEVOPS_DOMAIN',
value: domain.presence || instance_domain)
end
end
end end
end end
...@@ -161,11 +161,6 @@ class JiraService < IssueTrackerService ...@@ -161,11 +161,6 @@ class JiraService < IssueTrackerService
add_comment(data, jira_issue) add_comment(data, jira_issue)
end end
# reason why service cannot be tested
def disabled_title
"Please fill in Password and Username."
end
def test(_) def test(_)
result = test_settings result = test_settings
success = result.present? success = result.present?
......
...@@ -105,19 +105,19 @@ class KubernetesService < DeploymentService ...@@ -105,19 +105,19 @@ class KubernetesService < DeploymentService
def predefined_variables def predefined_variables
config = YAML.dump(kubeconfig) config = YAML.dump(kubeconfig)
variables = [ Gitlab::Ci::Variables::Collection.new.tap do |variables|
{ key: 'KUBE_URL', value: api_url, public: true }, variables
{ key: 'KUBE_TOKEN', value: token, public: false }, .append(key: 'KUBE_URL', value: api_url)
{ key: 'KUBE_NAMESPACE', value: actual_namespace, public: true }, .append(key: 'KUBE_TOKEN', value: token, public: false)
{ key: 'KUBECONFIG', value: config, public: false, file: true } .append(key: 'KUBE_NAMESPACE', value: actual_namespace)
] .append(key: 'KUBECONFIG', value: config, public: false, file: true)
if ca_pem.present? if ca_pem.present?
variables << { key: 'KUBE_CA_PEM', value: ca_pem, public: true }
variables << { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
end
variables variables
.append(key: 'KUBE_CA_PEM', value: ca_pem)
.append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true)
end
end
end end
# Constructs a list of terminals from the reactive cache # Constructs a list of terminals from the reactive cache
...@@ -197,7 +197,7 @@ class KubernetesService < DeploymentService ...@@ -197,7 +197,7 @@ class KubernetesService < DeploymentService
kubeclient = build_kubeclient! kubeclient = build_kubeclient!
kubeclient.get_pods(namespace: actual_namespace).as_json kubeclient.get_pods(namespace: actual_namespace).as_json
rescue KubeException => err rescue Kubeclient::HttpError => err
raise err unless err.error_code == 404 raise err unless err.error_code == 404
[] []
......
...@@ -39,10 +39,6 @@ class PipelinesEmailService < Service ...@@ -39,10 +39,6 @@ class PipelinesEmailService < Service
project.pipelines.any? project.pipelines.any?
end end
def disabled_title
'Please setup a pipeline on your repository.'
end
def test_data(project, user) def test_data(project, user)
data = Gitlab::DataBuilder::Pipeline.build(project.pipelines.last) data = Gitlab::DataBuilder::Pipeline.build(project.pipelines.last)
data[:user] = user.hook_attrs data[:user] = user.hook_attrs
......
...@@ -169,7 +169,7 @@ class ProjectWiki ...@@ -169,7 +169,7 @@ class ProjectWiki
private private
def create_repo!(raw_repository) def create_repo!(raw_repository)
gitlab_shell.add_repository(project.repository_storage, disk_path) gitlab_shell.create_repository(project.repository_storage, disk_path)
raise CouldNotCreateWikiError unless raw_repository.exists? raise CouldNotCreateWikiError unless raw_repository.exists?
......
...@@ -162,11 +162,6 @@ class Service < ActiveRecord::Base ...@@ -162,11 +162,6 @@ class Service < ActiveRecord::Base
true true
end end
# reason why service cannot be tested
def disabled_title
"Please setup a project repository."
end
# Provide convenient accessor methods # Provide convenient accessor methods
# for each serialized property. # for each serialized property.
# Also keep track of updated properties in a similar way as ActiveModel::Dirty # Also keep track of updated properties in a similar way as ActiveModel::Dirty
......
...@@ -32,7 +32,7 @@ module Ci ...@@ -32,7 +32,7 @@ module Ci
kubeclient = build_kubeclient! kubeclient = build_kubeclient!
kubeclient.get_secrets.as_json kubeclient.get_secrets.as_json
rescue KubeException => err rescue Kubeclient::HttpError => err
raise err unless err.error_code == 404 raise err unless err.error_code == 404
[] []
......
...@@ -12,7 +12,7 @@ module Clusters ...@@ -12,7 +12,7 @@ module Clusters
else else
check_timeout check_timeout
end end
rescue KubeException => ke rescue Kubeclient::HttpError => ke
app.make_errored!("Kubernetes error: #{ke.message}") unless app.errored? app.make_errored!("Kubernetes error: #{ke.message}") unless app.errored?
end end
......
...@@ -10,7 +10,7 @@ module Clusters ...@@ -10,7 +10,7 @@ module Clusters
ClusterWaitForAppInstallationWorker.perform_in( ClusterWaitForAppInstallationWorker.perform_in(
ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id) ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
rescue KubeException => ke rescue Kubeclient::HttpError => ke
app.make_errored!("Kubernetes error: #{ke.message}") app.make_errored!("Kubernetes error: #{ke.message}")
rescue StandardError rescue StandardError
app.make_errored!("Can't start installation process") app.make_errored!("Can't start installation process")
......
module Files module Files
class CreateService < Files::BaseService class CreateService < Files::BaseService
def create_commit! def create_commit!
handler = Lfs::FileModificationHandler.new(project, @branch_name) transformer = Lfs::FileTransformer.new(project, @branch_name)
handler.new_file(@file_path, @file_content) do |content_or_lfs_pointer| result = transformer.new_file(@file_path, @file_content)
create_transformed_commit(content_or_lfs_pointer)
end create_transformed_commit(result.content)
end end
private private
......
...@@ -3,11 +3,33 @@ module Files ...@@ -3,11 +3,33 @@ module Files
UPDATE_FILE_ACTIONS = %w(update move delete).freeze UPDATE_FILE_ACTIONS = %w(update move delete).freeze
def create_commit! def create_commit!
transformer = Lfs::FileTransformer.new(project, @branch_name)
actions = actions_after_lfs_transformation(transformer, params[:actions])
commit_actions!(actions)
end
private
def actions_after_lfs_transformation(transformer, actions)
actions.map do |action|
if action[:action] == 'create'
result = transformer.new_file(action[:file_path], action[:content], encoding: action[:encoding])
action[:content] = result.content
action[:encoding] = result.encoding
end
action
end
end
def commit_actions!(actions)
repository.multi_action( repository.multi_action(
current_user, current_user,
message: @commit_message, message: @commit_message,
branch_name: @branch_name, branch_name: @branch_name,
actions: params[:actions], actions: actions,
author_email: @author_email, author_email: @author_email,
author_name: @author_name, author_name: @author_name,
start_project: @start_project, start_project: @start_project,
...@@ -17,8 +39,6 @@ module Files ...@@ -17,8 +39,6 @@ module Files
raise_error(e) raise_error(e)
end end
private
def validate! def validate!
super super
......
module Lfs module Lfs
class FileModificationHandler # Usage: Calling `new_file` check to see if a file should be in LFS and
# return a transformed result with `content` and `encoding` to commit.
#
# For LFS an LfsObject linked to the project is stored and an LFS
# pointer returned. If the file isn't in LFS the untransformed content
# is returned to save in the commit.
#
# transformer = Lfs::FileTransformer.new(project, @branch_name)
# content_or_lfs_pointer = transformer.new_file(file_path, content).content
# create_transformed_commit(content_or_lfs_pointer)
#
class FileTransformer
attr_reader :project, :branch_name attr_reader :project, :branch_name
delegate :repository, to: :project delegate :repository, to: :project
...@@ -9,24 +20,37 @@ module Lfs ...@@ -9,24 +20,37 @@ module Lfs
@branch_name = branch_name @branch_name = branch_name
end end
def new_file(file_path, file_content) def new_file(file_path, file_content, encoding: nil)
if project.lfs_enabled? && lfs_file?(file_path) if project.lfs_enabled? && lfs_file?(file_path)
file_content = Base64.decode64(file_content) if encoding == 'base64'
lfs_pointer_file = Gitlab::Git::LfsPointerFile.new(file_content) lfs_pointer_file = Gitlab::Git::LfsPointerFile.new(file_content)
lfs_object = create_lfs_object!(lfs_pointer_file, file_content) lfs_object = create_lfs_object!(lfs_pointer_file, file_content)
content = lfs_pointer_file.pointer
success = yield(content) link_lfs_object!(lfs_object)
link_lfs_object!(lfs_object) if success Result.new(content: lfs_pointer_file.pointer, encoding: 'text')
else else
yield(file_content) Result.new(content: file_content, encoding: encoding)
end
end
class Result
attr_reader :content, :encoding
def initialize(content:, encoding:)
@content = content
@encoding = encoding
end end
end end
private private
def lfs_file?(file_path) def lfs_file?(file_path)
repository.attributes_at(branch_name, file_path)['filter'] == 'lfs' cached_attributes.attributes(file_path)['filter'] == 'lfs'
end
def cached_attributes
@cached_attributes ||= Gitlab::Git::AttributesAtRefParser.new(repository, branch_name)
end end
def create_lfs_object!(lfs_pointer_file, file_content) def create_lfs_object!(lfs_pointer_file, file_content)
......
module MergeRequests module MergeRequests
class MergeRequestDiffCacheService class MergeRequestDiffCacheService
def execute(merge_request) def execute(merge_request, new_diff)
# Executing the iteration we cache all the highlighted diff information # Executing the iteration we cache all the highlighted diff information
merge_request.diffs.diff_files.to_a merge_request.diffs.diff_files.to_a
# Remove cache for all diffs on this MR. Do not use the association on the
# model, as that will interfere with other actions happening when
# reloading the diff.
MergeRequestDiff.where(merge_request: merge_request).each do |merge_request_diff|
next if merge_request_diff == new_diff
merge_request_diff.diffs.clear_cache!
end
end end
end end
end end
...@@ -208,9 +208,9 @@ class NotificationService ...@@ -208,9 +208,9 @@ class NotificationService
def new_access_request(member) def new_access_request(member)
return true unless member.notifiable?(:subscription) return true unless member.notifiable?(:subscription)
recipients = member.source.members.owners_and_masters recipients = member.source.members.active_without_invites.owners_and_masters
if fallback_to_group_owners_masters?(recipients, member) if fallback_to_group_owners_masters?(recipients, member)
recipients = member.source.group.members.owners_and_masters recipients = member.source.group.members.active_without_invites.owners_and_masters
end end
recipients.each { |recipient| deliver_access_request_email(recipient, member) } recipients.each { |recipient| deliver_access_request_email(recipient, member) }
......
...@@ -26,7 +26,7 @@ module Projects ...@@ -26,7 +26,7 @@ module Projects
end end
def project_tree_saver def project_tree_saver
Gitlab::ImportExport::ProjectTreeSaver.new(project: project, current_user: @current_user, shared: @shared) Gitlab::ImportExport::ProjectTreeSaver.new(project: project, current_user: @current_user, shared: @shared, params: @params)
end end
def uploads_saver def uploads_saver
......
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
%span.runner-state.runner-state-specific %span.runner-state.runner-state-specific
Specific Specific
- add_to_breadcrumbs _("Runners"), admin_runners_path
- breadcrumb_title "##{@runner.id}"
- @no_container = true
- if @runner.shared? - if @runner.shared?
.bs-callout.bs-callout-success .bs-callout.bs-callout-success
%h4 This Runner will process jobs from ALL UNASSIGNED projects %h4 This Runner will process jobs from ALL UNASSIGNED projects
......
...@@ -2,8 +2,12 @@ ...@@ -2,8 +2,12 @@
- blob = discussion.blob - blob = discussion.blob
- discussions = { discussion.original_line_code => [discussion] } - discussions = { discussion.original_line_code => [discussion] }
- diff_file_class = diff_file.text? ? 'text-file' : 'js-image-file' - diff_file_class = diff_file.text? ? 'text-file' : 'js-image-file'
- diff_data = {}
- expanded = discussion.expanded? || local_assigns.fetch(:expanded, nil)
- unless expanded
- diff_data = { lines_path: project_merge_request_discussion_path(discussion.project, discussion.noteable, discussion) }
.diff-file.file-holder{ class: diff_file_class } .diff-file.file-holder{ class: diff_file_class, data: diff_data }
.js-file-title.file-title.file-title-flex-parent .js-file-title.file-title.file-title-flex-parent
.file-header-content .file-header-content
= render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false = render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false
...@@ -11,6 +15,8 @@ ...@@ -11,6 +15,8 @@
- if diff_file.text? - if diff_file.text?
.diff-content.code.js-syntax-highlight .diff-content.code.js-syntax-highlight
%table %table
- if expanded
- discussions = { discussion.original_line_code => [discussion] }
= render partial: "projects/diffs/line", = render partial: "projects/diffs/line",
collection: discussion.truncated_diff_lines, collection: discussion.truncated_diff_lines,
as: :line, as: :line,
...@@ -18,10 +24,15 @@ ...@@ -18,10 +24,15 @@
discussions: discussions, discussions: discussions,
discussion_expanded: true, discussion_expanded: true,
plain: true } plain: true }
- else
%tr.line_holder.line-holder-placeholder
%td.old_line.diff-line-num
%td.new_line.diff-line-num
%td.line_content
.js-code-placeholder
= render "discussions/diff_discussion", discussions: [discussion], expanded: true
- else - else
- partial = (diff_file.new_file? || diff_file.deleted_file?) ? 'single_image_diff' : 'replaced_image_diff' - partial = (diff_file.new_file? || diff_file.deleted_file?) ? 'single_image_diff' : 'replaced_image_diff'
= render partial: "projects/diffs/#{partial}", locals: { diff_file: diff_file, position: discussion.position.to_json, click_to_comment: false } = render partial: "projects/diffs/#{partial}", locals: { diff_file: diff_file, position: discussion.position.to_json, click_to_comment: false }
.note-container .note-container
= render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse_class: true } = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse_class: true }
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
.discussion.js-toggle-container{ data: { discussion_id: discussion.id } } .discussion.js-toggle-container{ data: { discussion_id: discussion.id } }
.discussion-header .discussion-header
.discussion-actions .discussion-actions
%button.note-action-button.discussion-toggle-button.js-toggle-button{ type: "button" } %button.note-action-button.discussion-toggle-button.js-toggle-button{ type: "button", class: ("js-toggle-lazy-diff" unless expanded) }
- if expanded - if expanded
= icon("chevron-up") = icon("chevron-up")
- else - else
......
- local_assigns.fetch(:view)
%span.bold
%span{ title: 'Invoke Time', data: { defer_to: "#{view.defer_key}-gc_time" } }...
\/
%span{ title: 'Invoke Count', data: { defer_to: "#{view.defer_key}-invokes" } }...
gc
- local_assigns.fetch(:view) - local_assigns.fetch(:view)
%strong %button.btn-blank.btn-link.bold{ type: 'button', data: { toggle: 'modal', target: '#modal-peek-gitaly-details' } }
%span{ data: { defer_to: "#{view.defer_key}-duration" } } ... %span{ data: { defer_to: "#{view.defer_key}-duration" } }...
\/ \/
%span{ data: { defer_to: "#{view.defer_key}-calls" } } ... %span{ data: { defer_to: "#{view.defer_key}-calls" } }...
Gitaly #modal-peek-gitaly-details.modal{ tabindex: -1, role: 'dialog' }
.modal-dialog.modal-full
.modal-content
.modal-header
%button.close{ type: 'button', data: { dismiss: 'modal' }, 'aria-label' => 'Close' }
%span{ 'aria-hidden' => 'true' }
&times;
%h4
Gitaly requests
.modal-body{ data: { defer_to: "#{view.defer_key}-details" } }...
gitaly
- local_assigns.fetch(:view)
%span.bold
%span{ data: { defer_to: "#{view.defer_key}-duration" } }...
\/
%span{ data: { defer_to: "#{view.defer_key}-calls" } }...
redis
- local_assigns.fetch(:view)
%span.bold
%span{ data: { defer_to: "#{view.defer_key}-duration" } }...
\/
%span{ data: { defer_to: "#{view.defer_key}-calls" } }...
sidekiq
%strong %button.btn-blank.btn-link.bold{ type: 'button', data: { toggle: 'modal', target: '#modal-peek-pg-queries' } }
%a.js-toggle-modal-peek-sql
%span{ data: { defer_to: "#{view.defer_key}-duration" } }... %span{ data: { defer_to: "#{view.defer_key}-duration" } }...
\/ \/
%span{ data: { defer_to: "#{view.defer_key}-calls" } }... %span{ data: { defer_to: "#{view.defer_key}-calls" } }...
...@@ -7,7 +6,9 @@ ...@@ -7,7 +6,9 @@
.modal-dialog.modal-full .modal-dialog.modal-full
.modal-content .modal-content
.modal-header .modal-header
%button.close.btn.btn-link.btn-sm{ type: 'button', data: { dismiss: 'modal' } } X %button.close{ type: 'button', data: { dismiss: 'modal' }, 'aria-label' => 'Close' }
%span{ 'aria-hidden' => 'true' }
&times;
%h4 %h4
SQL queries SQL queries
.modal-body{ data: { defer_to: "#{view.defer_key}-queries" } }... .modal-body{ data: { defer_to: "#{view.defer_key}-queries" } }...
...@@ -15,11 +15,6 @@ ...@@ -15,11 +15,6 @@
.footer-block.row-content-block .footer-block.row-content-block
= service_save_button(@service) = service_save_button(@service)
&nbsp; &nbsp;
- if @service.valid? && @service.activated?
- unless @service.can_test?
- disabled_class = 'disabled'
- disabled_title = @service.disabled_title
= link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel' = link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel'
- if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true) - if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true)
......
...@@ -4,10 +4,11 @@ class ProjectExportWorker ...@@ -4,10 +4,11 @@ class ProjectExportWorker
sidekiq_options retry: 3 sidekiq_options retry: 3
def perform(current_user_id, project_id) def perform(current_user_id, project_id, params = {})
params = params.with_indifferent_access
current_user = User.find(current_user_id) current_user = User.find(current_user_id)
project = Project.find(project_id) project = Project.find(project_id)
::Projects::ImportExport::ExportService.new(project, current_user).execute ::Projects::ImportExport::ExportService.new(project, current_user, params).execute
end end
end end
--- ---
title: Add index on section_name_id on ci_build_trace_sections table title: lazy load diffs on merge request discussions
merge_request: merge_request:
author: author:
type: performance type: performance
---
title: Adds the option to the project export API to override the project description and display GitLab export description once imported
merge_request: 17744
author:
type: added
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment