Commit 83874edb authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'master' into 'update-kubeclient'

 Conflicts:
   Gemfile.lock
parents 53e2987b ea5221ae
...@@ -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'
...@@ -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)
...@@ -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)
...@@ -415,7 +415,7 @@ GEM ...@@ -415,7 +415,7 @@ GEM
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,7 +450,7 @@ GEM ...@@ -454,7 +450,7 @@ 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 (3.0.0) kubeclient (3.0.0)
...@@ -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)
...@@ -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)
...@@ -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,11 +1086,10 @@ DEPENDENCIES ...@@ -1090,11 +1086,10 @@ 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 (~> 3.0) kubeclient (~> 3.0)
letter_opener_web (~> 1.3.0) letter_opener_web (~> 1.3.0)
license_finder (~> 3.1) license_finder (~> 3.1)
...@@ -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,
},
});
},
});
}
}); });
...@@ -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>
`,
};
...@@ -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';
......
...@@ -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"
......
...@@ -17,8 +17,6 @@ ...@@ -17,8 +17,6 @@
*/ */
@mixin markdown-table { @mixin markdown-table {
width: auto; width: auto;
display: block;
overflow-x: auto;
} }
/* /*
......
...@@ -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
......
...@@ -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
......
...@@ -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
......
...@@ -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(
......
...@@ -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
......
...@@ -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
......
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
---
title: Set breadcrumb for admin/runners/show
merge_request: 17431
author: Takuya Noguchi
type: fixed
---
title: Update documentation to reflect current minimum required versions of node and
yarn
merge_request: 17706
author:
type: other
---
title: Add Gitaly call details to performance bar
merge_request:
author:
type: added
---
title: Update ruby-saml to 1.7.2 and omniauth-saml to 1.10.0
merge_request: 17734
author: Takuya Noguchi
type: security
---
title: Fix markdown table showing extra column
merge_request: 17669
author:
type: fixed
--- ---
title: Remove double caching of Repository#empty? title: Fix broken loading state for close issue button
merge_request: merge_request:
author: author:
type: fixed type: fixed
---
title: Update foreman from 0.78.0 to 0.84.0
merge_request: 17690
author: Takuya Noguchi
type: other
---
title: Stop caching highlighted diffs in Redis unnecessarily
merge_request: 17746
author:
type: performance
---
title: Update knapsack to 1.16.0
merge_request: 17735
author: Takuya Noguchi
type: other
---
title: Add slash command for moving issues
merge_request:
author: Adam Pahlevi
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.
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