Merge branch 'master' into go-go-gadget-webpack
* master: (83 commits) Fix small typo on new branch button spec Abillity to promote project labels to group labels 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. cop for gem fetched from a git source Add CHANGELOG entry Remove new branch button for confidential issues Remove flash warning from login page add complete changelog Add project ID index to `project_authorizations` table to optimize queries Fix disable storing of sensitive information when importing a new repo Fix notification when global=disabled, group=watch Ensure rake is called within the correct bundle context Fix 8.16.0 release date. Improve search within group logic Add changelog item about new attributes in group api Update group api doc with full_name and full_path attributes Add tests for nested groups in search service and search helper ...
|
@ -273,7 +273,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
|
||||
|
@ -303,7 +303,7 @@ karma:
|
|||
<<: *dedicated-runner
|
||||
script:
|
||||
- npm link istanbul
|
||||
- rake karma
|
||||
- bundle exec rake karma
|
||||
artifacts:
|
||||
name: coverage-javascript
|
||||
expire_in: 31d
|
||||
|
@ -354,10 +354,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
|
||||
|
|
|
@ -18,7 +18,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)
|
||||
|
|
2
Gemfile
|
@ -35,7 +35,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'
|
||||
|
|
|
@ -375,7 +375,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)
|
||||
|
@ -900,7 +900,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)
|
||||
|
@ -1007,4 +1007,4 @@ DEPENDENCIES
|
|||
wikicloth (= 0.8.1)
|
||||
|
||||
BUNDLED WITH
|
||||
1.13.7
|
||||
1.14.2
|
||||
|
|
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
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
/* global autosize */
|
||||
|
||||
var autosize = require('vendor/autosize');
|
||||
require('vendor/jquery.ba-resize');
|
||||
|
||||
(function() {
|
||||
$(function() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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,64 +0,0 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */
|
||||
/* global GitLab */
|
||||
/* global DropzoneInput */
|
||||
/* global autosize */
|
||||
|
||||
var autosize = require('vendor/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;
|
||||
|
|
|
@ -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,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 */
|
||||
|
@ -421,7 +420,7 @@ require('vendor/task_list');
|
|||
|
||||
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()]);
|
||||
};
|
||||
|
@ -885,7 +884,7 @@ require('vendor/task_list');
|
|||
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)
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -162,6 +162,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.panel-without-border {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-succes .panel-heading,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -76,6 +76,10 @@
|
|||
font-size: 14px;
|
||||
}
|
||||
|
||||
.action-name {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.todo-body {
|
||||
.todo-note {
|
||||
word-wrap: break-word;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,62 +1,88 @@
|
|||
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.execute(
|
||||
merge_request.source_project,
|
||||
merge_request.source_branch,
|
||||
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.execute(
|
||||
source_project,
|
||||
source_branch,
|
||||
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
|
||||
|
@ -71,17 +97,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}\""
|
||||
|
@ -89,31 +115,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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
// This element is filled in using JavaScript.
|
||||
|
||||
.content-block.content-block-small
|
||||
= render 'new_branch'
|
||||
= render 'new_branch' unless @issue.confidential?
|
||||
= render 'award_emoji/awards_block', awardable: @issue, inline: true
|
||||
|
||||
%section.issuable-discussion
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
- if @labels.empty?
|
||||
$('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
|
|
@ -3,37 +3,35 @@
|
|||
- hide_class = ''
|
||||
= render "projects/issues/head"
|
||||
|
||||
%div{ class: container_class }
|
||||
.top-area.adjust
|
||||
.nav-text
|
||||
Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
|
||||
- if @labels.exists? || @prioritized_labels.exists?
|
||||
%div{ class: container_class }
|
||||
.top-area.adjust
|
||||
.nav-text
|
||||
Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
|
||||
|
||||
.nav-controls
|
||||
.nav-controls
|
||||
- if can?(current_user, :admin_label, @project)
|
||||
= link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
|
||||
New label
|
||||
|
||||
.labels
|
||||
- if can?(current_user, :admin_label, @project)
|
||||
= link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
|
||||
New label
|
||||
-# Only show it in the first page
|
||||
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
|
||||
.prioritized-labels{ class: ('hide' if hide) }
|
||||
%h5 Prioritized Labels
|
||||
%ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
|
||||
#js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" }
|
||||
= render 'shared/empty_states/priority_labels'
|
||||
- if @prioritized_labels.present?
|
||||
= render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label
|
||||
|
||||
.labels
|
||||
- if can?(current_user, :admin_label, @project)
|
||||
-# Only show it in the first page
|
||||
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
|
||||
.prioritized-labels{ class: ('hide' if hide) }
|
||||
%h5 Prioritized Labels
|
||||
%ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
|
||||
%p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet
|
||||
- if @prioritized_labels.present?
|
||||
= render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label
|
||||
|
||||
.other-labels
|
||||
- if can?(current_user, :admin_label, @project)
|
||||
%h5{ class: ('hide' if hide) } Other Labels
|
||||
%ul.content-list.manage-labels-list.js-other-labels
|
||||
- if @labels.present?
|
||||
= render partial: 'shared/label', subject: @project, collection: @labels, as: :label
|
||||
= paginate @labels, theme: 'gitlab'
|
||||
- if @labels.blank?
|
||||
.nothing-here-block
|
||||
- if @labels.present?
|
||||
.other-labels
|
||||
- if can?(current_user, :admin_label, @project)
|
||||
Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}.
|
||||
- else
|
||||
No labels created
|
||||
%h5{ class: ('hide' if hide) } Other Labels
|
||||
%ul.content-list.manage-labels-list.js-other-labels
|
||||
= render partial: 'shared/label', subject: @project, collection: @labels, as: :label
|
||||
= paginate @labels, theme: 'gitlab'
|
||||
- else
|
||||
= render 'shared/empty_states/labels'
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
- if @teams_error_message
|
||||
= content_for :flash_message do
|
||||
.alert.alert-danger= @teams_error_message
|
||||
|
||||
%p
|
||||
You aren’t a member of any team on the Mattermost instance at
|
||||
%strong= Gitlab.config.mattermost.host
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
= note_count
|
||||
|
||||
.merge-request-info
|
||||
#{merge_request.to_reference} ·
|
||||
#{issuable_reference(merge_request)} ·
|
||||
opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
|
||||
by #{link_to_member(@project, merge_request.author, avatar: false)}
|
||||
- if merge_request.target_project.default_branch != merge_request.target_branch
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
= f.label :import_url, class: 'control-label' do
|
||||
%span Git repository URL
|
||||
.col-sm-10
|
||||
= f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true
|
||||
= f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true
|
||||
|
||||
.well.prepend-top-20
|
||||
%ul
|
||||
|
@ -13,4 +13,4 @@
|
|||
%li
|
||||
The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination.
|
||||
%li
|
||||
To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}.
|
||||
To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}.
|
||||
|
|
|
@ -1,16 +1,7 @@
|
|||
- if @issues.to_a.any?
|
||||
- @issues.group_by(&:project).each do |group|
|
||||
.panel.panel-default.panel-small
|
||||
- project = group[0]
|
||||
.panel-heading
|
||||
= link_to project.name_with_namespace, namespace_project_issues_path(project.namespace, project)
|
||||
- if can?(current_user, :create_issue, project)
|
||||
.pull-right
|
||||
= link_to 'New issue', new_namespace_project_issue_path(project.namespace, project)
|
||||
|
||||
%ul.content-list.issues-list
|
||||
- group[1].each do |issue|
|
||||
= render 'projects/issues/issue', issue: issue
|
||||
.panel.panel-default.panel-small.panel-without-border
|
||||
%ul.content-list.issues-list
|
||||
= render partial: 'projects/issues/issue', collection: @issues
|
||||
= paginate @issues, theme: "gitlab"
|
||||
- else
|
||||
= render 'shared/empty_states/issues'
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
%li
|
||||
= link_to 'Edit', edit_label_path(label)
|
||||
%li
|
||||
= link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'}
|
||||
= link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, data: {confirm: 'Remove this label? Are you sure?'}
|
||||
|
||||
.pull-right.hidden-xs.hidden-sm.hidden-md
|
||||
= link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action') do
|
||||
|
@ -66,11 +66,15 @@
|
|||
%a.js-subscribe-button{ data: { url: toggle_subscription_group_label_path(label.group, label) } }
|
||||
Group level
|
||||
|
||||
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_group, label.project.group)
|
||||
= link_to promote_namespace_project_label_path(label.project.namespace, label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do
|
||||
%span.sr-only Promote to Group
|
||||
= icon('level-up')
|
||||
- if can?(current_user, :admin_label, label)
|
||||
= link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
|
||||
%span.sr-only Edit
|
||||
= icon('pencil-square-o')
|
||||
= link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do
|
||||
= link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do
|
||||
%span.sr-only Delete
|
||||
= icon('trash-o')
|
||||
|
||||
|
|
|
@ -1,16 +1,8 @@
|
|||
- if @merge_requests.to_a.any?
|
||||
- @merge_requests.group_by(&:target_project).each do |group|
|
||||
.panel.panel-default.panel-small
|
||||
- project = group[0]
|
||||
.panel-heading
|
||||
= link_to project.name_with_namespace, namespace_project_merge_requests_path(project.namespace, project)
|
||||
- if can?(current_user, :create_merge_request, project)
|
||||
.pull-right
|
||||
= link_to 'New merge request', new_namespace_project_merge_request_path(project.namespace, project)
|
||||
.panel.panel-default.panel-small.panel-without-border
|
||||
%ul.content-list.mr-list
|
||||
= render partial: 'projects/merge_requests/merge_request', collection: @merge_requests
|
||||
|
||||
%ul.content-list.mr-list
|
||||
- group[1].each do |merge_request|
|
||||
= render 'projects/merge_requests/merge_request', merge_request: merge_request
|
||||
= paginate @merge_requests, theme: "gitlab"
|
||||
|
||||
- else
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
- if outdated_browser?
|
||||
- link = "https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/requirements.md#supported-web-browsers"
|
||||
.browser-alert
|
||||
GitLab may not work properly because you are using an outdated web browser.
|
||||
%br
|
||||
Please install a
|
||||
= link_to 'supported web browser', link
|
||||
= link_to 'supported web browser', help_page_url('install/requirements', anchor: 'supported-web-browsers')
|
||||
for a better experience.
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
.row.empty-state.labels
|
||||
.pull-right.col-xs-12.col-sm-6
|
||||
.svg-content
|
||||
= render 'shared/empty_states/icons/labels.svg'
|
||||
.col-xs-12.col-sm-6
|
||||
.text-content
|
||||
%h4 Labels can be applied to issues and merge requests to categorize them.
|
||||
%p You can also star label to make it a priority label.
|
||||
- if can?(current_user, :admin_label, @project)
|
||||
= link_to 'New label', new_namespace_project_label_path(@project.namespace, @project), class: 'btn btn-new', title: 'New label', id: 'new_label_link'
|
||||
= link_to 'Generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link'
|
|
@ -0,0 +1,3 @@
|
|||
.text-center
|
||||
= render 'shared/empty_states/icons/priority_labels.svg'
|
||||
%p Star labels to start sorting by priority
|
After Width: | Height: | Size: 5.6 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="116" height="68" viewBox="181 0 116 68"><g fill="none" fill-rule="evenodd" transform="translate(182)"><rect width="78" height="34" x="37" y="34" fill="#FAFAFA" rx="3"/><rect width="78" height="34" x="31" y="28" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="3"/><path fill="#FFF" stroke="#FC6D26" stroke-width="3" d="M34 35.8c-.6 0-1.4 0-1.8.4L29 38.8c-1 .7-1.7.4-2-.7l-.6-4c0-.5-.5-1.2-1-1.5L22 30.2c-1-.6-1-1.5 0-2l3.7-2c.5-.2 1-.8 1.2-1.3l1-4.2c.3-1 1-1.3 2-.5l3 3c.3.3 1 .6 1.6.6l4.2-.3c1 0 1.5.7 1 1.7L38 29c-.3.6-.3 1.4 0 2l1.3 3.8c.4 1 0 1.8-1.2 1.6l-4-.6z" stroke-linecap="round"/><path fill="#FDE5D8" d="M51.6 14.3c-.2-.2-.8-.4-1-.3l-2.8.5c-.7 0-1-.4-.8-1l1-2.8V9.5L46.6 7c-.3-.7 0-1.2.8-1h2.7c.3 0 .8-.2 1-.5l2-2c.6-.5 1-.4 1.3.3l.7 2.8c0 .3.4.8.7 1l2.3 1.2c.7.3.7 1 0 1.3l-2.2 1.7-.6 1-.4 3c-.2.6-.7.8-1.3.4l-2-1.7zM5.4 43.2c-.2-.2-.5-.2-.7-.2l-1.8.3c-.6 0-1-.2-.7-.7l.7-1.8V40l-1-1.7c0-.4 0-.7.6-.7h1.8c.3 0 .6 0 .8-.2L6.5 36c.3-.3.7-.2.8.2l.5 2 .5.5 1.6.8c.3.2.3.7 0 1l-1.6 1c-.2 0-.4.4-.4.7l-.4 2c0 .3-.4.5-.8.2l-1.4-1.2zM10.4 9.2C10.2 9 10 9 9.7 9L8 9.3c-.6 0-1-.2-.7-.7L8 6.8V6L7 4.3c0-.4 0-.7.6-.7h1.8c.3 0 .6 0 .8-.2L11.5 2c.3-.3.7-.2.8.2l.5 2 .5.5 1.6.8c.3.2.3.7 0 1l-1.6 1c-.2 0-.4.4-.4.7l-.4 2c0 .3-.4.5-.8.2l-1.4-1.2z"/><rect width="52" height="4" x="43" y="38" fill="#EEE" rx="2"/><rect width="36" height="4" x="43" y="48" fill="#EEE" rx="2"/></g></svg>
|
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
@ -0,0 +1 @@
|
|||
<svg width="30px" height="30px" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"><path d="M19.25,14.9765625 C19.25,14.1380166 19.0234398,13.3697952 18.5703125,12.671875 L12.6796875,18.5546875 C13.3932327,19.0182315 14.1666625,19.25 15,19.25 C15.5781279,19.25 16.1289036,19.1367199 16.6523438,18.9101562 C17.1757839,18.6835926 17.6276023,18.3802102 18.0078125,18 C18.3880227,17.6197898 18.690103,17.1653672 18.9140625,16.6367188 C19.138022,16.1080703 19.25,15.5546904 19.25,14.9765625 Z M11.4453125,17.3125 L17.34375,11.421875 C16.6406215,10.9479143 15.8593793,10.7109375 15,10.7109375 C14.2291628,10.7109375 13.5182324,10.9010398 12.8671875,11.28125 C12.2161426,11.6614602 11.7005227,12.1796842 11.3203125,12.8359375 C10.9401023,13.4921908 10.75,14.2057253 10.75,14.9765625 C10.75,15.8203167 10.9817685,16.5989548 11.4453125,17.3125 Z M21,14.9765625 C21,15.7942749 20.8411474,16.5755171 20.5234375,17.3203125 C20.2057276,18.0651079 19.7799506,18.7057265 19.2460938,19.2421875 C18.7122369,19.7786485 18.0742225,20.2057276 17.3320312,20.5234375 C16.58984,20.8411474 15.8125041,21 15,21 C14.1874959,21 13.41016,20.8411474 12.6679688,20.5234375 C11.9257775,20.2057276 11.2877631,19.7786485 10.7539062,19.2421875 C10.2200494,18.7057265 9.79427242,18.0651079 9.4765625,17.3203125 C9.15885258,16.5755171 9,15.7942749 9,14.9765625 C9,14.1588501 9.15885258,13.37891 9.4765625,12.6367188 C9.79427242,11.8945275 10.2200494,11.255211 10.7539062,10.71875 C11.2877631,10.182289 11.9257775,9.75520992 12.6679688,9.4375 C13.41016,9.11979008 14.1874959,8.9609375 15,8.9609375 C15.8125041,8.9609375 16.58984,9.11979008 17.3320312,9.4375 C18.0742225,9.75520992 18.7122369,10.182289 19.2460938,10.71875 C19.7799506,11.255211 20.2057276,11.8945275 20.5234375,12.6367188 C20.8411474,13.37891 21,14.1588501 21,14.9765625 Z"></path></svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1 @@
|
|||
<svg width="30px" height="30px" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"><path d="M21.5401786,15.2320328 L11.90625,20.5858274 C11.7950143,20.6486998 11.6994982,20.6559541 11.6196987,20.6075908 C11.5398992,20.5592275 11.5,20.4721748 11.5,20.3464301 L11.5,9.66785867 C11.5,9.54211399 11.5398992,9.45506129 11.6196987,9.40669795 C11.6994982,9.35833462 11.7950143,9.36558901 11.90625,9.42846135 L21.5401786,14.782256 C21.6514142,14.8451283 21.7070312,14.9200904 21.7070312,15.0071444 C21.7070312,15.0941984 21.6514142,15.1691604 21.5401786,15.2320328 Z"></path></svg>
|
After Width: | Height: | Size: 578 B |
|
@ -0,0 +1 @@
|
|||
<svg width="30px" height="30px" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"><path d="M20.6114971,16.0821413 C20.6114971,16.106323 20.6090789,16.1232499 20.6042426,16.1329226 C20.2947172,17.42906 19.6466582,18.4797378 18.6600462,19.2849873 C17.6734341,20.0902369 16.5175677,20.4928556 15.1924122,20.4928556 C14.4863075,20.4928556 13.8031856,20.3598584 13.1430261,20.0938601 C12.4828665,19.8278617 11.8940517,19.4482152 11.376564,18.9549092 L10.4407381,19.8907351 C10.3488478,19.9826254 10.2400319,20.0285699 10.1142872,20.0285699 C9.98854256,20.0285699 9.87972669,19.9826254 9.78783635,19.8907351 C9.69594601,19.7988447 9.65000153,19.6900289 9.65000153,19.5642842 L9.65000153,16.3142842 C9.65000153,16.1885395 9.69594601,16.0797236 9.78783635,15.9878333 C9.87972669,15.895943 9.98854256,15.8499985 10.1142872,15.8499985 L13.3642872,15.8499985 C13.4900319,15.8499985 13.5988478,15.895943 13.6907381,15.9878333 C13.7826285,16.0797236 13.828573,16.1885395 13.828573,16.3142842 C13.828573,16.4400289 13.7826285,16.5488447 13.6907381,16.6407351 L12.6968765,17.6345967 C13.0402562,17.9537947 13.4295752,18.200444 13.8648453,18.374552 C14.3001153,18.5486601 14.7523057,18.6357128 15.2214301,18.6357128 C15.8694988,18.6357128 16.4740315,18.4785343 17.0350462,18.1641726 C17.5960609,17.8498109 18.0458332,17.4169655 18.3843765,16.8656235 C18.4375762,16.7834058 18.5657371,16.5004845 18.7688631,16.0168512 C18.8075538,15.9056155 18.8800977,15.8499985 18.9864971,15.8499985 L20.3793542,15.8499985 C20.4422265,15.8499985 20.4966345,15.8729707 20.5425797,15.9189159 C20.5885248,15.9648611 20.6114971,16.019269 20.6114971,16.0821413 Z M20.7928587,10.2785699 L20.7928587,13.5285699 C20.7928587,13.6543146 20.7469142,13.7631305 20.6550238,13.8550208 C20.5631335,13.9469111 20.4543176,13.9928556 20.328573,13.9928556 L17.078573,13.9928556 C16.9528283,13.9928556 16.8440124,13.9469111 16.7521221,13.8550208 C16.6602317,13.7631305 16.6142872,13.6543146 16.6142872,13.5285699 C16.6142872,13.4028252 16.6602317,13.2940094 16.7521221,13.202119 L17.7532381,12.2010029 C17.0374607,11.5384252 16.1935332,11.2071413 15.2214301,11.2071413 C14.5733614,11.2071413 13.9688287,11.3643198 13.407814,11.6786815 C12.8467993,11.9930432 12.397027,12.4258886 12.0584837,12.9772306 C12.005284,13.0594483 11.8771231,13.3423696 11.6739971,13.8260029 C11.6353064,13.9372386 11.5627625,13.9928556 11.4563631,13.9928556 L10.0127247,13.9928556 C9.9498524,13.9928556 9.89544446,13.9698834 9.84949929,13.9239382 C9.80355412,13.877993 9.78058188,13.8235851 9.78058188,13.7607128 L9.78058188,13.7099315 C10.0949436,12.4137941 10.7478388,11.3631163 11.7392872,10.5578668 C12.7307356,9.75261722 13.8914383,9.34999847 15.2214301,9.34999847 C15.9275348,9.34999847 16.6142839,9.48420472 17.281698,9.75262124 C17.949112,10.0210378 18.541554,10.3994752 19.0590417,10.8879449 L20.0021221,9.95211901 C20.0940124,9.86022867 20.2028283,9.81428419 20.328573,9.81428419 C20.4543176,9.81428419 20.5631335,9.86022867 20.6550238,9.95211901 C20.7469142,10.0440094 20.7928587,10.1528252 20.7928587,10.2785699 Z"></path></svg>
|
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1 @@
|
|||
<svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M20.1357204,10.2985704 L20.1357204,19.7271418 C20.1357204,19.8432138 20.0933101,19.9436592 20.0084882,20.0284811 C19.9236664,20.1133029 19.823221,20.1557132 19.707149,20.1557132 L10.2785775,20.1557132 C10.1625055,20.1557132 10.0620601,20.1133029 9.97723825,20.0284811 C9.89241639,19.9436592 9.8500061,19.8432138 9.8500061,19.7271418 L9.8500061,10.2985704 C9.8500061,10.1824984 9.89241639,10.0820529 9.97723825,9.99723107 C10.0620601,9.91240922 10.1625055,9.86999893 10.2785775,9.86999893 L19.707149,9.86999893 C19.823221,9.86999893 19.9236664,9.91240922 20.0084882,9.99723107 C20.0933101,10.0820529 20.1357204,10.1824984 20.1357204,10.2985704 Z"></path></svg>
|
After Width: | Height: | Size: 770 B |
|
@ -18,7 +18,7 @@
|
|||
%p
|
||||
Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out
|
||||
= succeed "." do
|
||||
%a{ href: "http://docs.gitlab.com/ce/workflow/notifications.html", target: "_blank" } notification emails
|
||||
%a{ href: help_page_path('workflow/notifications'), target: "_blank" } notification emails
|
||||
.col-lg-8
|
||||
- NotificationSetting::EMAIL_EVENTS.each_with_index do |event, index|
|
||||
- field_id = "#{notifications_menu_identifier("modal", notification_setting)}_notification_setting[#{event}]"
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Added labels empty state
|
||||
merge_request: 7443
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Don't group issues by project on group-level and dashboard issue indexes.
|
||||
merge_request: 8111
|
||||
author: Bernardo Castro
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Fix disable storing of sensitive information when importing a new repo
|
||||
merge_request: 8885
|
||||
author: Bernard Pietraga
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Refactor MergeRequests::BuildService
|
||||
merge_request: 8462
|
||||
author: Rydkin Maxim
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Remove flash warning from login page
|
||||
merge_request: 8864
|
||||
author: Gerald J. Padilla
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Convert pipeline action icons to svg to have them propperly positioned
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Avoid repeated dashes in $CI_ENVIRONMENT_SLUG
|
||||
merge_request: 8638
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Update and pin the `jwt` gem to ~> 1.5.6
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Fix notifications when set at group level
|
||||
merge_request: 6813
|
||||
author: Alexandre Maia
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Cop for gem fetched from a git source
|
||||
merge_request: 8856
|
||||
author: Adam Pahlevi
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Adds documentation for how to use Vue.js
|
||||
merge_request: 8866
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Add read-only full_path and full_name attributes to Group API
|
||||
merge_request: 8827
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Remove new branch button for confidential issues
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Improve build policy and access abilities
|
||||
merge_request: 8711
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Ignore encrypted attributes in Import/Export
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Ensure autogenerated title does not cause failing spec
|
||||
merge_request: 8963
|
||||
author: brian m. carlson
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Changed composer installer script in the CI PHP example doc
|
||||
merge_request: 4342
|
||||
author: Jeffrey Cafferata
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Resets assignee dropdown when sidebar is open
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: "Project labels can now be promoted to group labels"
|
||||
merge_request: 7242
|
||||
author: Olaf Tomalka
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Add project ID index to `project_authorizations` table to optimize queries
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Improve performance of slash commands
|
||||
merge_request: 8876
|
||||
author:
|
|
@ -220,6 +220,7 @@ constraints(ProjectUrlConstrainer.new) do
|
|||
end
|
||||
|
||||
member do
|
||||
post :promote
|
||||
post :toggle_subscription
|
||||
delete :remove_priority
|
||||
end
|
||||
|
|
|
@ -8,8 +8,9 @@ class AddEnvironmentSlug < ActiveRecord::Migration
|
|||
DOWNTIME_REASON = 'Adding NOT NULL column environments.slug with dependent data'
|
||||
|
||||
# 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
|
||||
|
||||
def up
|
||||
environments = Arel::Table.new(:environments)
|
||||
|
@ -39,17 +40,24 @@ class AddEnvironmentSlug < ActiveRecord::Migration
|
|||
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
|
||||
|
||||
slugified
|
||||
end
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
class AddIndexToProjectAuthorizations < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index(:project_authorizations, :project_id)
|
||||
end
|
||||
end
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20170121130655) do
|
||||
ActiveRecord::Schema.define(version: 20170130204620) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -874,6 +874,7 @@ ActiveRecord::Schema.define(version: 20170121130655) do
|
|||
t.integer "access_level"
|
||||
end
|
||||
|
||||
add_index "project_authorizations", ["project_id"], name: "index_project_authorizations_on_project_id", using: :btree
|
||||
add_index "project_authorizations", ["user_id", "project_id", "access_level"], name: "index_project_authorizations_on_user_id_project_id_access_level", unique: true, using: :btree
|
||||
|
||||
create_table "project_features", force: :cascade do |t|
|
||||
|
|