Commit 5a1a1e67 authored by Ruben Davila's avatar Ruben Davila

Merge branch '8-11-stable-ee' into stable-to-master

 Conflicts:
	app/assets/javascripts/application.js
	app/assets/javascripts/boards/boards_bundle.js.es6
	app/assets/javascripts/boards/components/board.js.es6
	app/assets/javascripts/boards/components/board_list.js.es6
	app/assets/javascripts/boards/mixins/sortable_default_options.js.es6
	app/assets/javascripts/boards/models/label.js.es6
	app/assets/javascripts/labels_select.js
	app/assets/javascripts/protected_branch_access_dropdown.js.es6
	app/assets/stylesheets/pages/boards.scss
	app/assets/stylesheets/pages/builds.scss
	app/assets/stylesheets/pages/pipelines.scss
	app/controllers/projects/boards/issues_controller.rb
	app/controllers/projects/merge_requests_controller.rb
	app/helpers/blob_helper.rb
	app/helpers/ci_status_helper.rb
	app/models/ci/pipeline.rb
	app/models/diff_note.rb
	app/models/note.rb
	app/models/project.rb
	app/views/discussions/_diff_with_notes.html.haml
	app/views/projects/boards/components/_board.html.haml
	app/views/projects/boards/components/_card.html.haml
	app/views/projects/ci/pipelines/_pipeline.html.haml
	app/views/projects/merge_requests/widget/_open.html.haml
	app/views/projects/notes/_hints.html.haml
	app/views/projects/protected_branches/_create_protected_branch.html.haml
	app/views/shared/issuable/_filter.html.haml
	app/views/shared/issuable/_form.html.haml
	app/views/shared/issuable/_label_page_default.html.haml
	config/application.rb
	db/schema.rb
	doc/user/project/protected_branches.md
	features/steps/project/merge_requests.rb
	spec/controllers/autocomplete_controller_spec.rb
	spec/controllers/projects/discussions_controller_spec.rb
	spec/features/boards/boards_spec.rb
	spec/features/protected_branches/access_control_ee_spec.rb
	spec/features/protected_branches_spec.rb
	spec/fixtures/api/schemas/issue.json
	spec/lib/ee/gitlab/ldap/sync/group_spec.rb
	spec/workers/elastic_indexer_worker_spec.rb
parents 52d5efaa 671f7e85
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.11.0 (unreleased) v 8.11.2 (unreleased)
v 8.11.1
- Fix file links on project page when default view is Files !5933
- Fixed enter key in search input not working !5888
v 8.11.0
- Use test coverage value from the latest successful pipeline in badge. !5862
- Add test coverage report badge. !5708 - Add test coverage report badge. !5708
- Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) - 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) - Ability to specify branches for Pivotal Tracker integration (Egor Lynko)
- Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
- Add delimiter to project stars and forks count (ClemMakesApps) - Add delimiter to project stars and forks count (ClemMakesApps)
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres) - 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) - Fix the title of the toggle dropdown button. !5515 (herminiotorres)
- Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz) - Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz)
- Update to Ruby 2.3.1. !4948 - Update to Ruby 2.3.1. !4948
...@@ -18,6 +27,7 @@ v 8.11.0 (unreleased) ...@@ -18,6 +27,7 @@ v 8.11.0 (unreleased)
- API: Endpoints for enabling and disabling deploy keys - 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 - 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) - 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) - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell)
- GitLab Performance Monitoring can now track custom events such as the number of tags pushed to a repository - 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) - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell)
...@@ -25,6 +35,8 @@ v 8.11.0 (unreleased) ...@@ -25,6 +35,8 @@ v 8.11.0 (unreleased)
- Ignore URLs starting with // in Markdown links !5677 (winniehell) - Ignore URLs starting with // in Markdown links !5677 (winniehell)
- Fix CI status icon link underline (ClemMakesApps) - Fix CI status icon link underline (ClemMakesApps)
- The Repository class is now instrumented - 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) - Fix filter label tooltip HTML rendering (ClemMakesApps)
- Cache the commit author in RequestStore to avoid extra lookups in PostReceive - Cache the commit author in RequestStore to avoid extra lookups in PostReceive
- Expand commit message width in repo view (ClemMakesApps) - Expand commit message width in repo view (ClemMakesApps)
...@@ -34,9 +46,11 @@ v 8.11.0 (unreleased) ...@@ -34,9 +46,11 @@ v 8.11.0 (unreleased)
- API: Add deployment endpoints - API: Add deployment endpoints
- API: Add Play endpoint on Builds - API: Add Play endpoint on Builds
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI' - 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 - Show member roles to all users on members page
- Project.visible_to_user is instrumented again - Project.visible_to_user is instrumented again
- Fix awardable button mutuality loading spinners (ClemMakesApps) - 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 - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
- Optimize maximum user access level lookup in loading of notes - Optimize maximum user access level lookup in loading of notes
- Send notification emails to users newly mentioned in issue and MR edits !5800 - Send notification emails to users newly mentioned in issue and MR edits !5800
...@@ -79,7 +93,6 @@ v 8.11.0 (unreleased) ...@@ -79,7 +93,6 @@ v 8.11.0 (unreleased)
- Add archived badge to project list !5798 - Add archived badge to project list !5798
- Add simple identifier to public SSH keys (muteor) - Add simple identifier to public SSH keys (muteor)
- Admin page now references docs instead of a specific file !5600 (AnAverageHuman) - 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) - Fix filter input alignment (ClemMakesApps)
- Include old revision in merge request update hooks (Ben Boeckel) - Include old revision in merge request update hooks (Ben Boeckel)
- Add build event color in HipChat messages (David Eisner) - Add build event color in HipChat messages (David Eisner)
...@@ -124,9 +137,11 @@ v 8.11.0 (unreleased) ...@@ -124,9 +137,11 @@ v 8.11.0 (unreleased)
- Change requests_profiles resource constraint to catch virtually any file - Change requests_profiles resource constraint to catch virtually any file
- Bump gitlab_git to lazy load compare commits - Bump gitlab_git to lazy load compare commits
- Reduce number of queries made for merge_requests/:id/diffs - 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) - 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 bug where destroying a namespace would not always destroy projects
- Fix RequestProfiler::Middleware error when code is reloaded in development - 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 - 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 - 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) - 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) ...@@ -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 - 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 auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska)
- Add pipelines tab to merge requests - Add pipelines tab to merge requests
- Fix notification_service argument error of declined invitation emails
- Fix a memory leak caused by Banzai::Filter::SanitizationFilter - Fix a memory leak caused by Banzai::Filter::SanitizationFilter
- Speed up todos queries by limiting the projects set we join with - 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 - Ensure file editing in UI does not overwrite commited changes without warning user
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.11.0 (unreleased) v 8.11.2 (Unreleased)
v 8.11.1
- Additional documentation on protected branches for EE
- Change slash commands docs location
v 8.11.0
- Allow projects to be moved between repository storages - Allow projects to be moved between repository storages
- Add rake task to remove old repository copies from repositories moved to another storage - Add rake task to remove old repository copies from repositories moved to another storage
- Performance improvement of push rules - Performance improvement of push rules
......
8.11.0-ee-pre 8.11.0-ee
<?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>
\ No newline at end of file
...@@ -27,8 +27,6 @@ ...@@ -27,8 +27,6 @@
/*= require bootstrap/tooltip */ /*= require bootstrap/tooltip */
/*= require bootstrap/popover */ /*= require bootstrap/popover */
/*= require select2 */ /*= require select2 */
/*= require ace-rails-ap */
/*= require ace/ext-searchbox */
/*= require underscore */ /*= require underscore */
/*= require dropzone */ /*= require dropzone */
/*= require mousetrap */ /*= require mousetrap */
......
/*= 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 @@ $(() => { ...@@ -38,7 +38,7 @@ $(() => {
ready () { ready () {
Store.disabled = this.disabled; Store.disabled = this.disabled;
gl.boardService.all() gl.boardService.all()
.then((resp) => { .then((resp) => {
resp.json().forEach((board) => { resp.json().forEach((board) => {
const list = Store.addList(board); const list = Store.addList(board);
......
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
draggable: '.is-draggable', draggable: '.is-draggable',
handle: '.js-board-handle', handle: '.js-board-handle',
onEnd: (e) => { onEnd: (e) => {
document.body.classList.remove('is-dragging'); gl.issueBoards.onEnd();
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) { if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
const order = this.sortable.toArray(), const order = this.sortable.toArray(),
...@@ -72,10 +72,6 @@ ...@@ -72,10 +72,6 @@
} }
}); });
if (bp.getBreakpointSize() === 'xs') {
options.handle = '.js-board-drag-handle';
}
this.sortable = Sortable.create(this.$el.parentNode, options); this.sortable = Sortable.create(this.$el.parentNode, options);
}, },
beforeDestroy () { beforeDestroy () {
......
...@@ -63,6 +63,8 @@ ...@@ -63,6 +63,8 @@
Store.moving.issue = card.issue; Store.moving.issue = card.issue;
Store.moving.list = card.list; Store.moving.list = card.list;
gl.issueBoards.onStart();
}, },
onAdd: (e) => { onAdd: (e) => {
gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue); gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue);
...@@ -72,10 +74,6 @@ ...@@ -72,10 +74,6 @@
} }
}); });
if (bp.getBreakpointSize() === 'xs') {
options.handle = '.js-card-drag-handle';
}
this.sortable = Sortable.create(this.$els.list, options); this.sortable = Sortable.create(this.$els.list, options);
// Scroll event on list to load more // Scroll event on list to load more
......
...@@ -2,6 +2,19 @@ ...@@ -2,6 +2,19 @@
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {}; 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) => { gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
let defaultSortOptions = { let defaultSortOptions = {
forceFallback: true, forceFallback: true,
...@@ -9,14 +22,11 @@ ...@@ -9,14 +22,11 @@
fallbackOnBody: true, fallbackOnBody: true,
ghostClass: 'is-ghost', ghostClass: 'is-ghost',
filter: '.has-tooltip', filter: '.has-tooltip',
scrollSensitivity: 100, delay: gl.issueBoards.touchEnabled ? 100 : 0,
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
scrollSpeed: 20, scrollSpeed: 20,
onStart () { onStart: gl.issueBoards.onStart,
document.body.classList.add('is-dragging'); onEnd: gl.issueBoards.onEnd
},
onEnd () {
document.body.classList.remove('is-dragging');
}
} }
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; }); Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
......
...@@ -3,6 +3,7 @@ class ListLabel { ...@@ -3,6 +3,7 @@ class ListLabel {
this.id = obj.id; this.id = obj.id;
this.title = obj.title; this.title = obj.title;
this.color = obj.color; this.color = obj.color;
this.textColor = obj.text_color;
this.description = obj.description; this.description = obj.description;
this.priority = (obj.priority !== null) ? obj.priority : Infinity; this.priority = (obj.priority !== null) ? obj.priority : Infinity;
} }
......
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
path = page.split(':'); path = page.split(':');
shortcut_handler = null; shortcut_handler = null;
switch (page) { switch (page) {
case 'projects:boards:show':
shortcut_handler = new ShortcutsNavigation();
break;
case 'projects:issues:index': case 'projects:issues:index':
Issuable.init(); Issuable.init();
new IssuableBulkActions(); new IssuableBulkActions();
...@@ -126,10 +129,12 @@ ...@@ -126,10 +129,12 @@
new NotificationsDropdown(); new NotificationsDropdown();
break; break;
case 'groups:group_members:index': case 'groups:group_members:index':
new gl.MemberExpirationDate();
new GroupMembers(); new GroupMembers();
new UsersSelect(); new UsersSelect();
break; break;
case 'projects:project_members:index': case 'projects:project_members:index':
new gl.MemberExpirationDate();
new ProjectMembers(); new ProjectMembers();
new UsersSelect(); new UsersSelect();
break; break;
...@@ -171,6 +176,7 @@ ...@@ -171,6 +176,7 @@
new BuildArtifacts(); new BuildArtifacts();
break; break;
case 'projects:group_links:index': case 'projects:group_links:index':
new gl.MemberExpirationDate();
new GroupsSelect(); new GroupsSelect();
break; break;
case 'search:show': case 'search:show':
......
...@@ -31,8 +31,7 @@ ...@@ -31,8 +31,7 @@
this.input this.input
.on('keydown', function (e) { .on('keydown', function (e) {
var keyCode = e.which; var keyCode = e.which;
if (keyCode === 13 && !options.elIsInput) {
if (keyCode === 13) {
e.preventDefault() e.preventDefault()
} }
}) })
...@@ -47,7 +46,7 @@ ...@@ -47,7 +46,7 @@
} else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
$inputContainer.removeClass(HAS_VALUE_CLASS); $inputContainer.removeClass(HAS_VALUE_CLASS);
} }
if (keyCode === 13) { if (keyCode === 13 && !options.elIsInput) {
return false; return false;
} }
if (this.options.remote) { if (this.options.remote) {
...@@ -111,9 +110,9 @@ ...@@ -111,9 +110,9 @@
matches = fuzzaldrinPlus.match($el.text().trim(), search_text); matches = fuzzaldrinPlus.match($el.text().trim(), search_text);
if (!$el.is('.dropdown-header')) { if (!$el.is('.dropdown-header')) {
if (matches.length) { if (matches.length) {
return $el.show(); return $el.show().removeClass('option-hidden');
} else { } else {
return $el.hide(); return $el.hide().addClass('option-hidden');
} }
} }
}); });
...@@ -179,7 +178,7 @@ ...@@ -179,7 +178,7 @@
})(); })();
GitLabDropdown = (function() { 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"; LOADING_CLASS = "is-loading";
...@@ -191,6 +190,12 @@ ...@@ -191,6 +190,12 @@
currentIndex = -1; 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'; FILTER_INPUT = '.dropdown-input .dropdown-input-field';
function GitLabDropdown(el1, options) { function GitLabDropdown(el1, options) {
...@@ -213,6 +218,7 @@ ...@@ -213,6 +218,7 @@
if (this.options.data) { if (this.options.data) {
if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) { if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) {
this.fullData = this.options.data; this.fullData = this.options.data;
currentIndex = -1;
this.parseData(this.options.data); this.parseData(this.options.data);
} else { } else {
this.remote = new GitLabDropdownRemote(this.options.data, { this.remote = new GitLabDropdownRemote(this.options.data, {
...@@ -232,6 +238,7 @@ ...@@ -232,6 +238,7 @@
} }
if (this.options.filterable) { if (this.options.filterable) {
this.filter = new GitLabDropdownFilter(this.filterInput, { this.filter = new GitLabDropdownFilter(this.filterInput, {
elIsInput: $(this.el).is('input'),
filterInputBlur: this.filterInputBlur, filterInputBlur: this.filterInputBlur,
filterByText: this.options.filterByText, filterByText: this.options.filterByText,
onFilter: this.options.onFilter, onFilter: this.options.onFilter,
...@@ -240,7 +247,7 @@ ...@@ -240,7 +247,7 @@
keys: searchFields, keys: searchFields,
elements: (function(_this) { elements: (function(_this) {
return function() { return function() {
selector = '.dropdown-content li:not(.divider)'; selector = '.dropdown-content li:not(' + NON_SELECTABLE_CLASSES + ')';
if (_this.dropdown.find('.dropdown-toggle-page').length) { if (_this.dropdown.find('.dropdown-toggle-page').length) {
selector = ".dropdown-page-one " + selector; selector = ".dropdown-page-one " + selector;
} }
...@@ -256,12 +263,16 @@ ...@@ -256,12 +263,16 @@
return function(data) { return function(data) {
_this.parseData(data); _this.parseData(data);
if (_this.filterInput.val() !== '') { if (_this.filterInput.val() !== '') {
selector = '.dropdown-content li:not(.divider):visible'; selector = SELECTABLE_CLASSES;
if (_this.dropdown.find('.dropdown-toggle-page').length) { if (_this.dropdown.find('.dropdown-toggle-page').length) {
selector = ".dropdown-page-one " + selector; selector = ".dropdown-page-one " + selector;
} }
$(selector, _this.dropdown).first().find('a').addClass('is-focused'); if ($(_this.el).is('input')) {
return currentIndex = 0; currentIndex = -1;
} else {
$(selector, _this.dropdown).first().find('a').addClass('is-focused');
currentIndex = 0;
}
} }
}; };
})(this) })(this)
...@@ -376,7 +387,7 @@ ...@@ -376,7 +387,7 @@
var $target; var $target;
if (this.options.multiSelect) { if (this.options.multiSelect) {
$target = $(e.target); $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(); e.stopPropagation();
return false; return false;
} else { } else {
...@@ -387,7 +398,7 @@ ...@@ -387,7 +398,7 @@
GitLabDropdown.prototype.opened = function() { GitLabDropdown.prototype.opened = function() {
var contentHtml; var contentHtml;
currentIndex = -1; this.resetRows();
this.addArrowKeyEvent(); this.addArrowKeyEvent();
if (this.options.setIndeterminateIds) { if (this.options.setIndeterminateIds) {
this.options.setIndeterminateIds.call(this); this.options.setIndeterminateIds.call(this);
...@@ -410,6 +421,7 @@ ...@@ -410,6 +421,7 @@
GitLabDropdown.prototype.hidden = function(e) { GitLabDropdown.prototype.hidden = function(e) {
var $input; var $input;
this.resetRows();
this.removeArrayKeyEvent(); this.removeArrayKeyEvent();
$input = this.dropdown.find(".dropdown-input-field"); $input = this.dropdown.find(".dropdown-input-field");
if (this.options.filterable) { if (this.options.filterable) {
...@@ -463,7 +475,7 @@ ...@@ -463,7 +475,7 @@
return "<li class='separator'></li>"; return "<li class='separator'></li>";
} }
if (data.header != null) { 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) { if (this.options.renderRow) {
html = this.options.renderRow.call(this.options, data, this); html = this.options.renderRow.call(this.options, data, this);
...@@ -495,11 +507,16 @@ ...@@ -495,11 +507,16 @@
text = this.highlightTextMatches(text, this.filterInput.val()); text = this.highlightTextMatches(text, this.filterInput.val());
} }
if (group) { if (group) {
groupAttrs = "data-group='" + group + "' data-index='" + index + "'"; groupAttrs = 'data-group=' + group + ' data-index=' + index;
} else { } else {
groupAttrs = ''; 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; return html;
}; };
...@@ -521,17 +538,6 @@ ...@@ -521,17 +538,6 @@
return html = "<li class='dropdown-menu-empty-link'> <a href='#' class='is-focused'> No matching results. </a> </li>"; 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) { GitLabDropdown.prototype.rowClicked = function(el) {
var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value; var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value;
isInput = $(this.el).is('input'); isInput = $(this.el).is('input');
...@@ -612,13 +618,23 @@ ...@@ -612,13 +618,23 @@
GitLabDropdown.prototype.selectRowAtIndex = function(index) { GitLabDropdown.prototype.selectRowAtIndex = function(index) {
var $el, selector; var $el, selector;
selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(" + index + ") a"; // If we pass an option index
if (typeof index !== "undefined") {
selector = SELECTABLE_CLASSES + ":eq(" + index + ") a";
} else {
selector = ".dropdown-content .is-focused";
}
if (this.dropdown.find(".dropdown-toggle-page").length) { if (this.dropdown.find(".dropdown-toggle-page").length) {
selector = ".dropdown-page-one " + selector; selector = ".dropdown-page-one " + selector;
} }
$el = $(selector, this.dropdown); $el = $(selector, this.dropdown);
if ($el.length) { if ($el.length) {
return $el.first().trigger('click'); var href = $el.attr('href');
if (href && href !== '#') {
Turbolinks.visit(href);
} else {
$el.first().trigger('click');
}
} }
}; };
...@@ -626,7 +642,7 @@ ...@@ -626,7 +642,7 @@
var $input, ARROW_KEY_CODES, selector; var $input, ARROW_KEY_CODES, selector;
ARROW_KEY_CODES = [38, 40]; ARROW_KEY_CODES = [38, 40];
$input = this.dropdown.find(".dropdown-input-field"); $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) { if (this.dropdown.find(".dropdown-toggle-page").length) {
selector = ".dropdown-page-one " + selector; selector = ".dropdown-page-one " + selector;
} }
...@@ -654,7 +670,7 @@ ...@@ -654,7 +670,7 @@
return false; return false;
} }
if (currentKeyCode === 13 && currentIndex !== -1) { if (currentKeyCode === 13 && currentIndex !== -1) {
return _this.selectRowAtIndex($('.is-focused', _this.dropdown).closest('li').index() - 1); _this.selectRowAtIndex();
} }
}; };
})(this)); })(this));
...@@ -664,6 +680,11 @@ ...@@ -664,6 +680,11 @@
return $('body').off('keydown'); return $('body').off('keydown');
}; };
GitLabDropdown.prototype.resetRows = function resetRows() {
currentIndex = -1;
$('.is-focused', this.dropdown).removeClass('is-focused');
};
GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) { GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) {
var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop; var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop;
$('.is-focused', this.dropdown).removeClass('is-focused'); $('.is-focused', this.dropdown).removeClass('is-focused');
...@@ -677,10 +698,14 @@ ...@@ -677,10 +698,14 @@
listItemHeight = $listItem.outerHeight(); listItemHeight = $listItem.outerHeight();
listItemTop = $listItem.prop('offsetTop'); listItemTop = $listItem.prop('offsetTop');
listItemBottom = listItemTop + listItemHeight; listItemBottom = listItemTop + listItemHeight;
if (listItemBottom > dropdownContentBottom + dropdownScrollTop) { if (!index) {
return $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom); $dropdownContent.scrollTop(0)
} else if (listItemTop < dropdownContentTop + dropdownScrollTop) { } else if (index === ($listItems.length - 1)) {
return $dropdownContent.scrollTop(listItemTop - dropdownContentTop); $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);
} }
}; };
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
var _this; var _this;
_this = this; _this = this;
$('.js-label-select').each(function(i, dropdown) { $('.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); $dropdown = $(dropdown);
projectId = $dropdown.data('project-id'); projectId = $dropdown.data('project-id');
labelUrl = $dropdown.data('labels'); labelUrl = $dropdown.data('labels');
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
$block = $selectbox.closest('.block'); $block = $selectbox.closest('.block');
$form = $dropdown.closest('form'); $form = $dropdown.closest('form');
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span'); $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span');
$sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
$value = $block.find('.value'); $value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut(); $loading = $block.find('.block-loading').fadeOut();
if (issueUpdateURL != null) { if (issueUpdateURL != null) {
...@@ -31,7 +32,11 @@ ...@@ -31,7 +32,11 @@
labelNoneHTMLTemplate = '<span class="no-value">None</span>'; 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() { saveLabelData = function() {
var data, selected; var data, selected;
...@@ -52,7 +57,7 @@ ...@@ -52,7 +57,7 @@
dataType: 'JSON', dataType: 'JSON',
data: data data: data
}).done(function(data) { }).done(function(data) {
var labelCount, template; var labelCount, template, labelTooltipTitle, labelTitles;
$loading.fadeOut(); $loading.fadeOut();
$dropdown.trigger('loaded.gl.dropdown'); $dropdown.trigger('loaded.gl.dropdown');
$selectbox.hide(); $selectbox.hide();
...@@ -66,6 +71,27 @@ ...@@ -66,6 +71,27 @@
} }
$value.removeAttr('style').html(template); $value.removeAttr('style').html(template);
$sidebarCollapsedValue.text(labelCount); $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({ $('.has-tooltip', $value).tooltip({
container: 'body' container: 'body'
}); });
......
/*= require ace-rails-ap */
/*= require ace/ext-searchbox */
(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);
...@@ -5,9 +5,6 @@ ...@@ -5,9 +5,6 @@
return $(this).fadeOut(); return $(this).fadeOut();
}); });
} }
return ProjectMembers; return ProjectMembers;
})(); })();
}).call(this); }).call(this);
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
if (onSelect) { if (onSelect) {
onSelect(item, $el, self); onSelect(item, $el, self);
} }
} }
}); });
} }
......
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
KEYCODE = { KEYCODE = {
ESCAPE: 27, ESCAPE: 27,
BACKSPACE: 8, BACKSPACE: 8,
ENTER: 13 ENTER: 13,
UP: 38,
DOWN: 40
}; };
function SearchAutocomplete(opts) { function SearchAutocomplete(opts) {
...@@ -223,6 +225,12 @@ ...@@ -223,6 +225,12 @@
case KEYCODE.ESCAPE: case KEYCODE.ESCAPE:
this.restoreOriginalState(); this.restoreOriginalState();
break; break;
case KEYCODE.ENTER:
this.disableAutocomplete();
break;
case KEYCODE.UP:
case KEYCODE.DOWN:
return;
default: default:
if (this.searchInput.val() === '') { if (this.searchInput.val() === '') {
this.disableAutocomplete(); this.disableAutocomplete();
...@@ -319,9 +327,11 @@ ...@@ -319,9 +327,11 @@
}; };
SearchAutocomplete.prototype.disableAutocomplete = function() { SearchAutocomplete.prototype.disableAutocomplete = function() {
this.searchInput.addClass('disabled'); if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) {
this.dropdown.removeClass('open'); this.searchInput.addClass('disabled');
return this.restoreMenu(); this.dropdown.removeClass('open').trigger('hidden.bs.dropdown');
this.restoreMenu();
}
}; };
SearchAutocomplete.prototype.restoreMenu = function() { SearchAutocomplete.prototype.restoreMenu = function() {
......
/*= require_tree . */
(function() {
$(function() {
var editor = ace.edit("editor")
$(".snippet-form-holder form").on('submit', function() {
$(".snippet-file-content").val(editor.getValue());
});
});
}).call(this);
...@@ -14,12 +14,20 @@ ...@@ -14,12 +14,20 @@
margin-top: 0; margin-top: 0;
} }
// Single code lines should wrap
code { code {
font-family: $monospace_font; font-family: $monospace_font;
white-space: pre; white-space: pre-wrap;
word-wrap: normal; word-wrap: normal;
} }
// Multi-line code blocks should scroll horizontally
pre {
code {
white-space: pre;
}
}
kbd { kbd {
display: inline-block; display: inline-block;
padding: 3px 5px; padding: 3px 5px;
......
...@@ -8,7 +8,11 @@ ...@@ -8,7 +8,11 @@
} }
.is-dragging { .is-dragging {
// Important because plugin sets inline CSS
opacity: 1!important;
* { * {
// !important to make sure no style can override this when dragging
cursor: -webkit-grabbing; cursor: -webkit-grabbing;
cursor: grabbing; cursor: grabbing;
} }
...@@ -101,8 +105,8 @@ ...@@ -101,8 +105,8 @@
.board { .board {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
min-width: calc(100vw - 15px); min-width: calc(85vw - 15px);
max-width: calc(100vw - 15px); max-width: calc(85vw - 15px);
margin-bottom: 25px; margin-bottom: 25px;
padding-right: ($gl-padding / 2); padding-right: ($gl-padding / 2);
padding-left: ($gl-padding / 2); padding-left: ($gl-padding / 2);
...@@ -154,14 +158,6 @@ ...@@ -154,14 +158,6 @@
padding: $gl-padding; padding: $gl-padding;
font-size: 1em; font-size: 1em;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
.board-mobile-handle {
position: relative;
left: 0;
top: 1px;
margin-top: 0;
margin-right: 5px;
}
} }
.board-search-container { .board-search-container {
...@@ -254,11 +250,6 @@ ...@@ -254,11 +250,6 @@
opacity: 0.3; opacity: 0.3;
} }
.is-dragging {
// Important because plugin sets inline CSS
opacity: 1!important;
}
.card { .card {
position: relative; position: relative;
width: 100%; width: 100%;
...@@ -269,11 +260,7 @@ ...@@ -269,11 +260,7 @@
list-style: none; list-style: none;
&.user-can-drag { &.user-can-drag {
padding-left: ($gl-padding * 2); padding-left: $gl-padding;
@media (min-width: $screen-sm-min) {
padding-left: $gl-padding;
}
} }
&:not(:last-child) { &:not(:last-child) {
...@@ -294,17 +281,6 @@ ...@@ -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 { .card-title {
margin: 0; margin: 0;
font-size: 1em; font-size: 1em;
...@@ -316,6 +292,7 @@ ...@@ -316,6 +292,7 @@
.card-footer { .card-footer {
margin-top: 5px; margin-top: 5px;
line-height: 25px;
.label { .label {
margin-right: 4px; margin-right: 4px;
......
...@@ -168,7 +168,6 @@ ...@@ -168,7 +168,6 @@
text-overflow: ellipsis; text-overflow: ellipsis;
&:hover { &:hover {
background-color: $row-hover;
color: $gl-text-color; color: $gl-text-color;
} }
} }
...@@ -190,6 +189,10 @@ ...@@ -190,6 +189,10 @@
display: block; display: block;
} }
} }
&:hover {
background-color: $row-hover;
}
} }
} }
} }
......
...@@ -66,6 +66,15 @@ ...@@ -66,6 +66,15 @@
margin-left: 8px; margin-left: 8px;
} }
} }
.ci-status-link {
svg {
position: relative;
top: 2px;
margin: 0 2px 0 3px;
}
}
} }
.ci-status-link { .ci-status-link {
......
...@@ -34,11 +34,4 @@ ...@@ -34,11 +34,4 @@
} }
} }
} }
.wiki {
code {
white-space: pre-wrap;
word-break: keep-all;
}
}
} }
...@@ -374,3 +374,10 @@ ...@@ -374,3 +374,10 @@
} }
} }
} }
.merge-request-details {
.title {
margin-bottom: 20px;
}
}
...@@ -300,6 +300,17 @@ ...@@ -300,6 +300,17 @@
&.playable { &.playable {
background-color: $gray-light; background-color: $gray-light;
svg {
height: 12px;
width: 12px;
position: relative;
top: 1px;
path {
fill: $layout-link-gray;
}
}
} }
.build-content { .build-content {
...@@ -319,10 +330,6 @@ ...@@ -319,10 +330,6 @@
margin-right: 5px; margin-right: 5px;
} }
.fa {
font-size: 13px;
}
// Connect first build in each stage with right horizontal line // Connect first build in each stage with right horizontal line
&:first-child { &:first-child {
&::after { &::after {
......
...@@ -748,3 +748,29 @@ a.allowed-to-merge, a.allowed-to-push { ...@@ -748,3 +748,29 @@ a.allowed-to-merge, a.allowed-to-push {
width: 300px; 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;
}
}
}
...@@ -113,6 +113,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -113,6 +113,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:sentry_dsn, :sentry_dsn,
:akismet_enabled, :akismet_enabled,
:akismet_api_key, :akismet_api_key,
:koding_enabled,
:koding_url,
:email_author_in_body, :email_author_in_body,
:repository_checks_enabled, :repository_checks_enabled,
:metrics_packet_size, :metrics_packet_size,
......
...@@ -42,7 +42,7 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -42,7 +42,7 @@ class Admin::GroupsController < Admin::ApplicationController
end end
def members_update 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.' redirect_to [:admin, @group], notice: 'Users were successfully added.'
end end
......
...@@ -7,7 +7,7 @@ class Admin::ImpersonationsController < Admin::ApplicationController ...@@ -7,7 +7,7 @@ class Admin::ImpersonationsController < Admin::ApplicationController
warden.set_user(impersonator, scope: :user) warden.set_user(impersonator, scope: :user)
Gitlab::AppLogger.info("User #{original_user.username} has stopped impersonating #{impersonator.username}") Gitlab::AppLogger.info("User #{impersonator.username} has stopped impersonating #{original_user.username}")
session[:impersonator_id] = nil session[:impersonator_id] = nil
......
...@@ -2,6 +2,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -2,6 +2,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
before_action :find_todos, only: [:index, :destroy_all] before_action :find_todos, only: [:index, :destroy_all]
def index def index
@sort = params[:sort]
@todos = @todos.page(params[:page]) @todos = @todos.page(params[:page])
end end
......
...@@ -21,10 +21,15 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -21,10 +21,15 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
def create def create
access_level = params[:access_level]
user_ids = params[:user_ids].split(',') user_ids = params[:user_ids].split(',')
@group.add_users(user_ids, access_level, current_user) @group.add_users(
user_ids,
params[:access_level],
current_user: current_user,
expires_at: params[:expires_at]
)
group_members = @group.group_members.where(user_id: user_ids) group_members = @group.group_members.where(user_id: user_ids)
group_members.each do |group_member| group_members.each do |group_member|
...@@ -76,7 +81,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -76,7 +81,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
protected protected
def member_params def member_params
params.require(:group_member).permit(:access_level, :user_id) params.require(:group_member).permit(:access_level, :user_id, :expires_at)
end end
# MembershipActions concern # MembershipActions concern
......
class KodingController < ApplicationController
before_action :check_integration!, :authenticate_user!, :reject_blocked!
layout 'koding'
def index
path = File.join(Rails.root, 'doc/user/project/koding.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 ...@@ -12,7 +12,7 @@ module Projects
only: [:iid, :title, :confidential], only: [:iid, :title, :confidential],
include: { include: {
assignee: { only: [:id, :name, :username], methods: [:avatar_url] }, 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 end
......
...@@ -11,7 +11,9 @@ class Projects::GroupLinksController < Projects::ApplicationController ...@@ -11,7 +11,9 @@ class Projects::GroupLinksController < Projects::ApplicationController
return render_404 unless can?(current_user, :read_group, group) return render_404 unless can?(current_user, :read_group, group)
project.project_group_links.create( project.project_group_links.create(
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) redirect_to namespace_project_group_links_path(project.namespace, project)
......
...@@ -10,7 +10,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -10,7 +10,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled before_action :module_enabled
before_action :merge_request, only: [ before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check, :edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check,
:ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :approve, :rebase :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts,
:approve, :rebase
] ]
before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines]
before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines] before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines]
......
...@@ -36,7 +36,13 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -36,7 +36,13 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end end
def create 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
)
members = @project.project_members.where(user_id: params[:user_ids].split(',')) members = @project.project_members.where(user_id: params[:user_ids].split(','))
members.each do |member| members.each do |member|
...@@ -105,7 +111,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -105,7 +111,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
protected protected
def member_params def member_params
params.require(:project_member).permit(:user_id, :access_level) params.require(:project_member).permit(:user_id, :access_level, :expires_at)
end end
# MembershipActions concern # MembershipActions concern
......
...@@ -5,7 +5,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -5,7 +5,7 @@ class ProjectsController < Projects::ApplicationController
before_action :project, except: [:new, :create] before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create] before_action :repository, except: [:new, :create]
before_action :assign_ref_vars, only: [:show], if: :repo_exists? before_action :assign_ref_vars, only: [:show], if: :repo_exists?
before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?] before_action :assign_tree_vars, only: [:show], if: [:repo_exists?, :project_view_files?]
# Authorize # Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export]
...@@ -344,4 +344,11 @@ class ProjectsController < Projects::ApplicationController ...@@ -344,4 +344,11 @@ class ProjectsController < Projects::ApplicationController
def get_id def get_id
project.repository.root_ref project.repository.root_ref
end end
# ExtractsPath will set @id = project.path on the show route, but it has to be the
# branch name for the tree view to work correctly.
def assign_tree_vars
@id = get_id
tree
end
end end
...@@ -33,7 +33,7 @@ class TodosFinder ...@@ -33,7 +33,7 @@ class TodosFinder
# the project IDs yielded by the todos query thus far # the project IDs yielded by the todos query thus far
items = by_project(items) items = by_project(items)
items.reorder(id: :desc) sort(items)
end end
private private
...@@ -106,6 +106,10 @@ class TodosFinder ...@@ -106,6 +106,10 @@ class TodosFinder
params[:type] params[:type]
end end
def sort(items)
params[:sort] ? items.sort(params[:sort]) : items.reorder(id: :desc)
end
def by_action(items) def by_action(items)
if action? if action?
items = items.where(action: to_action_id) items = items.where(action: to_action_id)
......
...@@ -35,6 +35,10 @@ module ApplicationSettingsHelper ...@@ -35,6 +35,10 @@ module ApplicationSettingsHelper
current_application_settings.akismet_enabled? current_application_settings.akismet_enabled?
end end
def koding_enabled?
current_application_settings.koding_enabled?
end
def allowed_protocols_present? def allowed_protocols_present?
current_application_settings.enabled_git_access_protocol.present? current_application_settings.enabled_git_access_protocol.present?
end end
......
...@@ -217,4 +217,12 @@ module BlobHelper ...@@ -217,4 +217,12 @@ module BlobHelper
def gitlab_ci_ymls def gitlab_ci_ymls
@gitlab_ci_ymls ||= Gitlab::Template::GitlabCiYmlTemplate.dropdown_names @gitlab_ci_ymls ||= Gitlab::Template::GitlabCiYmlTemplate.dropdown_names
end 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 end
...@@ -39,7 +39,7 @@ module CiStatusHelper ...@@ -39,7 +39,7 @@ module CiStatusHelper
when 'running' when 'running'
'icon_status_running' 'icon_status_running'
when 'play' when 'play'
return icon('play fw') 'icon_play'
when 'created' when 'created'
'icon_status_pending' 'icon_status_pending'
else else
......
...@@ -72,6 +72,15 @@ module IssuablesHelper ...@@ -72,6 +72,15 @@ module IssuablesHelper
end end
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 private
def sidebar_gutter_collapsed? def sidebar_gutter_collapsed?
......
...@@ -245,6 +245,60 @@ module ProjectsHelper ...@@ -245,6 +245,60 @@ module ProjectsHelper
) )
end 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) def contribution_guide_path(project)
if project && contribution_guide = project.repository.contribution_guide if project && contribution_guide = project.repository.contribution_guide
namespace_project_blob_path( namespace_project_blob_path(
......
...@@ -15,20 +15,9 @@ module TimeHelper ...@@ -15,20 +15,9 @@ module TimeHelper
"#{from.to_s(:short)} - #{to.to_s(:short)}" "#{from.to_s(:short)} - #{to.to_s(:short)}"
end end
def duration_in_numbers(finished_at, started_at) def duration_in_numbers(duration)
interval = interval_in_seconds(started_at, finished_at) time_format = duration < 1.hour ? "%M:%S" : "%H:%M:%S"
time_format = interval < 1.hour ? "%M:%S" : "%H:%M:%S"
Time.at(interval).utc.strftime(time_format) Time.at(duration).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
end end
end end
...@@ -179,40 +179,46 @@ class Ability ...@@ -179,40 +179,46 @@ class Ability
end end
def project_abilities(user, project) def project_abilities(user, project)
rules = []
key = "/user/#{user.id}/project/#{project.id}" key = "/user/#{user.id}/project/#{project.id}"
RequestStore.store[key] ||= begin if RequestStore.active?
# Push abilities on the users team role RequestStore.store[key] ||= uncached_project_abilities(user, project)
rules.push(*project_team_rules(project.team, user)) else
uncached_project_abilities(user, project)
end
end
rules << :change_repository_storage if user.admin? 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? || rules << :change_repository_storage if user.admin?
project.owner == user ||
(project.group && project.group.has_owner?(user))
if owner owner = user.admin? ||
rules.push(*project_owner_rules) project.owner == user ||
end (project.group && project.group.has_owner?(user))
if project.public? || (project.internal? && !user.external?) if owner
rules.push(*public_project_rules) rules.push(*project_owner_rules)
end
# Allow to read builds for internal projects if project.public? || (project.internal? && !user.external?)
rules << :read_build if project.public_builds? rules.push(*public_project_rules)
unless owner || project.team.member?(user) || project_group_member?(project, user) # Allow to read builds for internal projects
rules << :request_access if project.request_access_enabled rules << :read_build if project.public_builds?
end
end
if project.archived? unless owner || project.team.member?(user) || project_group_member?(project, user)
rules -= project_archived_rules rules << :request_access if project.request_access_enabled
end end
end
rules - project_disabled_features_rules(project) if project.archived?
rules -= project_archived_rules
end end
(rules - project_disabled_features_rules(project)).uniq
end end
def project_team_rules(team, user) def project_team_rules(team, user)
......
...@@ -55,6 +55,10 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -55,6 +55,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true, presence: true,
if: :akismet_enabled if: :akismet_enabled
validates :koding_url,
presence: true,
if: :koding_enabled
validates :max_attachment_size, validates :max_attachment_size,
presence: true, presence: true,
numericality: { only_integer: true, greater_than: 0 } numericality: { only_integer: true, greater_than: 0 }
...@@ -157,6 +161,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -157,6 +161,8 @@ class ApplicationSetting < ActiveRecord::Base
two_factor_grace_period: 48, two_factor_grace_period: 48,
recaptcha_enabled: false, recaptcha_enabled: false,
akismet_enabled: false, akismet_enabled: false,
koding_enabled: false,
koding_url: nil,
repository_checks_enabled: true, repository_checks_enabled: true,
disabled_oauth_sign_in_sources: [], disabled_oauth_sign_in_sources: [],
send_user_confirmation_email: false, send_user_confirmation_email: false,
......
...@@ -62,6 +62,7 @@ module Ci ...@@ -62,6 +62,7 @@ module Ci
status_event: 'enqueue' status_event: 'enqueue'
) )
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build) MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
build.pipeline.mark_as_processable_after_stage(build.stage_idx)
new_build new_build
end end
end end
......
...@@ -78,6 +78,10 @@ module Ci ...@@ -78,6 +78,10 @@ module Ci
CommitStatus.where(pipeline: pluck(:id)).stages CommitStatus.where(pipeline: pluck(:id)).stages
end end
def self.total_duration
where.not(duration: nil).sum(:duration)
end
def stages_with_latest_statuses def stages_with_latest_statuses
statuses.latest.order(:stage_idx).group_by(&:stage) statuses.latest.order(:stage_idx).group_by(&:stage)
end end
...@@ -146,6 +150,10 @@ module Ci ...@@ -146,6 +150,10 @@ module Ci
end end
end end
def mark_as_processable_after_stage(stage_idx)
builds.skipped.where('stage_idx > ?', stage_idx).find_each(&:process)
end
def latest? def latest?
return false unless ref return false unless ref
commit = project.commit(ref) commit = project.commit(ref)
...@@ -250,7 +258,13 @@ module Ci ...@@ -250,7 +258,13 @@ module Ci
end end
def update_duration def update_duration
self.duration = statuses.latest.duration self.duration = calculate_duration
end
def execute_hooks
data = pipeline_data
project.execute_hooks(data, :pipeline_hooks)
project.execute_services(data, :pipeline_hooks)
end end
def execute_hooks def execute_hooks
......
...@@ -229,7 +229,7 @@ class Commit ...@@ -229,7 +229,7 @@ class Commit
def diff_refs def diff_refs
Gitlab::Diff::DiffRefs.new( Gitlab::Diff::DiffRefs.new(
base_sha: self.parent_id || self.sha, base_sha: self.parent_id || Gitlab::Git::BLANK_SHA,
head_sha: self.sha head_sha: self.sha
) )
end end
......
...@@ -21,6 +21,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -21,6 +21,7 @@ class CommitStatus < ActiveRecord::Base
where(id: max_id.group(:name, :commit_id)) where(id: max_id.group(:name, :commit_id))
end end
scope :retried, -> { where.not(id: latest) } scope :retried, -> { where.not(id: latest) }
scope :ordered, -> { order(:name) } scope :ordered, -> { order(:name) }
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
...@@ -30,6 +31,10 @@ class CommitStatus < ActiveRecord::Base ...@@ -30,6 +31,10 @@ class CommitStatus < ActiveRecord::Base
transition [:created, :skipped] => :pending transition [:created, :skipped] => :pending
end end
event :process do
transition skipped: :created
end
event :run do event :run do
transition pending: :running transition pending: :running
end end
...@@ -107,13 +112,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -107,13 +112,7 @@ class CommitStatus < ActiveRecord::Base
end end
def duration def duration
duration = calculate_duration
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
duration
end end
def stuck? def stuck?
......
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
...@@ -134,7 +134,10 @@ module Issuable ...@@ -134,7 +134,10 @@ module Issuable
end end
def order_labels_priority(excluded_labels: []) 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]). group(arel_table[:id]).
reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')) reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC'))
end end
...@@ -162,20 +165,6 @@ module Issuable ...@@ -162,20 +165,6 @@ module Issuable
grouping_columns grouping_columns
end 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 end
def today? def today?
......
...@@ -17,6 +17,10 @@ module NoteOnDiff ...@@ -17,6 +17,10 @@ module NoteOnDiff
raise NotImplementedError raise NotImplementedError
end end
def original_line_code
raise NotImplementedError
end
def diff_attributes def diff_attributes
raise NotImplementedError raise NotImplementedError
end end
......
...@@ -35,5 +35,19 @@ module Sortable ...@@ -35,5 +35,19 @@ module Sortable
all all
end end
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
end end
...@@ -23,7 +23,7 @@ module Spammable ...@@ -23,7 +23,7 @@ module Spammable
def submittable_as_spam? def submittable_as_spam?
if user_agent_detail if user_agent_detail
user_agent_detail.submittable? user_agent_detail.submittable? && current_application_settings.akismet_enabled
else else
false false
end end
......
...@@ -35,11 +35,6 @@ module Statuseable ...@@ -35,11 +35,6 @@ module Statuseable
all.pluck(self.status_sql).first all.pluck(self.status_sql).first
end end
def duration
duration_array = all.map(&:duration).compact
duration_array.reduce(:+)
end
def started_at def started_at
all.minimum(:started_at) all.minimum(:started_at)
end end
...@@ -85,4 +80,14 @@ module Statuseable ...@@ -85,4 +80,14 @@ module Statuseable
def complete? def complete?
COMPLETED_STATUSES.include?(status) COMPLETED_STATUSES.include?(status)
end end
private
def calculate_duration
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
end
end end
...@@ -16,6 +16,9 @@ class DiffNote < Note ...@@ -16,6 +16,9 @@ class DiffNote < Note
after_initialize :ensure_original_discussion_id after_initialize :ensure_original_discussion_id
before_validation :set_original_position, :update_position, on: :create before_validation :set_original_position, :update_position, on: :create
before_validation :set_line_code, :set_original_discussion_id 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 after_save :keep_around_commits
class << self class << self
...@@ -57,6 +60,10 @@ class DiffNote < Note ...@@ -57,6 +60,10 @@ class DiffNote < Note
diff_file.position(line) == self.original_position diff_file.position(line) == self.original_position
end end
def original_line_code
self.diff_file.line_code(self.diff_line)
end
def active?(diff_refs = nil) def active?(diff_refs = nil)
return false unless supported? return false unless supported?
return true if for_commit? return true if for_commit?
......
...@@ -12,6 +12,7 @@ class Discussion ...@@ -12,6 +12,7 @@ class Discussion
:for_merge_request?, :for_merge_request?,
:line_code, :line_code,
:original_line_code,
:diff_file, :diff_file,
:for_line?, :for_line?,
:active?, :active?,
......
...@@ -107,34 +107,41 @@ class Group < Namespace ...@@ -107,34 +107,41 @@ class Group < Namespace
end end
end end
def add_users(user_ids, access_level, current_user = nil, skip_notification: false) def add_users(user_ids, access_level, current_user: nil, skip_notification: false, expires_at: nil)
user_ids.each do |user_id| user_ids.each do |user_id|
Member.add_user(self.group_members, user_id, access_level, current_user, skip_notification: skip_notification) Member.add_user(
self.group_members,
user_id,
access_level,
current_user: current_user,
skip_notification: skip_notification,
expires_at: expires_at
)
end end
end end
def add_user(user, access_level, current_user = nil, skip_notification: false) def add_user(user, access_level, current_user: nil, skip_notification: false, expires_at: nil)
add_users([user], access_level, current_user, skip_notification: skip_notification) add_users([user], access_level, current_user: current_user, skip_notification: skip_notification, expires_at: expires_at)
end end
def add_owner(user, current_user = nil, skip_notification: false) def add_owner(user, current_user = nil, skip_notification: false)
self.add_user(user, Gitlab::Access::OWNER, current_user, skip_notification: skip_notification) add_user(user, Gitlab::Access::OWNER, current_user: current_user, skip_notification: skip_notification)
end end
def add_guest(user, current_user = nil) 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 end
def add_reporter(user, current_user = nil) 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 end
def add_developer(user, current_user = nil) 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 end
def add_master(user, current_user = nil) 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 end
def has_owner?(user) def has_owner?(user)
......
...@@ -49,6 +49,10 @@ class LegacyDiffNote < Note ...@@ -49,6 +49,10 @@ class LegacyDiffNote < Note
!line.meta? && diff_file.line_code(line) == self.line_code !line.meta? && diff_file.line_code(line) == self.line_code
end end
def original_line_code
self.line_code
end
# Check if this note is part of an "active" discussion # Check if this note is part of an "active" discussion
# #
# This will always return true for anything except MergeRequest noteables, # This will always return true for anything except MergeRequest noteables,
......
class Member < ActiveRecord::Base class Member < ActiveRecord::Base
include Sortable include Sortable
include Importable include Importable
include Expirable
include Gitlab::Access include Gitlab::Access
attr_accessor :raw_invite_token attr_accessor :raw_invite_token
...@@ -74,7 +75,7 @@ class Member < ActiveRecord::Base ...@@ -74,7 +75,7 @@ class Member < ActiveRecord::Base
user user
end end
def add_user(members, user_id, access_level, current_user = nil, skip_notification: false) def add_user(members, user_id, access_level, current_user: nil, skip_notification: false, expires_at: nil)
user = user_for_id(user_id) user = user_for_id(user_id)
# `user` can be either a User object or an email to be invited # `user` can be either a User object or an email to be invited
...@@ -88,7 +89,7 @@ class Member < ActiveRecord::Base ...@@ -88,7 +89,7 @@ class Member < ActiveRecord::Base
if can_update_member?(current_user, member) || project_creator?(member, access_level) if can_update_member?(current_user, member) || project_creator?(member, access_level)
member.created_by ||= current_user member.created_by ||= current_user
member.access_level = access_level member.access_level = access_level
member.expires_at = expires_at
member.skip_notification = skip_notification member.skip_notification = skip_notification
member.save member.save
......
...@@ -35,7 +35,7 @@ class ProjectMember < Member ...@@ -35,7 +35,7 @@ class ProjectMember < Member
# :master # :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) access_level = if roles_hash.has_key?(access)
roles_hash[access] roles_hash[access]
elsif roles_hash.values.include?(access.to_i) elsif roles_hash.values.include?(access.to_i)
...@@ -51,7 +51,13 @@ class ProjectMember < Member ...@@ -51,7 +51,13 @@ class ProjectMember < Member
project = Project.find(project_id) project = Project.find(project_id)
users.each do |user| 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 end
end end
......
...@@ -265,6 +265,8 @@ class Note < ActiveRecord::Base ...@@ -265,6 +265,8 @@ class Note < ActiveRecord::Base
def ensure_discussion_id def ensure_discussion_id
return unless self.persisted? 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 return if self.discussion_id
set_discussion_id set_discussion_id
......
...@@ -59,6 +59,7 @@ class Project < ActiveRecord::Base ...@@ -59,6 +59,7 @@ class Project < ActiveRecord::Base
has_one :push_rule, dependent: :destroy has_one :push_rule, dependent: :destroy
has_one :board, dependent: :destroy has_one :board, dependent: :destroy
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
# Project services # Project services
...@@ -702,7 +703,10 @@ class Project < ActiveRecord::Base ...@@ -702,7 +703,10 @@ class Project < ActiveRecord::Base
end end
def new_issue_address(author) 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( Gitlab::IncomingEmail.reply_address(
"#{path_with_namespace}+#{author.authentication_token}") "#{path_with_namespace}+#{author.authentication_token}")
end end
...@@ -1109,8 +1113,8 @@ class Project < ActiveRecord::Base ...@@ -1109,8 +1113,8 @@ class Project < ActiveRecord::Base
project_members.find_by(user_id: user) project_members.find_by(user_id: user)
end end
def add_user(user, access_level, current_user = nil) def add_user(user, access_level, current_user: nil, expires_at: nil)
team.add_user(user, access_level, current_user) team.add_user(user, access_level, current_user: current_user, expires_at: expires_at)
end end
def default_branch def default_branch
......
class ProjectGroupLink < ActiveRecord::Base class ProjectGroupLink < ActiveRecord::Base
include Expirable
GUEST = 10 GUEST = 10
REPORTER = 20 REPORTER = 20
DEVELOPER = 30 DEVELOPER = 30
...@@ -26,7 +28,7 @@ class ProjectGroupLink < ActiveRecord::Base ...@@ -26,7 +28,7 @@ class ProjectGroupLink < ActiveRecord::Base
self.class.access_options.key(self.group_access) self.class.access_options.key(self.group_access)
end end
private private
def different_group def different_group
if self.group && self.project && self.project.group == self.group if self.group && self.project && self.project.group == self.group
......
...@@ -15,9 +15,9 @@ class ProjectTeam ...@@ -15,9 +15,9 @@ class ProjectTeam
users, access, current_user = *args users, access, current_user = *args
if users.respond_to?(:each) if users.respond_to?(:each)
add_users(users, access, current_user) add_users(users, access, current_user: current_user)
else else
add_user(users, access, current_user) add_user(users, access, current_user: current_user)
end end
end end
...@@ -33,19 +33,20 @@ class ProjectTeam ...@@ -33,19 +33,20 @@ class ProjectTeam
member member
end end
def add_users(users, access, current_user = nil) def add_users(users, access, current_user: nil, expires_at: nil)
return false if group_member_lock return false if group_member_lock
ProjectMember.add_users_to_projects( ProjectMember.add_users_to_projects(
[project.id], [project.id],
users, users,
access, access,
current_user current_user: current_user,
expires_at: expires_at
) )
end end
def add_user(user, access, current_user = nil) def add_user(user, access, current_user: nil, expires_at: nil)
add_users([user], access, current_user) add_users([user], access, current_user: current_user, expires_at: expires_at)
end end
# Remove all users from project team # Remove all users from project team
......
...@@ -342,7 +342,7 @@ class Repository ...@@ -342,7 +342,7 @@ class Repository
def cache_keys def cache_keys
%i(size commit_count %i(size commit_count
readme version contribution_guide changelog readme version contribution_guide changelog
license_blob license_key gitignore) license_blob license_key gitignore koding_yml)
end end
# Keys for data on branch/tag operations. # Keys for data on branch/tag operations.
...@@ -618,6 +618,14 @@ class Repository ...@@ -618,6 +618,14 @@ class Repository
end end
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 def gitlab_ci_yml
return nil unless head_exists? return nil unless head_exists?
......
class Todo < ActiveRecord::Base class Todo < ActiveRecord::Base
include Sortable
ASSIGNED = 1 ASSIGNED = 1
MENTIONED = 2 MENTIONED = 2
BUILD_FAILED = 3 BUILD_FAILED = 3
...@@ -41,6 +43,23 @@ class Todo < ActiveRecord::Base ...@@ -41,6 +43,23 @@ class Todo < ActiveRecord::Base
after_save :keep_around_commit 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? def build_failed?
action == BUILD_FAILED action == BUILD_FAILED
end end
......
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 ...@@ -11,12 +11,7 @@ module Members
unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member) unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member)
raise Gitlab::Access::AccessDeniedError raise Gitlab::Access::AccessDeniedError
end end
AuthorizedDestroyService.new(member, current_user).execute
member.destroy
if member.request? && member.user != current_user
notification_service.decline_access_request(member)
end
end end
end end
end end
...@@ -255,7 +255,6 @@ class NotificationService ...@@ -255,7 +255,6 @@ class NotificationService
project_member.real_source_type, project_member.real_source_type,
project_member.project.id, project_member.project.id,
project_member.invite_email, project_member.invite_email,
project_member.access_level,
project_member.created_by_id project_member.created_by_id
).deliver_later ).deliver_later
end end
...@@ -282,7 +281,6 @@ class NotificationService ...@@ -282,7 +281,6 @@ class NotificationService
group_member.real_source_type, group_member.real_source_type,
group_member.group.id, group_member.group.id,
group_member.invite_email, group_member.invite_email,
group_member.access_level,
group_member.created_by_id group_member.created_by_id
).deliver_later ).deliver_later
end end
......
...@@ -439,6 +439,25 @@ ...@@ -439,6 +439,25 @@
.col-sm-10 .col-sm-10
= f.text_field :elasticsearch_port, class: 'form-control', placeholder: ApplicationSetting.current.elasticsearch_port = f.text_field :elasticsearch_port, class: 'form-control', placeholder: ApplicationSetting.current.elasticsearch_port
%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 administration documentation", help_page_path("administration/integration/koding")
.form-actions .form-actions
= f.submit 'Save', class: 'btn btn-save' = f.submit 'Save', class: 'btn btn-save'
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
- if build.duration - if build.duration
%p.duration %p.duration
= custom_icon("icon_timer") = custom_icon("icon_timer")
= duration_in_numbers(build.finished_at, build.started_at) = duration_in_numbers(build.duration)
- if build.finished_at - if build.finished_at
%p.finished-at %p.finished-at
......
...@@ -43,6 +43,25 @@ ...@@ -43,6 +43,25 @@
class: 'select2 trigger-submit', include_blank: true, class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Action'}) 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 .prepend-top-default
- if @todos.any? - if @todos.any?
.js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} } .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.diff-content.code.js-syntax-highlight .diff-content.code.js-syntax-highlight
%table %table
- discussions = { discussion.line_code => discussion } - discussions = { discussion.original_line_code => discussion }
= render partial: "projects/diffs/line", = render partial: "projects/diffs/line",
collection: discussion.truncated_diff_lines, collection: discussion.truncated_diff_lines,
as: :line, as: :line,
......
...@@ -14,5 +14,14 @@ ...@@ -14,5 +14,14 @@
Read more about role permissions Read more about role permissions
%strong= link_to "here", help_page_path("user/permissions"), class: "vlink" %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 .form-actions
= f.submit 'Add users to group', class: "btn btn-create" = f.submit 'Add users to group', class: "btn btn-create"
:plain :plain
$("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}'); $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}');
new MemberExpirationDate();
.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}
- page_title "Koding"
- page_description "Koding Dashboard"
- header_title "Koding", koding_path
= render template: "layouts/application"
...@@ -12,6 +12,11 @@ ...@@ -12,6 +12,11 @@
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
%span %span
Activity 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 = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do = link_to dashboard_groups_path, title: 'Groups' do
%span %span
......
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
Graphs Graphs
- if project_nav_tab? :issues - 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 = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do
%span %span
Issues Issues
......
- page_title "Edit", @blob.path, @ref - 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 - if @conflict
.alert.alert-danger .alert.alert-danger
...@@ -16,14 +19,10 @@ ...@@ -16,14 +19,10 @@
= link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
= editing_preview_title(@blob.name) = 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 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
= hidden_field_tag 'last_commit_sha', @last_commit_sha = hidden_field_tag 'last_commit_sha', @last_commit_sha
= hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] = 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) = 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'))
- page_title "New File", @path.presence, @ref - 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 %h3.page-title
New File New File
.file-editor .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 'projects/blob/editor', ref: @ref
= render 'shared/new_commit_form', placeholder: "Add new file" = render 'shared/new_commit_form', placeholder: "Add new file"
= hidden_field_tag 'content', '', id: 'file-content' = hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref, = render 'projects/commit_button', ref: @ref,
cancel_path: namespace_project_tree_path(@project.namespace, @project, @id) 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 @@ ...@@ -11,7 +11,6 @@
.board-inner .board-inner
%header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" } %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) }" } %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 }} {{ list.title }}
%span.pull-right{ "v-if" => "list.type !== 'blank'" } %span.pull-right{ "v-if" => "list.type !== 'blank'" }
{{ list.issues.length }} {{ list.issues.length }}
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
"track-by" => "id" } "track-by" => "id" }
%li.card{ ":class" => "{ 'user-can-drag': !disabled }", %li.card{ ":class" => "{ 'user-can-drag': !disabled }",
":index" => "index" } ":index" => "index" }
= icon("align-justify", class: "board-mobile-handle js-card-drag-handle", "v-if" => "!disabled")
%h4.card-title %h4.card-title
= icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential") = icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential")
%a{ ":href" => "issueLinkBase + '/' + issue.id", %a{ ":href" => "issueLinkBase + '/' + issue.id",
......
- 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 @@ ...@@ -63,7 +63,7 @@
- if build.duration - if build.duration
%p.duration %p.duration
= custom_icon("icon_timer") = custom_icon("icon_timer")
= duration_in_numbers(build.finished_at, build.started_at) = duration_in_numbers(build.duration)
- if build.finished_at - if build.finished_at
%p.finished-at %p.finished-at
= icon("calendar") = icon("calendar")
......
- status = pipeline.status - status = pipeline.status
%tr.commit %tr.commit
%td.commit-link %td.commit-link
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do
- if defined?(status_icon_only) && status_icon_only - if defined?(status_icon_only) && status_icon_only
= ci_icon_for_status(status) = ci_icon_for_status(status)
- else - else
= ci_status_with_icon(status) = ci_status_with_icon(status)
%td %td
.branch-commit .branch-commit
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do
%span ##{pipeline.id} %span ##{pipeline.id}
- if pipeline.ref - if pipeline.ref
- unless defined?(hide_branch) && hide_branch - unless defined?(hide_branch) && hide_branch
.icon-container .icon-container
= pipeline.tag? ? icon('tag') : icon('code-fork') = pipeline.tag? ? icon('tag') : icon('code-fork')
= link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace branch-name" = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name"
.icon-container .icon-container
= custom_icon("icon_commit") = custom_icon("icon_commit")
= link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: "commit-id monospace" = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace"
- if pipeline.latest? - if pipeline.latest?
%span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
- if pipeline.triggered? - if pipeline.triggered?
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
%p.commit-title %p.commit-title
- if commit = pipeline.commit - if commit = pipeline.commit
= author_avatar(commit, size: 20) = author_avatar(commit, size: 20)
= link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "commit-row-message" = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(pipeline.project.namespace, pipeline.project, commit.id), class: "commit-row-message"
- else - else
Cant find HEAD commit for this branch Cant find HEAD commit for this branch
...@@ -41,17 +41,17 @@ ...@@ -41,17 +41,17 @@
- status = stages_status[stage] - status = stages_status[stage]
- tooltip = "#{stage.titleize}: #{status || 'not found'}" - tooltip = "#{stage.titleize}: #{status || 'not found'}"
- if status - if status
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
= ci_icon_for_status(status) = ci_icon_for_status(status)
- else - else
.light.has-tooltip{ title: tooltip } .light.has-tooltip{ title: tooltip }
\- \-
%td %td
- if pipeline.started_at && pipeline.finished_at - if pipeline.duration
%p.duration %p.duration
= custom_icon("icon_timer") = custom_icon("icon_timer")
= duration_in_numbers(pipeline.finished_at, pipeline.started_at) = duration_in_numbers(pipeline.duration)
- if pipeline.finished_at - if pipeline.finished_at
%p.finished-at %p.finished-at
= icon("calendar") = icon("calendar")
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
%ul.dropdown-menu.dropdown-menu-align-right %ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |build| - actions.each do |build|
%li %li
= link_to play_namespace_project_build_path(@project.namespace, @project, build), method: :post, rel: 'nofollow' do = link_to play_namespace_project_build_path(pipeline.project.namespace, pipeline.project, build), method: :post, rel: 'nofollow' do
= icon("play") = icon("play")
%span= build.name.humanize %span= build.name.humanize
- if artifacts.present? - if artifacts.present?
...@@ -82,15 +82,15 @@ ...@@ -82,15 +82,15 @@
%ul.dropdown-menu.dropdown-menu-align-right %ul.dropdown-menu.dropdown-menu-align-right
- artifacts.each do |build| - artifacts.each do |build|
%li %li
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do = link_to download_namespace_project_build_artifacts_path(pipeline.project.namespace, pipeline.project, build), rel: 'nofollow' do
= icon("download") = icon("download")
%span Download '#{build.name}' artifacts %span Download '#{build.name}' artifacts
- if can?(current_user, :update_pipeline, @project) - if can?(current_user, :update_pipeline, pipeline.project)
.cancel-retry-btns.inline .cancel-retry-btns.inline
- if pipeline.retryable? - if pipeline.retryable?
= link_to retry_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do = link_to retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do
= icon("repeat") = icon("repeat")
- if pipeline.cancelable? - if pipeline.cancelable?
= link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do = link_to cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
= icon("remove") = icon("remove")
...@@ -56,10 +56,10 @@ ...@@ -56,10 +56,10 @@
= pluralize(@commit.pipelines.count, 'pipeline') = pluralize(@commit.pipelines.count, 'pipeline')
= link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status-link ci-status-icon-#{@commit.status}" do = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status-link ci-status-icon-#{@commit.status}" do
= ci_icon_for_status(@commit.status) = ci_icon_for_status(@commit.status)
= ci_label_for_status(@commit.status) %span.ci-status-label
- if @commit.pipelines.duration = ci_label_for_status(@commit.status)
in in
= time_interval_in_words @commit.pipelines.duration = time_interval_in_words @commit.pipelines.total_duration
.commit-box.content-block .commit-box.content-block
%h3.commit-title %h3.commit-title
......
...@@ -17,6 +17,13 @@ ...@@ -17,6 +17,13 @@
.select-wrapper .select-wrapper
= select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control" = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control"
%span.caret %span.caret
.form-group
= label_tag :expires_at, 'Access expiration date', class: 'label-light'
.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, all users in the group will automatically lose access to this project.
= submit_tag "Share", class: "btn btn-create" = submit_tag "Share", class: "btn btn-create"
.col-lg-9.col-lg-offset-3 .col-lg-9.col-lg-offset-3
%hr %hr
...@@ -35,6 +42,10 @@ ...@@ -35,6 +42,10 @@
= group.name = group.name
%br %br
up to #{group_link.human_access} up to #{group_link.human_access}
- if group_link.expires?
·
%span{ class: ('text-warning' if group_link.expires_soon?) }
expires in #{distance_of_time_in_words_to_now(group_link.expires_at)}
.pull-right .pull-right
= link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: "btn btn-transparent" do = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: "btn btn-transparent" do
%span.sr-only disable sharing %span.sr-only disable sharing
......
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
- if @merge_request.open? - if @merge_request.open?
.pull-right .pull-right
- if @merge_request.source_branch_exists? - if @merge_request.source_branch_exists?
- if koding_enabled? && @repository.koding_yml
= link_to koding_project_url(@merge_request.source_project, @merge_request.source_branch, @merge_request.commits.first.short_id), class: "btn inline btn-grouped btn-sm", target: '_blank' do
Run in IDE (Koding)
= link_to "#modal_merge_info", class: "btn inline btn-grouped btn-sm", "data-toggle" => "modal" do = link_to "#modal_merge_info", class: "btn inline btn-grouped btn-sm", "data-toggle" => "modal" do
Check out branch Check out branch
......
...@@ -6,10 +6,10 @@ ...@@ -6,10 +6,10 @@
-# the new push and commits, during which it will think the conflicts still exist. -# the new push and commits, during which it will think the conflicts still exist.
-# We send this param to get the widget to treat the MR as having no more conflicts. -# We send this param to get the widget to treat the MR as having no more conflicts.
- resolved_conflicts = params[:resolved_conflicts] - resolved_conflicts = params[:resolved_conflicts]
- if Gitlab::Geo.secondary? - if Gitlab::Geo.secondary?
= render 'projects/merge_requests/widget/open/geo' = render 'projects/merge_requests/widget/open/geo'
- elsif @project.archived? - if @project.archived?
= render 'projects/merge_requests/widget/open/archived' = render 'projects/merge_requests/widget/open/archived'
- elsif @merge_request.commits.blank? - elsif @merge_request.commits.blank?
= render 'projects/merge_requests/widget/open/nothing' = render 'projects/merge_requests/widget/open/nothing'
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.comment-toolbar.clearfix .comment-toolbar.clearfix
.toolbar-text .toolbar-text
Styling with Styling with
= link_to 'Markdown', help_page_path('markdown/markdown'), target: '_blank', tabindex: -1 = link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1
- if supports_slash_commands - if supports_slash_commands
and and
= link_to 'slash commands', help_page_path('user/project/slash_commands'), target: '_blank', tabindex: -1 = link_to 'slash commands', help_page_path('user/project/slash_commands'), target: '_blank', tabindex: -1
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
= link_to @pipeline.ref, namespace_project_commits_path(@project.namespace, @project, @pipeline.ref), class: "monospace" = link_to @pipeline.ref, namespace_project_commits_path(@project.namespace, @project, @pipeline.ref), class: "monospace"
- if @pipeline.duration - if @pipeline.duration
in in
= time_interval_in_words @pipeline.duration = time_interval_in_words(@pipeline.duration)
.pull-right .pull-right
= link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), class: "ci-status ci-#{@pipeline.status}" do = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), class: "ci-status ci-#{@pipeline.status}" do
......
...@@ -14,5 +14,14 @@ ...@@ -14,5 +14,14 @@
Read more about role permissions Read more about role permissions
%strong= link_to "here", help_page_path("user/permissions"), class: "vlink" %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 project.
.form-actions .form-actions
= f.submit 'Add users to project', class: "btn btn-create" = f.submit 'Add users to project', class: "btn btn-create"
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment