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 ...
This commit is contained in:
commit
54fca95160
|
|
@ -107,7 +107,7 @@ setup-test-env:
|
|||
<<: *dedicated-runner
|
||||
stage: prepare
|
||||
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'
|
||||
artifacts:
|
||||
expire_in: 7d
|
||||
|
|
@ -271,7 +271,7 @@ rake db:migrate:reset:
|
|||
<<: *use-db
|
||||
<<: *dedicated-runner
|
||||
script:
|
||||
- rake db:migrate:reset
|
||||
- bundle exec rake db:migrate:reset
|
||||
|
||||
rake db:seed_fu:
|
||||
stage: test
|
||||
|
|
@ -302,7 +302,7 @@ teaspoon:
|
|||
script:
|
||||
- npm install
|
||||
- npm link istanbul
|
||||
- rake teaspoon
|
||||
- bundle exec rake teaspoon
|
||||
artifacts:
|
||||
name: coverage-javascript
|
||||
expire_in: 31d
|
||||
|
|
@ -353,10 +353,10 @@ migration paths:
|
|||
- cp config/resque.yml.example config/resque.yml
|
||||
- sed -i 's/localhost/redis/g' config/resque.yml
|
||||
- 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
|
||||
- source scripts/prepare_build.sh
|
||||
- rake db:migrate
|
||||
- bundle exec rake db:migrate
|
||||
|
||||
coverage:
|
||||
stage: post-test
|
||||
|
|
|
|||
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -2,6 +2,17 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
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)
|
||||
|
||||
- allow issue filter bar to be operated with mouse only. !8681
|
||||
|
|
@ -18,7 +29,7 @@ entry.
|
|||
- Prevent users from deleting system deploy keys via the project deploy key API.
|
||||
- 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
|
||||
- Validate label's title length. !5767 (Tomáš Kukrál)
|
||||
|
|
|
|||
|
|
@ -286,14 +286,6 @@ request is as follows:
|
|||
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).
|
||||
|
||||
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
|
||||
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
|
||||
|
|
|
|||
3
Gemfile
3
Gemfile
|
|
@ -36,7 +36,7 @@ gem 'omniauth-twitter', '~> 1.2.0'
|
|||
gem 'omniauth_crowd', '~> 2.2.0'
|
||||
gem 'omniauth-authentiq', '~> 0.2.0'
|
||||
gem 'rack-oauth2', '~> 1.2.1'
|
||||
gem 'jwt'
|
||||
gem 'jwt', '~> 1.5.6'
|
||||
|
||||
# Spam and anti-bot protection
|
||||
gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'
|
||||
|
|
@ -280,6 +280,7 @@ group :development, :test do
|
|||
gem 'rspec-retry', '~> 0.4.5'
|
||||
gem 'spinach-rails', '~> 0.2.1'
|
||||
gem 'spinach-rerun-reporter', '~> 0.0.2'
|
||||
gem 'rspec_profiling'
|
||||
|
||||
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
|
||||
gem 'minitest', '~> 5.7.0'
|
||||
|
|
|
|||
13
Gemfile.lock
13
Gemfile.lock
|
|
@ -379,7 +379,7 @@ GEM
|
|||
json (1.8.3)
|
||||
json-schema (2.6.2)
|
||||
addressable (~> 2.3.8)
|
||||
jwt (1.5.4)
|
||||
jwt (1.5.6)
|
||||
kaminari (0.17.0)
|
||||
actionpack (>= 3.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
|
|
@ -642,6 +642,11 @@ GEM
|
|||
rspec-retry (0.4.5)
|
||||
rspec-core
|
||||
rspec-support (3.5.0)
|
||||
rspec_profiling (0.0.4)
|
||||
activerecord
|
||||
pg
|
||||
rails
|
||||
sqlite3
|
||||
rubocop (0.46.0)
|
||||
parser (>= 2.3.1.1, < 3.0)
|
||||
powerpack (~> 0.1)
|
||||
|
|
@ -743,6 +748,7 @@ GEM
|
|||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
sqlite3 (1.3.11)
|
||||
stackprof (0.2.10)
|
||||
state_machines (0.4.0)
|
||||
state_machines-activemodel (0.4.0)
|
||||
|
|
@ -906,7 +912,7 @@ DEPENDENCIES
|
|||
jquery-rails (~> 4.1.0)
|
||||
jquery-ui-rails (~> 5.0.0)
|
||||
json-schema (~> 2.6.2)
|
||||
jwt
|
||||
jwt (~> 1.5.6)
|
||||
kaminari (~> 0.17.0)
|
||||
knapsack (~> 1.11.0)
|
||||
kubeclient (~> 2.2.0)
|
||||
|
|
@ -965,6 +971,7 @@ DEPENDENCIES
|
|||
rqrcode-rails3 (~> 0.1.7)
|
||||
rspec-rails (~> 3.5.0)
|
||||
rspec-retry (~> 0.4.5)
|
||||
rspec_profiling
|
||||
rubocop (~> 0.46.0)
|
||||
rubocop-rspec (~> 1.9.1)
|
||||
ruby-fogbugz (~> 0.2.1)
|
||||
|
|
@ -1015,4 +1022,4 @@ DEPENDENCIES
|
|||
wikicloth (= 0.8.1)
|
||||
|
||||
BUNDLED WITH
|
||||
1.13.7
|
||||
1.14.2
|
||||
|
|
|
|||
102
PROCESS.md
102
PROCESS.md
|
|
@ -12,106 +12,54 @@ etc.).
|
|||
|
||||
## Common actions
|
||||
|
||||
### Issue team
|
||||
### Issue triaging
|
||||
|
||||
- Looks for issues without [workflow labels](#how-we-handle-issues) and triages
|
||||
issue
|
||||
- Closes invalid issues with a comment (duplicates,
|
||||
[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
|
||||
Our issue triage policies are [described in our handbook]. You are very welcome
|
||||
to help the GitLab team triage issues. We also organize [issue bash events] once
|
||||
every quarter.
|
||||
|
||||
The most important thing is making sure valid issues receive feedback from the
|
||||
development team. Therefore the priority is mentioning developers that can help
|
||||
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
|
||||
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
|
||||
core team members will mention this person.
|
||||
|
||||
[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
|
||||
|
||||
### Merge request coaching
|
||||
|
||||
Several people from the [GitLab team][team] are helping community members to get
|
||||
their contributions accepted by meeting our [Definition of done][CONTRIBUTING.md#definition-of-done].
|
||||
|
||||
What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/.
|
||||
|
||||
## Workflow labels
|
||||
|
||||
Workflow labels are purposely not very detailed since that would be hard to keep
|
||||
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.
|
||||
Labelling issues is described in the [GitLab Inc engineering workflow].
|
||||
|
||||
- ~"Awaiting Feedback" Feedback pending from the reporter
|
||||
- ~UX needs help from a UX designer
|
||||
- ~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.
|
||||
|
||||
## Functional labels
|
||||
|
||||
These labels describe what development specialities are involved such as: `CI`,
|
||||
`Core`, `Documentation`, `Frontend`, `Issues`, `Merge Requests`, `Omnibus`,
|
||||
`Release`, `Repository`, `UX`.
|
||||
[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-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.
|
||||
|
||||
## 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 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
|
||||
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
|
||||
the [Thoughtbot code review guide].
|
||||
star, smile, etc.). Some good tips about code reviews can be found in our
|
||||
[Code Review Guidelines].
|
||||
|
||||
[Code Review Guidelines]: https://docs.gitlab.com/ce/development/code_review.html
|
||||
|
||||
## 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
|
||||
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
|
||||
|
|
@ -120,10 +68,9 @@ things.
|
|||
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
|
||||
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
|
||||
manager.
|
||||
decision will be made by the maintainers and the release managers.
|
||||
|
||||
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
|
||||
~"Pick into Stable" set. Merge requests without a milestone and this label will
|
||||
not be merged into any stable branches.
|
||||
|
|
@ -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
|
||||
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/
|
||||
[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
|
||||
|
|
|
|||
|
|
@ -84,7 +84,6 @@
|
|||
var $sidebarGutterToggle = $('.js-sidebar-toggle');
|
||||
var $flash = $('.flash-container');
|
||||
var bootstrapBreakpoint = bp.getBreakpointSize();
|
||||
var checkInitialSidebarSize;
|
||||
var fitSidebarForSize;
|
||||
|
||||
// Set the default path for all cookies to GitLab's root directory
|
||||
|
|
@ -246,19 +245,11 @@
|
|||
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 () {
|
||||
return fitSidebarForSize();
|
||||
});
|
||||
gl.awardsHandler = new AwardsHandler();
|
||||
checkInitialSidebarSize();
|
||||
new Aside();
|
||||
|
||||
// bind sidebar events
|
||||
new gl.Sidebar();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */
|
||||
/* global autosize */
|
||||
|
||||
/*= require jquery.ba-resize */
|
||||
/*= require autosize */
|
||||
|
||||
(function() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,19 @@
|
|||
/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */
|
||||
(function(w) {
|
||||
$(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.
|
||||
// Button does not change visibility. If button has icon - it changes chevron style.
|
||||
//
|
||||
|
|
@ -10,14 +23,7 @@
|
|||
//
|
||||
$('body').on('click', '.js-toggle-button', function(e) {
|
||||
e.preventDefault();
|
||||
$(this)
|
||||
.find('.fa')
|
||||
.toggleClass('fa-chevron-down fa-chevron-up')
|
||||
.end()
|
||||
.closest('.js-toggle-container')
|
||||
.find('.js-toggle-content')
|
||||
.toggle()
|
||||
;
|
||||
toggleContainer($(this).closest('.js-toggle-container'));
|
||||
});
|
||||
|
||||
// If we're accessing a permalink, ensure it is not inside a
|
||||
|
|
@ -26,8 +32,8 @@
|
|||
var anchor = hash && document.getElementById(hash);
|
||||
var container = anchor && $(anchor).closest('.js-toggle-container');
|
||||
|
||||
if (container && container.find('.js-toggle-content').is(':hidden')) {
|
||||
container.find('.js-toggle-button').trigger('click');
|
||||
if (container) {
|
||||
toggleContainer(container, true);
|
||||
anchor.scrollIntoView();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,6 +29,12 @@
|
|||
watch: {
|
||||
detail: {
|
||||
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;
|
||||
},
|
||||
deep: true
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
(function() {
|
||||
this.CommitsList = (function() {
|
||||
function CommitsList() {}
|
||||
var CommitsList = {};
|
||||
|
||||
CommitsList.timer = null;
|
||||
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
});
|
||||
this.content = $("#commits-list");
|
||||
this.searchField = $("#commits-search");
|
||||
this.lastSearch = this.searchField.val();
|
||||
return this.initSearch();
|
||||
};
|
||||
|
||||
|
|
@ -37,6 +38,7 @@
|
|||
var commitsUrl, form, search;
|
||||
form = $(".commits-search-form");
|
||||
search = CommitsList.searchField.val();
|
||||
if (search === CommitsList.lastSearch) return;
|
||||
commitsUrl = form.attr("action") + '?' + form.serialize();
|
||||
CommitsList.content.fadeTo('fast', 0.5);
|
||||
return $.ajax({
|
||||
|
|
@ -47,12 +49,16 @@
|
|||
return CommitsList.content.fadeTo('fast', 1.0);
|
||||
},
|
||||
success: function(data) {
|
||||
CommitsList.lastSearch = search;
|
||||
CommitsList.content.html(data.html);
|
||||
return history.replaceState({
|
||||
page: commitsUrl
|
||||
// Change url so if user reload a page - search results are saved
|
||||
}, document.title, commitsUrl);
|
||||
},
|
||||
error: function() {
|
||||
CommitsList.lastSearch = null;
|
||||
},
|
||||
dataType: "json"
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
|
||||
//= require lib/utils/url_utility */
|
||||
|
||||
(() => {
|
||||
const UNFOLD_COUNT = 20;
|
||||
|
||||
|
|
@ -104,11 +106,11 @@
|
|||
}
|
||||
|
||||
highlighSelectedLine() {
|
||||
const hash = gl.utils.getLocationHash();
|
||||
const $diffFiles = $('.diff-file');
|
||||
$diffFiles.find('.hll').removeClass('hll');
|
||||
|
||||
if (window.location.hash !== '') {
|
||||
const hash = window.location.hash.replace('#', '');
|
||||
if (hash) {
|
||||
$diffFiles
|
||||
.find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`)
|
||||
.addClass('hll');
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
/* global ShortcutsIssuable */
|
||||
/* global ZenMode */
|
||||
/* global Milestone */
|
||||
/* global GLForm */
|
||||
/* global IssuableForm */
|
||||
/* global LabelsSelect */
|
||||
/* global MilestoneSelect */
|
||||
|
|
@ -64,17 +63,6 @@
|
|||
new UsernameValidator();
|
||||
new ActiveTabMemoizer();
|
||||
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:index':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
|
|
@ -110,7 +98,7 @@
|
|||
case 'projects:milestones:edit':
|
||||
new ZenMode();
|
||||
new gl.DueDateSelectors();
|
||||
new GLForm($('.milestone-form'));
|
||||
new gl.GLForm($('.milestone-form'));
|
||||
break;
|
||||
case 'groups:milestones:new':
|
||||
new ZenMode();
|
||||
|
|
@ -121,7 +109,7 @@
|
|||
case 'projects:issues:new':
|
||||
case 'projects:issues:edit':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
new GLForm($('.issue-form'));
|
||||
new gl.GLForm($('.issue-form'));
|
||||
new IssuableForm($('.issue-form'));
|
||||
new LabelsSelect();
|
||||
new MilestoneSelect();
|
||||
|
|
@ -131,7 +119,7 @@
|
|||
case 'projects:merge_requests:edit':
|
||||
new gl.Diff();
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
new GLForm($('.merge-request-form'));
|
||||
new gl.GLForm($('.merge-request-form'));
|
||||
new IssuableForm($('.merge-request-form'));
|
||||
new LabelsSelect();
|
||||
new MilestoneSelect();
|
||||
|
|
@ -139,11 +127,11 @@
|
|||
break;
|
||||
case 'projects:tags:new':
|
||||
new ZenMode();
|
||||
new GLForm($('.tag-form'));
|
||||
new gl.GLForm($('.tag-form'));
|
||||
break;
|
||||
case 'projects:releases:edit':
|
||||
new ZenMode();
|
||||
new GLForm($('.release-form'));
|
||||
new gl.GLForm($('.release-form'));
|
||||
break;
|
||||
case 'projects:merge_requests:show':
|
||||
new gl.Diff();
|
||||
|
|
@ -280,6 +268,17 @@
|
|||
break;
|
||||
}
|
||||
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':
|
||||
new Admin();
|
||||
switch (path[1]) {
|
||||
|
|
@ -332,7 +331,7 @@
|
|||
new gl.Wikis();
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
new ZenMode();
|
||||
new GLForm($('.wiki-form'));
|
||||
new gl.GLForm($('.wiki-form'));
|
||||
break;
|
||||
case 'snippets':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
|
|
@ -357,7 +356,7 @@
|
|||
}
|
||||
// If we haven't installed a custom shortcut handler, install the default one
|
||||
if (!shortcut_handler) {
|
||||
return new Shortcuts();
|
||||
new Shortcuts();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ require('../window')(function(w){
|
|||
|
||||
w.droplabAjax = {
|
||||
_loadUrlData: function _loadUrlData(url) {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
var xhr = new XMLHttpRequest;
|
||||
xhr.open('GET', url, true);
|
||||
|
|
@ -16,6 +17,7 @@ require('../window')(function(w){
|
|||
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]);
|
||||
|
|
@ -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) {
|
||||
var self = this;
|
||||
self.cache = self.cache || {};
|
||||
var config = hook.config.droplabAjax;
|
||||
this.hook = hook;
|
||||
|
||||
|
|
@ -50,22 +65,16 @@ require('../window')(function(w){
|
|||
dynamicList.outerHTML = loadingTemplate.outerHTML;
|
||||
}
|
||||
|
||||
this._loadUrlData(config.endpoint)
|
||||
.then(function(d) {
|
||||
if (config.loadingTemplate) {
|
||||
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) {
|
||||
throw new droplabAjaxException(e.message || e);
|
||||
});
|
||||
if (self.cache[config.endpoint]) {
|
||||
self._loadData(self.cache[config.endpoint], config, self);
|
||||
} else {
|
||||
this._loadUrlData(config.endpoint)
|
||||
.then(function(d) {
|
||||
self._loadData(d, config, self);
|
||||
}).catch(function(e) {
|
||||
throw new droplabAjaxException(e.message || e);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
|
|
|
|||
|
|
@ -72,32 +72,22 @@ require('../window')(function(w){
|
|||
var params = config.params || {};
|
||||
params[config.searchKey] = searchValue;
|
||||
var self = this;
|
||||
this._loadUrlData(config.endpoint + this.buildParams(params)).then(function(data) {
|
||||
if (config.loadingTemplate && self.hook.list.data === undefined ||
|
||||
self.hook.list.data.length === 0) {
|
||||
const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
|
||||
self.cache = self.cache || {};
|
||||
var url = config.endpoint + this.buildParams(params);
|
||||
var urlCachedData = self.cache[url];
|
||||
|
||||
if (dataLoadingTemplate) {
|
||||
dataLoadingTemplate.outerHTML = self.listTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.destroyed) {
|
||||
var hookListChildren = self.hook.list.list.children;
|
||||
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
|
||||
|
||||
if (onlyDynamicList && data.length === 0) {
|
||||
self.hook.list.hide();
|
||||
}
|
||||
|
||||
self.hook.list.setData.call(self.hook.list, data);
|
||||
}
|
||||
self.notLoading();
|
||||
self.hook.list.currentIndex = 0;
|
||||
});
|
||||
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);
|
||||
|
|
@ -105,6 +95,7 @@ require('../window')(function(w){
|
|||
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]);
|
||||
|
|
@ -115,6 +106,30 @@ require('../window')(function(w){
|
|||
});
|
||||
},
|
||||
|
||||
_loadData: function _loadData(data, config, self) {
|
||||
if (config.loadingTemplate && self.hook.list.data === undefined ||
|
||||
self.hook.list.data.length === 0) {
|
||||
const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
|
||||
|
||||
if (dataLoadingTemplate) {
|
||||
dataLoadingTemplate.outerHTML = self.listTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.destroyed) {
|
||||
var hookListChildren = self.hook.list.list.children;
|
||||
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
|
||||
|
||||
if (onlyDynamicList && data.length === 0) {
|
||||
self.hook.list.hide();
|
||||
}
|
||||
|
||||
self.hook.list.setData.call(self.hook.list, data);
|
||||
}
|
||||
self.notLoading();
|
||||
self.hook.list.currentIndex = 0;
|
||||
},
|
||||
|
||||
buildParams: function(params) {
|
||||
if (!params) return '';
|
||||
var paramsArray = Object.keys(params).map(function(param) {
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@
|
|||
<th class="environments-deploy">Last deployment</th>
|
||||
<th class="environments-build">Build</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>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
|||
|
|
@ -39,8 +39,15 @@
|
|||
getSearchInput() {
|
||||
const query = gl.DropdownUtils.getSearchInput(this.input);
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,12 @@
|
|||
if (lastToken !== searchToken) {
|
||||
const title = updatedItem.title.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
|
||||
const matchWithoutSymbol = lastToken.symbol === filterSymbol && title.indexOf(value) !== -1;
|
||||
|
|
@ -83,8 +88,9 @@
|
|||
const selectionStart = input.selectionStart;
|
||||
let inputValue = input.value;
|
||||
// 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
|
||||
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
|
||||
// Regex matches first space
|
||||
|
|
|
|||
|
|
@ -196,7 +196,8 @@
|
|||
});
|
||||
|
||||
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('&')}`);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,15 @@
|
|||
symbol: '~',
|
||||
}];
|
||||
|
||||
const alternativeTokenKeys = [{
|
||||
key: 'label',
|
||||
type: 'string',
|
||||
param: 'name',
|
||||
symbol: '~',
|
||||
}];
|
||||
|
||||
const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
|
||||
|
||||
const conditions = [{
|
||||
url: 'assignee_id=0',
|
||||
tokenKey: 'assignee',
|
||||
|
|
@ -44,6 +53,10 @@
|
|||
return tokenKeys;
|
||||
}
|
||||
|
||||
static getAlternatives() {
|
||||
return alternativeTokenKeys;
|
||||
}
|
||||
|
||||
static getConditions() {
|
||||
return conditions;
|
||||
}
|
||||
|
|
@ -57,7 +70,7 @@
|
|||
}
|
||||
|
||||
static searchByKeyParam(keyParam) {
|
||||
return tokenKeys.find((tokenKey) => {
|
||||
return tokenKeysWithAlternative.find((tokenKey) => {
|
||||
let tokenKeyParam = tokenKey.key;
|
||||
|
||||
if (tokenKey.param) {
|
||||
|
|
|
|||
|
|
@ -512,12 +512,17 @@
|
|||
|
||||
// Append the menu into the dropdown
|
||||
GitLabDropdown.prototype.appendMenu = function(html) {
|
||||
return this.clearMenu().append(html);
|
||||
};
|
||||
|
||||
GitLabDropdown.prototype.clearMenu = function() {
|
||||
var selector;
|
||||
selector = '.dropdown-content';
|
||||
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
/* 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);
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/* 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 @@
|
|||
} else {
|
||||
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) {
|
||||
return group.name;
|
||||
return group.full_name;
|
||||
};
|
||||
|
||||
return GroupsSelect;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
/* 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 Cookies */
|
||||
/* global bp */
|
||||
|
||||
(function() {
|
||||
this.IssuableContext = (function() {
|
||||
|
|
@ -37,6 +39,13 @@
|
|||
}, 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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels');
|
||||
this.otherLabels = otherLabels || $('.js-other-labels');
|
||||
this.errorMessage = 'Unable to update label prioritization at this time';
|
||||
this.emptyState = document.querySelector('#js-priority-labels-empty-state');
|
||||
this.prioritizedLabels.sortable({
|
||||
items: 'li',
|
||||
placeholder: 'list-placeholder',
|
||||
|
|
@ -29,7 +30,12 @@
|
|||
const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
|
||||
const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`);
|
||||
$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) {
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
/*= require ace-rails-ap */
|
||||
/*= require ace/ace */
|
||||
/*= require ace/ext-searchbox */
|
||||
/*= require ./ace/ace_config_paths */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
<%
|
||||
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 @@
|
|||
|
||||
w.gl.utils.getSelectedFragment = () => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.rangeCount === 0) return null;
|
||||
const documentFragment = selection.getRangeAt(0).cloneContents();
|
||||
if (documentFragment.textContent.length === 0) return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -74,8 +74,9 @@
|
|||
// 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
|
||||
// around the link and/or a persisted underline text decoration.
|
||||
return $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
|
||||
return event.preventDefault();
|
||||
$('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
/* 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 GLForm */
|
||||
/* global Autosave */
|
||||
/* global ResolveService */
|
||||
/* global mrRefreshWidgetUrl */
|
||||
|
|
@ -420,7 +419,7 @@
|
|||
|
||||
Notes.prototype.setupNoteForm = function(form) {
|
||||
var textarea;
|
||||
new GLForm(form);
|
||||
new gl.GLForm(form);
|
||||
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()]);
|
||||
};
|
||||
|
|
@ -884,7 +883,7 @@
|
|||
var targetId = $originalContentEl.data('target-id');
|
||||
var targetType = $originalContentEl.data('target-type');
|
||||
|
||||
new GLForm($editForm.find('form'));
|
||||
new gl.GLForm($editForm.find('form'));
|
||||
|
||||
$editForm.find('form')
|
||||
.attr('action', postUrl)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
}
|
||||
|
||||
onSubmitForm(e) {
|
||||
e.preventDefault();
|
||||
return this.saveForm();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@
|
|||
filterable: true,
|
||||
fieldName: 'group_id',
|
||||
search: {
|
||||
fields: ['name']
|
||||
fields: ['full_name']
|
||||
},
|
||||
data: function(term, callback) {
|
||||
return Api.groups(term, {}, function(data) {
|
||||
data.unshift({
|
||||
name: 'Any'
|
||||
full_name: 'Any'
|
||||
});
|
||||
data.splice(1, 0, 'divider');
|
||||
return callback(data);
|
||||
|
|
@ -28,10 +28,10 @@
|
|||
return obj.id;
|
||||
},
|
||||
text: function(obj) {
|
||||
return obj.name;
|
||||
return obj.full_name;
|
||||
},
|
||||
toggleLabel: function(obj) {
|
||||
return ($groupDropdown.data('default-label')) + " " + obj.name;
|
||||
return ($groupDropdown.data('default-label')) + " " + obj.full_name;
|
||||
},
|
||||
clicked: (function(_this) {
|
||||
return function() {
|
||||
|
|
|
|||
|
|
@ -39,17 +39,20 @@
|
|||
}
|
||||
|
||||
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();
|
||||
if (!documentFragment) return;
|
||||
if (!documentFragment) {
|
||||
replyField.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the documentFragment contains more than just Markdown, don't copy as GFM.
|
||||
if (documentFragment.querySelector('.md, .wiki')) return;
|
||||
|
||||
selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment);
|
||||
|
||||
replyField = $('.js-main-target-form #note_note');
|
||||
if (selected.trim() === "") {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@
|
|||
},
|
||||
success: (data) => {
|
||||
$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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@
|
|||
};
|
||||
|
||||
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;
|
||||
}).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) {
|
||||
return function(date) {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@
|
|||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
margin-bottom: 2px;
|
||||
flex-shrink: 0;
|
||||
-webkit-flex-shrink: 0;
|
||||
|
||||
&.s16 { margin-right: 4px; }
|
||||
&.s24 { margin-right: 4px; }
|
||||
|
|
|
|||
|
|
@ -278,6 +278,10 @@
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin: $btn-side-margin $btn-side-margin 0 0;
|
||||
}
|
||||
|
||||
@media(max-width: $screen-xs-max) {
|
||||
margin-top: 50px;
|
||||
text-align: center;
|
||||
|
|
@ -286,6 +290,12 @@
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media(min-width: $screen-xs-max) {
|
||||
&.labels .text-content {
|
||||
margin-top: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flex-container-block {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
.calender-block {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
direction: rtl;
|
||||
|
||||
@media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
|
||||
overflow-x: scroll;
|
||||
|
|
|
|||
|
|
@ -125,7 +125,8 @@
|
|||
top: 100%;
|
||||
left: 0;
|
||||
z-index: 9;
|
||||
width: 240px;
|
||||
max-width: 280px;
|
||||
min-width: 240px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
|
|
|
|||
|
|
@ -132,6 +132,11 @@
|
|||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
|
||||
&> span {
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,10 +146,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.hint-dropdown {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.filter-dropdown-loading {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ header {
|
|||
&:focus,
|
||||
&:active {
|
||||
background-color: $gray-light;
|
||||
color: darken($gl-text-color-secondary, 30%);
|
||||
color: $gl-text-color;
|
||||
|
||||
.todos-pending-count {
|
||||
background: darken($todo-alert-blue, 10%);
|
||||
|
|
|
|||
|
|
@ -58,3 +58,9 @@
|
|||
fill: $gl-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-link {
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -294,16 +294,18 @@
|
|||
|
||||
.container-fluid {
|
||||
position: relative;
|
||||
|
||||
.nav-control {
|
||||
@media (max-width: $screen-sm-max) {
|
||||
margin-right: 75px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.controls {
|
||||
float: right;
|
||||
padding: 7px 0 0;
|
||||
|
||||
@media (max-width: $screen-sm-max) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
i {
|
||||
color: $layout-link-gray;
|
||||
}
|
||||
|
|
@ -361,6 +363,7 @@
|
|||
.fade-left {
|
||||
@include fade(right, $gray-light);
|
||||
left: -5px;
|
||||
text-align: center;
|
||||
|
||||
.fa {
|
||||
left: -7px;
|
||||
|
|
|
|||
|
|
@ -162,6 +162,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.panel-without-border {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-succes .panel-heading,
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ $count-arrow-border: #dce0e5;
|
|||
$save-project-loader-color: #555;
|
||||
$divergence-graph-bar-bg: #ccc;
|
||||
$divergence-graph-separator-bg: #ccc;
|
||||
$general-hover-transition-duration: 150ms;
|
||||
$general-hover-transition-duration: 100ms;
|
||||
$general-hover-transition-curve: linear;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@
|
|||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
@extend .fixed-width-container;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -420,10 +420,6 @@
|
|||
.merge-request-tabs-holder {
|
||||
background-color: $white-light;
|
||||
|
||||
.container-limited {
|
||||
max-width: $limited-layout-width;
|
||||
}
|
||||
|
||||
&.affix {
|
||||
top: 100px;
|
||||
left: 0;
|
||||
|
|
@ -433,10 +429,26 @@
|
|||
@media (max-width: $screen-xs-max) {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.affix) .container-fluid {
|
||||
padding-left: 0;
|
||||
padding-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;
|
||||
}
|
||||
}
|
||||
|
||||
.limit-container-width:not(.container-limited) {
|
||||
.merge-request-tabs-holder:not(.affix) {
|
||||
.merge-request-tabs-container {
|
||||
max-width: $limited-layout-width - ($gl-padding * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -214,9 +214,9 @@
|
|||
&:not(:last-child) {
|
||||
&::after {
|
||||
content: '';
|
||||
width: 8px;
|
||||
width: 7px;
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
right: -7px;
|
||||
top: 10px;
|
||||
border-bottom: 2px solid $border-color;
|
||||
}
|
||||
|
|
@ -494,31 +494,27 @@
|
|||
|
||||
// Action Icons in big pipeline-graph nodes
|
||||
> .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;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
background: $white-light;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 100%;
|
||||
display: block;
|
||||
|
||||
&::before {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $gl-text-color;
|
||||
background-color: $stage-hover-bg;
|
||||
border: 1px solid $stage-hover-bg;
|
||||
}
|
||||
&:hover {
|
||||
background-color: $stage-hover-bg;
|
||||
border: 1px solid $stage-hover-bg;
|
||||
}
|
||||
|
||||
.ci-play-icon {
|
||||
padding: 5px 5px 5px 7px;
|
||||
svg {
|
||||
fill: $border-color;
|
||||
position: relative;
|
||||
left: -1px;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
&:hover svg {
|
||||
fill: $gl-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -657,7 +653,7 @@
|
|||
font-weight: 100;
|
||||
font-size: 15px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
right: 13px;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
|
|
@ -825,11 +821,23 @@
|
|||
|
||||
&:hover,
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
color: $gl-text-color;
|
||||
background-color: $stage-hover-bg;
|
||||
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
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@
|
|||
margin: 15px 5px 0 0;
|
||||
|
||||
input {
|
||||
height: 27px;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -523,7 +523,7 @@ a.deploy-project-label {
|
|||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: darken($notes-light-color, 15%);
|
||||
color: $gl-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@
|
|||
font-size: 14px;
|
||||
}
|
||||
|
||||
.action-name {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.todo-body {
|
||||
.todo-note {
|
||||
word-wrap: break-word;
|
||||
|
|
|
|||
|
|
@ -18,15 +18,14 @@ class AutocompleteController < ApplicationController
|
|||
if params[:search].blank?
|
||||
# Include current user if available to filter by "Me"
|
||||
if params[:current_user].present? && current_user
|
||||
@users = @users.where.not(id: current_user.id)
|
||||
@users = [current_user, *@users]
|
||||
end
|
||||
|
||||
if params[:author_id].present?
|
||||
author = User.find_by_id(params[:author_id])
|
||||
@users = [author, *@users] if author
|
||||
@users = [author, *@users].uniq if author
|
||||
end
|
||||
|
||||
@users.uniq!
|
||||
end
|
||||
|
||||
render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ class DashboardController < Dashboard::ApplicationController
|
|||
|
||||
before_action :event_filter, only: :activity
|
||||
before_action :projects, only: [:issues, :merge_requests]
|
||||
before_action :set_show_full_reference, only: [:issues, :merge_requests]
|
||||
|
||||
respond_to :html
|
||||
|
||||
|
|
@ -34,4 +35,8 @@ class DashboardController < Dashboard::ApplicationController
|
|||
@events = @event_filter.apply_filter(@events).with_associations
|
||||
@events = @events.limit(20).offset(params[:offset] || 0)
|
||||
end
|
||||
|
||||
def set_show_full_reference
|
||||
@show_full_reference = true
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class Explore::ProjectsController < Explore::ApplicationController
|
|||
|
||||
def trending
|
||||
@projects = filter_projects(Project.trending)
|
||||
@projects = @projects.sort(@sort = params[:sort])
|
||||
@projects = @projects.page(params[:page])
|
||||
|
||||
respond_to do |format|
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class Projects::BuildsController < Projects::ApplicationController
|
|||
private
|
||||
|
||||
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
|
||||
|
||||
def build_path(build)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,17 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
def branches
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ class Projects::LabelsController < Projects::ApplicationController
|
|||
include ToggleSubscriptionAction
|
||||
|
||||
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 :authorize_read_label!
|
||||
before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update,
|
||||
:generate, :destroy, :remove_priority,
|
||||
:set_priorities]
|
||||
before_action :authorize_admin_group!, only: [:promote]
|
||||
|
||||
respond_to :js, :html
|
||||
|
||||
|
|
@ -71,13 +72,7 @@ class Projects::LabelsController < Projects::ApplicationController
|
|||
@label.destroy
|
||||
@labels = find_labels
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
redirect_to(namespace_project_labels_path(@project.namespace, @project),
|
||||
notice: 'Label was removed')
|
||||
end
|
||||
format.js
|
||||
end
|
||||
redirect_to(namespace_project_labels_path(@project.namespace, @project), notice: 'Label was removed')
|
||||
end
|
||||
|
||||
def remove_priority
|
||||
|
|
@ -108,6 +103,32 @@ class Projects::LabelsController < Projects::ApplicationController
|
|||
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
|
||||
|
||||
def module_enabled
|
||||
|
|
@ -135,4 +156,8 @@ class Projects::LabelsController < Projects::ApplicationController
|
|||
def authorize_admin_labels!
|
||||
return render_404 unless can?(current_user, :admin_label, @project)
|
||||
end
|
||||
|
||||
def authorize_admin_group!
|
||||
return render_404 unless can?(current_user, :admin_group, @project.group)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class Projects::MattermostsController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def teams
|
||||
@teams ||= @service.list_teams(current_user)
|
||||
@teams, @teams_error_message = @service.list_teams(current_user)
|
||||
end
|
||||
|
||||
def service
|
||||
|
|
|
|||
|
|
@ -214,7 +214,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
|
||||
render 'show'
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -32,12 +32,6 @@ class Projects::RefsController < Projects::ApplicationController
|
|||
|
||||
redirect_to new_path
|
||||
end
|
||||
format.js do
|
||||
@ref = params[:ref]
|
||||
define_tree_vars
|
||||
tree
|
||||
render "tree"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
# For users who haven't customized the setting, we simply delegate to
|
||||
# `DashboardController#show`, which is the default.
|
||||
class RootController < Dashboard::ProjectsController
|
||||
skip_before_action :authenticate_user!, only: [:index]
|
||||
before_action :redirect_to_custom_dashboard, only: [:index]
|
||||
|
||||
def index
|
||||
|
|
@ -16,7 +17,7 @@ class RootController < Dashboard::ProjectsController
|
|||
private
|
||||
|
||||
def redirect_to_custom_dashboard
|
||||
return unless current_user
|
||||
return redirect_to new_user_session_path unless current_user
|
||||
|
||||
case current_user.dashboard
|
||||
when 'stars'
|
||||
|
|
|
|||
|
|
@ -162,6 +162,10 @@ module IssuablesHelper
|
|||
]
|
||||
end
|
||||
|
||||
def issuable_reference(issuable)
|
||||
@show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
|
||||
end
|
||||
|
||||
def issuable_filter_present?
|
||||
issuable_filter_params.any? { |k| params.key?(k) }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ module SearchHelper
|
|||
{
|
||||
category: "Groups",
|
||||
id: group.id,
|
||||
label: "#{search_result_sanitize(group.name)}",
|
||||
label: "#{search_result_sanitize(group.full_name)}",
|
||||
url: group_path(group)
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,49 +13,6 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
[\r\n] # any number of newline characters
|
||||
}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 :import_sources
|
||||
serialize :disabled_oauth_sign_in_sources, Array
|
||||
|
|
@ -199,14 +156,64 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
|
||||
def self.expire
|
||||
Rails.cache.delete(CACHE_KEY)
|
||||
rescue
|
||||
# Gracefully handle when Redis is not available. For example,
|
||||
# omnibus may fail here during gitlab:assets:compile.
|
||||
end
|
||||
|
||||
def self.cached
|
||||
Rails.cache.fetch(CACHE_KEY)
|
||||
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
|
||||
create(DEFAULTS)
|
||||
create(defaults)
|
||||
end
|
||||
|
||||
def home_page_url_column_exist
|
||||
|
|
|
|||
|
|
@ -100,8 +100,8 @@ class Commit
|
|||
commit_reference(from_project, id, full: full)
|
||||
end
|
||||
|
||||
def reference_link_text(from_project = nil)
|
||||
commit_reference(from_project, short_id)
|
||||
def reference_link_text(from_project = nil, full: false)
|
||||
commit_reference(from_project, short_id, full: full)
|
||||
end
|
||||
|
||||
def diff_line_count
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
class Environment < ActiveRecord::Base
|
||||
# Used to generate random suffixes for the slug
|
||||
LETTERS = 'a'..'z'
|
||||
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
|
||||
|
||||
|
|
@ -148,17 +149,24 @@ class Environment < ActiveRecord::Base
|
|||
slugified = name.to_s.downcase.gsub(/[^a-z0-9]/, '-')
|
||||
|
||||
# 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)
|
||||
slugified = slugified[0..23]
|
||||
|
||||
# Cannot end with a "-" character (Kubernetes label limitation)
|
||||
slugified = slugified[0..-2] if slugified[-1] == "-"
|
||||
# Cannot end with a dash (Kubernetes label limitation)
|
||||
slugified.chop! if slugified.end_with?('-')
|
||||
|
||||
# Add a random suffix, shortening the current string if necessary, if it
|
||||
# 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
|
||||
end
|
||||
|
|
|
|||
|
|
@ -97,10 +97,11 @@ class Issue < ActiveRecord::Base
|
|||
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}"
|
||||
|
||||
"#{project.to_reference(from_project, full: full)}#{reference}"
|
||||
"#{project.to_reference(from, full: full)}#{reference}"
|
||||
end
|
||||
|
||||
def referenced_merge_requests(current_user = nil)
|
||||
|
|
|
|||
|
|
@ -179,10 +179,11 @@ class MergeRequest < ActiveRecord::Base
|
|||
work_in_progress?(title) ? title : "WIP: #{title}"
|
||||
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}"
|
||||
|
||||
"#{project.to_reference(from_project, full: full)}#{reference}"
|
||||
"#{project.to_reference(from, full: full)}#{reference}"
|
||||
end
|
||||
|
||||
def first_commit
|
||||
|
|
|
|||
|
|
@ -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_statistics, -> { includes(:statistics) }
|
||||
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!
|
||||
scope :with_feature_enabled, ->(feature) {
|
||||
|
|
@ -591,10 +592,11 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def to_reference(from_project = nil, full: false)
|
||||
if full || cross_namespace_reference?(from_project)
|
||||
# `from` argument can be a Namespace or Project.
|
||||
def to_reference(from = nil, full: false)
|
||||
if full || cross_namespace_reference?(from)
|
||||
path_with_namespace
|
||||
elsif cross_project_reference?(from_project)
|
||||
elsif cross_project_reference?(from)
|
||||
path
|
||||
end
|
||||
end
|
||||
|
|
@ -1291,21 +1293,26 @@ class Project < ActiveRecord::Base
|
|||
|
||||
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
|
||||
#
|
||||
# from_project - Refering Project object
|
||||
def cross_project_reference?(from_project)
|
||||
from_project && self != from_project
|
||||
def cross_project_reference?(from)
|
||||
return true if from.is_a?(Namespace)
|
||||
|
||||
from && self != from
|
||||
end
|
||||
|
||||
def pushes_since_gc_redis_key
|
||||
"projects/#{id}/pushes_since_gc"
|
||||
end
|
||||
|
||||
def cross_namespace_reference?(from_project)
|
||||
from_project && namespace != from_project.namespace
|
||||
end
|
||||
|
||||
def default_branch_protected?
|
||||
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
|
||||
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
|
||||
|
|
|
|||
|
|
@ -31,13 +31,13 @@ class ChatSlashCommandsService < Service
|
|||
return unless valid_token?(params[:token])
|
||||
|
||||
user = find_chat_user(params)
|
||||
unless user
|
||||
url = authorize_chat_name_url(params)
|
||||
return presenter.authorize_chat_name(url)
|
||||
end
|
||||
|
||||
Gitlab::ChatCommands::Command.new(project, user,
|
||||
params).execute
|
||||
if user
|
||||
Gitlab::ChatCommands::Command.new(project, user, params).execute
|
||||
else
|
||||
url = authorize_chat_name_url(params)
|
||||
Gitlab::ChatCommands::Presenters::Access.new(url).authorize
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -49,8 +49,4 @@ class ChatSlashCommandsService < Service
|
|||
def authorize_chat_name_url(params)
|
||||
ChatNames::AuthorizeUserService.new(self, params).execute
|
||||
end
|
||||
|
||||
def presenter
|
||||
Gitlab::ChatCommands::Presenter.new
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -60,9 +60,9 @@ class JiraService < IssueTrackerService
|
|||
end
|
||||
|
||||
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
|
||||
[JIRA service documentation](https://docs.gitlab.com/ce/project_services/jira.html).'
|
||||
[JIRA service documentation](#{help_page_url('project_services/jira')})."
|
||||
end
|
||||
|
||||
def title
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ class MattermostSlashCommandsService < ChatSlashCommandsService
|
|||
[false, e.message]
|
||||
end
|
||||
|
||||
def list_teams(user)
|
||||
Mattermost::Team.new(user).all
|
||||
def list_teams(current_user)
|
||||
[Mattermost::Team.new(current_user).all, nil]
|
||||
rescue Mattermost::Error => e
|
||||
[[], e.message]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1241,7 +1241,18 @@ class Repository
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
def keep_around_ref_name(sha)
|
||||
|
|
|
|||
|
|
@ -103,9 +103,9 @@ class Todo < ActiveRecord::Base
|
|||
|
||||
def target_reference
|
||||
if for_commit?
|
||||
target.short_id
|
||||
target.reference_link_text(full: true)
|
||||
else
|
||||
target.to_reference
|
||||
target.to_reference(full: true)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
module Ci
|
||||
class BuildPolicy < CommitStatusPolicy
|
||||
def rules
|
||||
can! :read_build if @subject.project.public_builds?
|
||||
|
||||
super
|
||||
|
||||
# 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.
|
|||
class Projects::LabelsController < Projects::ApplicationController
|
||||
def edit
|
||||
@label = Gitlab::View::Presenter::Factory
|
||||
.new(@label, user: current_user)
|
||||
.new(@label, current_user: current_user)
|
||||
.fabricate!
|
||||
end
|
||||
end
|
||||
|
|
@ -132,7 +132,7 @@ and then in the controller:
|
|||
```ruby
|
||||
class Projects::LabelsController < Projects::ApplicationController
|
||||
def edit
|
||||
@label = @label.present(user: current_user)
|
||||
@label = @label.present(current_user: current_user)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
|
@ -147,7 +147,7 @@ end
|
|||
You can also present the model in the view:
|
||||
|
||||
```ruby
|
||||
- label = @label.present(current_user)
|
||||
- label = @label.present(current_user: current_user)
|
||||
|
||||
%div{ class: label.text_color }
|
||||
= render partial: label, label: label
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ class BaseSerializer
|
|||
def represent(resource, opts = {})
|
||||
self.class.entity_class
|
||||
.represent(resource, opts.merge(request: @request))
|
||||
.as_json
|
||||
end
|
||||
|
||||
def self.entity(entity_class)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
class PipelineSerializer < BaseSerializer
|
||||
entity PipelineEntity
|
||||
class InvalidResourceError < StandardError; end
|
||||
include API::Helpers::Pagination
|
||||
Struct.new('Pagination', :request, :response)
|
||||
|
||||
entity PipelineEntity
|
||||
|
||||
def represent(resource, opts = {})
|
||||
if paginated?
|
||||
raise InvalidResourceError unless resource.respond_to?(:page)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
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
|
||||
|
|
@ -1,63 +1,89 @@
|
|||
module MergeRequests
|
||||
class BuildService < MergeRequests::BaseService
|
||||
def execute
|
||||
merge_request = MergeRequest.new(params)
|
||||
|
||||
# Set MR attributes
|
||||
merge_request.can_be_created = true
|
||||
self.merge_request = MergeRequest.new(params)
|
||||
merge_request.can_be_created = true
|
||||
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
|
||||
|
||||
merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project)
|
||||
if branches_specified? && branches_valid?
|
||||
compare_branches
|
||||
assign_title_and_description
|
||||
else
|
||||
merge_request.can_be_created = false
|
||||
end
|
||||
|
||||
merge_request.target_project ||= (project.forked_from_project || project)
|
||||
merge_request.target_branch ||= merge_request.target_project.default_branch
|
||||
|
||||
messages = validate_branches(merge_request)
|
||||
return build_failed(merge_request, messages) unless messages.empty?
|
||||
|
||||
compare = CompareService.new(
|
||||
merge_request.source_project,
|
||||
merge_request.source_branch
|
||||
).execute(
|
||||
merge_request.target_project,
|
||||
merge_request.target_branch,
|
||||
)
|
||||
|
||||
merge_request.compare_commits = compare.commits
|
||||
merge_request.compare = compare
|
||||
|
||||
set_title_and_description(merge_request)
|
||||
merge_request
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_branches(merge_request)
|
||||
messages = []
|
||||
attr_accessor :merge_request
|
||||
|
||||
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
|
||||
delegate :target_branch, :source_branch, :source_project, :target_project, :compare_commits, :wip_title, :description, :errors, to: :merge_request
|
||||
|
||||
if merge_request.source_project == merge_request.target_project &&
|
||||
merge_request.target_branch == merge_request.source_branch
|
||||
def find_source_project
|
||||
source_project || project
|
||||
end
|
||||
|
||||
messages << 'You must select different branches'
|
||||
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
|
||||
|
||||
# See if source and target branches exist
|
||||
if merge_request.source_branch.present? && !merge_request.source_project.commit(merge_request.source_branch)
|
||||
messages << "Source branch \"#{merge_request.source_branch}\" does not exist"
|
||||
end
|
||||
def find_target_branch
|
||||
target_branch || target_project.default_branch
|
||||
end
|
||||
|
||||
if merge_request.target_branch.present? && !merge_request.target_project.commit(merge_request.target_branch)
|
||||
messages << "Target branch \"#{merge_request.target_branch}\" does not exist"
|
||||
end
|
||||
def branches_specified?
|
||||
params[:source_branch] && params[:target_branch]
|
||||
end
|
||||
|
||||
messages
|
||||
def branches_valid?
|
||||
validate_branches
|
||||
errors.blank?
|
||||
end
|
||||
|
||||
def compare_branches
|
||||
compare = CompareService.new(
|
||||
source_project,
|
||||
source_branch
|
||||
).execute(
|
||||
target_project,
|
||||
target_branch
|
||||
)
|
||||
|
||||
merge_request.compare_commits = compare.commits
|
||||
merge_request.compare = compare
|
||||
end
|
||||
|
||||
def validate_branches
|
||||
add_error('You must select source and target branch') unless branches_present?
|
||||
add_error('You must select different branches') if same_source_and_target?
|
||||
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?
|
||||
end
|
||||
|
||||
def add_error(message)
|
||||
errors.add(:base, message)
|
||||
end
|
||||
|
||||
def branches_present?
|
||||
target_branch.present? && source_branch.present?
|
||||
end
|
||||
|
||||
def same_source_and_target?
|
||||
source_project == target_project && target_branch == source_branch
|
||||
end
|
||||
|
||||
def source_branch_exists?
|
||||
source_branch.blank? || source_project.commit(source_branch)
|
||||
end
|
||||
|
||||
def target_branch_exists?
|
||||
target_branch.blank? || target_project.commit(target_branch)
|
||||
end
|
||||
|
||||
# When your branch name starts with an iid followed by a dash this pattern will be
|
||||
|
|
@ -72,17 +98,17 @@ module MergeRequests
|
|||
# - Setting the title as 'Resolves "Emoji don't show up in commit title"' if there is
|
||||
# more than one commit in the MR
|
||||
#
|
||||
def set_title_and_description(merge_request)
|
||||
if match = merge_request.source_branch.match(/\A(\d+)-/)
|
||||
def assign_title_and_description
|
||||
if match = source_branch.match(/\A(\d+)-/)
|
||||
iid = match[1]
|
||||
end
|
||||
|
||||
commits = merge_request.compare_commits
|
||||
commits = compare_commits
|
||||
if commits && commits.count == 1
|
||||
commit = commits.first
|
||||
merge_request.title = commit.title
|
||||
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
|
||||
when Issue
|
||||
merge_request.title = "Resolve \"#{issue.title}\""
|
||||
|
|
@ -90,31 +116,20 @@ module MergeRequests
|
|||
merge_request.title = "Resolve #{issue.title}"
|
||||
end
|
||||
else
|
||||
merge_request.title = merge_request.source_branch.titleize.humanize
|
||||
merge_request.title = source_branch.titleize.humanize
|
||||
end
|
||||
|
||||
if iid
|
||||
closes_issue = "Closes ##{iid}"
|
||||
|
||||
if merge_request.description.present?
|
||||
if description.present?
|
||||
merge_request.description += closes_issue.prepend("\n\n")
|
||||
else
|
||||
merge_request.description = closes_issue
|
||||
end
|
||||
end
|
||||
|
||||
merge_request.title = merge_request.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
|
||||
merge_request.title = wip_title if commits.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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_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
|
||||
end
|
||||
|
|
@ -415,8 +415,8 @@ class NotificationService
|
|||
end
|
||||
|
||||
# Build a list of users based on group notification settings
|
||||
def select_group_member_setting(project, project_members, global_setting, users_global_level_watch)
|
||||
uids = notification_settings_for(project, :watch)
|
||||
def select_group_member_setting(group, project_members, global_setting, users_global_level_watch)
|
||||
uids = notification_settings_for(group, :watch)
|
||||
|
||||
# Group setting is watch, add to users list if user is not project member
|
||||
users = []
|
||||
|
|
@ -473,7 +473,7 @@ class NotificationService
|
|||
|
||||
setting = user.notification_settings_for(project)
|
||||
|
||||
if !setting && project.group
|
||||
if project.group && (setting.nil? || setting.global?)
|
||||
setting = user.notification_settings_for(project.group)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ module Projects
|
|||
if project.update_attributes(params.except(:default_branch))
|
||||
if project.previous_changes.include?('path')
|
||||
project.rename_repo
|
||||
else
|
||||
system_hook_service.execute_hooks_for(project, :update)
|
||||
end
|
||||
|
||||
success
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ module Search
|
|||
def execute
|
||||
group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
|
||||
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])
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
- status = local_assigns.fetch(:status)
|
||||
- link = local_assigns.fetch(:link, true)
|
||||
- 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
|
||||
= custom_icon(status.icon)
|
||||
= status.text
|
||||
|
|
|
|||
|
|
@ -16,4 +16,4 @@
|
|||
|
||||
- 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
|
||||
= icon(status.action_icon, class: status.action_class)
|
||||
= custom_icon(status.action_icon)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
- subject = local_assigns.fetch(:subject)
|
||||
- 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}"
|
||||
|
||||
- if status.has_details?
|
||||
|
|
@ -16,5 +16,5 @@
|
|||
|
||||
- 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
|
||||
%i.ci-action-icon-wrapper
|
||||
= icon(status.action_icon, class: status.action_class)
|
||||
%i.ci-action-icon-wrapper{ class: "js-#{status.action_icon.dasherize}" }
|
||||
= custom_icon(status.action_icon)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,4 @@
|
|||
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
|
||||
|
||||
= render 'shared/issuable/filter', type: :issues
|
||||
|
||||
.prepend-top-default
|
||||
= render 'shared/issues'
|
||||
= render 'shared/issues'
|
||||
|
|
|
|||
|
|
@ -7,6 +7,4 @@
|
|||
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
|
||||
|
||||
= render 'shared/issuable/filter', type: :merge_requests
|
||||
|
||||
.prepend-top-default
|
||||
= render 'shared/merge_requests'
|
||||
= render 'shared/merge_requests'
|
||||
|
|
|
|||
|
|
@ -11,8 +11,11 @@
|
|||
= link_to_author(todo)
|
||||
- else
|
||||
(removed)
|
||||
%span.todo-label
|
||||
|
||||
%span.action-name
|
||||
= todo_action_name(todo)
|
||||
|
||||
%span.todo-label
|
||||
- if todo.target
|
||||
= todo_target_link(todo)
|
||||
- else
|
||||
|
|
|
|||
|
|
@ -67,21 +67,17 @@
|
|||
= sort_title_oldest_created
|
||||
|
||||
|
||||
.prepend-top-default
|
||||
.js-todos-all
|
||||
- if @todos.any?
|
||||
.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
|
||||
- project = group[0]
|
||||
.panel-heading
|
||||
= link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
|
||||
|
||||
.panel.panel-default.panel-small.panel-without-border
|
||||
%ul.content-list.todos-list
|
||||
= render group[1]
|
||||
= render @todos
|
||||
= paginate @todos, theme: "gitlab"
|
||||
|
||||
- elsif current_user.todos.any?
|
||||
.todos-all-done
|
||||
= render "shared/empty_states/todos_all_done.svg"
|
||||
= render "shared/empty_states/icons/todos_all_done.svg"
|
||||
- if todos_filter_empty?
|
||||
%h4.text-center
|
||||
= Gitlab.config.gitlab.no_todos_messages.sample
|
||||
|
|
@ -98,7 +94,7 @@
|
|||
- else
|
||||
.todos-empty
|
||||
.todos-empty-hero
|
||||
= render "shared/empty_states/todos_empty.svg"
|
||||
= render "shared/empty_states/icons/todos_empty.svg"
|
||||
.todos-empty-content
|
||||
%h4
|
||||
Todos let you see what you should do next.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@
|
|||
- if current_user
|
||||
To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
|
||||
|
||||
.prepend-top-default
|
||||
= render 'shared/issues'
|
||||
= render 'shared/issues'
|
||||
- else
|
||||
= render 'shared/empty_states/issues', project_select_button: true
|
||||
|
|
|
|||
|
|
@ -15,5 +15,4 @@
|
|||
- if current_user
|
||||
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'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
= render 'layouts/nav/admin_settings'
|
||||
.scrolling-tabs-container{ class: nav_control_class }
|
||||
= render 'layouts/nav/admin_settings'
|
||||
.fade-left
|
||||
= icon('angle-left')
|
||||
.fade-right
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
.content-block.build-header
|
||||
.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
|
||||
%strong ##{@build.id}
|
||||
%strong.js-build-id ##{@build.id}
|
||||
in pipeline
|
||||
= link_to pipeline_path(@build.pipeline) do
|
||||
%strong ##{@build.pipeline.id}
|
||||
|
|
|
|||
|
|
@ -63,9 +63,10 @@
|
|||
- if @commit.status
|
||||
.well-segment.pipeline-info
|
||||
%div{ class: "icon-container ci-status-icon-#{@commit.status}" }
|
||||
= ci_icon_for_status(@commit.status)
|
||||
= link_to namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id) do
|
||||
= ci_icon_for_status(@commit.status)
|
||||
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
|
||||
= link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
|
||||
%span.ci-status-label
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
- page_title "Pipelines", "#{@commit.title} (#{@commit.short_id})", "Commits"
|
||||
- page_title 'Pipelines', "#{@commit.title} (#{@commit.short_id})", 'Commits'
|
||||
|
||||
= render "commit_box"
|
||||
|
||||
= render "ci_menu"
|
||||
= render "pipelines_list", pipelines: @commit.pipelines.order(id: :desc)
|
||||
= render 'commit_box'
|
||||
= render 'ci_menu'
|
||||
= render 'pipelines_list', pipelines: @pipelines
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
%fieldset.append-bottom-0
|
||||
.row
|
||||
.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
|
||||
= f.text_field :name, class: "form-control", id: "project_name_edit"
|
||||
|
||||
|
|
@ -183,6 +183,8 @@
|
|||
%li Build traces and artifacts
|
||||
%li LFS objects
|
||||
%li Container registry images
|
||||
%li CI variables
|
||||
%li Any encrypted tokens
|
||||
%hr
|
||||
- if can? current_user, :archive_project, @project
|
||||
.row.prepend-top-default
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
%div{ class: container_class }
|
||||
.top-area.adjust
|
||||
.col-md-9
|
||||
%h3.page-title= @environment.name.capitalize
|
||||
%h3.page-title= @environment.name
|
||||
.col-md-3
|
||||
.nav-controls
|
||||
= render 'projects/environments/terminal_button', environment: @environment
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
%th ID
|
||||
%th Commit
|
||||
%th Build
|
||||
%th
|
||||
%th Created
|
||||
%th.hidden-xs
|
||||
|
||||
= render @deployments
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
= note_count
|
||||
|
||||
.issue-info
|
||||
#{issue.to_reference} ·
|
||||
#{issuable_reference(issue)} ·
|
||||
opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
|
||||
by #{link_to_member(@project, issue.author, avatar: false)}
|
||||
- if issue.milestone
|
||||
|
|
|
|||
|
|
@ -19,10 +19,8 @@
|
|||
= render 'shared/issuable/nav', type: :issues
|
||||
.nav-controls
|
||||
- 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')
|
||||
%span.icon-label
|
||||
Subscribe
|
||||
- if can? current_user, :create_issue, @project
|
||||
= link_to new_namespace_project_issue_path(@project.namespace,
|
||||
@project,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue