Commit d0c240b6 authored by Linus G Thiel's avatar Linus G Thiel

Merge branch 'master' into 22211-500-instead-of-404

parents f7f42914 73adae0f
...@@ -19,6 +19,8 @@ variables: ...@@ -19,6 +19,8 @@ variables:
before_script: before_script:
- source ./scripts/prepare_build.sh - source ./scripts/prepare_build.sh
- cp config/gitlab.yml.example config/gitlab.yml - cp config/gitlab.yml.example config/gitlab.yml
- mkdir -p tmp/tests
- mount -t tmpfs tmpfs tmp/tests || echo "tmpfs mount failed, falling back to disc"
- bundle --version - bundle --version
- '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"' - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"'
- retry gem install knapsack - retry gem install knapsack
...@@ -207,9 +209,7 @@ rubocop: *exec ...@@ -207,9 +209,7 @@ rubocop: *exec
rake haml_lint: *exec rake haml_lint: *exec
rake scss_lint: *exec rake scss_lint: *exec
rake brakeman: *exec rake brakeman: *exec
rake flay: rake flay: *exec
<<: *exec
allow_failure: yes
license_finder: *exec license_finder: *exec
rake downtime_check: *exec rake downtime_check: *exec
......
...@@ -2,41 +2,63 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -2,41 +2,63 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.13.0 (unreleased) v 8.13.0 (unreleased)
- Respond with 404 Not Found for non-existent tags (Linus Thiel) - Respond with 404 Not Found for non-existent tags (Linus Thiel)
- Truncate long labels with ellipsis in labels page
- Update runner version only when updating contacted_at - Update runner version only when updating contacted_at
- Add link from system note to compare with previous version - Add link from system note to compare with previous version
- Improve issue load time performance by avoiding ORDER BY in find_by call
- Use gitlab-shell v3.6.2 (GIT TRACE logging) - Use gitlab-shell v3.6.2 (GIT TRACE logging)
- Add `/projects/visible` API endpoint (Ben Boeckel)
- Fix centering of custom header logos (Ashley Dumaine) - Fix centering of custom header logos (Ashley Dumaine)
- ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup
- AbstractReferenceFilter caches project_refs on RequestStore when active - AbstractReferenceFilter caches project_refs on RequestStore when active
- Replaced the check sign to arrow in the show build view. !6501 - Replaced the check sign to arrow in the show build view. !6501
- Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar)
- Speed-up group milestones show page - Speed-up group milestones show page
- Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps)
- Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs)
- Add tag shortcut from the Commit page. !6543
- Keep refs for each deployment - Keep refs for each deployment
- Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller)
- Add more tests for calendar contribution (ClemMakesApps) - Add more tests for calendar contribution (ClemMakesApps)
- Update Gitlab Shell to fix some problems with moving projects between storages
- Cache rendered markdown in the database, rather than Redis
- Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references
- Simplify Mentionable concern instance methods - Simplify Mentionable concern instance methods
- Fix permission for setting an issue's due date - Fix permission for setting an issue's due date
- API: Multi-file commit !6096 (mahcsig)
- Revert "Label list shows all issues (opened or closed) with that label"
- Expose expires_at field when sharing project on API - Expose expires_at field when sharing project on API
- Fix VueJS template tags being rendered in code comments - Fix VueJS template tags being rendered in code comments
- Added copy file path button to merge request diff files
- Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell)
- Add Issue Board API support (andrebsguedes)
- Allow the Koding integration to be configured through the API - Allow the Koding integration to be configured through the API
- Add new issue button to each list on Issues Board
- Added soft wrap button to repository file/blob editor - Added soft wrap button to repository file/blob editor
- Update namespace validation to forbid reserved names (.git and .atom) (Will Starms)
- Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps)
- Fix todos page mobile viewport layout (ClemMakesApps) - Fix todos page mobile viewport layout (ClemMakesApps)
- Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps)
- Remove redundant mixins (ClemMakesApps)
- Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison)
- Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska)
- Fix that manual jobs would no longer block jobs in the next stage. !6604 - Fix that manual jobs would no longer block jobs in the next stage. !6604
- Add configurable email subject suffix (Fu Xu) - Add configurable email subject suffix (Fu Xu)
- Added tooltip to fork count on project show page. (Justin DiPierro)
- Use a ConnectionPool for Rails.cache on Sidekiq servers - Use a ConnectionPool for Rails.cache on Sidekiq servers
- Replace `alias_method_chain` with `Module#prepend` - Replace `alias_method_chain` with `Module#prepend`
- Enable GitLab Import/Export for non-admin users. - Enable GitLab Import/Export for non-admin users.
- Preserve label filters when sorting !6136 (Joseph Frazier) - Preserve label filters when sorting !6136 (Joseph Frazier)
- MergeRequest#new form load diff asynchronously
- Only update issuable labels if they have been changed - Only update issuable labels if they have been changed
- Take filters in account in issuable counters. !6496 - Take filters in account in issuable counters. !6496
- Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*)
- Prevent flash alert text from being obscured when container is fluid
- Append issue template to existing description !6149 (Joseph Frazier) - Append issue template to existing description !6149 (Joseph Frazier)
- Trending projects now only show public projects and the list of projects is cached for a day - Trending projects now only show public projects and the list of projects is cached for a day
- Memoize Gitlab Shell's secret token (!6599, Justin DiPierro)
- Revoke button in Applications Settings underlines on hover. - Revoke button in Applications Settings underlines on hover.
- Use higher size on Gitlab::Redis connection pool on Sidekiq servers
- Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska)
- Fix Long commit messages overflow viewport in file tree - Fix Long commit messages overflow viewport in file tree
- Revert avoid touching file system on Build#artifacts? - Revert avoid touching file system on Build#artifacts?
...@@ -44,28 +66,46 @@ v 8.13.0 (unreleased) ...@@ -44,28 +66,46 @@ v 8.13.0 (unreleased)
- Add broadcast messages and alerts below sub-nav - Add broadcast messages and alerts below sub-nav
- Better empty state for Groups view - Better empty state for Groups view
- Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe)
- Replace bootstrap caret with fontawesome caret (ClemMakesApps)
- Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533
- Add organization field to user profile - Add organization field to user profile
- Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts)
- Fix deploy status responsiveness error !6633
- Make searching for commits case insensitive
- Fix resolved discussion display in side-by-side diff view !6575 - Fix resolved discussion display in side-by-side diff view !6575
- Optimize GitHub importing for speed and memory - Optimize GitHub importing for speed and memory
- API: expose pipeline data in builds API (!6502, Guilherme Salazar) - API: expose pipeline data in builds API (!6502, Guilherme Salazar)
- Notify the Merger about merge after successful build (Dimitris Karakasilis) - Notify the Merger about merge after successful build (Dimitris Karakasilis)
- Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein)
- Reduce queries needed to find users using their SSH keys when pushing commits - Reduce queries needed to find users using their SSH keys when pushing commits
- Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska)
- Fix broken repository 500 errors in project list - Fix broken repository 500 errors in project list
- Fix Pipeline list commit column width should be adjusted - Fix Pipeline list commit column width should be adjusted
- Close todos when accepting merge requests via the API !6486 (tonygambone) - Close todos when accepting merge requests via the API !6486 (tonygambone)
- Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer)
- Retouch environments list and deployments list
- Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Add Container Registry on/off status to Admin Area !6638 (the-undefined)
- Grouped pipeline dropdown is a scrollable container - Grouped pipeline dropdown is a scrollable container
- Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi)
v 8.12.4 (unreleased) - Fix a typo in doc/api/labels.md
- Fix type mismatch bug when closing Jira issue - API: all unknown routing will be handled with 404 Not Found
- Skip wiki creation when GitHub project has wiki enabled
- Fix failed project deletion when feature visibility set to private v 8.12.5 (unreleased)
- Fix issues importing services via Import/Export
- Restrict failed login attempts for users with 2FA enabled v 8.12.4
- Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. (lukehowell) - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell)
- Fix padding in build sidebar. !6506
- Changed compare dropdowns to dropdowns with isolated search input. !6550
- Fix race condition on LFS Token. !6592
- Fix type mismatch bug when closing Jira issue. !6619
- Fix lint-doc error. !6623
- Skip wiki creation when GitHub project has wiki enabled. !6665
- Fix issues importing services via Import/Export. !6667
- Restrict failed login attempts for users with 2FA enabled. !6668
- Fix failed project deletion when feature visibility set to private. !6688
- Prevent claiming associated model IDs via import.
- Set GitLab project exported file permissions to owner only
- Change user & group landing page routing from /u/:username to /:username
v 8.12.3 v 8.12.3
- Update Gitlab Shell to support low IO priority for storage moves - Update Gitlab Shell to support low IO priority for storage moves
...@@ -85,6 +125,7 @@ v 8.12.2 ...@@ -85,6 +125,7 @@ v 8.12.2
- Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv)
- Fix resolve discussion buttons endpoint path - Fix resolve discussion buttons endpoint path
- Refactor remnants of CoffeeScript destructured opts and super !6261 - Refactor remnants of CoffeeScript destructured opts and super !6261
- Prevent running GfmAutocomplete setup for each diff note !6569
v 8.12.1 v 8.12.1
- Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST
...@@ -288,6 +329,7 @@ v 8.11.7 ...@@ -288,6 +329,7 @@ v 8.11.7
- Avoid conflict with admin labels when importing GitHub labels. !6158 - Avoid conflict with admin labels when importing GitHub labels. !6158
- Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234 - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234
- Allow the Rails cookie to be used for API authentication. - Allow the Rails cookie to be used for API authentication.
- Updating verbiage on git basics to be more intuitive
v 8.11.6 v 8.11.6
- Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005
...@@ -448,6 +490,7 @@ v 8.11.0 ...@@ -448,6 +490,7 @@ v 8.11.0
- Add pipeline events hook - Add pipeline events hook
- Bump gitlab_git to speedup DiffCollection iterations - Bump gitlab_git to speedup DiffCollection iterations
- Rewrite description of a blocked user in admin settings. (Elias Werberich) - Rewrite description of a blocked user in admin settings. (Elias Werberich)
- Clarify documentation for Runners API (Gennady Trafimenkov)
- Make branches sortable without push permission !5462 (winniehell) - Make branches sortable without push permission !5462 (winniehell)
- Check for Ci::Build artifacts at database level on pipeline partial - Check for Ci::Build artifacts at database level on pipeline partial
- Convert image diff background image to CSS (ClemMakesApps) - Convert image diff background image to CSS (ClemMakesApps)
......
...@@ -226,8 +226,7 @@ a feedback issue (if there isn't one already) and leave a comment asking for it ...@@ -226,8 +226,7 @@ a feedback issue (if there isn't one already) and leave a comment asking for it
to be marked as `Accepting merge requests`. Please include screenshots or to be marked as `Accepting merge requests`. Please include screenshots or
wireframes if the feature will also change the UI. wireframes if the feature will also change the UI.
Merge requests can be filed either at [GitLab.com][gitlab-mr-tracker] or at Merge requests should be opened at [GitLab.com][gitlab-mr-tracker].
[github.com][github-mr-tracker].
If you are new to GitLab development (or web development in general), see the If you are new to GitLab development (or web development in general), see the
[I want to contribute!](#i-want-to-contribute) section to get you started with [I want to contribute!](#i-want-to-contribute) section to get you started with
...@@ -246,10 +245,17 @@ tests are least likely to receive timely feedback. The workflow to make a merge ...@@ -246,10 +245,17 @@ tests are least likely to receive timely feedback. The workflow to make a merge
request is as follows: request is as follows:
1. Fork the project into your personal space on GitLab.com 1. Fork the project into your personal space on GitLab.com
1. Create a feature branch, branch away from `master`. 1. Create a feature branch, branch away from `master`
1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code 1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG) 1. Add your changes to the [CHANGELOG](CHANGELOG):
1. If you are writing documentation, make sure to read the [documentation styleguide][doc-styleguide] 1. If you are fixing a ~regression issue, you can add your entry to the next
patch release (e.g. `8.12.5` if current version is `8.12.4`)
1. Otherwise, add your entry to the next minor release (e.g. `8.13.0` if
current version is `8.12.4`
1. Please add your entry at a random place among the entries of the targeted
release
1. If you are writing documentation, make sure to follow the
[documentation styleguide][doc-styleguide]
1. If you have multiple commits please combine them into one commit by 1. If you have multiple commits please combine them into one commit by
[squashing them][git-squash] [squashing them][git-squash]
1. Push the commit(s) to your fork 1. Push the commit(s) to your fork
...@@ -258,7 +264,7 @@ request is as follows: ...@@ -258,7 +264,7 @@ request is as follows:
1. The MR description should give a motive for your change and the method you 1. The MR description should give a motive for your change and the method you
used to achieve it, see the [merge request description format] used to achieve it, see the [merge request description format]
(#merge-request-description-format) (#merge-request-description-format)
1. If the MR changes the UI it should include before and after screenshots 1. If the MR changes the UI it should include *Before* and *After* screenshots
1. If the MR changes CSS classes please include the list of affected pages, 1. If the MR changes CSS classes please include the list of affected pages,
`grep css-class ./app -R` `grep css-class ./app -R`
1. Link any relevant [issues][ce-tracker] in the merge request description and 1. Link any relevant [issues][ce-tracker] in the merge request description and
...@@ -270,7 +276,9 @@ request is as follows: ...@@ -270,7 +276,9 @@ request is as follows:
[shell command guidelines](doc/development/shell_commands.md) [shell command guidelines](doc/development/shell_commands.md)
1. If your code creates new files on disk please read the 1. If your code creates new files on disk please read the
[shared files guidelines](doc/development/shared_files.md). [shared files guidelines](doc/development/shared_files.md).
1. When writing commit messages please follow [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) [guidelines](http://chris.beams.io/posts/git-commit/). 1. When writing commit messages please follow
[these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
[guidelines](http://chris.beams.io/posts/git-commit/).
1. If your merge request adds one or more migrations, make sure to execute all 1. If your merge request adds one or more migrations, make sure to execute all
migrations on a fresh database before the MR is reviewed. If the review leads migrations on a fresh database before the MR is reviewed. If the review leads
to large changes in the MR, do this again once the review is complete. to large changes in the MR, do this again once the review is complete.
...@@ -305,23 +313,6 @@ Please ensure that your merge request meets the contribution acceptance criteria ...@@ -305,23 +313,6 @@ Please ensure that your merge request meets the contribution acceptance criteria
When having your code reviewed and when reviewing merge requests please take the When having your code reviewed and when reviewing merge requests please take the
[code review guidelines](doc/development/code_review.md) into account. [code review guidelines](doc/development/code_review.md) into account.
### Merge request description format
Please submit merge requests using the following template in the merge request
description area. Copy-paste it to retain the markdown format.
```
## What does this MR do?
## Are there points in the code the reviewer needs to double check?
## Why was this MR needed?
## What are the relevant issue numbers?
## Screenshots (if relevant)
```
### Contribution acceptance criteria ### Contribution acceptance criteria
1. The change is as small as possible 1. The change is as small as possible
...@@ -333,8 +324,8 @@ description area. Copy-paste it to retain the markdown format. ...@@ -333,8 +324,8 @@ description area. Copy-paste it to retain the markdown format.
aforementioned failing test aforementioned failing test
1. Your MR initially contains a single commit (please use `git rebase -i` to 1. Your MR initially contains a single commit (please use `git rebase -i` to
squash commits) squash commits)
1. Your changes can merge without problems (if not please merge `master`, never 1. Your changes can merge without problems (if not please rebase if you're the
rebase commits pushed to the remote server) only one working on your feature branch, otherwise, merge `master`)
1. Does not break any existing functionality 1. Does not break any existing functionality
1. Fixes one specific issue or implements one specific feature (do not combine 1. Fixes one specific issue or implements one specific feature (do not combine
things, send separate merge requests if needed) things, send separate merge requests if needed)
...@@ -352,7 +343,10 @@ description area. Copy-paste it to retain the markdown format. ...@@ -352,7 +343,10 @@ description area. Copy-paste it to retain the markdown format.
entire line to follow it. This prevents linting tools from generating warnings. entire line to follow it. This prevents linting tools from generating warnings.
- Don't touch neighbouring lines. As an exception, automatic mass - Don't touch neighbouring lines. As an exception, automatic mass
refactoring modifications may leave style non-compliant. refactoring modifications may leave style non-compliant.
1. If the merge request adds any new libraries (gems, JavaScript libraries, etc.), they should conform to our [Licensing guidelines][license-finder-doc]. See the instructions in that document for help if your MR fails the "license-finder" test with a "Dependencies that need approval" error. 1. If the merge request adds any new libraries (gems, JavaScript libraries,
etc.), they should conform to our [Licensing guidelines][license-finder-doc].
See the instructions in that document for help if your MR fails the
"license-finder" test with a "Dependencies that need approval" error.
## Changes for Stable Releases ## Changes for Stable Releases
...@@ -468,7 +462,6 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor ...@@ -468,7 +462,6 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests [accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests
[accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Accepting+Merge+Requests [accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Accepting+Merge+Requests
[gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests [gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests
[github-mr-tracker]: https://github.com/gitlabhq/gitlabhq/pulls
[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit [gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
[git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits [git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
[closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed [closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
......
...@@ -110,6 +110,7 @@ gem 'creole', '~> 0.5.0' ...@@ -110,6 +110,7 @@ gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1' gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2' gem 'asciidoctor', '~> 1.5.2'
gem 'rouge', '~> 2.0' gem 'rouge', '~> 2.0'
gem 'truncato', '~> 0.7.8'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s # See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM # and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
...@@ -231,7 +232,7 @@ gem 'net-ssh', '~> 3.0.1' ...@@ -231,7 +232,7 @@ gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0' gem 'base32', '~> 0.3.0'
# Sentry integration # Sentry integration
gem 'sentry-raven', '~> 1.1.0' gem 'sentry-raven', '~> 2.0.0'
gem 'premailer-rails', '~> 1.9.0' gem 'premailer-rails', '~> 1.9.0'
......
...@@ -665,8 +665,8 @@ GEM ...@@ -665,8 +665,8 @@ GEM
activesupport (>= 3.1) activesupport (>= 3.1)
select2-rails (3.5.9.3) select2-rails (3.5.9.3)
thor (~> 0.14) thor (~> 0.14)
sentry-raven (1.1.0) sentry-raven (2.0.2)
faraday (>= 0.7.6) faraday (>= 0.7.6, < 0.10.x)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.7.0) sexp_processor (4.7.0)
sham_rack (1.3.6) sham_rack (1.3.6)
...@@ -745,6 +745,9 @@ GEM ...@@ -745,6 +745,9 @@ GEM
tilt (2.0.5) tilt (2.0.5)
timecop (0.8.1) timecop (0.8.1)
timfel-krb5-auth (0.8.3) timfel-krb5-auth (0.8.3)
truncato (0.7.8)
htmlentities (~> 4.3.1)
nokogiri (~> 1.6.1)
turbolinks (2.5.3) turbolinks (2.5.3)
coffee-rails coffee-rails
tzinfo (1.2.2) tzinfo (1.2.2)
...@@ -948,7 +951,7 @@ DEPENDENCIES ...@@ -948,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 (~> 1.1.0) sentry-raven (~> 2.0.0)
settingslogic (~> 2.0.9) settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6) sham_rack (~> 1.3.6)
shoulda-matchers (~> 2.8.0) shoulda-matchers (~> 2.8.0)
...@@ -971,6 +974,7 @@ DEPENDENCIES ...@@ -971,6 +974,7 @@ DEPENDENCIES
test_after_commit (~> 0.4.2) test_after_commit (~> 0.4.2)
thin (~> 1.7.0) thin (~> 1.7.0)
timecop (~> 0.8.0) timecop (~> 0.8.0)
truncato (~> 0.7.8)
turbolinks (~> 2.5.0) turbolinks (~> 2.5.0)
u2f (~> 0.2.1) u2f (~> 0.2.1)
uglifier (~> 2.7.2) uglifier (~> 2.7.2)
......
# GitLab # GitLab
[![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](http://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
......
...@@ -21,16 +21,14 @@ ...@@ -21,16 +21,14 @@
}; };
Activities.prototype.toggleFilter = function(sender) { Activities.prototype.toggleFilter = function(sender) {
var event_filters, filter; var filter = sender.attr("id").split("_")[0];
$('.event-filter .active').removeClass("active"); $('.event-filter .active').removeClass("active");
event_filters = $.cookie("event_filter"); $.cookie("event_filter", filter, {
filter = sender.attr("id").split("_")[0];
$.cookie("event_filter", (event_filters !== filter ? filter : ""), {
path: gon.relative_url_root || '/' path: gon.relative_url_root || '/'
}); });
if (event_filters !== filter) {
return sender.closest('li').toggleClass("active"); sender.closest('li').toggleClass("active");
}
}; };
return Activities; return Activities;
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
namespacesPath: "/api/:version/namespaces.json", namespacesPath: "/api/:version/namespaces.json",
groupProjectsPath: "/api/:version/groups/:id/projects.json", groupProjectsPath: "/api/:version/groups/:id/projects.json",
projectsPath: "/api/:version/projects.json?simple=true", projectsPath: "/api/:version/projects.json?simple=true",
labelsPath: "/api/:version/projects/:id/labels", labelsPath: "/:namespace_path/:project_path/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", gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key",
...@@ -23,12 +23,13 @@ ...@@ -23,12 +23,13 @@
}, },
// Return groups list. Filtered by query // Return groups list. Filtered by query
// Only active groups retrieved // Only active groups retrieved
groups: function(query, skip_ldap, callback) { groups: function(query, skip_ldap, skip_groups, callback) {
var url = Api.buildUrl(Api.groupsPath); var url = Api.buildUrl(Api.groupsPath);
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: {
search: query, search: query,
skip_groups: skip_groups,
per_page: 20 per_page: 20
}, },
dataType: "json" dataType: "json"
...@@ -65,13 +66,14 @@ ...@@ -65,13 +66,14 @@
return callback(projects); return callback(projects);
}); });
}, },
newLabel: function(project_id, data, callback) { newLabel: function(namespace_path, project_path, data, callback) {
var url = Api.buildUrl(Api.labelsPath) var url = Api.buildUrl(Api.labelsPath)
.replace(':id', project_id); .replace(':namespace_path', namespace_path)
.replace(':project_path', project_path);
return $.ajax({ return $.ajax({
url: url, url: url,
type: "POST", type: "POST",
data: data, data: {'label': data},
dataType: "json" dataType: "json"
}).done(function(label) { }).done(function(label) {
return callback(label); return callback(label);
......
...@@ -21,7 +21,8 @@ ...@@ -21,7 +21,8 @@
}, },
data () { data () {
return { return {
filters: Store.state.filters filters: Store.state.filters,
showIssueForm: false
}; };
}, },
watch: { watch: {
...@@ -33,6 +34,11 @@ ...@@ -33,6 +34,11 @@
deep: true deep: true
} }
}, },
methods: {
showNewIssueForm() {
this.showIssueForm = !this.showIssueForm;
}
},
ready () { ready () {
const options = gl.issueBoards.getBoardSortableDefaultOptions({ const options = gl.issueBoards.getBoardSortableDefaultOptions({
disabled: this.disabled, disabled: this.disabled,
......
...@@ -8,10 +8,8 @@ ...@@ -8,10 +8,8 @@
data () { data () {
return { return {
predefinedLabels: [ predefinedLabels: [
new ListLabel({ title: 'Development', color: '#5CB85C' }), new ListLabel({ title: 'To Do', color: '#F0AD4E' }),
new ListLabel({ title: 'Testing', color: '#F0AD4E' }), new ListLabel({ title: 'Doing', color: '#5CB85C' })
new ListLabel({ title: 'Production', color: '#FF5F00' }),
new ListLabel({ title: 'Ready', color: '#FF0000' })
] ]
} }
}, },
......
//= require ./board_card //= require ./board_card
//= require ./board_new_issue
(() => { (() => {
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -8,14 +9,16 @@ ...@@ -8,14 +9,16 @@
gl.issueBoards.BoardList = Vue.extend({ gl.issueBoards.BoardList = Vue.extend({
components: { components: {
'board-card': gl.issueBoards.BoardCard 'board-card': gl.issueBoards.BoardCard,
'board-new-issue': gl.issueBoards.BoardNewIssue
}, },
props: { props: {
disabled: Boolean, disabled: Boolean,
list: Object, list: Object,
issues: Array, issues: Array,
loading: Boolean, loading: Boolean,
issueLinkBase: String issueLinkBase: String,
showIssueForm: Boolean
}, },
data () { data () {
return { return {
...@@ -73,7 +76,7 @@ ...@@ -73,7 +76,7 @@
group: 'issues', group: 'issues',
sort: false, sort: false,
disabled: this.disabled, disabled: this.disabled,
filter: '.board-list-count', filter: '.board-list-count, .is-disabled',
onStart: (e) => { onStart: (e) => {
const card = this.$refs.issue[e.oldIndex]; const card = this.$refs.issue[e.oldIndex];
......
(() => {
window.gl = window.gl || {};
gl.issueBoards.BoardNewIssue = Vue.extend({
props: {
list: Object,
showIssueForm: Boolean
},
data() {
return {
title: '',
error: false
};
},
watch: {
showIssueForm () {
this.$els.input.focus();
}
},
methods: {
submit(e) {
e.preventDefault();
if (this.title.trim() === '') return;
this.error = false;
const labels = this.list.label ? [this.list.label] : [];
const issue = new ListIssue({
title: this.title,
labels
});
this.list.newIssue(issue)
.then((data) => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$els.submitButton).enable();
})
.catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$els.submitButton).enable();
// Remove the issue
this.list.removeIssue(issue);
// Show error message
this.error = true;
this.showIssueForm = true;
});
this.cancel();
},
cancel() {
this.showIssueForm = false;
this.title = '';
}
}
});
})();
...@@ -3,8 +3,7 @@ $(() => { ...@@ -3,8 +3,7 @@ $(() => {
$('.js-new-board-list').each(function () { $('.js-new-board-list').each(function () {
const $this = $(this); const $this = $(this);
new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('project-id'));
$this.glDropdown({ $this.glDropdown({
data(term, callback) { data(term, callback) {
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
fallbackClass: 'is-dragging', fallbackClass: 'is-dragging',
fallbackOnBody: true, fallbackOnBody: true,
ghostClass: 'is-ghost', ghostClass: 'is-ghost',
filter: '.has-tooltip', filter: '.has-tooltip, .btn',
delay: gl.issueBoards.touchEnabled ? 100 : 0, delay: gl.issueBoards.touchEnabled ? 100 : 0,
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100, scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
scrollSpeed: 20, scrollSpeed: 20,
......
...@@ -87,6 +87,17 @@ class List { ...@@ -87,6 +87,17 @@ class List {
}); });
} }
newIssue (issue) {
this.addIssue(issue);
this.issuesSize++;
return gl.boardService.newIssue(this.id, issue)
.then((resp) => {
const data = resp.json();
issue.id = data.iid;
});
}
createIssues (data) { createIssues (data) {
data.forEach((issueObj) => { data.forEach((issueObj) => {
this.addIssue(new ListIssue(issueObj)); this.addIssue(new ListIssue(issueObj));
......
...@@ -58,4 +58,10 @@ class BoardService { ...@@ -58,4 +58,10 @@ class BoardService {
to_list_id to_list_id
}); });
} }
newIssue (id, issue) {
return this.issues.save({ id }, {
issue
});
}
}; };
...@@ -146,7 +146,7 @@ ...@@ -146,7 +146,7 @@
$date = $('.js-artifacts-remove'); $date = $('.js-artifacts-remove');
if ($date.length) { if ($date.length) {
date = $date.text(); date = $date.text();
return $date.text($.timefor(new Date(date.replace(/-/g, '/')), ' ')); return $date.text($.timefor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '));
} }
}; };
......
(function (w) { (function (w) {
class CreateLabelDropdown { class CreateLabelDropdown {
constructor ($el, projectId) { constructor ($el, namespacePath, projectPath) {
this.$el = $el; this.$el = $el;
this.projectId = projectId; this.namespacePath = namespacePath;
this.projectPath = projectPath;
this.$dropdownBack = $('.dropdown-menu-back', this.$el.closest('.dropdown')); this.$dropdownBack = $('.dropdown-menu-back', this.$el.closest('.dropdown'));
this.$cancelButton = $('.js-cancel-label-btn', this.$el); this.$cancelButton = $('.js-cancel-label-btn', this.$el);
this.$newLabelField = $('#new_label_name', this.$el); this.$newLabelField = $('#new_label_name', this.$el);
...@@ -91,8 +92,8 @@ ...@@ -91,8 +92,8 @@
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
Api.newLabel(this.projectId, { Api.newLabel(this.namespacePath, this.projectPath, {
name: this.$newLabelField.val(), title: this.$newLabelField.val(),
color: this.$newColorField.val() color: this.$newColorField.val()
}, (label) => { }, (label) => {
this.$newLabelCreateButton.enable(); this.$newLabelCreateButton.enable();
......
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new GLForm($('.issue-form')); new GLForm($('.issue-form'));
new IssuableForm($('.issue-form')); new IssuableForm($('.issue-form'));
new LabelsSelect();
new MilestoneSelect();
new gl.IssuableTemplateSelectors(); new gl.IssuableTemplateSelectors();
break; break;
case 'projects:merge_requests:new': case 'projects:merge_requests:new':
...@@ -67,6 +69,8 @@ ...@@ -67,6 +69,8 @@
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new GLForm($('.merge-request-form')); new GLForm($('.merge-request-form'));
new IssuableForm($('.merge-request-form')); new IssuableForm($('.merge-request-form'));
new LabelsSelect();
new MilestoneSelect();
new gl.IssuableTemplateSelectors(); new gl.IssuableTemplateSelectors();
break; break;
case 'projects:tags:new': case 'projects:tags:new':
......
...@@ -52,37 +52,27 @@ ...@@ -52,37 +52,27 @@
} }
} }
}, },
setup: function(input) { setup: _.debounce(function(input) {
// Add GFM auto-completion to all input fields, that accept GFM input. // Add GFM auto-completion to all input fields, that accept GFM input.
this.input = input || $('.js-gfm-input'); this.input = input || $('.js-gfm-input');
// destroy previous instances // destroy previous instances
this.destroyAtWho(); this.destroyAtWho();
// set up instances // set up instances
this.setupAtWho(); this.setupAtWho();
if (this.dataSource) {
if (!this.dataLoading && !this.cachedData) { if (this.dataSource && !this.dataLoading && !this.cachedData) {
this.dataLoading = true; this.dataLoading = true;
setTimeout((function(_this) { return this.fetchData(this.dataSource)
return function() { .done((data) => {
var fetch; this.dataLoading = false;
fetch = _this.fetchData(_this.dataSource); this.loadData(data);
return fetch.done(function(data) {
_this.dataLoading = false;
return _this.loadData(data);
}); });
}; };
// We should wait until initializations are done
// and only trigger the last .setup since
// The previous .dataSource belongs to the previous issuable
// and the last one will have the **proper** .dataSource property
// TODO: Make this a singleton and turn off events when moving to another page
})(this), 1000);
}
if (this.cachedData != null) { if (this.cachedData != null) {
return this.loadData(this.cachedData); return this.loadData(this.cachedData);
} }
} }, 1000),
},
setupAtWho: function() { setupAtWho: function() {
// Emoji // Emoji
this.input.atwho({ this.input.atwho({
......
...@@ -443,6 +443,7 @@ ...@@ -443,6 +443,7 @@
var contentHtml; var contentHtml;
this.resetRows(); this.resetRows();
this.addArrowKeyEvent(); this.addArrowKeyEvent();
if (this.options.setIndeterminateIds) { if (this.options.setIndeterminateIds) {
this.options.setIndeterminateIds.call(this); this.options.setIndeterminateIds.call(this);
} }
...@@ -460,9 +461,21 @@ ...@@ -460,9 +461,21 @@
if (this.options.filterable) { if (this.options.filterable) {
this.filterInput.focus(); this.filterInput.focus();
} }
if (this.options.showMenuAbove) {
this.positionMenuAbove();
}
return this.dropdown.trigger('shown.gl.dropdown'); return this.dropdown.trigger('shown.gl.dropdown');
}; };
GitLabDropdown.prototype.positionMenuAbove = function() {
var $button = $(this.el);
var $menu = this.dropdown.find('.dropdown-menu');
$menu.css('top', ($button.height() + $menu.height()) * -1);
};
GitLabDropdown.prototype.hidden = function(e) { GitLabDropdown.prototype.hidden = function(e) {
var $input; var $input;
this.resetRows(); this.resetRows();
...@@ -725,6 +738,7 @@ ...@@ -725,6 +738,7 @@
return false; return false;
} }
if (currentKeyCode === 13 && currentIndex !== -1) { if (currentKeyCode === 13 && currentIndex !== -1) {
e.preventDefault();
_this.selectRowAtIndex(); _this.selectRowAtIndex();
} }
}; };
......
...@@ -5,14 +5,15 @@ ...@@ -5,14 +5,15 @@
function GroupsSelect() { function GroupsSelect() {
$('.ajax-groups-select').each((function(_this) { $('.ajax-groups-select').each((function(_this) {
return function(i, select) { return function(i, select) {
var skip_ldap; var skip_ldap, skip_groups;
skip_ldap = $(select).hasClass('skip_ldap'); skip_ldap = $(select).hasClass('skip_ldap');
skip_groups = $(select).data('skip-groups') || [];
return $(select).select2({ return $(select).select2({
placeholder: "Search for a group", placeholder: "Search for a group",
multiple: $(select).hasClass('multiselect'), multiple: $(select).hasClass('multiselect'),
minimumInputLength: 0, minimumInputLength: 0,
query: function(query) { query: function(query) {
return Api.groups(query.term, skip_ldap, function(groups) { return Api.groups(query.term, skip_ldap, skip_groups, function(groups) {
var data; var data;
data = { data = {
results: groups results: groups
......
...@@ -51,7 +51,6 @@ ...@@ -51,7 +51,6 @@
}).remove(); }).remove();
// Submit the form to get new data // Submit the form to get new data
Issuable.filterResults($('.filter-form')); Issuable.filterResults($('.filter-form'));
return $('.js-label-select').trigger('update.label');
}); });
}, },
filterResults: (function(_this) { filterResults: (function(_this) {
......
This diff is collapsed.
...@@ -38,6 +38,11 @@ ...@@ -38,6 +38,11 @@
gl.utils.getPagePath = function() { gl.utils.getPagePath = function() {
return $('body').data('page').split(':')[0]; return $('body').data('page').split(':')[0];
}; };
gl.utils.parseUrl = function (url) {
var parser = document.createElement('a');
parser.href = url;
return parser;
};
return jQuery.timefor = function(time, suffix, expiredLabel) { return jQuery.timefor = function(time, suffix, expiredLabel) {
var suffixFromNow, timefor; var suffixFromNow, timefor;
if (!time) { if (!time) {
......
...@@ -61,6 +61,9 @@ ...@@ -61,6 +61,9 @@
function MergeRequestTabs(opts) { function MergeRequestTabs(opts) {
this.opts = opts != null ? opts : {}; this.opts = opts != null ? opts : {};
this.opts.setUrl = this.opts.setUrl !== undefined ? this.opts.setUrl : true; this.opts.setUrl = this.opts.setUrl !== undefined ? this.opts.setUrl : true;
this.buildsLoaded = this.opts.buildsLoaded || false;
this.setCurrentAction = bind(this.setCurrentAction, this); this.setCurrentAction = bind(this.setCurrentAction, this);
this.tabShown = bind(this.tabShown, this); this.tabShown = bind(this.tabShown, this);
this.showTab = bind(this.showTab, this); this.showTab = bind(this.showTab, this);
...@@ -93,7 +96,7 @@ ...@@ -93,7 +96,7 @@
this.loadCommits($target.attr('href')); this.loadCommits($target.attr('href'));
this.expandView(); this.expandView();
this.resetViewContainer(); this.resetViewContainer();
} else if (action === 'diffs') { } else if (this.isDiffAction(action)) {
this.loadDiff($target.attr('href')); this.loadDiff($target.attr('href'));
if ((typeof bp !== "undefined" && bp !== null) && bp.getBreakpointSize() !== 'lg') { if ((typeof bp !== "undefined" && bp !== null) && bp.getBreakpointSize() !== 'lg') {
this.shrinkView(); this.shrinkView();
...@@ -170,8 +173,9 @@ ...@@ -170,8 +173,9 @@
action = 'notes'; action = 'notes';
} }
this.currentAction = action; this.currentAction = action;
// Remove a trailing '/commits' or '/diffs' // Remove a trailing '/commits' '/diffs' '/builds' '/pipelines' '/new' '/new/diffs'
new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines)(\.html)?\/?$/, ''); new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines|new|new\/diffs)(\.html)?\/?$/, '');
// Append the new action if we're on a tab other than 'notes' // Append the new action if we're on a tab other than 'notes'
if (action !== 'notes') { if (action !== 'notes') {
new_state += "/" + action; new_state += "/" + action;
...@@ -210,8 +214,13 @@ ...@@ -210,8 +214,13 @@
if (this.diffsLoaded) { if (this.diffsLoaded) {
return; return;
} }
// We extract pathname for the current Changes tab anchor href
// some pages like MergeRequestsController#new has query parameters on that anchor
var url = gl.utils.parseUrl(source);
return this._get({ return this._get({
url: (source + ".json") + this._location.search, url: (url.pathname + ".json") + this._location.search,
success: (function(_this) { success: (function(_this) {
return function(data) { return function(data) {
$('#diffs').html(data.html); $('#diffs').html(data.html);
...@@ -223,7 +232,7 @@ ...@@ -223,7 +232,7 @@
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')); gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'));
$('#diffs .js-syntax-highlight').syntaxHighlight(); $('#diffs .js-syntax-highlight').syntaxHighlight();
$('#diffs .diff-file').singleFileDiff(); $('#diffs .diff-file').singleFileDiff();
if (_this.diffViewType() === 'parallel' && _this.currentAction === 'diffs') { if (_this.diffViewType() === 'parallel' && (_this.isDiffAction(_this.currentAction)) ) {
_this.expandViewContainer(); _this.expandViewContainer();
} }
_this.diffsLoaded = true; _this.diffsLoaded = true;
...@@ -324,6 +333,10 @@ ...@@ -324,6 +333,10 @@
return $('.inline-parallel-buttons a.active').data('view-type'); return $('.inline-parallel-buttons a.active').data('view-type');
}; };
MergeRequestTabs.prototype.isDiffAction = function(action) {
return action === 'diffs' || action === 'new/diffs'
};
MergeRequestTabs.prototype.expandViewContainer = function() { MergeRequestTabs.prototype.expandViewContainer = function() {
var $wrapper = $('.content-wrapper .container-fluid'); var $wrapper = $('.content-wrapper .container-fluid');
if (this.fixedLayoutPref === null) { if (this.fixedLayoutPref === null) {
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
this.currentProject = JSON.parse(currentProject); this.currentProject = JSON.parse(currentProject);
} }
$('.js-milestone-select').each(function(i, dropdown) { $('.js-milestone-select').each(function(i, dropdown) {
var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId; var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId, showMenuAbove;
$dropdown = $(dropdown); $dropdown = $(dropdown);
projectId = $dropdown.data('project-id'); projectId = $dropdown.data('project-id');
milestonesUrl = $dropdown.data('milestones'); milestonesUrl = $dropdown.data('milestones');
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
selectedMilestone = $dropdown.data('selected'); selectedMilestone = $dropdown.data('selected');
showNo = $dropdown.data('show-no'); showNo = $dropdown.data('show-no');
showAny = $dropdown.data('show-any'); showAny = $dropdown.data('show-any');
showMenuAbove = $dropdown.data('showMenuAbove');
showUpcoming = $dropdown.data('show-upcoming'); showUpcoming = $dropdown.data('show-upcoming');
useId = $dropdown.data('use-id'); useId = $dropdown.data('use-id');
defaultLabel = $dropdown.data('default-label'); defaultLabel = $dropdown.data('default-label');
...@@ -31,12 +32,12 @@ ...@@ -31,12 +32,12 @@
collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>'); collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>');
} }
return $dropdown.glDropdown({ return $dropdown.glDropdown({
showMenuAbove: showMenuAbove,
data: function(term, callback) { data: function(term, callback) {
return $.ajax({ return $.ajax({
url: milestonesUrl url: milestonesUrl
}).done(function(data) { }).done(function(data) {
var extraOptions; var extraOptions = [];
extraOptions = [];
if (showAny) { if (showAny) {
extraOptions.push({ extraOptions.push({
id: 0, id: 0,
...@@ -58,10 +59,14 @@ ...@@ -58,10 +59,14 @@
title: 'Upcoming' title: 'Upcoming'
}); });
} }
if (extraOptions.length > 2) { if (extraOptions.length) {
extraOptions.push('divider'); extraOptions.push('divider');
} }
return callback(extraOptions.concat(data));
callback(extraOptions.concat(data));
if (showMenuAbove) {
$dropdown.data('glDropdown').positionMenuAbove();
}
}); });
}, },
filterable: true, filterable: true,
...@@ -69,19 +74,20 @@ ...@@ -69,19 +74,20 @@
fields: ['title'] fields: ['title']
}, },
selectable: true, selectable: true,
toggleLabel: function(selected) { toggleLabel: function(selected, el, e) {
if (selected && 'id' in selected) { if (selected && 'id' in selected && $(el).hasClass('is-active')) {
return selected.title; return selected.title;
} else { } else {
return defaultLabel; return defaultLabel;
} }
}, },
defaultLabel: defaultLabel,
fieldName: $dropdown.data('field-name'), fieldName: $dropdown.data('field-name'),
text: function(milestone) { text: function(milestone) {
return _.escape(milestone.title); return _.escape(milestone.title);
}, },
id: function(milestone) { id: function(milestone) {
if (!useId) { if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) {
return milestone.name; return milestone.name;
} else { } else {
return milestone.id; return milestone.id;
...@@ -100,7 +106,8 @@ ...@@ -100,7 +106,8 @@
page = $('body').data('page'); page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index'; isIssueIndex = page === 'projects:issues:index';
isMRIndex = (page === page && page === 'projects:merge_requests:index'); isMRIndex = (page === page && page === 'projects:merge_requests:index');
if ($dropdown.hasClass('js-filter-bulk-update')) { if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
e.preventDefault();
return; return;
} }
if (page === 'projects:boards:show') { if (page === 'projects:boards:show') {
......
...@@ -3,12 +3,21 @@ ...@@ -3,12 +3,21 @@
const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); const $pipelineBtn = $(this).closest('.toggle-pipeline-btn');
const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph');
const $btnText = $(this).find('.toggle-btn-text'); const $btnText = $(this).find('.toggle-btn-text');
const $icon = $(this).find('.fa');
$($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed');
const expandIcon = 'fa-caret-down';
const hideIcon = 'fa-caret-up';
graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') if(graphCollapsed) {
$btnText.text('Expand');
$icon.removeClass(hideIcon).addClass(expandIcon);
} else {
$btnText.text('Hide');
$icon.removeClass(expandIcon).addClass(hideIcon);
}
} }
$(document).on('click', '.toggle-pipeline-btn', toggleGraph); $(document).on('click', '.toggle-pipeline-btn', toggleGraph);
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
data = groups.concat(projects); data = groups.concat(projects);
return finalCallback(data); return finalCallback(data);
}; };
return Api.groups(term, false, groupsCallback); return Api.groups(term, false, false, groupsCallback);
}; };
} else { } else {
projectsCallback = finalCallback; projectsCallback = finalCallback;
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
data = groups.concat(projects); data = groups.concat(projects);
return finalCallback(data); return finalCallback(data);
}; };
return Api.groups(query.term, false, groupsCallback); return Api.groups(query.term, false, false, groupsCallback);
}; };
} else { } else {
projectsCallback = finalCallback; projectsCallback = finalCallback;
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
filterable: true, filterable: true,
fieldName: 'group_id', fieldName: 'group_id',
data: function(term, callback) { data: function(term, callback) {
return Api.groups(term, null, function(data) { return Api.groups(term, false, false, function(data) {
data.unshift({ data.unshift({
name: 'Any' name: 'Any'
}); });
......
...@@ -89,7 +89,7 @@ content on the Users#show page. ...@@ -89,7 +89,7 @@ content on the Users#show page.
const action = $target.data('action'); const action = $target.data('action');
const source = $target.attr('href'); const source = $target.attr('href');
this.setTab(source, action); this.setTab(source, action);
return this.setCurrentAction(action); return this.setCurrentAction(source, action);
} }
activateTab(action) { activateTab(action) {
...@@ -142,14 +142,9 @@ content on the Users#show page. ...@@ -142,14 +142,9 @@ content on the Users#show page.
.toggle(status); .toggle(status);
} }
setCurrentAction(action) { setCurrentAction(source, action) {
const regExp = new RegExp(`\/(${this.actions.join('|')})(\.html)?\/?$`); let new_state = source
let new_state = this._location.pathname;
new_state = new_state.replace(/\/+$/, ''); new_state = new_state.replace(/\/+$/, '');
new_state = new_state.replace(regExp, '');
if (action !== this.defaultAction) {
new_state += `/${action}`;
}
new_state += this._location.search + this._location.hash; new_state += this._location.search + this._location.hash;
history.replaceState({ history.replaceState({
turbolinks: true, turbolinks: true,
......
...@@ -14,11 +14,12 @@ ...@@ -14,11 +14,12 @@
$('.js-user-search').each((function(_this) { $('.js-user-search').each((function(_this) {
return function(i, dropdown) { return function(i, dropdown) {
var options = {}; var options = {};
var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser; var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser, showMenuAbove;
$dropdown = $(dropdown); $dropdown = $(dropdown);
options.projectId = $dropdown.data('project-id'); options.projectId = $dropdown.data('project-id');
options.showCurrentUser = $dropdown.data('current-user'); options.showCurrentUser = $dropdown.data('current-user');
showNullUser = $dropdown.data('null-user'); showNullUser = $dropdown.data('null-user');
showMenuAbove = $dropdown.data('showMenuAbove');
showAnyUser = $dropdown.data('any-user'); showAnyUser = $dropdown.data('any-user');
firstUser = $dropdown.data('first-user'); firstUser = $dropdown.data('first-user');
options.authorId = $dropdown.data('author-id'); options.authorId = $dropdown.data('author-id');
...@@ -70,9 +71,10 @@ ...@@ -70,9 +71,10 @@
return $collapsedSidebar.html(collapsedAssigneeTemplate(user)); return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
}); });
}; };
collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <a class="author_link" href="/u/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>'); collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <a class="author_link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>');
assigneeTemplate = _.template('<% if (username) { %> <a class="author_link bold" href="/u/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>'); assigneeTemplate = _.template('<% if (username) { %> <a class="author_link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>');
return $dropdown.glDropdown({ return $dropdown.glDropdown({
showMenuAbove: showMenuAbove,
data: function(term, callback) { data: function(term, callback) {
var isAuthorFilter; var isAuthorFilter;
isAuthorFilter = $('.js-author-search'); isAuthorFilter = $('.js-author-search');
...@@ -116,8 +118,11 @@ ...@@ -116,8 +118,11 @@
if (showDivider) { if (showDivider) {
users.splice(showDivider, 0, "divider"); users.splice(showDivider, 0, "divider");
} }
// Send the data back
return callback(users); callback(users);
if (showMenuAbove) {
$dropdown.data('glDropdown').positionMenuAbove();
}
}); });
}, },
filterable: true, filterable: true,
...@@ -127,8 +132,8 @@ ...@@ -127,8 +132,8 @@
}, },
selectable: true, selectable: true,
fieldName: $dropdown.data('field-name'), fieldName: $dropdown.data('field-name'),
toggleLabel: function(selected) { toggleLabel: function(selected, el) {
if (selected && 'id' in selected) { if (selected && 'id' in selected && $(el).hasClass('is-active')) {
if (selected.text) { if (selected.text) {
return selected.text; return selected.text;
} else { } else {
...@@ -138,6 +143,7 @@ ...@@ -138,6 +143,7 @@
return defaultLabel; return defaultLabel;
} }
}, },
defaultLabel: defaultLabel,
inputId: 'issue_assignee_id', inputId: 'issue_assignee_id',
hidden: function(e) { hidden: function(e) {
$selectbox.hide(); $selectbox.hide();
...@@ -149,7 +155,9 @@ ...@@ -149,7 +155,9 @@
page = $('body').data('page'); page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index'; isIssueIndex = page === 'projects:issues:index';
isMRIndex = (page === page && page === 'projects:merge_requests:index'); isMRIndex = (page === page && page === 'projects:merge_requests:index');
if ($dropdown.hasClass('js-filter-bulk-update')) { if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
e.preventDefault();
selectedId = user.id;
return; return;
} }
if (page === 'projects:boards:show') { if (page === 'projects:boards:show') {
...@@ -167,6 +175,9 @@ ...@@ -167,6 +175,9 @@
return assignTo(selected); return assignTo(selected);
} }
}, },
id: function (user) {
return user.id;
},
renderRow: function(user) { renderRow: function(user) {
var avatar, img, listClosingTags, listWithName, listWithUserName, selected, username; var avatar, img, listClosingTags, listWithName, listWithUserName, selected, username;
username = user.username ? "@" + user.username : ""; username = user.username ? "@" + user.username : "";
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
width: 40px; width: 40px;
height: 40px; height: 40px;
padding: 0; padding: 0;
@include border-radius($avatar_radius); border-radius: $avatar_radius;
border: 1px solid rgba(0, 0, 0, .1); border: 1px solid rgba(0, 0, 0, .1);
&.avatar-inline { &.avatar-inline {
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
} }
&.avatar-tile { &.avatar-tile {
@include border-radius(0); border-radius: 0;
border: none; border: none;
} }
......
...@@ -133,7 +133,7 @@ ...@@ -133,7 +133,7 @@
} }
.identicon { .identicon {
@include border-radius(50%); border-radius: 50%;
} }
} }
......
@mixin btn-default { @mixin btn-default {
@include border-radius(3px); border-radius: 3px;
font-size: $gl-font-size; font-size: $gl-font-size;
font-weight: 500; font-weight: 500;
padding: $gl-vert-padding $gl-btn-padding; padding: $gl-vert-padding $gl-btn-padding;
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
&:active { &:active {
outline: none; outline: none;
background-color: $btn-active-gray; background-color: $btn-active-gray;
@include box-shadow($gl-btn-active-background); box-shadow: $gl-btn-active-background;
} }
} }
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
&:active, &:active,
&.active { &.active {
@include box-shadow ($gl-btn-active-background); box-shadow: $gl-btn-active-background;
background-color: $dark; background-color: $dark;
border-color: $border-dark; border-color: $border-dark;
...@@ -194,10 +194,17 @@ ...@@ -194,10 +194,17 @@
pointer-events: none !important; pointer-events: none !important;
} }
.caret { .fa-caret-down,
.fa-caret-up {
margin-left: 5px; margin-left: 5px;
} }
&.dropdown-toggle {
.fa-caret-down {
margin-left: 3px;
}
}
svg { svg {
height: 15px; height: 15px;
width: 15px; width: 15px;
...@@ -272,7 +279,7 @@ ...@@ -272,7 +279,7 @@
} }
.active { .active {
@include box-shadow($gl-btn-active-background); box-shadow: $gl-btn-active-background;
border: 1px solid #c6cacf !important; border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important; background-color: #e4e7ed !important;
......
.caret {
display: inline-block;
width: 0;
height: 0;
margin-left: 2px;
vertical-align: middle;
border-top: $caret-width-base dashed;
border-right: $caret-width-base solid transparent;
border-left: $caret-width-base solid transparent;
}
.btn-group {
.caret {
margin-left: 0;
}
}
.dropdown { .dropdown {
position: relative; position: relative;
...@@ -604,3 +587,9 @@ ...@@ -604,3 +587,9 @@
display: block; display: block;
color: $gl-placeholder-color; color: $gl-placeholder-color;
} }
.dropdown-toggle-text {
&.is-default {
color: $gl-placeholder-color;
}
}
...@@ -21,7 +21,8 @@ ...@@ -21,7 +21,8 @@
.flash-notice, .flash-alert { .flash-notice, .flash-alert {
border-radius: $border-radius-default; border-radius: $border-radius-default;
.container-fluid.container-limited.flash-text { .container-fluid,
.container-fluid.container-limited {
background: transparent; background: transparent;
} }
} }
...@@ -35,12 +36,6 @@ ...@@ -35,12 +36,6 @@
} }
} }
.content-wrapper {
.flash-notice .container-fluid {
background-color: transparent;
}
}
@media (max-width: $screen-md-min) { @media (max-width: $screen-md-min) {
ul.notes { ul.notes {
.flash-container.timeline-content { .flash-container.timeline-content {
......
...@@ -73,7 +73,7 @@ label { ...@@ -73,7 +73,7 @@ label {
} }
.form-control { .form-control {
@include box-shadow(none); box-shadow: none;
border-radius: 3px; border-radius: 3px;
padding: $gl-vert-padding $gl-input-padding; padding: $gl-vert-padding $gl-input-padding;
} }
...@@ -81,10 +81,10 @@ label { ...@@ -81,10 +81,10 @@ label {
.select-wrapper { .select-wrapper {
position: relative; position: relative;
.caret { .fa-caret-down {
position: absolute; position: absolute;
right: 10px; right: 10px;
top: $gl-padding; top: 10px;
color: $gray-darkest; color: $gray-darkest;
pointer-events: none; pointer-events: none;
} }
......
...@@ -57,6 +57,10 @@ header { ...@@ -57,6 +57,10 @@ header {
&:hover, &:focus, &:active { &:hover, &:focus, &:active {
background-color: $background-color; background-color: $background-color;
} }
.fa-caret-down {
font-size: 15px;
}
} }
.navbar-toggle { .navbar-toggle {
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
margin-top: 5px; margin-top: 5px;
} }
@include border-radius(3px); border-radius: 3px;
display: block; display: block;
float: left; float: left;
margin-right: 10px; margin-right: 10px;
......
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
} }
.markdown-area { .markdown-area {
@include border-radius(0); border-radius: 0;
background: #fff; background: #fff;
border: 1px solid #ddd; border: 1px solid #ddd;
min-height: 140px; min-height: 140px;
......
/**
* Generic mixins
*/
@mixin box-shadow($shadow) {
box-shadow: $shadow;
}
@mixin border-radius($radius) {
border-radius: $radius;
}
/** /**
* Prefilled mixins * Prefilled mixins
* Mixins with fixed values * Mixins with fixed values
......
...@@ -133,5 +133,5 @@ ...@@ -133,5 +133,5 @@
font-size: 20px; font-size: 20px;
color: #777; color: #777;
z-index: 100; z-index: 100;
@include box-shadow(0 1px 2px #ddd); box-shadow: 0 1px 2px #ddd;
} }
...@@ -21,7 +21,14 @@ ...@@ -21,7 +21,14 @@
padding-right: 10px; padding-right: 10px;
b { b {
@extend .caret; display: inline-block;
width: 0;
height: 0;
margin-left: 2px;
vertical-align: middle;
border-top: $caret-width-base dashed;
border-right: $caret-width-base solid transparent;
border-left: $caret-width-base solid transparent;
color: $gray-darkest; color: $gray-darkest;
} }
} }
...@@ -39,8 +46,8 @@ ...@@ -39,8 +46,8 @@
} }
.select2-drop { .select2-drop {
@include box-shadow(rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0); box-shadow: rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0;
@include border-radius ($border-radius-default); border-radius: $border-radius-default;
border: none; border: none;
min-width: 175px; min-width: 175px;
} }
...@@ -65,7 +72,7 @@ ...@@ -65,7 +72,7 @@
.select2-container-active { .select2-container-active {
.select2-choice, .select2-choices { .select2-choice, .select2-choices {
@include box-shadow(none); box-shadow: none;
} }
} }
...@@ -75,13 +82,13 @@ ...@@ -75,13 +82,13 @@
outline: 0; outline: 0;
background-image: none; background-image: none;
background-color: $white-dark; background-color: $white-dark;
@include box-shadow($gl-btn-active-gradient); box-shadow: $gl-btn-active-gradient;
} }
} }
.select2-container-multi { .select2-container-multi {
.select2-choices { .select2-choices {
@include border-radius($border-radius-default); border-radius: $border-radius-default;
border-color: $input-border; border-color: $input-border;
background: none; background: none;
...@@ -116,7 +123,7 @@ ...@@ -116,7 +123,7 @@
&.select2-container-active .select2-choices, &.select2-container-active .select2-choices,
&.select2-dropdown-open .select2-choices { &.select2-dropdown-open .select2-choices {
border-color: $border-white-normal; border-color: $border-white-normal;
@include box-shadow($gl-btn-active-gradient); box-shadow: $gl-btn-active-gradient;
} }
} }
...@@ -150,7 +157,7 @@ ...@@ -150,7 +157,7 @@
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right 0 bottom 6px; background-position: right 0 bottom 6px;
border: 1px solid $input-border; border: 1px solid $input-border;
@include border-radius($border-radius-default); border-radius: $border-radius-default;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
&:focus { &:focus {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
&.page-sidebar-pinned { &.page-sidebar-pinned {
.sidebar-wrapper { .sidebar-wrapper {
@include box-shadow(none); box-shadow: none;
} }
} }
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
width: 0; width: 0;
overflow: hidden; overflow: hidden;
transition: width $sidebar-transition-duration; transition: width $sidebar-transition-duration;
@include box-shadow(2px 0 16px 0 $black-transparent); box-shadow: 2px 0 16px 0 $black-transparent;
} }
} }
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
.count { .count {
float: right; float: right;
padding: 0 8px; padding: 0 8px;
@include border-radius(6px); border-radius: 6px;
} }
} }
......
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
font-size: 13px; font-size: 13px;
line-height: 1.6em; line-height: 1.6em;
overflow-x: auto; overflow-x: auto;
@include border-radius(2px); border-radius: 2px;
} }
p > code { p > code {
......
...@@ -162,6 +162,10 @@ lex ...@@ -162,6 +162,10 @@ lex
list-style: none; list-style: none;
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
&.is-smaller {
height: calc(100% - 185px);
}
} }
.board-list-loading { .board-list-loading {
...@@ -233,3 +237,31 @@ lex ...@@ -233,3 +237,31 @@ lex
margin-right: 5px; margin-right: 5px;
} }
} }
.board-new-issue-form {
margin: 5px;
}
.board-issue-count-holder {
margin-top: -3px;
.btn {
line-height: 12px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
.board-issue-count {
padding-right: 10px;
padding-left: 10px;
line-height: 21px;
border-radius: $border-radius-base;
border: 1px solid $border-color;
&.has-btn {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-width: 1px 0 1px 1px;
}
}
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
.bordered-box { .bordered-box {
border: 1px solid $border-color; border: 1px solid $border-color;
@include border-radius($border-radius-default); border-radius: $border-radius-default;
} }
......
.file-editor { .file-editor {
#editor { #editor {
border: none; border: none;
@include border-radius(0); border-radius: 0;
height: 500px; height: 500px;
margin: 0; margin: 0;
padding: 0; padding: 0;
......
.environments-container,
.deployments-container {
width: 100%;
overflow: auto;
}
.environments { .environments {
.deployment-column {
.avatar {
float: none;
}
}
.commit-title { .commit-title {
margin: 0; margin: 0;
...@@ -9,6 +20,7 @@ ...@@ -9,6 +20,7 @@
width: 12px; width: 12px;
} }
.external-url,
.dropdown-new { .dropdown-new {
color: $table-text-gray; color: $table-text-gray;
} }
...@@ -21,16 +33,35 @@ ...@@ -21,16 +33,35 @@
} }
} }
.build-link,
.branch-name { .branch-name {
color: $gl-dark-link-color; color: $gl-dark-link-color;
} }
.deployment {
.build-column {
.build-link {
color: $gl-dark-link-color;
}
.avatar {
float: none;
}
}
}
} }
.table.builds.environments { .table.builds.environments {
min-width: 500px;
.icon-container { .icon-container {
width: 20px; width: 20px;
text-align: center; text-align: center;
} }
.branch-commit {
.commit-id {
margin-right: 0;
}
}
} }
...@@ -91,7 +91,7 @@ ...@@ -91,7 +91,7 @@
float: right; float: right;
border: 1px solid #eee; border: 1px solid #eee;
padding: 5px; padding: 5px;
@include border-radius(5px); border-radius: 5px;
background: $gray-light; background: $gray-light;
margin-left: 10px; margin-left: 10px;
top: -6px; top: -6px;
......
.suggest-colors { .suggest-colors {
margin-top: 5px; margin-top: 5px;
a { a {
@include border-radius(4px); border-radius: 4px;
width: 30px; width: 30px;
height: 30px; height: 30px;
display: inline-block; display: inline-block;
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
overflow: hidden; overflow: hidden;
a { a {
@include border-radius(0); border-radius: 0;
width: (100% / 7); width: (100% / 7);
margin-right: 0; margin-right: 0;
margin-bottom: -5px; margin-bottom: -5px;
...@@ -59,6 +59,13 @@ ...@@ -59,6 +59,13 @@
width: 200px; width: 200px;
margin-bottom: 0; margin-bottom: 0;
} }
.label {
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
max-width: 100%;
}
} }
.label-description { .label-description {
......
...@@ -73,12 +73,12 @@ ...@@ -73,12 +73,12 @@
height: auto; height: auto;
&.top { &.top {
@include border-radius(5px 5px 0 0); border-radius: 5px 5px 0 0;
margin-bottom: 0; margin-bottom: 0;
} }
&.bottom { &.bottom {
@include border-radius(0 0 5px 5px); border-radius: 0 0 5px 5px;
border-top: 0; border-top: 0;
margin-bottom: 20px; margin-bottom: 20px;
} }
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
&.middle { &.middle {
border-top: 0; border-top: 0;
margin-bottom: 0; margin-bottom: 0;
@include border-radius(0); border-radius: 0;
} }
&:active, &:focus { &:active, &:focus {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
background: $background-color; background: $background-color;
color: $gl-gray; color: $gl-gray;
border: 1px solid $border-color; border: 1px solid $border-color;
@include border-radius(2px); border-radius: 2px;
form { form {
margin-bottom: 0; margin-bottom: 0;
...@@ -350,6 +350,10 @@ ...@@ -350,6 +350,10 @@
.issuable-form-select-holder { .issuable-form-select-holder {
display: inline-block; display: inline-block;
width: 250px; width: 250px;
.dropdown-menu-toggle {
width: 100%;
}
} }
.table-holder { .table-holder {
......
...@@ -334,7 +334,7 @@ ul.notes { ...@@ -334,7 +334,7 @@ ul.notes {
.add-diff-note { .add-diff-note {
margin-top: -4px; margin-top: -4px;
@include border-radius(40px); border-radius: 40px;
background: #fff; background: #fff;
padding: 4px; padding: 4px;
font-size: 16px; font-size: 16px;
......
...@@ -229,9 +229,12 @@ ...@@ -229,9 +229,12 @@
.fa { .fa {
color: $table-text-gray; color: $table-text-gray;
margin-right: 6px;
font-size: 14px; font-size: 14px;
} }
svg, .fa {
margin-right: 0;
}
} }
.btn-remove { .btn-remove {
...@@ -272,18 +275,8 @@ ...@@ -272,18 +275,8 @@
.toggle-pipeline-btn { .toggle-pipeline-btn {
background-color: $gray-dark; background-color: $gray-dark;
.caret {
border-top: none;
border-bottom: 4px solid;
}
&.graph-collapsed { &.graph-collapsed {
background-color: $white-light; background-color: $white-light;
.caret {
border-bottom: none;
border-top: 4px solid;
}
} }
} }
......
...@@ -94,7 +94,7 @@ ...@@ -94,7 +94,7 @@
.profile-user-bio { .profile-user-bio {
// Limits the width of the user bio for readability. // Limits the width of the user bio for readability.
max-width: 600px; max-width: 600px;
margin: 15px auto 0; margin: 10px auto;
padding: 0 16px; padding: 0 16px;
} }
...@@ -213,29 +213,22 @@ ...@@ -213,29 +213,22 @@
} }
.user-profile { .user-profile {
.cover-controls a { .cover-controls a {
margin-left: 5px; margin-left: 5px;
} }
.profile-header { .profile-header {
margin: 0 auto; margin: 0 auto;
.avatar-holder { .avatar-holder {
width: 90px; width: 90px;
display: inline-block; margin: 0 auto 10px;
}
.user-info {
display: inline-block;
text-align: left;
vertical-align: middle;
margin-left: 15px;
.handle {
color: $gl-gray-light;
}
.member-date {
margin-bottom: 4px;
}
} }
} }
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.cover-block { .cover-block {
padding-top: 20px; padding-top: 20px;
} }
...@@ -258,10 +251,6 @@ ...@@ -258,10 +251,6 @@
} }
} }
.user-profile-nav {
margin-top: 15px;
}
table.u2f-registrations { table.u2f-registrations {
th:not(:last-child), td:not(:last-child) { th:not(:last-child), td:not(:last-child) {
border-right: solid 1px transparent; border-right: solid 1px transparent;
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
text-align: center; text-align: center;
.preview { .preview {
@include border-radius(4px); border-radius: 4px;
height: 80px; height: 80px;
margin-bottom: 10px; margin-bottom: 10px;
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
width: 160px; width: 160px;
img { img {
@include border-radius(4px); border-radius: 4px;
max-width: 100%; max-width: 100%;
} }
......
...@@ -354,7 +354,7 @@ a.deploy-project-label { ...@@ -354,7 +354,7 @@ a.deploy-project-label {
justify-content: flex-start; justify-content: flex-start;
.fork-thumbnail { .fork-thumbnail {
@include border-radius($border-radius-base); border-radius: $border-radius-base;
background-color: $white-light; background-color: $white-light;
border: 1px solid $border-white-light; border: 1px solid $border-white-light;
height: 202px; height: 202px;
...@@ -371,7 +371,7 @@ a.deploy-project-label { ...@@ -371,7 +371,7 @@ a.deploy-project-label {
background-color: $gray-light; background-color: $gray-light;
border: 1px solid $gray-dark; border: 1px solid $gray-dark;
margin: 0 auto; margin: 0 auto;
@include border-radius(50%); border-radius: 50%;
i { i {
font-size: 100px; font-size: 100px;
color: $gray-dark; color: $gray-dark;
...@@ -390,7 +390,7 @@ a.deploy-project-label { ...@@ -390,7 +390,7 @@ a.deploy-project-label {
} }
img { img {
@include border-radius(50%); border-radius: 50%;
max-width: 100px; max-width: 100px;
} }
} }
...@@ -496,7 +496,7 @@ pre.light-well { ...@@ -496,7 +496,7 @@ pre.light-well {
} }
.light-well { .light-well {
@include border-radius (2px); border-radius: 2px;
color: #5b6169; color: #5b6169;
font-size: 13px; font-size: 13px;
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
margin-right: 10px; margin-right: 10px;
border: 1px solid #eee; border: 1px solid #eee;
white-space: nowrap; white-space: nowrap;
@include border-radius(4px); border-radius: 4px;
&:hover { &:hover {
text-decoration: none; text-decoration: none;
......
...@@ -37,7 +37,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController ...@@ -37,7 +37,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
end end
def preview def preview
@message = broadcast_message_params[:message] @broadcast_message = BroadcastMessage.new(broadcast_message_params)
end end
protected protected
......
...@@ -173,7 +173,8 @@ class ApplicationController < ActionController::Base ...@@ -173,7 +173,8 @@ class ApplicationController < ActionController::Base
end end
def event_filter def event_filter
filters = cookies['event_filter'].split(',') if cookies['event_filter'].present? # Split using comma to maintain backward compatibility Ex/ "filter1,filter2"
filters = cookies['event_filter'].split(',')[0] if cookies['event_filter'].present?
@event_filter ||= EventFilter.new(filters) @event_filter ||= EventFilter.new(filters)
end end
......
module Ci
class ApplicationController < ::ApplicationController
def self.railtie_helpers_paths
"app/helpers/ci"
end
end
end
module Ci module Ci
class LintsController < ApplicationController class LintsController < ::ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
def show def show
......
module Ci module Ci
class ProjectsController < Ci::ApplicationController class ProjectsController < ::ApplicationController
before_action :project before_action :project
before_action :no_cache, only: [:badge] before_action :no_cache, only: [:badge]
before_action :authorize_read_project!, except: [:badge, :index] before_action :authorize_read_project!, except: [:badge, :index]
......
class NamespacesController < ApplicationController
skip_before_action :authenticate_user!
def show
namespace = Namespace.find_by(path: params[:id])
if namespace
if namespace.is_a?(Group)
group = namespace
else
user = namespace.owner
end
end
if user
redirect_to user_path(user)
elsif group && can?(current_user, :read_group, group)
redirect_to group_path(group)
elsif current_user.nil?
authenticate_user!
else
render_404
end
end
end
...@@ -2,6 +2,7 @@ module Projects ...@@ -2,6 +2,7 @@ module Projects
module Boards module Boards
class IssuesController < Boards::ApplicationController class IssuesController < Boards::ApplicationController
before_action :authorize_read_issue!, only: [:index] before_action :authorize_read_issue!, only: [:index]
before_action :authorize_create_issue!, only: [:create]
before_action :authorize_update_issue!, only: [:update] before_action :authorize_update_issue!, only: [:update]
def index def index
...@@ -9,16 +10,23 @@ module Projects ...@@ -9,16 +10,23 @@ module Projects
issues = issues.page(params[:page]) issues = issues.page(params[:page])
render json: { render json: {
issues: issues.as_json( issues: serialize_as_json(issues),
only: [:iid, :title, :confidential],
include: {
assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
}),
size: issues.total_count size: issues.total_count
} }
end end
def create
list = project.board.lists.find(params[:list_id])
service = ::Boards::Issues::CreateService.new(project, current_user, issue_params)
issue = service.execute(list)
if issue.valid?
render json: serialize_as_json(issue)
else
render json: issue.errors, status: :unprocessable_entity
end
end
def update def update
service = ::Boards::Issues::MoveService.new(project, current_user, move_params) service = ::Boards::Issues::MoveService.new(project, current_user, move_params)
...@@ -43,6 +51,10 @@ module Projects ...@@ -43,6 +51,10 @@ module Projects
return render_403 unless can?(current_user, :read_issue, project) return render_403 unless can?(current_user, :read_issue, project)
end end
def authorize_create_issue!
return render_403 unless can?(current_user, :admin_issue, project)
end
def authorize_update_issue! def authorize_update_issue!
return render_403 unless can?(current_user, :update_issue, issue) return render_403 unless can?(current_user, :update_issue, issue)
end end
...@@ -54,6 +66,19 @@ module Projects ...@@ -54,6 +66,19 @@ module Projects
def move_params def move_params
params.permit(:id, :from_list_id, :to_list_id) params.permit(:id, :from_list_id, :to_list_id)
end end
def issue_params
params.require(:issue).permit(:title).merge(request: request)
end
def serialize_as_json(resource)
resource.as_json(
only: [:iid, :title, :confidential],
include: {
assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
})
end
end end
end end
end end
class Projects::BoardsController < Projects::ApplicationController class Projects::BoardsController < Projects::ApplicationController
include IssuableCollections
respond_to :html respond_to :html
before_action :authorize_read_board!, only: [:show] before_action :authorize_read_board!, only: [:show]
......
...@@ -4,10 +4,15 @@ class Projects::GroupLinksController < Projects::ApplicationController ...@@ -4,10 +4,15 @@ class Projects::GroupLinksController < Projects::ApplicationController
def index def index
@group_links = project.project_group_links.all @group_links = project.project_group_links.all
@skip_groups = @group_links.pluck(:group_id)
@skip_groups << project.group.try(:id)
end end
def create def create
group = Group.find(params[:link_group_id]) group = Group.find(params[:link_group_id]) if params[:link_group_id].present?
if group
return render_404 unless can?(current_user, :read_group, group) return render_404 unless can?(current_user, :read_group, group)
project.project_group_links.create( project.project_group_links.create(
...@@ -15,6 +20,9 @@ class Projects::GroupLinksController < Projects::ApplicationController ...@@ -15,6 +20,9 @@ class Projects::GroupLinksController < Projects::ApplicationController
group_access: params[:link_group_access], group_access: params[:link_group_access],
expires_at: params[:expires_at] expires_at: params[:expires_at]
) )
else
flash[:alert] = 'Please select a group.'
end
redirect_to namespace_project_group_links_path(project.namespace, project) redirect_to namespace_project_group_links_path(project.namespace, project)
end end
......
...@@ -159,7 +159,8 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -159,7 +159,8 @@ class Projects::IssuesController < Projects::ApplicationController
protected protected
def issue def issue
@noteable = @issue ||= @project.issues.find_by(iid: params[:id]) || redirect_old # The Sortable default scope causes performance issues when used with find_by
@noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take || redirect_old
end end
alias_method :subscribable_resource, :issue alias_method :subscribable_resource, :issue
alias_method :issuable, :issue alias_method :issuable, :issue
......
...@@ -30,9 +30,15 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -30,9 +30,15 @@ class Projects::LabelsController < Projects::ApplicationController
@label = @project.labels.create(label_params) @label = @project.labels.create(label_params)
if @label.valid? if @label.valid?
redirect_to namespace_project_labels_path(@project.namespace, @project) respond_to do |format|
format.html { redirect_to namespace_project_labels_path(@project.namespace, @project) }
format.json { render json: @label }
end
else else
render 'new' respond_to do |format|
format.html { render 'new' }
format.json { render json: { message: @label.errors.messages }, status: 400 }
end
end end
end end
......
...@@ -19,6 +19,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -19,6 +19,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :define_diff_comment_vars, only: [:diffs] before_action :define_diff_comment_vars, only: [:diffs]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines] before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines]
before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines] before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines]
before_action :apply_diff_view_cookie!, only: [:new_diffs]
before_action :build_merge_request, only: [:new, :new_diffs]
# Allow read any merge_request # Allow read any merge_request
before_action :authorize_read_merge_request! before_action :authorize_read_merge_request!
...@@ -210,29 +212,26 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -210,29 +212,26 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def new def new
apply_diff_view_cookie! define_new_vars
end
build_merge_request
@noteable = @merge_request
@target_branches = if @merge_request.target_project def new_diffs
@merge_request.target_project.repository.branch_names respond_to do |format|
format.html do
define_new_vars
render "new"
end
format.json do
@diffs = if @merge_request.can_be_created
@merge_request.diffs(diff_options)
else else
[] []
end end
@target_project = merge_request.target_project
@source_project = merge_request.source_project
@commits = @merge_request.compare_commits.reverse
@commit = @merge_request.diff_head_commit
@base_commit = @merge_request.diff_base_commit
@diffs = @merge_request.diffs(diff_options) if @merge_request.compare
@diff_notes_disabled = true @diff_notes_disabled = true
@pipeline = @merge_request.pipeline
@statuses = @pipeline.statuses.relevant if @pipeline
@note_counts = Note.where(commit_id: @commits.map(&:id)). render json: { html: view_to_html_string('projects/merge_requests/_new_diffs', diffs: @diffs) }
group(:commit_id).count end
end
end end
def create def create
...@@ -490,6 +489,27 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -490,6 +489,27 @@ class Projects::MergeRequestsController < Projects::ApplicationController
) )
end end
def define_new_vars
@noteable = @merge_request
@target_branches = if @merge_request.target_project
@merge_request.target_project.repository.branch_names
else
[]
end
@target_project = merge_request.target_project
@source_project = merge_request.source_project
@commits = @merge_request.compare_commits.reverse
@commit = @merge_request.diff_head_commit
@base_commit = @merge_request.diff_base_commit
@pipeline = @merge_request.pipeline
@statuses = @pipeline.statuses.relevant if @pipeline
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
end
def invalid_mr def invalid_mr
# Render special view for MR with removed target branch # Render special view for MR with removed target branch
render 'invalid' render 'invalid'
...@@ -521,7 +541,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -521,7 +541,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def build_merge_request def build_merge_request
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
@merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute
end end
def compared_diff_version def compared_diff_version
......
...@@ -16,7 +16,7 @@ module AppearancesHelper ...@@ -16,7 +16,7 @@ module AppearancesHelper
end end
def brand_text def brand_text
markdown(brand_item.description) markdown_field(brand_item, :description)
end end
def brand_item def brand_item
......
...@@ -11,18 +11,6 @@ module ApplicationSettingsHelper ...@@ -11,18 +11,6 @@ module ApplicationSettingsHelper
current_application_settings.signin_enabled? current_application_settings.signin_enabled?
end end
def extra_sign_in_text
current_application_settings.sign_in_text
end
def after_sign_up_text
current_application_settings.after_sign_up_text
end
def shared_runners_text
current_application_settings.shared_runners_text
end
def user_oauth_applications? def user_oauth_applications?
current_application_settings.user_oauth_applications current_application_settings.user_oauth_applications
end end
......
...@@ -4,15 +4,18 @@ module AvatarsHelper ...@@ -4,15 +4,18 @@ module AvatarsHelper
user: commit_or_event.author, user: commit_or_event.author,
user_name: commit_or_event.author_name, user_name: commit_or_event.author_name,
user_email: commit_or_event.author_email, user_email: commit_or_event.author_email,
css_class: 'hidden-xs'
})) }))
end end
def user_avatar(options = {}) def user_avatar(options = {})
avatar_size = options[:size] || 16 avatar_size = options[:size] || 16
user_name = options[:user].try(:name) || options[:user_name] user_name = options[:user].try(:name) || options[:user_name]
css_class = options[:css_class] || ''
avatar = image_tag( avatar = image_tag(
avatar_icon(options[:user] || options[:user_email], avatar_size), avatar_icon(options[:user] || options[:user_email], avatar_size),
class: "avatar has-tooltip hidden-xs s#{avatar_size}", class: "avatar has-tooltip s#{avatar_size} #{css_class}",
alt: "#{user_name}'s avatar", alt: "#{user_name}'s avatar",
title: user_name, title: user_name,
data: { container: 'body' } data: { container: 'body' }
......
...@@ -3,7 +3,7 @@ module BroadcastMessagesHelper ...@@ -3,7 +3,7 @@ module BroadcastMessagesHelper
return unless message.present? return unless message.present?
content_tag :div, class: 'broadcast-message', style: broadcast_message_style(message) do content_tag :div, class: 'broadcast-message', style: broadcast_message_style(message) do
icon('bullhorn') << ' ' << render_broadcast_message(message.message) icon('bullhorn') << ' ' << render_broadcast_message(message)
end end
end end
...@@ -32,7 +32,7 @@ module BroadcastMessagesHelper ...@@ -32,7 +32,7 @@ module BroadcastMessagesHelper
end end
end end
def render_broadcast_message(message) def render_broadcast_message(broadcast_message)
Banzai.render(message, pipeline: :broadcast_message).html_safe Banzai.render_field(broadcast_message, :message).html_safe
end end
end end
...@@ -15,10 +15,11 @@ module ButtonHelper ...@@ -15,10 +15,11 @@ module ButtonHelper
# #
# See http://clipboardjs.com/#usage # See http://clipboardjs.com/#usage
def clipboard_button(data = {}) def clipboard_button(data = {})
css_class = data[:class] || 'btn-clipboard'
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button, content_tag :button,
icon('clipboard'), icon('clipboard'),
class: "btn btn-clipboard", class: "btn #{css_class}",
data: data, data: data,
type: :button, type: :button,
title: "Copy to Clipboard" title: "Copy to Clipboard"
......
...@@ -40,8 +40,9 @@ module DropdownsHelper ...@@ -40,8 +40,9 @@ module DropdownsHelper
end end
def dropdown_toggle(toggle_text, data_attr, options = {}) def dropdown_toggle(toggle_text, data_attr, options = {})
default_label = data_attr[:default_label]
content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do
output = content_tag(:span, toggle_text, class: "dropdown-toggle-text") output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}")
output << icon('chevron-down') output << icon('chevron-down')
output.html_safe output.html_safe
end end
......
...@@ -13,14 +13,12 @@ module GitlabMarkdownHelper ...@@ -13,14 +13,12 @@ module GitlabMarkdownHelper
def link_to_gfm(body, url, html_options = {}) def link_to_gfm(body, url, html_options = {})
return "" if body.blank? return "" if body.blank?
escaped_body = if body.start_with?('<img') context = {
body project: @project,
else current_user: (current_user if defined?(current_user)),
escape_once(body) pipeline: :single_line,
end }
gfm_body = Banzai.render(body, context)
user = current_user if defined?(current_user)
gfm_body = Banzai.render(escaped_body, project: @project, current_user: user, pipeline: :single_line)
fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body) fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a' if fragment.children.size == 1 && fragment.children[0].name == 'a'
...@@ -51,17 +49,15 @@ module GitlabMarkdownHelper ...@@ -51,17 +49,15 @@ module GitlabMarkdownHelper
context[:project] ||= @project context[:project] ||= @project
html = Banzai.render(text, context) html = Banzai.render(text, context)
banzai_postprocess(html, context)
end
context.merge!( def markdown_field(object, field)
current_user: (current_user if defined?(current_user)), object = object.for_display if object.respond_to?(:for_display)
return "" unless object.present?
# RelativeLinkFilter
requested_path: @path,
project_wiki: @project_wiki,
ref: @ref
)
Banzai.post_process(html, context) html = Banzai.render_field(object, field)
banzai_postprocess(html, object.banzai_render_context(field))
end end
def asciidoc(text) def asciidoc(text)
...@@ -196,4 +192,18 @@ module GitlabMarkdownHelper ...@@ -196,4 +192,18 @@ module GitlabMarkdownHelper
icon(options[:icon]) icon(options[:icon])
end end
end end
# Calls Banzai.post_process with some common context options
def banzai_postprocess(html, context)
context.merge!(
current_user: (current_user if defined?(current_user)),
# RelativeLinkFilter
requested_path: @path,
project_wiki: @project_wiki,
ref: @ref
)
Banzai.post_process(html, context)
end
end end
...@@ -8,18 +8,12 @@ module IssuablesHelper ...@@ -8,18 +8,12 @@ module IssuablesHelper
end end
def multi_label_name(current_labels, default_label) def multi_label_name(current_labels, default_label)
# current_labels may be a string from before if current_labels && current_labels.any?
if current_labels.is_a?(Array) title = current_labels.first.try(:title)
if current_labels.count > 1 if current_labels.size > 1
"#{current_labels[0]} +#{current_labels.count - 1} more" "#{title} +#{current_labels.size - 1} more"
else else
current_labels[0] title
end
elsif current_labels.is_a?(String)
if current_labels.nil? || current_labels.empty?
default_label
else
current_labels
end end
else else
default_label default_label
......
...@@ -113,14 +113,13 @@ module IssuesHelper ...@@ -113,14 +113,13 @@ module IssuesHelper
end end
end end
def award_user_list(awards, current_user) def award_user_list(awards, current_user, limit: 10)
names = awards.map do |award| names = awards.map do |award|
award.user == current_user ? 'You' : award.user.name award.user == current_user ? 'You' : award.user.name
end end
# Take first 9 OR current user + first 9
current_user_name = names.delete('You') current_user_name = names.delete('You')
names = names.first(9).insert(0, current_user_name).compact names = names.insert(0, current_user_name).compact.first(limit)
names << "#{awards.size - names.size} more." if awards.size > names.size names << "#{awards.size - names.size} more." if awards.size > names.size
......
...@@ -115,8 +115,9 @@ module LabelsHelper ...@@ -115,8 +115,9 @@ module LabelsHelper
end end
def labels_filter_path def labels_filter_path
if @project project = @target_project || @project
namespace_project_labels_path(@project.namespace, @project, :json) if project
namespace_project_labels_path(project.namespace, project, :json)
else else
dashboard_labels_path(:json) dashboard_labels_path(:json)
end end
......
...@@ -71,8 +71,9 @@ module MilestonesHelper ...@@ -71,8 +71,9 @@ module MilestonesHelper
end end
def milestones_filter_dropdown_path def milestones_filter_dropdown_path
if @project project = @target_project || @project
namespace_project_milestones_path(@project.namespace, @project, :json) if project
namespace_project_milestones_path(project.namespace, project, :json)
else else
dashboard_milestones_path(:json) dashboard_milestones_path(:json)
end end
......
...@@ -153,8 +153,18 @@ module SearchHelper ...@@ -153,8 +153,18 @@ module SearchHelper
search_path(options) search_path(options)
end end
# Sanitize html generated after parsing markdown from issue description or comment # Sanitize a HTML field for search display. Most tags are stripped out and the
def search_md_sanitize(html) # maximum length is set to 200 characters.
def search_md_sanitize(object, field)
html = markdown_field(object, field)
html = Truncato.truncate(
html,
count_tags: false,
count_tail: false,
max_length: 200
)
# Truncato's filtered_tags and filtered_attributes are not quite the same
sanitize(html, tags: %w(a p ol ul li pre code)) sanitize(html, tags: %w(a p ol ul li pre code))
end end
end end
...@@ -49,12 +49,10 @@ module SelectsHelper ...@@ -49,12 +49,10 @@ module SelectsHelper
end end
def select2_tag(id, opts = {}) def select2_tag(id, opts = {})
css_class = '' opts[:class] << ' multiselect' if opts[:multiple]
css_class << 'multiselect ' if opts[:multiple]
css_class << (opts[:class] || '')
value = opts[:selected] || '' value = opts[:selected] || ''
hidden_field_tag(id, value, class: css_class) hidden_field_tag(id, value, opts)
end end
private private
......
class AbuseReport < ActiveRecord::Base class AbuseReport < ActiveRecord::Base
include CacheMarkdownField
cache_markdown_field :message, pipeline: :single_line
belongs_to :reporter, class_name: 'User' belongs_to :reporter, class_name: 'User'
belongs_to :user belongs_to :user
...@@ -7,6 +11,9 @@ class AbuseReport < ActiveRecord::Base ...@@ -7,6 +11,9 @@ class AbuseReport < ActiveRecord::Base
validates :message, presence: true validates :message, presence: true
validates :user_id, uniqueness: { message: 'has already been reported' } validates :user_id, uniqueness: { message: 'has already been reported' }
# For CacheMarkdownField
alias_method :author, :reporter
def remove_user(deleted_by:) def remove_user(deleted_by:)
user.block user.block
DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true) DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true)
......
class Appearance < ActiveRecord::Base class Appearance < ActiveRecord::Base
include CacheMarkdownField
cache_markdown_field :description
validates :title, presence: true validates :title, presence: true
validates :description, presence: true validates :description, presence: true
validates :logo, file_size: { maximum: 1.megabyte } validates :logo, file_size: { maximum: 1.megabyte }
......
class ApplicationSetting < ActiveRecord::Base class ApplicationSetting < ActiveRecord::Base
include CacheMarkdownField
include TokenAuthenticatable include TokenAuthenticatable
add_authentication_token_field :runners_registration_token add_authentication_token_field :runners_registration_token
add_authentication_token_field :health_check_access_token add_authentication_token_field :health_check_access_token
...@@ -17,6 +19,11 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -17,6 +19,11 @@ class ApplicationSetting < ActiveRecord::Base
serialize :domain_whitelist, Array serialize :domain_whitelist, Array
serialize :domain_blacklist, Array serialize :domain_blacklist, Array
cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text
cache_markdown_field :shared_runners_text, pipeline: :plain_markdown
cache_markdown_field :after_sign_up_text
attr_accessor :domain_whitelist_raw, :domain_blacklist_raw attr_accessor :domain_whitelist_raw, :domain_blacklist_raw
validates :session_expire_delay, validates :session_expire_delay,
......
class BroadcastMessage < ActiveRecord::Base class BroadcastMessage < ActiveRecord::Base
include CacheMarkdownField
include Sortable include Sortable
cache_markdown_field :message, pipeline: :broadcast_message
validates :message, presence: true validates :message, presence: true
validates :starts_at, presence: true validates :starts_at, presence: true
validates :ends_at, presence: true validates :ends_at, presence: true
......
...@@ -251,9 +251,8 @@ module Ci ...@@ -251,9 +251,8 @@ module Ci
Ci::ProcessPipelineService.new(project, user).execute(self) Ci::ProcessPipelineService.new(project, user).execute(self)
end end
def build_updated def update_status
with_lock do with_lock do
reload
case latest_builds_status case latest_builds_status
when 'pending' then enqueue when 'pending' then enqueue
when 'running' then run when 'running' then run
......
class CommitStatus < ActiveRecord::Base class CommitStatus < ActiveRecord::Base
include HasStatus include HasStatus
include Importable include Importable
include AfterCommitQueue
self.table_name = 'ci_builds' self.table_name = 'ci_builds'
...@@ -84,21 +85,35 @@ class CommitStatus < ActiveRecord::Base ...@@ -84,21 +85,35 @@ class CommitStatus < ActiveRecord::Base
commit_status.update_attributes finished_at: Time.now commit_status.update_attributes finished_at: Time.now
end end
after_transition any => [:success, :failed, :canceled] do |commit_status|
commit_status.pipeline.try(:process!)
true
end
after_transition do |commit_status, transition| after_transition do |commit_status, transition|
commit_status.pipeline.try(:build_updated) unless transition.loopback? return if transition.loopback?
commit_status.run_after_commit do
pipeline.try do |pipeline|
if complete?
ProcessPipelineWorker.perform_async(pipeline.id)
else
UpdatePipelineWorker.perform_async(pipeline.id)
end
end
end
end end
after_transition [:created, :pending, :running] => :success do |commit_status| after_transition [:created, :pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) commit_status.run_after_commit do
# TODO, temporary fix for race condition
UpdatePipelineWorker.new.perform(pipeline.id)
MergeRequests::MergeWhenBuildSucceedsService
.new(pipeline.project, nil).trigger(self)
end
end end
after_transition any => :failed do |commit_status| after_transition any => :failed do |commit_status|
MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status) commit_status.run_after_commit do
MergeRequests::AddTodoWhenBuildFailsService
.new(pipeline.project, nil).execute(self)
end
end end
end end
......
# This module takes care of updating cache columns for Markdown-containing
# fields. Use like this in the body of your class:
#
# include CacheMarkdownField
# cache_markdown_field :foo
# cache_markdown_field :bar
# cache_markdown_field :baz, pipeline: :single_line
#
# Corresponding foo_html, bar_html and baz_html fields should exist.
module CacheMarkdownField
# Knows about the relationship between markdown and html field names, and
# stores the rendering contexts for the latter
class FieldData
extend Forwardable
def initialize
@data = {}
end
def_delegators :@data, :[], :[]=
def_delegator :@data, :keys, :markdown_fields
def html_field(markdown_field)
"#{markdown_field}_html"
end
def html_fields
markdown_fields.map {|field| html_field(field) }
end
end
# Dynamic registries don't really work in Rails as it's not guaranteed that
# every class will be loaded, so hardcode the list.
CACHING_CLASSES = %w[
AbuseReport
Appearance
ApplicationSetting
BroadcastMessage
Issue
Label
MergeRequest
Milestone
Namespace
Note
Project
Release
Snippet
]
def self.caching_classes
CACHING_CLASSES.map(&:constantize)
end
extend ActiveSupport::Concern
included do
cattr_reader :cached_markdown_fields do
FieldData.new
end
# Returns the default Banzai render context for the cached markdown field.
def banzai_render_context(field)
raise ArgumentError.new("Unknown field: #{field.inspect}") unless
cached_markdown_fields.markdown_fields.include?(field)
# Always include a project key, or Banzai complains
project = self.project if self.respond_to?(:project)
context = cached_markdown_fields[field].merge(project: project)
# Banzai is less strict about authors, so don't always have an author key
context[:author] = self.author if self.respond_to?(:author)
context
end
# Allow callers to look up the cache field name, rather than hardcoding it
def markdown_cache_field_for(field)
raise ArgumentError.new("Unknown field: #{field}") unless
cached_markdown_fields.markdown_fields.include?(field)
cached_markdown_fields.html_field(field)
end
# Always exclude _html fields from attributes (including serialization).
# They contain unredacted HTML, which would be a security issue
alias_method :attributes_before_markdown_cache, :attributes
def attributes
attrs = attributes_before_markdown_cache
cached_markdown_fields.html_fields.each do |field|
attrs.delete(field)
end
attrs
end
end
class_methods do
private
# Specify that a field is markdown. Its rendered output will be cached in
# a corresponding _html field. Any custom rendering options may be provided
# as a context.
def cache_markdown_field(markdown_field, context = {})
raise "Add #{self} to CacheMarkdownField::CACHING_CLASSES" unless
CacheMarkdownField::CACHING_CLASSES.include?(self.to_s)
cached_markdown_fields[markdown_field] = context
html_field = cached_markdown_fields.html_field(markdown_field)
cache_method = "#{markdown_field}_cache_refresh".to_sym
invalidation_method = "#{html_field}_invalidated?".to_sym
define_method(cache_method) do
html = Banzai::Renderer.cacheless_render_field(self, markdown_field)
__send__("#{html_field}=", html)
true
end
# The HTML becomes invalid if any dependent fields change. For now, assume
# author and project invalidate the cache in all circumstances.
define_method(invalidation_method) do
changed_fields = changed_attributes.keys
invalidations = changed_fields & [markdown_field.to_s, "author", "project"]
!invalidations.empty?
end
before_save cache_method, if: invalidation_method
end
end
end
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
# #
module Issuable module Issuable
extend ActiveSupport::Concern extend ActiveSupport::Concern
include CacheMarkdownField
include Participable include Participable
include Mentionable include Mentionable
include Subscribable include Subscribable
...@@ -13,6 +14,9 @@ module Issuable ...@@ -13,6 +14,9 @@ module Issuable
include Awardable include Awardable
included do included do
cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :description
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
belongs_to :assignee, class_name: "User" belongs_to :assignee, class_name: "User"
belongs_to :updated_by, class_name: "User" belongs_to :updated_by, class_name: "User"
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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