Commit 2846f95d authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'master' into refactor/ci-config-move-global-entries

* master: (352 commits)
  Display last commit of deleted branch in push events (!4699)
  add changelog
  add missing attribute to attr_encrypted so it is fully backwards-compatible
  Add "GitLab team members only" to diagram link
  doc: note that .gitattributes uses default branch
  use the conf lexer so we have highlighted comments
  first draft of docs
  support cgi style options, such as erb?parent=json
  move the path alias to a more appropriate location
  make #custom_language private
  appease rubocop
  add an alias for Snippet#path
  appease rubocop
  check the tag so that an instance will pass too
  fix the spec, using project.change_head
  Revert "bump the master sha for gitlab-test!9"
  bump the master sha for gitlab-test!9
  add custom highlighting via .gitattributes
  Rename Licenses API to License Templates API
  Check for conflict with wiki projects when creating a new project.
  ...
parents c019585c 365015e3
...@@ -134,6 +134,11 @@ spinach 9 10: *spinach-knapsack ...@@ -134,6 +134,11 @@ spinach 9 10: *spinach-knapsack
image: "ruby:2.3" image: "ruby:2.3"
only: only:
- master - master
cache:
key: "ruby-23"
paths:
- vendor/apt
- vendor/ruby
.rspec-knapsack-ruby23: &rspec-knapsack-ruby23 .rspec-knapsack-ruby23: &rspec-knapsack-ruby23
<<: *rspec-knapsack <<: *rspec-knapsack
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.9.0 (unreleased) v 8.10.0 (unreleased)
- Replace Haml with Hamlit to make view rendering faster. !3666
- Wrap code blocks on Activies and Todos page. !4783 (winniehell)
- Display last commit of deleted branch in push events !4699 (winniehell)
- Add Sidekiq queue duration to transaction metrics.
- Make images fit to the size of the viewport !4810
- Fix check for New Branch button on Issue page !4630 (winniehell)
- Fix MR-auto-close text added to description. !4836
- Fix pagination when sorting by columns with lots of ties (like priority)
- Exclude email check from the standard health check
- Fix changing issue state columns in milestone view
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- Check for conflicts with existing Project's wiki path when creating a new project.
- Add API endpoint for a group issues !4520 (mahcsig)
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
v 8.9.3 (unreleased)
- Fix encrypted data backwards compatibility after upgrading attr_encrypted gem
v 8.9.2
- Fix visibility of snippets when searching.
- Fix an information disclosure when requesting access to a group containing private projects.
- Update omniauth-saml to 1.6.0 !4951
v 8.9.1
- Refactor labels documentation. !3347
- Eager load award emoji on notes. !4628
- Fix some CI wording in documentation. !4660
- Document `GIT_STRATEGY` and `GIT_DEPTH`. !4720
- Add documentation for the export & import features. !4732
- Add some docs for Docker Registry configuration. !4738
- Ensure we don't send the "access request declined" email to access requesters on project deletion. !4744
- Display group/project access requesters separately in the admin area. !4798
- Add documentation and examples for configuring cloud storage for registry images. !4812
- Clarifies documentation about artifact expiry. !4831
- Fix the Network graph links. !4832
- Fix MR-auto-close text added to description. !4836
- Add documentation for award emoji now that comments can be awarded with emojis. !4839
- Fix typo in export failure email. !4847
- Fix header vertical centering. !4170
- Fix subsequent SAML sign ins. !4718
- Set button label when picking an option from status dropdown. !4771
- Prevent invalid URLs from raising exceptions in WikiLink Filter. !4775
- Handle external issues in IssueReferenceFilter. !4789
- Support for rendering/redacting multiple documents. !4828
- Update Todos documentation and screenshots to include new functionality. !4840
- Hide nav arrows by default. !4843
- Added bottom padding to label color suggestion link. !4845
- Use jQuery objects in ref dropdown. !4850
- Fix GitLab project import issues related to notes and builds. !4855
- Restrict header logo to 36px so it doesn't overflow. !4861
- Fix unwanted label unassignment. !4863
- Fix mobile Safari bug where horizontal nav arrows would flicker on scroll. !4869
- Restore old behavior around diff notes to outdated discussions. !4870
- Fix merge requests project settings help link anchor. !4873
- Fix 404 when accessing pipelines as guest user on public projects. !4881
- Remove width restriction for logo on sign-in page. !4888
- Bump gitlab_git to 10.2.3 to fix false truncated warnings with ISO-8559 files. !4884
- Apply selected value as label. !4886
- Fix temp file being deleted after the request while importing a GitLab project. !4894
- Fix pagination when sorting by columns with lots of ties (like priority)
- Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise.
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- Fix a wrong MR status when merge_when_build_succeeds & project.only_allow_merge_if_build_succeeds are true. !4912
- Add SMTP as default delivery method to match gitlab-org/omnibus-gitlab!826. !4915
- Remove duplicate 'New Page' button on edit wiki page
v 8.9.0
- Fix builds API response not including commit data
- Fix error when CI job variables key specified but not defined - Fix error when CI job variables key specified but not defined
- Fix pipeline status when there are no builds in pipeline - Fix pipeline status when there are no builds in pipeline
- Fix Error 500 when using closes_issues API with an external issue tracker - Fix Error 500 when using closes_issues API with an external issue tracker
...@@ -8,17 +76,21 @@ v 8.9.0 (unreleased) ...@@ -8,17 +76,21 @@ v 8.9.0 (unreleased)
- Bulk assign/unassign labels to issues. - Bulk assign/unassign labels to issues.
- Ability to prioritize labels !4009 / !3205 (Thijs Wouters) - Ability to prioritize labels !4009 / !3205 (Thijs Wouters)
- Show Star and Fork buttons on mobile. - Show Star and Fork buttons on mobile.
- Performance improvements on RelativeLinkFilter
- Fix endless redirections when accessing user OAuth applications when they are disabled - Fix endless redirections when accessing user OAuth applications when they are disabled
- Allow enabling wiki page events from Webhook management UI - Allow enabling wiki page events from Webhook management UI
- Bump rouge to 1.11.0 - Bump rouge to 1.11.0
- Fix issue with arrow keys not working in search autocomplete dropdown - Fix issue with arrow keys not working in search autocomplete dropdown
- Fix an issue where note polling stopped working if a window was in the - Fix an issue where note polling stopped working if a window was in the
background during a refresh. background during a refresh.
- Pre-processing Markdown now only happens when needed
- Make EmailsOnPushWorker use Sidekiq mailers queue - Make EmailsOnPushWorker use Sidekiq mailers queue
- Redesign all Devise emails. !4297 - Redesign all Devise emails. !4297
- Don't show 'Leave Project' to group members - Don't show 'Leave Project' to group members
- Fix wiki page events' webhook to point to the wiki repository - Fix wiki page events' webhook to point to the wiki repository
- Add a border around images to differentiate them from the background.
- Don't show tags for revert and cherry-pick operations - Don't show tags for revert and cherry-pick operations
- Show image ID on registry page
- Fix issue todo not remove when leave project !4150 (Long Nguyen) - Fix issue todo not remove when leave project !4150 (Long Nguyen)
- Allow customisable text on the 'nearly there' page after a user signs up - Allow customisable text on the 'nearly there' page after a user signs up
- Bump recaptcha gem to 3.0.0 to remove deprecated stoken support - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support
...@@ -55,6 +127,7 @@ v 8.9.0 (unreleased) ...@@ -55,6 +127,7 @@ v 8.9.0 (unreleased)
- Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged - Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged
- Don't allow MRs to be merged when commits were added since the last review / page load - Don't allow MRs to be merged when commits were added since the last review / page load
- Add DB index on users.state - Add DB index on users.state
- Limit email on push diff size to 30 files / 150 KB
- Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database - Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database
- Changed the Slack build message to use the singular duration if necessary (Aran Koning) - Changed the Slack build message to use the singular duration if necessary (Aran Koning)
- Fix race condition on merge when build succeeds - Fix race condition on merge when build succeeds
...@@ -69,6 +142,7 @@ v 8.9.0 (unreleased) ...@@ -69,6 +142,7 @@ v 8.9.0 (unreleased)
- Todos will display target state if issuable target is 'Closed' or 'Merged' - Todos will display target state if issuable target is 'Closed' or 'Merged'
- Validate only and except regexp - Validate only and except regexp
- Fix bug when sorting issues by milestone due date and filtering by two or more labels - Fix bug when sorting issues by milestone due date and filtering by two or more labels
- POST to API /projects/:id/runners/:runner_id would give 409 if the runner was already enabled for this project
- Add support for using Yubikeys (U2F) for two-factor authentication - Add support for using Yubikeys (U2F) for two-factor authentication
- Link to blank group icon doesn't throw a 404 anymore - Link to blank group icon doesn't throw a 404 anymore
- Remove 'main language' feature - Remove 'main language' feature
...@@ -76,13 +150,16 @@ v 8.9.0 (unreleased) ...@@ -76,13 +150,16 @@ v 8.9.0 (unreleased)
- Pipelines can be canceled only when there are running builds - Pipelines can be canceled only when there are running builds
- Allow authentication using personal access tokens - Allow authentication using personal access tokens
- Use downcased path to container repository as this is expected path by Docker - Use downcased path to container repository as this is expected path by Docker
- Allow to use CI token to fetch LFS objects
- Custom notification settings - Custom notification settings
- Projects pending deletion will render a 404 page - Projects pending deletion will render a 404 page
- Measure queue duration between gitlab-workhorse and Rails - Measure queue duration between gitlab-workhorse and Rails
- Added Gfm autocomplete for labels - Added Gfm autocomplete for labels
- Added edit note 'up' shortcut documentation to the help panel and docs screenshot #18114
- Make Omniauth providers specs to not modify global configuration - Make Omniauth providers specs to not modify global configuration
- Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir) - Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir)
- Make authentication service for Container Registry to be compatible with < Docker 1.11 - Make authentication service for Container Registry to be compatible with < Docker 1.11
- Make it possible to lock a runner from being enabled for other projects
- Add Application Setting to configure Container Registry token expire delay (default 5min) - Add Application Setting to configure Container Registry token expire delay (default 5min)
- Cache assigned issue and merge request counts in sidebar nav - Cache assigned issue and merge request counts in sidebar nav
- Use Knapsack only in CI environment - Use Knapsack only in CI environment
...@@ -100,6 +177,7 @@ v 8.9.0 (unreleased) ...@@ -100,6 +177,7 @@ v 8.9.0 (unreleased)
- An indicator is now displayed at the top of the comment field for confidential issues. - An indicator is now displayed at the top of the comment field for confidential issues.
- Show categorised search queries in the search autocomplete - Show categorised search queries in the search autocomplete
- RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented
- Dropdown for `.gitlab-ci.yml` templates
- Improve issuables APIs performance when accessing notes !4471 - Improve issuables APIs performance when accessing notes !4471
- Add sorting dropdown to tags page !4423 - Add sorting dropdown to tags page !4423
- External links now open in a new tab - External links now open in a new tab
...@@ -128,10 +206,17 @@ v 8.9.0 (unreleased) ...@@ -128,10 +206,17 @@ v 8.9.0 (unreleased)
- Various associations are now eager loaded when parsing issue references to reduce the number of queries executed - Various associations are now eager loaded when parsing issue references to reduce the number of queries executed
- Set inverse_of for Project/Service association to reduce the number of queries - Set inverse_of for Project/Service association to reduce the number of queries
- Update tanuki logo highlight/loading colors - Update tanuki logo highlight/loading colors
- Remove explicit Gitlab::Metrics.action assignments, are already automatic.
- Use Git cached counters for branches and tags on project page - Use Git cached counters for branches and tags on project page
- Cache participable participants in an instance variable.
- Filter parameters for request_uri value on instrumented transactions. - Filter parameters for request_uri value on instrumented transactions.
- Remove duplicated keys add UNIQUE index to keys fingerprint column
- ExtractsPath get ref_names from repository cache, if not there access git.
- Cache user todo counts from TodoService - Cache user todo counts from TodoService
- Ensure Todos counters doesn't count Todos for projects pending delete - Ensure Todos counters doesn't count Todos for projects pending delete
- Add left/right arrows horizontal navigation
- Add tooltip to pin/unpin navbar
- Add new sub nav style to Wiki and Graphs sub navigation
v 8.8.5 v 8.8.5
- Import GitHub repositories respecting the API rate limit !4166 - Import GitHub repositories respecting the API rate limit !4166
......
...@@ -30,7 +30,7 @@ gem 'omniauth-github', '~> 1.1.1' ...@@ -30,7 +30,7 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-gitlab', '~> 1.0.0'
gem 'omniauth-google-oauth2', '~> 0.2.0' gem 'omniauth-google-oauth2', '~> 0.2.0'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-saml', '~> 1.5.0' gem 'omniauth-saml', '~> 1.6.0'
gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth_crowd', '~> 2.2.0'
...@@ -48,7 +48,7 @@ gem 'attr_encrypted', '~> 3.0.0' ...@@ -48,7 +48,7 @@ gem 'attr_encrypted', '~> 3.0.0'
gem 'u2f', '~> 0.2.1' gem 'u2f', '~> 0.2.1'
# Browser detection # Browser detection
gem "browser", '~> 2.0.3' 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
...@@ -76,7 +76,7 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' ...@@ -76,7 +76,7 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
gem "kaminari", "~> 0.17.0" gem "kaminari", "~> 0.17.0"
# HAML # HAML
gem "haml-rails", '~> 0.9.0' gem 'hamlit', '~> 2.5'
# Files attachments # Files attachments
gem "carrierwave", '~> 0.10.0' gem "carrierwave", '~> 0.10.0'
...@@ -234,7 +234,7 @@ gem 'net-ssh', '~> 3.0.1' ...@@ -234,7 +234,7 @@ gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0' gem 'base32', '~> 0.3.0'
# Sentry integration # Sentry integration
gem 'sentry-raven', '~> 0.15' gem 'sentry-raven', '~> 1.1.0'
gem 'premailer-rails', '~> 1.9.0' gem 'premailer-rails', '~> 1.9.0'
...@@ -330,7 +330,7 @@ gem "newrelic_rpm", '~> 3.14' ...@@ -330,7 +330,7 @@ gem "newrelic_rpm", '~> 3.14'
gem 'octokit', '~> 4.3.0' gem 'octokit', '~> 4.3.0'
gem "mail_room", "~> 0.7" gem "mail_room", "~> 0.8"
gem 'email_reply_parser', '~> 0.5.8' gem 'email_reply_parser', '~> 0.5.8'
......
...@@ -98,7 +98,7 @@ GEM ...@@ -98,7 +98,7 @@ GEM
autoprefixer-rails (>= 5.2.1) autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4) sass (>= 3.3.4)
brakeman (3.3.2) brakeman (3.3.2)
browser (2.0.3) browser (2.2.0)
builder (3.2.2) builder (3.2.2)
bullet (5.0.0) bullet (5.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
...@@ -277,7 +277,7 @@ GEM ...@@ -277,7 +277,7 @@ GEM
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_emoji (0.3.1) gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1) gemojione (~> 2.2, >= 2.2.1)
gitlab_git (10.2.0) gitlab_git (10.2.3)
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)
...@@ -320,14 +320,10 @@ GEM ...@@ -320,14 +320,10 @@ GEM
grape-entity (0.4.8) grape-entity (0.4.8)
activesupport activesupport
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
haml (4.0.7) hamlit (2.5.0)
temple (~> 0.7.6)
thor
tilt tilt
haml-rails (0.9.0)
actionpack (>= 4.0.1)
activesupport (>= 4.0.1)
haml (>= 4.0.6, < 5.0)
html2haml (>= 1.0.1)
railties (>= 4.0.1)
hashie (3.4.3) hashie (3.4.3)
health_check (1.5.1) health_check (1.5.1)
rails (>= 2.3.0) rails (>= 2.3.0)
...@@ -337,11 +333,6 @@ GEM ...@@ -337,11 +333,6 @@ GEM
html-pipeline (1.11.0) html-pipeline (1.11.0)
activesupport (>= 2) activesupport (>= 2)
nokogiri (~> 1.4) nokogiri (~> 1.4)
html2haml (2.0.0)
erubis (~> 2.7.0)
haml (~> 4.0.0)
nokogiri (~> 1.6.0)
ruby_parser (~> 3.5)
htmlentities (4.3.4) htmlentities (4.3.4)
http_parser.rb (0.5.3) http_parser.rb (0.5.3)
httparty (0.13.7) httparty (0.13.7)
...@@ -398,7 +389,7 @@ GEM ...@@ -398,7 +389,7 @@ GEM
systemu (~> 2.6.2) systemu (~> 2.6.2)
mail (2.6.4) mail (2.6.4)
mime-types (>= 1.16, < 4) mime-types (>= 1.16, < 4)
mail_room (0.7.0) mail_room (0.8.0)
method_source (0.8.2) method_source (0.8.2)
mime-types (2.99.2) mime-types (2.99.2)
mimemagic (0.3.0) mimemagic (0.3.0)
...@@ -468,9 +459,9 @@ GEM ...@@ -468,9 +459,9 @@ GEM
omniauth-oauth2 (1.3.1) omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0) oauth2 (~> 1.0)
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-saml (1.5.0) omniauth-saml (1.6.0)
omniauth (~> 1.3) omniauth (~> 1.3)
ruby-saml (~> 1.1, >= 1.1.1) ruby-saml (~> 1.3)
omniauth-shibboleth (1.2.1) omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0) omniauth (>= 1.0.0)
omniauth-twitter (1.2.1) omniauth-twitter (1.2.1)
...@@ -631,9 +622,8 @@ GEM ...@@ -631,9 +622,8 @@ GEM
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-progressbar (1.8.1) ruby-progressbar (1.8.1)
ruby-saml (1.1.2) ruby-saml (1.3.0)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
uuid (~> 2.3)
ruby_parser (3.8.2) ruby_parser (3.8.2)
sexp_processor (~> 4.1) sexp_processor (~> 4.1)
rubyntlm (0.5.2) rubyntlm (0.5.2)
...@@ -665,7 +655,7 @@ GEM ...@@ -665,7 +655,7 @@ GEM
activesupport (>= 3.1, < 4.3) activesupport (>= 3.1, < 4.3)
select2-rails (3.5.9.3) select2-rails (3.5.9.3)
thor (~> 0.14) thor (~> 0.14)
sentry-raven (0.15.6) sentry-raven (1.1.0)
faraday (>= 0.7.6) faraday (>= 0.7.6)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.7.0) sexp_processor (4.7.0)
...@@ -733,6 +723,7 @@ GEM ...@@ -733,6 +723,7 @@ GEM
railties (>= 3.2.5, < 6) railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0) teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0) teaspoon (>= 1.0.0)
temple (0.7.7)
term-ansicolor (1.3.2) term-ansicolor (1.3.2)
tins (~> 1.0) tins (~> 1.0)
test_after_commit (0.4.2) test_after_commit (0.4.2)
...@@ -833,7 +824,7 @@ DEPENDENCIES ...@@ -833,7 +824,7 @@ DEPENDENCIES
binding_of_caller (~> 0.7.2) binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0) bootstrap-sass (~> 3.3.0)
brakeman (~> 3.3.0) brakeman (~> 3.3.0)
browser (~> 2.0.3) browser (~> 2.2)
bullet bullet
bundler-audit bundler-audit
byebug byebug
...@@ -882,7 +873,7 @@ DEPENDENCIES ...@@ -882,7 +873,7 @@ DEPENDENCIES
gon (~> 6.0.1) gon (~> 6.0.1)
grape (~> 0.13.0) grape (~> 0.13.0)
grape-entity (~> 0.4.2) grape-entity (~> 0.4.2)
haml-rails (~> 0.9.0) hamlit (~> 2.5)
health_check (~> 1.5.1) health_check (~> 1.5.1)
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0) html-pipeline (~> 1.11.0)
...@@ -899,7 +890,7 @@ DEPENDENCIES ...@@ -899,7 +890,7 @@ DEPENDENCIES
license_finder license_finder
licensee (~> 8.0.0) licensee (~> 8.0.0)
loofah (~> 2.0.3) loofah (~> 2.0.3)
mail_room (~> 0.7) mail_room (~> 0.8)
method_source (~> 0.8) method_source (~> 0.8)
minitest (~> 5.7.0) minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
...@@ -920,7 +911,7 @@ DEPENDENCIES ...@@ -920,7 +911,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.0) omniauth-gitlab (~> 1.0.0)
omniauth-google-oauth2 (~> 0.2.0) omniauth-google-oauth2 (~> 0.2.0)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-saml (~> 1.5.0) omniauth-saml (~> 1.6.0)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
...@@ -960,7 +951,7 @@ DEPENDENCIES ...@@ -960,7 +951,7 @@ DEPENDENCIES
sdoc (~> 0.3.20) sdoc (~> 0.3.20)
seed-fu (~> 2.3.5) seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
sentry-raven (~> 0.15) sentry-raven (~> 1.1.0)
settingslogic (~> 2.0.9) settingslogic (~> 2.0.9)
sham_rack sham_rack
shoulda-matchers (~> 2.8.0) shoulda-matchers (~> 2.8.0)
......
8.9.0-pre 8.10.0-pre
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
labelsPath: "/api/:version/projects/:id/labels" labelsPath: "/api/:version/projects/:id/labels"
licensePath: "/api/:version/licenses/:key" licensePath: "/api/:version/licenses/:key"
gitignorePath: "/api/:version/gitignores/:key" gitignorePath: "/api/:version/gitignores/:key"
gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key"
group: (group_id, callback) -> group: (group_id, callback) ->
url = Api.buildUrl(Api.groupPath) url = Api.buildUrl(Api.groupPath)
...@@ -110,6 +111,12 @@ ...@@ -110,6 +111,12 @@
$.get url, (gitignore) -> $.get url, (gitignore) ->
callback(gitignore) callback(gitignore)
gitlabCiYml: (key, callback) ->
url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key)
$.get url, (file) ->
callback(file)
buildUrl: (url) -> buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root? url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version) return url.replace(':version', gon.api_version)
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
#= require_directory ./ci #= require_directory ./ci
#= require_directory ./commit #= require_directory ./commit
#= require_directory ./extensions #= require_directory ./extensions
#= require_directory ./lib #= require_directory ./lib/utils
#= require_directory ./u2f #= require_directory ./u2f
#= require_directory . #= require_directory .
#= require fuzzaldrin-plus #= require fuzzaldrin-plus
...@@ -121,6 +121,11 @@ window.onload = -> ...@@ -121,6 +121,11 @@ window.onload = ->
setTimeout shiftWindow, 100 setTimeout shiftWindow, 100
$ -> $ ->
$document = $(document)
$window = $(window)
$body = $('body')
gl.utils.preventDisabledButtons() gl.utils.preventDisabledButtons()
bootstrapBreakpoint = bp.getBreakpointSize() bootstrapBreakpoint = bp.getBreakpointSize()
...@@ -152,7 +157,7 @@ $ -> ...@@ -152,7 +157,7 @@ $ ->
), 1 ), 1
# Initialize tooltips # Initialize tooltips
$('body').tooltip( $body.tooltip(
selector: '.has-tooltip, [data-toggle="tooltip"]' selector: '.has-tooltip, [data-toggle="tooltip"]'
placement: (_, el) -> placement: (_, el) ->
$el = $(el) $el = $(el)
...@@ -171,7 +176,7 @@ $ -> ...@@ -171,7 +176,7 @@ $ ->
flash.show() flash.show()
# Disable form buttons while a form is submitting # Disable form buttons while a form is submitting
$('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> $body.on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
buttons = $('[type="submit"]', @) buttons = $('[type="submit"]', @)
switch e.type switch e.type
...@@ -184,7 +189,7 @@ $ -> ...@@ -184,7 +189,7 @@ $ ->
$('.account-box').hover -> $(@).toggleClass('hover') $('.account-box').hover -> $(@).toggleClass('hover')
# Commit show suppressed diff # Commit show suppressed diff
$(document).on 'click', '.diff-content .js-show-suppressed-diff', -> $document.on 'click', '.diff-content .js-show-suppressed-diff', ->
$container = $(@).parent() $container = $(@).parent()
$container.next('table').show() $container.next('table').show()
$container.remove() $container.remove()
...@@ -197,13 +202,13 @@ $ -> ...@@ -197,13 +202,13 @@ $ ->
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left") $('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
# Show/hide comments on diff # Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) -> $body.on "click", ".js-toggle-diff-comments", (e) ->
$(@).toggleClass('active') $(@).toggleClass('active')
$(@).closest(".diff-file").find(".notes_holder").toggle() $(@).closest(".diff-file").find(".notes_holder").toggle()
e.preventDefault() e.preventDefault()
$(document).off "click", '.js-confirm-danger' $document.off "click", '.js-confirm-danger'
$(document).on "click", '.js-confirm-danger', (e) -> $document.on "click", '.js-confirm-danger', (e) ->
e.preventDefault() e.preventDefault()
btn = $(e.target) btn = $(e.target)
text = btn.data("confirm-danger-message") text = btn.data("confirm-danger-message")
...@@ -211,7 +216,7 @@ $ -> ...@@ -211,7 +216,7 @@ $ ->
new ConfirmDangerModal(form, text) new ConfirmDangerModal(form, text)
$(document).on 'click', 'button', -> $document.on 'click', 'button', ->
$(this).blur() $(this).blur()
$('input[type="search"]').each -> $('input[type="search"]').each ->
...@@ -219,7 +224,7 @@ $ -> ...@@ -219,7 +224,7 @@ $ ->
$this.attr 'value', $this.val() $this.attr 'value', $this.val()
return return
$(document) $document
.off 'keyup', 'input[type="search"]' .off 'keyup', 'input[type="search"]'
.on 'keyup', 'input[type="search"]' , (e) -> .on 'keyup', 'input[type="search"]' , (e) ->
$this = $(this) $this = $(this)
...@@ -227,7 +232,7 @@ $ -> ...@@ -227,7 +232,7 @@ $ ->
$sidebarGutterToggle = $('.js-sidebar-toggle') $sidebarGutterToggle = $('.js-sidebar-toggle')
$(document) $document
.off 'breakpoint:change' .off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) -> .on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs' if breakpoint is 'sm' or breakpoint is 'xs'
...@@ -239,14 +244,14 @@ $ -> ...@@ -239,14 +244,14 @@ $ ->
oldBootstrapBreakpoint = bootstrapBreakpoint oldBootstrapBreakpoint = bootstrapBreakpoint
bootstrapBreakpoint = bp.getBreakpointSize() bootstrapBreakpoint = bp.getBreakpointSize()
if bootstrapBreakpoint != oldBootstrapBreakpoint if bootstrapBreakpoint != oldBootstrapBreakpoint
$(document).trigger('breakpoint:change', [bootstrapBreakpoint]) $document.trigger('breakpoint:change', [bootstrapBreakpoint])
checkInitialSidebarSize = -> checkInitialSidebarSize = ->
bootstrapBreakpoint = bp.getBreakpointSize() bootstrapBreakpoint = bp.getBreakpointSize()
if bootstrapBreakpoint is "xs" or "sm" if bootstrapBreakpoint is "xs" or "sm"
$(document).trigger('breakpoint:change', [bootstrapBreakpoint]) $document.trigger('breakpoint:change', [bootstrapBreakpoint])
$(window) $window
.off "resize.app" .off "resize.app"
.on "resize.app", (e) -> .on "resize.app", (e) ->
fitSidebarForSize() fitSidebarForSize()
...@@ -256,29 +261,45 @@ $ -> ...@@ -256,29 +261,45 @@ $ ->
new Aside() new Aside()
# Sidenav pinning # Sidenav pinning
if $(window).width() < 1440 and $.cookie('pin_nav') is 'true' if $window.width() < 1440 and $.cookie('pin_nav') is 'true'
$.cookie('pin_nav', 'false') $.cookie('pin_nav', 'false', { path: '/' })
$('.page-with-sidebar') $('.page-with-sidebar')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded') .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
.removeClass('page-sidebar-pinned') .removeClass('page-sidebar-pinned')
$('.navbar-fixed-top').removeClass('header-pinned-nav') $('.navbar-fixed-top').removeClass('header-pinned-nav')
$(document) $document
.off 'click', '.js-nav-pin' .off 'click', '.js-nav-pin'
.on 'click', '.js-nav-pin', (e) -> .on 'click', '.js-nav-pin', (e) ->
e.preventDefault() e.preventDefault()
$pinBtn = $(e.currentTarget)
$page = $ '.page-with-sidebar'
$topNav = $ '.navbar-fixed-top'
$tooltip = $ "##{$pinBtn.attr('aria-describedby')}"
doPinNav = not $page.is('.page-sidebar-pinned')
tooltipText = 'Pin navigation'
$(this).toggleClass 'is-active' $(this).toggleClass 'is-active'
if $.cookie('pin_nav') is 'true' if doPinNav
$.cookie 'pin_nav', 'false' $page.addClass('page-sidebar-pinned')
$('.page-with-sidebar') $topNav.addClass('header-pinned-nav')
.removeClass('page-sidebar-pinned')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
$('.navbar-fixed-top')
.removeClass('header-pinned-nav')
.toggleClass('header-collapsed header-expanded')
else else
$.cookie 'pin_nav', 'true' $tooltip.remove() # Remove it immediately when collapsing the sidebar
$('.page-with-sidebar').addClass('page-sidebar-pinned') $page.removeClass('page-sidebar-pinned')
$('.navbar-fixed-top').addClass('header-pinned-nav') .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
$topNav.removeClass('header-pinned-nav')
.toggleClass('header-collapsed header-expanded')
# Save settings
$.cookie 'pin_nav', doPinNav, { path: '/' }
if $.cookie('pin_nav') is 'true' or doPinNav
tooltipText = 'Unpin navigation'
# Update tooltip text immediately
$tooltip.find('.tooltip-inner').text(tooltipText)
# Persist tooltip title
$pinBtn.attr('title', tooltipText).tooltip('fixTitle')
...@@ -341,7 +341,9 @@ class @AwardsHandler ...@@ -341,7 +341,9 @@ class @AwardsHandler
for emoji in frequentlyUsedEmojis for emoji in frequentlyUsedEmojis
$(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul) $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
$('input.emoji-search').after(ul).after($('<h5>').text('Frequently used')) $('.emoji-menu-content')
.prepend(ul)
.prepend($('<h5>').text('Frequently used'))
@frequentEmojiBlockRendered = true @frequentEmojiBlockRendered = true
...@@ -356,7 +358,7 @@ class @AwardsHandler ...@@ -356,7 +358,7 @@ class @AwardsHandler
if term if term
# Generate a search result block # Generate a search result block
h5 = $('<h5>').text('Search results').addClass('emoji-search') h5 = $('<h5>').text('Search results')
found_emojis = @searchEmojis(term).show() found_emojis = @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()
......
#= require blob/template_selector
class @BlobCiYamlSelector extends TemplateSelector
requestFile: (query) ->
Api.gitlabCiYml query.name, @requestFileSuccess.bind(@)
class @BlobCiYamlSelectors
constructor: (opts) ->
{
@$dropdowns = $('.js-gitlab-ci-yml-selector')
@editor
} = opts
@$dropdowns.each (i, dropdown) =>
$dropdown = $(dropdown)
new BlobCiYamlSelector(
pattern: /(.gitlab-ci.yml)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
dropdown: $dropdown,
editor: @editor
)
...@@ -15,6 +15,7 @@ class @EditBlob ...@@ -15,6 +15,7 @@ class @EditBlob
new BlobLicenseSelectors { @editor } new BlobLicenseSelectors { @editor }
new BlobGitignoreSelectors { @editor } new BlobGitignoreSelectors { @editor }
new BlobCiYamlSelectors { @editor }
initModePanesAndLinks: -> initModePanesAndLinks: ->
@$editModePanes = $(".js-edit-mode-pane") @$editModePanes = $(".js-edit-mode-pane")
......
...@@ -19,6 +19,7 @@ class @TemplateSelector ...@@ -19,6 +19,7 @@ class @TemplateSelector
data: @data, data: @data,
filterable: true, filterable: true,
selectable: true, selectable: true,
toggleLabel: @toggleLabel,
search: search:
fields: ['name'] fields: ['name']
clicked: @onClick clicked: @onClick
...@@ -31,6 +32,9 @@ class @TemplateSelector ...@@ -31,6 +32,9 @@ class @TemplateSelector
@onFilenameUpdate() @onFilenameUpdate()
) )
toggleLabel: (item) ->
item.name
onFilenameUpdate: -> onFilenameUpdate: ->
return unless @$input.length return unless @$input.length
......
...@@ -58,7 +58,7 @@ class GitLabDropdownFilter ...@@ -58,7 +58,7 @@ class GitLabDropdownFilter
filter: (search_text) -> filter: (search_text) ->
data = @options.data() data = @options.data()
if data? if data? and not @options.filterByText
results = data results = data
if search_text isnt '' if search_text isnt ''
...@@ -102,10 +102,11 @@ class GitLabDropdownFilter ...@@ -102,10 +102,11 @@ class GitLabDropdownFilter
$el = $(@) $el = $(@)
matches = fuzzaldrinPlus.match($el.text().trim(), search_text) matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
if matches.length unless $el.is('.dropdown-header')
$el.show() if matches.length
else $el.show()
$el.hide() else
$el.hide()
else else
elements.show() elements.show()
...@@ -191,6 +192,7 @@ class GitLabDropdown ...@@ -191,6 +192,7 @@ class GitLabDropdown
if @options.filterable if @options.filterable
@filter = new GitLabDropdownFilter @filterInput, @filter = new GitLabDropdownFilter @filterInput,
filterInputBlur: @filterInputBlur filterInputBlur: @filterInputBlur
filterByText: @options.filterByText
remote: @options.filterRemote remote: @options.filterRemote
query: @options.data query: @options.data
keys: searchFields keys: searchFields
...@@ -278,7 +280,7 @@ class GitLabDropdown ...@@ -278,7 +280,7 @@ class GitLabDropdown
html = @renderData(data) html = @renderData(data)
# Render the full menu # Render the full menu
full_html = @renderMenu(html.join("")) full_html = @renderMenu(html)
@appendMenu(full_html) @appendMenu(full_html)
...@@ -349,7 +351,8 @@ class GitLabDropdown ...@@ -349,7 +351,8 @@ class GitLabDropdown
if @options.renderMenu if @options.renderMenu
menu_html = @options.renderMenu(html) menu_html = @options.renderMenu(html)
else else
menu_html = "<ul>#{html}</ul>" menu_html = $('<ul />')
.append(html)
return menu_html return menu_html
...@@ -358,7 +361,9 @@ class GitLabDropdown ...@@ -358,7 +361,9 @@ class GitLabDropdown
selector = '.dropdown-content' selector = '.dropdown-content'
if @dropdown.find(".dropdown-toggle-page").length if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content" selector = ".dropdown-page-one .dropdown-content"
$(selector, @dropdown).html html $(selector, @dropdown)
.empty()
.append(html)
# Render the row # Render the row
renderItem: (data, group = false, index = false) -> renderItem: (data, group = false, index = false) ->
...@@ -457,7 +462,7 @@ class GitLabDropdown ...@@ -457,7 +462,7 @@ class GitLabDropdown
# Toggle the dropdown label # Toggle the dropdown label
if @options.toggleLabel if @options.toggleLabel
@updateLabel() @updateLabel(selectedObject, el, @)
else else
selectedObject selectedObject
else if el.hasClass(INDETERMINATE_CLASS) else if el.hasClass(INDETERMINATE_CLASS)
...@@ -484,7 +489,7 @@ class GitLabDropdown ...@@ -484,7 +489,7 @@ class GitLabDropdown
# Toggle the dropdown label # Toggle the dropdown label
if @options.toggleLabel if @options.toggleLabel
@updateLabel(selectedObject, el) @updateLabel(selectedObject, el, @)
if value? if value?
if !field.length and fieldName if !field.length and fieldName
@addInput(fieldName, value) @addInput(fieldName, value)
...@@ -583,8 +588,8 @@ class GitLabDropdown ...@@ -583,8 +588,8 @@ class GitLabDropdown
# Scroll the dropdown content up # Scroll the dropdown content up
$dropdownContent.scrollTop(listItemTop - dropdownContentTop) $dropdownContent.scrollTop(listItemTop - dropdownContentTop)
updateLabel: (selected = null, el = null) => updateLabel: (selected = null, el = null, instance = null) =>
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el) $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance)
$.fn.glDropdown = (opts) -> $.fn.glDropdown = (opts) ->
return @.each -> return @.each ->
......
...@@ -34,6 +34,8 @@ class @GLForm ...@@ -34,6 +34,8 @@ class @GLForm
# form and textarea event listeners # form and textarea event listeners
@addEventListeners() @addEventListeners()
gl.text.init(@form)
# hide discard button # hide discard button
@form.find('.js-note-discard').hide() @form.find('.js-note-discard').hide()
...@@ -42,6 +44,7 @@ class @GLForm ...@@ -42,6 +44,7 @@ class @GLForm
clearEventListeners: -> clearEventListeners: ->
@textarea.off 'focus' @textarea.off 'focus'
@textarea.off 'blur' @textarea.off 'blur'
gl.text.removeListeners(@form)
addEventListeners: -> addEventListeners: ->
@textarea.on 'focus', -> @textarea.on 'focus', ->
......
...@@ -4,5 +4,4 @@ ...@@ -4,5 +4,4 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the # It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file. # the compiled file.
# #
#= require Chart
#= require_tree . #= require_tree .
...@@ -121,7 +121,11 @@ class @ContributorsMasterGraph extends ContributorsGraph ...@@ -121,7 +121,11 @@ class @ContributorsMasterGraph extends ContributorsGraph
class @ContributorsAuthorGraph extends ContributorsGraph class @ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) -> constructor: (@data) ->
@width = $('.content').width()/2 - 100 # Don't split graph size in half for mobile devices.
if $(window).width() < 768
@width = $('.content').width() - 80
else
@width = ($('.content').width() / 2) - 100
@height = 200 @height = 200
@x = null @x = null
@y = null @y = null
......
...@@ -68,12 +68,15 @@ issuable_created = false ...@@ -68,12 +68,15 @@ issuable_created = false
Turbolinks.visit(issuesUrl); Turbolinks.visit(issuesUrl);
initChecks: -> initChecks: ->
@issuableBulkActions = $('.bulk-update').data('bulkActions')
$('.check_all_issues').off('click').on('click', -> $('.check_all_issues').off('click').on('click', ->
$('.selected_issue').prop('checked', @checked) $('.selected_issue').prop('checked', @checked)
Issuable.checkChanged() Issuable.checkChanged()
) )
$('.selected_issue').off('change').on('change', Issuable.checkChanged) $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(@))
checkChanged: -> checkChanged: ->
checked_issues = $('.selected_issue:checked') checked_issues = $('.selected_issue:checked')
...@@ -88,3 +91,6 @@ issuable_created = false ...@@ -88,3 +91,6 @@ issuable_created = false
$('#update_issues_ids').val [] $('#update_issues_ids').val []
$('.issues_bulk_update').hide() $('.issues_bulk_update').hide()
$('.issues-other-filters').show() $('.issues-other-filters').show()
@issuableBulkActions.willUpdateLabels = false
return true
...@@ -99,7 +99,7 @@ class @Issue ...@@ -99,7 +99,7 @@ class @Issue
# If the user doesn't have the required permissions the container isn't # If the user doesn't have the required permissions the container isn't
# rendered at all. # rendered at all.
return unless $container return if $container.length is 0
$.getJSON($container.data('path')) $.getJSON($container.data('path'))
.error -> .error ->
......
...@@ -6,6 +6,13 @@ class @IssueStatusSelect ...@@ -6,6 +6,13 @@ class @IssueStatusSelect
$(el).glDropdown( $(el).glDropdown(
selectable: true selectable: true
fieldName: fieldName fieldName: fieldName
toggleLabel: (selected, el, instance) =>
label = 'Author'
$item = instance.dropdown.find('.is-active')
label = $item.text() if $item.length
label
clicked: (item, $el, e)->
e.preventDefault()
id: (obj, el) -> id: (obj, el) ->
$(el).data("id") $(el).data("id")
) )
...@@ -7,6 +7,11 @@ class @IssuableBulkActions ...@@ -7,6 +7,11 @@ class @IssuableBulkActions
@issues = @getElement('.issues-list .issue') @issues = @getElement('.issues-list .issue')
} = opts } = opts
# Save instance
@form.data 'bulkActions', @
@willUpdateLabels = false
@bindEvents() @bindEvents()
# Fixes bulk-assign not working when navigating through pages # Fixes bulk-assign not working when navigating through pages
...@@ -87,11 +92,12 @@ class @IssuableBulkActions ...@@ -87,11 +92,12 @@ class @IssuableBulkActions
add_label_ids : [] add_label_ids : []
remove_label_ids : [] remove_label_ids : []
@getLabelsToApply().map (id) -> if @willUpdateLabels
formData.update.add_label_ids.push id @getLabelsToApply().map (id) ->
formData.update.add_label_ids.push id
@getLabelsToRemove().map (id) -> @getLabelsToRemove().map (id) ->
formData.update.remove_label_ids.push id formData.update.remove_label_ids.push id
formData formData
......
...@@ -319,6 +319,8 @@ class @LabelsSelect ...@@ -319,6 +319,8 @@ class @LabelsSelect
multiSelect: $dropdown.hasClass 'js-multiselect' multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: (label) -> clicked: (label) ->
_this.enableBulkLabelDropdown()
if $dropdown.hasClass('js-filter-bulk-update') if $dropdown.hasClass('js-filter-bulk-update')
return return
...@@ -377,3 +379,8 @@ class @LabelsSelect ...@@ -377,3 +379,8 @@ class @LabelsSelect
label_ids.push $("#issue_#{issue_id}").data('labels') label_ids.push $("#issue_#{issue_id}").data('labels')
_.intersection.apply _, label_ids _.intersection.apply _, label_ids
enableBulkLabelDropdown: ->
if $('.selected_issue:checked').length
issuableBulkActions = $('.bulk-update').data('bulkActions')
issuableBulkActions.willUpdateLabels = true
...@@ -3,11 +3,10 @@ hideEndFade = ($scrollingTabs) -> ...@@ -3,11 +3,10 @@ hideEndFade = ($scrollingTabs) ->
$this = $(@) $this = $(@)
$this $this
.find('.fade-right') .siblings('.fade-right')
.toggleClass('end-scroll', $this.width() is $this.prop('scrollWidth')) .toggleClass('scrolling', $this.width() < $this.prop('scrollWidth'))
$ -> $ ->
$('.fade-left').addClass('end-scroll')
hideEndFade($('.scrolling-tabs')) hideEndFade($('.scrolling-tabs'))
...@@ -21,5 +20,5 @@ $ -> ...@@ -21,5 +20,5 @@ $ ->
currentPosition = $this.scrollLeft() currentPosition = $this.scrollLeft()
maxPosition = $this.prop('scrollWidth') - $this.outerWidth() maxPosition = $this.prop('scrollWidth') - $this.outerWidth()
$this.find('.fade-left').toggleClass('end-scroll', currentPosition is 0) $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0)
$this.find('.fade-right').toggleClass('end-scroll', currentPosition is maxPosition) $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1)
#= require raphael
#= require g.raphael
#= require g.bar
((w) ->
w.gl ?= {}
w.gl.text ?= {}
gl.text.randomString = -> Math.random().toString(36).substring(7)
gl.text.replaceRange = (s, start, end, substitute) ->
s.substring(0, start) + substitute + s.substring(end);
gl.text.selectedText = (text, textarea) ->
text.substring(textarea.selectionStart, textarea.selectionEnd)
gl.text.insertText = (textArea, text, tag, selected, wrap) ->
selectedSplit = selected.split('\n')
startChar = if not wrap and textArea.selectionStart > 0 then '\n' else ''
if selectedSplit.length > 1 and not wrap
insertText = selectedSplit.map((val) ->
if val.indexOf(tag) is 0
"#{val.replace(tag, '')}"
else
"#{tag}#{val}"
).join('\n')
else
insertText = "#{startChar}#{tag}#{selected}#{if wrap then tag else ' '}"
if document.queryCommandSupported('insertText')
document.execCommand 'insertText', false, insertText
else
try
document.execCommand("ms-beginUndoUnit")
textArea.value = @replaceRange(
text,
textArea.selectionStart,
textArea.selectionEnd,
insertText)
try
document.execCommand("ms-endUndoUnit")
@moveCursor(textArea, tag, wrap)
gl.text.moveCursor = (textArea, tag, wrapped) ->
return unless textArea.setSelectionRange
if textArea.selectionStart is textArea.selectionEnd
if wrapped
pos = textArea.selectionStart - tag.length
else
pos = textArea.selectionStart
textArea.setSelectionRange pos, pos
gl.text.updateText = (textArea, tag, wrap) ->
$textArea = $(textArea)
oldVal = $textArea.val()
textArea = $textArea.get(0)
text = $textArea.val()
selected = @selectedText(text, textArea)
$textArea.focus()
@insertText(textArea, text, tag, selected, wrap)
gl.text.init = (form) ->
self = @
$('.js-md', form)
.off 'click'
.on 'click', ->
$this = $(@)
self.updateText(
$this.closest('.md-area').find('textarea'),
$this.data('md-tag'),
not $this.data('md-prepend')
)
gl.text.removeListeners = (form) ->
$('.js-md', form).off()
) window
...@@ -4,18 +4,10 @@ class @Milestone ...@@ -4,18 +4,10 @@ class @Milestone
type: "PUT" type: "PUT"
url: issue_url url: issue_url
data: data data: data
success: (data) -> success: (_data) =>
if data.saved == true @successCallback(_data, li)
if data.assignee_avatar_url error: (data) ->
img_tag = $('<img/>') new Flash("Issue update failed", 'alert')
img_tag.attr('src', data.assignee_avatar_url)
img_tag.addClass('avatar s16')
$(li).find('.assignee-icon').html(img_tag)
else
$(li).find('.assignee-icon').html('')
$(li).effect 'highlight'
else
new Flash("Issue update failed", 'alert')
dataType: "json" dataType: "json"
@sortIssues: (data) -> @sortIssues: (data) ->
...@@ -25,9 +17,10 @@ class @Milestone ...@@ -25,9 +17,10 @@ class @Milestone
type: "PUT" type: "PUT"
url: sort_issues_url url: sort_issues_url
data: data data: data
success: (data) -> success: (_data) =>
if data.saved != true @successCallback(_data)
new Flash("Issues update failed", 'alert') error: ->
new Flash("Issues update failed", 'alert')
dataType: "json" dataType: "json"
@sortMergeRequests: (data) -> @sortMergeRequests: (data) ->
...@@ -37,9 +30,10 @@ class @Milestone ...@@ -37,9 +30,10 @@ class @Milestone
type: "PUT" type: "PUT"
url: sort_mr_url url: sort_mr_url
data: data data: data
success: (data) -> success: (_data) =>
if data.saved != true @successCallback(_data)
new Flash("MR update failed", 'alert') error: (data) ->
new Flash("Issue update failed", 'alert')
dataType: "json" dataType: "json"
@updateMergeRequest: (li, merge_request_url, data) -> @updateMergeRequest: (li, merge_request_url, data) ->
...@@ -47,20 +41,23 @@ class @Milestone ...@@ -47,20 +41,23 @@ class @Milestone
type: "PUT" type: "PUT"
url: merge_request_url url: merge_request_url
data: data data: data
success: (data) -> success: (_data) =>
if data.saved == true @successCallback(_data, li)
if data.assignee_avatar_url error: (data) ->
img_tag = $('<img/>') new Flash("Issue update failed", 'alert')
img_tag.attr('src', data.assignee_avatar_url)
img_tag.addClass('avatar s16')
$(li).find('.assignee-icon').html(img_tag)
else
$(li).find('.assignee-icon').html('')
$(li).effect 'highlight'
else
new Flash("Issue update failed", 'alert')
dataType: "json" dataType: "json"
@successCallback: (data, element) =>
if data.assignee
img_tag = $('<img/>')
img_tag.attr('src', data.assignee.avatar_url)
img_tag.addClass('avatar s16')
$(element).find('.assignee-icon').html(img_tag)
else
$(element).find('.assignee-icon').html('')
$(element).effect 'highlight'
constructor: -> constructor: ->
oldMouseStart = $.ui.sortable.prototype._mouseStart oldMouseStart = $.ui.sortable.prototype._mouseStart
$.ui.sortable.prototype._mouseStart = (event, overrideHandle, noActivation) -> $.ui.sortable.prototype._mouseStart = (event, overrideHandle, noActivation) ->
...@@ -81,8 +78,10 @@ class @Milestone ...@@ -81,8 +78,10 @@ class @Milestone
stop: (event, ui) -> stop: (event, ui) ->
$(".issues-sortable-list").css "min-height", "0px" $(".issues-sortable-list").css "min-height", "0px"
update: (event, ui) -> update: (event, ui) ->
data = $(this).sortable("serialize") # Prevents sorting from container which element has been removed.
Milestone.sortIssues(data) if $(this).find(ui.item).length > 0
data = $(this).sortable("serialize")
Milestone.sortIssues(data)
receive: (event, ui) -> receive: (event, ui) ->
new_state = $(this).data('state') new_state = $(this).data('state')
......
...@@ -4,9 +4,6 @@ ...@@ -4,9 +4,6 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the # It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file. # the compiled file.
# #
#= require raphael
#= require g.raphael
#= require g.bar
#= require_tree . #= require_tree .
$ -> $ ->
......
...@@ -102,12 +102,15 @@ class @Notes ...@@ -102,12 +102,15 @@ class @Notes
keydownNoteText: (e) -> keydownNoteText: (e) ->
$this = $(this) $this = $(this)
if $this.val() is '' and e.which is 38 #aka the up key if $this.val() is '' and e.which is 38 and not isMetaKey e
myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last") myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
if myLastNote.length if myLastNote.length
myLastNoteEditBtn = myLastNote.find('.js-note-edit') myLastNoteEditBtn = myLastNote.find('.js-note-edit')
myLastNoteEditBtn.trigger('click', [true, myLastNote]) myLastNoteEditBtn.trigger('click', [true, myLastNote])
isMetaKey = (e) ->
(e.metaKey or e.ctrlKey or e.altKey or e.shiftKey)
initRefresh: -> initRefresh: ->
clearInterval(Notes.interval) clearInterval(Notes.interval)
Notes.interval = setInterval => Notes.interval = setInterval =>
......
class @NotificationsDropdown class @NotificationsDropdown
$ -> constructor: ->
$(document) $(document)
.off 'click', '.update-notification' .off 'click', '.update-notification'
.on 'click', '.update-notification', (e) -> .on 'click', '.update-notification', (e) ->
...@@ -18,7 +18,8 @@ class @NotificationsDropdown ...@@ -18,7 +18,8 @@ class @NotificationsDropdown
.off 'ajax:success', '.notification-form' .off 'ajax:success', '.notification-form'
.on 'ajax:success', '.notification-form', (e, data) -> .on 'ajax:success', '.notification-form', (e, data) ->
if data.saved if data.saved
new Flash('Notification settings saved', 'notice') $(e.currentTarget)
$(e.currentTarget).closest('.notification-dropdown').replaceWith(data.html) .closest('.notification-dropdown')
.replaceWith(data.html)
else else
new Flash('Failed to save new settings', 'alert') new Flash('Failed to save new settings', 'alert')
...@@ -19,6 +19,7 @@ class @Project ...@@ -19,6 +19,7 @@ class @Project
$('.clone').text(url) $('.clone').text(url)
# Ref switcher # Ref switcher
@initRefSwitcher()
$('.project-refs-select').on 'change', -> $('.project-refs-select').on 'change', ->
$(@).parents('form').submit() $(@).parents('form').submit()
...@@ -34,7 +35,6 @@ class @Project ...@@ -34,7 +35,6 @@ class @Project
$(@).parents('.no-password-message').remove() $(@).parents('.no-password-message').remove()
e.preventDefault() e.preventDefault()
@projectSelectDropdown() @projectSelectDropdown()
projectSelectDropdown: -> projectSelectDropdown: ->
...@@ -50,3 +50,42 @@ class @Project ...@@ -50,3 +50,42 @@ class @Project
changeProject: (url) -> changeProject: (url) ->
window.location = url window.location = url
initRefSwitcher: ->
$('.js-project-refs-dropdown').each ->
$dropdown = $(@)
selected = $dropdown.data('selected')
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
url: $dropdown.data('refs-url')
data:
ref: $dropdown.data('ref')
).done (refs) ->
callback(refs)
selectable: true
filterable: true
filterByText: true
fieldName: 'ref'
renderRow: (ref) ->
if ref.header?
$('<li />')
.addClass('dropdown-header')
.text(ref.header)
else
link = $('<a />')
.attr('href', '#')
.addClass(if ref is selected then 'is-active' else '')
.text(ref)
.attr('data-ref', escape(ref))
$('<li />')
.append(link)
id: (obj, $el) ->
$el.attr('data-ref')
toggleLabel: (obj, $el) ->
$el.text().trim()
clicked: (e) ->
$dropdown.closest('form').submit()
)
# This is a manifest file that'll be compiled into including all the files listed below.
# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
# be included in the compiled file accessible from http://example.com/assets/application.js
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
# #
#= require d3
#= require_tree . #= require_tree .
...@@ -37,3 +37,4 @@ ...@@ -37,3 +37,4 @@
@import "framework/timeline.scss"; @import "framework/timeline.scss";
@import "framework/typography.scss"; @import "framework/typography.scss";
@import "framework/zen.scss"; @import "framework/zen.scss";
@import "framework/blank";
.blank-state {
padding-top: 20px;
padding-bottom: 20px;
text-align: center;
}
.blank-state-no-icon {
padding-top: 40px;
padding-bottom: 40px;
}
.blank-state-title {
margin-top: 0;
margin-bottom: 5px;
font-size: 19px;
font-weight: normal;
}
.blank-state-text {
margin-top: 0;
margin-bottom: $gl-padding;
font-size: 15px;
}
...@@ -97,6 +97,22 @@ ...@@ -97,6 +97,22 @@
} }
} }
.sub-header-block {
background-color: $white-light;
border-bottom: 1px solid $white-dark;
padding: 11px 0;
margin-bottom: 11px;
.oneline {
line-height: 35px;
}
&.no-bottom-space {
border-bottom: 0;
margin-bottom: 0;
}
}
.cover-block { .cover-block {
text-align: center; text-align: center;
background: $background-color; background: $background-color;
......
...@@ -461,10 +461,12 @@ ...@@ -461,10 +461,12 @@
} }
} }
.ui-state-active, .ui-datepicker-calendar {
.ui-state-hover { .ui-state-hover,
color: $md-link-color; .ui-state-active {
background-color: $calendar-hover-bg; color: #fff;
border: 0;
}
} }
.ui-datepicker-prev, .ui-datepicker-prev,
......
...@@ -26,7 +26,6 @@ header { ...@@ -26,7 +26,6 @@ header {
text-align: center; text-align: center;
#tanuki-logo, img { #tanuki-logo, img {
width: 36px;
height: 36px; height: 36px;
} }
} }
...@@ -132,6 +131,10 @@ header { ...@@ -132,6 +131,10 @@ header {
transition-duration: .3s; transition-duration: .3s;
z-index: 999; z-index: 999;
svg, img {
height: 36px;
}
&:hover { &:hover {
cursor: pointer; cursor: pointer;
} }
......
...@@ -65,6 +65,11 @@ ...@@ -65,6 +65,11 @@
a { a {
padding-top: 0; padding-top: 0;
line-height: 1; line-height: 1;
border-bottom: 1px solid $border-color;
&.btn.btn-xs {
padding: 2px 5px;
}
} }
} }
} }
...@@ -97,5 +102,30 @@ ...@@ -97,5 +102,30 @@
white-space: pre-wrap; white-space: pre-wrap;
word-break: keep-all; word-break: keep-all;
} }
@include bulleted-list;
}
}
.toolbar-group {
float: left;
margin-right: -5px;
margin-left: $gl-padding;
&:first-child {
margin-left: 0;
}
}
.toolbar-btn {
float: left;
padding: 0 5px;
color: #959494;
background: transparent;
border: 0;
outline: 0;
&:hover {
color: $gl-link-color;
} }
} }
...@@ -110,3 +110,17 @@ ...@@ -110,3 +110,17 @@
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 24px;
} }
@mixin bulleted-list {
> ul {
list-style-type: disc;
ul {
list-style-type: circle;
ul {
list-style-type: square;
}
}
}
}
\ No newline at end of file
@mixin fade($gradient-direction, $rgba, $gradient-color) { @mixin fade($gradient-direction, $rgba, $gradient-color) {
visibility: visible; visibility: hidden;
opacity: 1; opacity: 0;
z-index: 2; z-index: 2;
position: absolute; position: absolute;
bottom: 12px; bottom: 12px;
...@@ -13,11 +13,18 @@ ...@@ -13,11 +13,18 @@
background: -moz-linear-gradient($gradient-direction, $rgba, $gradient-color 45%); background: -moz-linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
background: linear-gradient($gradient-direction, $rgba, $gradient-color 45%); background: linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
&.end-scroll { &.scrolling {
visibility: hidden; visibility: visible;
opacity: 0; opacity: 1;
transition-duration: .3s; transition-duration: .3s;
} }
.fa {
position: relative;
top: 3px;
font-size: 13px;
color: $btn-placeholder-gray;
}
} }
@mixin scrolling-links() { @mixin scrolling-links() {
...@@ -25,6 +32,7 @@ ...@@ -25,6 +32,7 @@
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
&::-webkit-scrollbar { &::-webkit-scrollbar {
display: none; display: none;
} }
...@@ -104,10 +112,6 @@ ...@@ -104,10 +112,6 @@
width: 50%; width: 50%;
line-height: 28px; line-height: 28px;
&.wiki-page {
padding: 16px 10px 11px;
}
/* Small devices (phones, tablets, 768px and lower) */ /* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) { @media (max-width: $screen-sm-min) {
width: 100%; width: 100%;
...@@ -136,7 +140,7 @@ ...@@ -136,7 +140,7 @@
} }
/* Small devices (phones, tablets, 768px and lower) */ /* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-max) { @media (max-width: $screen-xs-max) {
width: 100%; width: 100%;
} }
} }
...@@ -220,6 +224,7 @@ ...@@ -220,6 +224,7 @@
form { form {
display: block; display: block;
height: auto; height: auto;
margin-bottom: 14px;
input { input {
width: 100%; width: 100%;
...@@ -268,7 +273,7 @@ ...@@ -268,7 +273,7 @@
float: right; float: right;
padding: 7px 0 0; padding: 7px 0 0;
@media (max-width: $screen-xs-max) { @media (max-width: $screen-sm-max) {
display: none; display: none;
} }
...@@ -299,33 +304,9 @@ ...@@ -299,33 +304,9 @@
} }
.nav-links { .nav-links {
@include scrolling-links();
border-bottom: none; border-bottom: none;
height: 51px; height: 51px;
svg {
position: relative;
top: 2px;
margin-right: 2px;
height: 15px;
width: auto;
path,
polygon {
fill: $layout-link-gray;
}
}
.fade-right {
@include fade(left, rgba(250, 250, 250, 0.4), $background-color);
right: 0;
}
.fade-left {
@include fade(right, rgba(250, 250, 250, 0.4), $background-color);
left: 0;
}
li { li {
a { a {
...@@ -361,18 +342,6 @@ ...@@ -361,18 +342,6 @@
} }
} }
} }
.nav-control {
.fade-right {
@media (min-width: $screen-xs-max) {
right: 68px;
}
@media (max-width: $screen-xs-min) {
right: 0;
}
}
}
} }
.scrolling-tabs-container { .scrolling-tabs-container {
...@@ -380,15 +349,42 @@ ...@@ -380,15 +349,42 @@
.nav-links { .nav-links {
@include scrolling-links(); @include scrolling-links();
}
.fade-right {
@include fade(left, rgba(255, 255, 255, 0.4), $background-color);
right: -5px;
.fa {
right: -7px;
}
}
.fade-left {
@include fade(right, rgba(255, 255, 255, 0.4), $background-color);
left: -5px;
.fa {
left: -7px;
}
}
&.sub-nav-scroll {
.fade-right { .fade-right {
@include fade(left, rgba(255, 255, 255, 0.4), $background-color);
right: 0; right: 0;
.fa {
right: -23px;
}
} }
.fade-left { .fade-left {
@include fade(right, rgba(255, 255, 255, 0.4), $background-color);
left: 0; left: 0;
.fa {
left: 10px;
}
} }
} }
} }
...@@ -401,21 +397,19 @@ ...@@ -401,21 +397,19 @@
.fade-right { .fade-right {
@include fade(left, rgba(255, 255, 255, 0.4), $white-light); @include fade(left, rgba(255, 255, 255, 0.4), $white-light);
right: 0; right: -5px;
.fa {
right: -7px;
}
} }
.fade-left { .fade-left {
@include fade(right, rgba(255, 255, 255, 0.4), $white-light); @include fade(right, rgba(255, 255, 255, 0.4), $white-light);
left: 0; left: -5px;
}
&.event-filter {
.fade-right {
visibility: hidden;
@media (max-width: $screen-xs-max) { .fa {
visibility: visible; left: -7px;
}
} }
} }
} }
......
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
margin-top: -2px; margin-top: -2px;
float: right; float: right;
} }
.dropdown-menu-toggle {
line-height: 20px;
}
} }
.panel-body { .panel-body {
......
...@@ -165,11 +165,6 @@ ...@@ -165,11 +165,6 @@
background-size: 16px 16px !important; background-size: 16px 16px !important;
} }
/** Branch/tag selector **/
.project-refs-form .select2-container {
width: 160px !important;
}
.select2-results .select2-no-results, .select2-results .select2-no-results,
.select2-results .select2-searching, .select2-results .select2-searching,
.select2-results .select2-ajax-error, .select2-results .select2-ajax-error,
......
...@@ -91,7 +91,6 @@ ...@@ -91,7 +91,6 @@
text-decoration: none; text-decoration: none;
font-weight: normal; font-weight: normal;
outline: none; outline: none;
white-space: nowrap;
&:hover, &:hover,
&:active, &:active,
......
...@@ -8,8 +8,9 @@ ...@@ -8,8 +8,9 @@
.emoji-menu { .emoji-menu {
position: absolute; position: absolute;
margin-top: 3px; margin-top: 3px;
z-index: 1000; padding: $gl-padding;
min-width: 160px; z-index: 9;
width: 300px;
font-size: 14px; font-size: 14px;
background-color: $award-emoji-menu-bg; background-color: $award-emoji-menu-bg;
border: 1px solid $award-emoji-menu-border; border: 1px solid $award-emoji-menu-border;
...@@ -33,20 +34,18 @@ ...@@ -33,20 +34,18 @@
} }
.emoji-menu-content { .emoji-menu-content {
padding: $gl-padding;
width: 300px;
height: 300px; height: 300px;
overflow-y: scroll; overflow-y: scroll;
input.emoji-search {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC");
background-repeat: no-repeat;
background-position: right 5px center;
background-size: 16px;
}
} }
} }
.emoji-search {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC");
background-repeat: no-repeat;
background-position: right 5px center;
background-size: 16px;
}
.emoji-menu-list { .emoji-menu-list {
list-style: none; list-style: none;
padding-left: 0; padding-left: 0;
......
...@@ -80,9 +80,14 @@ ...@@ -80,9 +80,14 @@
.commit { .commit {
padding: 10px 0; padding: 10px 0;
position: relative;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-left: 46px; padding-left: 20px;
.commit-info-block {
padding-left: 44px;
}
} }
&:not(:last-child) { &:not(:last-child) {
...@@ -95,8 +100,11 @@ ...@@ -95,8 +100,11 @@
vertical-align: baseline; vertical-align: baseline;
} }
.avatar { .avatar {
margin-left: -46px; position: absolute;
top: 10px;
left: 16px;
} }
.item-title { .item-title {
......
...@@ -4,6 +4,11 @@ ...@@ -4,6 +4,11 @@
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
border-radius: 3px; border-radius: 3px;
.commit-short-id {
font-family: $regular_font;
font-weight: 400;
}
.diff-header { .diff-header {
position: relative; position: relative;
background: $background-color; background: $background-color;
......
...@@ -60,13 +60,14 @@ ...@@ -60,13 +60,14 @@
.encoding-selector, .encoding-selector,
.license-selector, .license-selector,
.gitignore-selector { .gitignore-selector,
.gitlab-ci-yml-selector {
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
font-family: $regular_font; font-family: $regular_font;
} }
.gitignore-selector, .license-selector { .gitignore-selector, .license-selector, .gitlab-ci-yml-selector {
.dropdown { .dropdown {
line-height: 21px; line-height: 21px;
} }
...@@ -76,4 +77,10 @@ ...@@ -76,4 +77,10 @@
width: 220px; width: 220px;
} }
} }
.gitlab-ci-yml-selector {
.dropdown-menu-toggle {
width: 250px;
}
}
} }
...@@ -54,6 +54,10 @@ ...@@ -54,6 +54,10 @@
} }
} }
code {
white-space: pre-wrap;
}
pre { pre {
border: none; border: none;
background: #f9f9f9; background: #f9f9f9;
......
...@@ -57,4 +57,12 @@ ...@@ -57,4 +57,12 @@
.documentation { .documentation {
padding: 7px; padding: 7px;
// Border around images in the help pages.
img:not(.emoji) {
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px;
max-height: calc(100vh - 100px);
}
} }
...@@ -4,6 +4,14 @@ ...@@ -4,6 +4,14 @@
margin-right: 1px; margin-right: 1px;
} }
} }
// Border around images in issue and MR descriptions.
.description img:not(.emoji) {
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px;
max-height: calc(100vh - 100px);
}
} }
.issuable-filter-count { .issuable-filter-count {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
height: 30px; height: 30px;
display: inline-block; display: inline-block;
margin-right: 10px; margin-right: 10px;
margin-bottom: 10px;
} }
&.suggest-colors-dropdown { &.suggest-colors-dropdown {
...@@ -50,11 +51,10 @@ ...@@ -50,11 +51,10 @@
.label-row { .label-row {
.label-name { .label-name {
display: block; display: inline-block;
margin-bottom: 10px; margin-bottom: 10px;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
display: inline-block;
width: 200px; width: 200px;
margin-bottom: 0; margin-bottom: 0;
} }
...@@ -63,6 +63,7 @@ ...@@ -63,6 +63,7 @@
.label-description { .label-description {
display: block; display: block;
margin-bottom: 10px; margin-bottom: 10px;
margin-left: 50px;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
display: inline-block; display: inline-block;
......
...@@ -119,7 +119,12 @@ ...@@ -119,7 +119,12 @@
margin-bottom: 0; margin-bottom: 0;
} }
@media (max-width: $screen-sm-max) { .btn-grouped {
margin-left: 0;
margin-right: 7px;
}
@media (max-width: $screen-xs-max) {
h4 { h4 {
font-size: 15px; font-size: 15px;
} }
...@@ -131,10 +136,14 @@ ...@@ -131,10 +136,14 @@
.btn, .btn,
.btn-group, .btn-group,
.accept-action { .accept-action {
width: 100%;
margin-bottom: 4px; margin-bottom: 4px;
} }
.accept-action {
width: 100%;
text-align: center;
}
.accept-control { .accept-control {
width: 100%; width: 100%;
text-align: center; text-align: center;
...@@ -284,7 +293,7 @@ ...@@ -284,7 +293,7 @@
margin-bottom: 0; margin-bottom: 0;
} }
@media (min-width: $screen-sm-min) { @media (min-width: $screen-xs-min) {
float: left; float: left;
width: 50%; width: 50%;
margin-bottom: 0; margin-bottom: 0;
......
...@@ -179,6 +179,10 @@ ...@@ -179,6 +179,10 @@
border-top: 1px solid $border-color; border-top: 1px solid $border-color;
} }
.md-helper {
padding-top: 10px;
}
.toolbar-button { .toolbar-button {
padding: 0; padding: 0;
background: none; background: none;
...@@ -219,3 +223,16 @@ ...@@ -219,3 +223,16 @@
float: left; float: left;
} }
} }
.note-form-actions {
@media (max-width: $screen-xs-max) {
.btn {
float: none;
width: 100%;
&:not(:last-child) {
margin-bottom: 10px;
}
}
}
}
...@@ -84,24 +84,14 @@ ul.notes { ...@@ -84,24 +84,14 @@ ul.notes {
word-wrap: break-word; word-wrap: break-word;
@include md-typography; @include md-typography;
// Reset ul style types since we're nested inside a ul already
@include bulleted-list;
// On diffs code should wrap nicely and not overflow // On diffs code should wrap nicely and not overflow
code { code {
white-space: pre-wrap; white-space: pre-wrap;
} }
// Reset ul style types since we're nested inside a ul already
& > ul {
list-style-type: disc;
ul {
list-style-type: circle;
ul {
list-style-type: square;
}
}
}
ul.task-list { ul.task-list {
ul:not(.task-list) { ul:not(.task-list) {
padding-left: 1.3em; padding-left: 1.3em;
...@@ -117,6 +107,14 @@ ul.notes { ...@@ -117,6 +107,14 @@ ul.notes {
code { code {
word-break: keep-all; word-break: keep-all;
} }
// Border around images in issue and MR comments.
img:not(.emoji) {
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px 0;
max-height: calc(100vh - 100px);
}
} }
} }
......
...@@ -101,7 +101,8 @@ ...@@ -101,7 +101,8 @@
.notifications-btn { .notifications-btn {
.fa-bell { .fa-bell,
.fa-spinner {
margin-right: 6px; margin-right: 6px;
} }
...@@ -373,7 +374,7 @@ a.deploy-project-label { ...@@ -373,7 +374,7 @@ a.deploy-project-label {
.project-stats { .project-stats {
margin-top: $gl-padding; margin-top: $gl-padding;
margin-bottom: 0; margin-bottom: 0;
padding: 16px 0; padding: 0;
background-color: $white-light; background-color: $white-light;
font-size: 0; font-size: 0;
...@@ -382,13 +383,14 @@ a.deploy-project-label { ...@@ -382,13 +383,14 @@ a.deploy-project-label {
} }
.nav li { .nav li {
display: inline; display: inline-block;
margin: 16px 0;
margin-right: 16px;
} }
.nav > li > a { .nav > li > a {
background-color: transparent; background-color: transparent;
margin-right: 12px; padding: 5px 10px;
padding: 0 10px;
font-size: 15px; font-size: 15px;
color: $notes-light-color; color: $notes-light-color;
} }
...@@ -402,12 +404,17 @@ a.deploy-project-label { ...@@ -402,12 +404,17 @@ a.deploy-project-label {
font-size: 17px; font-size: 17px;
} }
li.missing a { li.missing {
color: #5a6069; border: 1px dashed $border-gray-light;
border: 1px dashed #dce0e5; border-radius: $border-radius-default;
a {
color: $notes-light-color;
display: block;
}
&:hover { &:hover {
background-color: #f0f2f5; background-color: $gray-normal;
} }
} }
...@@ -616,3 +623,9 @@ pre.light-well { ...@@ -616,3 +623,9 @@ pre.light-well {
color: $gl-text-green; color: $gl-text-green;
} }
} }
.project-refs-form {
.dropdown-menu {
width: 300px;
}
}
...@@ -14,24 +14,38 @@ ...@@ -14,24 +14,38 @@
font-size: 10px; font-size: 10px;
} }
#contributors-master {
@include make-md-column(12);
svg {
width: 100%;
}
}
#contributors { #contributors {
.contributors-list { .contributors-list {
margin: 0 0 10px; margin: 0 0 10px;
list-style: none; list-style: none;
padding: 0; padding: 0;
svg {
width: 100%;
}
} }
.person { .person {
&:nth-child(even) { @include make-md-column(6);
float: right;
}
float: left;
margin-top: 10px; margin-top: 10px;
@media (max-width: $screen-sm-min) {
width: 100%;
}
} }
.person .spark { .person .spark {
display: block; display: block;
background: #f3f3f3; background: #f3f3f3;
width: 100%;
} }
.person .area-contributor { .person .area-contributor {
......
...@@ -62,6 +62,10 @@ ...@@ -62,6 +62,10 @@
} }
} }
code {
white-space: pre-wrap;
}
pre { pre {
border: none; border: none;
background: #f9f9f9; background: #f9f9f9;
......
...@@ -5,6 +5,7 @@ class Admin::AppearancesController < Admin::ApplicationController ...@@ -5,6 +5,7 @@ class Admin::AppearancesController < Admin::ApplicationController
end end
def preview def preview
render 'preview', layout: 'devise'
end end
def create def create
......
class Admin::RunnerProjectsController < Admin::ApplicationController class Admin::RunnerProjectsController < Admin::ApplicationController
before_action :project, only: [:create] before_action :project, only: [:create]
def index
@runner_projects = project.runner_projects.all
@runner_project = project.runner_projects.new
end
def create def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id]) @runner = Ci::Runner.find(params[:runner_project][:runner_id])
if @runner.assign_to(@project, current_user) return head(403) if @runner.is_shared? || @runner.locked?
runner_project = @runner.assign_to(@project, current_user)
if runner_project.persisted?
redirect_to admin_runner_path(@runner) redirect_to admin_runner_path(@runner)
else else
redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project' redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project'
......
...@@ -36,6 +36,10 @@ class ApplicationController < ActionController::Base ...@@ -36,6 +36,10 @@ class ApplicationController < ActionController::Base
render_404 render_404
end end
rescue_from Gitlab::Access::AccessDeniedError do |exception|
render_403
end
def redirect_back_or_default(default: root_path, options: {}) def redirect_back_or_default(default: root_path, options: {})
redirect_to request.referer.present? ? :back : default, options redirect_to request.referer.present? ? :back : default, options
end end
......
...@@ -21,29 +21,18 @@ module MembershipActions ...@@ -21,29 +21,18 @@ module MembershipActions
def leave def leave
@member = membershipable.members.find_by(user_id: current_user) @member = membershipable.members.find_by(user_id: current_user)
return render_403 unless @member Members::DestroyService.new(@member, current_user).execute
source_type = @member.real_source_type.humanize(capitalize: false) source_type = @member.real_source_type.humanize(capitalize: false)
notice =
if can?(current_user, action_member_permission(:destroy, @member), @member) if @member.request?
notice = "Your access request to the #{source_type} has been withdrawn."
if @member.request?
"Your access request to the #{source_type} has been withdrawn."
else
"You left the \"#{@member.source.human_name}\" #{source_type}."
end
@member.destroy
redirect_to [:dashboard, @member.real_source_type.tableize], notice: notice
else
if cannot_leave?
alert = "You can not leave the \"#{@member.source.human_name}\" #{source_type}."
alert << " Transfer or delete the #{source_type}."
redirect_to polymorphic_url(membershipable), alert: alert
else else
render_403 "You left the \"#{@member.source.human_name}\" #{source_type}."
end end
end redirect_path = @member.request? ? @member.source : [:dashboard, @member.real_source_type.tableize]
redirect_to redirect_path, notice: notice
end end
protected protected
...@@ -51,8 +40,4 @@ module MembershipActions ...@@ -51,8 +40,4 @@ module MembershipActions
def membershipable def membershipable
raise NotImplementedError raise NotImplementedError
end end
def cannot_leave?
raise NotImplementedError
end
end end
class Dashboard::GroupsController < Dashboard::ApplicationController class Dashboard::GroupsController < Dashboard::ApplicationController
def index def index
@group_members = current_user.group_members.page(params[:page]) @group_members = current_user.group_members.includes(:source).page(params[:page])
end end
end end
...@@ -36,9 +36,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -36,9 +36,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
def destroy def destroy
@group_member = @group.group_members.find(params[:id]) @group_member = @group.group_members.find(params[:id])
return render_403 unless can?(current_user, :destroy_group_member, @group_member) Members::DestroyService.new(@group_member, current_user).execute
@group_member.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
...@@ -68,8 +66,4 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -68,8 +66,4 @@ class Groups::GroupMembersController < Groups::ApplicationController
# MembershipActions concern # MembershipActions concern
alias_method :membershipable, :group alias_method :membershipable, :group
def cannot_leave?
@group.last_owner?(current_user)
end
end end
...@@ -12,9 +12,13 @@ class Import::GitlabProjectsController < Import::BaseController ...@@ -12,9 +12,13 @@ class Import::GitlabProjectsController < Import::BaseController
return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." }) return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." })
end end
imported_file = project_params[:file].path + "-import"
FileUtils.copy_entry(project_params[:file].path, imported_file)
@project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id], @project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id],
current_user, current_user,
File.expand_path(project_params[:file].path), File.expand_path(imported_file),
project_params[:path]).execute project_params[:path]).execute
if @project.saved? if @project.saved?
......
...@@ -74,7 +74,7 @@ class Projects::ApplicationController < ApplicationController ...@@ -74,7 +74,7 @@ class Projects::ApplicationController < ApplicationController
end end
def require_branch_head def require_branch_head
unless @repository.branch_names.include?(@ref) unless @repository.branch_exists?(@ref)
redirect_to( redirect_to(
namespace_project_tree_path(@project.namespace, @project, @ref), namespace_project_tree_path(@project.namespace, @project, @ref),
notice: "This action is not allowed unless you are on a branch" notice: "This action is not allowed unless you are on a branch"
......
...@@ -16,6 +16,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -16,6 +16,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action :from_merge_request, only: [:edit, :update] before_action :from_merge_request, only: [:edit, :update]
before_action :require_branch_head, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff] before_action :editor_variables, except: [:show, :preview, :diff]
before_action :validate_diff_params, only: :diff
def new def new
commit unless @repository.empty? commit unless @repository.empty?
...@@ -146,4 +147,10 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -146,4 +147,10 @@ class Projects::BlobController < Projects::ApplicationController
file_content_encoding: params[:encoding] file_content_encoding: params[:encoding]
} }
end end
def validate_diff_params
if [:since, :to, :offset].any? { |key| params[key].blank? }
render nothing: true
end
end
end end
...@@ -18,9 +18,16 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -18,9 +18,16 @@ class Projects::CommitController < Projects::ApplicationController
apply_diff_view_cookie! apply_diff_view_cookie!
@grouped_diff_notes = commit.notes.grouped_diff_notes @grouped_diff_notes = commit.notes.grouped_diff_notes
@notes = commit.notes.non_diff_notes.fresh
Banzai::NoteRenderer.render(
@grouped_diff_notes.values.flatten + @notes,
@project,
current_user,
)
@note = @project.build_commit_note(commit) @note = @project.build_commit_note(commit)
@notes = commit.notes.non_diff_notes.fresh
@noteable = @commit @noteable = @commit
@comments_target = { @comments_target = {
noteable_type: 'Commit', noteable_type: 'Commit',
......
...@@ -62,8 +62,12 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -62,8 +62,12 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def show def show
raw_notes = @issue.notes_with_associations.fresh
@notes = Banzai::NoteRenderer.
render(raw_notes, @project, current_user, @path, @project_wiki, @ref)
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.with_associations.fresh
@noteable = @issue @noteable = @issue
respond_to do |format| respond_to do |format|
...@@ -111,6 +115,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -111,6 +115,7 @@ class Projects::IssuesController < Projects::ApplicationController
render :edit render :edit
end end
end end
format.json do format.json do
render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }) render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end end
......
...@@ -85,6 +85,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -85,6 +85,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@grouped_diff_notes = @merge_request.notes.grouped_diff_notes @grouped_diff_notes = @merge_request.notes.grouped_diff_notes
Banzai::NoteRenderer.render(
@grouped_diff_notes.values.flatten,
@project,
current_user,
@path,
@project_wiki,
@ref
)
respond_to do |format| respond_to do |format|
format.html format.html
format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } } format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } }
...@@ -190,7 +199,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -190,7 +199,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def merge def merge
return access_denied! unless @merge_request.can_be_merged_by?(current_user) return access_denied! unless @merge_request.can_be_merged_by?(current_user)
unless @merge_request.mergeable? # Disable the CI check if merge_when_build_succeeds is enabled since we have
# to wait until CI completes to know
unless @merge_request.mergeable?(skip_ci_check: merge_when_build_succeeds_active?)
@status = :failed @status = :failed
return return
end end
...@@ -204,8 +215,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -204,8 +215,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@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?
if @merge_request.pipeline && @merge_request.pipeline.active? unless @merge_request.pipeline
@status = :failed
return
end
if @merge_request.pipeline.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
.execute(@merge_request) .execute(@merge_request)
@status = :merge_when_build_succeeds @status = :merge_when_build_succeeds
...@@ -320,8 +336,21 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -320,8 +336,21 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_show_vars def define_show_vars
# Build a note object for comment form # Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request) @note = @project.notes.new(noteable: @merge_request)
@notes = @merge_request.mr_and_commit_notes.inc_author.fresh
@discussions = @notes.discussions @discussions = @merge_request.mr_and_commit_notes.
inc_author_project_award_emoji.
fresh.
discussions
@notes = Banzai::NoteRenderer.render(
@discussions.flatten,
@project,
current_user,
@path,
@project_wiki,
@ref
)
@noteable = @merge_request @noteable = @merge_request
# Get commits from repository # Get commits from repository
...@@ -368,4 +397,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -368,4 +397,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def ensure_ref_fetched def ensure_ref_fetched
@merge_request.ensure_ref_fetched @merge_request.ensure_ref_fetched
end end
def merge_when_build_succeeds_active?
params[:merge_when_build_succeeds].present? &&
@merge_request.pipeline && @merge_request.pipeline.active?
end
end end
...@@ -24,6 +24,10 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -24,6 +24,10 @@ class Projects::NotesController < Projects::ApplicationController
def create def create
@note = Notes::CreateService.new(project, current_user, note_params).execute @note = Notes::CreateService.new(project, current_user, note_params).execute
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
end
respond_to do |format| respond_to do |format|
format.json { render json: note_json(@note) } format.json { render json: note_json(@note) }
format.html { redirect_back_or_default } format.html { redirect_back_or_default }
...@@ -33,6 +37,10 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -33,6 +37,10 @@ class Projects::NotesController < Projects::ApplicationController
def update def update
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note) @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
end
respond_to do |format| respond_to do |format|
format.json { render json: note_json(@note) } format.json { render json: note_json(@note) }
format.html { redirect_back_or_default } format.html { redirect_back_or_default }
...@@ -118,6 +126,8 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -118,6 +126,8 @@ class Projects::NotesController < Projects::ApplicationController
name: note.name name: note.name
} }
elsif note.valid? elsif note.valid?
Banzai::NoteRenderer.render([note], @project, current_user)
{ {
valid: true, valid: true,
id: note.id, id: note.id,
......
...@@ -54,6 +54,6 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -54,6 +54,6 @@ class Projects::PipelinesController < Projects::ApplicationController
end end
def commit def commit
@commit ||= @pipeline.commit_data @commit ||= @pipeline.commit
end end
end end
...@@ -50,9 +50,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -50,9 +50,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def destroy def destroy
@project_member = @project.project_members.find(params[:id]) @project_member = @project.project_members.find(params[:id])
return render_403 unless can?(current_user, :destroy_project_member, @project_member) Members::DestroyService.new(@project_member, current_user).execute
@project_member.destroy
respond_to do |format| respond_to do |format|
format.html do format.html do
...@@ -98,8 +96,4 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -98,8 +96,4 @@ class Projects::ProjectMembersController < Projects::ApplicationController
# MembershipActions concern # MembershipActions concern
alias_method :membershipable, :project alias_method :membershipable, :project
def cannot_leave?
current_user == @project.owner
end
end end
...@@ -6,11 +6,13 @@ class Projects::RunnerProjectsController < Projects::ApplicationController ...@@ -6,11 +6,13 @@ class Projects::RunnerProjectsController < Projects::ApplicationController
def create def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id]) @runner = Ci::Runner.find(params[:runner_project][:runner_id])
return head(403) if @runner.is_shared? || @runner.locked?
return head(403) unless current_user.ci_authorized_runners.include?(@runner) return head(403) unless current_user.ci_authorized_runners.include?(@runner)
path = runners_path(project) path = runners_path(project)
runner_project = @runner.assign_to(project, current_user)
if @runner.assign_to(project, current_user) if runner_project.persisted?
redirect_to path redirect_to path
else else
redirect_to path, alert: 'Failed adding runner to project' redirect_to path, alert: 'Failed adding runner to project'
......
...@@ -5,10 +5,9 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -5,10 +5,9 @@ class Projects::RunnersController < Projects::ApplicationController
layout 'project_settings' layout 'project_settings'
def index def index
@runners = project.runners.ordered @project_runners = project.runners.ordered
@specific_runners = current_user.ci_authorized_runners. @assignable_runners = current_user.ci_authorized_runners.
where.not(id: project.runners). assignable_for(project).ordered.page(params[:page]).per(20)
ordered.page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active @shared_runners = Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all) @shared_runners_count = @shared_runners.count(:all)
end end
......
class ProjectsController < Projects::ApplicationController class ProjectsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
before_action :authenticate_user!, except: [:show, :activity] before_action :authenticate_user!, except: [:show, :activity, :refs]
before_action :project, except: [:new, :create] before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create] before_action :repository, except: [:new, :create]
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists? before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
...@@ -251,6 +251,24 @@ class ProjectsController < Projects::ApplicationController ...@@ -251,6 +251,24 @@ class ProjectsController < Projects::ApplicationController
} }
end end
def refs
options = {
'Branches' => @repository.branch_names,
}
unless @repository.tag_count.zero?
options['Tags'] = VersionSorter.rsort(@repository.tag_names)
end
# If reference is commit id - we should add it to branch/tag selectbox
ref = Addressable::URI.unescape(params[:ref])
if ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/
options['Commits'] = [ref]
end
render json: options.to_json
end
private private
def determine_layout def determine_layout
...@@ -285,8 +303,14 @@ class ProjectsController < Projects::ApplicationController ...@@ -285,8 +303,14 @@ class ProjectsController < Projects::ApplicationController
project.repository_exists? && !project.empty_repo? project.repository_exists? && !project.empty_repo?
end end
# Override get_id from ExtractsPath, which returns the branch and file path # Override extract_ref from ExtractsPath, which returns the branch and file path
# for the blob/tree, which in this case is just the root of the default branch. # for the blob/tree, which in this case is just the root of the default branch.
# This way we avoid to access the repository.ref_names.
def extract_ref(_id)
[get_id, '']
end
# Override get_id from ExtractsPath in this case is just the root of the default branch.
def get_id def get_id
project.repository.root_ref project.repository.root_ref
end end
......
...@@ -101,22 +101,6 @@ module ApplicationHelper ...@@ -101,22 +101,6 @@ module ApplicationHelper
'Never' 'Never'
end end
def grouped_options_refs
repository = @project.repository
options = [
['Branches', repository.branch_names],
['Tags', VersionSorter.rsort(repository.tag_names)]
]
# If reference is commit id - we should add it to branch/tag selectbox
if @ref && !options.flatten.include?(@ref) && @ref =~ /\A[0-9a-zA-Z]{6,52}\z/
options << ['Commit', [@ref]]
end
grouped_options_for_select(options, @ref || @project.default_branch)
end
# Define whenever show last push event # Define whenever show last push event
# with suggestion to create MR # with suggestion to create MR
def show_last_push_widget?(event) def show_last_push_widget?(event)
...@@ -132,7 +116,7 @@ module ApplicationHelper ...@@ -132,7 +116,7 @@ module ApplicationHelper
return false if project.merge_requests.where(source_branch: event.branch_name).opened.any? return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
# Skip if user removed branch right after that # Skip if user removed branch right after that
return false unless project.repository.branch_names.include?(event.branch_name) return false unless project.repository.branch_exists?(event.branch_name)
true true
end end
...@@ -213,7 +197,7 @@ module ApplicationHelper ...@@ -213,7 +197,7 @@ module ApplicationHelper
def render_markup(file_name, file_content) def render_markup(file_name, file_content)
if gitlab_markdown?(file_name) if gitlab_markdown?(file_name)
Haml::Helpers.preserve(markdown(file_content)) Hamlit::RailsHelpers.preserve(markdown(file_content))
elsif asciidoc?(file_name) elsif asciidoc?(file_name)
asciidoc(file_content) asciidoc(file_content)
elsif plain?(file_name) elsif plain?(file_name)
......
module BlobHelper module BlobHelper
def highlighter(blob_name, blob_content, nowrap: false) def highlighter(blob_name, blob_content, repository: nil, nowrap: false)
Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap) Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap, repository: repository)
end end
def highlight(blob_name, blob_content, nowrap: false, plain: false) def highlight(blob_name, blob_content, repository: nil, nowrap: false, plain: false)
Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain) Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain, repository: repository)
end end
def no_highlight_files def no_highlight_files
...@@ -29,7 +29,7 @@ module BlobHelper ...@@ -29,7 +29,7 @@ module BlobHelper
if !on_top_of_branch?(project, ref) if !on_top_of_branch?(project, ref)
button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' } button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref) elsif can_edit_blob?(blob, project, ref)
link_to "Edit", edit_path, class: 'btn btn-file-option' link_to "Edit", edit_path, class: 'btn btn-sm'
elsif can?(current_user, :fork_project, project) elsif can?(current_user, :fork_project, project)
continue_params = { continue_params = {
to: edit_path, to: edit_path,
...@@ -186,12 +186,16 @@ module BlobHelper ...@@ -186,12 +186,16 @@ module BlobHelper
end end
def gitignore_names def gitignore_names
return @gitignore_names if defined?(@gitignore_names) @gitignore_names ||=
Gitlab::Template::Gitignore.categories.keys.map do |k|
[k, Gitlab::Template::Gitignore.by_category(k).map { |t| { name: t.name } }]
end.to_h
end
@gitignore_names = { def gitlab_ci_ymls
Global: Gitlab::Gitignore.global.map { |gitignore| { name: gitignore.name } }, @gitlab_ci_ymls ||=
# Note that the key here doesn't cover it really Gitlab::Template::GitlabCiYml.categories.keys.map do |k|
Languages: Gitlab::Gitignore.languages_frameworks.map{ |gitignore| { name: gitignore.name } } [k, Gitlab::Template::GitlabCiYml.by_category(k).map { |t| { name: t.name } }]
} end.to_h
end end
end end
...@@ -10,7 +10,7 @@ module BranchesHelper ...@@ -10,7 +10,7 @@ module BranchesHelper
end end
def can_push_branch?(project, branch_name) def can_push_branch?(project, branch_name)
return false unless project.repository.branch_names.include?(branch_name) return false unless project.repository.branch_exists?(branch_name)
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name) ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
end end
......
...@@ -50,8 +50,6 @@ module GitlabMarkdownHelper ...@@ -50,8 +50,6 @@ module GitlabMarkdownHelper
context[:project] ||= @project context[:project] ||= @project
text = Banzai.pre_process(text, context)
html = Banzai.render(text, context) html = Banzai.render(text, context)
context.merge!( context.merge!(
...@@ -185,4 +183,17 @@ module GitlabMarkdownHelper ...@@ -185,4 +183,17 @@ module GitlabMarkdownHelper
'' ''
end end
end end
def markdown_toolbar_button(options = {})
data = options[:data].merge({ container: "body" })
content_tag :button,
type: "button",
class: "toolbar-btn js-md has-tooltip hidden-xs",
tabindex: -1,
data: data,
title: options[:title],
aria: { label: options[:title] } do
icon(options[:icon])
end
end
end end
module JavascriptHelper module JavascriptHelper
def page_specific_javascripts(js = nil) def page_specific_javascript_tag(js)
@page_specific_javascripts = js unless js.nil? javascript_include_tag asset_path(js), { "data-turbolinks-track" => true }
@page_specific_javascripts
end end
end end
...@@ -12,6 +12,11 @@ module Emails ...@@ -12,6 +12,11 @@ module Emails
@member_id = member_id @member_id = member_id
admins = member_source.members.owners_and_masters.includes(:user).pluck(:notification_email) admins = member_source.members.owners_and_masters.includes(:user).pluck(:notification_email)
# A project in a group can have no explicit owners/masters, in that case
# we fallbacks to the group's owners/masters.
if admins.empty? && member_source.respond_to?(:group) && member_source.group
admins = member_source.group.members.owners_and_masters.includes(:user).pluck(:notification_email)
end
mail(to: admins, mail(to: admins,
subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}")) subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}"))
......
...@@ -196,7 +196,8 @@ class Ability ...@@ -196,7 +196,8 @@ class Ability
@public_project_rules ||= project_guest_rules + [ @public_project_rules ||= project_guest_rules + [
:download_code, :download_code,
:fork_project, :fork_project,
:read_commit_status :read_commit_status,
:read_pipeline
] ]
end end
......
...@@ -300,18 +300,12 @@ module Ci ...@@ -300,18 +300,12 @@ module Ci
project.valid_runners_token? token project.valid_runners_token? token
end end
def can_be_served?(runner)
return false unless has_tags? || runner.run_untagged?
(tag_list - runner.tag_list).empty?
end
def has_tags? def has_tags?
tag_list.any? tag_list.any?
end end
def any_runners_online? def any_runners_online?
project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) } project.any_runners? { |runner| runner.active? && runner.online? && runner.can_pick?(self) }
end end
def stuck? def stuck?
......
...@@ -37,22 +37,22 @@ module Ci ...@@ -37,22 +37,22 @@ module Ci
end end
def git_author_name def git_author_name
commit_data.author_name if commit_data commit.try(:author_name)
end end
def git_author_email def git_author_email
commit_data.author_email if commit_data commit.try(:author_email)
end end
def git_commit_message def git_commit_message
commit_data.message if commit_data commit.try(:message)
end end
def short_sha def short_sha
Ci::Pipeline.truncate_sha(sha) Ci::Pipeline.truncate_sha(sha)
end end
def commit_data def commit
@commit ||= project.commit(sha) @commit ||= project.commit(sha)
rescue rescue
nil nil
...@@ -163,13 +163,26 @@ module Ci ...@@ -163,13 +163,26 @@ module Ci
end end
def skip_ci? def skip_ci?
git_commit_message =~ /(\[ci skip\])/ if git_commit_message git_commit_message =~ /\[(ci skip|skip ci)\]/i if git_commit_message
end end
def environments def environments
builds.where.not(environment: nil).success.pluck(:environment).uniq builds.where.not(environment: nil).success.pluck(:environment).uniq
end end
# Manually set the notes for a Ci::Pipeline
# There is no ActiveRecord relation between Ci::Pipeline and notes
# as they are related to a commit sha. This method helps importing
# them using the +Gitlab::ImportExport::RelationFactory+ class.
def notes=(notes)
notes.each do |note|
note[:id] = nil
note[:commit_id] = sha
note[:noteable_id] = self['id']
note.save!
end
end
def notes def notes
Note.for_commit_id(sha) Note.for_commit_id(sha)
end end
......
...@@ -4,7 +4,7 @@ module Ci ...@@ -4,7 +4,7 @@ module Ci
LAST_CONTACT_TIME = 5.minutes.ago LAST_CONTACT_TIME = 5.minutes.ago
AVAILABLE_SCOPES = %w[specific shared active paused online] AVAILABLE_SCOPES = %w[specific shared active paused online]
FORM_EDITABLE = %i[description tag_list active run_untagged] FORM_EDITABLE = %i[description tag_list active run_untagged locked]
has_many :builds, class_name: 'Ci::Build' has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
...@@ -26,6 +26,13 @@ module Ci ...@@ -26,6 +26,13 @@ module Ci
.where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id) .where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
end end
scope :assignable_for, ->(project) do
# FIXME: That `to_sql` is needed to workaround a weird Rails bug.
# Without that, placeholders would miss one and couldn't match.
where(locked: false).
where.not("id IN (#{project.runners.select(:id).to_sql})").specific
end
validate :tag_constraints validate :tag_constraints
acts_as_taggable acts_as_taggable
...@@ -56,7 +63,7 @@ module Ci ...@@ -56,7 +63,7 @@ module Ci
def assign_to(project, current_user = nil) def assign_to(project, current_user = nil)
self.is_shared = false if shared? self.is_shared = false if shared?
self.save self.save
project.runner_projects.create!(runner_id: self.id) project.runner_projects.create(runner_id: self.id)
end end
def display_name def display_name
...@@ -91,6 +98,10 @@ module Ci ...@@ -91,6 +98,10 @@ module Ci
!shared? !shared?
end end
def can_pick?(build)
assignable_for?(build.project) && accepting_tags?(build)
end
def only_for?(project) def only_for?(project)
projects == [project] projects == [project]
end end
...@@ -111,5 +122,13 @@ module Ci ...@@ -111,5 +122,13 @@ module Ci
'can not be empty when runner is not allowed to pick untagged jobs') 'can not be empty when runner is not allowed to pick untagged jobs')
end end
end end
def assignable_for?(project)
!locked? || projects.exists?(id: project.id)
end
def accepting_tags?(build)
(run_untagged? || build.has_tags?) && (build.tag_list - tag_list).empty?
end
end end
end end
...@@ -13,6 +13,7 @@ module Ci ...@@ -13,6 +13,7 @@ module Ci
attr_encrypted :value, attr_encrypted :value,
mode: :per_attribute_iv_and_salt, mode: :per_attribute_iv_and_salt,
insecure_mode: true,
key: Gitlab::Application.secrets.db_key_base, key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
end end
......
...@@ -271,6 +271,32 @@ class Commit ...@@ -271,6 +271,32 @@ class Commit
merged_merge_request ? 'merge request' : 'commit' merged_merge_request ? 'merge request' : 'commit'
end end
# Get the URI type of the given path
#
# Used to build URLs to files in the repository in GFM.
#
# path - String path to check
#
# Examples:
#
# uri_type('doc/README.md') # => :blob
# uri_type('doc/logo.png') # => :raw
# uri_type('doc/api') # => :tree
# uri_type('not/found') # => :nil
#
# Returns a symbol
def uri_type(path)
entry = @raw.tree.path(path)
if entry[:type] == :blob
blob = Gitlab::Git::Blob.new(name: entry[:name])
blob.image? ? :raw : :blob
else
entry[:type]
end
rescue Rugged::TreeError
nil
end
private private
def repo_changes def repo_changes
......
...@@ -8,6 +8,8 @@ class CommitStatus < ActiveRecord::Base ...@@ -8,6 +8,8 @@ class CommitStatus < ActiveRecord::Base
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id, touch: true belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id, touch: true
belongs_to :user belongs_to :user
delegate :commit, to: :pipeline
validates :pipeline, presence: true, unless: :importing? validates :pipeline, presence: true, unless: :importing?
validates_presence_of :name validates_presence_of :name
......
...@@ -2,10 +2,11 @@ module Awardable ...@@ -2,10 +2,11 @@ module Awardable
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
has_many :award_emoji, as: :awardable, dependent: :destroy has_many :award_emoji, -> { includes(:user) }, as: :awardable, dependent: :destroy
if self < Participable if self < Participable
participant :award_emoji_with_associations # By default we always load award_emoji user association
participant :award_emoji
end end
end end
...@@ -34,12 +35,9 @@ module Awardable ...@@ -34,12 +35,9 @@ module Awardable
end end
end end
def award_emoji_with_associations
award_emoji.includes(:user)
end
def grouped_awards(with_thumbs: true) def grouped_awards(with_thumbs: true)
awards = award_emoji_with_associations.group_by(&:name) # By default we always load award_emoji user association
awards = award_emoji.group_by(&:name)
if with_thumbs if with_thumbs
awards[AwardEmoji::UPVOTE_NAME] ||= [] awards[AwardEmoji::UPVOTE_NAME] ||= []
......
...@@ -19,9 +19,14 @@ module Issuable ...@@ -19,9 +19,14 @@ module Issuable
belongs_to :milestone belongs_to :milestone
has_many :notes, as: :noteable, dependent: :destroy do has_many :notes, as: :noteable, dependent: :destroy do
def authors_loaded? def authors_loaded?
# We check first if we're loaded to not load unnecesarily. # We check first if we're loaded to not load unnecessarily.
loaded? && to_a.all? { |note| note.association(:author).loaded? } loaded? && to_a.all? { |note| note.association(:author).loaded? }
end end
def award_emojis_loaded?
# We check first if we're loaded to not load unnecessarily.
loaded? && to_a.all? { |note| note.association(:award_emoji).loaded? }
end
end end
has_many :label_links, as: :target, dependent: :destroy has_many :label_links, as: :target, dependent: :destroy
has_many :labels, through: :label_links has_many :labels, through: :label_links
...@@ -49,7 +54,7 @@ module Issuable ...@@ -49,7 +54,7 @@ module Issuable
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) } scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) } scope :join_project, -> { joins(:project) }
scope :inc_notes_with_associations, -> { includes(notes: :author) } scope :inc_notes_with_associations, -> { includes(notes: [ :project, :author, :award_emoji ]) }
scope :references_project, -> { references(:project) } scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.where(projects: { archived: false }) } scope :non_archived, -> { join_project.where(projects: { archived: false }) }
...@@ -112,15 +117,18 @@ module Issuable ...@@ -112,15 +117,18 @@ module Issuable
end end
def sort(method, excluded_labels: []) def sort(method, excluded_labels: [])
case method.to_s sorted = case method.to_s
when 'milestone_due_asc' then order_milestone_due_asc when 'milestone_due_asc' then order_milestone_due_asc
when 'milestone_due_desc' then order_milestone_due_desc when 'milestone_due_desc' then order_milestone_due_desc
when 'downvotes_desc' then order_downvotes_desc when 'downvotes_desc' then order_downvotes_desc
when 'upvotes_desc' then order_upvotes_desc when 'upvotes_desc' then order_upvotes_desc
when 'priority' then order_labels_priority(excluded_labels: excluded_labels) when 'priority' then order_labels_priority(excluded_labels: excluded_labels)
else else
order_by(method) order_by(method)
end end
# Break ties with the ID column for pagination
sorted.order(id: :desc)
end end
def order_labels_priority(excluded_labels: []) def order_labels_priority(excluded_labels: [])
...@@ -257,7 +265,14 @@ module Issuable ...@@ -257,7 +265,14 @@ module Issuable
# already have their authors loaded (possibly because the scope # already have their authors loaded (possibly because the scope
# `inc_notes_with_associations` was used) and skip the inclusion if that's # `inc_notes_with_associations` was used) and skip the inclusion if that's
# the case. # the case.
notes.authors_loaded? ? notes : notes.includes(:author) includes = []
includes << :author unless notes.authors_loaded?
includes << :award_emoji unless notes.award_emojis_loaded?
if includes.any?
notes.includes(includes)
else
notes
end
end end
def updated_tasks def updated_tasks
......
...@@ -53,6 +53,16 @@ module Participable ...@@ -53,6 +53,16 @@ module Participable
# #
# Returns an Array of User instances. # Returns an Array of User instances.
def participants(current_user = nil) def participants(current_user = nil)
@participants ||= Hash.new do |hash, user|
hash[user] = raw_participants(user)
end
@participants[current_user]
end
private
def raw_participants(current_user = nil)
current_user ||= author current_user ||= author
ext = Gitlab::ReferenceExtractor.new(project, current_user) ext = Gitlab::ReferenceExtractor.new(project, current_user)
participants = Set.new participants = Set.new
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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