Commit f39ba1bb authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into pipeline-emails

* upstream/master: (372 commits)
  Enable Lint/StringConversionInInterpolation cop and autocorrect offenses
  resolve duplicated changelog entry
  credit myself 😄
  change determine conditions
  override subject method in devise mailer
  follow the styleguide: Don't use parentheses around a literal
  wrap subject with method subject
  move spec back into shared example `an email sent from GitLab`
  stub config settings in spec
  remove empty line at block body end
  remove extra entry
  create new test in `spec/mailers/notify_spec.rb`
  move changelog to 8.13
  add configurable email subject suffix
  Fixes sidebar navigation.
  Convert "SSH Keys" Spinach features to RSpec
  Enable import/export back for non-admins
  Update gitlab-shell to 3.6.3
  Updated artwork of empty group state.
  Better empty state for Groups view.
  ...
parents db6b2b18 a1aea326
*.erb *.erb
lib/gitlab/sanitizers/svg/whitelist.rb lib/gitlab/sanitizers/svg/whitelist.rb
lib/gitlab/diff/position_tracer.rb
image: "ruby:2.3.1" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3-git-2.7-phantomjs-2.1"
cache: cache:
key: "ruby-231" key: "ruby-231"
paths: paths:
- vendor/apt
- vendor/ruby - vendor/ruby
variables: variables:
...@@ -12,7 +11,7 @@ variables: ...@@ -12,7 +11,7 @@ variables:
RSPEC_RETRY_RETRY_COUNT: "3" RSPEC_RETRY_RETRY_COUNT: "3"
RAILS_ENV: "test" RAILS_ENV: "test"
SIMPLECOV: "true" SIMPLECOV: "true"
USE_DB: "true" SETUP_DB: "true"
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
GIT_DEPTH: "20" GIT_DEPTH: "20"
PHANTOMJS_VERSION: "2.1.1" PHANTOMJS_VERSION: "2.1.1"
...@@ -23,7 +22,7 @@ before_script: ...@@ -23,7 +22,7 @@ before_script:
- bundle --version - bundle --version
- '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"' - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"'
- retry gem install knapsack - retry gem install knapsack
- '[ "$USE_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate' - '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate'
stages: stages:
- prepare - prepare
...@@ -35,7 +34,7 @@ stages: ...@@ -35,7 +34,7 @@ stages:
.knapsack-state: &knapsack-state .knapsack-state: &knapsack-state
services: [] services: []
variables: variables:
USE_DB: "false" SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false" USE_BUNDLE_INSTALL: "false"
cache: cache:
key: "knapsack" key: "knapsack"
...@@ -141,14 +140,13 @@ spinach 9 10: *spinach-knapsack ...@@ -141,14 +140,13 @@ spinach 9 10: *spinach-knapsack
# Execute all testing suites against Ruby 2.1 # Execute all testing suites against Ruby 2.1
.ruby-21: &ruby-21 .ruby-21: &ruby-21
image: "ruby:2.1" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.1-git-2.7-phantomjs-2.1"
<<: *use-db <<: *use-db
only: only:
- master - master
cache: cache:
key: "ruby21" key: "ruby21"
paths: paths:
- vendor/apt
- vendor/ruby - vendor/ruby
.rspec-knapsack-ruby21: &rspec-knapsack-ruby21 .rspec-knapsack-ruby21: &rspec-knapsack-ruby21
...@@ -196,7 +194,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21 ...@@ -196,7 +194,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21
.ruby-static-analysis: &ruby-static-analysis .ruby-static-analysis: &ruby-static-analysis
variables: variables:
SIMPLECOV: "false" SIMPLECOV: "false"
USE_DB: "false" SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
.exec: &exec .exec: &exec
...@@ -209,9 +207,6 @@ rubocop: *exec ...@@ -209,9 +207,6 @@ rubocop: *exec
rake haml_lint: *exec rake haml_lint: *exec
rake scss_lint: *exec rake scss_lint: *exec
rake brakeman: *exec rake brakeman: *exec
rake flog:
<<: *exec
allow_failure: yes
rake flay: rake flay:
<<: *exec <<: *exec
allow_failure: yes allow_failure: yes
...@@ -224,6 +219,23 @@ rake db:migrate:reset: ...@@ -224,6 +219,23 @@ rake db:migrate:reset:
script: script:
- rake db:migrate:reset - rake db:migrate:reset
rake db:seed_fu:
stage: test
<<: *use-db
variables:
SIZE: "1"
SETUP_DB: "false"
RAILS_ENV: "development"
script:
- git clone https://gitlab.com/gitlab-org/gitlab-test.git
/home/git/repositories/gitlab-org/gitlab-test.git
- bundle exec rake db:setup db:seed_fu
artifacts:
when: on_failure
expire_in: 1d
paths:
- log/development.log
teaspoon: teaspoon:
stage: test stage: test
<<: *use-db <<: *use-db
...@@ -272,7 +284,7 @@ coverage: ...@@ -272,7 +284,7 @@ coverage:
stage: post-test stage: post-test
services: [] services: []
variables: variables:
USE_DB: "false" SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
script: script:
- bundle exec scripts/merge-simplecov - bundle exec scripts/merge-simplecov
...@@ -288,7 +300,7 @@ coverage: ...@@ -288,7 +300,7 @@ coverage:
notify:slack: notify:slack:
stage: post-test stage: post-test
variables: variables:
USE_DB: "false" SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false" USE_BUNDLE_INSTALL: "false"
script: script:
- ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>" - ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
......
...@@ -639,6 +639,10 @@ Lint/RescueException: ...@@ -639,6 +639,10 @@ Lint/RescueException:
Lint/ShadowedException: Lint/ShadowedException:
Enabled: false Enabled: false
# Checks for Object#to_s usage in string interpolation.
Lint/StringConversionInInterpolation:
Enabled: true
# Do not use prefix `_` for a variable that is used. # Do not use prefix `_` for a variable that is used.
Lint/UnderscorePrefixedVariableName: Lint/UnderscorePrefixedVariableName:
Enabled: true Enabled: true
...@@ -767,33 +771,26 @@ Rails/ScopeArgs: ...@@ -767,33 +771,26 @@ Rails/ScopeArgs:
RSpec/AnyInstance: RSpec/AnyInstance:
Enabled: false Enabled: false
# Check for expectations where `be(...)` can replace `eql(...)`. # Check that the first argument to the top level describe is the tested class or
RSpec/BeEql: # module.
Enabled: false
# Check that the first argument to the top level describe is a constant.
RSpec/DescribeClass: RSpec/DescribeClass:
Enabled: false Enabled: false
# Checks that tests use `described_class`. # Use `described_class` for tested class / module.
RSpec/DescribedClass:
Enabled: false
# Checks that the second argument to `describe` specifies a method.
RSpec/DescribeMethod: RSpec/DescribeMethod:
Enabled: false Enabled: false
# Checks if an example group does not include any tests. # Checks that the second argument to top level describe is the tested method
RSpec/EmptyExampleGroup: # name.
RSpec/DescribedClass:
Enabled: false Enabled: false
CustomIncludeMethods: []
# Checks for long examples. # Checks for long example.
RSpec/ExampleLength: RSpec/ExampleLength:
Enabled: false Enabled: false
Max: 5 Max: 5
# Checks that example descriptions do not start with "should". # Do not use should when describing your tests.
RSpec/ExampleWording: RSpec/ExampleWording:
Enabled: false Enabled: false
CustomTransform: CustomTransform:
...@@ -802,10 +799,6 @@ RSpec/ExampleWording: ...@@ -802,10 +799,6 @@ RSpec/ExampleWording:
not: does not not: does not
IgnoredWords: [] IgnoredWords: []
# Checks for `expect(...)` calls containing literal values.
RSpec/ExpectActual:
Enabled: false
# Checks the file and folder naming of the spec file. # Checks the file and folder naming of the spec file.
RSpec/FilePath: RSpec/FilePath:
Enabled: false Enabled: false
...@@ -817,65 +810,19 @@ RSpec/FilePath: ...@@ -817,65 +810,19 @@ RSpec/FilePath:
RSpec/Focus: RSpec/Focus:
Enabled: true Enabled: true
# Checks the arguments passed to `before`, `around`, and `after`.
RSpec/HookArgument:
Enabled: false
EnforcedStyle: implicit
# Check that a consistent implict expectation style is used.
# TODO (rspeicher): Available in rubocop-rspec 1.8.0
# RSpec/ImplicitExpect:
# Enabled: true
# EnforcedStyle: is_expected
# Checks for the usage of instance variables. # Checks for the usage of instance variables.
RSpec/InstanceVariable: RSpec/InstanceVariable:
Enabled: false Enabled: false
# Checks for `subject` definitions that come after `let` definitions. # Checks for multiple top-level describes.
RSpec/LeadingSubject:
Enabled: false
# Checks unreferenced `let!` calls being used for test setup.
RSpec/LetSetup:
Enabled: false
# Check that chains of messages are not being stubbed.
RSpec/MessageChain:
Enabled: false
# Checks for consistent message expectation style.
RSpec/MessageExpectation:
Enabled: false
EnforcedStyle: allow
# Checks for multiple top level describes.
RSpec/MultipleDescribes: RSpec/MultipleDescribes:
Enabled: false Enabled: false
# Checks if examples contain too many `expect` calls. # Enforces the usage of the same method on all negative message expectations.
RSpec/MultipleExpectations:
Enabled: false
Max: 1
# Checks for explicitly referenced test subjects.
RSpec/NamedSubject:
Enabled: false
# Checks for nested example groups.
RSpec/NestedGroups:
Enabled: false
MaxNesting: 2
# Checks for consistent method usage for negating expectations.
RSpec/NotToNot: RSpec/NotToNot:
EnforcedStyle: not_to EnforcedStyle: not_to
Enabled: true Enabled: true
# Checks for stubbed test subjects.
RSpec/SubjectStub:
Enabled: false
# Prefer using verifying doubles over normal doubles. # Prefer using verifying doubles over normal doubles.
RSpec/VerifiedDoubles: RSpec/VerifiedDoubles:
Enabled: false Enabled: false
...@@ -27,11 +27,6 @@ Lint/Loop: ...@@ -27,11 +27,6 @@ Lint/Loop:
Lint/ShadowingOuterLocalVariable: Lint/ShadowingOuterLocalVariable:
Enabled: false Enabled: false
# Offense count: 6
# Cop supports --auto-correct.
Lint/StringConversionInInterpolation:
Enabled: false
# Offense count: 49 # Offense count: 49
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
......
This diff is collapsed.
...@@ -6,10 +6,8 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3' ...@@ -6,10 +6,8 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with # Responders respond_to and respond_with
gem 'responders', '~> 2.0' gem 'responders', '~> 2.0'
# Specify a sprockets version due to increased performance gem 'sprockets', '~> 3.7.0'
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069 gem 'sprockets-es6', '~> 0.9.2'
gem 'sprockets', '~> 3.6.0'
gem 'sprockets-es6'
# Default values for AR models # Default values for AR models
gem 'default_value_for', '~> 3.0.0' gem 'default_value_for', '~> 3.0.0'
...@@ -19,7 +17,7 @@ gem 'mysql2', '~> 0.3.16', group: :mysql ...@@ -19,7 +17,7 @@ gem 'mysql2', '~> 0.3.16', group: :mysql
gem 'pg', '~> 0.18.2', group: :postgres gem 'pg', '~> 0.18.2', group: :postgres
# Authentication libraries # Authentication libraries
gem 'devise', '~> 4.0' gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0' gem 'doorkeeper', '~> 4.2.0'
gem 'omniauth', '~> 1.3.1' gem 'omniauth', '~> 1.3.1'
gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-auth0', '~> 1.4.1'
...@@ -53,7 +51,7 @@ gem 'browser', '~> 2.2' ...@@ -53,7 +51,7 @@ gem 'browser', '~> 2.2'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem 'gitlab_git', '~> 10.6.6' gem 'gitlab_git', '~> 10.6.7'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
...@@ -102,7 +100,7 @@ gem 'seed-fu', '~> 2.3.5' ...@@ -102,7 +100,7 @@ gem 'seed-fu', '~> 2.3.5'
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0' gem 'html-pipeline', '~> 1.11.0'
gem 'task_list', '~> 1.0.2', require: 'task_list/railtie' gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie'
gem 'github-markup', '~> 1.4' gem 'github-markup', '~> 1.4'
gem 'redcarpet', '~> 3.3.3' gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.3.2' gem 'RedCloth', '~> 4.3.2'
...@@ -122,8 +120,8 @@ gem 'diffy', '~> 3.0.3' ...@@ -122,8 +120,8 @@ gem 'diffy', '~> 3.0.3'
# Application server # Application server
group :unicorn do group :unicorn do
gem 'unicorn', '~> 4.9.0' gem 'unicorn', '~> 5.1.0'
gem 'unicorn-worker-killer', '~> 0.4.2' gem 'unicorn-worker-killer', '~> 0.4.4'
end end
# State machine # State machine
...@@ -212,7 +210,7 @@ gem 'oj', '~> 2.17.4' ...@@ -212,7 +210,7 @@ gem 'oj', '~> 2.17.4'
gem 'chronic', '~> 0.10.2' gem 'chronic', '~> 0.10.2'
gem 'chronic_duration', '~> 0.10.6' gem 'chronic_duration', '~> 0.10.6'
gem 'sass-rails', '~> 5.0.0' gem 'sass-rails', '~> 5.0.6'
gem 'coffee-rails', '~> 4.1.0' gem 'coffee-rails', '~> 4.1.0'
gem 'uglifier', '~> 2.7.2' gem 'uglifier', '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0' gem 'turbolinks', '~> 2.5.0'
...@@ -298,11 +296,10 @@ group :development, :test do ...@@ -298,11 +296,10 @@ group :development, :test do
gem 'spring-commands-teaspoon', '~> 0.0.2' gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.42.0', require: false gem 'rubocop', '~> 0.42.0', require: false
gem 'rubocop-rspec', '~> 1.7.0', require: false gem 'rubocop-rspec', '~> 1.5.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false gem 'scss_lint', '~> 0.47.0', require: false
gem 'haml_lint', '~> 0.18.2', require: false gem 'haml_lint', '~> 0.18.2', require: false
gem 'simplecov', '0.12.0', require: false gem 'simplecov', '0.12.0', require: false
gem 'flog', '~> 4.3.2', require: false
gem 'flay', '~> 2.6.1', require: false gem 'flay', '~> 2.6.1', require: false
gem 'bundler-audit', '~> 0.5.0', require: false gem 'bundler-audit', '~> 0.5.0', require: false
...@@ -322,10 +319,6 @@ group :test do ...@@ -322,10 +319,6 @@ group :test do
gem 'timecop', '~> 0.8.0' gem 'timecop', '~> 0.8.0'
end end
group :production do
gem 'gitlab_meta', '7.0'
end
gem 'newrelic_rpm', '~> 3.16' gem 'newrelic_rpm', '~> 3.16'
gem 'octokit', '~> 4.3.0' gem 'octokit', '~> 4.3.0'
...@@ -334,7 +327,7 @@ gem 'mail_room', '~> 0.8' ...@@ -334,7 +327,7 @@ gem 'mail_room', '~> 0.8'
gem 'email_reply_parser', '~> 0.5.8' gem 'email_reply_parser', '~> 0.5.8'
gem 'ruby-prof', '~> 0.15.9' gem 'ruby-prof', '~> 0.16.2'
## CI ## CI
gem 'activerecord-session_store', '~> 1.0.0' gem 'activerecord-session_store', '~> 1.0.0'
......
...@@ -157,11 +157,15 @@ GEM ...@@ -157,11 +157,15 @@ GEM
database_cleaner (1.5.3) database_cleaner (1.5.3)
debug_inspector (0.0.2) debug_inspector (0.0.2)
debugger-ruby_core_source (1.3.8) debugger-ruby_core_source (1.3.8)
deckar01-task_list (1.0.5)
activesupport (~> 4.0)
html-pipeline
rack (~> 1.0)
default_value_for (3.0.2) default_value_for (3.0.2)
activerecord (>= 3.2.0, < 5.1) activerecord (>= 3.2.0, < 5.1)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
devise (4.1.1) devise (4.2.0)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 4.1.0, < 5.1) railties (>= 4.1.0, < 5.1)
...@@ -209,9 +213,6 @@ GEM ...@@ -209,9 +213,6 @@ GEM
flay (2.6.1) flay (2.6.1)
ruby_parser (~> 3.0) ruby_parser (~> 3.0)
sexp_processor (~> 4.0) sexp_processor (~> 4.0)
flog (4.3.2)
ruby_parser (~> 3.1, > 3.1.0)
sexp_processor (~> 4.4)
flowdock (0.7.1) flowdock (0.7.1)
httparty (~> 0.7) httparty (~> 0.7)
multi_json multi_json
...@@ -279,12 +280,11 @@ GEM ...@@ -279,12 +280,11 @@ GEM
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_git (10.6.6) gitlab_git (10.6.7)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
rugged (~> 0.24.0) rugged (~> 0.24.0)
gitlab_meta (7.0)
gitlab_omniauth-ldap (1.2.1) gitlab_omniauth-ldap (1.2.1)
net-ldap (~> 0.9) net-ldap (~> 0.9)
omniauth (~> 1.0) omniauth (~> 1.0)
...@@ -554,7 +554,7 @@ GEM ...@@ -554,7 +554,7 @@ GEM
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.1.0) rainbow (2.1.0)
raindrops (0.15.0) raindrops (0.17.0)
rake (10.5.0) rake (10.5.0)
rb-fsevent (0.9.6) rb-fsevent (0.9.6)
rb-inotify (0.9.5) rb-inotify (0.9.5)
...@@ -588,7 +588,7 @@ GEM ...@@ -588,7 +588,7 @@ GEM
request_store (1.3.1) request_store (1.3.1)
rerun (0.11.0) rerun (0.11.0)
listen (~> 3.0) listen (~> 3.0)
responders (2.1.1) responders (2.3.0)
railties (>= 4.2.0, < 5.1) railties (>= 4.2.0, < 5.1)
rinku (2.0.0) rinku (2.0.0)
rotp (2.1.2) rotp (2.1.2)
...@@ -626,11 +626,11 @@ GEM ...@@ -626,11 +626,11 @@ GEM
rainbow (>= 1.99.1, < 3.0) rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1) unicode-display_width (~> 1.0, >= 1.0.1)
rubocop-rspec (1.7.0) rubocop-rspec (1.5.0)
rubocop (>= 0.42.0) rubocop (>= 0.40.0)
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-prof (0.15.9) ruby-prof (0.16.2)
ruby-progressbar (1.8.1) ruby-progressbar (1.8.1)
ruby-saml (1.3.0) ruby-saml (1.3.0)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
...@@ -645,7 +645,7 @@ GEM ...@@ -645,7 +645,7 @@ GEM
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
sass (3.4.22) sass (3.4.22)
sass-rails (5.0.5) sass-rails (5.0.6)
railties (>= 4.0.0, < 6) railties (>= 4.0.0, < 6)
sass (~> 3.1) sass (~> 3.1)
sprockets (>= 2.8, < 4.0) sprockets (>= 2.8, < 4.0)
...@@ -706,10 +706,10 @@ GEM ...@@ -706,10 +706,10 @@ GEM
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2) spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1) spring (>= 0.9.1)
sprockets (3.6.3) sprockets (3.7.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-es6 (0.9.0) sprockets-es6 (0.9.2)
babel-source (>= 5.8.11) babel-source (>= 5.8.11)
babel-transpiler babel-transpiler
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
...@@ -729,8 +729,6 @@ GEM ...@@ -729,8 +729,6 @@ GEM
ffi ffi
sysexits (1.2.0) sysexits (1.2.0)
systemu (2.6.5) systemu (2.6.5)
task_list (1.0.2)
html-pipeline
teaspoon (1.1.5) teaspoon (1.1.5)
railties (>= 3.2.5, < 6) railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0) teaspoon-jasmine (2.2.0)
...@@ -760,9 +758,8 @@ GEM ...@@ -760,9 +758,8 @@ GEM
unf_ext unf_ext
unf_ext (0.0.7.2) unf_ext (0.0.7.2)
unicode-display_width (1.1.1) unicode-display_width (1.1.1)
unicorn (4.9.0) unicorn (5.1.0)
kgio (~> 2.6) kgio (~> 2.6)
rack
raindrops (~> 0.7) raindrops (~> 0.7)
unicorn-worker-killer (0.4.4) unicorn-worker-killer (0.4.4)
get_process_mem (~> 0) get_process_mem (~> 0)
...@@ -836,8 +833,9 @@ DEPENDENCIES ...@@ -836,8 +833,9 @@ DEPENDENCIES
creole (~> 0.5.0) creole (~> 0.5.0)
d3_rails (~> 3.5.0) d3_rails (~> 3.5.0)
database_cleaner (~> 1.5.0) database_cleaner (~> 1.5.0)
deckar01-task_list (= 1.0.5)
default_value_for (~> 3.0.0) default_value_for (~> 3.0.0)
devise (~> 4.0) devise (~> 4.2)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
diffy (~> 3.0.3) diffy (~> 3.0.3)
doorkeeper (~> 4.2.0) doorkeeper (~> 4.2.0)
...@@ -847,7 +845,6 @@ DEPENDENCIES ...@@ -847,7 +845,6 @@ DEPENDENCIES
factory_girl_rails (~> 4.6.0) factory_girl_rails (~> 4.6.0)
ffaker (~> 2.0.0) ffaker (~> 2.0.0)
flay (~> 2.6.1) flay (~> 2.6.1)
flog (~> 4.3.2)
fog-aws (~> 0.9) fog-aws (~> 0.9)
fog-azure (~> 0.0) fog-azure (~> 0.0)
fog-core (~> 1.40) fog-core (~> 1.40)
...@@ -863,8 +860,7 @@ DEPENDENCIES ...@@ -863,8 +860,7 @@ DEPENDENCIES
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
github-markup (~> 1.4) github-markup (~> 1.4)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_git (~> 10.6.6) gitlab_git (~> 10.6.7)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2) gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.2) gollum-rugged_adapter (~> 0.4.2)
...@@ -943,11 +939,11 @@ DEPENDENCIES ...@@ -943,11 +939,11 @@ DEPENDENCIES
rspec-rails (~> 3.5.0) rspec-rails (~> 3.5.0)
rspec-retry (~> 0.4.5) rspec-retry (~> 0.4.5)
rubocop (~> 0.42.0) rubocop (~> 0.42.0)
rubocop-rspec (~> 1.7.0) rubocop-rspec (~> 1.5.0)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.15.9) ruby-prof (~> 0.16.2)
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 5.0.0) sass-rails (~> 5.0.6)
scss_lint (~> 0.47.0) scss_lint (~> 0.47.0)
sdoc (~> 0.3.20) sdoc (~> 0.3.20)
seed-fu (~> 2.3.5) seed-fu (~> 2.3.5)
...@@ -966,11 +962,10 @@ DEPENDENCIES ...@@ -966,11 +962,10 @@ DEPENDENCIES
spring-commands-rspec (~> 1.0.4) spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.1.0) spring-commands-spinach (~> 1.1.0)
spring-commands-teaspoon (~> 0.0.2) spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 3.6.0) sprockets (~> 3.7.0)
sprockets-es6 sprockets-es6 (~> 0.9.2)
state_machines-activerecord (~> 0.4.0) state_machines-activerecord (~> 0.4.0)
sys-filesystem (~> 1.1.6) sys-filesystem (~> 1.1.6)
task_list (~> 1.0.2)
teaspoon (~> 1.1.0) teaspoon (~> 1.1.0)
teaspoon-jasmine (~> 2.2.0) teaspoon-jasmine (~> 2.2.0)
test_after_commit (~> 0.4.2) test_after_commit (~> 0.4.2)
...@@ -981,8 +976,8 @@ DEPENDENCIES ...@@ -981,8 +976,8 @@ DEPENDENCIES
uglifier (~> 2.7.2) uglifier (~> 2.7.2)
underscore-rails (~> 1.8.0) underscore-rails (~> 1.8.0)
unf (~> 0.1.4) unf (~> 0.1.4)
unicorn (~> 4.9.0) unicorn (~> 5.1.0)
unicorn-worker-killer (~> 0.4.2) unicorn-worker-killer (~> 0.4.4)
version_sorter (~> 2.1.0) version_sorter (~> 2.1.0)
virtus (~> 1.0.1) virtus (~> 1.0.1)
vmstat (~> 2.2) vmstat (~> 2.2)
...@@ -991,4 +986,4 @@ DEPENDENCIES ...@@ -991,4 +986,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.13.0 1.13.1
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
## Canonical source ## Canonical source
The cannonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/). The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
## Open source software to collaborate on code ## Open source software to collaborate on code
......
8.12.0-pre 8.13.0-pre
...@@ -16,9 +16,6 @@ ...@@ -16,9 +16,6 @@
.replace(':id', group_id); .replace(':id', group_id);
return $.ajax({ return $.ajax({
url: url, url: url,
data: {
private_token: gon.api_token
},
dataType: "json" dataType: "json"
}).done(function(group) { }).done(function(group) {
return callback(group); return callback(group);
...@@ -31,7 +28,6 @@ ...@@ -31,7 +28,6 @@
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: {
private_token: gon.api_token,
search: query, search: query,
per_page: 20 per_page: 20
}, },
...@@ -46,7 +42,6 @@ ...@@ -46,7 +42,6 @@
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: {
private_token: gon.api_token,
search: query, search: query,
per_page: 20 per_page: 20
}, },
...@@ -61,7 +56,6 @@ ...@@ -61,7 +56,6 @@
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: {
private_token: gon.api_token,
search: query, search: query,
order_by: order, order_by: order,
per_page: 20 per_page: 20
...@@ -74,7 +68,6 @@ ...@@ -74,7 +68,6 @@
newLabel: function(project_id, data, callback) { newLabel: function(project_id, data, callback) {
var url = Api.buildUrl(Api.labelsPath) var url = Api.buildUrl(Api.labelsPath)
.replace(':id', project_id); .replace(':id', project_id);
data.private_token = gon.api_token;
return $.ajax({ return $.ajax({
url: url, url: url,
type: "POST", type: "POST",
...@@ -93,7 +86,6 @@ ...@@ -93,7 +86,6 @@
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: {
private_token: gon.api_token,
search: query, search: query,
per_page: 20 per_page: 20
}, },
......
...@@ -247,7 +247,7 @@ ...@@ -247,7 +247,7 @@
$this.toggleClass('active'); $this.toggleClass('active');
var notesHolders = $this.closest('.diff-file').find('.notes_holder'); var notesHolders = $this.closest('.diff-file').find('.notes_holder');
if ($this.hasClass('active')) { if ($this.hasClass('active')) {
notesHolders.show(); notesHolders.show().find('.hide').show();
} else { } else {
notesHolders.hide(); notesHolders.hide();
} }
......
...@@ -357,7 +357,7 @@ ...@@ -357,7 +357,7 @@
$('ul.emoji-menu-search, h5.emoji-search').remove(); $('ul.emoji-menu-search, h5.emoji-search').remove();
if (term) { if (term) {
// Generate a search result block // Generate a search result block
h5 = $('<h5>').text('Search results'); h5 = $('<h5 class="emoji-search" />').text('Search results');
found_emojis = _this.searchEmojis(term).show(); found_emojis = _this.searchEmojis(term).show();
ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis); ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis);
$('.emoji-menu-content ul, .emoji-menu-content h5').hide(); $('.emoji-menu-content ul, .emoji-menu-content h5').hide();
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
// submitted textarea // submitted textarea
})(this)); })(this));
this.initModePanesAndLinks(); this.initModePanesAndLinks();
this.initSoftWrap();
new BlobLicenseSelectors({ new BlobLicenseSelectors({
editor: this.editor editor: this.editor
}); });
...@@ -50,6 +51,7 @@ ...@@ -50,6 +51,7 @@
this.$editModePanes.hide(); this.$editModePanes.hide();
currentPane.fadeIn(200); currentPane.fadeIn(200);
if (paneId === "#preview") { if (paneId === "#preview") {
this.$toggleButton.hide();
return $.post(currentLink.data("preview-url"), { return $.post(currentLink.data("preview-url"), {
content: this.editor.getValue() content: this.editor.getValue()
}, function(response) { }, function(response) {
...@@ -57,10 +59,23 @@ ...@@ -57,10 +59,23 @@
return currentPane.syntaxHighlight(); return currentPane.syntaxHighlight();
}); });
} else { } else {
this.$toggleButton.show();
return this.editor.focus(); return this.editor.focus();
} }
}; };
EditBlob.prototype.initSoftWrap = function() {
this.isSoftWrapped = false;
this.$toggleButton = $('.soft-wrap-toggle');
this.$toggleButton.on('click', this.toggleSoftWrap.bind(this));
};
EditBlob.prototype.toggleSoftWrap = function(e) {
this.isSoftWrapped = !this.isSoftWrapped;
this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
};
return EditBlob; return EditBlob;
})(); })();
......
...@@ -23,8 +23,9 @@ ...@@ -23,8 +23,9 @@
selectable: true, selectable: true,
filterable: true, filterable: true,
filterByText: true, filterByText: true,
fieldName: $dropdown.attr('name'), toggleLabel: true,
filterInput: 'input[type="text"]', fieldName: $dropdown.data('field-name'),
filterInput: 'input[type="search"]',
renderRow: function(ref) { renderRow: function(ref) {
var link; var link;
if (ref.header != null) { if (ref.header != null) {
......
((global) => { ((global) => {
const COOKIE_NAME = 'cycle_analytics_help_dismissed'; const COOKIE_NAME = 'cycle_analytics_help_dismissed';
const store = gl.cycleAnalyticsStore = {
isLoading: true,
hasError: false,
isHelpDismissed: $.cookie(COOKIE_NAME),
analytics: {}
};
gl.CycleAnalytics = class CycleAnalytics { gl.CycleAnalytics = class CycleAnalytics {
constructor() { constructor() {
const that = this; const that = this;
this.isHelpDismissed = $.cookie(COOKIE_NAME);
this.vue = new Vue({ this.vue = new Vue({
el: '#cycle-analytics', el: '#cycle-analytics',
name: 'CycleAnalytics', name: 'CycleAnalytics',
created: this.fetchData(), created: this.fetchData(),
data: this.decorateData({ isLoading: true }), data: store,
methods: { methods: {
dismissLanding() { dismissLanding() {
that.dismissLanding(); that.dismissLanding();
...@@ -21,6 +26,7 @@ ...@@ -21,6 +26,7 @@
} }
fetchData(options) { fetchData(options) {
store.isLoading = true;
options = options || { startDate: 30 }; options = options || { startDate: 30 };
$.ajax({ $.ajax({
...@@ -30,22 +36,20 @@ ...@@ -30,22 +36,20 @@
contentType: 'application/json', contentType: 'application/json',
data: { start_date: options.startDate } data: { start_date: options.startDate }
}).done((data) => { }).done((data) => {
this.vue.$data = this.decorateData(data); this.decorateData(data);
this.initDropdown(); this.initDropdown();
}) })
.error((data) => { .error((data) => {
this.handleError(data); this.handleError(data);
}) })
.always(() => { .always(() => {
this.vue.isLoading = false; store.isLoading = false;
}) })
} }
decorateData(data) { decorateData(data) {
data.summary = data.summary || []; data.summary = data.summary || [];
data.stats = data.stats || []; data.stats = data.stats || [];
data.isHelpDismissed = this.isHelpDismissed;
data.isLoading = data.isLoading || false;
data.summary.forEach((item) => { data.summary.forEach((item) => {
item.value = item.value || '-'; item.value = item.value || '-';
...@@ -53,23 +57,21 @@ ...@@ -53,23 +57,21 @@
data.stats.forEach((item) => { data.stats.forEach((item) => {
item.value = item.value || '- - -'; item.value = item.value || '- - -';
}) });
return data; store.analytics = data;
} }
handleError(data) { handleError(data) {
this.vue.$data = { store.hasError = true;
hasError: true,
isHelpDismissed: this.isHelpDismissed
};
new Flash('There was an error while fetching cycle analytics data.', 'alert'); new Flash('There was an error while fetching cycle analytics data.', 'alert');
} }
dismissLanding() { dismissLanding() {
this.vue.isHelpDismissed = true; store.isHelpDismissed = true;
$.cookie(COOKIE_NAME, true); $.cookie(COOKIE_NAME, true, {
path: gon.relative_url_root || '/'
});
} }
initDropdown() { initDropdown() {
...@@ -82,7 +84,6 @@ ...@@ -82,7 +84,6 @@
const value = $target.data('value'); const value = $target.data('value');
$label.text($target.text().trim()); $label.text($target.text().trim());
this.vue.isLoading = true;
this.fetchData({ startDate: value }); this.fetchData({ startDate: value });
}) })
} }
......
((w) => { ((w) => {
w.ResolveBtn = Vue.extend({ w.ResolveBtn = Vue.extend({
mixins: [
ButtonMixins
],
props: { props: {
noteId: Number, noteId: Number,
discussionId: String, discussionId: String,
resolved: Boolean, resolved: Boolean,
namespacePath: String,
projectPath: String, projectPath: String,
canResolve: Boolean, canResolve: Boolean,
resolvedBy: String resolvedBy: String
...@@ -69,10 +65,10 @@ ...@@ -69,10 +65,10 @@
if (this.isResolved) { if (this.isResolved) {
promise = ResolveService promise = ResolveService
.unresolve(this.namespace, this.noteId); .unresolve(this.projectPath, this.noteId);
} else { } else {
promise = ResolveService promise = ResolveService
.resolve(this.namespace, this.noteId); .resolve(this.projectPath, this.noteId);
} }
promise.then((response) => { promise.then((response) => {
......
((w) => { ((w) => {
w.ResolveDiscussionBtn = Vue.extend({ w.ResolveDiscussionBtn = Vue.extend({
mixins: [
ButtonMixins
],
props: { props: {
discussionId: String, discussionId: String,
mergeRequestId: Number, mergeRequestId: Number,
namespacePath: String,
projectPath: String, projectPath: String,
canResolve: Boolean, canResolve: Boolean,
}, },
...@@ -50,7 +46,7 @@ ...@@ -50,7 +46,7 @@
}, },
methods: { methods: {
resolve: function () { resolve: function () {
ResolveService.toggleResolveForDiscussion(this.namespace, this.mergeRequestId, this.discussionId); ResolveService.toggleResolveForDiscussion(this.projectPath, this.mergeRequestId, this.discussionId);
} }
}, },
created: function () { created: function () {
......
((w) => {
w.ButtonMixins = {
computed: {
namespace: function () {
return `${this.namespacePath}/${this.projectPath}`;
}
}
};
})(window);
...@@ -9,32 +9,32 @@ ...@@ -9,32 +9,32 @@
Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken(); Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken();
} }
prepareRequest(namespace) { prepareRequest(root) {
this.setCSRF(); this.setCSRF();
Vue.http.options.root = `/${namespace}`; Vue.http.options.root = root;
} }
resolve(namespace, noteId) { resolve(projectPath, noteId) {
this.prepareRequest(namespace); this.prepareRequest(projectPath);
return this.noteResource.save({ noteId }, {}); return this.noteResource.save({ noteId }, {});
} }
unresolve(namespace, noteId) { unresolve(projectPath, noteId) {
this.prepareRequest(namespace); this.prepareRequest(projectPath);
return this.noteResource.delete({ noteId }, {}); return this.noteResource.delete({ noteId }, {});
} }
toggleResolveForDiscussion(namespace, mergeRequestId, discussionId) { toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId], const discussion = CommentsStore.state[discussionId],
isResolved = discussion.isResolved(); isResolved = discussion.isResolved();
let promise; let promise;
if (isResolved) { if (isResolved) {
promise = this.unResolveAll(namespace, mergeRequestId, discussionId); promise = this.unResolveAll(projectPath, mergeRequestId, discussionId);
} else { } else {
promise = this.resolveAll(namespace, mergeRequestId, discussionId); promise = this.resolveAll(projectPath, mergeRequestId, discussionId);
} }
promise.then((response) => { promise.then((response) => {
...@@ -57,10 +57,10 @@ ...@@ -57,10 +57,10 @@
}) })
} }
resolveAll(namespace, mergeRequestId, discussionId) { resolveAll(projectPath, mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId]; const discussion = CommentsStore.state[discussionId];
this.prepareRequest(namespace); this.prepareRequest(projectPath);
discussion.loading = true; discussion.loading = true;
...@@ -70,10 +70,10 @@ ...@@ -70,10 +70,10 @@
}, {}); }, {});
} }
unResolveAll(namespace, mergeRequestId, discussionId) { unResolveAll(projectPath, mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId]; const discussion = CommentsStore.state[discussionId];
this.prepareRequest(namespace); this.prepareRequest(projectPath);
discussion.loading = true; discussion.loading = true;
......
...@@ -608,27 +608,28 @@ ...@@ -608,27 +608,28 @@
} }
} }
field = []; field = [];
fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName(selectedObject) : this.options.fieldName;
value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id; value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id;
if (isInput) { if (isInput) {
field = $(this.el); field = $(this.el);
} else if(value) { } else if(value) {
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']"); field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']");
} }
if (field.length && el.hasClass(ACTIVE_CLASS)) { if (el.hasClass(ACTIVE_CLASS)) {
el.removeClass(ACTIVE_CLASS); el.removeClass(ACTIVE_CLASS);
if (field && field.length) {
if (isInput) { if (isInput) {
field.val(''); field.val('');
} else { } else {
field.remove(); field.remove();
} }
}
} else if (el.hasClass(INDETERMINATE_CLASS)) { } else if (el.hasClass(INDETERMINATE_CLASS)) {
el.addClass(ACTIVE_CLASS); el.addClass(ACTIVE_CLASS);
el.removeClass(INDETERMINATE_CLASS); el.removeClass(INDETERMINATE_CLASS);
if (field.length && value == null) { if (field && field.length && value == null) {
field.remove(); field.remove();
} }
if (!field.length && fieldName) { if ((!field || !field.length) && fieldName) {
this.addInput(fieldName, value, selectedObject); this.addInput(fieldName, value, selectedObject);
} }
} else { } else {
...@@ -638,15 +639,15 @@ ...@@ -638,15 +639,15 @@
this.dropdown.parent().find("input[name='" + fieldName + "']").remove(); this.dropdown.parent().find("input[name='" + fieldName + "']").remove();
} }
} }
if (field.length && value == null) { if (field && field.length && value == null) {
field.remove(); field.remove();
} }
// Toggle active class for the tick mark // Toggle active class for the tick mark
el.addClass(ACTIVE_CLASS); el.addClass(ACTIVE_CLASS);
if (value != null) { if (value != null) {
if (!field.length && fieldName) { if ((!field || !field.length) && fieldName) {
this.addInput(fieldName, value, selectedObject); this.addInput(fieldName, value, selectedObject);
} else if (field.length) { } else if (field && field.length) {
field.val(value).trigger('change'); field.val(value).trigger('change');
} }
} }
......
...@@ -15,24 +15,31 @@ ...@@ -15,24 +15,31 @@
return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>'); return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>');
}, },
initSearch: function() { initSearch: function() {
this.timer = null; // `immediate` param set to false debounces on the `trailing` edge, lets user finish typing
return $('#issuable_search').off('keyup').on('keyup', function() { const debouncedExecSearch = _.debounce(Issuable.executeSearch, 500, false);
clearTimeout(this.timer);
return this.timer = setTimeout(function() { $('#issuable_search').off('keyup').on('keyup', debouncedExecSearch);
var $form, $input, $search;
$search = $('#issuable_search'); // ensures existing filters are preserved when manually submitted
$form = $('.js-filter-form'); $('#issue_search_form').on('submit', (e) => {
$input = $("input[name='" + ($search.attr('name')) + "']", $form); e.preventDefault();
if ($input.length === 0) { debouncedExecSearch(e);
$form.append("<input type='hidden' name='" + ($search.attr('name')) + "' value='" + (_.escape($search.val())) + "'/>"); });
},
executeSearch: function(e) {
const $search = $('#issuable_search');
const $searchName = $search.attr('name');
const $searchValue = $search.val();
const $filtersForm = $('.js-filter-form');
const $input = $(`input[name='${$searchName}']`, $filtersForm);
if (!$input.length) {
$filtersForm.append(`<input type='hidden' name='${$searchName}' value='${_.escape($searchValue)}'/>`);
} else { } else {
$input.val($search.val()); $input.val($searchValue);
}
if ($search.val() !== '') {
return Issuable.filterResults($form);
} }
}, 500);
}); Issuable.filterResults($filtersForm);
}, },
initLabelFilterRemove: function() { initLabelFilterRemove: function() {
return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) { return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
var _this; var _this;
_this = this; _this = this;
$('.js-label-select').each(function(i, dropdown) { $('.js-label-select').each(function(i, dropdown) {
var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip; var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected;
$dropdown = $(dropdown); $dropdown = $(dropdown);
projectId = $dropdown.data('project-id'); projectId = $dropdown.data('project-id');
labelUrl = $dropdown.data('labels'); labelUrl = $dropdown.data('labels');
...@@ -24,6 +24,11 @@ ...@@ -24,6 +24,11 @@
$sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip'); $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
$value = $block.find('.value'); $value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut(); $loading = $block.find('.block-loading').fadeOut();
initialSelected = $selectbox
.find('input[name="' + $dropdown.data('field-name') + '"]')
.map(function () {
return this.value;
}).get();
if (issueUpdateURL != null) { if (issueUpdateURL != null) {
issueURLSplit = issueUpdateURL.split('/'); issueURLSplit = issueUpdateURL.split('/');
} }
...@@ -43,6 +48,10 @@ ...@@ -43,6 +48,10 @@
selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").map(function() { selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").map(function() {
return this.value; return this.value;
}).get(); }).get();
if (_.isEqual(initialSelected, selected)) return;
initialSelected = selected;
data = {}; data = {};
data[abilityName] = {}; data[abilityName] = {};
data[abilityName].label_ids = selected; data[abilityName].label_ids = selected;
...@@ -280,12 +289,12 @@ ...@@ -280,12 +289,12 @@
if (page === 'projects:boards:show') { if (page === 'projects:boards:show') {
if (label.isAny) { if (label.isAny) {
gl.issueBoards.BoardsStore.state.filters['label_name'] = []; gl.issueBoards.BoardsStore.state.filters['label_name'] = [];
} else if (label.title) { } else if ($el.hasClass('is-active')) {
gl.issueBoards.BoardsStore.state.filters['label_name'].push(label.title); gl.issueBoards.BoardsStore.state.filters['label_name'].push(label.title);
} else { } else {
var filters = gl.issueBoards.BoardsStore.state.filters['label_name']; var filters = gl.issueBoards.BoardsStore.state.filters['label_name'];
filters = filters.filter(function (label) { filters = filters.filter(function (filteredLabel) {
return label !== $el.text().trim(); return filteredLabel !== label.title;
}); });
gl.issueBoards.BoardsStore.state.filters['label_name'] = filters; gl.issueBoards.BoardsStore.state.filters['label_name'] = filters;
} }
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
while (i < sURLVariables.length) { while (i < sURLVariables.length) {
sParameterName = sURLVariables[i].split('='); sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] === sParam) { if (sParameterName[0] === sParam) {
values.push(sParameterName[1]); values.push(sParameterName[1].replace(/\+/g, ' '));
} }
i++; i++;
} }
......
...@@ -432,14 +432,12 @@ ...@@ -432,14 +432,12 @@
var $form = $(xhr.target); var $form = $(xhr.target);
if ($form.attr('data-resolve-all') != null) { if ($form.attr('data-resolve-all') != null) {
var namespacePath = $form.attr('data-namespace-path'), var projectPath = $form.data('project-path')
projectPath = $form.attr('data-project-path') discussionId = $form.data('discussion-id'),
discussionId = $form.attr('data-discussion-id'), mergeRequestId = $form.data('noteable-iid');
mergeRequestId = $form.attr('data-noteable-iid'),
namespace = namespacePath + '/' + projectPath;
if (ResolveService != null) { if (ResolveService != null) {
ResolveService.toggleResolveForDiscussion(namespace, mergeRequestId, discussionId); ResolveService.toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId);
} }
} }
...@@ -854,7 +852,6 @@ ...@@ -854,7 +852,6 @@
.closest('form') .closest('form')
.attr('data-discussion-id', discussionId) .attr('data-discussion-id', discussionId)
.attr('data-resolve-all', 'true') .attr('data-resolve-all', 'true')
.attr('data-namespace-path', $this.attr('data-namespace-path'))
.attr('data-project-path', $this.attr('data-project-path')); .attr('data-project-path', $this.attr('data-project-path'));
}; };
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
function ProjectFindFile(element1, options) { function ProjectFindFile(element1, options) {
this.element = element1; this.element = element1;
this.options = options; this.options = options;
this.goToBlob = bind(this.goToBlob, this);
this.goToTree = bind(this.goToTree, this); this.goToTree = bind(this.goToTree, this);
this.selectRowDown = bind(this.selectRowDown, this); this.selectRowDown = bind(this.selectRowDown, this);
this.selectRowUp = bind(this.selectRowUp, this); this.selectRowUp = bind(this.selectRowUp, this);
...@@ -36,16 +35,6 @@ ...@@ -36,16 +35,6 @@
} }
}; };
})(this)); })(this));
return this.element.find(".tree-content-holder .tree-table").on("click", function(event) {
var path;
if (event.target.nodeName !== "A") {
path = this.element.find(".tree-item-file-name a", this).attr("href");
if (path) {
return location.href = path;
}
}
});
// init event
}; };
ProjectFindFile.prototype.findFile = function() { ProjectFindFile.prototype.findFile = function() {
...@@ -121,11 +110,12 @@ ...@@ -121,11 +110,12 @@
// make tbody row html // make tbody row html
ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) { ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) {
var $tr; var $tr;
$tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>"); $tr = $("<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>");
if (matches) { if (matches) {
$tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl)); $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl));
} else { } else {
$tr.find("a").attr("href", blobItemUrl).text(filePath); $tr.find("a").attr("href", blobItemUrl);
$tr.find(".str-truncated").text(filePath);
} }
return $tr; return $tr;
}; };
...@@ -164,14 +154,6 @@ ...@@ -164,14 +154,6 @@
return location.href = this.options.treeUrl; return location.href = this.options.treeUrl;
}; };
ProjectFindFile.prototype.goToBlob = function() {
var path;
path = this.element.find(".tree-item.selected .tree-item-file-name a").attr("href");
if (path) {
return location.href = path;
}
};
return ProjectFindFile; return ProjectFindFile;
})(); })();
......
...@@ -40,7 +40,6 @@ ...@@ -40,7 +40,6 @@
dataType: 'json', dataType: 'json',
data: { data: {
_method: 'PATCH', _method: 'PATCH',
id: this.$wrap.data('banchId'),
protected_branch: { protected_branch: {
merge_access_levels_attributes: [{ merge_access_levels_attributes: [{
id: this.$allowedToMergeDropdown.data('access-level-id'), id: this.$allowedToMergeDropdown.data('access-level-id'),
......
...@@ -389,4 +389,41 @@ ...@@ -389,4 +389,41 @@
})(); })();
$(function() {
var $projectOptionsDataEl = $('.js-search-project-options');
var $groupOptionsDataEl = $('.js-search-group-options');
var $dashboardOptionsDataEl = $('.js-search-dashboard-options');
if ($projectOptionsDataEl.length) {
gl.projectOptions = gl.projectOptions || {};
var projectPath = $projectOptionsDataEl.data('project-path');
gl.projectOptions[projectPath] = {
name: $projectOptionsDataEl.data('name'),
issuesPath: $projectOptionsDataEl.data('issues-path'),
mrPath: $projectOptionsDataEl.data('mr-path')
};
}
if ($groupOptionsDataEl.length) {
gl.groupOptions = gl.groupOptions || {};
var groupPath = $groupOptionsDataEl.data('group-path');
gl.groupOptions[groupPath] = {
name: $groupOptionsDataEl.data('name'),
issuesPath: $groupOptionsDataEl.data('issues-path'),
mrPath: $groupOptionsDataEl.data('mr-path')
};
}
if ($dashboardOptionsDataEl.length) {
gl.dashboardOptions = {
issuesPath: $dashboardOptionsDataEl.data('issues-path'),
mrPath: $dashboardOptionsDataEl.data('mr-path')
};
}
});
}).call(this); }).call(this);
...@@ -10,12 +10,13 @@ ...@@ -10,12 +10,13 @@
ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>'; ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. Click to expand it.</div>'; COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
function SingleFileDiff(file) { function SingleFileDiff(file) {
this.file = file; this.file = file;
this.toggleDiff = bind(this.toggleDiff, this); this.toggleDiff = bind(this.toggleDiff, this);
this.content = $('.diff-content', this.file); this.content = $('.diff-content', this.file);
this.$toggleIcon = $('.diff-toggle-caret', this.file);
this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path'); this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
this.isOpen = !this.diffForPath; this.isOpen = !this.diffForPath;
if (this.diffForPath) { if (this.diffForPath) {
...@@ -23,18 +24,22 @@ ...@@ -23,18 +24,22 @@
this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide(); this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
this.content = null; this.content = null;
this.collapsedContent.after(this.loadingContent); this.collapsedContent.after(this.loadingContent);
this.$toggleIcon.addClass('fa-caret-right');
} else { } else {
this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide(); this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
this.content.after(this.collapsedContent); this.content.after(this.collapsedContent);
this.$toggleIcon.addClass('fa-caret-down');
} }
this.collapsedContent.on('click', this.toggleDiff); $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff);
$('.file-title > a', this.file).on('click', this.toggleDiff);
} }
SingleFileDiff.prototype.toggleDiff = function(e) { SingleFileDiff.prototype.toggleDiff = function(e) {
var $target = $(e.target);
if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
this.isOpen = !this.isOpen; this.isOpen = !this.isOpen;
if (!this.isOpen && !this.hasError) { if (!this.isOpen && !this.hasError) {
this.content.hide(); this.content.hide();
this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down');
this.collapsedContent.show(); this.collapsedContent.show();
if (typeof DiffNotesApp !== 'undefined') { if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents(); DiffNotesApp.compileComponents();
...@@ -42,10 +47,12 @@ ...@@ -42,10 +47,12 @@
} else if (this.content) { } else if (this.content) {
this.collapsedContent.hide(); this.collapsedContent.hide();
this.content.show(); this.content.show();
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
if (typeof DiffNotesApp !== 'undefined') { if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents(); DiffNotesApp.compileComponents();
} }
} else { } else {
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
return this.getContentHTML(); return this.getContentHTML();
} }
}; };
......
...@@ -19,10 +19,8 @@ ...@@ -19,10 +19,8 @@
&.diff-collapsed { &.diff-collapsed {
padding: 5px; padding: 5px;
.click-to-expand {
cursor: pointer; cursor: pointer;
&:hover {
background-color: $row-hover;
} }
} }
} }
...@@ -129,8 +127,6 @@ ...@@ -129,8 +127,6 @@
position: relative; position: relative;
.avatar-holder { .avatar-holder {
margin-bottom: 16px;
.avatar, .identicon { .avatar, .identicon {
margin: 0 auto; margin: 0 auto;
float: none; float: none;
...@@ -143,13 +139,7 @@ ...@@ -143,13 +139,7 @@
.cover-title { .cover-title {
color: $gl-header-color; color: $gl-header-color;
margin: 0;
font-size: 24px;
font-weight: normal;
margin-bottom: 10px;
color: #4c4e54;
font-size: 23px; font-size: 23px;
line-height: 1.1;
h1 { h1 {
color: $gl-gray-dark; color: $gl-gray-dark;
...@@ -213,6 +203,9 @@ ...@@ -213,6 +203,9 @@
} }
} }
} }
&.user-cover-block {
padding: 24px 0 0;
}
.group-info { .group-info {
......
...@@ -336,3 +336,9 @@ ...@@ -336,3 +336,9 @@
box-shadow: inset 0 0 0 white; box-shadow: inset 0 0 0 white;
} }
} }
@media (max-width: $screen-xs-max) {
.btn-wide-on-xs {
width: 100%;
}
}
...@@ -26,6 +26,15 @@ ...@@ -26,6 +26,15 @@
padding: 10px $gl-padding; padding: 10px $gl-padding;
word-wrap: break-word; word-wrap: break-word;
border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0;
cursor: pointer;
&:hover {
background-color: $dark-background-color;
}
.diff-toggle-caret {
padding-right: 6px;
}
&.file-title-clear { &.file-title-clear {
padding-left: 0; padding-left: 0;
......
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
margin: 0; margin: 0;
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
font-size: 14px; font-size: 14px;
z-index: 100; position: relative;
z-index: 1;
.flash-notice { .flash-notice {
@extend .alert; @extend .alert;
...@@ -34,6 +35,12 @@ ...@@ -34,6 +35,12 @@
} }
} }
.content-wrapper {
.flash-notice .container-fluid {
background-color: transparent;
}
}
@media (max-width: $screen-md-min) { @media (max-width: $screen-md-min) {
ul.notes { ul.notes {
.flash-container.timeline-content { .flash-container.timeline-content {
...@@ -41,4 +48,3 @@ ...@@ -41,4 +48,3 @@
} }
} }
} }
...@@ -112,11 +112,15 @@ header { ...@@ -112,11 +112,15 @@ header {
.header-logo { .header-logo {
position: absolute; position: absolute;
left: 50%; left: 50%;
margin-left: -18px;
top: 7px; top: 7px;
transition-duration: .3s; transition-duration: .3s;
z-index: 999; z-index: 999;
#logo {
position: relative;
left: -50%;
}
svg, img { svg, img {
height: 36px; height: 36px;
} }
...@@ -126,8 +130,12 @@ header { ...@@ -126,8 +130,12 @@ header {
} }
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
right: 25px; right: 20px;
left: auto; left: auto;
#logo {
left: auto;
}
} }
} }
......
...@@ -164,7 +164,7 @@ ul.content-list { ...@@ -164,7 +164,7 @@ ul.content-list {
} }
.no-comments { .no-comments {
opacity: 0.5; opacity: .5;
} }
} }
......
...@@ -99,8 +99,7 @@ ...@@ -99,8 +99,7 @@
.top-area { .top-area {
@include clearfix; @include clearfix;
border-bottom: 1px solid $btn-gray-hover;
border-bottom: 1px solid #eee;
.nav-text { .nav-text {
padding-top: 16px; padding-top: 16px;
......
...@@ -142,6 +142,7 @@ ...@@ -142,6 +142,7 @@
transition-duration: .3s; transition-duration: .3s;
position: absolute; position: absolute;
top: 0; top: 0;
cursor: pointer;
&:hover, &:hover,
&:focus { &:focus {
......
...@@ -204,7 +204,7 @@ body { ...@@ -204,7 +204,7 @@ body {
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
color: $gl-header-color; color: $gl-title-color;
font-weight: 600; font-weight: 600;
} }
......
...@@ -102,7 +102,7 @@ $gl-grayish-blue: #7f8fa4; ...@@ -102,7 +102,7 @@ $gl-grayish-blue: #7f8fa4;
$gl-gray: $gl-text-color; $gl-gray: $gl-text-color;
$gl-gray-dark: #313236; $gl-gray-dark: #313236;
$gl-gray-light: $gl-placeholder-color; $gl-gray-light: $gl-placeholder-color;
$gl-header-color: $gl-title-color; $gl-header-color: #4c4e54;
/* /*
* Lists * Lists
...@@ -269,6 +269,12 @@ $calendar-hover-bg: #ecf3fe; ...@@ -269,6 +269,12 @@ $calendar-hover-bg: #ecf3fe;
$calendar-border-color: rgba(#000, .1); $calendar-border-color: rgba(#000, .1);
$calendar-unselectable-bg: $gray-light; $calendar-unselectable-bg: $gray-light;
/*
* Cycle Analytics
*/
$cycle-analytics-box-padding: 30px;
$cycle-analytics-box-text-color: #8c8c8c;
/* /*
* Personal Access Tokens * Personal Access Tokens
*/ */
......
...@@ -197,6 +197,7 @@ lex ...@@ -197,6 +197,7 @@ lex
a { a {
color: inherit; color: inherit;
word-wrap: break-word;
} }
} }
......
...@@ -107,6 +107,14 @@ ...@@ -107,6 +107,14 @@
.block { .block {
width: 100%; width: 100%;
&.coverage {
padding: 0 16px 11px;
}
.btn-group-justified {
margin-top: 5px;
}
} }
.js-build-variable { .js-build-variable {
...@@ -210,6 +218,9 @@ ...@@ -210,6 +218,9 @@
.build-detail-row { .build-detail-row {
margin-bottom: 5px; margin-bottom: 5px;
&:last-of-type {
margin-bottom: 0;
}
} }
.build-light-text { .build-light-text {
......
#cycle-analytics { #cycle-analytics {
margin: 24px auto 0; margin: 24px auto 0;
width: 800px; max-width: 800px;
position: relative; position: relative;
.panel { .panel {
...@@ -9,11 +9,19 @@ ...@@ -9,11 +9,19 @@
padding: 24px 0; padding: 24px 0;
border-bottom: none; border-bottom: none;
position: relative; position: relative;
@media (max-width: $screen-sm-min) {
padding: 6px 0 24px;
}
} }
.column { .column {
text-align: center; text-align: center;
@media (max-width: $screen-sm-min) {
padding: 15px 0;
}
.header { .header {
font-size: 30px; font-size: 30px;
line-height: 38px; line-height: 38px;
...@@ -28,11 +36,14 @@ ...@@ -28,11 +36,14 @@
&:last-child { &:last-child {
text-align: right; text-align: right;
@media (max-width: $screen-sm-min) {
text-align: center;
}
} }
} }
.dropdown { .dropdown {
position: relative;
top: 13px; top: 13px;
} }
} }
...@@ -40,7 +51,7 @@ ...@@ -40,7 +51,7 @@
.bordered-box { .bordered-box {
border: 1px solid $border-color; border: 1px solid $border-color;
@include border-radius($border-radius-default); @include border-radius($border-radius-default);
position: relative;
} }
.content-list { .content-list {
...@@ -60,9 +71,15 @@ ...@@ -60,9 +71,15 @@
line-height: 19px; line-height: 19px;
font-size: 15px; font-size: 15px;
font-weight: 600; font-weight: 600;
color: $gl-title-color;
}
&.text {
color: $layout-link-gray;
&.value-col {
color: $gl-title-color;
} }
&:text {
color: #8c8c8c;
} }
} }
} }
...@@ -71,7 +88,9 @@ ...@@ -71,7 +88,9 @@
text-align: right; text-align: right;
span { span {
line-height: 42px; position: relative;
vertical-align: middle;
top: 3px;
} }
} }
} }
...@@ -82,21 +101,25 @@ ...@@ -82,21 +101,25 @@
.dismiss-icon { .dismiss-icon {
position: absolute; position: absolute;
right: $gl-padding; right: $cycle-analytics-box-padding;
cursor: pointer; cursor: pointer;
color: #b2b2b2; color: #b2b2b2;
} }
.svg-container {
text-align: center;
svg { svg {
margin: 0 20px;
float: left;
width: 136px; width: 136px;
height: 136px; height: 136px;
} }
}
.inner-content { .inner-content {
width: 480px; @media (max-width: $screen-sm-min) {
float: left; padding: 0 28px;
text-align: center;
}
h4 { h4 {
color: $gl-text-color; color: $gl-text-color;
...@@ -104,7 +127,7 @@ ...@@ -104,7 +127,7 @@
} }
p { p {
color: #8c8c8c; color: $cycle-analytics-box-text-color;
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
} }
} }
......
...@@ -59,6 +59,7 @@ ...@@ -59,6 +59,7 @@
} }
.encoding-selector, .encoding-selector,
.soft-wrap-toggle,
.license-selector, .license-selector,
.gitignore-selector, .gitignore-selector,
.gitlab-ci-yml-selector { .gitlab-ci-yml-selector {
...@@ -67,6 +68,24 @@ ...@@ -67,6 +68,24 @@
font-family: $regular_font; font-family: $regular_font;
} }
.soft-wrap-toggle {
margin: 0 $btn-side-margin;
.soft-wrap {
display: block;
}
.no-wrap {
display: none;
}
&.soft-wrap-active {
.soft-wrap {
display: none;
}
.no-wrap {
display: block;
}
}
}
.gitignore-selector, .license-selector, .gitlab-ci-yml-selector { .gitignore-selector, .license-selector, .gitlab-ci-yml-selector {
.dropdown { .dropdown {
line-height: 21px; line-height: 21px;
......
...@@ -55,3 +55,50 @@ ...@@ -55,3 +55,50 @@
} }
} }
} }
.groups-header {
@media (min-width: $screen-sm-min) {
.nav-links {
width: 35%;
}
.nav-controls {
width: 65%;
}
}
}
.groups-empty-state {
padding: 50px 100px;
overflow: hidden;
@media (max-width: $screen-md-min) {
padding: 50px 0;
}
svg {
float: right;
@media (max-width: $screen-md-min) {
float: none;
display: block;
width: 250px;
position: relative;
left: 50%;
margin-left: -125px;
}
}
.text-content {
float: left;
width: 460px;
margin-top: 120px;
@media (max-width: $screen-md-min) {
float: none;
margin-top: 60px;
width: auto;
text-align: center;
}
}
}
...@@ -2,7 +2,10 @@ ...@@ -2,7 +2,10 @@
max-width: 90%; max-width: 90%;
} }
li.milestone { .milestones {
.milestone {
padding: 10px 16px;
h4 { h4 {
font-weight: bold; font-weight: bold;
} }
...@@ -10,6 +13,7 @@ li.milestone { ...@@ -10,6 +13,7 @@ li.milestone {
.progress { .progress {
height: 6px; height: 6px;
} }
}
} }
.milestone-content { .milestone-content {
...@@ -29,6 +33,7 @@ li.milestone { ...@@ -29,6 +33,7 @@ li.milestone {
// Issue title // Issue title
span a { span a {
color: $gl-text-color; color: $gl-text-color;
word-wrap: break-word;
} }
} }
} }
...@@ -64,3 +69,14 @@ li.milestone { ...@@ -64,3 +69,14 @@ li.milestone {
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
padding: 20px 0; padding: 20px 0;
} }
@media (max-width: $screen-sm-min) {
.milestone-actions {
@include clearfix();
padding-top: $gl-vert-padding;
.btn:first-child {
margin-left: 0;
}
}
}
...@@ -177,6 +177,10 @@ ...@@ -177,6 +177,10 @@
border-bottom: 2px solid $border-color; border-bottom: 2px solid $border-color;
} }
} }
a {
display: block;
}
} }
} }
......
...@@ -93,8 +93,9 @@ ...@@ -93,8 +93,9 @@
.profile-user-bio { .profile-user-bio {
// Limits the width of the user bio for readability. // Limits the width of the user bio for readability.
max-width: 750px; max-width: 600px;
margin: auto; margin: 15px auto 0;
padding: 0 16px;
} }
.user-avatar-button { .user-avatar-button {
...@@ -212,6 +213,28 @@ ...@@ -212,6 +213,28 @@
} }
.user-profile { .user-profile {
.cover-controls a {
margin-left: 5px;
}
.profile-header {
margin: 0 auto;
.avatar-holder {
width: 90px;
display: inline-block;
}
.user-info {
display: inline-block;
text-align: left;
vertical-align: middle;
margin-left: 15px;
.handle {
color: $gl-gray-light;
}
.member-date {
margin-bottom: 4px;
}
}
}
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.cover-block { .cover-block {
padding-top: 20px; padding-top: 20px;
...@@ -219,14 +242,24 @@ ...@@ -219,14 +242,24 @@
.cover-controls { .cover-controls {
position: static; position: static;
padding: 0 16px;
margin-bottom: 20px; margin-bottom: 20px;
display: -webkit-flex;
display: flex;
.btn { .btn {
display: inline-block; -webkit-flex-grow: 1;
width: 46%; flex-grow: 1;
&:first-child {
margin-left: 0;
} }
} }
} }
}
}
.user-profile-nav {
margin-top: 15px;
} }
table.u2f-registrations { table.u2f-registrations {
......
...@@ -743,6 +743,62 @@ pre.light-well { ...@@ -743,6 +743,62 @@ pre.light-well {
.dropdown-menu { .dropdown-menu {
width: 300px; width: 300px;
} }
&.from .compare-dropdown-toggle {
width: 237px;
}
&.to .compare-dropdown-toggle {
width: 254px;
}
.dropdown-toggle-text {
display: block;
height: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
}
.compare-ellipsis {
display: inline;
}
@media (max-width: $screen-xs-max) {
.compare-form-group {
.input-group {
width: 100%;
& > .compare-dropdown-toggle {
width: 100%;
}
}
.dropdown-menu {
width: 100%;
}
}
.compare-switch-container {
text-align: center;
padding: 0 0 $gl-padding;
.commits-compare-switch {
float: none;
}
}
.compare-ellipsis {
display: block;
text-align: center;
padding: 0 0 $gl-padding;
}
.commits-compare-btn {
width: 100%;
}
} }
.clearable-input { .clearable-input {
......
...@@ -103,7 +103,7 @@ ...@@ -103,7 +103,7 @@
// Custom dropdown positioning // Custom dropdown positioning
.dropdown-menu { .dropdown-menu {
top: 30px; top: 37px;
left: -5px; left: -5px;
padding: 0; padding: 0;
......
...@@ -12,11 +12,18 @@ ...@@ -12,11 +12,18 @@
.snippet-file-content { .snippet-file-content {
border-radius: 3px; border-radius: 3px;
margin-bottom: $gl-padding;
.btn-clipboard { .btn-clipboard {
@extend .btn; @extend .btn;
} }
} }
.project-snippets .awards {
border-bottom: 1px solid $table-border-color;
padding-bottom: $gl-padding;
}
.snippet-title { .snippet-title {
font-size: 24px; font-size: 24px;
font-weight: 600; font-weight: 600;
...@@ -29,3 +36,7 @@ ...@@ -29,3 +36,7 @@
float: right; float: right;
} }
} }
.snippet-scope-menu .btn-new {
margin-top: 15px;
}
...@@ -27,7 +27,12 @@ ...@@ -27,7 +27,12 @@
} }
.last-commit { .last-commit {
@include str-truncated(60%); @include str-truncated(506px);
@media (min-width: $screen-sm-max) and (max-width: $screen-md-max) {
@include str-truncated(450px);
}
} }
.commit-history-link-spacer { .commit-history-link-spacer {
...@@ -55,6 +60,15 @@ ...@@ -55,6 +60,15 @@
} }
.tree-item { .tree-item {
.link-container {
padding: 0;
a {
padding: 10px $gl-padding;
display: block;
}
}
.tree-item-file-name { .tree-item-file-name {
max-width: 320px; max-width: 320px;
vertical-align: middle; vertical-align: middle;
......
...@@ -10,7 +10,7 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -10,7 +10,7 @@ class Admin::GroupsController < Admin::ApplicationController
def show def show
@members = @group.members.order("access_level DESC").page(params[:members_page]) @members = @group.members.order("access_level DESC").page(params[:members_page])
@requesters = @group.requesters @requesters = AccessRequestsFinder.new(@group).execute(current_user)
@projects = @group.projects.page(params[:projects_page]) @projects = @group.projects.page(params[:projects_page])
end end
......
...@@ -22,7 +22,7 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -22,7 +22,7 @@ class Admin::ProjectsController < Admin::ApplicationController
end end
@project_members = @project.members.page(params[:project_members_page]) @project_members = @project.members.page(params[:project_members_page])
@requesters = @project.requesters @requesters = AccessRequestsFinder.new(@project).execute(current_user)
end end
def transfer def transfer
......
...@@ -14,6 +14,7 @@ module Ci ...@@ -14,6 +14,7 @@ module Ci
@config_processor = Ci::GitlabCiYamlProcessor.new(@content) @config_processor = Ci::GitlabCiYamlProcessor.new(@content)
@stages = @config_processor.stages @stages = @config_processor.stages
@builds = @config_processor.builds @builds = @config_processor.builds
@jobs = @config_processor.jobs
end end
rescue rescue
@error = 'Undefined error' @error = 'Undefined error'
......
...@@ -13,18 +13,10 @@ module IssuableCollections ...@@ -13,18 +13,10 @@ module IssuableCollections
issues_finder.execute issues_finder.execute
end end
def all_issues_collection
IssuesFinder.new(current_user, filter_params_all).execute
end
def merge_requests_collection def merge_requests_collection
merge_requests_finder.execute merge_requests_finder.execute
end end
def all_merge_requests_collection
MergeRequestsFinder.new(current_user, filter_params_all).execute
end
def issues_finder def issues_finder
@issues_finder ||= issuable_finder_for(IssuesFinder) @issues_finder ||= issuable_finder_for(IssuesFinder)
end end
...@@ -62,10 +54,6 @@ module IssuableCollections ...@@ -62,10 +54,6 @@ module IssuableCollections
@filter_params @filter_params
end end
def filter_params_all
@filter_params_all ||= filter_params.merge(state: 'all', sort: nil)
end
def set_default_scope def set_default_scope
params[:scope] = 'all' if params[:scope].blank? params[:scope] = 'all' if params[:scope].blank?
end end
......
...@@ -10,8 +10,6 @@ module IssuesAction ...@@ -10,8 +10,6 @@ module IssuesAction
.preload(:author, :project) .preload(:author, :project)
.page(params[:page]) .page(params[:page])
@all_issues = all_issues_collection.non_archived
respond_to do |format| respond_to do |format|
format.html format.html
format.atom { render layout: false } format.atom { render layout: false }
......
module MembershipActions module MembershipActions
extend ActiveSupport::Concern extend ActiveSupport::Concern
include MembersHelper
def request_access def request_access
membershipable.request_access(current_user) membershipable.request_access(current_user)
...@@ -10,11 +9,7 @@ module MembershipActions ...@@ -10,11 +9,7 @@ module MembershipActions
end end
def approve_access_request def approve_access_request
@member = membershipable.requesters.find(params[:id]) Members::ApproveAccessRequestService.new(membershipable, current_user, params).execute
return render_403 unless can?(current_user, action_member_permission(:update, @member), @member)
@member.accept_request
redirect_to polymorphic_url([membershipable, :members]) redirect_to polymorphic_url([membershipable, :members])
end end
......
...@@ -9,7 +9,5 @@ module MergeRequestsAction ...@@ -9,7 +9,5 @@ module MergeRequestsAction
.non_archived .non_archived
.preload(:author, :target_project) .preload(:author, :target_project)
.page(params[:page]) .page(params[:page])
@all_merge_requests = all_merge_requests_collection.non_archived
end end
end end
...@@ -7,7 +7,7 @@ module SpammableActions ...@@ -7,7 +7,7 @@ module SpammableActions
def mark_as_spam def mark_as_spam
if SpamService.new(spammable).mark_as_spam! if SpamService.new(spammable).mark_as_spam!
redirect_to spammable, notice: "#{spammable.class.to_s} was submitted to Akismet successfully." redirect_to spammable, notice: "#{spammable.class} was submitted to Akismet successfully."
else else
redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.' redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.'
end end
......
...@@ -10,7 +10,9 @@ module ToggleAwardEmoji ...@@ -10,7 +10,9 @@ module ToggleAwardEmoji
if awardable.user_can_award?(current_user, name) if awardable.user_can_award?(current_user, name)
awardable.toggle_award_emoji(name, current_user) awardable.toggle_award_emoji(name, current_user)
TodoService.new.new_award_emoji(to_todoable(awardable), current_user)
todoable = to_todoable(awardable)
TodoService.new.new_award_emoji(todoable, current_user) if todoable
render json: { ok: true } render json: { ok: true }
else else
...@@ -24,8 +26,10 @@ module ToggleAwardEmoji ...@@ -24,8 +26,10 @@ module ToggleAwardEmoji
case awardable case awardable
when Note when Note
awardable.noteable awardable.noteable
else when MergeRequest, Issue
awardable awardable
when Snippet
nil
end end
end end
......
...@@ -15,7 +15,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -15,7 +15,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
@members = @members.order('access_level DESC').page(params[:page]).per(50) @members = @members.order('access_level DESC').page(params[:page]).per(50)
@requesters = @group.requesters if can?(current_user, :admin_group, @group) @requesters = AccessRequestsFinder.new(@group).execute(current_user)
@group_member = @group.group_members.new @group_member = @group.group_members.new
end end
......
class Import::GitlabProjectsController < Import::BaseController class Import::GitlabProjectsController < Import::BaseController
before_action :verify_gitlab_project_import_enabled before_action :verify_gitlab_project_import_enabled
before_action :authenticate_admin!
def new def new
@namespace_id = project_params[:namespace_id] @namespace_id = project_params[:namespace_id]
...@@ -48,8 +47,4 @@ class Import::GitlabProjectsController < Import::BaseController ...@@ -48,8 +47,4 @@ class Import::GitlabProjectsController < Import::BaseController
:path, :namespace_id, :file :path, :namespace_id, :file
) )
end end
def authenticate_admin!
render_404 unless current_user.is_admin?
end
end end
...@@ -11,10 +11,8 @@ class JwtController < ApplicationController ...@@ -11,10 +11,8 @@ class JwtController < ApplicationController
service = SERVICES[params[:service]] service = SERVICES[params[:service]]
return head :not_found unless service return head :not_found unless service
@authentication_result ||= Gitlab::Auth::Result.new
result = service.new(@authentication_result.project, @authentication_result.actor, auth_params). result = service.new(@authentication_result.project, @authentication_result.actor, auth_params).
execute(authentication_abilities: @authentication_result.authentication_abilities) execute(authentication_abilities: @authentication_result.authentication_abilities || [])
render json: result, status: result[:http_status] render json: result, status: result[:http_status]
end end
...@@ -22,10 +20,12 @@ class JwtController < ApplicationController ...@@ -22,10 +20,12 @@ class JwtController < ApplicationController
private private
def authenticate_project_or_user def authenticate_project_or_user
@authentication_result = Gitlab::Auth::Result.new
authenticate_with_http_basic do |login, password| authenticate_with_http_basic do |login, password|
@authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip) @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip)
render_403 unless @authentication_result.success? && render_unauthorized unless @authentication_result.success? &&
(@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User)) (@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User))
end end
rescue Gitlab::Auth::MissingPersonalTokenError rescue Gitlab::Auth::MissingPersonalTokenError
...@@ -33,10 +33,21 @@ class JwtController < ApplicationController ...@@ -33,10 +33,21 @@ class JwtController < ApplicationController
end end
def render_missing_personal_token def render_missing_personal_token
render plain: "HTTP Basic: Access denied\n" \ render json: {
errors: [
{ code: 'UNAUTHORIZED',
message: "HTTP Basic: Access denied\n" \
"You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \ "You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \
"You can generate one at #{profile_personal_access_tokens_url}", "You can generate one at #{profile_personal_access_tokens_url}" }
status: 401 ] }, status: 401
end
def render_unauthorized
render json: {
errors: [
{ code: 'UNAUTHORIZED',
message: 'HTTP Basic: Access denied' }
] }, status: 401
end end
def auth_params def auth_params
......
...@@ -73,7 +73,8 @@ class ProfilesController < Profiles::ApplicationController ...@@ -73,7 +73,8 @@ class ProfilesController < Profiles::ApplicationController
:skype, :skype,
:twitter, :twitter,
:username, :username,
:website_url :website_url,
:organization
) )
end end
end end
...@@ -33,7 +33,7 @@ module Projects ...@@ -33,7 +33,7 @@ module Projects
def issue def issue
@issue ||= @issue ||=
IssuesFinder.new(current_user, project_id: project.id, state: 'all') IssuesFinder.new(current_user, project_id: project.id)
.execute .execute
.where(iid: params[:id]) .where(iid: params[:id])
.first! .first!
......
...@@ -10,10 +10,11 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -10,10 +10,11 @@ class Projects::CommitController < Projects::ApplicationController
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :authorize_download_code!, except: [:cancel_builds, :retry_builds] before_action :authorize_download_code!, except: [:cancel_builds, :retry_builds]
before_action :authorize_update_build!, only: [:cancel_builds, :retry_builds] before_action :authorize_update_build!, only: [:cancel_builds, :retry_builds]
before_action :authorize_read_pipeline!, only: [:pipelines]
before_action :authorize_read_commit_status!, only: [:builds] before_action :authorize_read_commit_status!, only: [:builds]
before_action :commit before_action :commit
before_action :define_commit_vars, only: [:show, :diff_for_path, :builds] before_action :define_commit_vars, only: [:show, :diff_for_path, :builds, :pipelines]
before_action :define_status_vars, only: [:show, :builds] before_action :define_status_vars, only: [:show, :builds, :pipelines]
before_action :define_note_vars, only: [:show, :diff_for_path] before_action :define_note_vars, only: [:show, :diff_for_path]
before_action :authorize_edit_tree!, only: [:revert, :cherry_pick] before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
...@@ -31,6 +32,9 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -31,6 +32,9 @@ class Projects::CommitController < Projects::ApplicationController
render_diff_for_path(@commit.diffs(diff_options)) render_diff_for_path(@commit.diffs(diff_options))
end end
def pipelines
end
def builds def builds
end end
...@@ -96,10 +100,6 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -96,10 +100,6 @@ class Projects::CommitController < Projects::ApplicationController
@noteable = @commit ||= @project.commit(params[:id]) @noteable = @commit ||= @project.commit(params[:id])
end end
def pipelines
@pipelines ||= project.pipelines.where(sha: commit.sha)
end
def ci_builds def ci_builds
@ci_builds ||= Ci::Build.where(pipeline: pipelines) @ci_builds ||= Ci::Build.where(pipeline: pipelines)
end end
...@@ -134,8 +134,9 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -134,8 +134,9 @@ class Projects::CommitController < Projects::ApplicationController
end end
def define_status_vars def define_status_vars
@statuses = CommitStatus.where(pipeline: pipelines).relevant @ci_pipelines = project.pipelines.where(sha: commit.sha)
@builds = Ci::Build.where(pipeline: pipelines).relevant @statuses = CommitStatus.where(pipeline: @ci_pipelines).relevant
@builds = Ci::Build.where(pipeline: @ci_pipelines).relevant
end end
def assign_change_commit_vars(mr_source_branch) def assign_change_commit_vars(mr_source_branch)
......
...@@ -32,11 +32,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -32,11 +32,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController
return # Allow access return # Allow access
end end
elsif allow_kerberos_spnego_auth? && spnego_provided? elsif allow_kerberos_spnego_auth? && spnego_provided?
user = find_kerberos_user kerberos_user = find_kerberos_user
if user if kerberos_user
@authentication_result = Gitlab::Auth::Result.new( @authentication_result = Gitlab::Auth::Result.new(
user, nil, :kerberos, Gitlab::Auth.full_authentication_abilities) kerberos_user, nil, :kerberos, Gitlab::Auth.full_authentication_abilities)
send_final_spnego_response send_final_spnego_response
return # Allow access return # Allow access
......
...@@ -28,8 +28,6 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -28,8 +28,6 @@ class Projects::IssuesController < Projects::ApplicationController
@labels = @project.labels.where(title: params[:label_name]) @labels = @project.labels.where(title: params[:label_name])
@all_issues = all_issues_collection
respond_to do |format| respond_to do |format|
format.html format.html
format.atom { render layout: false } format.atom { render layout: false }
...@@ -56,7 +54,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -56,7 +54,7 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def show def show
raw_notes = @issue.notes_with_associations.fresh raw_notes = @issue.notes.inc_relations_for_view.fresh
@notes = Banzai::NoteRenderer. @notes = Banzai::NoteRenderer.
render(raw_notes, @project, current_user, @path, @project_wiki, @ref) render(raw_notes, @project, current_user, @path, @project_wiki, @ref)
......
...@@ -18,6 +18,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -18,6 +18,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :define_commit_vars, only: [:diffs] before_action :define_commit_vars, only: [:diffs]
before_action :define_diff_comment_vars, only: [:diffs] before_action :define_diff_comment_vars, only: [:diffs]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines] before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines]
before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines]
# Allow read any merge_request # Allow read any merge_request
before_action :authorize_read_merge_request! before_action :authorize_read_merge_request!
...@@ -37,8 +38,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -37,8 +38,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@labels = @project.labels.where(title: params[:label_name]) @labels = @project.labels.where(title: params[:label_name])
@all_merge_requests = all_merge_requests_collection
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
...@@ -277,7 +276,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -277,7 +276,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def remove_wip def remove_wip
MergeRequests::UpdateService.new(project, current_user, title: @merge_request.wipless_title).execute(@merge_request) MergeRequests::UpdateService.new(project, current_user, wip_event: 'unwip').execute(@merge_request)
redirect_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), redirect_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request),
notice: "The merge request can now be merged." notice: "The merge request can now be merged."
...@@ -310,8 +309,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -310,8 +309,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return return
end end
TodoService.new.merge_merge_request(merge_request, current_user)
@merge_request.update(merge_error: nil) @merge_request.update(merge_error: nil)
if params[:merge_when_build_succeeds].present? if params[:merge_when_build_succeeds].present?
...@@ -420,10 +417,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -420,10 +417,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def validates_merge_request def validates_merge_request
# If source project was removed and merge request for some reason
# wasn't close (Ex. mr from fork to origin)
return invalid_mr if !@merge_request.source_project && @merge_request.open?
# Show git not found page # Show git not found page
# if there is no saved commits between source & target branch # if there is no saved commits between source & target branch
if @merge_request.commits.blank? if @merge_request.commits.blank?
...@@ -498,7 +491,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -498,7 +491,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def invalid_mr def invalid_mr
# Render special view for MR with removed source or target branch # Render special view for MR with removed target branch
render 'invalid' render 'invalid'
end end
...@@ -540,4 +533,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -540,4 +533,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@diff_notes_disabled = !@merge_request_diff.latest? @diff_notes_disabled = !@merge_request_diff.latest?
@diffs = @merge_request_diff.diffs(diff_options) @diffs = @merge_request_diff.diffs(diff_options)
end end
def close_merge_request_without_source_project
if !@merge_request.source_project && @merge_request.open?
@merge_request.close
end
end
end end
...@@ -29,7 +29,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -29,7 +29,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@group_members = @group_members.order('access_level DESC') @group_members = @group_members.order('access_level DESC')
end end
@requesters = @project.requesters if can?(current_user, :admin_project, @project) @requesters = AccessRequestsFinder.new(@project).execute(current_user)
@project_member = @project.project_members.new @project_member = @project.project_members.new
@project_group_links = @project.project_group_links @project_group_links = @project.project_group_links
......
class Projects::SnippetsController < Projects::ApplicationController class Projects::SnippetsController < Projects::ApplicationController
include ToggleAwardEmoji
before_action :module_enabled before_action :module_enabled
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji]
# Allow read any snippet # Allow read any snippet
before_action :authorize_read_project_snippet!, except: [:new, :create, :index] before_action :authorize_read_project_snippet!, except: [:new, :create, :index]
...@@ -80,6 +82,7 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -80,6 +82,7 @@ class Projects::SnippetsController < Projects::ApplicationController
def snippet def snippet
@snippet ||= @project.snippets.find(params[:id]) @snippet ||= @project.snippets.find(params[:id])
end end
alias_method :awardable, :snippet
def authorize_read_project_snippet! def authorize_read_project_snippet!
return render_404 unless can?(current_user, :read_project_snippet, @snippet) return render_404 unless can?(current_user, :read_project_snippet, @snippet)
......
...@@ -137,10 +137,10 @@ class ProjectsController < Projects::ApplicationController ...@@ -137,10 +137,10 @@ class ProjectsController < Projects::ApplicationController
noteable = noteable =
case params[:type] case params[:type]
when 'Issue' when 'Issue'
IssuesFinder.new(current_user, project_id: @project.id, state: 'all'). IssuesFinder.new(current_user, project_id: @project.id).
execute.find_by(iid: params[:type_id]) execute.find_by(iid: params[:type_id])
when 'MergeRequest' when 'MergeRequest'
MergeRequestsFinder.new(current_user, project_id: @project.id, state: 'all'). MergeRequestsFinder.new(current_user, project_id: @project.id).
execute.find_by(iid: params[:type_id]) execute.find_by(iid: params[:type_id])
when 'Commit' when 'Commit'
@project.commit(params[:type_id]) @project.commit(params[:type_id])
...@@ -324,7 +324,12 @@ class ProjectsController < Projects::ApplicationController ...@@ -324,7 +324,12 @@ class ProjectsController < Projects::ApplicationController
end end
def repo_exists? def repo_exists?
project.repository_exists? && !project.empty_repo? project.repository_exists? && !project.empty_repo? && project.repo
rescue Gitlab::Git::Repository::NoRepository
project.repository.expire_exists_cache
false
end end
def project_view_files? def project_view_files?
......
...@@ -6,8 +6,6 @@ class SearchController < ApplicationController ...@@ -6,8 +6,6 @@ class SearchController < ApplicationController
layout 'search' layout 'search'
def show def show
return if params[:search].nil? || params[:search].blank?
if params[:project_id].present? if params[:project_id].present?
@project = Project.find_by(id: params[:project_id]) @project = Project.find_by(id: params[:project_id])
@project = nil unless can?(current_user, :download_code, @project) @project = nil unless can?(current_user, :download_code, @project)
...@@ -18,6 +16,8 @@ class SearchController < ApplicationController ...@@ -18,6 +16,8 @@ class SearchController < ApplicationController
@group = nil unless can?(current_user, :read_group, @group) @group = nil unless can?(current_user, :read_group, @group)
end end
return if params[:search].nil? || params[:search].blank?
@search_term = params[:search] @search_term = params[:search]
@scope = params[:scope] @scope = params[:scope]
......
class SnippetsController < ApplicationController class SnippetsController < ApplicationController
include ToggleAwardEmoji
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read snippet # Allow read snippet
...@@ -85,6 +87,7 @@ class SnippetsController < ApplicationController ...@@ -85,6 +87,7 @@ class SnippetsController < ApplicationController
PersonalSnippet.find(params[:id]) PersonalSnippet.find(params[:id])
end end
end end
alias_method :awardable, :snippet
def authorize_read_snippet! def authorize_read_snippet!
authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet) authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet)
......
...@@ -65,7 +65,7 @@ class UsersController < ApplicationController ...@@ -65,7 +65,7 @@ class UsersController < ApplicationController
format.html { render 'show' } format.html { render 'show' }
format.json do format.json do
render json: { render json: {
html: view_to_html_string("snippets/_snippets", collection: @snippets) html: view_to_html_string("snippets/_snippets", collection: @snippets, remote: true)
} }
end end
end end
......
class AccessRequestsFinder
attr_accessor :source
# Arguments:
# source - a Group or Project
def initialize(source)
@source = source
end
def execute(*args)
execute!(*args)
rescue Gitlab::Access::AccessDeniedError
[]
end
def execute!(current_user)
raise Gitlab::Access::AccessDeniedError unless can_see_access_requests?(current_user)
source.requesters
end
private
def can_see_access_requests?(current_user)
source && Ability.allowed?(current_user, :"admin_#{source.class.to_s.underscore}", source)
end
end
...@@ -183,17 +183,12 @@ class IssuableFinder ...@@ -183,17 +183,12 @@ class IssuableFinder
end end
def by_state(items) def by_state(items)
case params[:state] params[:state] ||= 'all'
when 'closed'
items.closed if items.respond_to?(params[:state])
when 'merged' items.public_send(params[:state])
items.respond_to?(:merged) ? items.merged : items.closed
when 'all'
items
when 'opened'
items.opened
else else
raise 'You must specify default state' items
end end
end end
......
...@@ -280,23 +280,6 @@ module ApplicationHelper ...@@ -280,23 +280,6 @@ module ApplicationHelper
end end
end end
def state_filters_text_for(state, records)
titles = {
opened: "Open"
}
state_title = titles[state] || state.to_s.humanize
count = records.public_send(state).size
html = content_tag :span, state_title
if count.present?
html += " "
html += content_tag :span, number_with_delimiter(count), class: 'badge'
end
html.html_safe
end
def truncate_first_line(message, length = 50) def truncate_first_line(message, length = 50)
truncate(message.each_line.first.chomp, length: length) if message truncate(message.each_line.first.chomp, length: length) if message
end end
......
module AwardEmojiHelper
def toggle_award_url(awardable)
if @project
url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
else
url_for([:toggle_award_emoji, awardable])
end
end
end
...@@ -56,7 +56,7 @@ module CiStatusHelper ...@@ -56,7 +56,7 @@ module CiStatusHelper
def render_commit_status(commit, tooltip_placement: 'auto left') def render_commit_status(commit, tooltip_placement: 'auto left')
project = commit.project project = commit.project
path = builds_namespace_project_commit_path(project.namespace, project, commit) path = pipelines_namespace_project_commit_path(project.namespace, project, commit)
render_status_with_link('commit', commit.status, path, tooltip_placement: tooltip_placement) render_status_with_link('commit', commit.status, path, tooltip_placement: tooltip_placement)
end end
......
...@@ -70,6 +70,10 @@ module GitlabRoutingHelper ...@@ -70,6 +70,10 @@ module GitlabRoutingHelper
namespace_project_runner_path(@project.namespace, @project, runner, *args) namespace_project_runner_path(@project.namespace, @project, runner, *args)
end end
def environment_path(environment, *args)
namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
end
def issue_path(entity, *args) def issue_path(entity, *args)
namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args) namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
end end
...@@ -102,6 +106,14 @@ module GitlabRoutingHelper ...@@ -102,6 +106,14 @@ module GitlabRoutingHelper
end end
end end
def toggle_award_emoji_personal_snippet_path(*args)
toggle_award_emoji_snippet_path(*args)
end
def toggle_award_emoji_namespace_project_project_snippet_path(*args)
toggle_award_emoji_namespace_project_snippet_path(*args)
end
## Members ## Members
def project_members_url(project, *args) def project_members_url(project, *args)
namespace_project_project_members_url(project.namespace, project) namespace_project_project_members_url(project.namespace, project)
......
...@@ -94,6 +94,24 @@ module IssuablesHelper ...@@ -94,6 +94,24 @@ module IssuablesHelper
label_names.join(', ') label_names.join(', ')
end end
def issuables_state_counter_text(issuable_type, state)
titles = {
opened: "Open"
}
state_title = titles[state] || state.to_s.humanize
count =
Rails.cache.fetch(issuables_state_counter_cache_key(issuable_type, state), expires_in: 2.minutes) do
issuables_count_for_state(issuable_type, state)
end
html = content_tag(:span, state_title)
html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge')
html.html_safe
end
private private
def sidebar_gutter_collapsed? def sidebar_gutter_collapsed?
...@@ -111,4 +129,22 @@ module IssuablesHelper ...@@ -111,4 +129,22 @@ module IssuablesHelper
issuable.open? ? :opened : :closed issuable.open? ? :opened : :closed
end end
end end
def issuables_count_for_state(issuable_type, state)
issuables_finder = public_send("#{issuable_type}_finder")
issuables_finder.params[:state] = state
issuables_finder.execute.page(1).total_count
end
IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page]
private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY
def issuables_state_counter_cache_key(issuable_type, state)
opts = params.with_indifferent_access
opts[:state] = state
opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-'))
end
end end
module LfsHelper module LfsHelper
include Gitlab::Routing.url_helpers
def require_lfs_enabled! def require_lfs_enabled!
return if Gitlab.config.lfs.enabled return if Gitlab.config.lfs.enabled
render( render(
json: { json: {
message: 'Git LFS is not enabled on this GitLab server, contact your admin.', message: 'Git LFS is not enabled on this GitLab server, contact your admin.',
documentation_url: "#{Gitlab.config.gitlab.url}/help", documentation_url: help_url,
}, },
status: 501 status: 501
) )
...@@ -46,7 +48,7 @@ module LfsHelper ...@@ -46,7 +48,7 @@ module LfsHelper
render( render(
json: { json: {
message: 'Access forbidden. Check your access level.', message: 'Access forbidden. Check your access level.',
documentation_url: "#{Gitlab.config.gitlab.url}/help", documentation_url: help_url,
}, },
content_type: "application/vnd.git-lfs+json", content_type: "application/vnd.git-lfs+json",
status: 403 status: 403
...@@ -57,7 +59,7 @@ module LfsHelper ...@@ -57,7 +59,7 @@ module LfsHelper
render( render(
json: { json: {
message: 'Not found.', message: 'Not found.',
documentation_url: "#{Gitlab.config.gitlab.url}/help", documentation_url: help_url,
}, },
content_type: "application/vnd.git-lfs+json", content_type: "application/vnd.git-lfs+json",
status: 404 status: 404
......
...@@ -35,6 +35,30 @@ module MilestonesHelper ...@@ -35,6 +35,30 @@ module MilestonesHelper
milestone.issues.with_label(label.title).send(state).size milestone.issues.with_label(label.title).send(state).size
end end
# Returns count of milestones for different states
# Uses explicit hash keys as the 'opened' state URL params differs from the db value
# and we need to add the total
def milestone_counts(milestones)
counts = milestones.reorder(nil).group(:state).count
{
opened: counts['active'] || 0,
closed: counts['closed'] || 0,
all: counts.values.sum || 0
}
end
# Show 'active' class if provided GET param matches check
# `or_blank` allows the function to return 'active' when given an empty param
# Could be refactored to be simpler but that may make it harder to read
def milestone_class_for_state(param, check, match_blank_param = false)
if match_blank_param
'active' if param.blank? || param == check
else
'active' if param == check
end
end
def milestone_progress_bar(milestone) def milestone_progress_bar(milestone)
options = { options = {
class: 'progress-bar progress-bar-success', class: 'progress-bar progress-bar-success',
......
...@@ -139,7 +139,7 @@ module ProjectsHelper ...@@ -139,7 +139,7 @@ module ProjectsHelper
end end
options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field)) options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field))
content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control", data: { field: field }).html_safe content_tag(:select, options, name: "project[project_feature_attributes][#{field}]", id: "project_project_feature_attributes_#{field}", class: "pull-right form-control", data: { field: field }).html_safe
end end
private private
......
...@@ -3,4 +3,12 @@ class DeviseMailer < Devise::Mailer ...@@ -3,4 +3,12 @@ class DeviseMailer < Devise::Mailer
default reply_to: Gitlab.config.gitlab.email_reply_to default reply_to: Gitlab.config.gitlab.email_reply_to
layout 'devise_mailer' layout 'devise_mailer'
protected
def subject_for(key)
subject = super
subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present?
subject
end
end end
...@@ -45,7 +45,7 @@ module Emails ...@@ -45,7 +45,7 @@ module Emails
@token = token @token = token
mail(to: member.invite_email, mail(to: member.invite_email,
subject: "Invitation to join the #{member_source.human_name} #{member_source.model_name.singular}") subject: subject("Invitation to join the #{member_source.human_name} #{member_source.model_name.singular}"))
end end
def member_invite_accepted_email(member_source_type, member_id) def member_invite_accepted_email(member_source_type, member_id)
......
...@@ -93,6 +93,7 @@ class Notify < BaseMailer ...@@ -93,6 +93,7 @@ class Notify < BaseMailer
subject = "" subject = ""
subject << "#{@project.name} | " if @project subject << "#{@project.name} | " if @project
subject << extra.join(' | ') if extra.present? subject << extra.join(' | ') if extra.present?
subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present?
subject subject
end end
...@@ -110,7 +111,7 @@ class Notify < BaseMailer ...@@ -110,7 +111,7 @@ class Notify < BaseMailer
headers['X-GitLab-Reply-Key'] = reply_key headers['X-GitLab-Reply-Key'] = reply_key
if !@labels_url && @sent_notification && @sent_notification.unsubscribable? if !@labels_url && @sent_notification && @sent_notification.unsubscribable?
headers['List-Unsubscribe'] = unsubscribe_sent_notification_url(@sent_notification, force: true) headers['List-Unsubscribe'] = "<#{unsubscribe_sent_notification_url(@sent_notification, force: true)}>"
@sent_notification_url = unsubscribe_sent_notification_url(@sent_notification) @sent_notification_url = unsubscribe_sent_notification_url(@sent_notification)
end end
......
...@@ -4,4 +4,12 @@ class Board < ActiveRecord::Base ...@@ -4,4 +4,12 @@ class Board < ActiveRecord::Base
has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all
validates :project, presence: true validates :project, presence: true
def backlog_list
lists.merge(List.backlog).take
end
def done_list
lists.merge(List.done).take
end
end end
...@@ -91,7 +91,7 @@ module Ci ...@@ -91,7 +91,7 @@ module Ci
sha: build.sha, sha: build.sha,
ref: build.ref, ref: build.ref,
tag: build.tag, tag: build.tag,
options: build.options[:environment], options: build.options.to_h[:environment],
variables: build.variables) variables: build.variables)
service.execute(build) service.execute(build)
end end
...@@ -378,7 +378,7 @@ module Ci ...@@ -378,7 +378,7 @@ module Ci
end end
def artifacts? def artifacts?
!artifacts_expired? && self[:artifacts_file].present? !artifacts_expired? && artifacts_file.exists?
end end
def artifacts_metadata? def artifacts_metadata?
...@@ -498,8 +498,11 @@ module Ci ...@@ -498,8 +498,11 @@ module Ci
end end
def hide_secrets(trace) def hide_secrets(trace)
trace = Ci::MaskSecret.mask(trace, project.runners_token) if project return unless trace
trace = Ci::MaskSecret.mask(trace, token)
trace = trace.dup
Ci::MaskSecret.mask!(trace, project.runners_token) if project
Ci::MaskSecret.mask!(trace, token)
trace trace
end end
end end
......
...@@ -80,7 +80,7 @@ class CommitRange ...@@ -80,7 +80,7 @@ class CommitRange
end end
def inspect def inspect
%(#<#{self.class}:#{object_id} #{to_s}>) %(#<#{self.class}:#{object_id} #{self}>)
end end
def to_s def to_s
......
...@@ -8,9 +8,6 @@ module AccessRequestable ...@@ -8,9 +8,6 @@ module AccessRequestable
extend ActiveSupport::Concern extend ActiveSupport::Concern
def request_access(user) def request_access(user)
members.create( Members::RequestAccessService.new(self, user).execute
access_level: Gitlab::Access::DEVELOPER,
user: user,
requested_at: Time.now.utc)
end end
end end
...@@ -71,6 +71,12 @@ module Awardable ...@@ -71,6 +71,12 @@ module Awardable
end end
end end
def user_authored?(current_user)
author = self.respond_to?(:author) ? self.author : self.user
author == current_user
end
def awarded_emoji?(emoji_name, current_user) def awarded_emoji?(emoji_name, current_user)
award_emoji.where(name: emoji_name, user: current_user).exists? award_emoji.where(name: emoji_name, user: current_user).exists?
end end
......
...@@ -200,10 +200,6 @@ module Issuable ...@@ -200,10 +200,6 @@ module Issuable
end end
end end
def user_authored?(user)
user == author
end
def subscribed_without_subscriptions?(user) def subscribed_without_subscriptions?(user)
participants(user).include?(user) participants(user).include?(user)
end end
......
...@@ -10,15 +10,33 @@ class CycleAnalytics ...@@ -10,15 +10,33 @@ class CycleAnalytics
end end
def commits def commits
repository = @project.repository.raw_repository ref = @project.default_branch.presence
count_commits_for(ref)
if @project.default_branch
repository.log(ref: @project.default_branch, after: @from).count
end
end end
def deploys def deploys
@project.deployments.where("created_at > ?", @from).count @project.deployments.where("created_at > ?", @from).count
end end
private
# Don't use the `Gitlab::Git::Repository#log` method, because it enforces
# a limit. Since we need a commit count, we _can't_ enforce a limit, so
# the easiest way forward is to replicate the relevant portions of the
# `log` function here.
def count_commits_for(ref)
return unless ref
repository = @project.repository.raw_repository
sha = @project.repository.commit(ref).sha
cmd = %W(git --git-dir=#{repository.path} log)
cmd << '--format=%H'
cmd << "--after=#{@from.iso8601}"
cmd << sha
raw_output = IO.popen(cmd) { |io| io.read }
raw_output.lines.count
end
end end
end end
...@@ -8,7 +8,8 @@ class GlobalMilestone ...@@ -8,7 +8,8 @@ class GlobalMilestone
milestones = milestones.group_by(&:title) milestones = milestones.group_by(&:title)
milestones.map do |title, milestones| milestones.map do |title, milestones|
new(title, milestones) milestones_relation = Milestone.where(id: milestones.map(&:id))
new(title, milestones_relation)
end end
end end
...@@ -31,7 +32,7 @@ class GlobalMilestone ...@@ -31,7 +32,7 @@ class GlobalMilestone
end end
def projects def projects
@projects ||= Project.for_milestones(milestones.map(&:id)) @projects ||= Project.for_milestones(milestones.select(:id))
end end
def state def state
...@@ -53,19 +54,19 @@ class GlobalMilestone ...@@ -53,19 +54,19 @@ class GlobalMilestone
end end
def issues def issues
@issues ||= Issue.of_milestones(milestones.map(&:id)).includes(:project) @issues ||= Issue.of_milestones(milestones.select(:id)).includes(:project, :assignee, :labels)
end end
def merge_requests def merge_requests
@merge_requests ||= MergeRequest.of_milestones(milestones.map(&:id)).includes(:target_project) @merge_requests ||= MergeRequest.of_milestones(milestones.select(:id)).includes(:target_project, :assignee, :labels)
end end
def participants def participants
@participants ||= milestones.map(&:participants).flatten.compact.uniq @participants ||= milestones.includes(:participants).map(&:participants).flatten.compact.uniq
end end
def labels def labels
@labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten) @labels ||= GlobalLabel.build_collection(milestones.includes(:labels).map(&:labels).flatten)
.sort_by!(&:title) .sort_by!(&:title)
end end
......
...@@ -102,40 +102,44 @@ class Group < Namespace ...@@ -102,40 +102,44 @@ class Group < Namespace
self[:lfs_enabled] self[:lfs_enabled]
end end
def add_users(user_ids, access_level, current_user: nil, expires_at: nil) def add_users(users, access_level, current_user: nil, expires_at: nil)
user_ids.each do |user_id| GroupMember.add_users_to_group(
Member.add_user( self,
self.group_members, users,
user_id,
access_level, access_level,
current_user: current_user, current_user: current_user,
expires_at: expires_at expires_at: expires_at
) )
end end
end
def add_user(user, access_level, current_user: nil, expires_at: nil) def add_user(user, access_level, current_user: nil, expires_at: nil)
add_users([user], access_level, current_user: current_user, expires_at: expires_at) GroupMember.add_user(
self,
user,
access_level,
current_user: current_user,
expires_at: expires_at
)
end end
def add_guest(user, current_user = nil) def add_guest(user, current_user = nil)
add_user(user, Gitlab::Access::GUEST, current_user: current_user) add_user(user, :guest, current_user: current_user)
end end
def add_reporter(user, current_user = nil) def add_reporter(user, current_user = nil)
add_user(user, Gitlab::Access::REPORTER, current_user: current_user) add_user(user, :reporter, current_user: current_user)
end end
def add_developer(user, current_user = nil) def add_developer(user, current_user = nil)
add_user(user, Gitlab::Access::DEVELOPER, current_user: current_user) add_user(user, :developer, current_user: current_user)
end end
def add_master(user, current_user = nil) def add_master(user, current_user = nil)
add_user(user, Gitlab::Access::MASTER, current_user: current_user) add_user(user, :master, current_user: current_user)
end end
def add_owner(user, current_user = nil) def add_owner(user, current_user = nil)
add_user(user, Gitlab::Access::OWNER, current_user: current_user) add_user(user, :owner, current_user: current_user)
end end
def has_owner?(user) def has_owner?(user)
......
...@@ -80,49 +80,70 @@ class Member < ActiveRecord::Base ...@@ -80,49 +80,70 @@ class Member < ActiveRecord::Base
find_by(invite_token: invite_token) find_by(invite_token: invite_token)
end end
# This method is used to find users that have been entered into the "Add members" field. def add_user(source, user, access_level, current_user: nil, expires_at: nil)
# These can be the User objects directly, their IDs, their emails, or new emails to be invited. user = retrieve_user(user)
def user_for_id(user_id) access_level = retrieve_access_level(access_level)
return user_id if user_id.is_a?(User)
user = User.find_by(id: user_id)
user ||= User.find_by(email: user_id)
user ||= user_id
user
end
def add_user(members, user_id, access_level, current_user: nil, expires_at: nil)
user = user_for_id(user_id)
# `user` can be either a User object or an email to be invited # `user` can be either a User object or an email to be invited
member =
if user.is_a?(User) if user.is_a?(User)
member = members.find_or_initialize_by(user_id: user.id) source.members.find_by(user_id: user.id) ||
source.requesters.find_by(user_id: user.id) ||
source.members.build(user_id: user.id)
else else
member = members.build source.members.build(invite_email: user)
member.invite_email = user
end end
if can_update_member?(current_user, member) || project_creator?(member, access_level) return member unless can_update_member?(current_user, member)
member.created_by ||= current_user
member.access_level = access_level
member.expires_at = expires_at
member.attributes = {
created_by: member.created_by || current_user,
access_level: access_level,
expires_at: expires_at
}
if member.request?
::Members::ApproveAccessRequestService.new(source, current_user, id: member.id).execute
else
member.save member.save
end end
member
end
def access_levels
Gitlab::Access.sym_options
end end
private private
# This method is used to find users that have been entered into the "Add members" field.
# These can be the User objects directly, their IDs, their emails, or new emails to be invited.
def retrieve_user(user)
return user if user.is_a?(User)
User.find_by(id: user) || User.find_by(email: user) || user
end
def retrieve_access_level(access_level)
access_levels.fetch(access_level) { access_level.to_i }
end
def can_update_member?(current_user, member) def can_update_member?(current_user, member)
# There is no current user for bulk actions, in which case anything is allowed # There is no current user for bulk actions, in which case anything is allowed
!current_user || !current_user || current_user.can?(:"update_#{member.type.underscore}", member)
current_user.can?(:update_group_member, member) ||
current_user.can?(:update_project_member, member)
end end
def project_creator?(member, access_level) def add_users_to_source(source, users, access_level, current_user: nil, expires_at: nil)
member.new_record? && member.owner? && users.each do |user|
access_level.to_i == ProjectMember::MASTER add_user(
source,
user,
access_level,
current_user: current_user,
expires_at: expires_at
)
end
end end
end end
......
...@@ -12,6 +12,22 @@ class GroupMember < Member ...@@ -12,6 +12,22 @@ class GroupMember < Member
Gitlab::Access.options_with_owner Gitlab::Access.options_with_owner
end end
def self.access_levels
Gitlab::Access.sym_options_with_owner
end
def self.add_users_to_group(group, users, access_level, current_user: nil, expires_at: nil)
self.transaction do
add_users_to_source(
group,
users,
access_level,
current_user: current_user,
expires_at: expires_at
)
end
end
def group def group
source source
end end
......
...@@ -34,25 +34,14 @@ class ProjectMember < Member ...@@ -34,25 +34,14 @@ class ProjectMember < Member
# :master # :master
# ) # )
# #
def add_users_to_projects(project_ids, user_ids, access, current_user: nil, expires_at: nil) def add_users_to_projects(project_ids, users, access_level, current_user: nil, expires_at: nil)
access_level = if roles_hash.has_key?(access) self.transaction do
roles_hash[access]
elsif roles_hash.values.include?(access.to_i)
access
else
raise "Non valid access"
end
users = user_ids.map { |user_id| Member.user_for_id(user_id) }
ProjectMember.transaction do
project_ids.each do |project_id| project_ids.each do |project_id|
project = Project.find(project_id) project = Project.find(project_id)
users.each do |user| add_users_to_source(
Member.add_user( project,
project.project_members, users,
user,
access_level, access_level,
current_user: current_user, current_user: current_user,
expires_at: expires_at expires_at: expires_at
...@@ -61,11 +50,6 @@ class ProjectMember < Member ...@@ -61,11 +50,6 @@ class ProjectMember < Member
end end
end end
true
rescue
false
end
def truncate_teams(project_ids) def truncate_teams(project_ids)
ProjectMember.transaction do ProjectMember.transaction do
members = ProjectMember.where(source_id: project_ids) members = ProjectMember.where(source_id: project_ids)
...@@ -84,13 +68,15 @@ class ProjectMember < Member ...@@ -84,13 +68,15 @@ class ProjectMember < Member
truncate_teams [project.id] truncate_teams [project.id]
end end
def roles_hash
Gitlab::Access.sym_options
end
def access_level_roles def access_level_roles
Gitlab::Access.options Gitlab::Access.options
end end
private
def can_update_member?(current_user, member)
super || (member.owner? && member.new_record?)
end
end end
def access_field def access_field
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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