Commit 54fca951 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into fix-git-hooks-when-creating-file

* upstream/master: (190 commits)
  Remove unnecessary returns / unset variables from the CoffeeScript -> JS conversion.
  update spec
  Change the reply shortcut to focus the field even without a selection.
  use destroy_all
  Remove settings cog from within admin scroll tabs; keep links centered
  add changelog
  remove old project members from project
  add spec replicating validation error
  Fix small typo on new branch button spec
  Improve styling of the new issue message
  Don't capitalize environment name in show page
  Abillity to promote project labels to group labels
  Edited the column header for the environments list from created to updated and added created to environments detail page colum header titles
  Update and pin the `jwt` gem to ~> 1.5.6
  refactor merge request build service
  Update index.md
  Clarify that Auto Deploy requires a public project.
  19164 Add settings dropdown to mobile screens
  cop for gem fetched from a git source
  Add CHANGELOG entry
  ...
parents eb242fc8 40a82435
...@@ -107,7 +107,7 @@ setup-test-env: ...@@ -107,7 +107,7 @@ setup-test-env:
<<: *dedicated-runner <<: *dedicated-runner
stage: prepare stage: prepare
script: script:
- bundle exec rake assets:precompile 2>/dev/null - bundle exec rake gitlab:assets:compile 2>/dev/null
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
artifacts: artifacts:
expire_in: 7d expire_in: 7d
...@@ -271,7 +271,7 @@ rake db:migrate:reset: ...@@ -271,7 +271,7 @@ rake db:migrate:reset:
<<: *use-db <<: *use-db
<<: *dedicated-runner <<: *dedicated-runner
script: script:
- rake db:migrate:reset - bundle exec rake db:migrate:reset
rake db:seed_fu: rake db:seed_fu:
stage: test stage: test
...@@ -302,7 +302,7 @@ teaspoon: ...@@ -302,7 +302,7 @@ teaspoon:
script: script:
- npm install - npm install
- npm link istanbul - npm link istanbul
- rake teaspoon - bundle exec rake teaspoon
artifacts: artifacts:
name: coverage-javascript name: coverage-javascript
expire_in: 31d expire_in: 31d
...@@ -353,10 +353,10 @@ migration paths: ...@@ -353,10 +353,10 @@ migration paths:
- cp config/resque.yml.example config/resque.yml - cp config/resque.yml.example config/resque.yml
- sed -i 's/localhost/redis/g' config/resque.yml - sed -i 's/localhost/redis/g' config/resque.yml
- bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3 - bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3
- rake db:drop db:create db:schema:load db:seed_fu - bundle exec rake db:drop db:create db:schema:load db:seed_fu
- git checkout $CI_BUILD_REF - git checkout $CI_BUILD_REF
- source scripts/prepare_build.sh - source scripts/prepare_build.sh
- rake db:migrate - bundle exec rake db:migrate
coverage: coverage:
stage: post-test stage: post-test
......
...@@ -2,6 +2,17 @@ ...@@ -2,6 +2,17 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 8.16.3 (2017-01-27)
- Add caching of droplab ajax requests. !8725
- Fix access to the wiki code via HTTP when repository feature disabled. !8758
- Revert 3f17f29a. !8785
- Fix race conditions for AuthorizedProjectsWorker.
- Fix autocomplete initial undefined state.
- Fix Error 500 when repositories contain annotated tags pointing to blobs.
- Fix /explore sorting.
- Fixed label dropdown toggle text not correctly updating.
## 8.16.2 (2017-01-25) ## 8.16.2 (2017-01-25)
- allow issue filter bar to be operated with mouse only. !8681 - allow issue filter bar to be operated with mouse only. !8681
...@@ -18,7 +29,7 @@ entry. ...@@ -18,7 +29,7 @@ entry.
- Prevent users from deleting system deploy keys via the project deploy key API. - Prevent users from deleting system deploy keys via the project deploy key API.
- Upgrade omniauth gem to 1.3.2. - Upgrade omniauth gem to 1.3.2.
## 8.16.0 (2017-02-22) ## 8.16.0 (2017-01-22)
- Add LDAP Rake task to rename a provider. !2181 - Add LDAP Rake task to rename a provider. !2181
- Validate label's title length. !5767 (Tomáš Kukrál) - Validate label's title length. !5767 (Tomáš Kukrál)
......
...@@ -286,14 +286,6 @@ request is as follows: ...@@ -286,14 +286,6 @@ request is as follows:
1. For tests that use Capybara or PhantomJS, see this [article on how 1. For tests that use Capybara or PhantomJS, see this [article on how
to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara). to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
The **official merge window** is in the beginning of the month from the 1st to
the 7th day of the month. This is the best time to submit an MR and get
feedback fast. Before this time the GitLab Inc. team is still dealing with work
that is created by the monthly release such as regressions requiring patch
releases. After the 7th it is already getting closer to the release date of the
next version. This means there is less time to fix the issues created by
merging large new features.
Please keep the change in a single MR **as small as possible**. If you want to Please keep the change in a single MR **as small as possible**. If you want to
contribute a large feature think very hard what the minimum viable change is. contribute a large feature think very hard what the minimum viable change is.
Can you split the functionality? Can you only submit the backend/API code? Can Can you split the functionality? Can you only submit the backend/API code? Can
......
...@@ -36,7 +36,7 @@ gem 'omniauth-twitter', '~> 1.2.0' ...@@ -36,7 +36,7 @@ gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.2.0' gem 'omniauth-authentiq', '~> 0.2.0'
gem 'rack-oauth2', '~> 1.2.1' gem 'rack-oauth2', '~> 1.2.1'
gem 'jwt' gem 'jwt', '~> 1.5.6'
# Spam and anti-bot protection # Spam and anti-bot protection
gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails' gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'
...@@ -280,6 +280,7 @@ group :development, :test do ...@@ -280,6 +280,7 @@ group :development, :test do
gem 'rspec-retry', '~> 0.4.5' gem 'rspec-retry', '~> 0.4.5'
gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2' gem 'spinach-rerun-reporter', '~> 0.0.2'
gem 'rspec_profiling'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0' gem 'minitest', '~> 5.7.0'
......
...@@ -379,7 +379,7 @@ GEM ...@@ -379,7 +379,7 @@ GEM
json (1.8.3) json (1.8.3)
json-schema (2.6.2) json-schema (2.6.2)
addressable (~> 2.3.8) addressable (~> 2.3.8)
jwt (1.5.4) jwt (1.5.6)
kaminari (0.17.0) kaminari (0.17.0)
actionpack (>= 3.0.0) actionpack (>= 3.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
...@@ -642,6 +642,11 @@ GEM ...@@ -642,6 +642,11 @@ GEM
rspec-retry (0.4.5) rspec-retry (0.4.5)
rspec-core rspec-core
rspec-support (3.5.0) rspec-support (3.5.0)
rspec_profiling (0.0.4)
activerecord
pg
rails
sqlite3
rubocop (0.46.0) rubocop (0.46.0)
parser (>= 2.3.1.1, < 3.0) parser (>= 2.3.1.1, < 3.0)
powerpack (~> 0.1) powerpack (~> 0.1)
...@@ -743,6 +748,7 @@ GEM ...@@ -743,6 +748,7 @@ GEM
actionpack (>= 4.0) actionpack (>= 4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
sqlite3 (1.3.11)
stackprof (0.2.10) stackprof (0.2.10)
state_machines (0.4.0) state_machines (0.4.0)
state_machines-activemodel (0.4.0) state_machines-activemodel (0.4.0)
...@@ -906,7 +912,7 @@ DEPENDENCIES ...@@ -906,7 +912,7 @@ DEPENDENCIES
jquery-rails (~> 4.1.0) jquery-rails (~> 4.1.0)
jquery-ui-rails (~> 5.0.0) jquery-ui-rails (~> 5.0.0)
json-schema (~> 2.6.2) json-schema (~> 2.6.2)
jwt jwt (~> 1.5.6)
kaminari (~> 0.17.0) kaminari (~> 0.17.0)
knapsack (~> 1.11.0) knapsack (~> 1.11.0)
kubeclient (~> 2.2.0) kubeclient (~> 2.2.0)
...@@ -965,6 +971,7 @@ DEPENDENCIES ...@@ -965,6 +971,7 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.5.0) rspec-rails (~> 3.5.0)
rspec-retry (~> 0.4.5) rspec-retry (~> 0.4.5)
rspec_profiling
rubocop (~> 0.46.0) rubocop (~> 0.46.0)
rubocop-rspec (~> 1.9.1) rubocop-rspec (~> 1.9.1)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
...@@ -1015,4 +1022,4 @@ DEPENDENCIES ...@@ -1015,4 +1022,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.13.7 1.14.2
...@@ -12,106 +12,54 @@ etc.). ...@@ -12,106 +12,54 @@ etc.).
## Common actions ## Common actions
### Issue team ### Issue triaging
- Looks for issues without [workflow labels](#how-we-handle-issues) and triages Our issue triage policies are [described in our handbook]. You are very welcome
issue to help the GitLab team triage issues. We also organize [issue bash events] once
- Closes invalid issues with a comment (duplicates, every quarter.
[fixed in newer version](#issue-fixed-in-newer-version),
[issue report for old version](#issue-report-for-old-version), not a problem
in GitLab, etc.)
- Asks for feedback from issue reporter
([invalid issue reports](#improperly-formatted-issue),
[format code](#code-format), etc.)
- Monitors all issues for feedback (but especially ones commented on since
automatically watching them)
- Closes issues with no feedback from the reporter for two weeks
### Merge marshall & merge request coach
- Responds to merge requests the issue team mentions them in and monitors for
new merge requests
- Provides feedback to the merge request submitter to improve the merge request
(style, tests, etc.)
- Mark merge requests `Ready for Merge` when they meet the
[contribution acceptance criteria]
- Mention developer(s) based on the
[list of members and their specialities][team]
- Closes merge requests with no feedback from the reporter for two weeks
## Priorities of the issue team
1. Mentioning people (critical)
1. Workflow labels (normal)
1. Functional labels (minor)
1. Assigning issues (avoid if possible)
## Mentioning people
The most important thing is making sure valid issues receive feedback from the The most important thing is making sure valid issues receive feedback from the
development team. Therefore the priority is mentioning developers that can help development team. Therefore the priority is mentioning developers that can help
on those issues. Please select someone with relevant experience from on those issues. Please select someone with relevant experience from
[GitLab core team][core-team]. If there is nobody mentioned with that expertise [GitLab team][team]. If there is nobody mentioned with that expertise
look in the commit history for the affected files to find someone. Avoid look in the commit history for the affected files to find someone. Avoid
mentioning the lead developer, this is the person that is least likely to give a mentioning the lead developer, this is the person that is least likely to give a
timely response. If the involvement of the lead developer is needed the other timely response. If the involvement of the lead developer is needed the other
core team members will mention this person. core team members will mention this person.
## Workflow labels [described in our handbook]: https://about.gitlab.com/handbook/engineering/issues/issue-triage-policies/
[issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815
Workflow labels are purposely not very detailed since that would be hard to keep ### Merge request coaching
updated as you would need to re-evaluate them after every comment. We optionally
use functional labels on demand when we want to group related issues to get an
overview (for example all issues related to RVM, to tackle them in one go) and
to add details to the issue.
- ~"Awaiting Feedback" Feedback pending from the reporter Several people from the [GitLab team][team] are helping community members to get
- ~UX needs help from a UX designer their contributions accepted by meeting our [Definition of done][CONTRIBUTING.md#definition-of-done].
- ~Frontend needs help from a Front-end engineer. Please follow the
["Implement design & UI elements" guidelines].
- ~"Accepting Merge Requests" is a low priority, well-defined issue that we
encourage people to contribute to. Not exclusive with other labels.
- ~"feature proposal" is a proposal for a new feature for GitLab. People are encouraged to vote
in support or comment for further detail. Do not use `feature request`.
- ~bug is an issue reporting undesirable or incorrect behavior.
- ~customer is an issue reported by enterprise subscribers. This label should
be accompanied by *bug* or *feature proposal* labels.
Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label. What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/.
## Workflow labels
## Functional labels Labelling issues is described in the [GitLab Inc engineering workflow].
These labels describe what development specialities are involved such as: `CI`, [GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
`Core`, `Documentation`, `Frontend`, `Issues`, `Merge Requests`, `Omnibus`,
`Release`, `Repository`, `UX`.
## Assigning issues ## Assigning issues
If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover. If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover.
## Label colors
- Light orange `#fef2c0`: workflow labels for issue team members (awaiting
feedback, awaiting confirmation of fix)
- Bright orange `#eb6420`: workflow labels for core team members (attached MR,
awaiting developer action/feedback)
- Light blue `#82C5FF`: functional labels
- Green labels `#009800`: issues that can generally be ignored. For example,
issues given the following labels normally can be closed immediately:
- Support (see copy & paste response:
[Support requests and configuration questions](#support-requests-and-configuration-questions)
## Be kind ## Be kind
Be kind to people trying to contribute. Be aware that people may be a non-native Be kind to people trying to contribute. Be aware that people may be a non-native
English speaker, they might not understand things or they might be very English speaker, they might not understand things or they might be very
sensitive as to how you word things. Use Emoji to express your feelings (heart, sensitive as to how you word things. Use Emoji to express your feelings (heart,
star, smile, etc.). Some good tips about giving feedback to merge requests is in star, smile, etc.). Some good tips about code reviews can be found in our
the [Thoughtbot code review guide]. [Code Review Guidelines].
[Code Review Guidelines]: https://docs.gitlab.com/ce/development/code_review.html
## Feature Freeze ## Feature Freeze
5 working days before the 22nd the stable branches for the upcoming release will On the 7th of each month, the stable branches for the upcoming release will
be frozen for major changes. Merge requests may still be merged into master be frozen for major changes. Merge requests may still be merged into master
during this period. By freezing the stable branches prior to a release there's during this period. By freezing the stable branches prior to a release there's
no need to worry about last minute merge requests potentially breaking a lot of no need to worry about last minute merge requests potentially breaking a lot of
...@@ -120,10 +68,9 @@ things. ...@@ -120,10 +68,9 @@ things.
What is considered to be a major change is determined on a case by case basis as What is considered to be a major change is determined on a case by case basis as
this definition depends very much on the context of changes. For example, a 5 this definition depends very much on the context of changes. For example, a 5
line change might have a big impact on the entire application. Ultimately the line change might have a big impact on the entire application. Ultimately the
decision will be made by those reviewing a merge request and the release decision will be made by the maintainers and the release managers.
manager.
During the feature freeze all merge requests that are meant to go into the next During the feature freeze all merge requests that are meant to go into the upcoming
release should have the correct milestone assigned _and_ have the label release should have the correct milestone assigned _and_ have the label
~"Pick into Stable" set. Merge requests without a milestone and this label will ~"Pick into Stable" set. Merge requests without a milestone and this label will
not be merged into any stable branches. not be merged into any stable branches.
...@@ -189,7 +136,6 @@ prevent duplication with the GitLab.com issue tracker. ...@@ -189,7 +136,6 @@ prevent duplication with the GitLab.com issue tracker.
Since this is an older issue I'll be closing this for now. If you think this is Since this is an older issue I'll be closing this for now. If you think this is
still an issue I encourage you to open it on the \[GitLab.com issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues). still an issue I encourage you to open it on the \[GitLab.com issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues).
[core-team]: https://about.gitlab.com/core-team/
[team]: https://about.gitlab.com/team/ [team]: https://about.gitlab.com/team/
[contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria [contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria
["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements ["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements
......
...@@ -84,7 +84,6 @@ ...@@ -84,7 +84,6 @@
var $sidebarGutterToggle = $('.js-sidebar-toggle'); var $sidebarGutterToggle = $('.js-sidebar-toggle');
var $flash = $('.flash-container'); var $flash = $('.flash-container');
var bootstrapBreakpoint = bp.getBreakpointSize(); var bootstrapBreakpoint = bp.getBreakpointSize();
var checkInitialSidebarSize;
var fitSidebarForSize; var fitSidebarForSize;
// Set the default path for all cookies to GitLab's root directory // Set the default path for all cookies to GitLab's root directory
...@@ -246,19 +245,11 @@ ...@@ -246,19 +245,11 @@
return $document.trigger('breakpoint:change', [bootstrapBreakpoint]); return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
} }
}; };
checkInitialSidebarSize = function () {
bootstrapBreakpoint = bp.getBreakpointSize();
if (bootstrapBreakpoint === 'xs' || 'sm') {
return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
}
};
$window.off('resize.app').on('resize.app', function () { $window.off('resize.app').on('resize.app', function () {
return fitSidebarForSize(); return fitSidebarForSize();
}); });
gl.awardsHandler = new AwardsHandler(); gl.awardsHandler = new AwardsHandler();
checkInitialSidebarSize();
new Aside(); new Aside();
// bind sidebar events // bind sidebar events
new gl.Sidebar(); new gl.Sidebar();
}); });
......
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */ /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */
/* global autosize */ /* global autosize */
/*= require jquery.ba-resize */
/*= require autosize */ /*= require autosize */
(function() { (function() {
......
/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */ /* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */
(function(w) { (function(w) {
$(function() { $(function() {
var toggleContainer = function(container, /* optional */toggleState) {
var $container = $(container);
$container
.find('.js-toggle-button .fa')
.toggleClass('fa-chevron-up', toggleState)
.toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
$container
.find('.js-toggle-content')
.toggle(toggleState);
};
// Toggle button. Show/hide content inside parent container. // Toggle button. Show/hide content inside parent container.
// Button does not change visibility. If button has icon - it changes chevron style. // Button does not change visibility. If button has icon - it changes chevron style.
// //
...@@ -10,14 +23,7 @@ ...@@ -10,14 +23,7 @@
// //
$('body').on('click', '.js-toggle-button', function(e) { $('body').on('click', '.js-toggle-button', function(e) {
e.preventDefault(); e.preventDefault();
$(this) toggleContainer($(this).closest('.js-toggle-container'));
.find('.fa')
.toggleClass('fa-chevron-down fa-chevron-up')
.end()
.closest('.js-toggle-container')
.find('.js-toggle-content')
.toggle()
;
}); });
// If we're accessing a permalink, ensure it is not inside a // If we're accessing a permalink, ensure it is not inside a
...@@ -26,8 +32,8 @@ ...@@ -26,8 +32,8 @@
var anchor = hash && document.getElementById(hash); var anchor = hash && document.getElementById(hash);
var container = anchor && $(anchor).closest('.js-toggle-container'); var container = anchor && $(anchor).closest('.js-toggle-container');
if (container && container.find('.js-toggle-content').is(':hidden')) { if (container) {
container.find('.js-toggle-button').trigger('click'); toggleContainer(container, true);
anchor.scrollIntoView(); anchor.scrollIntoView();
} }
}); });
......
...@@ -29,6 +29,12 @@ ...@@ -29,6 +29,12 @@
watch: { watch: {
detail: { detail: {
handler () { handler () {
if (this.issue.id !== this.detail.issue.id) {
$('.js-issue-board-sidebar', this.$el).each((i, el) => {
$(el).data('glDropdown').clearMenu();
});
}
this.issue = this.detail.issue; this.issue = this.detail.issue;
}, },
deep: true deep: true
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
(function() { (function() {
this.CommitsList = (function() { this.CommitsList = (function() {
function CommitsList() {} var CommitsList = {};
CommitsList.timer = null; CommitsList.timer = null;
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
}); });
this.content = $("#commits-list"); this.content = $("#commits-list");
this.searchField = $("#commits-search"); this.searchField = $("#commits-search");
this.lastSearch = this.searchField.val();
return this.initSearch(); return this.initSearch();
}; };
...@@ -37,6 +38,7 @@ ...@@ -37,6 +38,7 @@
var commitsUrl, form, search; var commitsUrl, form, search;
form = $(".commits-search-form"); form = $(".commits-search-form");
search = CommitsList.searchField.val(); search = CommitsList.searchField.val();
if (search === CommitsList.lastSearch) return;
commitsUrl = form.attr("action") + '?' + form.serialize(); commitsUrl = form.attr("action") + '?' + form.serialize();
CommitsList.content.fadeTo('fast', 0.5); CommitsList.content.fadeTo('fast', 0.5);
return $.ajax({ return $.ajax({
...@@ -47,12 +49,16 @@ ...@@ -47,12 +49,16 @@
return CommitsList.content.fadeTo('fast', 1.0); return CommitsList.content.fadeTo('fast', 1.0);
}, },
success: function(data) { success: function(data) {
CommitsList.lastSearch = search;
CommitsList.content.html(data.html); CommitsList.content.html(data.html);
return history.replaceState({ return history.replaceState({
page: commitsUrl page: commitsUrl
// Change url so if user reload a page - search results are saved // Change url so if user reload a page - search results are saved
}, document.title, commitsUrl); }, document.title, commitsUrl);
}, },
error: function() {
CommitsList.lastSearch = null;
},
dataType: "json" dataType: "json"
}); });
}; };
......
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
//= require lib/utils/url_utility */
(() => { (() => {
const UNFOLD_COUNT = 20; const UNFOLD_COUNT = 20;
...@@ -104,11 +106,11 @@ ...@@ -104,11 +106,11 @@
} }
highlighSelectedLine() { highlighSelectedLine() {
const hash = gl.utils.getLocationHash();
const $diffFiles = $('.diff-file'); const $diffFiles = $('.diff-file');
$diffFiles.find('.hll').removeClass('hll'); $diffFiles.find('.hll').removeClass('hll');
if (window.location.hash !== '') { if (hash) {
const hash = window.location.hash.replace('#', '');
$diffFiles $diffFiles
.find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`) .find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`)
.addClass('hll'); .addClass('hll');
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
/* global ShortcutsIssuable */ /* global ShortcutsIssuable */
/* global ZenMode */ /* global ZenMode */
/* global Milestone */ /* global Milestone */
/* global GLForm */
/* global IssuableForm */ /* global IssuableForm */
/* global LabelsSelect */ /* global LabelsSelect */
/* global MilestoneSelect */ /* global MilestoneSelect */
...@@ -64,17 +63,6 @@ ...@@ -64,17 +63,6 @@
new UsernameValidator(); new UsernameValidator();
new ActiveTabMemoizer(); new ActiveTabMemoizer();
break; break;
case 'sessions:create':
if (!gon.u2f) break;
window.gl.u2fAuthenticate = new gl.U2FAuthenticate(
$("#js-authenticate-u2f"),
'#js-login-u2f-form',
gon.u2f,
document.querySelector('#js-login-2fa-device'),
document.querySelector('.js-2fa-form'),
);
window.gl.u2fAuthenticate.start();
break;
case 'projects:boards:show': case 'projects:boards:show':
case 'projects:boards:index': case 'projects:boards:index':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
...@@ -110,7 +98,7 @@ ...@@ -110,7 +98,7 @@
case 'projects:milestones:edit': case 'projects:milestones:edit':
new ZenMode(); new ZenMode();
new gl.DueDateSelectors(); new gl.DueDateSelectors();
new GLForm($('.milestone-form')); new gl.GLForm($('.milestone-form'));
break; break;
case 'groups:milestones:new': case 'groups:milestones:new':
new ZenMode(); new ZenMode();
...@@ -121,7 +109,7 @@ ...@@ -121,7 +109,7 @@
case 'projects:issues:new': case 'projects:issues:new':
case 'projects:issues:edit': case 'projects:issues:edit':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new GLForm($('.issue-form')); new gl.GLForm($('.issue-form'));
new IssuableForm($('.issue-form')); new IssuableForm($('.issue-form'));
new LabelsSelect(); new LabelsSelect();
new MilestoneSelect(); new MilestoneSelect();
...@@ -131,7 +119,7 @@ ...@@ -131,7 +119,7 @@
case 'projects:merge_requests:edit': case 'projects:merge_requests:edit':
new gl.Diff(); new gl.Diff();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new GLForm($('.merge-request-form')); new gl.GLForm($('.merge-request-form'));
new IssuableForm($('.merge-request-form')); new IssuableForm($('.merge-request-form'));
new LabelsSelect(); new LabelsSelect();
new MilestoneSelect(); new MilestoneSelect();
...@@ -139,11 +127,11 @@ ...@@ -139,11 +127,11 @@
break; break;
case 'projects:tags:new': case 'projects:tags:new':
new ZenMode(); new ZenMode();
new GLForm($('.tag-form')); new gl.GLForm($('.tag-form'));
break; break;
case 'projects:releases:edit': case 'projects:releases:edit':
new ZenMode(); new ZenMode();
new GLForm($('.release-form')); new gl.GLForm($('.release-form'));
break; break;
case 'projects:merge_requests:show': case 'projects:merge_requests:show':
new gl.Diff(); new gl.Diff();
...@@ -280,6 +268,17 @@ ...@@ -280,6 +268,17 @@
break; break;
} }
switch (path.first()) { switch (path.first()) {
case 'sessions':
case 'omniauth_callbacks':
if (!gon.u2f) break;
gl.u2fAuthenticate = new gl.U2FAuthenticate(
$('#js-authenticate-u2f'),
'#js-login-u2f-form',
gon.u2f,
document.querySelector('#js-login-2fa-device'),
document.querySelector('.js-2fa-form'),
);
gl.u2fAuthenticate.start();
case 'admin': case 'admin':
new Admin(); new Admin();
switch (path[1]) { switch (path[1]) {
...@@ -332,7 +331,7 @@ ...@@ -332,7 +331,7 @@
new gl.Wikis(); new gl.Wikis();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new ZenMode(); new ZenMode();
new GLForm($('.wiki-form')); new gl.GLForm($('.wiki-form'));
break; break;
case 'snippets': case 'snippets':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
...@@ -357,7 +356,7 @@ ...@@ -357,7 +356,7 @@
} }
// If we haven't installed a custom shortcut handler, install the default one // If we haven't installed a custom shortcut handler, install the default one
if (!shortcut_handler) { if (!shortcut_handler) {
return new Shortcuts(); new Shortcuts();
} }
}; };
......
...@@ -9,6 +9,7 @@ require('../window')(function(w){ ...@@ -9,6 +9,7 @@ require('../window')(function(w){
w.droplabAjax = { w.droplabAjax = {
_loadUrlData: function _loadUrlData(url) { _loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest; var xhr = new XMLHttpRequest;
xhr.open('GET', url, true); xhr.open('GET', url, true);
...@@ -16,6 +17,7 @@ require('../window')(function(w){ ...@@ -16,6 +17,7 @@ require('../window')(function(w){
if(xhr.readyState === XMLHttpRequest.DONE) { if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) { if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText); var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data); return resolve(data);
} else { } else {
return reject([xhr.responseText, xhr.status]); return reject([xhr.responseText, xhr.status]);
...@@ -26,8 +28,21 @@ require('../window')(function(w){ ...@@ -26,8 +28,21 @@ require('../window')(function(w){
}); });
}, },
_loadData: function _loadData(data, config, self) {
if (config.loadingTemplate) {
var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate;
}
}
self.hook.list[config.method].call(self.hook.list, data);
},
init: function init(hook) { init: function init(hook) {
var self = this; var self = this;
self.cache = self.cache || {};
var config = hook.config.droplabAjax; var config = hook.config.droplabAjax;
this.hook = hook; this.hook = hook;
...@@ -50,22 +65,16 @@ require('../window')(function(w){ ...@@ -50,22 +65,16 @@ require('../window')(function(w){
dynamicList.outerHTML = loadingTemplate.outerHTML; dynamicList.outerHTML = loadingTemplate.outerHTML;
} }
if (self.cache[config.endpoint]) {
self._loadData(self.cache[config.endpoint], config, self);
} else {
this._loadUrlData(config.endpoint) this._loadUrlData(config.endpoint)
.then(function(d) { .then(function(d) {
if (config.loadingTemplate) { self._loadData(d, config, self);
var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate;
}
}
if (!self.hook.list.hidden) {
self.hook.list[config.method].call(self.hook.list, d);
}
}).catch(function(e) { }).catch(function(e) {
throw new droplabAjaxException(e.message || e); throw new droplabAjaxException(e.message || e);
}); });
}
}, },
destroy: function() { destroy: function() {
......
...@@ -72,7 +72,41 @@ require('../window')(function(w){ ...@@ -72,7 +72,41 @@ require('../window')(function(w){
var params = config.params || {}; var params = config.params || {};
params[config.searchKey] = searchValue; params[config.searchKey] = searchValue;
var self = this; var self = this;
this._loadUrlData(config.endpoint + this.buildParams(params)).then(function(data) { self.cache = self.cache || {};
var url = config.endpoint + this.buildParams(params);
var urlCachedData = self.cache[url];
if (urlCachedData) {
self._loadData(urlCachedData, config, self);
} else {
this._loadUrlData(url)
.then(function(data) {
self._loadData(data, config, self);
});
}
},
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
if (config.loadingTemplate && self.hook.list.data === undefined || if (config.loadingTemplate && self.hook.list.data === undefined ||
self.hook.list.data.length === 0) { self.hook.list.data.length === 0) {
const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]'); const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
...@@ -94,25 +128,6 @@ require('../window')(function(w){ ...@@ -94,25 +128,6 @@ require('../window')(function(w){
} }
self.notLoading(); self.notLoading();
self.hook.list.currentIndex = 0; self.hook.list.currentIndex = 0;
});
},
_loadUrlData: function _loadUrlData(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
}, },
buildParams: function(params) { buildParams: function(params) {
......
...@@ -182,7 +182,7 @@ ...@@ -182,7 +182,7 @@
<th class="environments-deploy">Last deployment</th> <th class="environments-deploy">Last deployment</th>
<th class="environments-build">Build</th> <th class="environments-build">Build</th>
<th class="environments-commit">Commit</th> <th class="environments-commit">Commit</th>
<th class="environments-date">Created</th> <th class="environments-date">Updated</th>
<th class="hidden-xs environments-actions"></th> <th class="hidden-xs environments-actions"></th>
</tr> </tr>
</thead> </thead>
......
...@@ -39,8 +39,15 @@ ...@@ -39,8 +39,15 @@
getSearchInput() { getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input); const query = gl.DropdownUtils.getSearchInput(this.input);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query); const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
let value = lastToken.value || '';
return lastToken.value || ''; // Removes the first character if it is a quotation so that we can search
// with multiple words
if (value[0] === '"' || value[0] === '\'') {
value = value.slice(1);
}
return value;
} }
init() { init() {
......
...@@ -28,7 +28,12 @@ ...@@ -28,7 +28,12 @@
if (lastToken !== searchToken) { if (lastToken !== searchToken) {
const title = updatedItem.title.toLowerCase(); const title = updatedItem.title.toLowerCase();
let value = lastToken.value.toLowerCase(); let value = lastToken.value.toLowerCase();
value = value.replace(/"(.*?)"/g, str => str.slice(1).slice(0, -1));
// Removes the first character if it is a quotation so that we can search
// with multiple words
if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
value = value.slice(1);
}
// Eg. filterSymbol = ~ for labels // Eg. filterSymbol = ~ for labels
const matchWithoutSymbol = lastToken.symbol === filterSymbol && title.indexOf(value) !== -1; const matchWithoutSymbol = lastToken.symbol === filterSymbol && title.indexOf(value) !== -1;
...@@ -83,8 +88,9 @@ ...@@ -83,8 +88,9 @@
const selectionStart = input.selectionStart; const selectionStart = input.selectionStart;
let inputValue = input.value; let inputValue = input.value;
// Replace all spaces inside quote marks with underscores // Replace all spaces inside quote marks with underscores
// (will continue to match entire string until an end quote is found if any)
// This helps with matching the beginning & end of a token:key // This helps with matching the beginning & end of a token:key
inputValue = inputValue.replace(/("(.*?)"|:\s+)/g, str => str.replace(/\s/g, '_')); inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_'));
// Get the right position for the word selected // Get the right position for the word selected
// Regex matches first space // Regex matches first space
......
...@@ -196,7 +196,8 @@ ...@@ -196,7 +196,8 @@
}); });
if (searchToken) { if (searchToken) {
paths.push(`search=${encodeURIComponent(searchToken)}`); const sanitized = searchToken.split(' ').map(t => encodeURIComponent(t)).join('+');
paths.push(`search=${sanitized}`);
} }
Turbolinks.visit(`?scope=all&utf8=✓&${paths.join('&')}`); Turbolinks.visit(`?scope=all&utf8=✓&${paths.join('&')}`);
......
...@@ -21,6 +21,15 @@ ...@@ -21,6 +21,15 @@
symbol: '~', symbol: '~',
}]; }];
const alternativeTokenKeys = [{
key: 'label',
type: 'string',
param: 'name',
symbol: '~',
}];
const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
const conditions = [{ const conditions = [{
url: 'assignee_id=0', url: 'assignee_id=0',
tokenKey: 'assignee', tokenKey: 'assignee',
...@@ -44,6 +53,10 @@ ...@@ -44,6 +53,10 @@
return tokenKeys; return tokenKeys;
} }
static getAlternatives() {
return alternativeTokenKeys;
}
static getConditions() { static getConditions() {
return conditions; return conditions;
} }
...@@ -57,7 +70,7 @@ ...@@ -57,7 +70,7 @@
} }
static searchByKeyParam(keyParam) { static searchByKeyParam(keyParam) {
return tokenKeys.find((tokenKey) => { return tokenKeysWithAlternative.find((tokenKey) => {
let tokenKeyParam = tokenKey.key; let tokenKeyParam = tokenKey.key;
if (tokenKey.param) { if (tokenKey.param) {
......
...@@ -512,12 +512,17 @@ ...@@ -512,12 +512,17 @@
// Append the menu into the dropdown // Append the menu into the dropdown
GitLabDropdown.prototype.appendMenu = function(html) { GitLabDropdown.prototype.appendMenu = function(html) {
return this.clearMenu().append(html);
};
GitLabDropdown.prototype.clearMenu = function() {
var selector; var selector;
selector = '.dropdown-content'; selector = '.dropdown-content';
if (this.dropdown.find(".dropdown-toggle-page").length) { if (this.dropdown.find(".dropdown-toggle-page").length) {
selector = ".dropdown-page-one .dropdown-content"; selector = ".dropdown-page-one .dropdown-content";
} }
return $(selector, this.dropdown).empty().append(html);
return $(selector, this.dropdown).empty();
}; };
GitLabDropdown.prototype.renderItem = function(data, group, index) { GitLabDropdown.prototype.renderItem = function(data, group, index) {
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */
/* global GitLab */
/* global DropzoneInput */
/* global autosize */
(function() {
this.GLForm = (function() {
function GLForm(form) {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
// Before we start, we should clean up any previous data for this form
this.destroy();
// Setup the form
this.setupForm();
this.form.data('gl-form', this);
}
GLForm.prototype.destroy = function() {
// Clean form listeners
this.clearEventListeners();
return this.form.data('gl-form', null);
};
GLForm.prototype.setupForm = function() {
var isNewForm;
isNewForm = this.form.is(':not(.gfm-form)');
this.form.removeClass('js-new-note-form');
if (isNewForm) {
this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
new DropzoneInput(this.form);
autosize(this.textarea);
// form and textarea event listeners
this.addEventListeners();
}
gl.text.init(this.form);
// hide discard button
this.form.find('.js-note-discard').hide();
return this.form.show();
};
GLForm.prototype.clearEventListeners = function() {
this.textarea.off('focus');
this.textarea.off('blur');
return gl.text.removeListeners(this.form);
};
GLForm.prototype.addEventListeners = function() {
this.textarea.on('focus', function() {
return $(this).closest('.md-area').addClass('is-focused');
});
return this.textarea.on('blur', function() {
return $(this).closest('.md-area').removeClass('is-focused');
});
};
return GLForm;
})();
}).call(this);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */
/* global GitLab */
/* global DropzoneInput */
/* global autosize */
(() => {
const global = window.gl || (window.gl = {});
function GLForm(form) {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
// Before we start, we should clean up any previous data for this form
this.destroy();
// Setup the form
this.setupForm();
this.form.data('gl-form', this);
}
GLForm.prototype.destroy = function() {
// Clean form listeners
this.clearEventListeners();
return this.form.data('gl-form', null);
};
GLForm.prototype.setupForm = function() {
var isNewForm;
isNewForm = this.form.is(':not(.gfm-form)');
this.form.removeClass('js-new-note-form');
if (isNewForm) {
this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
new DropzoneInput(this.form);
autosize(this.textarea);
// form and textarea event listeners
this.addEventListeners();
}
gl.text.init(this.form);
// hide discard button
this.form.find('.js-note-discard').hide();
this.form.show();
if (this.isAutosizeable) this.setupAutosize();
};
GLForm.prototype.setupAutosize = function () {
this.textarea.off('autosize:resized')
.on('autosize:resized', this.setHeightData.bind(this));
this.textarea.off('mouseup.autosize')
.on('mouseup.autosize', this.destroyAutosize.bind(this));
setTimeout(() => {
autosize(this.textarea);
this.textarea.css('resize', 'vertical');
}, 0);
};
GLForm.prototype.setHeightData = function () {
this.textarea.data('height', this.textarea.outerHeight());
};
GLForm.prototype.destroyAutosize = function () {
const outerHeight = this.textarea.outerHeight();
if (this.textarea.data('height') === outerHeight) return;
autosize.destroy(this.textarea);
this.textarea.data('height', outerHeight);
this.textarea.outerHeight(outerHeight);
this.textarea.css('max-height', window.outerHeight);
};
GLForm.prototype.clearEventListeners = function() {
this.textarea.off('focus');
this.textarea.off('blur');
return gl.text.removeListeners(this.form);
};
GLForm.prototype.addEventListeners = function() {
this.textarea.on('focus', function() {
return $(this).closest('.md-area').addClass('is-focused');
});
return this.textarea.on('blur', function() {
return $(this).closest('.md-area').removeClass('is-focused');
});
};
global.GLForm = GLForm;
})();
...@@ -59,11 +59,11 @@ ...@@ -59,11 +59,11 @@
} else { } else {
avatar = gon.default_avatar_url; avatar = gon.default_avatar_url;
} }
return "<div class='group-result'> <div class='group-name'>" + group.name + "</div> <div class='group-path'>" + group.path + "</div> </div>"; return "<div class='group-result'> <div class='group-name'>" + group.full_name + "</div> <div class='group-path'>" + group.full_path + "</div> </div>";
}; };
GroupsSelect.prototype.formatSelection = function(group) { GroupsSelect.prototype.formatSelection = function(group) {
return group.name; return group.full_name;
}; };
return GroupsSelect; return GroupsSelect;
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */
/* global UsersSelect */ /* global UsersSelect */
/* global Cookies */
/* global bp */
(function() { (function() {
this.IssuableContext = (function() { this.IssuableContext = (function() {
...@@ -37,6 +39,13 @@ ...@@ -37,6 +39,13 @@
}, 0); }, 0);
} }
}); });
window.addEventListener('beforeunload', function() {
// collapsed_gutter cookie hides the sidebar
var bpBreakpoint = bp.getBreakpointSize();
if (bpBreakpoint === 'xs' || bpBreakpoint === 'sm') {
Cookies.set('collapsed_gutter', true);
}
});
$(".right-sidebar").niceScroll(); $(".right-sidebar").niceScroll();
} }
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels'); this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels');
this.otherLabels = otherLabels || $('.js-other-labels'); this.otherLabels = otherLabels || $('.js-other-labels');
this.errorMessage = 'Unable to update label prioritization at this time'; this.errorMessage = 'Unable to update label prioritization at this time';
this.emptyState = document.querySelector('#js-priority-labels-empty-state');
this.prioritizedLabels.sortable({ this.prioritizedLabels.sortable({
items: 'li', items: 'li',
placeholder: 'list-placeholder', placeholder: 'list-placeholder',
...@@ -29,7 +30,12 @@ ...@@ -29,7 +30,12 @@
const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`); const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`);
$tooltip.tooltip('destroy'); $tooltip.tooltip('destroy');
return _this.toggleLabelPriority($label, action); _this.toggleLabelPriority($label, action);
_this.toggleEmptyState($label, $btn, action);
}
toggleEmptyState($label, $btn, action) {
this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li'));
} }
toggleLabelPriority($label, action, persistState) { toggleLabelPriority($label, action, persistState) {
......
/*= require ace-rails-ap */ /*= require ace/ace */
/*= require ace/ext-searchbox */ /*= require ace/ext-searchbox */
/*= require ./ace/ace_config_paths */
<%
ace_gem_path = Bundler.rubygems.find_name('ace-rails-ap').first.full_gem_path
ace_workers = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/worker-*.js'].sort.map do |file|
File.basename(file, '.js').sub(/^worker-/, '')
end
ace_modes = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/mode-*.js'].sort.map do |file|
File.basename(file, '.js').sub(/^mode-/, '')
end
%>
(function() {
window.gon = window.gon || {};
var basePath = (window.gon.relative_url_root || '').replace(/\/$/, '') + '/assets/ace';
ace.config.set('basePath', basePath);
// configure paths for all worker modules
<% ace_workers.each do |worker| %>
ace.config.setModuleUrl('ace/mode/<%= worker %>_worker', basePath + '/worker-<%= worker %>.js');
<% end %>
// configure paths for all mode modules
<% ace_modes.each do |mode| %>
ace.config.setModuleUrl('ace/mode/<%= mode %>', basePath + '/mode-<%= mode %>.js');
<% end %>
})();
...@@ -162,6 +162,7 @@ ...@@ -162,6 +162,7 @@
w.gl.utils.getSelectedFragment = () => { w.gl.utils.getSelectedFragment = () => {
const selection = window.getSelection(); const selection = window.getSelection();
if (selection.rangeCount === 0) return null;
const documentFragment = selection.getRangeAt(0).cloneContents(); const documentFragment = selection.getRangeAt(0).cloneContents();
if (documentFragment.textContent.length === 0) return null; if (documentFragment.textContent.length === 0) return null;
......
...@@ -74,8 +74,9 @@ ...@@ -74,8 +74,9 @@
// If not done this way, the line number anchor will sometimes keep its // If not done this way, the line number anchor will sometimes keep its
// active state even when the event is cancelled, resulting in an ugly border // active state even when the event is cancelled, resulting in an ugly border
// around the link and/or a persisted underline text decoration. // around the link and/or a persisted underline text decoration.
return $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) { $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
return event.preventDefault(); event.preventDefault();
event.stopPropagation();
}); });
}; };
......
/* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape */ /* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape */
/* global Flash */ /* global Flash */
/* global GLForm */
/* global Autosave */ /* global Autosave */
/* global ResolveService */ /* global ResolveService */
/* global mrRefreshWidgetUrl */ /* global mrRefreshWidgetUrl */
...@@ -420,7 +419,7 @@ ...@@ -420,7 +419,7 @@
Notes.prototype.setupNoteForm = function(form) { Notes.prototype.setupNoteForm = function(form) {
var textarea; var textarea;
new GLForm(form); new gl.GLForm(form);
textarea = form.find(".js-note-text"); textarea = form.find(".js-note-text");
return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]); return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]);
}; };
...@@ -884,7 +883,7 @@ ...@@ -884,7 +883,7 @@
var targetId = $originalContentEl.data('target-id'); var targetId = $originalContentEl.data('target-id');
var targetType = $originalContentEl.data('target-type'); var targetType = $originalContentEl.data('target-type');
new GLForm($editForm.find('form')); new gl.GLForm($editForm.find('form'));
$editForm.find('form') $editForm.find('form')
.attr('action', postUrl) .attr('action', postUrl)
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
} }
onSubmitForm(e) { onSubmitForm(e) {
e.preventDefault();
return this.saveForm(); return this.saveForm();
} }
......
...@@ -13,12 +13,12 @@ ...@@ -13,12 +13,12 @@
filterable: true, filterable: true,
fieldName: 'group_id', fieldName: 'group_id',
search: { search: {
fields: ['name'] fields: ['full_name']
}, },
data: function(term, callback) { data: function(term, callback) {
return Api.groups(term, {}, function(data) { return Api.groups(term, {}, function(data) {
data.unshift({ data.unshift({
name: 'Any' full_name: 'Any'
}); });
data.splice(1, 0, 'divider'); data.splice(1, 0, 'divider');
return callback(data); return callback(data);
...@@ -28,10 +28,10 @@ ...@@ -28,10 +28,10 @@
return obj.id; return obj.id;
}, },
text: function(obj) { text: function(obj) {
return obj.name; return obj.full_name;
}, },
toggleLabel: function(obj) { toggleLabel: function(obj) {
return ($groupDropdown.data('default-label')) + " " + obj.name; return ($groupDropdown.data('default-label')) + " " + obj.full_name;
}, },
clicked: (function(_this) { clicked: (function(_this) {
return function() { return function() {
......
...@@ -39,17 +39,20 @@ ...@@ -39,17 +39,20 @@
} }
ShortcutsIssuable.prototype.replyWithSelectedText = function() { ShortcutsIssuable.prototype.replyWithSelectedText = function() {
var quote, replyField, documentFragment, selected, separator; var quote, documentFragment, selected, separator;
var replyField = $('.js-main-target-form #note_note');
documentFragment = window.gl.utils.getSelectedFragment(); documentFragment = window.gl.utils.getSelectedFragment();
if (!documentFragment) return; if (!documentFragment) {
replyField.focus();
return;
}
// If the documentFragment contains more than just Markdown, don't copy as GFM. // If the documentFragment contains more than just Markdown, don't copy as GFM.
if (documentFragment.querySelector('.md, .wiki')) return; if (documentFragment.querySelector('.md, .wiki')) return;
selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment); selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment);
replyField = $('.js-main-target-form #note_note');
if (selected.trim() === "") { if (selected.trim() === "") {
return; return;
} }
......
...@@ -85,7 +85,7 @@ ...@@ -85,7 +85,7 @@
}, },
success: (data) => { success: (data) => {
$target.remove(); $target.remove();
$('.prepend-top-default').html('<div class="nothing-here-block">You\'re all done!</div>'); $('.js-todos-all').html('<div class="nothing-here-block">You\'re all done!</div>');
return this.updateBadges(data); return this.updateBadges(data);
} }
}); });
......
...@@ -158,7 +158,7 @@ ...@@ -158,7 +158,7 @@
}; };
Calendar.prototype.renderMonths = function() { Calendar.prototype.renderMonths = function() {
return this.svg.append('g').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) { return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) {
return date.x; return date.x;
}).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) { }).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) {
return function(date) { return function(date) {
......
...@@ -37,6 +37,8 @@ ...@@ -37,6 +37,8 @@
display: inline-block; display: inline-block;
margin-left: 4px; margin-left: 4px;
margin-bottom: 2px; margin-bottom: 2px;
flex-shrink: 0;
-webkit-flex-shrink: 0;
&.s16 { margin-right: 4px; } &.s16 { margin-right: 4px; }
&.s24 { margin-right: 4px; } &.s24 { margin-right: 4px; }
......
...@@ -278,6 +278,10 @@ ...@@ -278,6 +278,10 @@
display: inline-block; display: inline-block;
} }
.btn {
margin: $btn-side-margin $btn-side-margin 0 0;
}
@media(max-width: $screen-xs-max) { @media(max-width: $screen-xs-max) {
margin-top: 50px; margin-top: 50px;
text-align: center; text-align: center;
...@@ -286,6 +290,12 @@ ...@@ -286,6 +290,12 @@
width: 100%; width: 100%;
} }
} }
@media(min-width: $screen-xs-max) {
&.labels .text-content {
margin-top: 70px;
}
}
} }
.flex-container-block { .flex-container-block {
......
.calender-block { .calender-block {
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
direction: rtl;
@media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
overflow-x: scroll; overflow-x: scroll;
......
...@@ -125,7 +125,8 @@ ...@@ -125,7 +125,8 @@
top: 100%; top: 100%;
left: 0; left: 0;
z-index: 9; z-index: 9;
width: 240px; max-width: 280px;
min-width: 240px;
margin-top: 2px; margin-top: 2px;
margin-bottom: 0; margin-bottom: 0;
font-size: 14px; font-size: 14px;
......
...@@ -132,6 +132,11 @@ ...@@ -132,6 +132,11 @@
display: flex; display: flex;
-webkit-flex-direction: column; -webkit-flex-direction: column;
flex-direction: column; flex-direction: column;
&> span {
white-space: normal;
word-break: break-all;
}
} }
} }
...@@ -141,10 +146,6 @@ ...@@ -141,10 +146,6 @@
} }
} }
.hint-dropdown {
width: 250px;
}
.filter-dropdown-loading { .filter-dropdown-loading {
padding: 8px 16px; padding: 8px 16px;
} }
...@@ -71,7 +71,7 @@ header { ...@@ -71,7 +71,7 @@ header {
&:focus, &:focus,
&:active { &:active {
background-color: $gray-light; background-color: $gray-light;
color: darken($gl-text-color-secondary, 30%); color: $gl-text-color;
.todos-pending-count { .todos-pending-count {
background: darken($todo-alert-blue, 10%); background: darken($todo-alert-blue, 10%);
......
...@@ -58,3 +58,9 @@ ...@@ -58,3 +58,9 @@
fill: $gl-text-color; fill: $gl-text-color;
} }
} }
.icon-link {
&:hover {
text-decoration: none;
}
}
...@@ -294,16 +294,18 @@ ...@@ -294,16 +294,18 @@
.container-fluid { .container-fluid {
position: relative; position: relative;
.nav-control {
@media (max-width: $screen-sm-max) {
margin-right: 75px;
}
}
} }
.controls { .controls {
float: right; float: right;
padding: 7px 0 0; padding: 7px 0 0;
@media (max-width: $screen-sm-max) {
display: none;
}
i { i {
color: $layout-link-gray; color: $layout-link-gray;
} }
...@@ -361,6 +363,7 @@ ...@@ -361,6 +363,7 @@
.fade-left { .fade-left {
@include fade(right, $gray-light); @include fade(right, $gray-light);
left: -5px; left: -5px;
text-align: center;
.fa { .fa {
left: -7px; left: -7px;
......
...@@ -162,6 +162,10 @@ ...@@ -162,6 +162,10 @@
} }
} }
} }
&.panel-without-border {
border: 0;
}
} }
.panel-succes .panel-heading, .panel-succes .panel-heading,
......
...@@ -178,7 +178,7 @@ $count-arrow-border: #dce0e5; ...@@ -178,7 +178,7 @@ $count-arrow-border: #dce0e5;
$save-project-loader-color: #555; $save-project-loader-color: #555;
$divergence-graph-bar-bg: #ccc; $divergence-graph-bar-bg: #ccc;
$divergence-graph-separator-bg: #ccc; $divergence-graph-separator-bg: #ccc;
$general-hover-transition-duration: 150ms; $general-hover-transition-duration: 100ms;
$general-hover-transition-curve: linear; $general-hover-transition-curve: linear;
......
...@@ -26,10 +26,6 @@ ...@@ -26,10 +26,6 @@
border: 0; border: 0;
} }
} }
.container-fluid {
@extend .fixed-width-container;
}
} }
} }
......
...@@ -420,10 +420,6 @@ ...@@ -420,10 +420,6 @@
.merge-request-tabs-holder { .merge-request-tabs-holder {
background-color: $white-light; background-color: $white-light;
.container-limited {
max-width: $limited-layout-width;
}
&.affix { &.affix {
top: 100px; top: 100px;
left: 0; left: 0;
...@@ -433,10 +429,26 @@ ...@@ -433,10 +429,26 @@
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
right: 0; right: 0;
} }
.merge-request-tabs-container {
padding-left: $gl-padding;
padding-right: $gl-padding;
}
}
}
.limit-container-width {
.merge-request-tabs-container {
max-width: $limited-layout-width;
margin-left: auto;
margin-right: auto;
} }
}
&:not(.affix) .container-fluid { .limit-container-width:not(.container-limited) {
padding-left: 0; .merge-request-tabs-holder:not(.affix) {
padding-right: 0; .merge-request-tabs-container {
max-width: $limited-layout-width - ($gl-padding * 2);
}
} }
} }
...@@ -214,9 +214,9 @@ ...@@ -214,9 +214,9 @@
&:not(:last-child) { &:not(:last-child) {
&::after { &::after {
content: ''; content: '';
width: 8px; width: 7px;
position: absolute; position: absolute;
right: -8px; right: -7px;
top: 10px; top: 10px;
border-bottom: 2px solid $border-color; border-bottom: 2px solid $border-color;
} }
...@@ -494,31 +494,27 @@ ...@@ -494,31 +494,27 @@
// Action Icons in big pipeline-graph nodes // Action Icons in big pipeline-graph nodes
> .ci-action-icon-container .ci-action-icon-wrapper { > .ci-action-icon-container .ci-action-icon-wrapper {
i {
color: $border-color;
border-radius: 100%;
border: 1px solid $border-color;
padding: 5px 6px;
font-size: 13px;
background: $white-light;
height: 30px; height: 30px;
width: 30px; width: 30px;
background: $white-light;
&::before { border: 1px solid $border-color;
position: relative; border-radius: 100%;
top: 3px; display: block;
left: 3px;
}
&:hover { &:hover {
color: $gl-text-color;
background-color: $stage-hover-bg; background-color: $stage-hover-bg;
border: 1px solid $stage-hover-bg; border: 1px solid $stage-hover-bg;
} }
svg {
fill: $border-color;
position: relative;
left: -1px;
top: -1px;
} }
.ci-play-icon { &:hover svg {
padding: 5px 5px 5px 7px; fill: $gl-text-color;
} }
} }
...@@ -657,7 +653,7 @@ ...@@ -657,7 +653,7 @@
font-weight: 100; font-weight: 100;
font-size: 15px; font-size: 15px;
position: absolute; position: absolute;
right: 5px; right: 13px;
top: 8px; top: 8px;
} }
...@@ -825,11 +821,23 @@ ...@@ -825,11 +821,23 @@
&:hover, &:hover,
&:focus { &:focus {
text-decoration: none;
color: $gl-text-color;
background-color: $stage-hover-bg; background-color: $stage-hover-bg;
border: 1px solid transparent; border: 1px solid transparent;
} }
svg {
width: 22px;
height: 22px;
left: -6px;
position: relative;
top: -3px;
fill: $action-icon-color;
}
&:hover svg,
&:focus svg {
fill: $gl-text-color;
}
} }
// link to the build // link to the build
......
...@@ -198,7 +198,7 @@ ...@@ -198,7 +198,7 @@
margin: 15px 5px 0 0; margin: 15px 5px 0 0;
input { input {
height: 27px; height: 28px;
} }
} }
...@@ -523,7 +523,7 @@ a.deploy-project-label { ...@@ -523,7 +523,7 @@ a.deploy-project-label {
&:hover, &:hover,
&:focus { &:focus {
color: darken($notes-light-color, 15%); color: $gl-text-color;
} }
} }
......
...@@ -76,6 +76,10 @@ ...@@ -76,6 +76,10 @@
font-size: 14px; font-size: 14px;
} }
.action-name {
font-weight: normal;
}
.todo-body { .todo-body {
.todo-note { .todo-note {
word-wrap: break-word; word-wrap: break-word;
......
...@@ -18,15 +18,14 @@ class AutocompleteController < ApplicationController ...@@ -18,15 +18,14 @@ class AutocompleteController < ApplicationController
if params[:search].blank? if params[:search].blank?
# Include current user if available to filter by "Me" # Include current user if available to filter by "Me"
if params[:current_user].present? && current_user if params[:current_user].present? && current_user
@users = @users.where.not(id: current_user.id)
@users = [current_user, *@users] @users = [current_user, *@users]
end end
if params[:author_id].present? if params[:author_id].present?
author = User.find_by_id(params[:author_id]) author = User.find_by_id(params[:author_id])
@users = [author, *@users] if author @users = [author, *@users].uniq if author
end end
@users.uniq!
end end
render json: @users, only: [:name, :username, :id], methods: [:avatar_url] render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
......
...@@ -4,6 +4,7 @@ class DashboardController < Dashboard::ApplicationController ...@@ -4,6 +4,7 @@ class DashboardController < Dashboard::ApplicationController
before_action :event_filter, only: :activity before_action :event_filter, only: :activity
before_action :projects, only: [:issues, :merge_requests] before_action :projects, only: [:issues, :merge_requests]
before_action :set_show_full_reference, only: [:issues, :merge_requests]
respond_to :html respond_to :html
...@@ -34,4 +35,8 @@ class DashboardController < Dashboard::ApplicationController ...@@ -34,4 +35,8 @@ class DashboardController < Dashboard::ApplicationController
@events = @event_filter.apply_filter(@events).with_associations @events = @event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0) @events = @events.limit(20).offset(params[:offset] || 0)
end end
def set_show_full_reference
@show_full_reference = true
end
end end
...@@ -22,6 +22,7 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -22,6 +22,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def trending def trending
@projects = filter_projects(Project.trending) @projects = filter_projects(Project.trending)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]) @projects = @projects.page(params[:page])
respond_to do |format| respond_to do |format|
......
...@@ -94,7 +94,7 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -94,7 +94,7 @@ class Projects::BuildsController < Projects::ApplicationController
private private
def build def build
@build ||= project.builds.find_by!(id: params[:id]).present(user: current_user) @build ||= project.builds.find_by!(id: params[:id]).present(current_user: current_user)
end end
def build_path(build) def build_path(build)
......
...@@ -30,6 +30,17 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -30,6 +30,17 @@ class Projects::CommitController < Projects::ApplicationController
end end
def pipelines def pipelines
@pipelines = @commit.pipelines.order(id: :desc)
respond_to do |format|
format.html
format.json do
render json: PipelineSerializer
.new(project: @project, user: @current_user)
.with_pagination(request, response)
.represent(@pipelines)
end
end
end end
def branches def branches
......
...@@ -2,12 +2,13 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -2,12 +2,13 @@ class Projects::LabelsController < Projects::ApplicationController
include ToggleSubscriptionAction include ToggleSubscriptionAction
before_action :module_enabled before_action :module_enabled
before_action :label, only: [:edit, :update, :destroy] before_action :label, only: [:edit, :update, :destroy, :promote]
before_action :find_labels, only: [:index, :set_priorities, :remove_priority, :toggle_subscription] before_action :find_labels, only: [:index, :set_priorities, :remove_priority, :toggle_subscription]
before_action :authorize_read_label! before_action :authorize_read_label!
before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update,
:generate, :destroy, :remove_priority, :generate, :destroy, :remove_priority,
:set_priorities] :set_priorities]
before_action :authorize_admin_group!, only: [:promote]
respond_to :js, :html respond_to :js, :html
...@@ -71,13 +72,7 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -71,13 +72,7 @@ class Projects::LabelsController < Projects::ApplicationController
@label.destroy @label.destroy
@labels = find_labels @labels = find_labels
respond_to do |format| redirect_to(namespace_project_labels_path(@project.namespace, @project), notice: 'Label was removed')
format.html do
redirect_to(namespace_project_labels_path(@project.namespace, @project),
notice: 'Label was removed')
end
format.js
end
end end
def remove_priority def remove_priority
...@@ -108,6 +103,32 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -108,6 +103,32 @@ class Projects::LabelsController < Projects::ApplicationController
end end
end end
def promote
promote_service = Labels::PromoteService.new(@project, @current_user)
begin
return render_404 unless promote_service.execute(@label)
respond_to do |format|
format.html do
redirect_to(namespace_project_labels_path(@project.namespace, @project),
notice: 'Label was promoted to a Group Label')
end
format.js
end
rescue ActiveRecord::RecordInvalid => e
Gitlab::AppLogger.error "Failed to promote label \"#{@label.title}\" to group label"
Gitlab::AppLogger.error e
respond_to do |format|
format.html do
redirect_to(namespace_project_labels_path(@project.namespace, @project),
notice: 'Failed to promote label due to internal error. Please contact administrators.')
end
format.js
end
end
end
protected protected
def module_enabled def module_enabled
...@@ -135,4 +156,8 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -135,4 +156,8 @@ class Projects::LabelsController < Projects::ApplicationController
def authorize_admin_labels! def authorize_admin_labels!
return render_404 unless can?(current_user, :admin_label, @project) return render_404 unless can?(current_user, :admin_label, @project)
end end
def authorize_admin_group!
return render_404 unless can?(current_user, :admin_group, @project.group)
end
end end
...@@ -34,7 +34,7 @@ class Projects::MattermostsController < Projects::ApplicationController ...@@ -34,7 +34,7 @@ class Projects::MattermostsController < Projects::ApplicationController
end end
def teams def teams
@teams ||= @service.list_teams(current_user) @teams, @teams_error_message = @service.list_teams(current_user)
end end
def service def service
......
...@@ -214,7 +214,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -214,7 +214,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render 'show' render 'show'
end end
format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_pipelines') } }
format.json do
render json: {
html: view_to_html_string('projects/merge_requests/show/_pipelines'),
pipelines: PipelineSerializer
.new(project: @project, user: @current_user)
.with_pagination(request, response)
.represent(@pipelines)
}
end
end end
end end
......
...@@ -32,12 +32,6 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -32,12 +32,6 @@ class Projects::RefsController < Projects::ApplicationController
redirect_to new_path redirect_to new_path
end end
format.js do
@ref = params[:ref]
define_tree_vars
tree
render "tree"
end
end end
end end
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
# For users who haven't customized the setting, we simply delegate to # For users who haven't customized the setting, we simply delegate to
# `DashboardController#show`, which is the default. # `DashboardController#show`, which is the default.
class RootController < Dashboard::ProjectsController class RootController < Dashboard::ProjectsController
skip_before_action :authenticate_user!, only: [:index]
before_action :redirect_to_custom_dashboard, only: [:index] before_action :redirect_to_custom_dashboard, only: [:index]
def index def index
...@@ -16,7 +17,7 @@ class RootController < Dashboard::ProjectsController ...@@ -16,7 +17,7 @@ class RootController < Dashboard::ProjectsController
private private
def redirect_to_custom_dashboard def redirect_to_custom_dashboard
return unless current_user return redirect_to new_user_session_path unless current_user
case current_user.dashboard case current_user.dashboard
when 'stars' when 'stars'
......
...@@ -162,6 +162,10 @@ module IssuablesHelper ...@@ -162,6 +162,10 @@ module IssuablesHelper
] ]
end end
def issuable_reference(issuable)
@show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
end
def issuable_filter_present? def issuable_filter_present?
issuable_filter_params.any? { |k| params.key?(k) } issuable_filter_params.any? { |k| params.key?(k) }
end end
......
...@@ -89,7 +89,7 @@ module SearchHelper ...@@ -89,7 +89,7 @@ module SearchHelper
{ {
category: "Groups", category: "Groups",
id: group.id, id: group.id,
label: "#{search_result_sanitize(group.name)}", label: "#{search_result_sanitize(group.full_name)}",
url: group_path(group) url: group_path(group)
} }
end end
......
...@@ -13,49 +13,6 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -13,49 +13,6 @@ class ApplicationSetting < ActiveRecord::Base
[\r\n] # any number of newline characters [\r\n] # any number of newline characters
}x }x
DEFAULTS_CE = {
after_sign_up_text: nil,
akismet_enabled: false,
container_registry_token_expire_delay: 5,
default_branch_protection: Settings.gitlab['default_branch_protection'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_projects_limit: Settings.gitlab['default_projects_limit'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
disabled_oauth_sign_in_sources: [],
domain_whitelist: Settings.gitlab['domain_whitelist'],
gravatar_enabled: Settings.gravatar['enabled'],
help_page_text: nil,
housekeeping_bitmaps_enabled: true,
housekeeping_enabled: true,
housekeeping_full_repack_period: 50,
housekeeping_gc_period: 200,
housekeeping_incremental_repack_period: 10,
import_sources: Gitlab::ImportSources.values,
koding_enabled: false,
koding_url: nil,
max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
plantuml_enabled: false,
plantuml_url: nil,
recaptcha_enabled: false,
repository_checks_enabled: true,
repository_storages: ['default'],
require_two_factor_authentication: false,
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
session_expire_delay: Settings.gitlab['session_expire_delay'],
send_user_confirmation_email: false,
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
shared_runners_text: nil,
sidekiq_throttling_enabled: false,
sign_in_text: nil,
signin_enabled: Settings.gitlab['signin_enabled'],
signup_enabled: Settings.gitlab['signup_enabled'],
two_factor_grace_period: 48,
user_default_external: false
}
DEFAULTS = DEFAULTS_CE
serialize :restricted_visibility_levels serialize :restricted_visibility_levels
serialize :import_sources serialize :import_sources
serialize :disabled_oauth_sign_in_sources, Array serialize :disabled_oauth_sign_in_sources, Array
...@@ -199,14 +156,64 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -199,14 +156,64 @@ class ApplicationSetting < ActiveRecord::Base
def self.expire def self.expire
Rails.cache.delete(CACHE_KEY) Rails.cache.delete(CACHE_KEY)
rescue
# Gracefully handle when Redis is not available. For example,
# omnibus may fail here during gitlab:assets:compile.
end end
def self.cached def self.cached
Rails.cache.fetch(CACHE_KEY) Rails.cache.fetch(CACHE_KEY)
end end
def self.defaults_ce
{
after_sign_up_text: nil,
akismet_enabled: false,
container_registry_token_expire_delay: 5,
default_branch_protection: Settings.gitlab['default_branch_protection'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_projects_limit: Settings.gitlab['default_projects_limit'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
disabled_oauth_sign_in_sources: [],
domain_whitelist: Settings.gitlab['domain_whitelist'],
gravatar_enabled: Settings.gravatar['enabled'],
help_page_text: nil,
housekeeping_bitmaps_enabled: true,
housekeeping_enabled: true,
housekeeping_full_repack_period: 50,
housekeeping_gc_period: 200,
housekeeping_incremental_repack_period: 10,
import_sources: Gitlab::ImportSources.values,
koding_enabled: false,
koding_url: nil,
max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
plantuml_enabled: false,
plantuml_url: nil,
recaptcha_enabled: false,
repository_checks_enabled: true,
repository_storages: ['default'],
require_two_factor_authentication: false,
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
session_expire_delay: Settings.gitlab['session_expire_delay'],
send_user_confirmation_email: false,
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
shared_runners_text: nil,
sidekiq_throttling_enabled: false,
sign_in_text: nil,
signin_enabled: Settings.gitlab['signin_enabled'],
signup_enabled: Settings.gitlab['signup_enabled'],
two_factor_grace_period: 48,
user_default_external: false
}
end
def self.defaults
defaults_ce
end
def self.create_from_defaults def self.create_from_defaults
create(DEFAULTS) create(defaults)
end end
def home_page_url_column_exist def home_page_url_column_exist
......
...@@ -100,8 +100,8 @@ class Commit ...@@ -100,8 +100,8 @@ class Commit
commit_reference(from_project, id, full: full) commit_reference(from_project, id, full: full)
end end
def reference_link_text(from_project = nil) def reference_link_text(from_project = nil, full: false)
commit_reference(from_project, short_id) commit_reference(from_project, short_id, full: full)
end end
def diff_line_count def diff_line_count
......
class Environment < ActiveRecord::Base class Environment < ActiveRecord::Base
# Used to generate random suffixes for the slug # Used to generate random suffixes for the slug
LETTERS = 'a'..'z'
NUMBERS = '0'..'9' NUMBERS = '0'..'9'
SUFFIX_CHARS = ('a'..'z').to_a + NUMBERS.to_a SUFFIX_CHARS = LETTERS.to_a + NUMBERS.to_a
belongs_to :project, required: true, validate: true belongs_to :project, required: true, validate: true
...@@ -148,17 +149,24 @@ class Environment < ActiveRecord::Base ...@@ -148,17 +149,24 @@ class Environment < ActiveRecord::Base
slugified = name.to_s.downcase.gsub(/[^a-z0-9]/, '-') slugified = name.to_s.downcase.gsub(/[^a-z0-9]/, '-')
# Must start with a letter # Must start with a letter
slugified = "env-" + slugified if NUMBERS.cover?(slugified[0]) slugified = 'env-' + slugified unless LETTERS.cover?(slugified[0])
# Repeated dashes are invalid (OpenShift limitation)
slugified.gsub!(/\-+/, '-')
# Maximum length: 24 characters (OpenShift limitation) # Maximum length: 24 characters (OpenShift limitation)
slugified = slugified[0..23] slugified = slugified[0..23]
# Cannot end with a "-" character (Kubernetes label limitation) # Cannot end with a dash (Kubernetes label limitation)
slugified = slugified[0..-2] if slugified[-1] == "-" slugified.chop! if slugified.end_with?('-')
# Add a random suffix, shortening the current string if necessary, if it # Add a random suffix, shortening the current string if necessary, if it
# has been slugified. This ensures uniqueness. # has been slugified. This ensures uniqueness.
slugified = slugified[0..16] + "-" + random_suffix if slugified != name if slugified != name
slugified = slugified[0..16]
slugified << '-' unless slugified.end_with?('-')
slugified << random_suffix
end
self.slug = slugified self.slug = slugified
end end
......
...@@ -97,10 +97,11 @@ class Issue < ActiveRecord::Base ...@@ -97,10 +97,11 @@ class Issue < ActiveRecord::Base
end end
end end
def to_reference(from_project = nil, full: false) # `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}" reference = "#{self.class.reference_prefix}#{iid}"
"#{project.to_reference(from_project, full: full)}#{reference}" "#{project.to_reference(from, full: full)}#{reference}"
end end
def referenced_merge_requests(current_user = nil) def referenced_merge_requests(current_user = nil)
......
...@@ -179,10 +179,11 @@ class MergeRequest < ActiveRecord::Base ...@@ -179,10 +179,11 @@ class MergeRequest < ActiveRecord::Base
work_in_progress?(title) ? title : "WIP: #{title}" work_in_progress?(title) ? title : "WIP: #{title}"
end end
def to_reference(from_project = nil, full: false) # `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}" reference = "#{self.class.reference_prefix}#{iid}"
"#{project.to_reference(from_project, full: full)}#{reference}" "#{project.to_reference(from, full: full)}#{reference}"
end end
def first_commit def first_commit
......
...@@ -225,6 +225,7 @@ class Project < ActiveRecord::Base ...@@ -225,6 +225,7 @@ class Project < ActiveRecord::Base
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') } scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_statistics, -> { includes(:statistics) } scope :with_statistics, -> { includes(:statistics) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) } scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
scope :inside_path, ->(path) { joins(:route).where('routes.path LIKE ?', "#{path}/%") }
# "enabled" here means "not disabled". It includes private features! # "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) { scope :with_feature_enabled, ->(feature) {
...@@ -591,10 +592,11 @@ class Project < ActiveRecord::Base ...@@ -591,10 +592,11 @@ class Project < ActiveRecord::Base
end end
end end
def to_reference(from_project = nil, full: false) # `from` argument can be a Namespace or Project.
if full || cross_namespace_reference?(from_project) def to_reference(from = nil, full: false)
if full || cross_namespace_reference?(from)
path_with_namespace path_with_namespace
elsif cross_project_reference?(from_project) elsif cross_project_reference?(from)
path path
end end
end end
...@@ -1291,21 +1293,26 @@ class Project < ActiveRecord::Base ...@@ -1291,21 +1293,26 @@ class Project < ActiveRecord::Base
private private
def cross_namespace_reference?(from)
case from
when Project
namespace != from.namespace
when Namespace
namespace != from
end
end
# Check if a reference is being done cross-project # Check if a reference is being done cross-project
# def cross_project_reference?(from)
# from_project - Refering Project object return true if from.is_a?(Namespace)
def cross_project_reference?(from_project)
from_project && self != from_project from && self != from
end end
def pushes_since_gc_redis_key def pushes_since_gc_redis_key
"projects/#{id}/pushes_since_gc" "projects/#{id}/pushes_since_gc"
end end
def cross_namespace_reference?(from_project)
from_project && namespace != from_project.namespace
end
def default_branch_protected? def default_branch_protected?
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL || current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
......
...@@ -31,13 +31,13 @@ class ChatSlashCommandsService < Service ...@@ -31,13 +31,13 @@ class ChatSlashCommandsService < Service
return unless valid_token?(params[:token]) return unless valid_token?(params[:token])
user = find_chat_user(params) user = find_chat_user(params)
unless user
if user
Gitlab::ChatCommands::Command.new(project, user, params).execute
else
url = authorize_chat_name_url(params) url = authorize_chat_name_url(params)
return presenter.authorize_chat_name(url) Gitlab::ChatCommands::Presenters::Access.new(url).authorize
end end
Gitlab::ChatCommands::Command.new(project, user,
params).execute
end end
private private
...@@ -49,8 +49,4 @@ class ChatSlashCommandsService < Service ...@@ -49,8 +49,4 @@ class ChatSlashCommandsService < Service
def authorize_chat_name_url(params) def authorize_chat_name_url(params)
ChatNames::AuthorizeUserService.new(self, params).execute ChatNames::AuthorizeUserService.new(self, params).execute
end end
def presenter
Gitlab::ChatCommands::Presenter.new
end
end end
...@@ -60,9 +60,9 @@ class JiraService < IssueTrackerService ...@@ -60,9 +60,9 @@ class JiraService < IssueTrackerService
end end
def help def help
'You need to configure JIRA before enabling this service. For more details "You need to configure JIRA before enabling this service. For more details
read the read the
[JIRA service documentation](https://docs.gitlab.com/ce/project_services/jira.html).' [JIRA service documentation](#{help_page_url('project_services/jira')})."
end end
def title def title
......
...@@ -28,8 +28,8 @@ class MattermostSlashCommandsService < ChatSlashCommandsService ...@@ -28,8 +28,8 @@ class MattermostSlashCommandsService < ChatSlashCommandsService
[false, e.message] [false, e.message]
end end
def list_teams(user) def list_teams(current_user)
Mattermost::Team.new(user).all [Mattermost::Team.new(current_user).all, nil]
rescue Mattermost::Error => e rescue Mattermost::Error => e
[[], e.message] [[], e.message]
end end
......
...@@ -1241,7 +1241,18 @@ class Repository ...@@ -1241,7 +1241,18 @@ class Repository
end end
def tags_sorted_by_committed_date def tags_sorted_by_committed_date
tags.sort_by { |tag| tag.dereferenced_target.committed_date } tags.sort_by do |tag|
# Annotated tags can point to any object (e.g. a blob), but generally
# tags point to a commit. If we don't have a commit, then just default
# to putting the tag at the end of the list.
target = tag.dereferenced_target
if target
target.committed_date
else
Time.now
end
end
end end
def keep_around_ref_name(sha) def keep_around_ref_name(sha)
......
...@@ -103,9 +103,9 @@ class Todo < ActiveRecord::Base ...@@ -103,9 +103,9 @@ class Todo < ActiveRecord::Base
def target_reference def target_reference
if for_commit? if for_commit?
target.short_id target.reference_link_text(full: true)
else else
target.to_reference target.to_reference(full: true)
end end
end end
......
module Ci module Ci
class BuildPolicy < CommitStatusPolicy class BuildPolicy < CommitStatusPolicy
def rules def rules
can! :read_build if @subject.project.public_builds?
super super
# If we can't read build we should also not have that # If we can't read build we should also not have that
......
...@@ -113,7 +113,7 @@ detects the presenter based on the presented subject's class. ...@@ -113,7 +113,7 @@ detects the presenter based on the presented subject's class.
class Projects::LabelsController < Projects::ApplicationController class Projects::LabelsController < Projects::ApplicationController
def edit def edit
@label = Gitlab::View::Presenter::Factory @label = Gitlab::View::Presenter::Factory
.new(@label, user: current_user) .new(@label, current_user: current_user)
.fabricate! .fabricate!
end end
end end
...@@ -132,7 +132,7 @@ and then in the controller: ...@@ -132,7 +132,7 @@ and then in the controller:
```ruby ```ruby
class Projects::LabelsController < Projects::ApplicationController class Projects::LabelsController < Projects::ApplicationController
def edit def edit
@label = @label.present(user: current_user) @label = @label.present(current_user: current_user)
end end
end end
``` ```
...@@ -147,7 +147,7 @@ end ...@@ -147,7 +147,7 @@ end
You can also present the model in the view: You can also present the model in the view:
```ruby ```ruby
- label = @label.present(current_user) - label = @label.present(current_user: current_user)
%div{ class: label.text_color } %div{ class: label.text_color }
= render partial: label, label: label = render partial: label, label: label
......
...@@ -6,6 +6,7 @@ class BaseSerializer ...@@ -6,6 +6,7 @@ class BaseSerializer
def represent(resource, opts = {}) def represent(resource, opts = {})
self.class.entity_class self.class.entity_class
.represent(resource, opts.merge(request: @request)) .represent(resource, opts.merge(request: @request))
.as_json
end end
def self.entity(entity_class) def self.entity(entity_class)
......
class PipelineSerializer < BaseSerializer class PipelineSerializer < BaseSerializer
entity PipelineEntity
class InvalidResourceError < StandardError; end class InvalidResourceError < StandardError; end
include API::Helpers::Pagination include API::Helpers::Pagination
Struct.new('Pagination', :request, :response) Struct.new('Pagination', :request, :response)
entity PipelineEntity
def represent(resource, opts = {}) def represent(resource, opts = {})
if paginated? if paginated?
raise InvalidResourceError unless resource.respond_to?(:page) raise InvalidResourceError unless resource.respond_to?(:page)
......
module Labels
class PromoteService < BaseService
BATCH_SIZE = 1000
def execute(label)
return unless project.group &&
label.is_a?(ProjectLabel)
Label.transaction do
new_label = clone_label_to_group_label(label)
label_ids_for_merge(new_label).find_in_batches(batch_size: BATCH_SIZE) do |batched_ids|
update_issuables(new_label, batched_ids)
update_issue_board_lists(new_label, batched_ids)
update_priorities(new_label, batched_ids)
# Order is important, project labels need to be last
update_project_labels(batched_ids)
end
# We skipped validations during creation. Let's run them now, after deleting conflicting labels
raise ActiveRecord::RecordInvalid.new(new_label) unless new_label.valid?
new_label
end
end
private
def label_ids_for_merge(new_label)
LabelsFinder.
new(current_user, title: new_label.title, group_id: project.group.id).
execute(skip_authorization: true).
where.not(id: new_label).
select(:id) # Can't use pluck() to avoid object-creation because of the batching
end
def update_issuables(new_label, label_ids)
LabelLink.
where(label: label_ids).
update_all(label_id: new_label)
end
def update_issue_board_lists(new_label, label_ids)
List.
where(label: label_ids).
update_all(label_id: new_label)
end
def update_priorities(new_label, label_ids)
LabelPriority.
where(label: label_ids).
update_all(label_id: new_label)
end
def update_project_labels(label_ids)
Label.where(id: label_ids).delete_all
end
def clone_label_to_group_label(label)
params = label.attributes.slice('title', 'description', 'color')
# Since the title of the new label has to be the same as the previous labels
# and we're merging old labels in batches we'll skip validation to omit 2-step
# merge process and do it in one batch
# We'll be forcing validation at the end of the transaction to ensure everything
# was merged correctly
new_label = GroupLabel.new(params.merge(group: project.group))
new_label.save(validate: false)
new_label
end
end
end
module MergeRequests module MergeRequests
class BuildService < MergeRequests::BaseService class BuildService < MergeRequests::BaseService
def execute def execute
merge_request = MergeRequest.new(params) self.merge_request = MergeRequest.new(params)
# Set MR attributes
merge_request.can_be_created = true merge_request.can_be_created = true
merge_request.compare_commits = [] merge_request.compare_commits = []
merge_request.source_project = project unless merge_request.source_project merge_request.source_project = find_source_project
merge_request.target_project = find_target_project
merge_request.target_branch = find_target_branch
if branches_specified? && branches_valid?
compare_branches
assign_title_and_description
else
merge_request.can_be_created = false
end
merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project) merge_request
end
private
merge_request.target_project ||= (project.forked_from_project || project) attr_accessor :merge_request
merge_request.target_branch ||= merge_request.target_project.default_branch
messages = validate_branches(merge_request) delegate :target_branch, :source_branch, :source_project, :target_project, :compare_commits, :wip_title, :description, :errors, to: :merge_request
return build_failed(merge_request, messages) unless messages.empty?
def find_source_project
source_project || project
end
def find_target_project
return target_project if target_project.present? && can?(current_user, :read_project, target_project)
project.forked_from_project || project
end
def find_target_branch
target_branch || target_project.default_branch
end
def branches_specified?
params[:source_branch] && params[:target_branch]
end
def branches_valid?
validate_branches
errors.blank?
end
def compare_branches
compare = CompareService.new( compare = CompareService.new(
merge_request.source_project, source_project,
merge_request.source_branch source_branch
).execute( ).execute(
merge_request.target_project, target_project,
merge_request.target_branch, target_branch
) )
merge_request.compare_commits = compare.commits merge_request.compare_commits = compare.commits
merge_request.compare = compare merge_request.compare = compare
set_title_and_description(merge_request)
end end
private def validate_branches
add_error('You must select source and target branch') unless branches_present?
def validate_branches(merge_request) add_error('You must select different branches') if same_source_and_target?
messages = [] add_error("Source branch \"#{source_branch}\" does not exist") unless source_branch_exists?
add_error("Target branch \"#{target_branch}\" does not exist") unless target_branch_exists?
if merge_request.target_branch.blank? || merge_request.source_branch.blank?
messages <<
if params[:source_branch] || params[:target_branch]
"You must select source and target branch"
end
end end
if merge_request.source_project == merge_request.target_project && def add_error(message)
merge_request.target_branch == merge_request.source_branch errors.add(:base, message)
end
messages << 'You must select different branches' def branches_present?
target_branch.present? && source_branch.present?
end end
# See if source and target branches exist def same_source_and_target?
if merge_request.source_branch.present? && !merge_request.source_project.commit(merge_request.source_branch) source_project == target_project && target_branch == source_branch
messages << "Source branch \"#{merge_request.source_branch}\" does not exist"
end end
if merge_request.target_branch.present? && !merge_request.target_project.commit(merge_request.target_branch) def source_branch_exists?
messages << "Target branch \"#{merge_request.target_branch}\" does not exist" source_branch.blank? || source_project.commit(source_branch)
end end
messages def target_branch_exists?
target_branch.blank? || target_project.commit(target_branch)
end end
# When your branch name starts with an iid followed by a dash this pattern will be # When your branch name starts with an iid followed by a dash this pattern will be
...@@ -72,17 +98,17 @@ module MergeRequests ...@@ -72,17 +98,17 @@ module MergeRequests
# - Setting the title as 'Resolves "Emoji don't show up in commit title"' if there is # - Setting the title as 'Resolves "Emoji don't show up in commit title"' if there is
# more than one commit in the MR # more than one commit in the MR
# #
def set_title_and_description(merge_request) def assign_title_and_description
if match = merge_request.source_branch.match(/\A(\d+)-/) if match = source_branch.match(/\A(\d+)-/)
iid = match[1] iid = match[1]
end end
commits = merge_request.compare_commits commits = compare_commits
if commits && commits.count == 1 if commits && commits.count == 1
commit = commits.first commit = commits.first
merge_request.title = commit.title merge_request.title = commit.title
merge_request.description ||= commit.description.try(:strip) merge_request.description ||= commit.description.try(:strip)
elsif iid && issue = merge_request.target_project.get_issue(iid, current_user) elsif iid && issue = target_project.get_issue(iid, current_user)
case issue case issue
when Issue when Issue
merge_request.title = "Resolve \"#{issue.title}\"" merge_request.title = "Resolve \"#{issue.title}\""
...@@ -90,31 +116,20 @@ module MergeRequests ...@@ -90,31 +116,20 @@ module MergeRequests
merge_request.title = "Resolve #{issue.title}" merge_request.title = "Resolve #{issue.title}"
end end
else else
merge_request.title = merge_request.source_branch.titleize.humanize merge_request.title = source_branch.titleize.humanize
end end
if iid if iid
closes_issue = "Closes ##{iid}" closes_issue = "Closes ##{iid}"
if merge_request.description.present? if description.present?
merge_request.description += closes_issue.prepend("\n\n") merge_request.description += closes_issue.prepend("\n\n")
else else
merge_request.description = closes_issue merge_request.description = closes_issue
end end
end end
merge_request.title = merge_request.wip_title if commits.empty? merge_request.title = wip_title if commits.empty?
merge_request
end
def build_failed(merge_request, messages)
messages.compact.each do |message|
merge_request.errors.add(:base, message)
end
merge_request.compare_commits = []
merge_request.can_be_created = false
merge_request
end end
end end
end end
...@@ -365,7 +365,7 @@ class NotificationService ...@@ -365,7 +365,7 @@ class NotificationService
users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq) users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq)
users_with_project_setting = select_project_member_setting(project, users_with_project_level_global, users) users_with_project_setting = select_project_member_setting(project, users_with_project_level_global, users)
users_with_group_setting = select_group_member_setting(project, project_members, users_with_group_level_global, users) users_with_group_setting = select_group_member_setting(project.group, project_members, users_with_group_level_global, users)
User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a
end end
...@@ -415,8 +415,8 @@ class NotificationService ...@@ -415,8 +415,8 @@ class NotificationService
end end
# Build a list of users based on group notification settings # Build a list of users based on group notification settings
def select_group_member_setting(project, project_members, global_setting, users_global_level_watch) def select_group_member_setting(group, project_members, global_setting, users_global_level_watch)
uids = notification_settings_for(project, :watch) uids = notification_settings_for(group, :watch)
# Group setting is watch, add to users list if user is not project member # Group setting is watch, add to users list if user is not project member
users = [] users = []
...@@ -473,7 +473,7 @@ class NotificationService ...@@ -473,7 +473,7 @@ class NotificationService
setting = user.notification_settings_for(project) setting = user.notification_settings_for(project)
if !setting && project.group if project.group && (setting.nil? || setting.global?)
setting = user.notification_settings_for(project.group) setting = user.notification_settings_for(project.group)
end end
......
...@@ -22,6 +22,8 @@ module Projects ...@@ -22,6 +22,8 @@ module Projects
if project.update_attributes(params.except(:default_branch)) if project.update_attributes(params.except(:default_branch))
if project.previous_changes.include?('path') if project.previous_changes.include?('path')
project.rename_repo project.rename_repo
else
system_hook_service.execute_hooks_for(project, :update)
end end
success success
......
...@@ -9,7 +9,10 @@ module Search ...@@ -9,7 +9,10 @@ module Search
def execute def execute
group = Group.find_by(id: params[:group_id]) if params[:group_id].present? group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
projects = ProjectsFinder.new.execute(current_user) projects = ProjectsFinder.new.execute(current_user)
projects = projects.in_namespace(group.id) if group
if group
projects = projects.inside_path(group.full_path)
end
Gitlab::SearchResults.new(current_user, projects, params[:search]) Gitlab::SearchResults.new(current_user, projects, params[:search])
end end
......
- status = local_assigns.fetch(:status) - status = local_assigns.fetch(:status)
- link = local_assigns.fetch(:link, true)
- css_classes = "ci-status ci-#{status.group}" - css_classes = "ci-status ci-#{status.group}"
- if status.has_details? - if link && status.has_details?
= link_to status.details_path, class: css_classes do = link_to status.details_path, class: css_classes do
= custom_icon(status.icon) = custom_icon(status.icon)
= status.text = status.text
......
...@@ -16,4 +16,4 @@ ...@@ -16,4 +16,4 @@
- if status.has_action? - if status.has_action?
= link_to status.action_path, class: 'ci-action-icon-wrapper js-ci-action-icon', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title } do = link_to status.action_path, class: 'ci-action-icon-wrapper js-ci-action-icon', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title } do
= icon(status.action_icon, class: status.action_class) = custom_icon(status.action_icon)
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- subject = local_assigns.fetch(:subject) - subject = local_assigns.fetch(:subject)
- status = subject.detailed_status(current_user) - status = subject.detailed_status(current_user)
- klass = "ci-status-icon ci-status-icon-#{status.group}" - klass = "ci-status-icon ci-status-icon-#{status.group} js-ci-status-icon-#{status.group}"
- tooltip = "#{subject.name} - #{status.label}" - tooltip = "#{subject.name} - #{status.label}"
- if status.has_details? - if status.has_details?
...@@ -16,5 +16,5 @@ ...@@ -16,5 +16,5 @@
- if status.has_action? - if status.has_action?
= link_to status.action_path, class: 'ci-action-icon-container has-tooltip', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title } do = link_to status.action_path, class: 'ci-action-icon-container has-tooltip', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title } do
%i.ci-action-icon-wrapper %i.ci-action-icon-wrapper{ class: "js-#{status.action_icon.dasherize}" }
= icon(status.action_icon, class: status.action_class) = custom_icon(status.action_icon)
...@@ -15,6 +15,4 @@ ...@@ -15,6 +15,4 @@
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
= render 'shared/issues'
.prepend-top-default
= render 'shared/issues'
...@@ -7,6 +7,4 @@ ...@@ -7,6 +7,4 @@
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request" = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
= render 'shared/issuable/filter', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests
= render 'shared/merge_requests'
.prepend-top-default
= render 'shared/merge_requests'
...@@ -11,8 +11,11 @@ ...@@ -11,8 +11,11 @@
= link_to_author(todo) = link_to_author(todo)
- else - else
(removed) (removed)
%span.todo-label
%span.action-name
= todo_action_name(todo) = todo_action_name(todo)
%span.todo-label
- if todo.target - if todo.target
= todo_target_link(todo) = todo_target_link(todo)
- else - else
......
...@@ -67,21 +67,17 @@ ...@@ -67,21 +67,17 @@
= sort_title_oldest_created = sort_title_oldest_created
.prepend-top-default .js-todos-all
- if @todos.any? - if @todos.any?
.js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} } .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
- @todos.group_by(&:project).each do |group| .panel.panel-default.panel-small.panel-without-border
.panel.panel-default.panel-small
- project = group[0]
.panel-heading
= link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
%ul.content-list.todos-list %ul.content-list.todos-list
= render group[1] = render @todos
= paginate @todos, theme: "gitlab" = paginate @todos, theme: "gitlab"
- elsif current_user.todos.any? - elsif current_user.todos.any?
.todos-all-done .todos-all-done
= render "shared/empty_states/todos_all_done.svg" = render "shared/empty_states/icons/todos_all_done.svg"
- if todos_filter_empty? - if todos_filter_empty?
%h4.text-center %h4.text-center
= Gitlab.config.gitlab.no_todos_messages.sample = Gitlab.config.gitlab.no_todos_messages.sample
...@@ -98,7 +94,7 @@ ...@@ -98,7 +94,7 @@
- else - else
.todos-empty .todos-empty
.todos-empty-hero .todos-empty-hero
= render "shared/empty_states/todos_empty.svg" = render "shared/empty_states/icons/todos_empty.svg"
.todos-empty-content .todos-empty-content
%h4 %h4
Todos let you see what you should do next. Todos let you see what you should do next.
......
...@@ -23,7 +23,6 @@ ...@@ -23,7 +23,6 @@
- if current_user - if current_user
To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page. To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
.prepend-top-default
= render 'shared/issues' = render 'shared/issues'
- else - else
= render 'shared/empty_states/issues', project_select_button: true = render 'shared/empty_states/issues', project_select_button: true
...@@ -15,5 +15,4 @@ ...@@ -15,5 +15,4 @@
- if current_user - if current_user
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
.prepend-top-default = render 'shared/merge_requests'
= render 'shared/merge_requests'
= render 'layouts/nav/admin_settings'
.scrolling-tabs-container{ class: nav_control_class } .scrolling-tabs-container{ class: nav_control_class }
= render 'layouts/nav/admin_settings'
.fade-left .fade-left
= icon('angle-left') = icon('angle-left')
.fade-right .fade-right
......
.content-block.build-header .content-block.build-header
.header-content .header-content
= render 'ci/status/badge', status: @build.detailed_status(current_user) = render 'ci/status/badge', status: @build.detailed_status(current_user), link: false
Build Build
%strong ##{@build.id} %strong.js-build-id ##{@build.id}
in pipeline in pipeline
= link_to pipeline_path(@build.pipeline) do = link_to pipeline_path(@build.pipeline) do
%strong ##{@build.pipeline.id} %strong ##{@build.pipeline.id}
......
...@@ -63,9 +63,10 @@ ...@@ -63,9 +63,10 @@
- if @commit.status - if @commit.status
.well-segment.pipeline-info .well-segment.pipeline-info
%div{ class: "icon-container ci-status-icon-#{@commit.status}" } %div{ class: "icon-container ci-status-icon-#{@commit.status}" }
= link_to namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id) do
= ci_icon_for_status(@commit.status) = ci_icon_for_status(@commit.status)
Pipeline Pipeline
= link_to "##{@commit.pipelines.last.id}", pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace" = link_to "##{@commit.pipelines.last.id}", namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id), class: "monospace"
for for
= link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
%span.ci-status-label %span.ci-status-label
......
- page_title "Pipelines", "#{@commit.title} (#{@commit.short_id})", "Commits" - page_title 'Pipelines', "#{@commit.title} (#{@commit.short_id})", 'Commits'
= render "commit_box" = render 'commit_box'
= render 'ci_menu'
= render "ci_menu" = render 'pipelines_list', pipelines: @pipelines
= render "pipelines_list", pipelines: @commit.pipelines.order(id: :desc)
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
%fieldset.append-bottom-0 %fieldset.append-bottom-0
.row .row
.form-group.col-md-9 .form-group.col-md-9
= f.label :name, class: 'label-light' do = f.label :name, class: 'label-light', for: 'project_name_edit' do
Project name Project name
= f.text_field :name, class: "form-control", id: "project_name_edit" = f.text_field :name, class: "form-control", id: "project_name_edit"
...@@ -183,6 +183,8 @@ ...@@ -183,6 +183,8 @@
%li Build traces and artifacts %li Build traces and artifacts
%li LFS objects %li LFS objects
%li Container registry images %li Container registry images
%li CI variables
%li Any encrypted tokens
%hr %hr
- if can? current_user, :archive_project, @project - if can? current_user, :archive_project, @project
.row.prepend-top-default .row.prepend-top-default
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
%div{ class: container_class } %div{ class: container_class }
.top-area.adjust .top-area.adjust
.col-md-9 .col-md-9
%h3.page-title= @environment.name.capitalize %h3.page-title= @environment.name
.col-md-3 .col-md-3
.nav-controls .nav-controls
= render 'projects/environments/terminal_button', environment: @environment = render 'projects/environments/terminal_button', environment: @environment
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
%th ID %th ID
%th Commit %th Commit
%th Build %th Build
%th %th Created
%th.hidden-xs %th.hidden-xs
= render @deployments = render @deployments
......
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
= note_count = note_count
.issue-info .issue-info
#{issue.to_reference} &middot; #{issuable_reference(issue)} &middot;
opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
by #{link_to_member(@project, issue.author, avatar: false)} by #{link_to_member(@project, issue.author, avatar: false)}
- if issue.milestone - if issue.milestone
......
...@@ -19,10 +19,8 @@ ...@@ -19,10 +19,8 @@
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls .nav-controls
- if current_user - if current_user
= link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn append-right-10' do = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do
= icon('rss') = icon('rss')
%span.icon-label
Subscribe
- if can? current_user, :create_issue, @project - if can? current_user, :create_issue, @project
= link_to new_namespace_project_issue_path(@project.namespace, = link_to new_namespace_project_issue_path(@project.namespace,
@project, @project,
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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