Merge branch 'master' into dz-merge-request-version
Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
This commit is contained in:
commit
9329436deb
28
CHANGELOG
28
CHANGELOG
|
@ -1,12 +1,15 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.11.0 (unreleased)
|
||||
- Use test coverage value from the latest successful pipeline in badge. !5862
|
||||
- Add test coverage report badge. !5708
|
||||
- Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar)
|
||||
- Add Koding (online IDE) integration
|
||||
- Ability to specify branches for Pivotal Tracker integration (Egor Lynko)
|
||||
- Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
|
||||
- Add delimiter to project stars and forks count (ClemMakesApps)
|
||||
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
|
||||
- Fix adding line comments on the initial commit to a repo !5900
|
||||
- Fix the title of the toggle dropdown button. !5515 (herminiotorres)
|
||||
- Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz)
|
||||
- Update to Ruby 2.3.1. !4948
|
||||
|
@ -18,13 +21,17 @@ v 8.11.0 (unreleased)
|
|||
- API: Endpoints for enabling and disabling deploy keys
|
||||
- API: List access requests, request access, approve, and deny access requests to a project or a group. !4833
|
||||
- Use long options for curl examples in documentation !5703 (winniehell)
|
||||
- Added tooltip listing label names to the labels value in the collapsed issuable sidebar
|
||||
- Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell)
|
||||
- Fix badge count alignment (ClemMakesApps)
|
||||
- GitLab Performance Monitoring can now track custom events such as the number of tags pushed to a repository
|
||||
- Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell)
|
||||
- Allow naming U2F devices !5833
|
||||
- Ignore URLs starting with // in Markdown links !5677 (winniehell)
|
||||
- Fix CI status icon link underline (ClemMakesApps)
|
||||
- The Repository class is now instrumented
|
||||
- Fix commit mention font inconsistency (ClemMakesApps)
|
||||
- Do not escape URI when extracting path !5878 (winniehell)
|
||||
- Fix filter label tooltip HTML rendering (ClemMakesApps)
|
||||
- Cache the commit author in RequestStore to avoid extra lookups in PostReceive
|
||||
- Expand commit message width in repo view (ClemMakesApps)
|
||||
|
@ -34,9 +41,11 @@ v 8.11.0 (unreleased)
|
|||
- API: Add deployment endpoints
|
||||
- API: Add Play endpoint on Builds
|
||||
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
|
||||
- Show wall clock time when showing a pipeline. !5734
|
||||
- Show member roles to all users on members page
|
||||
- Project.visible_to_user is instrumented again
|
||||
- Fix awardable button mutuality loading spinners (ClemMakesApps)
|
||||
- Sort todos by date and priority
|
||||
- Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
|
||||
- Optimize maximum user access level lookup in loading of notes
|
||||
- Send notification emails to users newly mentioned in issue and MR edits !5800
|
||||
|
@ -55,6 +64,7 @@ v 8.11.0 (unreleased)
|
|||
- Enforce 2FA restrictions on API authentication endpoints !5820
|
||||
- Limit git rev-list output count to one in forced push check
|
||||
- Show deployment status on merge requests with external URLs
|
||||
- Fix branch title trailing space on hover (ClemMakesApps)
|
||||
- Clean up unused routes (Josef Strzibny)
|
||||
- Fix issue on empty project to allow developers to only push to protected branches if given permission
|
||||
- API: Add enpoints for pipelines
|
||||
|
@ -71,6 +81,7 @@ v 8.11.0 (unreleased)
|
|||
- Fix devise deprecation warnings.
|
||||
- Check for 2FA when using Git over HTTP and only allow PersonalAccessTokens as password in that case !5764
|
||||
- Update version_sorter and use new interface for faster tag sorting
|
||||
- Load branches asynchronously in Cherry Pick and Revert dialogs.
|
||||
- Optimize checking if a user has read access to a list of issues !5370
|
||||
- Store all DB secrets in secrets.yml, under descriptive names !5274
|
||||
- Fix syntax highlighting in file editor
|
||||
|
@ -79,7 +90,6 @@ v 8.11.0 (unreleased)
|
|||
- Add archived badge to project list !5798
|
||||
- Add simple identifier to public SSH keys (muteor)
|
||||
- Admin page now references docs instead of a specific file !5600 (AnAverageHuman)
|
||||
- Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363
|
||||
- Fix filter input alignment (ClemMakesApps)
|
||||
- Include old revision in merge request update hooks (Ben Boeckel)
|
||||
- Add build event color in HipChat messages (David Eisner)
|
||||
|
@ -105,12 +115,14 @@ v 8.11.0 (unreleased)
|
|||
- Fix search for notes which belongs to deleted objects
|
||||
- Allow Akismet to be trained by submitting issues as spam or ham !5538
|
||||
- Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska)
|
||||
- Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps)
|
||||
- Allow branch names ending with .json for graph and network page !5579 (winniehell)
|
||||
- Add the `sprockets-es6` gem
|
||||
- Improve OAuth2 client documentation (muteor)
|
||||
- Fix diff comments inverted toggle bug (ClemMakesApps)
|
||||
- Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska)
|
||||
- Profile requests when a header is passed
|
||||
- Fix button missing type (ClemMakesApps)
|
||||
- Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab.
|
||||
- Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible
|
||||
- Add commit stats in commit api. !5517 (dixpac)
|
||||
|
@ -119,14 +131,17 @@ v 8.11.0 (unreleased)
|
|||
- edit_blob_link will use blob passed onto the options parameter
|
||||
- Make error pages responsive (Takuya Noguchi)
|
||||
- The performance of the project dropdown used for moving issues has been improved
|
||||
- Move to project dropdown with infinite scroll for better performance
|
||||
- Fix skip_repo parameter being ignored when destroying a namespace
|
||||
- Add all builds into stage/job dropdowns on builds page
|
||||
- Change requests_profiles resource constraint to catch virtually any file
|
||||
- Bump gitlab_git to lazy load compare commits
|
||||
- Reduce number of queries made for merge_requests/:id/diffs
|
||||
- Add the option to set the expiration date for the project membership when giving a user access to a project. !5599 (Adam Niedzielski)
|
||||
- Sensible state specific default sort order for issues and merge requests !5453 (tomb0y)
|
||||
- Fix bug where destroying a namespace would not always destroy projects
|
||||
- Fix RequestProfiler::Middleware error when code is reloaded in development
|
||||
- Allow horizontal scrolling of code blocks in issue body
|
||||
- Catch what warden might throw when profiling requests to re-throw it
|
||||
- Avoid commit lookup on diff_helper passing existing local variable to the helper method
|
||||
- Add description to new_issue email and new_merge_request_email in text/plain content type. !5663 (dixpac)
|
||||
|
@ -141,6 +156,7 @@ v 8.11.0 (unreleased)
|
|||
- Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0
|
||||
- Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska)
|
||||
- Add pipelines tab to merge requests
|
||||
- Fix notification_service argument error of declined invitation emails
|
||||
- Fix a memory leak caused by Banzai::Filter::SanitizationFilter
|
||||
- Speed up todos queries by limiting the projects set we join with
|
||||
- Ensure file editing in UI does not overwrite commited changes without warning user
|
||||
|
@ -148,6 +164,10 @@ v 8.11.0 (unreleased)
|
|||
- Update gitlab_git gem to 10.4.7
|
||||
- Simplify SQL queries of marking a todo as done
|
||||
|
||||
v 8.10.7
|
||||
- Upgrade Hamlit to 2.6.1. !5873
|
||||
- Upgrade Doorkeeper to 4.2.0. !5881
|
||||
|
||||
v 8.10.6
|
||||
- Upgrade Rails to 4.2.7.1 for security fixes. !5781
|
||||
- Restore "Largest repository" sort option on Admin > Projects page. !5797
|
||||
|
@ -371,6 +391,9 @@ v 8.10.0
|
|||
- Fix migration corrupting import data for old version upgrades
|
||||
- Show tooltip on GitLab export link in new project page
|
||||
|
||||
v 8.9.8
|
||||
- Upgrade Doorkeeper to 4.2.0. !5881
|
||||
|
||||
v 8.9.7
|
||||
- Upgrade Rails to 4.2.7.1 for security fixes. !5781
|
||||
- Require administrator privileges to perform a project import.
|
||||
|
@ -640,6 +663,9 @@ v 8.9.0
|
|||
- Add tooltip to pin/unpin navbar
|
||||
- Add new sub nav style to Wiki and Graphs sub navigation
|
||||
|
||||
v 8.8.9
|
||||
- Upgrade Doorkeeper to 4.2.0. !5881
|
||||
|
||||
v 8.8.8
|
||||
- Upgrade Rails to 4.2.7.1 for security fixes. !5781
|
||||
|
||||
|
|
|
@ -387,7 +387,8 @@ description area. Copy-paste it to retain the markdown format.
|
|||
|
||||
1. The change is as small as possible
|
||||
1. Include proper tests and make all tests pass (unless it contains a test
|
||||
exposing a bug in existing code)
|
||||
exposing a bug in existing code). Every new class should have corresponding
|
||||
unit tests, even if the class is exercised at a higher level, such as a feature test.
|
||||
1. If you suspect a failing CI build is unrelated to your contribution, you may
|
||||
try and restart the failing CI job or ask a developer to fix the
|
||||
aforementioned failing test
|
||||
|
|
|
@ -1 +1 @@
|
|||
3.3.3
|
||||
3.4.0
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 14">
|
||||
<g fill="#d6d7d9">
|
||||
<path d="M8.7 0L5.3.3l3.2 6.8-3.2 6.6 3.5.3L12 6.9z"/>
|
||||
<ellipse cx="1.7" cy="11.1" rx="1.7" ry="1.7"/>
|
||||
<ellipse cx="1.7" cy="5.6" rx="1.7" ry="1.7"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 312 B |
|
@ -26,8 +26,6 @@
|
|||
/*= require bootstrap/tooltip */
|
||||
/*= require bootstrap/popover */
|
||||
/*= require select2 */
|
||||
/*= require ace-rails-ap */
|
||||
/*= require ace/ext-searchbox */
|
||||
/*= require underscore */
|
||||
/*= require dropzone */
|
||||
/*= require mousetrap */
|
||||
|
@ -153,7 +151,9 @@
|
|||
});
|
||||
});
|
||||
$('.remove-row').bind('ajax:success', function() {
|
||||
return $(this).closest('li').fadeOut();
|
||||
$(this).tooltip('destroy')
|
||||
.closest('li')
|
||||
.fadeOut();
|
||||
});
|
||||
$('.js-remove-tr').bind('ajax:before', function() {
|
||||
return $(this).hide();
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*= require_tree . */
|
||||
|
||||
(function() {
|
||||
$(function() {
|
||||
var url = $(".js-edit-blob-form").data("relative-url-root");
|
||||
url += $(".js-edit-blob-form").data("assets-prefix");
|
||||
|
||||
var blob = new EditBlob(url, $('.js-edit-blob-form').data('blob-language'));
|
||||
new NewCommitForm($('.js-edit-blob-form'));
|
||||
});
|
||||
|
||||
}).call(this);
|
|
@ -38,7 +38,7 @@ $(() => {
|
|||
ready () {
|
||||
Store.disabled = this.disabled;
|
||||
gl.boardService.all()
|
||||
.then((resp) => {
|
||||
.then((resp) => {
|
||||
resp.json().forEach((board) => {
|
||||
const list = Store.addList(board);
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
draggable: '.is-draggable',
|
||||
handle: '.js-board-handle',
|
||||
onEnd: (e) => {
|
||||
document.body.classList.remove('is-dragging');
|
||||
gl.issueBoards.onEnd();
|
||||
|
||||
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
|
||||
const order = this.sortable.toArray(),
|
||||
|
@ -72,10 +72,6 @@
|
|||
}
|
||||
});
|
||||
|
||||
if (bp.getBreakpointSize() === 'xs') {
|
||||
options.handle = '.js-board-drag-handle';
|
||||
}
|
||||
|
||||
this.sortable = Sortable.create(this.$el.parentNode, options);
|
||||
},
|
||||
beforeDestroy () {
|
||||
|
|
|
@ -63,6 +63,8 @@
|
|||
|
||||
Store.moving.issue = card.issue;
|
||||
Store.moving.list = card.list;
|
||||
|
||||
gl.issueBoards.onStart();
|
||||
},
|
||||
onAdd: (e) => {
|
||||
gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue);
|
||||
|
@ -72,10 +74,6 @@
|
|||
}
|
||||
});
|
||||
|
||||
if (bp.getBreakpointSize() === 'xs') {
|
||||
options.handle = '.js-card-drag-handle';
|
||||
}
|
||||
|
||||
this.sortable = Sortable.create(this.$els.list, options);
|
||||
|
||||
// Scroll event on list to load more
|
||||
|
|
|
@ -2,6 +2,19 @@
|
|||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.onStart = () => {
|
||||
$('.has-tooltip').tooltip('hide')
|
||||
.tooltip('disable');
|
||||
document.body.classList.add('is-dragging');
|
||||
};
|
||||
|
||||
gl.issueBoards.onEnd = () => {
|
||||
$('.has-tooltip').tooltip('enable');
|
||||
document.body.classList.remove('is-dragging');
|
||||
};
|
||||
|
||||
gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
|
||||
|
||||
gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
|
||||
let defaultSortOptions = {
|
||||
forceFallback: true,
|
||||
|
@ -9,14 +22,11 @@
|
|||
fallbackOnBody: true,
|
||||
ghostClass: 'is-ghost',
|
||||
filter: '.has-tooltip',
|
||||
scrollSensitivity: 100,
|
||||
delay: gl.issueBoards.touchEnabled ? 100 : 0,
|
||||
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
|
||||
scrollSpeed: 20,
|
||||
onStart () {
|
||||
document.body.classList.add('is-dragging');
|
||||
},
|
||||
onEnd () {
|
||||
document.body.classList.remove('is-dragging');
|
||||
}
|
||||
onStart: gl.issueBoards.onStart,
|
||||
onEnd: gl.issueBoards.onEnd
|
||||
}
|
||||
|
||||
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
|
||||
|
|
|
@ -3,6 +3,7 @@ class ListLabel {
|
|||
this.id = obj.id;
|
||||
this.title = obj.title;
|
||||
this.color = obj.color;
|
||||
this.textColor = obj.text_color;
|
||||
this.description = obj.description;
|
||||
this.priority = (obj.priority !== null) ? obj.priority : Infinity;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
path = page.split(':');
|
||||
shortcut_handler = null;
|
||||
switch (page) {
|
||||
case 'projects:boards:show':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
break;
|
||||
case 'projects:issues:index':
|
||||
Issuable.init();
|
||||
new IssuableBulkActions();
|
||||
|
@ -126,10 +129,12 @@
|
|||
new NotificationsDropdown();
|
||||
break;
|
||||
case 'groups:group_members:index':
|
||||
new gl.MemberExpirationDate();
|
||||
new GroupMembers();
|
||||
new UsersSelect();
|
||||
break;
|
||||
case 'projects:project_members:index':
|
||||
new gl.MemberExpirationDate();
|
||||
new ProjectMembers();
|
||||
new UsersSelect();
|
||||
break;
|
||||
|
@ -171,6 +176,7 @@
|
|||
new BuildArtifacts();
|
||||
break;
|
||||
case 'projects:group_links:index':
|
||||
new gl.MemberExpirationDate();
|
||||
new GroupsSelect();
|
||||
break;
|
||||
case 'search:show':
|
||||
|
|
|
@ -31,9 +31,8 @@
|
|||
this.input
|
||||
.on('keydown', function (e) {
|
||||
var keyCode = e.which;
|
||||
|
||||
if (keyCode === 13) {
|
||||
e.preventDefault()
|
||||
e.preventDefault();
|
||||
}
|
||||
})
|
||||
.on('keyup', function(e) {
|
||||
|
@ -111,9 +110,9 @@
|
|||
matches = fuzzaldrinPlus.match($el.text().trim(), search_text);
|
||||
if (!$el.is('.dropdown-header')) {
|
||||
if (matches.length) {
|
||||
return $el.show();
|
||||
return $el.show().removeClass('option-hidden');
|
||||
} else {
|
||||
return $el.hide();
|
||||
return $el.hide().addClass('option-hidden');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -179,7 +178,7 @@
|
|||
})();
|
||||
|
||||
GitLabDropdown = (function() {
|
||||
var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, currentIndex;
|
||||
var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, NON_SELECTABLE_CLASSES, SELECTABLE_CLASSES, currentIndex;
|
||||
|
||||
LOADING_CLASS = "is-loading";
|
||||
|
||||
|
@ -191,6 +190,12 @@
|
|||
|
||||
currentIndex = -1;
|
||||
|
||||
NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link, .option-hidden';
|
||||
|
||||
SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ")";
|
||||
|
||||
CURSOR_SELECT_SCROLL_PADDING = 5
|
||||
|
||||
FILTER_INPUT = '.dropdown-input .dropdown-input-field';
|
||||
|
||||
function GitLabDropdown(el1, options) {
|
||||
|
@ -213,6 +218,7 @@
|
|||
if (this.options.data) {
|
||||
if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) {
|
||||
this.fullData = this.options.data;
|
||||
currentIndex = -1;
|
||||
this.parseData(this.options.data);
|
||||
} else {
|
||||
this.remote = new GitLabDropdownRemote(this.options.data, {
|
||||
|
@ -240,7 +246,7 @@
|
|||
keys: searchFields,
|
||||
elements: (function(_this) {
|
||||
return function() {
|
||||
selector = '.dropdown-content li:not(.divider)';
|
||||
selector = '.dropdown-content li:not(' + NON_SELECTABLE_CLASSES + ')';
|
||||
if (_this.dropdown.find('.dropdown-toggle-page').length) {
|
||||
selector = ".dropdown-page-one " + selector;
|
||||
}
|
||||
|
@ -256,7 +262,7 @@
|
|||
return function(data) {
|
||||
_this.parseData(data);
|
||||
if (_this.filterInput.val() !== '') {
|
||||
selector = '.dropdown-content li:not(.divider):visible';
|
||||
selector = SELECTABLE_CLASSES;
|
||||
if (_this.dropdown.find('.dropdown-toggle-page').length) {
|
||||
selector = ".dropdown-page-one " + selector;
|
||||
}
|
||||
|
@ -376,7 +382,7 @@
|
|||
var $target;
|
||||
if (this.options.multiSelect) {
|
||||
$target = $(e.target);
|
||||
if (!$target.hasClass('dropdown-menu-close') && !$target.hasClass('dropdown-menu-close-icon') && !$target.data('is-link')) {
|
||||
if ($target && !$target.hasClass('dropdown-menu-close') && !$target.hasClass('dropdown-menu-close-icon') && !$target.data('is-link')) {
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
} else {
|
||||
|
@ -387,7 +393,7 @@
|
|||
|
||||
GitLabDropdown.prototype.opened = function() {
|
||||
var contentHtml;
|
||||
currentIndex = -1;
|
||||
this.resetRows();
|
||||
this.addArrowKeyEvent();
|
||||
if (this.options.setIndeterminateIds) {
|
||||
this.options.setIndeterminateIds.call(this);
|
||||
|
@ -410,6 +416,7 @@
|
|||
|
||||
GitLabDropdown.prototype.hidden = function(e) {
|
||||
var $input;
|
||||
this.resetRows();
|
||||
this.removeArrayKeyEvent();
|
||||
$input = this.dropdown.find(".dropdown-input-field");
|
||||
if (this.options.filterable) {
|
||||
|
@ -463,14 +470,15 @@
|
|||
return "<li class='separator'></li>";
|
||||
}
|
||||
if (data.header != null) {
|
||||
return "<li class='dropdown-header'>" + data.header + "</li>";
|
||||
return _.template('<li class="dropdown-header"><%- header %></li>')({ header: data.header });
|
||||
}
|
||||
if (this.options.renderRow) {
|
||||
html = this.options.renderRow.call(this.options, data, this);
|
||||
} else {
|
||||
if (!selected) {
|
||||
value = this.options.id ? this.options.id(data) : data.id;
|
||||
fieldName = this.options.fieldName;
|
||||
fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName() : this.options.fieldName;
|
||||
|
||||
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
|
||||
if (field.length) {
|
||||
selected = true;
|
||||
|
@ -494,11 +502,16 @@
|
|||
text = this.highlightTextMatches(text, this.filterInput.val());
|
||||
}
|
||||
if (group) {
|
||||
groupAttrs = "data-group='" + group + "' data-index='" + index + "'";
|
||||
groupAttrs = 'data-group=' + group + ' data-index=' + index;
|
||||
} else {
|
||||
groupAttrs = '';
|
||||
}
|
||||
html = "<li> <a href='" + url + "' " + groupAttrs + " class='" + cssClass + "'> " + text + " </a> </li>";
|
||||
html = _.template('<li><a href="<%- url %>" <%- groupAttrs %> class="<%- cssClass %>"><%= text %></a></li>')({
|
||||
url: url,
|
||||
groupAttrs: groupAttrs,
|
||||
cssClass: cssClass,
|
||||
text: text
|
||||
});
|
||||
}
|
||||
return html;
|
||||
};
|
||||
|
@ -520,20 +533,8 @@
|
|||
return html = "<li class='dropdown-menu-empty-link'> <a href='#' class='is-focused'> No matching results. </a> </li>";
|
||||
};
|
||||
|
||||
GitLabDropdown.prototype.highlightRow = function(index) {
|
||||
var selector;
|
||||
if (this.filterInput.val() !== "") {
|
||||
selector = '.dropdown-content li:first-child a';
|
||||
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
||||
selector = ".dropdown-page-one .dropdown-content li:first-child a";
|
||||
}
|
||||
return this.getElement(selector).addClass('is-focused');
|
||||
}
|
||||
};
|
||||
|
||||
GitLabDropdown.prototype.rowClicked = function(el) {
|
||||
var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value;
|
||||
fieldName = this.options.fieldName;
|
||||
isInput = $(this.el).is('input');
|
||||
if (this.renderedData) {
|
||||
groupName = el.data('group');
|
||||
|
@ -545,6 +546,7 @@
|
|||
selectedObject = this.renderedData[selectedIndex];
|
||||
}
|
||||
}
|
||||
fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName(selectedObject) : this.options.fieldName;
|
||||
value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id;
|
||||
if (isInput) {
|
||||
field = $(this.el);
|
||||
|
@ -559,10 +561,9 @@
|
|||
field.remove();
|
||||
}
|
||||
if (this.options.toggleLabel) {
|
||||
return this.updateLabel(selectedObject, el, this);
|
||||
} else {
|
||||
return selectedObject;
|
||||
this.updateLabel(selectedObject, el, this);
|
||||
}
|
||||
return selectedObject;
|
||||
} else if (el.hasClass(INDETERMINATE_CLASS)) {
|
||||
el.addClass(ACTIVE_CLASS);
|
||||
el.removeClass(INDETERMINATE_CLASS);
|
||||
|
@ -570,7 +571,7 @@
|
|||
field.remove();
|
||||
}
|
||||
if (!field.length && fieldName) {
|
||||
this.addInput(fieldName, value);
|
||||
this.addInput(fieldName, value, selectedObject);
|
||||
}
|
||||
return selectedObject;
|
||||
} else {
|
||||
|
@ -589,7 +590,7 @@
|
|||
}
|
||||
if (value != null) {
|
||||
if (!field.length && fieldName) {
|
||||
this.addInput(fieldName, value);
|
||||
this.addInput(fieldName, value, selectedObject);
|
||||
} else {
|
||||
field.val(value).trigger('change');
|
||||
}
|
||||
|
@ -598,24 +599,29 @@
|
|||
}
|
||||
};
|
||||
|
||||
GitLabDropdown.prototype.addInput = function(fieldName, value) {
|
||||
GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) {
|
||||
var $input;
|
||||
$input = $('<input>').attr('type', 'hidden').attr('name', fieldName).val(value);
|
||||
if (this.options.inputId != null) {
|
||||
$input.attr('id', this.options.inputId);
|
||||
}
|
||||
if (selectedObject && selectedObject.type) {
|
||||
$input.attr('data-type', selectedObject.type);
|
||||
}
|
||||
return this.dropdown.before($input);
|
||||
};
|
||||
|
||||
GitLabDropdown.prototype.selectRowAtIndex = function(index) {
|
||||
var $el, selector;
|
||||
selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(" + index + ") a";
|
||||
selector = SELECTABLE_CLASSES + ":eq(" + index + ") a";
|
||||
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
||||
selector = ".dropdown-page-one " + selector;
|
||||
}
|
||||
$el = $(selector, this.dropdown);
|
||||
if ($el.length) {
|
||||
return $el.first().trigger('click');
|
||||
$el.first().trigger('click');
|
||||
var href = $el.attr('href');
|
||||
if (href && href !== '#') Turbolinks.visit(href);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -623,7 +629,7 @@
|
|||
var $input, ARROW_KEY_CODES, selector;
|
||||
ARROW_KEY_CODES = [38, 40];
|
||||
$input = this.dropdown.find(".dropdown-input-field");
|
||||
selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator):visible';
|
||||
selector = SELECTABLE_CLASSES;
|
||||
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
||||
selector = ".dropdown-page-one " + selector;
|
||||
}
|
||||
|
@ -651,7 +657,7 @@
|
|||
return false;
|
||||
}
|
||||
if (currentKeyCode === 13 && currentIndex !== -1) {
|
||||
return _this.selectRowAtIndex($('.is-focused', _this.dropdown).closest('li').index() - 1);
|
||||
return _this.selectRowAtIndex(currentIndex);
|
||||
}
|
||||
};
|
||||
})(this));
|
||||
|
@ -661,6 +667,11 @@
|
|||
return $('body').off('keydown');
|
||||
};
|
||||
|
||||
GitLabDropdown.prototype.resetRows = function resetRows() {
|
||||
currentIndex = -1;
|
||||
$('.is-focused', this.dropdown).removeClass('is-focused');
|
||||
};
|
||||
|
||||
GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) {
|
||||
var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop;
|
||||
$('.is-focused', this.dropdown).removeClass('is-focused');
|
||||
|
@ -674,10 +685,14 @@
|
|||
listItemHeight = $listItem.outerHeight();
|
||||
listItemTop = $listItem.prop('offsetTop');
|
||||
listItemBottom = listItemTop + listItemHeight;
|
||||
if (listItemBottom > dropdownContentBottom + dropdownScrollTop) {
|
||||
return $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom);
|
||||
} else if (listItemTop < dropdownContentTop + dropdownScrollTop) {
|
||||
return $dropdownContent.scrollTop(listItemTop - dropdownContentTop);
|
||||
if (!index) {
|
||||
$dropdownContent.scrollTop(0)
|
||||
} else if (index === ($listItems.length - 1)) {
|
||||
$dropdownContent.scrollTop($dropdownContent.prop('scrollHeight'));
|
||||
} else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) {
|
||||
$dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING);
|
||||
} else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) {
|
||||
return $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -102,20 +102,34 @@
|
|||
};
|
||||
|
||||
IssuableForm.prototype.initMoveDropdown = function() {
|
||||
var $moveDropdown;
|
||||
var $moveDropdown, pageSize;
|
||||
$moveDropdown = $('.js-move-dropdown');
|
||||
if ($moveDropdown.length) {
|
||||
pageSize = $moveDropdown.data('page-size');
|
||||
return $('.js-move-dropdown').select2({
|
||||
ajax: {
|
||||
url: $moveDropdown.data('projects-url'),
|
||||
results: function(data) {
|
||||
quietMillis: 125,
|
||||
data: function(term, page, context) {
|
||||
return {
|
||||
results: data
|
||||
search: term,
|
||||
offset_id: context
|
||||
};
|
||||
},
|
||||
data: function(query) {
|
||||
results: function(data) {
|
||||
var context,
|
||||
more;
|
||||
|
||||
if (data.length >= pageSize)
|
||||
more = true;
|
||||
|
||||
if (data[data.length - 1])
|
||||
context = data[data.length - 1].id;
|
||||
|
||||
return {
|
||||
search: query
|
||||
results: data,
|
||||
more: more,
|
||||
context: context
|
||||
};
|
||||
}
|
||||
},
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
var _this;
|
||||
_this = this;
|
||||
$('.js-label-select').each(function(i, dropdown) {
|
||||
var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo;
|
||||
var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip;
|
||||
$dropdown = $(dropdown);
|
||||
projectId = $dropdown.data('project-id');
|
||||
labelUrl = $dropdown.data('labels');
|
||||
|
@ -21,6 +21,7 @@
|
|||
$block = $selectbox.closest('.block');
|
||||
$form = $dropdown.closest('form');
|
||||
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span');
|
||||
$sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
|
||||
$value = $block.find('.value');
|
||||
$loading = $block.find('.block-loading').fadeOut();
|
||||
if (issueUpdateURL != null) {
|
||||
|
@ -31,7 +32,11 @@
|
|||
labelNoneHTMLTemplate = '<span class="no-value">None</span>';
|
||||
}
|
||||
|
||||
new gl.CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), projectId);
|
||||
$sidebarLabelTooltip.tooltip();
|
||||
|
||||
if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) {
|
||||
new gl.CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), projectId);
|
||||
}
|
||||
|
||||
saveLabelData = function() {
|
||||
var data, selected;
|
||||
|
@ -52,7 +57,7 @@
|
|||
dataType: 'JSON',
|
||||
data: data
|
||||
}).done(function(data) {
|
||||
var labelCount, template;
|
||||
var labelCount, template, labelTooltipTitle, labelTitles;
|
||||
$loading.fadeOut();
|
||||
$dropdown.trigger('loaded.gl.dropdown');
|
||||
$selectbox.hide();
|
||||
|
@ -66,6 +71,27 @@
|
|||
}
|
||||
$value.removeAttr('style').html(template);
|
||||
$sidebarCollapsedValue.text(labelCount);
|
||||
|
||||
if (data.labels.length) {
|
||||
labelTitles = data.labels.map(function(label) {
|
||||
return label.title;
|
||||
});
|
||||
|
||||
if (labelTitles.length > 5) {
|
||||
labelTitles = labelTitles.slice(0, 5);
|
||||
labelTitles.push('and ' + (data.labels.length - 5) + ' more');
|
||||
}
|
||||
|
||||
labelTooltipTitle = labelTitles.join(', ');
|
||||
} else {
|
||||
labelTooltipTitle = '';
|
||||
$sidebarLabelTooltip.tooltip('destroy');
|
||||
}
|
||||
|
||||
$sidebarLabelTooltip
|
||||
.attr('title', labelTooltipTitle)
|
||||
.tooltip('fixTitle');
|
||||
|
||||
$('.has-tooltip', $value).tooltip({
|
||||
container: 'body'
|
||||
});
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
/*= require ace-rails-ap */
|
||||
/*= require ace/ext-searchbox */
|
|
@ -0,0 +1,32 @@
|
|||
(function() {
|
||||
// Add datepickers to all `js-access-expiration-date` elements. If those elements are
|
||||
// children of an element with the `clearable-input` class, and have a sibling
|
||||
// `js-clear-input` element, then show that element when there is a value in the
|
||||
// datepicker, and make clicking on that element clear the field.
|
||||
//
|
||||
gl.MemberExpirationDate = function() {
|
||||
function toggleClearInput() {
|
||||
$(this).closest('.clearable-input').toggleClass('has-value', $(this).val() !== '');
|
||||
}
|
||||
|
||||
var inputs = $('.js-access-expiration-date');
|
||||
|
||||
inputs.datepicker({
|
||||
dateFormat: 'yy-mm-dd',
|
||||
minDate: 1,
|
||||
onSelect: toggleClearInput
|
||||
});
|
||||
|
||||
inputs.next('.js-clear-input').on('click', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var input = $(this).closest('.clearable-input').find('.js-access-expiration-date');
|
||||
input.datepicker('setDate', null);
|
||||
toggleClearInput.call(input);
|
||||
});
|
||||
|
||||
inputs.on('blur', toggleClearInput);
|
||||
|
||||
inputs.each(toggleClearInput);
|
||||
};
|
||||
}).call(this);
|
|
@ -65,7 +65,8 @@
|
|||
url: $dropdown.data('refs-url'),
|
||||
data: {
|
||||
ref: $dropdown.data('ref')
|
||||
}
|
||||
},
|
||||
dataType: "json"
|
||||
}).done(function(refs) {
|
||||
return callback(refs);
|
||||
});
|
||||
|
@ -73,7 +74,7 @@
|
|||
selectable: true,
|
||||
filterable: true,
|
||||
filterByText: true,
|
||||
fieldName: 'ref',
|
||||
fieldName: $dropdown.data('field-name'),
|
||||
renderRow: function(ref) {
|
||||
var link;
|
||||
if (ref.header != null) {
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
return $(this).fadeOut();
|
||||
});
|
||||
}
|
||||
|
||||
return ProjectMembers;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -10,8 +10,12 @@
|
|||
selectable: true,
|
||||
inputId: $dropdown.data('input-id'),
|
||||
fieldName: $dropdown.data('field-name'),
|
||||
toggleLabel(item) {
|
||||
return item.text;
|
||||
toggleLabel(item, el) {
|
||||
if (el.is('.is-active')) {
|
||||
return item.text;
|
||||
} else {
|
||||
return 'Select';
|
||||
}
|
||||
},
|
||||
clicked(item, $el, e) {
|
||||
e.preventDefault();
|
||||
|
|
|
@ -47,9 +47,7 @@
|
|||
const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]');
|
||||
const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]');
|
||||
|
||||
if ($branchInput.val() && $allowedToMergeInput.val() && $allowedToPushInput.val()){
|
||||
this.$form.find('input[type="submit"]').removeAttr('disabled');
|
||||
}
|
||||
this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,9 @@
|
|||
const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`);
|
||||
const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`);
|
||||
|
||||
// Do not update if one dropdown has not selected any option
|
||||
if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return;
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: this.$wrap.data('url'),
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
KEYCODE = {
|
||||
ESCAPE: 27,
|
||||
BACKSPACE: 8,
|
||||
ENTER: 13
|
||||
ENTER: 13,
|
||||
UP: 38,
|
||||
DOWN: 40
|
||||
};
|
||||
|
||||
function SearchAutocomplete(opts) {
|
||||
|
@ -223,6 +225,12 @@
|
|||
case KEYCODE.ESCAPE:
|
||||
this.restoreOriginalState();
|
||||
break;
|
||||
case KEYCODE.ENTER:
|
||||
this.disableAutocomplete();
|
||||
break;
|
||||
case KEYCODE.UP:
|
||||
case KEYCODE.DOWN:
|
||||
return;
|
||||
default:
|
||||
if (this.searchInput.val() === '') {
|
||||
this.disableAutocomplete();
|
||||
|
@ -319,9 +327,11 @@
|
|||
};
|
||||
|
||||
SearchAutocomplete.prototype.disableAutocomplete = function() {
|
||||
this.searchInput.addClass('disabled');
|
||||
this.dropdown.removeClass('open');
|
||||
return this.restoreMenu();
|
||||
if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) {
|
||||
this.searchInput.addClass('disabled');
|
||||
this.dropdown.removeClass('open').trigger('hidden.bs.dropdown');
|
||||
this.restoreMenu();
|
||||
}
|
||||
};
|
||||
|
||||
SearchAutocomplete.prototype.restoreMenu = function() {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*= require_tree . */
|
||||
|
||||
(function() {
|
||||
$(function() {
|
||||
var editor = ace.edit("editor")
|
||||
|
||||
$(".snippet-form-holder form").on('submit', function() {
|
||||
$(".snippet-file-content").val(editor.getValue());
|
||||
});
|
||||
});
|
||||
|
||||
}).call(this);
|
|
@ -90,6 +90,15 @@
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// Allows dynamic-width text in the dropdown toggle.
|
||||
// Resizes to allow long text without overflowing the container.
|
||||
&.dynamic {
|
||||
width: auto;
|
||||
min-width: 160px;
|
||||
max-width: 100%;
|
||||
padding-right: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu,
|
||||
|
|
|
@ -63,9 +63,10 @@
|
|||
&.image_file {
|
||||
background: #eee;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
padding: 100px;
|
||||
max-width: 50%;
|
||||
padding: 20px;
|
||||
max-width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* Styles that apply to all GFM related forms.
|
||||
*/
|
||||
|
||||
.gfm-commit, .gfm-commit_range {
|
||||
.gfm-commit_range {
|
||||
font-family: $monospace_font;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
.modal-body {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
padding: 15px;
|
||||
|
||||
.form-actions {
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
font-weight: normal;
|
||||
background-color: #eee;
|
||||
color: #78a;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,8 @@
|
|||
min-width: 175px;
|
||||
}
|
||||
|
||||
.select2-results .select2-result-label {
|
||||
.select2-results .select2-result-label,
|
||||
.select2-more-results {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,12 +14,20 @@
|
|||
margin-top: 0;
|
||||
}
|
||||
|
||||
// Single code lines should wrap
|
||||
code {
|
||||
font-family: $monospace_font;
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
// Multi-line code blocks should scroll horizontally
|
||||
pre {
|
||||
code {
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
|
||||
kbd {
|
||||
display: inline-block;
|
||||
padding: 3px 5px;
|
||||
|
|
|
@ -8,9 +8,13 @@
|
|||
}
|
||||
|
||||
.is-dragging {
|
||||
// Important because plugin sets inline CSS
|
||||
opacity: 1!important;
|
||||
|
||||
* {
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: grabbing;
|
||||
// !important to make sure no style can override this when dragging
|
||||
cursor: -webkit-grabbing!important;
|
||||
cursor: grabbing!important;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,8 +105,8 @@
|
|||
.board {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
min-width: calc(100vw - 15px);
|
||||
max-width: calc(100vw - 15px);
|
||||
min-width: calc(85vw - 15px);
|
||||
max-width: calc(85vw - 15px);
|
||||
margin-bottom: 25px;
|
||||
padding-right: ($gl-padding / 2);
|
||||
padding-left: ($gl-padding / 2);
|
||||
|
@ -154,14 +158,6 @@
|
|||
padding: $gl-padding;
|
||||
font-size: 1em;
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
.board-mobile-handle {
|
||||
position: relative;
|
||||
left: 0;
|
||||
top: 1px;
|
||||
margin-top: 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.board-search-container {
|
||||
|
@ -254,11 +250,6 @@
|
|||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.is-dragging {
|
||||
// Important because plugin sets inline CSS
|
||||
opacity: 1!important;
|
||||
}
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
@ -269,11 +260,7 @@
|
|||
list-style: none;
|
||||
|
||||
&.user-can-drag {
|
||||
padding-left: ($gl-padding * 2);
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
padding-left: $gl-padding;
|
||||
}
|
||||
padding-left: $gl-padding;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
|
@ -294,17 +281,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.board-mobile-handle {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
margin-top: (-15px / 2);
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin: 0;
|
||||
font-size: 1em;
|
||||
|
@ -316,6 +292,7 @@
|
|||
|
||||
.card-footer {
|
||||
margin-top: 5px;
|
||||
line-height: 25px;
|
||||
|
||||
.label {
|
||||
margin-right: 4px;
|
||||
|
|
|
@ -168,7 +168,6 @@
|
|||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
background-color: $row-hover;
|
||||
color: $gl-text-color;
|
||||
}
|
||||
}
|
||||
|
@ -190,6 +189,10 @@
|
|||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $row-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,15 @@
|
|||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.ci-status-link {
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
margin: 0 2px 0 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ci-status-link {
|
||||
|
|
|
@ -34,11 +34,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wiki {
|
||||
code {
|
||||
white-space: pre-wrap;
|
||||
word-break: keep-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -384,3 +384,10 @@
|
|||
color: $gl-dark-link-color;
|
||||
}
|
||||
}
|
||||
|
||||
.merge-request-details {
|
||||
|
||||
.title {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -300,6 +300,17 @@
|
|||
|
||||
&.playable {
|
||||
background-color: $gray-light;
|
||||
|
||||
svg {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
||||
path {
|
||||
fill: $layout-link-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.build-content {
|
||||
|
@ -319,10 +330,6 @@
|
|||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.fa {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
// Connect first build in each stage with right horizontal line
|
||||
&:first-child {
|
||||
&::after {
|
||||
|
|
|
@ -719,3 +719,29 @@ pre.light-well {
|
|||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.clearable-input {
|
||||
position: relative;
|
||||
|
||||
.clear-icon {
|
||||
@extend .fa-times;
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 7px;
|
||||
top: 7px;
|
||||
color: $location-icon-color;
|
||||
|
||||
&:before {
|
||||
font-family: FontAwesome;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-value {
|
||||
.clear-icon {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,6 +109,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
:sentry_dsn,
|
||||
:akismet_enabled,
|
||||
:akismet_api_key,
|
||||
:koding_enabled,
|
||||
:koding_url,
|
||||
:email_author_in_body,
|
||||
:repository_checks_enabled,
|
||||
:metrics_packet_size,
|
||||
|
|
|
@ -42,7 +42,7 @@ class Admin::GroupsController < Admin::ApplicationController
|
|||
end
|
||||
|
||||
def members_update
|
||||
@group.add_users(params[:user_ids].split(','), params[:access_level], current_user)
|
||||
@group.add_users(params[:user_ids].split(','), params[:access_level], current_user: current_user)
|
||||
|
||||
redirect_to [:admin, @group], notice: 'Users were successfully added.'
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
|
|||
before_action :find_todos, only: [:index, :destroy_all]
|
||||
|
||||
def index
|
||||
@sort = params[:sort]
|
||||
@todos = @todos.page(params[:page])
|
||||
end
|
||||
|
||||
|
|
|
@ -21,7 +21,12 @@ class Groups::GroupMembersController < Groups::ApplicationController
|
|||
end
|
||||
|
||||
def create
|
||||
@group.add_users(params[:user_ids].split(','), params[:access_level], current_user)
|
||||
@group.add_users(
|
||||
params[:user_ids].split(','),
|
||||
params[:access_level],
|
||||
current_user: current_user,
|
||||
expires_at: params[:expires_at]
|
||||
)
|
||||
|
||||
redirect_to group_group_members_path(@group), notice: 'Users were successfully added.'
|
||||
end
|
||||
|
@ -63,7 +68,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
|
|||
protected
|
||||
|
||||
def member_params
|
||||
params.require(:group_member).permit(:access_level, :user_id)
|
||||
params.require(:group_member).permit(:access_level, :user_id, :expires_at)
|
||||
end
|
||||
|
||||
# MembershipActions concern
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
class KodingController < ApplicationController
|
||||
before_action :check_integration!, :authenticate_user!, :reject_blocked!
|
||||
layout 'koding'
|
||||
|
||||
def index
|
||||
path = File.join(Rails.root, 'doc/integration/koding-usage.md')
|
||||
@markdown = File.read(path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_integration!
|
||||
render_404 unless current_application_settings.koding_enabled?
|
||||
end
|
||||
end
|
|
@ -12,7 +12,7 @@ module Projects
|
|||
only: [:iid, :title, :confidential],
|
||||
include: {
|
||||
assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
|
||||
labels: { only: [:id, :title, :description, :color, :priority] }
|
||||
labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
@ -15,6 +15,13 @@ class Projects::BranchesController < Projects::ApplicationController
|
|||
diverging_commit_counts = repository.diverging_commit_counts(branch)
|
||||
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: @repository.branch_names
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def recent
|
||||
|
|
|
@ -11,7 +11,9 @@ class Projects::GroupLinksController < Projects::ApplicationController
|
|||
return render_404 unless can?(current_user, :read_group, group)
|
||||
|
||||
project.project_group_links.create(
|
||||
group: group, group_access: params[:link_group_access]
|
||||
group: group,
|
||||
group_access: params[:link_group_access],
|
||||
expires_at: params[:expires_at]
|
||||
)
|
||||
|
||||
redirect_to namespace_project_group_links_path(project.namespace, project)
|
||||
|
|
|
@ -36,7 +36,12 @@ class Projects::ProjectMembersController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def create
|
||||
@project.team.add_users(params[:user_ids].split(','), params[:access_level], current_user)
|
||||
@project.team.add_users(
|
||||
params[:user_ids].split(','),
|
||||
params[:access_level],
|
||||
expires_at: params[:expires_at],
|
||||
current_user: current_user
|
||||
)
|
||||
|
||||
redirect_to namespace_project_project_members_path(@project.namespace, @project)
|
||||
end
|
||||
|
@ -94,7 +99,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
|
|||
protected
|
||||
|
||||
def member_params
|
||||
params.require(:project_member).permit(:user_id, :access_level)
|
||||
params.require(:project_member).permit(:user_id, :access_level, :expires_at)
|
||||
end
|
||||
|
||||
# MembershipActions concern
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class MoveToProjectFinder
|
||||
PAGE_SIZE = 50
|
||||
|
||||
def initialize(user)
|
||||
@user = user
|
||||
end
|
||||
|
@ -8,6 +10,10 @@ class MoveToProjectFinder
|
|||
projects = projects.search(search) if search.present?
|
||||
projects = projects.excluding_project(from_project)
|
||||
|
||||
# infinite scroll using offset
|
||||
projects = projects.where('projects.id < ?', offset_id) if offset_id.present?
|
||||
projects = projects.limit(PAGE_SIZE)
|
||||
|
||||
# to ask for Project#name_with_namespace
|
||||
projects.includes(namespace: :owner)
|
||||
end
|
||||
|
|
|
@ -33,7 +33,7 @@ class TodosFinder
|
|||
# the project IDs yielded by the todos query thus far
|
||||
items = by_project(items)
|
||||
|
||||
items.reorder(id: :desc)
|
||||
sort(items)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -106,6 +106,10 @@ class TodosFinder
|
|||
params[:type]
|
||||
end
|
||||
|
||||
def sort(items)
|
||||
params[:sort] ? items.sort(params[:sort]) : items.reorder(id: :desc)
|
||||
end
|
||||
|
||||
def by_action(items)
|
||||
if action?
|
||||
items = items.where(action: to_action_id)
|
||||
|
|
|
@ -31,6 +31,10 @@ module ApplicationSettingsHelper
|
|||
current_application_settings.akismet_enabled?
|
||||
end
|
||||
|
||||
def koding_enabled?
|
||||
current_application_settings.koding_enabled?
|
||||
end
|
||||
|
||||
def allowed_protocols_present?
|
||||
current_application_settings.enabled_git_access_protocol.present?
|
||||
end
|
||||
|
|
|
@ -217,4 +217,12 @@ module BlobHelper
|
|||
def gitlab_ci_ymls
|
||||
@gitlab_ci_ymls ||= Gitlab::Template::GitlabCiYmlTemplate.dropdown_names
|
||||
end
|
||||
|
||||
def blob_editor_paths
|
||||
{
|
||||
'relative-url-root' => Rails.application.config.relative_url_root,
|
||||
'assets-prefix' => Gitlab::Application.config.assets.prefix,
|
||||
'blob-language' => @blob && @blob.language.try(:ace_mode)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -39,7 +39,7 @@ module CiStatusHelper
|
|||
when 'running'
|
||||
'icon_status_running'
|
||||
when 'play'
|
||||
return icon('play fw')
|
||||
'icon_play'
|
||||
when 'created'
|
||||
'icon_status_pending'
|
||||
else
|
||||
|
|
|
@ -72,6 +72,15 @@ module IssuablesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def issuable_labels_tooltip(labels, limit: 5)
|
||||
first, last = labels.partition.with_index{ |_, i| i < limit }
|
||||
|
||||
label_names = first.collect(&:name)
|
||||
label_names << "and #{last.size} more" unless last.empty?
|
||||
|
||||
label_names.join(', ')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sidebar_gutter_collapsed?
|
||||
|
|
|
@ -236,6 +236,60 @@ module ProjectsHelper
|
|||
)
|
||||
end
|
||||
|
||||
def add_koding_stack_path(project)
|
||||
namespace_project_new_blob_path(
|
||||
project.namespace,
|
||||
project,
|
||||
project.default_branch || 'master',
|
||||
file_name: '.koding.yml',
|
||||
commit_message: "Add Koding stack script",
|
||||
content: <<-CONTENT.strip_heredoc
|
||||
provider:
|
||||
aws:
|
||||
access_key: '${var.aws_access_key}'
|
||||
secret_key: '${var.aws_secret_key}'
|
||||
resource:
|
||||
aws_instance:
|
||||
#{project.path}-vm:
|
||||
instance_type: t2.nano
|
||||
user_data: |-
|
||||
|
||||
# Created by GitLab UI for :>
|
||||
|
||||
echo _KD_NOTIFY_@Installing Base packages...@
|
||||
|
||||
apt-get update -y
|
||||
apt-get install git -y
|
||||
|
||||
echo _KD_NOTIFY_@Cloning #{project.name}...@
|
||||
|
||||
export KODING_USER=${var.koding_user_username}
|
||||
export REPO_URL=#{root_url}${var.koding_queryString_repo}.git
|
||||
export BRANCH=${var.koding_queryString_branch}
|
||||
|
||||
sudo -i -u $KODING_USER git clone $REPO_URL -b $BRANCH
|
||||
|
||||
echo _KD_NOTIFY_@#{project.name} cloned.@
|
||||
CONTENT
|
||||
)
|
||||
end
|
||||
|
||||
def koding_project_url(project = nil, branch = nil, sha = nil)
|
||||
if project
|
||||
import_path = "/Home/Stacks/import"
|
||||
|
||||
repo = project.path_with_namespace
|
||||
branch ||= project.default_branch
|
||||
sha ||= project.commit.short_id
|
||||
|
||||
path = "#{import_path}?repo=#{repo}&branch=#{branch}&sha=#{sha}"
|
||||
|
||||
return URI.join(current_application_settings.koding_url, path).to_s
|
||||
end
|
||||
|
||||
current_application_settings.koding_url
|
||||
end
|
||||
|
||||
def contribution_guide_path(project)
|
||||
if project && contribution_guide = project.repository.contribution_guide
|
||||
namespace_project_blob_path(
|
||||
|
|
|
@ -15,20 +15,9 @@ module TimeHelper
|
|||
"#{from.to_s(:short)} - #{to.to_s(:short)}"
|
||||
end
|
||||
|
||||
def duration_in_numbers(finished_at, started_at)
|
||||
interval = interval_in_seconds(started_at, finished_at)
|
||||
time_format = interval < 1.hour ? "%M:%S" : "%H:%M:%S"
|
||||
def duration_in_numbers(duration)
|
||||
time_format = duration < 1.hour ? "%M:%S" : "%H:%M:%S"
|
||||
|
||||
Time.at(interval).utc.strftime(time_format)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def interval_in_seconds(started_at, finished_at = nil)
|
||||
if started_at && finished_at
|
||||
finished_at.to_i - started_at.to_i
|
||||
elsif started_at
|
||||
Time.now.to_i - started_at.to_i
|
||||
end
|
||||
Time.at(duration).utc.strftime(time_format)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -166,40 +166,46 @@ class Ability
|
|||
end
|
||||
|
||||
def project_abilities(user, project)
|
||||
rules = []
|
||||
key = "/user/#{user.id}/project/#{project.id}"
|
||||
|
||||
RequestStore.store[key] ||= begin
|
||||
# Push abilities on the users team role
|
||||
rules.push(*project_team_rules(project.team, user))
|
||||
|
||||
owner = user.admin? ||
|
||||
project.owner == user ||
|
||||
(project.group && project.group.has_owner?(user))
|
||||
|
||||
if owner
|
||||
rules.push(*project_owner_rules)
|
||||
end
|
||||
|
||||
if project.public? || (project.internal? && !user.external?)
|
||||
rules.push(*public_project_rules)
|
||||
|
||||
# Allow to read builds for internal projects
|
||||
rules << :read_build if project.public_builds?
|
||||
|
||||
unless owner || project.team.member?(user) || project_group_member?(project, user)
|
||||
rules << :request_access if project.request_access_enabled
|
||||
end
|
||||
end
|
||||
|
||||
if project.archived?
|
||||
rules -= project_archived_rules
|
||||
end
|
||||
|
||||
rules - project_disabled_features_rules(project)
|
||||
if RequestStore.active?
|
||||
RequestStore.store[key] ||= uncached_project_abilities(user, project)
|
||||
else
|
||||
uncached_project_abilities(user, project)
|
||||
end
|
||||
end
|
||||
|
||||
def uncached_project_abilities(user, project)
|
||||
rules = []
|
||||
# Push abilities on the users team role
|
||||
rules.push(*project_team_rules(project.team, user))
|
||||
|
||||
owner = user.admin? ||
|
||||
project.owner == user ||
|
||||
(project.group && project.group.has_owner?(user))
|
||||
|
||||
if owner
|
||||
rules.push(*project_owner_rules)
|
||||
end
|
||||
|
||||
if project.public? || (project.internal? && !user.external?)
|
||||
rules.push(*public_project_rules)
|
||||
|
||||
# Allow to read builds for internal projects
|
||||
rules << :read_build if project.public_builds?
|
||||
|
||||
unless owner || project.team.member?(user) || project_group_member?(project, user)
|
||||
rules << :request_access if project.request_access_enabled
|
||||
end
|
||||
end
|
||||
|
||||
if project.archived?
|
||||
rules -= project_archived_rules
|
||||
end
|
||||
|
||||
(rules - project_disabled_features_rules(project)).uniq
|
||||
end
|
||||
|
||||
def project_team_rules(team, user)
|
||||
# Rules based on role in project
|
||||
if team.master?(user)
|
||||
|
|
|
@ -55,6 +55,10 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
presence: true,
|
||||
if: :akismet_enabled
|
||||
|
||||
validates :koding_url,
|
||||
presence: true,
|
||||
if: :koding_enabled
|
||||
|
||||
validates :max_attachment_size,
|
||||
presence: true,
|
||||
numericality: { only_integer: true, greater_than: 0 }
|
||||
|
@ -149,6 +153,8 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
two_factor_grace_period: 48,
|
||||
recaptcha_enabled: false,
|
||||
akismet_enabled: false,
|
||||
koding_enabled: false,
|
||||
koding_url: nil,
|
||||
repository_checks_enabled: true,
|
||||
disabled_oauth_sign_in_sources: [],
|
||||
send_user_confirmation_email: false,
|
||||
|
|
|
@ -62,6 +62,7 @@ module Ci
|
|||
status_event: 'enqueue'
|
||||
)
|
||||
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
|
||||
build.pipeline.mark_as_processable_after_stage(build.stage_idx)
|
||||
new_build
|
||||
end
|
||||
end
|
||||
|
|
|
@ -78,6 +78,10 @@ module Ci
|
|||
CommitStatus.where(pipeline: pluck(:id)).stages
|
||||
end
|
||||
|
||||
def self.total_duration
|
||||
where.not(duration: nil).sum(:duration)
|
||||
end
|
||||
|
||||
def stages_with_latest_statuses
|
||||
statuses.latest.order(:stage_idx).group_by(&:stage)
|
||||
end
|
||||
|
@ -146,6 +150,10 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def mark_as_processable_after_stage(stage_idx)
|
||||
builds.skipped.where('stage_idx > ?', stage_idx).find_each(&:process)
|
||||
end
|
||||
|
||||
def latest?
|
||||
return false unless ref
|
||||
commit = project.commit(ref)
|
||||
|
@ -250,7 +258,7 @@ module Ci
|
|||
end
|
||||
|
||||
def update_duration
|
||||
self.duration = statuses.latest.duration
|
||||
self.duration = calculate_duration
|
||||
end
|
||||
|
||||
def execute_hooks
|
||||
|
|
|
@ -229,7 +229,7 @@ class Commit
|
|||
|
||||
def diff_refs
|
||||
Gitlab::Diff::DiffRefs.new(
|
||||
base_sha: self.parent_id || self.sha,
|
||||
base_sha: self.parent_id || Gitlab::Git::BLANK_SHA,
|
||||
head_sha: self.sha
|
||||
)
|
||||
end
|
||||
|
|
|
@ -21,6 +21,7 @@ class CommitStatus < ActiveRecord::Base
|
|||
|
||||
where(id: max_id.group(:name, :commit_id))
|
||||
end
|
||||
|
||||
scope :retried, -> { where.not(id: latest) }
|
||||
scope :ordered, -> { order(:name) }
|
||||
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
|
||||
|
@ -30,6 +31,10 @@ class CommitStatus < ActiveRecord::Base
|
|||
transition [:created, :skipped] => :pending
|
||||
end
|
||||
|
||||
event :process do
|
||||
transition skipped: :created
|
||||
end
|
||||
|
||||
event :run do
|
||||
transition pending: :running
|
||||
end
|
||||
|
@ -107,13 +112,7 @@ class CommitStatus < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def duration
|
||||
duration =
|
||||
if started_at && finished_at
|
||||
finished_at - started_at
|
||||
elsif started_at
|
||||
Time.now - started_at
|
||||
end
|
||||
duration
|
||||
calculate_duration
|
||||
end
|
||||
|
||||
def stuck?
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
module Expirable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
scope :expired, -> { where('expires_at <= ?', Time.current) }
|
||||
end
|
||||
|
||||
def expires?
|
||||
expires_at.present?
|
||||
end
|
||||
|
||||
def expires_soon?
|
||||
expires_at < 7.days.from_now
|
||||
end
|
||||
end
|
|
@ -131,7 +131,10 @@ module Issuable
|
|||
end
|
||||
|
||||
def order_labels_priority(excluded_labels: [])
|
||||
select("#{table_name}.*, (#{highest_label_priority(excluded_labels).to_sql}) AS highest_priority").
|
||||
condition_field = "#{table_name}.id"
|
||||
highest_priority = highest_label_priority(name, condition_field, excluded_labels: excluded_labels).to_sql
|
||||
|
||||
select("#{table_name}.*, (#{highest_priority}) AS highest_priority").
|
||||
group(arel_table[:id]).
|
||||
reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC'))
|
||||
end
|
||||
|
@ -159,20 +162,6 @@ module Issuable
|
|||
|
||||
grouping_columns
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def highest_label_priority(excluded_labels)
|
||||
query = Label.select(Label.arel_table[:priority].minimum).
|
||||
joins(:label_links).
|
||||
where(label_links: { target_type: name }).
|
||||
where("label_links.target_id = #{table_name}.id").
|
||||
reorder(nil)
|
||||
|
||||
query.where.not(title: excluded_labels) if excluded_labels.present?
|
||||
|
||||
query
|
||||
end
|
||||
end
|
||||
|
||||
def today?
|
||||
|
|
|
@ -17,6 +17,10 @@ module NoteOnDiff
|
|||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def original_line_code
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def diff_attributes
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
|
|
@ -35,5 +35,19 @@ module Sortable
|
|||
all
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def highest_label_priority(object_types, condition_field, excluded_labels: [])
|
||||
query = Label.select(Label.arel_table[:priority].minimum).
|
||||
joins(:label_links).
|
||||
where(label_links: { target_type: object_types }).
|
||||
where("label_links.target_id = #{condition_field}").
|
||||
reorder(nil)
|
||||
|
||||
query.where.not(title: excluded_labels) if excluded_labels.present?
|
||||
|
||||
query
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,11 +35,6 @@ module Statuseable
|
|||
all.pluck(self.status_sql).first
|
||||
end
|
||||
|
||||
def duration
|
||||
duration_array = all.map(&:duration).compact
|
||||
duration_array.reduce(:+)
|
||||
end
|
||||
|
||||
def started_at
|
||||
all.minimum(:started_at)
|
||||
end
|
||||
|
@ -85,4 +80,14 @@ module Statuseable
|
|||
def complete?
|
||||
COMPLETED_STATUSES.include?(status)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def calculate_duration
|
||||
if started_at && finished_at
|
||||
finished_at - started_at
|
||||
elsif started_at
|
||||
Time.now - started_at
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,6 +16,9 @@ class DiffNote < Note
|
|||
after_initialize :ensure_original_discussion_id
|
||||
before_validation :set_original_position, :update_position, on: :create
|
||||
before_validation :set_line_code, :set_original_discussion_id
|
||||
# We need to do this again, because it's already in `Note`, but is affected by
|
||||
# `update_position` and needs to run after that.
|
||||
before_validation :set_discussion_id
|
||||
after_save :keep_around_commits
|
||||
|
||||
class << self
|
||||
|
@ -57,6 +60,10 @@ class DiffNote < Note
|
|||
diff_file.position(line) == self.original_position
|
||||
end
|
||||
|
||||
def original_line_code
|
||||
self.diff_file.line_code(self.diff_line)
|
||||
end
|
||||
|
||||
def active?(diff_refs = nil)
|
||||
return false unless supported?
|
||||
return true if for_commit?
|
||||
|
|
|
@ -12,6 +12,7 @@ class Discussion
|
|||
:for_merge_request?,
|
||||
|
||||
:line_code,
|
||||
:original_line_code,
|
||||
:diff_file,
|
||||
:for_line?,
|
||||
:active?,
|
||||
|
|
|
@ -95,34 +95,40 @@ class Group < Namespace
|
|||
end
|
||||
end
|
||||
|
||||
def add_users(user_ids, access_level, current_user = nil)
|
||||
def add_users(user_ids, access_level, current_user: nil, expires_at: nil)
|
||||
user_ids.each do |user_id|
|
||||
Member.add_user(self.group_members, user_id, access_level, current_user)
|
||||
Member.add_user(
|
||||
self.group_members,
|
||||
user_id,
|
||||
access_level,
|
||||
current_user: current_user,
|
||||
expires_at: expires_at
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def add_user(user, access_level, current_user = nil)
|
||||
add_users([user], access_level, current_user)
|
||||
def add_user(user, access_level, current_user: nil, expires_at: nil)
|
||||
add_users([user], access_level, current_user: current_user, expires_at: expires_at)
|
||||
end
|
||||
|
||||
def add_guest(user, current_user = nil)
|
||||
add_user(user, Gitlab::Access::GUEST, current_user)
|
||||
add_user(user, Gitlab::Access::GUEST, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_reporter(user, current_user = nil)
|
||||
add_user(user, Gitlab::Access::REPORTER, current_user)
|
||||
add_user(user, Gitlab::Access::REPORTER, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_developer(user, current_user = nil)
|
||||
add_user(user, Gitlab::Access::DEVELOPER, current_user)
|
||||
add_user(user, Gitlab::Access::DEVELOPER, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_master(user, current_user = nil)
|
||||
add_user(user, Gitlab::Access::MASTER, current_user)
|
||||
add_user(user, Gitlab::Access::MASTER, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_owner(user, current_user = nil)
|
||||
add_user(user, Gitlab::Access::OWNER, current_user)
|
||||
add_user(user, Gitlab::Access::OWNER, current_user: current_user)
|
||||
end
|
||||
|
||||
def has_owner?(user)
|
||||
|
|
|
@ -49,6 +49,10 @@ class LegacyDiffNote < Note
|
|||
!line.meta? && diff_file.line_code(line) == self.line_code
|
||||
end
|
||||
|
||||
def original_line_code
|
||||
self.line_code
|
||||
end
|
||||
|
||||
# Check if this note is part of an "active" discussion
|
||||
#
|
||||
# This will always return true for anything except MergeRequest noteables,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
class Member < ActiveRecord::Base
|
||||
include Sortable
|
||||
include Importable
|
||||
include Expirable
|
||||
include Gitlab::Access
|
||||
|
||||
attr_accessor :raw_invite_token
|
||||
|
@ -73,7 +74,7 @@ class Member < ActiveRecord::Base
|
|||
user
|
||||
end
|
||||
|
||||
def add_user(members, user_id, access_level, current_user = nil)
|
||||
def add_user(members, user_id, access_level, current_user: nil, expires_at: nil)
|
||||
user = user_for_id(user_id)
|
||||
|
||||
# `user` can be either a User object or an email to be invited
|
||||
|
@ -87,6 +88,7 @@ class Member < ActiveRecord::Base
|
|||
if can_update_member?(current_user, member) || project_creator?(member, access_level)
|
||||
member.created_by ||= current_user
|
||||
member.access_level = access_level
|
||||
member.expires_at = expires_at
|
||||
|
||||
member.save
|
||||
end
|
||||
|
|
|
@ -34,7 +34,7 @@ class ProjectMember < Member
|
|||
# :master
|
||||
# )
|
||||
#
|
||||
def add_users_to_projects(project_ids, user_ids, access, current_user = nil)
|
||||
def add_users_to_projects(project_ids, user_ids, access, current_user: nil, expires_at: nil)
|
||||
access_level = if roles_hash.has_key?(access)
|
||||
roles_hash[access]
|
||||
elsif roles_hash.values.include?(access.to_i)
|
||||
|
@ -50,7 +50,13 @@ class ProjectMember < Member
|
|||
project = Project.find(project_id)
|
||||
|
||||
users.each do |user|
|
||||
Member.add_user(project.project_members, user, access_level, current_user)
|
||||
Member.add_user(
|
||||
project.project_members,
|
||||
user,
|
||||
access_level,
|
||||
current_user: current_user,
|
||||
expires_at: expires_at
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -259,6 +259,8 @@ class Note < ActiveRecord::Base
|
|||
|
||||
def ensure_discussion_id
|
||||
return unless self.persisted?
|
||||
# Needed in case the SELECT statement doesn't ask for `discussion_id`
|
||||
return unless self.has_attribute?(:discussion_id)
|
||||
return if self.discussion_id
|
||||
|
||||
set_discussion_id
|
||||
|
|
|
@ -611,7 +611,10 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def new_issue_address(author)
|
||||
if Gitlab::IncomingEmail.enabled? && author
|
||||
# This feature is disabled for the time being.
|
||||
return nil
|
||||
|
||||
if Gitlab::IncomingEmail.enabled? && author # rubocop:disable Lint/UnreachableCode
|
||||
Gitlab::IncomingEmail.reply_address(
|
||||
"#{path_with_namespace}+#{author.authentication_token}")
|
||||
end
|
||||
|
@ -1003,8 +1006,8 @@ class Project < ActiveRecord::Base
|
|||
project_members.find_by(user_id: user)
|
||||
end
|
||||
|
||||
def add_user(user, access_level, current_user = nil)
|
||||
team.add_user(user, access_level, current_user)
|
||||
def add_user(user, access_level, current_user: nil, expires_at: nil)
|
||||
team.add_user(user, access_level, current_user: current_user, expires_at: expires_at)
|
||||
end
|
||||
|
||||
def default_branch
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class ProjectGroupLink < ActiveRecord::Base
|
||||
include Expirable
|
||||
|
||||
GUEST = 10
|
||||
REPORTER = 20
|
||||
DEVELOPER = 30
|
||||
|
@ -26,7 +28,7 @@ class ProjectGroupLink < ActiveRecord::Base
|
|||
self.class.access_options.key(self.group_access)
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def different_group
|
||||
if self.group && self.project && self.project.group == self.group
|
||||
|
|
|
@ -15,9 +15,9 @@ class ProjectTeam
|
|||
users, access, current_user = *args
|
||||
|
||||
if users.respond_to?(:each)
|
||||
add_users(users, access, current_user)
|
||||
add_users(users, access, current_user: current_user)
|
||||
else
|
||||
add_user(users, access, current_user)
|
||||
add_user(users, access, current_user: current_user)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -33,17 +33,18 @@ class ProjectTeam
|
|||
member
|
||||
end
|
||||
|
||||
def add_users(users, access, current_user = nil)
|
||||
def add_users(users, access, current_user: nil, expires_at: nil)
|
||||
ProjectMember.add_users_to_projects(
|
||||
[project.id],
|
||||
users,
|
||||
access,
|
||||
current_user
|
||||
current_user: current_user,
|
||||
expires_at: expires_at
|
||||
)
|
||||
end
|
||||
|
||||
def add_user(user, access, current_user = nil)
|
||||
add_users([user], access, current_user)
|
||||
def add_user(user, access, current_user: nil, expires_at: nil)
|
||||
add_users([user], access, current_user: current_user, expires_at: expires_at)
|
||||
end
|
||||
|
||||
# Remove all users from project team
|
||||
|
|
|
@ -277,7 +277,7 @@ class Repository
|
|||
def cache_keys
|
||||
%i(size commit_count
|
||||
readme version contribution_guide changelog
|
||||
license_blob license_key gitignore)
|
||||
license_blob license_key gitignore koding_yml)
|
||||
end
|
||||
|
||||
# Keys for data on branch/tag operations.
|
||||
|
@ -553,6 +553,14 @@ class Repository
|
|||
end
|
||||
end
|
||||
|
||||
def koding_yml
|
||||
return nil unless head_exists?
|
||||
|
||||
cache.fetch(:koding_yml) do
|
||||
file_on_head(/\A\.koding\.yml\z/)
|
||||
end
|
||||
end
|
||||
|
||||
def gitlab_ci_yml
|
||||
return nil unless head_exists?
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class Todo < ActiveRecord::Base
|
||||
include Sortable
|
||||
|
||||
ASSIGNED = 1
|
||||
MENTIONED = 2
|
||||
BUILD_FAILED = 3
|
||||
|
@ -41,6 +43,23 @@ class Todo < ActiveRecord::Base
|
|||
|
||||
after_save :keep_around_commit
|
||||
|
||||
class << self
|
||||
def sort(method)
|
||||
method == "priority" ? order_by_labels_priority : order_by(method)
|
||||
end
|
||||
|
||||
# Order by priority depending on which issue/merge request the Todo belongs to
|
||||
# Todos with highest priority first then oldest todos
|
||||
# Need to order by created_at last because of differences on Mysql and Postgres when joining by type "Merge_request/Issue"
|
||||
def order_by_labels_priority
|
||||
highest_priority = highest_label_priority(["Issue", "MergeRequest"], "todos.target_id").to_sql
|
||||
|
||||
select("#{table_name}.*, (#{highest_priority}) AS highest_priority").
|
||||
order(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')).
|
||||
order('todos.created_at')
|
||||
end
|
||||
end
|
||||
|
||||
def build_failed?
|
||||
action == BUILD_FAILED
|
||||
end
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
module Members
|
||||
class AuthorizedDestroyService < BaseService
|
||||
attr_accessor :member, :user
|
||||
|
||||
def initialize(member, user = nil)
|
||||
@member, @user = member, user
|
||||
end
|
||||
|
||||
def execute
|
||||
return false if member.is_a?(GroupMember) && member.source.last_owner?(member.user)
|
||||
|
||||
member.destroy
|
||||
|
||||
if member.request? && member.user != user
|
||||
notification_service.decline_access_request(member)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,12 +11,7 @@ module Members
|
|||
unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member)
|
||||
raise Gitlab::Access::AccessDeniedError
|
||||
end
|
||||
|
||||
member.destroy
|
||||
|
||||
if member.request? && member.user != current_user
|
||||
notification_service.decline_access_request(member)
|
||||
end
|
||||
AuthorizedDestroyService.new(member, current_user).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -242,7 +242,6 @@ class NotificationService
|
|||
project_member.real_source_type,
|
||||
project_member.project.id,
|
||||
project_member.invite_email,
|
||||
project_member.access_level,
|
||||
project_member.created_by_id
|
||||
).deliver_later
|
||||
end
|
||||
|
@ -269,7 +268,6 @@ class NotificationService
|
|||
group_member.real_source_type,
|
||||
group_member.group.id,
|
||||
group_member.invite_email,
|
||||
group_member.access_level,
|
||||
group_member.created_by_id
|
||||
).deliver_later
|
||||
end
|
||||
|
|
|
@ -388,6 +388,25 @@
|
|||
.help-block
|
||||
If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database.
|
||||
|
||||
%fieldset
|
||||
%legend Koding
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
.checkbox
|
||||
= f.label :koding_enabled do
|
||||
= f.check_box :koding_enabled
|
||||
Enable Koding
|
||||
.form-group
|
||||
= f.label :koding_url, 'Koding URL', class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.text_field :koding_url, class: 'form-control', placeholder: 'http://gitlab.your-koding-instance.com:8090'
|
||||
.help-block
|
||||
Koding has integration enabled out of the box for the
|
||||
%strong gitlab
|
||||
team, and you need to provide that team's URL here. Learn more in the
|
||||
= succeed "." do
|
||||
= link_to "Koding integration documentation", help_page_path("integration/koding")
|
||||
|
||||
|
||||
.form-actions
|
||||
= f.submit 'Save', class: 'btn btn-save'
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
- if build.duration
|
||||
%p.duration
|
||||
= custom_icon("icon_timer")
|
||||
= duration_in_numbers(build.finished_at, build.started_at)
|
||||
= duration_in_numbers(build.duration)
|
||||
|
||||
- if build.finished_at
|
||||
%p.finished-at
|
||||
|
|
|
@ -43,6 +43,25 @@
|
|||
class: 'select2 trigger-submit', include_blank: true,
|
||||
data: {placeholder: 'Action'})
|
||||
|
||||
.pull-right
|
||||
.dropdown.inline.prepend-left-10
|
||||
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
%span.light
|
||||
- if @sort.present?
|
||||
= sort_options_hash[@sort]
|
||||
- else
|
||||
= sort_title_recently_created
|
||||
%b.caret
|
||||
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
|
||||
%li
|
||||
= link_to todos_filter_path(sort: sort_value_priority) do
|
||||
= sort_title_priority
|
||||
= link_to todos_filter_path(sort: sort_value_recently_created) do
|
||||
= sort_title_recently_created
|
||||
= link_to todos_filter_path(sort: sort_value_oldest_created) do
|
||||
= sort_title_oldest_created
|
||||
|
||||
|
||||
.prepend-top-default
|
||||
- if @todos.any?
|
||||
.js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
.diff-content.code.js-syntax-highlight
|
||||
%table
|
||||
- discussions = { discussion.line_code => discussion }
|
||||
- discussions = { discussion.original_line_code => discussion }
|
||||
= render partial: "projects/diffs/line",
|
||||
collection: discussion.truncated_diff_lines,
|
||||
as: :line,
|
||||
|
|
|
@ -14,5 +14,14 @@
|
|||
Read more about role permissions
|
||||
%strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
|
||||
|
||||
.form-group
|
||||
= f.label :expires_at, 'Access expiration date', class: 'control-label'
|
||||
.col-sm-10
|
||||
.clearable-input
|
||||
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date'
|
||||
%i.clear-icon.js-clear-input
|
||||
.help-block
|
||||
On this date, the user(s) will automatically lose access to this group and all of its projects.
|
||||
|
||||
.form-actions
|
||||
= f.submit 'Add users to group', class: "btn btn-create"
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
:plain
|
||||
$("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}');
|
||||
new MemberExpirationDate();
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.row-content-block.second-block.center
|
||||
%p
|
||||
= icon('circle', class: 'cgreen')
|
||||
Integration is active for
|
||||
= link_to koding_project_url, target: '_blank' do
|
||||
#{current_application_settings.koding_url}
|
||||
|
||||
.documentation.wiki
|
||||
= markdown @markdown
|
|
@ -0,0 +1,5 @@
|
|||
- page_title "Koding"
|
||||
- page_description "Koding Dashboard"
|
||||
- header_title "Koding", koding_path
|
||||
|
||||
= render template: "layouts/application"
|
|
@ -12,6 +12,11 @@
|
|||
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
|
||||
%span
|
||||
Activity
|
||||
- if koding_enabled?
|
||||
= nav_link(controller: :koding) do
|
||||
= link_to koding_path, title: 'Koding' do
|
||||
%span
|
||||
Koding
|
||||
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
|
||||
= link_to dashboard_groups_path, title: 'Groups' do
|
||||
%span
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
Graphs
|
||||
|
||||
- if project_nav_tab? :issues
|
||||
= nav_link(controller: [:issues, :labels, :milestones]) do
|
||||
= nav_link(controller: [:issues, :labels, :milestones, :boards]) do
|
||||
= link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do
|
||||
%span
|
||||
Issues
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
- page_title "Edit", @blob.path, @ref
|
||||
- content_for :page_specific_javascripts do
|
||||
= page_specific_javascript_tag('lib/ace.js')
|
||||
= page_specific_javascript_tag('blob_edit/blob_edit_bundle.js')
|
||||
|
||||
- if @conflict
|
||||
.alert.alert-danger
|
||||
|
@ -16,14 +19,10 @@
|
|||
= link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
|
||||
= editing_preview_title(@blob.name)
|
||||
|
||||
= form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form') do
|
||||
= form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do
|
||||
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
|
||||
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
|
||||
= hidden_field_tag 'last_commit_sha', @last_commit_sha
|
||||
= hidden_field_tag 'content', '', id: "file-content"
|
||||
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
|
||||
= render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
|
||||
|
||||
:javascript
|
||||
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
|
||||
new NewCommitForm($('.js-edit-blob-form'))
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
- page_title "New File", @path.presence, @ref
|
||||
- content_for :page_specific_javascripts do
|
||||
= page_specific_javascript_tag('lib/ace.js')
|
||||
= page_specific_javascript_tag('blob_edit/blob_edit_bundle.js')
|
||||
|
||||
%h3.page-title
|
||||
New File
|
||||
|
||||
.file-editor
|
||||
= form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-new-blob-form js-quick-submit js-requires-input') do
|
||||
= form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do
|
||||
= render 'projects/blob/editor', ref: @ref
|
||||
= render 'shared/new_commit_form', placeholder: "Add new file"
|
||||
|
||||
= hidden_field_tag 'content', '', id: 'file-content'
|
||||
= render 'projects/commit_button', ref: @ref,
|
||||
cancel_path: namespace_project_tree_path(@project.namespace, @project, @id)
|
||||
|
||||
:javascript
|
||||
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}")
|
||||
new NewCommitForm($('.js-new-blob-form'))
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
.board-inner
|
||||
%header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" }
|
||||
%h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" }
|
||||
= icon("align-justify", class: "board-mobile-handle js-board-drag-handle", "v-if" => "(!disabled && !list.preset)")
|
||||
{{ list.title }}
|
||||
%span.pull-right{ "v-if" => "list.type !== 'blank'" }
|
||||
{{ list.issues.length }}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
"track-by" => "id" }
|
||||
%li.card{ ":class" => "{ 'user-can-drag': !disabled }",
|
||||
":index" => "index" }
|
||||
= icon("align-justify", class: "board-mobile-handle js-card-drag-handle", "v-if" => "!disabled")
|
||||
%h4.card-title
|
||||
= icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential")
|
||||
%a{ ":href" => "issueLinkBase + '/' + issue.id",
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
- number_commits_ahead = diverging_commit_counts[:ahead]
|
||||
%li(class="js-branch-#{branch.name}")
|
||||
%div
|
||||
= link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do
|
||||
%span.item-title.str-truncated= branch.name
|
||||
= link_to namespace_project_tree_path(@project.namespace, @project, branch.name), class: 'item-title str-truncated' do
|
||||
= branch.name
|
||||
|
||||
- if branch.name == @repository.root_ref
|
||||
%span.label.label-primary default
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
- if koding_enabled? && current_user && can_push_branch?(@project, @project.default_branch)
|
||||
- if @repository.koding_yml
|
||||
= link_to koding_project_url(@project), class: 'btn', target: '_blank' do
|
||||
Run in IDE (Koding)
|
||||
- else
|
||||
= link_to add_koding_stack_path(@project), class: 'btn' do
|
||||
Set Up Koding
|
|
@ -63,7 +63,7 @@
|
|||
- if build.duration
|
||||
%p.duration
|
||||
= custom_icon("icon_timer")
|
||||
= duration_in_numbers(build.finished_at, build.started_at)
|
||||
= duration_in_numbers(build.duration)
|
||||
- if build.finished_at
|
||||
%p.finished-at
|
||||
= icon("calendar")
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue