diff options
author | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2016-08-22 11:34:41 +0300 |
---|---|---|
committer | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2016-08-22 11:34:41 +0300 |
commit | 9329436deb3931d28c28b41de56173ef8acd63b5 (patch) | |
tree | 7557875233d3b506c2496599db1814afc7558c7c /app | |
parent | 6db65143db5003f74ddb1c9868a3d852e5661a0a (diff) | |
parent | fb84439a92e759ff90811e98f6abf6bdbb3e6d55 (diff) | |
download | gitlab-ce-9329436deb3931d28c28b41de56173ef8acd63b5.tar.gz |
Merge branch 'master' into dz-merge-request-version
Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
Diffstat (limited to 'app')
120 files changed, 846 insertions, 293 deletions
diff --git a/app/assets/images/koding-logo.svg b/app/assets/images/koding-logo.svg new file mode 100644 index 00000000000..ad89d684d94 --- /dev/null +++ b/app/assets/images/koding-logo.svg @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 14"> + <g fill="#d6d7d9"> + <path d="M8.7 0L5.3.3l3.2 6.8-3.2 6.6 3.5.3L12 6.9z"/> + <ellipse cx="1.7" cy="11.1" rx="1.7" ry="1.7"/> + <ellipse cx="1.7" cy="5.6" rx="1.7" ry="1.7"/> + </g> +</svg>
\ No newline at end of file diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index a122fa2d637..fc354dfd677 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -26,8 +26,6 @@ /*= require bootstrap/tooltip */ /*= require bootstrap/popover */ /*= require select2 */ -/*= require ace-rails-ap */ -/*= require ace/ext-searchbox */ /*= require underscore */ /*= require dropzone */ /*= require mousetrap */ @@ -153,7 +151,9 @@ }); }); $('.remove-row').bind('ajax:success', function() { - return $(this).closest('li').fadeOut(); + $(this).tooltip('destroy') + .closest('li') + .fadeOut(); }); $('.js-remove-tr').bind('ajax:before', function() { return $(this).hide(); diff --git a/app/assets/javascripts/blob_edit/blob_edit_bundle.js b/app/assets/javascripts/blob_edit/blob_edit_bundle.js new file mode 100644 index 00000000000..2afef43f3d6 --- /dev/null +++ b/app/assets/javascripts/blob_edit/blob_edit_bundle.js @@ -0,0 +1,12 @@ +/*= require_tree . */ + +(function() { + $(function() { + var url = $(".js-edit-blob-form").data("relative-url-root"); + url += $(".js-edit-blob-form").data("assets-prefix"); + + var blob = new EditBlob(url, $('.js-edit-blob-form').data('blob-language')); + new NewCommitForm($('.js-edit-blob-form')); + }); + +}).call(this); diff --git a/app/assets/javascripts/blob/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index 649c79daee8..649c79daee8 100644 --- a/app/assets/javascripts/blob/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index 2c65d4427be..a612cf0f1ae 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -38,7 +38,7 @@ $(() => { ready () { Store.disabled = this.disabled; gl.boardService.all() - .then((resp) => { + .then((resp) => { resp.json().forEach((board) => { const list = Store.addList(board); diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6 index e17784e7948..d7f4107cb02 100644 --- a/app/assets/javascripts/boards/components/board.js.es6 +++ b/app/assets/javascripts/boards/components/board.js.es6 @@ -55,7 +55,7 @@ draggable: '.is-draggable', handle: '.js-board-handle', onEnd: (e) => { - document.body.classList.remove('is-dragging'); + gl.issueBoards.onEnd(); if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) { const order = this.sortable.toArray(), @@ -72,10 +72,6 @@ } }); - if (bp.getBreakpointSize() === 'xs') { - options.handle = '.js-board-drag-handle'; - } - this.sortable = Sortable.create(this.$el.parentNode, options); }, beforeDestroy () { diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 1503d14c508..a6644e9eb8c 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -63,6 +63,8 @@ Store.moving.issue = card.issue; Store.moving.list = card.list; + + gl.issueBoards.onStart(); }, onAdd: (e) => { gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue); @@ -72,10 +74,6 @@ } }); - if (bp.getBreakpointSize() === 'xs') { - options.handle = '.js-card-drag-handle'; - } - this.sortable = Sortable.create(this.$els.list, options); // Scroll event on list to load more diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 index b7afe4897b6..44addb3ea98 100644 --- a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 +++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 @@ -2,6 +2,19 @@ window.gl = window.gl || {}; window.gl.issueBoards = window.gl.issueBoards || {}; + gl.issueBoards.onStart = () => { + $('.has-tooltip').tooltip('hide') + .tooltip('disable'); + document.body.classList.add('is-dragging'); + }; + + gl.issueBoards.onEnd = () => { + $('.has-tooltip').tooltip('enable'); + document.body.classList.remove('is-dragging'); + }; + + gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch; + gl.issueBoards.getBoardSortableDefaultOptions = (obj) => { let defaultSortOptions = { forceFallback: true, @@ -9,14 +22,11 @@ fallbackOnBody: true, ghostClass: 'is-ghost', filter: '.has-tooltip', - scrollSensitivity: 100, + delay: gl.issueBoards.touchEnabled ? 100 : 0, + scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100, scrollSpeed: 20, - onStart () { - document.body.classList.add('is-dragging'); - }, - onEnd () { - document.body.classList.remove('is-dragging'); - } + onStart: gl.issueBoards.onStart, + onEnd: gl.issueBoards.onEnd } Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; }); diff --git a/app/assets/javascripts/boards/models/label.js.es6 b/app/assets/javascripts/boards/models/label.js.es6 index e81e91fe972..583829552cd 100644 --- a/app/assets/javascripts/boards/models/label.js.es6 +++ b/app/assets/javascripts/boards/models/label.js.es6 @@ -3,6 +3,7 @@ class ListLabel { this.id = obj.id; this.title = obj.title; this.color = obj.color; + this.textColor = obj.text_color; this.description = obj.description; this.priority = (obj.priority !== null) ? obj.priority : Infinity; } diff --git a/app/assets/javascripts/boards/test_utils/simulate_drag.js b/app/assets/javascripts/boards/test_utils/simulate_drag.js index 75f8b730195..75f8b730195 100755..100644 --- a/app/assets/javascripts/boards/test_utils/simulate_drag.js +++ b/app/assets/javascripts/boards/test_utils/simulate_drag.js diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 74c4ab563f9..ba64d2bcf0b 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -20,6 +20,9 @@ path = page.split(':'); shortcut_handler = null; switch (page) { + case 'projects:boards:show': + shortcut_handler = new ShortcutsNavigation(); + break; case 'projects:issues:index': Issuable.init(); new IssuableBulkActions(); @@ -126,10 +129,12 @@ new NotificationsDropdown(); break; case 'groups:group_members:index': + new gl.MemberExpirationDate(); new GroupMembers(); new UsersSelect(); break; case 'projects:project_members:index': + new gl.MemberExpirationDate(); new ProjectMembers(); new UsersSelect(); break; @@ -171,6 +176,7 @@ new BuildArtifacts(); break; case 'projects:group_links:index': + new gl.MemberExpirationDate(); new GroupsSelect(); break; case 'search:show': diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index d3394fae3f9..24abea0d30d 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -31,9 +31,8 @@ this.input .on('keydown', function (e) { var keyCode = e.which; - if (keyCode === 13) { - e.preventDefault() + e.preventDefault(); } }) .on('keyup', function(e) { @@ -111,9 +110,9 @@ matches = fuzzaldrinPlus.match($el.text().trim(), search_text); if (!$el.is('.dropdown-header')) { if (matches.length) { - return $el.show(); + return $el.show().removeClass('option-hidden'); } else { - return $el.hide(); + return $el.hide().addClass('option-hidden'); } } }); @@ -179,7 +178,7 @@ })(); GitLabDropdown = (function() { - var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, currentIndex; + var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, NON_SELECTABLE_CLASSES, SELECTABLE_CLASSES, currentIndex; LOADING_CLASS = "is-loading"; @@ -191,6 +190,12 @@ currentIndex = -1; + NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link, .option-hidden'; + + SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ")"; + + CURSOR_SELECT_SCROLL_PADDING = 5 + FILTER_INPUT = '.dropdown-input .dropdown-input-field'; function GitLabDropdown(el1, options) { @@ -213,6 +218,7 @@ if (this.options.data) { if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) { this.fullData = this.options.data; + currentIndex = -1; this.parseData(this.options.data); } else { this.remote = new GitLabDropdownRemote(this.options.data, { @@ -240,7 +246,7 @@ keys: searchFields, elements: (function(_this) { return function() { - selector = '.dropdown-content li:not(.divider)'; + selector = '.dropdown-content li:not(' + NON_SELECTABLE_CLASSES + ')'; if (_this.dropdown.find('.dropdown-toggle-page').length) { selector = ".dropdown-page-one " + selector; } @@ -256,7 +262,7 @@ return function(data) { _this.parseData(data); if (_this.filterInput.val() !== '') { - selector = '.dropdown-content li:not(.divider):visible'; + selector = SELECTABLE_CLASSES; if (_this.dropdown.find('.dropdown-toggle-page').length) { selector = ".dropdown-page-one " + selector; } @@ -376,7 +382,7 @@ var $target; if (this.options.multiSelect) { $target = $(e.target); - if (!$target.hasClass('dropdown-menu-close') && !$target.hasClass('dropdown-menu-close-icon') && !$target.data('is-link')) { + if ($target && !$target.hasClass('dropdown-menu-close') && !$target.hasClass('dropdown-menu-close-icon') && !$target.data('is-link')) { e.stopPropagation(); return false; } else { @@ -387,7 +393,7 @@ GitLabDropdown.prototype.opened = function() { var contentHtml; - currentIndex = -1; + this.resetRows(); this.addArrowKeyEvent(); if (this.options.setIndeterminateIds) { this.options.setIndeterminateIds.call(this); @@ -410,6 +416,7 @@ GitLabDropdown.prototype.hidden = function(e) { var $input; + this.resetRows(); this.removeArrayKeyEvent(); $input = this.dropdown.find(".dropdown-input-field"); if (this.options.filterable) { @@ -463,14 +470,15 @@ return "<li class='separator'></li>"; } if (data.header != null) { - return "<li class='dropdown-header'>" + data.header + "</li>"; + return _.template('<li class="dropdown-header"><%- header %></li>')({ header: data.header }); } if (this.options.renderRow) { html = this.options.renderRow.call(this.options, data, this); } else { if (!selected) { value = this.options.id ? this.options.id(data) : data.id; - fieldName = this.options.fieldName; + fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName() : this.options.fieldName; + field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']"); if (field.length) { selected = true; @@ -494,11 +502,16 @@ text = this.highlightTextMatches(text, this.filterInput.val()); } if (group) { - groupAttrs = "data-group='" + group + "' data-index='" + index + "'"; + groupAttrs = 'data-group=' + group + ' data-index=' + index; } else { groupAttrs = ''; } - html = "<li> <a href='" + url + "' " + groupAttrs + " class='" + cssClass + "'> " + text + " </a> </li>"; + html = _.template('<li><a href="<%- url %>" <%- groupAttrs %> class="<%- cssClass %>"><%= text %></a></li>')({ + url: url, + groupAttrs: groupAttrs, + cssClass: cssClass, + text: text + }); } return html; }; @@ -520,20 +533,8 @@ return html = "<li class='dropdown-menu-empty-link'> <a href='#' class='is-focused'> No matching results. </a> </li>"; }; - GitLabDropdown.prototype.highlightRow = function(index) { - var selector; - if (this.filterInput.val() !== "") { - selector = '.dropdown-content li:first-child a'; - if (this.dropdown.find(".dropdown-toggle-page").length) { - selector = ".dropdown-page-one .dropdown-content li:first-child a"; - } - return this.getElement(selector).addClass('is-focused'); - } - }; - GitLabDropdown.prototype.rowClicked = function(el) { var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value; - fieldName = this.options.fieldName; isInput = $(this.el).is('input'); if (this.renderedData) { groupName = el.data('group'); @@ -545,6 +546,7 @@ selectedObject = this.renderedData[selectedIndex]; } } + fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName(selectedObject) : this.options.fieldName; value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id; if (isInput) { field = $(this.el); @@ -559,10 +561,9 @@ field.remove(); } if (this.options.toggleLabel) { - return this.updateLabel(selectedObject, el, this); - } else { - return selectedObject; + this.updateLabel(selectedObject, el, this); } + return selectedObject; } else if (el.hasClass(INDETERMINATE_CLASS)) { el.addClass(ACTIVE_CLASS); el.removeClass(INDETERMINATE_CLASS); @@ -570,7 +571,7 @@ field.remove(); } if (!field.length && fieldName) { - this.addInput(fieldName, value); + this.addInput(fieldName, value, selectedObject); } return selectedObject; } else { @@ -589,7 +590,7 @@ } if (value != null) { if (!field.length && fieldName) { - this.addInput(fieldName, value); + this.addInput(fieldName, value, selectedObject); } else { field.val(value).trigger('change'); } @@ -598,24 +599,29 @@ } }; - GitLabDropdown.prototype.addInput = function(fieldName, value) { + GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) { var $input; $input = $('<input>').attr('type', 'hidden').attr('name', fieldName).val(value); if (this.options.inputId != null) { $input.attr('id', this.options.inputId); } + if (selectedObject && selectedObject.type) { + $input.attr('data-type', selectedObject.type); + } return this.dropdown.before($input); }; GitLabDropdown.prototype.selectRowAtIndex = function(index) { var $el, selector; - selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(" + index + ") a"; + selector = SELECTABLE_CLASSES + ":eq(" + index + ") a"; if (this.dropdown.find(".dropdown-toggle-page").length) { selector = ".dropdown-page-one " + selector; } $el = $(selector, this.dropdown); if ($el.length) { - return $el.first().trigger('click'); + $el.first().trigger('click'); + var href = $el.attr('href'); + if (href && href !== '#') Turbolinks.visit(href); } }; @@ -623,7 +629,7 @@ var $input, ARROW_KEY_CODES, selector; ARROW_KEY_CODES = [38, 40]; $input = this.dropdown.find(".dropdown-input-field"); - selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator):visible'; + selector = SELECTABLE_CLASSES; if (this.dropdown.find(".dropdown-toggle-page").length) { selector = ".dropdown-page-one " + selector; } @@ -651,7 +657,7 @@ return false; } if (currentKeyCode === 13 && currentIndex !== -1) { - return _this.selectRowAtIndex($('.is-focused', _this.dropdown).closest('li').index() - 1); + return _this.selectRowAtIndex(currentIndex); } }; })(this)); @@ -661,6 +667,11 @@ return $('body').off('keydown'); }; + GitLabDropdown.prototype.resetRows = function resetRows() { + currentIndex = -1; + $('.is-focused', this.dropdown).removeClass('is-focused'); + }; + GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) { var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop; $('.is-focused', this.dropdown).removeClass('is-focused'); @@ -674,10 +685,14 @@ listItemHeight = $listItem.outerHeight(); listItemTop = $listItem.prop('offsetTop'); listItemBottom = listItemTop + listItemHeight; - if (listItemBottom > dropdownContentBottom + dropdownScrollTop) { - return $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom); - } else if (listItemTop < dropdownContentTop + dropdownScrollTop) { - return $dropdownContent.scrollTop(listItemTop - dropdownContentTop); + if (!index) { + $dropdownContent.scrollTop(0) + } else if (index === ($listItems.length - 1)) { + $dropdownContent.scrollTop($dropdownContent.prop('scrollHeight')); + } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) { + $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING); + } else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) { + return $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING); } }; diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index 297d4f029f0..b7f92ae9883 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -102,20 +102,34 @@ }; IssuableForm.prototype.initMoveDropdown = function() { - var $moveDropdown; + var $moveDropdown, pageSize; $moveDropdown = $('.js-move-dropdown'); if ($moveDropdown.length) { + pageSize = $moveDropdown.data('page-size'); return $('.js-move-dropdown').select2({ ajax: { url: $moveDropdown.data('projects-url'), - results: function(data) { + quietMillis: 125, + data: function(term, page, context) { return { - results: data + search: term, + offset_id: context }; }, - data: function(query) { + results: function(data) { + var context, + more; + + if (data.length >= pageSize) + more = true; + + if (data[data.length - 1]) + context = data[data.length - 1].id; + return { - search: query + results: data, + more: more, + context: context }; } }, diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 0526430989f..565dbeacdb3 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -4,7 +4,7 @@ var _this; _this = this; $('.js-label-select').each(function(i, dropdown) { - var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo; + var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip; $dropdown = $(dropdown); projectId = $dropdown.data('project-id'); labelUrl = $dropdown.data('labels'); @@ -21,6 +21,7 @@ $block = $selectbox.closest('.block'); $form = $dropdown.closest('form'); $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span'); + $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip'); $value = $block.find('.value'); $loading = $block.find('.block-loading').fadeOut(); if (issueUpdateURL != null) { @@ -31,7 +32,11 @@ labelNoneHTMLTemplate = '<span class="no-value">None</span>'; } - new gl.CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), projectId); + $sidebarLabelTooltip.tooltip(); + + if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) { + new gl.CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), projectId); + } saveLabelData = function() { var data, selected; @@ -52,7 +57,7 @@ dataType: 'JSON', data: data }).done(function(data) { - var labelCount, template; + var labelCount, template, labelTooltipTitle, labelTitles; $loading.fadeOut(); $dropdown.trigger('loaded.gl.dropdown'); $selectbox.hide(); @@ -66,6 +71,27 @@ } $value.removeAttr('style').html(template); $sidebarCollapsedValue.text(labelCount); + + if (data.labels.length) { + labelTitles = data.labels.map(function(label) { + return label.title; + }); + + if (labelTitles.length > 5) { + labelTitles = labelTitles.slice(0, 5); + labelTitles.push('and ' + (data.labels.length - 5) + ' more'); + } + + labelTooltipTitle = labelTitles.join(', '); + } else { + labelTooltipTitle = ''; + $sidebarLabelTooltip.tooltip('destroy'); + } + + $sidebarLabelTooltip + .attr('title', labelTooltipTitle) + .tooltip('fixTitle'); + $('.has-tooltip', $value).tooltip({ container: 'body' }); diff --git a/app/assets/javascripts/lib/ace.js b/app/assets/javascripts/lib/ace.js new file mode 100644 index 00000000000..4cdf99cae72 --- /dev/null +++ b/app/assets/javascripts/lib/ace.js @@ -0,0 +1,2 @@ +/*= require ace-rails-ap */ +/*= require ace/ext-searchbox */ diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js new file mode 100644 index 00000000000..1935af491f7 --- /dev/null +++ b/app/assets/javascripts/member_expiration_date.js @@ -0,0 +1,32 @@ +(function() { + // Add datepickers to all `js-access-expiration-date` elements. If those elements are + // children of an element with the `clearable-input` class, and have a sibling + // `js-clear-input` element, then show that element when there is a value in the + // datepicker, and make clicking on that element clear the field. + // + gl.MemberExpirationDate = function() { + function toggleClearInput() { + $(this).closest('.clearable-input').toggleClass('has-value', $(this).val() !== ''); + } + + var inputs = $('.js-access-expiration-date'); + + inputs.datepicker({ + dateFormat: 'yy-mm-dd', + minDate: 1, + onSelect: toggleClearInput + }); + + inputs.next('.js-clear-input').on('click', function(event) { + event.preventDefault(); + + var input = $(this).closest('.clearable-input').find('.js-access-expiration-date'); + input.datepicker('setDate', null); + toggleClearInput.call(input); + }); + + inputs.on('blur', toggleClearInput); + + inputs.each(toggleClearInput); + }; +}).call(this); diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index b97f6d22715..4e1de4dfb72 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -65,7 +65,8 @@ url: $dropdown.data('refs-url'), data: { ref: $dropdown.data('ref') - } + }, + dataType: "json" }).done(function(refs) { return callback(refs); }); @@ -73,7 +74,7 @@ selectable: true, filterable: true, filterByText: true, - fieldName: 'ref', + fieldName: $dropdown.data('field-name'), renderRow: function(ref) { var link; if (ref.header != null) { diff --git a/app/assets/javascripts/project_members.js b/app/assets/javascripts/project_members.js index f6a796b325a..78f7b48bc7d 100644 --- a/app/assets/javascripts/project_members.js +++ b/app/assets/javascripts/project_members.js @@ -5,9 +5,6 @@ return $(this).fadeOut(); }); } - return ProjectMembers; - })(); - }).call(this); diff --git a/app/assets/javascripts/protected_branch_access_dropdown.js.es6 b/app/assets/javascripts/protected_branch_access_dropdown.js.es6 index 2fbb088fa04..7aeb5f92514 100644 --- a/app/assets/javascripts/protected_branch_access_dropdown.js.es6 +++ b/app/assets/javascripts/protected_branch_access_dropdown.js.es6 @@ -10,8 +10,12 @@ selectable: true, inputId: $dropdown.data('input-id'), fieldName: $dropdown.data('field-name'), - toggleLabel(item) { - return item.text; + toggleLabel(item, el) { + if (el.is('.is-active')) { + return item.text; + } else { + return 'Select'; + } }, clicked(item, $el, e) { e.preventDefault(); diff --git a/app/assets/javascripts/protected_branch_create.js.es6 b/app/assets/javascripts/protected_branch_create.js.es6 index 2efca2414dc..46beca469b9 100644 --- a/app/assets/javascripts/protected_branch_create.js.es6 +++ b/app/assets/javascripts/protected_branch_create.js.es6 @@ -47,9 +47,7 @@ const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]'); const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]'); - if ($branchInput.val() && $allowedToMergeInput.val() && $allowedToPushInput.val()){ - this.$form.find('input[type="submit"]').removeAttr('disabled'); - } + this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length)); } } diff --git a/app/assets/javascripts/protected_branch_edit.js.es6 b/app/assets/javascripts/protected_branch_edit.js.es6 index a59fcbfa082..40bc4adb71b 100644 --- a/app/assets/javascripts/protected_branch_edit.js.es6 +++ b/app/assets/javascripts/protected_branch_edit.js.es6 @@ -31,6 +31,9 @@ const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`); const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`); + // Do not update if one dropdown has not selected any option + if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return; + $.ajax({ type: 'POST', url: this.$wrap.data('url'), diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 990f6536eb2..227e8c696b4 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -7,7 +7,9 @@ KEYCODE = { ESCAPE: 27, BACKSPACE: 8, - ENTER: 13 + ENTER: 13, + UP: 38, + DOWN: 40 }; function SearchAutocomplete(opts) { @@ -223,6 +225,12 @@ case KEYCODE.ESCAPE: this.restoreOriginalState(); break; + case KEYCODE.ENTER: + this.disableAutocomplete(); + break; + case KEYCODE.UP: + case KEYCODE.DOWN: + return; default: if (this.searchInput.val() === '') { this.disableAutocomplete(); @@ -319,9 +327,11 @@ }; SearchAutocomplete.prototype.disableAutocomplete = function() { - this.searchInput.addClass('disabled'); - this.dropdown.removeClass('open'); - return this.restoreMenu(); + if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) { + this.searchInput.addClass('disabled'); + this.dropdown.removeClass('open').trigger('hidden.bs.dropdown'); + this.restoreMenu(); + } }; SearchAutocomplete.prototype.restoreMenu = function() { diff --git a/app/assets/javascripts/snippet/snippet_bundle.js b/app/assets/javascripts/snippet/snippet_bundle.js new file mode 100644 index 00000000000..855e97eb301 --- /dev/null +++ b/app/assets/javascripts/snippet/snippet_bundle.js @@ -0,0 +1,12 @@ +/*= require_tree . */ + +(function() { + $(function() { + var editor = ace.edit("editor") + + $(".snippet-form-holder form").on('submit', function() { + $(".snippet-file-content").val(editor.getValue()); + }); + }); + +}).call(this); diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 8846e08f390..be5c64c56d3 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -90,6 +90,15 @@ width: 100%; } } + + // Allows dynamic-width text in the dropdown toggle. + // Resizes to allow long text without overflowing the container. + &.dynamic { + width: auto; + min-width: 160px; + max-width: 100%; + padding-right: 25px; + } } .dropdown-menu, diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 407f1873431..d3e3fc50736 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -63,9 +63,10 @@ &.image_file { background: #eee; text-align: center; + img { - padding: 100px; - max-width: 50%; + padding: 20px; + max-width: 80%; } } diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss index f4d35c4b4b1..c0de09f3968 100644 --- a/app/assets/stylesheets/framework/gfm.scss +++ b/app/assets/stylesheets/framework/gfm.scss @@ -2,7 +2,7 @@ * Styles that apply to all GFM related forms. */ -.gfm-commit, .gfm-commit_range { +.gfm-commit_range { font-family: $monospace_font; font-size: 90%; } diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 26ad2870aa0..8374f30d0b2 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -1,6 +1,5 @@ .modal-body { position: relative; - overflow-y: auto; padding: 15px; .form-actions { diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 7852fc9a424..9e924f99e9c 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -72,6 +72,7 @@ font-weight: normal; background-color: #eee; color: #78a; + vertical-align: baseline; } } diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 21d87cc9d34..b2e22b60440 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -45,7 +45,8 @@ min-width: 175px; } -.select2-results .select2-result-label { +.select2-results .select2-result-label, +.select2-more-results { padding: 10px 15px; } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 8659604cb8b..06874a993fa 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -14,12 +14,20 @@ margin-top: 0; } + // Single code lines should wrap code { font-family: $monospace_font; - white-space: pre; + white-space: pre-wrap; word-wrap: normal; } + // Multi-line code blocks should scroll horizontally + pre { + code { + white-space: pre; + } + } + kbd { display: inline-block; padding: 3px 5px; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index ad4b2d6496f..9ac4d801ac4 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -8,9 +8,13 @@ } .is-dragging { + // Important because plugin sets inline CSS + opacity: 1!important; + * { - cursor: -webkit-grabbing; - cursor: grabbing; + // !important to make sure no style can override this when dragging + cursor: -webkit-grabbing!important; + cursor: grabbing!important; } } @@ -101,8 +105,8 @@ .board { display: -webkit-flex; display: flex; - min-width: calc(100vw - 15px); - max-width: calc(100vw - 15px); + min-width: calc(85vw - 15px); + max-width: calc(85vw - 15px); margin-bottom: 25px; padding-right: ($gl-padding / 2); padding-left: ($gl-padding / 2); @@ -154,14 +158,6 @@ padding: $gl-padding; font-size: 1em; border-bottom: 1px solid $border-color; - - .board-mobile-handle { - position: relative; - left: 0; - top: 1px; - margin-top: 0; - margin-right: 5px; - } } .board-search-container { @@ -254,11 +250,6 @@ opacity: 0.3; } -.is-dragging { - // Important because plugin sets inline CSS - opacity: 1!important; -} - .card { position: relative; width: 100%; @@ -269,11 +260,7 @@ list-style: none; &.user-can-drag { - padding-left: ($gl-padding * 2); - - @media (min-width: $screen-sm-min) { - padding-left: $gl-padding; - } + padding-left: $gl-padding; } &:not(:last-child) { @@ -294,17 +281,6 @@ } } -.board-mobile-handle { - position: absolute; - left: 10px; - top: 50%; - margin-top: (-15px / 2); - - @media (min-width: $screen-sm-min) { - display: none; - } -} - .card-title { margin: 0; font-size: 1em; @@ -316,6 +292,7 @@ .card-footer { margin-top: 5px; + line-height: 25px; .label { margin-right: 4px; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 81fce55853c..c1bb250b42d 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -168,7 +168,6 @@ text-overflow: ellipsis; &:hover { - background-color: $row-hover; color: $gl-text-color; } } @@ -190,6 +189,10 @@ display: block; } } + + &:hover { + background-color: $row-hover; + } } } } diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index bbe0c6c5f1f..53ec0002afe 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -66,6 +66,15 @@ margin-left: 8px; } } + + .ci-status-link { + + svg { + position: relative; + top: 2px; + margin: 0 2px 0 3px; + } + } } .ci-status-link { diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 1b389d83525..4d9c73c6840 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -34,11 +34,4 @@ } } } - - .wiki { - code { - white-space: pre-wrap; - word-break: keep-all; - } - } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index abe8414e5e0..fcdcc837298 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -384,3 +384,10 @@ color: $gl-dark-link-color; } } + +.merge-request-details { + + .title { + margin-bottom: 20px; + } +} diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index ce1c424624f..6fa097e3bf1 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -300,6 +300,17 @@ &.playable { background-color: $gray-light; + + svg { + height: 12px; + width: 12px; + position: relative; + top: 1px; + + path { + fill: $layout-link-gray; + } + } } .build-content { @@ -319,10 +330,6 @@ margin-right: 5px; } - .fa { - font-size: 13px; - } - // Connect first build in each stage with right horizontal line &:first-child { &::after { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 27dc2b2a1fa..eaf2d3270b3 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -719,3 +719,29 @@ pre.light-well { width: 300px; } } + +.clearable-input { + position: relative; + + .clear-icon { + @extend .fa-times; + display: none; + position: absolute; + right: 7px; + top: 7px; + color: $location-icon-color; + + &:before { + font-family: FontAwesome; + font-weight: normal; + font-style: normal; + } + } + + &.has-value { + .clear-icon { + cursor: pointer; + display: block; + } + } +} diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 9e1dc15de84..6ef7cf0bae6 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -109,6 +109,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :sentry_dsn, :akismet_enabled, :akismet_api_key, + :koding_enabled, + :koding_url, :email_author_in_body, :repository_checks_enabled, :metrics_packet_size, diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 4ce18321649..cdfa8d91a28 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -42,7 +42,7 @@ class Admin::GroupsController < Admin::ApplicationController end def members_update - @group.add_users(params[:user_ids].split(','), params[:access_level], current_user) + @group.add_users(params[:user_ids].split(','), params[:access_level], current_user: current_user) redirect_to [:admin, @group], notice: 'Users were successfully added.' end diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index c8390af3b36..d425d0f9014 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -2,6 +2,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController before_action :find_todos, only: [:index, :destroy_all] def index + @sort = params[:sort] @todos = @todos.page(params[:page]) end diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 9fc41a12536..272164cd0cc 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -21,7 +21,12 @@ class Groups::GroupMembersController < Groups::ApplicationController end def create - @group.add_users(params[:user_ids].split(','), params[:access_level], current_user) + @group.add_users( + params[:user_ids].split(','), + params[:access_level], + current_user: current_user, + expires_at: params[:expires_at] + ) redirect_to group_group_members_path(@group), notice: 'Users were successfully added.' end @@ -63,7 +68,7 @@ class Groups::GroupMembersController < Groups::ApplicationController protected def member_params - params.require(:group_member).permit(:access_level, :user_id) + params.require(:group_member).permit(:access_level, :user_id, :expires_at) end # MembershipActions concern diff --git a/app/controllers/koding_controller.rb b/app/controllers/koding_controller.rb new file mode 100644 index 00000000000..bb89f3090f9 --- /dev/null +++ b/app/controllers/koding_controller.rb @@ -0,0 +1,15 @@ +class KodingController < ApplicationController + before_action :check_integration!, :authenticate_user!, :reject_blocked! + layout 'koding' + + def index + path = File.join(Rails.root, 'doc/integration/koding-usage.md') + @markdown = File.read(path) + end + + private + + def check_integration! + render_404 unless current_application_settings.koding_enabled? + end +end diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 2d894b3dd4a..1a4f6b50e8f 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -12,7 +12,7 @@ module Projects only: [:iid, :title, :confidential], include: { assignee: { only: [:id, :name, :username], methods: [:avatar_url] }, - labels: { only: [:id, :title, :description, :color, :priority] } + labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] } }) end diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 48fe81b0d74..2de8ada3e29 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -15,6 +15,13 @@ class Projects::BranchesController < Projects::ApplicationController diverging_commit_counts = repository.diverging_commit_counts(branch) [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max end + + respond_to do |format| + format.html + format.json do + render json: @repository.branch_names + end + end end def recent diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 606552fa853..d0c4550733c 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -11,7 +11,9 @@ class Projects::GroupLinksController < Projects::ApplicationController return render_404 unless can?(current_user, :read_group, group) project.project_group_links.create( - group: group, group_access: params[:link_group_access] + group: group, + group_access: params[:link_group_access], + expires_at: params[:expires_at] ) redirect_to namespace_project_group_links_path(project.namespace, project) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 3435a118964..42a7e5a2c30 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -36,7 +36,12 @@ class Projects::ProjectMembersController < Projects::ApplicationController end def create - @project.team.add_users(params[:user_ids].split(','), params[:access_level], current_user) + @project.team.add_users( + params[:user_ids].split(','), + params[:access_level], + expires_at: params[:expires_at], + current_user: current_user + ) redirect_to namespace_project_project_members_path(@project.namespace, @project) end @@ -94,7 +99,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController protected def member_params - params.require(:project_member).permit(:user_id, :access_level) + params.require(:project_member).permit(:user_id, :access_level, :expires_at) end # MembershipActions concern diff --git a/app/finders/move_to_project_finder.rb b/app/finders/move_to_project_finder.rb index 3334b8556df..79eb45568be 100644 --- a/app/finders/move_to_project_finder.rb +++ b/app/finders/move_to_project_finder.rb @@ -1,4 +1,6 @@ class MoveToProjectFinder + PAGE_SIZE = 50 + def initialize(user) @user = user end @@ -8,6 +10,10 @@ class MoveToProjectFinder projects = projects.search(search) if search.present? projects = projects.excluding_project(from_project) + # infinite scroll using offset + projects = projects.where('projects.id < ?', offset_id) if offset_id.present? + projects = projects.limit(PAGE_SIZE) + # to ask for Project#name_with_namespace projects.includes(namespace: :owner) end diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 37bad596a16..06b3e8a9502 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -33,7 +33,7 @@ class TodosFinder # the project IDs yielded by the todos query thus far items = by_project(items) - items.reorder(id: :desc) + sort(items) end private @@ -106,6 +106,10 @@ class TodosFinder params[:type] end + def sort(items) + params[:sort] ? items.sort(params[:sort]) : items.reorder(id: :desc) + end + def by_action(items) if action? items = items.where(action: to_action_id) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 78c0b79d2bd..6de25bea654 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -31,6 +31,10 @@ module ApplicationSettingsHelper current_application_settings.akismet_enabled? end + def koding_enabled? + current_application_settings.koding_enabled? + end + def allowed_protocols_present? current_application_settings.enabled_git_access_protocol.present? end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 9ea03720c1e..e13b7cdd707 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -217,4 +217,12 @@ module BlobHelper def gitlab_ci_ymls @gitlab_ci_ymls ||= Gitlab::Template::GitlabCiYmlTemplate.dropdown_names end + + def blob_editor_paths + { + 'relative-url-root' => Rails.application.config.relative_url_root, + 'assets-prefix' => Gitlab::Application.config.assets.prefix, + 'blob-language' => @blob && @blob.language.try(:ace_mode) + } + end end diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 94df7d131ca..bb285a17baf 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -39,7 +39,7 @@ module CiStatusHelper when 'running' 'icon_status_running' when 'play' - return icon('play fw') + 'icon_play' when 'created' 'icon_status_pending' else diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 47d174361db..b9baeb1d6c4 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -72,6 +72,15 @@ module IssuablesHelper end end + def issuable_labels_tooltip(labels, limit: 5) + first, last = labels.partition.with_index{ |_, i| i < limit } + + label_names = first.collect(&:name) + label_names << "and #{last.size} more" unless last.empty? + + label_names.join(', ') + end + private def sidebar_gutter_collapsed? diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 505545fbabb..249d18c4486 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -236,6 +236,60 @@ module ProjectsHelper ) end + def add_koding_stack_path(project) + namespace_project_new_blob_path( + project.namespace, + project, + project.default_branch || 'master', + file_name: '.koding.yml', + commit_message: "Add Koding stack script", + content: <<-CONTENT.strip_heredoc + provider: + aws: + access_key: '${var.aws_access_key}' + secret_key: '${var.aws_secret_key}' + resource: + aws_instance: + #{project.path}-vm: + instance_type: t2.nano + user_data: |- + + # Created by GitLab UI for :> + + echo _KD_NOTIFY_@Installing Base packages...@ + + apt-get update -y + apt-get install git -y + + echo _KD_NOTIFY_@Cloning #{project.name}...@ + + export KODING_USER=${var.koding_user_username} + export REPO_URL=#{root_url}${var.koding_queryString_repo}.git + export BRANCH=${var.koding_queryString_branch} + + sudo -i -u $KODING_USER git clone $REPO_URL -b $BRANCH + + echo _KD_NOTIFY_@#{project.name} cloned.@ + CONTENT + ) + end + + def koding_project_url(project = nil, branch = nil, sha = nil) + if project + import_path = "/Home/Stacks/import" + + repo = project.path_with_namespace + branch ||= project.default_branch + sha ||= project.commit.short_id + + path = "#{import_path}?repo=#{repo}&branch=#{branch}&sha=#{sha}" + + return URI.join(current_application_settings.koding_url, path).to_s + end + + current_application_settings.koding_url + end + def contribution_guide_path(project) if project && contribution_guide = project.repository.contribution_guide namespace_project_blob_path( diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb index 790001222f1..271e839692a 100644 --- a/app/helpers/time_helper.rb +++ b/app/helpers/time_helper.rb @@ -15,20 +15,9 @@ module TimeHelper "#{from.to_s(:short)} - #{to.to_s(:short)}" end - def duration_in_numbers(finished_at, started_at) - interval = interval_in_seconds(started_at, finished_at) - time_format = interval < 1.hour ? "%M:%S" : "%H:%M:%S" + def duration_in_numbers(duration) + time_format = duration < 1.hour ? "%M:%S" : "%H:%M:%S" - Time.at(interval).utc.strftime(time_format) - end - - private - - def interval_in_seconds(started_at, finished_at = nil) - if started_at && finished_at - finished_at.to_i - started_at.to_i - elsif started_at - Time.now.to_i - started_at.to_i - end + Time.at(duration).utc.strftime(time_format) end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 07f703f205d..a49dd703926 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -166,38 +166,44 @@ class Ability end def project_abilities(user, project) - rules = [] key = "/user/#{user.id}/project/#{project.id}" - RequestStore.store[key] ||= begin - # Push abilities on the users team role - rules.push(*project_team_rules(project.team, user)) + if RequestStore.active? + RequestStore.store[key] ||= uncached_project_abilities(user, project) + else + uncached_project_abilities(user, project) + end + end - owner = user.admin? || - project.owner == user || - (project.group && project.group.has_owner?(user)) + def uncached_project_abilities(user, project) + rules = [] + # Push abilities on the users team role + rules.push(*project_team_rules(project.team, user)) - if owner - rules.push(*project_owner_rules) - end + owner = user.admin? || + project.owner == user || + (project.group && project.group.has_owner?(user)) - if project.public? || (project.internal? && !user.external?) - rules.push(*public_project_rules) + if owner + rules.push(*project_owner_rules) + end - # Allow to read builds for internal projects - rules << :read_build if project.public_builds? + if project.public? || (project.internal? && !user.external?) + rules.push(*public_project_rules) - unless owner || project.team.member?(user) || project_group_member?(project, user) - rules << :request_access if project.request_access_enabled - end - end + # Allow to read builds for internal projects + rules << :read_build if project.public_builds? - if project.archived? - rules -= project_archived_rules + unless owner || project.team.member?(user) || project_group_member?(project, user) + rules << :request_access if project.request_access_enabled end + end - rules - project_disabled_features_rules(project) + if project.archived? + rules -= project_archived_rules end + + (rules - project_disabled_features_rules(project)).uniq end def project_team_rules(team, user) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 8c19d9dc9c8..f0bcb2d7cda 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -55,6 +55,10 @@ class ApplicationSetting < ActiveRecord::Base presence: true, if: :akismet_enabled + validates :koding_url, + presence: true, + if: :koding_enabled + validates :max_attachment_size, presence: true, numericality: { only_integer: true, greater_than: 0 } @@ -149,6 +153,8 @@ class ApplicationSetting < ActiveRecord::Base two_factor_grace_period: 48, recaptcha_enabled: false, akismet_enabled: false, + koding_enabled: false, + koding_url: nil, repository_checks_enabled: true, disabled_oauth_sign_in_sources: [], send_user_confirmation_email: false, diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index ed056a07a49..096b3b801af 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -62,6 +62,7 @@ module Ci status_event: 'enqueue' ) MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build) + build.pipeline.mark_as_processable_after_stage(build.stage_idx) new_build end end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index c360a6ff729..087abe4cbb1 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -78,6 +78,10 @@ module Ci CommitStatus.where(pipeline: pluck(:id)).stages end + def self.total_duration + where.not(duration: nil).sum(:duration) + end + def stages_with_latest_statuses statuses.latest.order(:stage_idx).group_by(&:stage) end @@ -146,6 +150,10 @@ module Ci end end + def mark_as_processable_after_stage(stage_idx) + builds.skipped.where('stage_idx > ?', stage_idx).find_each(&:process) + end + def latest? return false unless ref commit = project.commit(ref) @@ -250,7 +258,7 @@ module Ci end def update_duration - self.duration = statuses.latest.duration + self.duration = calculate_duration end def execute_hooks diff --git a/app/models/commit.rb b/app/models/commit.rb index cc413448ce8..817d063e4a2 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -229,7 +229,7 @@ class Commit def diff_refs Gitlab::Diff::DiffRefs.new( - base_sha: self.parent_id || self.sha, + base_sha: self.parent_id || Gitlab::Git::BLANK_SHA, head_sha: self.sha ) end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 703ca90edb6..84ceeac7d3e 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -21,6 +21,7 @@ class CommitStatus < ActiveRecord::Base where(id: max_id.group(:name, :commit_id)) end + scope :retried, -> { where.not(id: latest) } scope :ordered, -> { order(:name) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } @@ -30,6 +31,10 @@ class CommitStatus < ActiveRecord::Base transition [:created, :skipped] => :pending end + event :process do + transition skipped: :created + end + event :run do transition pending: :running end @@ -107,13 +112,7 @@ class CommitStatus < ActiveRecord::Base end def duration - duration = - if started_at && finished_at - finished_at - started_at - elsif started_at - Time.now - started_at - end - duration + calculate_duration end def stuck? diff --git a/app/models/concerns/expirable.rb b/app/models/concerns/expirable.rb new file mode 100644 index 00000000000..be93435453b --- /dev/null +++ b/app/models/concerns/expirable.rb @@ -0,0 +1,15 @@ +module Expirable + extend ActiveSupport::Concern + + included do + scope :expired, -> { where('expires_at <= ?', Time.current) } + end + + def expires? + expires_at.present? + end + + def expires_soon? + expires_at < 7.days.from_now + end +end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index cbae1cd439b..afb5ce37c06 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -131,7 +131,10 @@ module Issuable end def order_labels_priority(excluded_labels: []) - select("#{table_name}.*, (#{highest_label_priority(excluded_labels).to_sql}) AS highest_priority"). + condition_field = "#{table_name}.id" + highest_priority = highest_label_priority(name, condition_field, excluded_labels: excluded_labels).to_sql + + select("#{table_name}.*, (#{highest_priority}) AS highest_priority"). group(arel_table[:id]). reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')) end @@ -159,20 +162,6 @@ module Issuable grouping_columns end - - private - - def highest_label_priority(excluded_labels) - query = Label.select(Label.arel_table[:priority].minimum). - joins(:label_links). - where(label_links: { target_type: name }). - where("label_links.target_id = #{table_name}.id"). - reorder(nil) - - query.where.not(title: excluded_labels) if excluded_labels.present? - - query - end end def today? diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb index 4be6a2f621b..a881fb83b7f 100644 --- a/app/models/concerns/note_on_diff.rb +++ b/app/models/concerns/note_on_diff.rb @@ -17,6 +17,10 @@ module NoteOnDiff raise NotImplementedError end + def original_line_code + raise NotImplementedError + end + def diff_attributes raise NotImplementedError end diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index 8b47b9e0abd..1ebecd86af9 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -35,5 +35,19 @@ module Sortable all end end + + private + + def highest_label_priority(object_types, condition_field, excluded_labels: []) + query = Label.select(Label.arel_table[:priority].minimum). + joins(:label_links). + where(label_links: { target_type: object_types }). + where("label_links.target_id = #{condition_field}"). + reorder(nil) + + query.where.not(title: excluded_labels) if excluded_labels.present? + + query + end end end diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb index 5d4b0a86899..750f937b724 100644 --- a/app/models/concerns/statuseable.rb +++ b/app/models/concerns/statuseable.rb @@ -35,11 +35,6 @@ module Statuseable all.pluck(self.status_sql).first end - def duration - duration_array = all.map(&:duration).compact - duration_array.reduce(:+) - end - def started_at all.minimum(:started_at) end @@ -85,4 +80,14 @@ module Statuseable def complete? COMPLETED_STATUSES.include?(status) end + + private + + def calculate_duration + if started_at && finished_at + finished_at - started_at + elsif started_at + Time.now - started_at + end + end end diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 52215f6e2ae..0c23c1c1934 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -16,6 +16,9 @@ class DiffNote < Note after_initialize :ensure_original_discussion_id before_validation :set_original_position, :update_position, on: :create before_validation :set_line_code, :set_original_discussion_id + # We need to do this again, because it's already in `Note`, but is affected by + # `update_position` and needs to run after that. + before_validation :set_discussion_id after_save :keep_around_commits class << self @@ -57,6 +60,10 @@ class DiffNote < Note diff_file.position(line) == self.original_position end + def original_line_code + self.diff_file.line_code(self.diff_line) + end + def active?(diff_refs = nil) return false unless supported? return true if for_commit? diff --git a/app/models/discussion.rb b/app/models/discussion.rb index 3fddc084af2..9676bc03470 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -12,6 +12,7 @@ class Discussion :for_merge_request?, :line_code, + :original_line_code, :diff_file, :for_line?, :active?, diff --git a/app/models/group.rb b/app/models/group.rb index 37631b99701..c48869ae465 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -95,34 +95,40 @@ class Group < Namespace end end - def add_users(user_ids, access_level, current_user = nil) + def add_users(user_ids, access_level, current_user: nil, expires_at: nil) user_ids.each do |user_id| - Member.add_user(self.group_members, user_id, access_level, current_user) + Member.add_user( + self.group_members, + user_id, + access_level, + current_user: current_user, + expires_at: expires_at + ) end end - def add_user(user, access_level, current_user = nil) - add_users([user], access_level, current_user) + def add_user(user, access_level, current_user: nil, expires_at: nil) + add_users([user], access_level, current_user: current_user, expires_at: expires_at) end def add_guest(user, current_user = nil) - add_user(user, Gitlab::Access::GUEST, current_user) + add_user(user, Gitlab::Access::GUEST, current_user: current_user) end def add_reporter(user, current_user = nil) - add_user(user, Gitlab::Access::REPORTER, current_user) + add_user(user, Gitlab::Access::REPORTER, current_user: current_user) end def add_developer(user, current_user = nil) - add_user(user, Gitlab::Access::DEVELOPER, current_user) + add_user(user, Gitlab::Access::DEVELOPER, current_user: current_user) end def add_master(user, current_user = nil) - add_user(user, Gitlab::Access::MASTER, current_user) + add_user(user, Gitlab::Access::MASTER, current_user: current_user) end def add_owner(user, current_user = nil) - add_user(user, Gitlab::Access::OWNER, current_user) + add_user(user, Gitlab::Access::OWNER, current_user: current_user) end def has_owner?(user) diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb index 8e26cbe9835..40277a9b139 100644 --- a/app/models/legacy_diff_note.rb +++ b/app/models/legacy_diff_note.rb @@ -49,6 +49,10 @@ class LegacyDiffNote < Note !line.meta? && diff_file.line_code(line) == self.line_code end + def original_line_code + self.line_code + end + # Check if this note is part of an "active" discussion # # This will always return true for anything except MergeRequest noteables, diff --git a/app/models/member.rb b/app/models/member.rb index 24ab1276ee9..64e0d33fb20 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -1,6 +1,7 @@ class Member < ActiveRecord::Base include Sortable include Importable + include Expirable include Gitlab::Access attr_accessor :raw_invite_token @@ -73,7 +74,7 @@ class Member < ActiveRecord::Base user end - def add_user(members, user_id, access_level, current_user = nil) + def add_user(members, user_id, access_level, current_user: nil, expires_at: nil) user = user_for_id(user_id) # `user` can be either a User object or an email to be invited @@ -87,6 +88,7 @@ class Member < ActiveRecord::Base if can_update_member?(current_user, member) || project_creator?(member, access_level) member.created_by ||= current_user member.access_level = access_level + member.expires_at = expires_at member.save end diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 18e97c969d7..ec2d40eb11c 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -34,7 +34,7 @@ class ProjectMember < Member # :master # ) # - def add_users_to_projects(project_ids, user_ids, access, current_user = nil) + def add_users_to_projects(project_ids, user_ids, access, current_user: nil, expires_at: nil) access_level = if roles_hash.has_key?(access) roles_hash[access] elsif roles_hash.values.include?(access.to_i) @@ -50,7 +50,13 @@ class ProjectMember < Member project = Project.find(project_id) users.each do |user| - Member.add_user(project.project_members, user, access_level, current_user) + Member.add_user( + project.project_members, + user, + access_level, + current_user: current_user, + expires_at: expires_at + ) end end end diff --git a/app/models/note.rb b/app/models/note.rb index 3bbf5db0b70..f2656df028b 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -259,6 +259,8 @@ class Note < ActiveRecord::Base def ensure_discussion_id return unless self.persisted? + # Needed in case the SELECT statement doesn't ask for `discussion_id` + return unless self.has_attribute?(:discussion_id) return if self.discussion_id set_discussion_id diff --git a/app/models/project.rb b/app/models/project.rb index 043da030468..1855760e694 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -611,7 +611,10 @@ class Project < ActiveRecord::Base end def new_issue_address(author) - if Gitlab::IncomingEmail.enabled? && author + # This feature is disabled for the time being. + return nil + + if Gitlab::IncomingEmail.enabled? && author # rubocop:disable Lint/UnreachableCode Gitlab::IncomingEmail.reply_address( "#{path_with_namespace}+#{author.authentication_token}") end @@ -1003,8 +1006,8 @@ class Project < ActiveRecord::Base project_members.find_by(user_id: user) end - def add_user(user, access_level, current_user = nil) - team.add_user(user, access_level, current_user) + def add_user(user, access_level, current_user: nil, expires_at: nil) + team.add_user(user, access_level, current_user: current_user, expires_at: expires_at) end def default_branch diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb index e52a6bd7c84..7613cbdea93 100644 --- a/app/models/project_group_link.rb +++ b/app/models/project_group_link.rb @@ -1,4 +1,6 @@ class ProjectGroupLink < ActiveRecord::Base + include Expirable + GUEST = 10 REPORTER = 20 DEVELOPER = 30 @@ -26,7 +28,7 @@ class ProjectGroupLink < ActiveRecord::Base self.class.access_options.key(self.group_access) end - private + private def different_group if self.group && self.project && self.project.group == self.group diff --git a/app/models/project_team.rb b/app/models/project_team.rb index d0a714cd6fc..ab6ea2aae36 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -15,9 +15,9 @@ class ProjectTeam users, access, current_user = *args if users.respond_to?(:each) - add_users(users, access, current_user) + add_users(users, access, current_user: current_user) else - add_user(users, access, current_user) + add_user(users, access, current_user: current_user) end end @@ -33,17 +33,18 @@ class ProjectTeam member end - def add_users(users, access, current_user = nil) + def add_users(users, access, current_user: nil, expires_at: nil) ProjectMember.add_users_to_projects( [project.id], users, access, - current_user + current_user: current_user, + expires_at: expires_at ) end - def add_user(user, access, current_user = nil) - add_users([user], access, current_user) + def add_user(user, access, current_user: nil, expires_at: nil) + add_users([user], access, current_user: current_user, expires_at: expires_at) end # Remove all users from project team diff --git a/app/models/repository.rb b/app/models/repository.rb index 2494c266cd2..bdc3b9d1c1c 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -277,7 +277,7 @@ class Repository def cache_keys %i(size commit_count readme version contribution_guide changelog - license_blob license_key gitignore) + license_blob license_key gitignore koding_yml) end # Keys for data on branch/tag operations. @@ -553,6 +553,14 @@ class Repository end end + def koding_yml + return nil unless head_exists? + + cache.fetch(:koding_yml) do + file_on_head(/\A\.koding\.yml\z/) + end + end + def gitlab_ci_yml return nil unless head_exists? diff --git a/app/models/todo.rb b/app/models/todo.rb index 8d7a5965aa1..6ae9956ade5 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -1,4 +1,6 @@ class Todo < ActiveRecord::Base + include Sortable + ASSIGNED = 1 MENTIONED = 2 BUILD_FAILED = 3 @@ -41,6 +43,23 @@ class Todo < ActiveRecord::Base after_save :keep_around_commit + class << self + def sort(method) + method == "priority" ? order_by_labels_priority : order_by(method) + end + + # Order by priority depending on which issue/merge request the Todo belongs to + # Todos with highest priority first then oldest todos + # Need to order by created_at last because of differences on Mysql and Postgres when joining by type "Merge_request/Issue" + def order_by_labels_priority + highest_priority = highest_label_priority(["Issue", "MergeRequest"], "todos.target_id").to_sql + + select("#{table_name}.*, (#{highest_priority}) AS highest_priority"). + order(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')). + order('todos.created_at') + end + end + def build_failed? action == BUILD_FAILED end diff --git a/app/services/members/authorized_destroy_service.rb b/app/services/members/authorized_destroy_service.rb new file mode 100644 index 00000000000..ca9db59cac7 --- /dev/null +++ b/app/services/members/authorized_destroy_service.rb @@ -0,0 +1,19 @@ +module Members + class AuthorizedDestroyService < BaseService + attr_accessor :member, :user + + def initialize(member, user = nil) + @member, @user = member, user + end + + def execute + return false if member.is_a?(GroupMember) && member.source.last_owner?(member.user) + + member.destroy + + if member.request? && member.user != user + notification_service.decline_access_request(member) + end + end + end +end diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb index 9e3f6af628d..9a2bf82ef51 100644 --- a/app/services/members/destroy_service.rb +++ b/app/services/members/destroy_service.rb @@ -11,12 +11,7 @@ module Members unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member) raise Gitlab::Access::AccessDeniedError end - - member.destroy - - if member.request? && member.user != current_user - notification_service.decline_access_request(member) - end + AuthorizedDestroyService.new(member, current_user).execute end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 66a838b3d13..6139ed56e25 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -242,7 +242,6 @@ class NotificationService project_member.real_source_type, project_member.project.id, project_member.invite_email, - project_member.access_level, project_member.created_by_id ).deliver_later end @@ -269,7 +268,6 @@ class NotificationService group_member.real_source_type, group_member.group.id, group_member.invite_email, - group_member.access_level, group_member.created_by_id ).deliver_later end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index c7fd344eea2..e0878512e62 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -388,6 +388,25 @@ .help-block If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database. + %fieldset + %legend Koding + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :koding_enabled do + = f.check_box :koding_enabled + Enable Koding + .form-group + = f.label :koding_url, 'Koding URL', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_field :koding_url, class: 'form-control', placeholder: 'http://gitlab.your-koding-instance.com:8090' + .help-block + Koding has integration enabled out of the box for the + %strong gitlab + team, and you need to provide that team's URL here. Learn more in the + = succeed "." do + = link_to "Koding integration documentation", help_page_path("integration/koding") + .form-actions = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml index 352adbedee4..f29d9c94441 100644 --- a/app/views/admin/builds/_build.html.haml +++ b/app/views/admin/builds/_build.html.haml @@ -51,7 +51,7 @@ - if build.duration %p.duration = custom_icon("icon_timer") - = duration_in_numbers(build.finished_at, build.started_at) + = duration_in_numbers(build.duration) - if build.finished_at %p.finished-at diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 4e340b6ec16..d320d3bcc1e 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -43,6 +43,25 @@ class: 'select2 trigger-submit', include_blank: true, data: {placeholder: 'Action'}) + .pull-right + .dropdown.inline.prepend-left-10 + %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %span.light + - if @sort.present? + = sort_options_hash[@sort] + - else + = sort_title_recently_created + %b.caret + %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort + %li + = link_to todos_filter_path(sort: sort_value_priority) do + = sort_title_priority + = link_to todos_filter_path(sort: sort_value_recently_created) do + = sort_title_recently_created + = link_to todos_filter_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created + + .prepend-top-default - if @todos.any? .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} } diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index b2e55f7647a..3a95a652810 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -7,7 +7,7 @@ .diff-content.code.js-syntax-highlight %table - - discussions = { discussion.line_code => discussion } + - discussions = { discussion.original_line_code => discussion } = render partial: "projects/diffs/line", collection: discussion.truncated_diff_lines, as: :line, diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml index 9bb9f962177..2fb3190ab11 100644 --- a/app/views/groups/group_members/_new_group_member.html.haml +++ b/app/views/groups/group_members/_new_group_member.html.haml @@ -14,5 +14,14 @@ Read more about role permissions %strong= link_to "here", help_page_path("user/permissions"), class: "vlink" + .form-group + = f.label :expires_at, 'Access expiration date', class: 'control-label' + .col-sm-10 + .clearable-input + = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date' + %i.clear-icon.js-clear-input + .help-block + On this date, the user(s) will automatically lose access to this group and all of its projects. + .form-actions = f.submit 'Add users to group', class: "btn btn-create" diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml index da71de4cd1e..742f9d7a433 100644 --- a/app/views/groups/group_members/update.js.haml +++ b/app/views/groups/group_members/update.js.haml @@ -1,2 +1,3 @@ :plain $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}'); + new MemberExpirationDate(); diff --git a/app/views/koding/index.html.haml b/app/views/koding/index.html.haml new file mode 100644 index 00000000000..111cc67336c --- /dev/null +++ b/app/views/koding/index.html.haml @@ -0,0 +1,9 @@ +.row-content-block.second-block.center + %p + = icon('circle', class: 'cgreen') + Integration is active for + = link_to koding_project_url, target: '_blank' do + #{current_application_settings.koding_url} + +.documentation.wiki + = markdown @markdown diff --git a/app/views/layouts/koding.html.haml b/app/views/layouts/koding.html.haml new file mode 100644 index 00000000000..22319bba745 --- /dev/null +++ b/app/views/layouts/koding.html.haml @@ -0,0 +1,5 @@ +- page_title "Koding" +- page_description "Koding Dashboard" +- header_title "Koding", koding_path + += render template: "layouts/application" diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 3a14751ea8e..67f558c854b 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -12,6 +12,11 @@ = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do %span Activity + - if koding_enabled? + = nav_link(controller: :koding) do + = link_to koding_path, title: 'Koding' do + %span + Koding = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = link_to dashboard_groups_path, title: 'Groups' do %span diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 1d3b8fc3683..f7012595a5a 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -65,7 +65,7 @@ Graphs - if project_nav_tab? :issues - = nav_link(controller: [:issues, :labels, :milestones]) do + = nav_link(controller: [:issues, :labels, :milestones, :boards]) do = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do %span Issues diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 7b0621f9401..680e95ac6b5 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -1,4 +1,7 @@ - page_title "Edit", @blob.path, @ref +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/ace.js') + = page_specific_javascript_tag('blob_edit/blob_edit_bundle.js') - if @conflict .alert.alert-danger @@ -16,14 +19,10 @@ = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do = editing_preview_title(@blob.name) - = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form') do + = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" = hidden_field_tag 'last_commit_sha', @last_commit_sha = hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id) - -:javascript - blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}") - new NewCommitForm($('.js-edit-blob-form')) diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index c952bc7e5db..b6ed9518c48 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -1,17 +1,16 @@ - page_title "New File", @path.presence, @ref +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/ace.js') + = page_specific_javascript_tag('blob_edit/blob_edit_bundle.js') %h3.page-title New File .file-editor - = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-new-blob-form js-quick-submit js-requires-input') do + = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do = render 'projects/blob/editor', ref: @ref = render 'shared/new_commit_form', placeholder: "Add new file" = hidden_field_tag 'content', '', id: 'file-content' = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_tree_path(@project.namespace, @project, @id) - -:javascript - blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}") - new NewCommitForm($('.js-new-blob-form')) diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index f8ebf397ee2..de53a298f84 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -11,7 +11,6 @@ .board-inner %header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" } %h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" } - = icon("align-justify", class: "board-mobile-handle js-board-drag-handle", "v-if" => "(!disabled && !list.preset)") {{ list.title }} %span.pull-right{ "v-if" => "list.type !== 'blank'" } {{ list.issues.length }} diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml index b20c23f6b8e..e8b60b54d80 100644 --- a/app/views/projects/boards/components/_card.html.haml +++ b/app/views/projects/boards/components/_card.html.haml @@ -9,7 +9,6 @@ "track-by" => "id" } %li.card{ ":class" => "{ 'user-can-drag': !disabled }", ":index" => "index" } - = icon("align-justify", class: "board-mobile-handle js-card-drag-handle", "v-if" => "!disabled") %h4.card-title = icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential") %a{ ":href" => "issueLinkBase + '/' + issue.id", diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 4bd85061240..6192ccb710b 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -5,8 +5,8 @@ - number_commits_ahead = diverging_commit_counts[:ahead] %li(class="js-branch-#{branch.name}") %div - = link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do - %span.item-title.str-truncated= branch.name + = link_to namespace_project_tree_path(@project.namespace, @project, branch.name), class: 'item-title str-truncated' do + = branch.name - if branch.name == @repository.root_ref %span.label.label-primary default diff --git a/app/views/projects/buttons/_koding.html.haml b/app/views/projects/buttons/_koding.html.haml new file mode 100644 index 00000000000..fdc80d44253 --- /dev/null +++ b/app/views/projects/buttons/_koding.html.haml @@ -0,0 +1,7 @@ +- if koding_enabled? && current_user && can_push_branch?(@project, @project.default_branch) + - if @repository.koding_yml + = link_to koding_project_url(@project), class: 'btn', target: '_blank' do + Run in IDE (Koding) + - else + = link_to add_koding_stack_path(@project), class: 'btn' do + Set Up Koding diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 91081435220..1fdf32466f2 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -63,7 +63,7 @@ - if build.duration %p.duration = custom_icon("icon_timer") - = duration_in_numbers(build.finished_at, build.started_at) + = duration_in_numbers(build.duration) - if build.finished_at %p.finished-at = icon("calendar") diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index be387201f8d..9a672b23341 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -48,10 +48,10 @@ \- %td - - if pipeline.started_at && pipeline.finished_at + - if pipeline.duration %p.duration = custom_icon("icon_timer") - = duration_in_numbers(pipeline.finished_at, pipeline.started_at) + = duration_in_numbers(pipeline.duration) - if pipeline.finished_at %p.finished-at = icon("calendar") diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index d9b800a4ded..e4cd55b9f7a 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -17,7 +17,9 @@ .form-group.branch = label_tag 'target_branch', target_label, class: 'control-label' .col-sm-10 - = select_tag "target_branch", project_branches, class: "select2 select2-sm js-target-branch" + = hidden_field_tag :target_branch, @project.default_branch, id: 'target_branch' + = dropdown_tag(@project.default_branch, options: { title: "Switch branch", filter: true, placeholder: "Search branches", toggle_class: 'js-project-refs-dropdown js-target-branch dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "target_branch", selected: @project.default_branch, target_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false }}) + - if can?(current_user, :push_code, @project) .js-create-merge-request-container .checkbox diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 3ad866bb2f1..29d767e7769 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -56,10 +56,10 @@ = 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 = ci_icon_for_status(@commit.status) - = ci_label_for_status(@commit.status) - - if @commit.pipelines.duration - in - = time_interval_in_words @commit.pipelines.duration + %span.ci-status-label + = ci_label_for_status(@commit.status) + in + = time_interval_in_words @commit.pipelines.total_duration .commit-box.content-block %h3.commit-title diff --git a/app/views/projects/group_links/index.html.haml b/app/views/projects/group_links/index.html.haml index 2b904544f28..ca700cb3a3b 100644 --- a/app/views/projects/group_links/index.html.haml +++ b/app/views/projects/group_links/index.html.haml @@ -17,6 +17,13 @@ .select-wrapper = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control" %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" .col-lg-9.col-lg-offset-3 %hr @@ -35,6 +42,10 @@ = group.name %br 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 = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: "btn btn-transparent" do %span.sr-only disable sharing diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 9f1a046ea74..3fb4191c60e 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -22,7 +22,7 @@ - if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue) .issuable-actions .clearfix.issue-btn-group.dropdown - %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } } + %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } %span.caret Options .dropdown-menu.dropdown-menu-align-right.hidden-lg diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index f8025fc1dbe..9d8b4cc56be 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -16,6 +16,9 @@ - if @merge_request.open? .pull-right - 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 Check out branch diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index b24bdf22ceb..098ce19da21 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -14,7 +14,7 @@ - if can?(current_user, :update_merge_request, @merge_request) .issuable-actions .clearfix.issue-btn-group.dropdown - %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } } + %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } %span.caret Options .dropdown-menu.dropdown-menu-align-right.hidden-lg diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index 8289aefcde7..063e83a407a 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -9,7 +9,7 @@ = link_to @pipeline.ref, namespace_project_commits_path(@project.namespace, @project, @pipeline.ref), class: "monospace" - if @pipeline.duration in - = time_interval_in_words @pipeline.duration + = time_interval_in_words(@pipeline.duration) .pull-right = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), class: "ci-status ci-#{@pipeline.status}" do diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index 978c4dfc5ec..fa8cbf71733 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -14,5 +14,14 @@ Read more about role permissions %strong= link_to "here", help_page_path("user/permissions"), class: "vlink" + .form-group + = f.label :expires_at, 'Access expiration date', class: 'control-label' + .col-sm-10 + .clearable-input + = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date' + %i.clear-icon.js-clear-input + .help-block + On this date, the user(s) will automatically lose access to this project. + .form-actions = f.submit 'Add users to project', class: "btn btn-create" diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 9031f01b496..9d063b3081f 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,6 +1,6 @@ - page_title "Members" -.project-members-page.prepend-top-default +.project-members-page.js-project-members-page.prepend-top-default - if can?(current_user, :admin_project_member, @project) .panel.panel-default .panel-heading diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml index 45f8ef89060..833954bc039 100644 --- a/app/views/projects/project_members/update.js.haml +++ b/app/views/projects/project_members/update.js.haml @@ -1,2 +1,3 @@ :plain $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @project_member))}'); + new MemberExpirationDate(); diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml index d4c6fa24768..e95a3b1b4c3 100644 --- a/app/views/projects/protected_branches/_create_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml @@ -22,16 +22,20 @@ %label.col-md-2.text-right{ for: 'merge_access_levels_attributes' } Allowed to merge: .col-md-10 - = dropdown_tag('Select', - options: { toggle_class: 'js-allowed-to-merge wide', - data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }}) + .merge_access_levels-container + = dropdown_tag('Select', + options: { toggle_class: 'js-allowed-to-merge wide', + dropdown_class: 'dropdown-menu-selectable', + data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }}) .form-group %label.col-md-2.text-right{ for: 'push_access_levels_attributes' } Allowed to push: .col-md-10 - = dropdown_tag('Select', - options: { toggle_class: 'js-allowed-to-push wide', - data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }}) + .push_access_levels-container + = dropdown_tag('Select', + options: { toggle_class: 'js-allowed-to-push wide', + dropdown_class: 'dropdown-menu-selectable', + data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }}) .panel-footer = f.submit 'Protect', class: 'btn-create btn', disabled: true diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index 835398b6f98..33d5cbff420 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -1,18 +1,20 @@ +- @no_container = true - page_title "Edit", @tag.name, "Tags" = render "projects/commits/head" -.row-content-block - .oneline - .title - Release notes for tag - %strong #{@tag.name} +%div{ class: container_class } + .sub-header-block.no-bottom-space + .oneline + .title + Release notes for tag + %strong #{@tag.name} + -.prepend-top-default = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f| = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..." = render 'projects/notes/hints' .error-alert - .form-actions.prepend-top-default + .prepend-top-default = f.submit 'Save changes', class: 'btn btn-save' = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel" diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index a666d07e9eb..340e159c874 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -64,10 +64,12 @@ %li.missing = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do Set Up CI + %li.project-repo-buttons-right .project-repo-buttons.project-right-buttons - if current_user = render 'shared/members/access_request_buttons', source: @project + = render "projects/buttons/koding" .btn-group.project-repo-btn-group = render "projects/buttons/download" @@ -86,4 +88,4 @@ Archived project! Repository is read-only %div{class: "project-show-#{default_project_view}"} - = render default_project_view
\ No newline at end of file + = render default_project_view diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml index ea7162d4d63..9a8252ab087 100644 --- a/app/views/shared/_ref_switcher.html.haml +++ b/app/views/shared/_ref_switcher.html.haml @@ -6,7 +6,7 @@ - @options && @options.each do |key, value| = hidden_field_tag key, value, id: nil .dropdown - = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project) }, { toggle_class: "js-project-refs-dropdown" } + = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown" } .dropdown-menu.dropdown-menu-selectable{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) } = dropdown_title "Switch branch/tag" = dropdown_filter "Search branches and tags" diff --git a/app/views/shared/icons/_icon_play.svg b/app/views/shared/icons/_icon_play.svg new file mode 100644 index 00000000000..80a6d41dbf6 --- /dev/null +++ b/app/views/shared/icons/_icon_play.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 11"><path fill-rule="evenodd" d="m9.283 6.47l-7.564 4.254c-.949.534-1.719.266-1.719-.576v-9.292c0-.852.756-1.117 1.719-.576l7.564 4.254c.949.534.963 1.392 0 1.934"/></svg>
\ No newline at end of file diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index d717c3d92ee..544ed6203aa 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -121,7 +121,7 @@ = label_tag :move_to_project_id, 'Move', class: 'control-label' .col-sm-10 .issuable-form-select-holder - = hidden_field_tag :move_to_project_id, nil, class: 'js-move-dropdown', data: { placeholder: 'Select project', projects_url: autocomplete_projects_path(project_id: @project.id) } + = hidden_field_tag :move_to_project_id, nil, class: 'js-move-dropdown', data: { placeholder: 'Select project', projects_url: autocomplete_projects_path(project_id: @project.id), page_size: MoveToProjectFinder::PAGE_SIZE } %span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default', title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' } diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 8e2fcbdfab8..c1b50e65af5 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -109,7 +109,7 @@ - if issuable.project.labels.any? .block.labels - .sidebar-collapsed-icon + .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } } = icon('tags') %span = issuable.labels_array.size diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index fc6e206d082..5f20e4bd42a 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -16,7 +16,7 @@ = button_tag icon('pencil'), type: 'button', class: 'btn inline js-toggle-button', - title: 'Edit access level' + title: 'Edit' - if member.request? = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), @@ -59,6 +59,10 @@ = time_ago_with_tooltip(member.requested_at) - else Joined #{time_ago_with_tooltip(member.created_at)} + - if member.expires? + · + %span{ class: ('text-warning' if member.expires_soon?) } + Expires in #{distance_of_time_in_words_to_now(member.expires_at)} - else = image_tag avatar_icon(member.invite_email, 40), class: "avatar s40", alt: '' @@ -73,8 +77,16 @@ - if show_roles .edit-member.hide.js-toggle-content %br - = form_for member, remote: true do |f| - .prepend-top-10 - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control' + = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| + .form-group + = label_tag "member_access_level_#{member.id}", 'Project access', class: 'control-label' + .col-sm-10 + = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control', id: "member_access_level_#{member.id}" + .form-group + = label_tag "member_expires_at_#{member.id}", 'Access expiration date', class: 'control-label' + .col-sm-10 + .clearable-input + = f.text_field :expires_at, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date', id: "member_expires_at_#{member.id}" + %i.clear-icon.js-clear-input .prepend-top-10 = f.submit 'Save', class: 'btn btn-save btn-sm' diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index 47ec09f62c6..0c788032020 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -1,3 +1,7 @@ +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/ace.js') + = page_specific_javascript_tag('snippet/snippet_bundle.js') + .snippet-form-holder = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f| = form_errors(@snippet) @@ -31,8 +35,3 @@ - else = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel" -:javascript - var editor = ace.edit("editor"); - $(".snippet-form-holder form").submit(function(){ - $(".snippet-file-content").val(editor.getValue()); - }); diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index c6a5af2809a..1dc7e0adef7 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -33,13 +33,13 @@ class EmailsOnPushWorker reverse_compare = false if action == :push - compare = CompareService.new.execute(project, before_sha, project, after_sha) + compare = CompareService.new.execute(project, after_sha, project, before_sha) diff_refs = compare.diff_refs return false if compare.same if compare.commits.empty? - compare = CompareService.new.execute(project, after_sha, project, before_sha) + compare = CompareService.new.execute(project, before_sha, project, after_sha) diff_refs = compare.diff_refs reverse_compare = true diff --git a/app/workers/remove_expired_group_links_worker.rb b/app/workers/remove_expired_group_links_worker.rb new file mode 100644 index 00000000000..246c8b6650a --- /dev/null +++ b/app/workers/remove_expired_group_links_worker.rb @@ -0,0 +1,7 @@ +class RemoveExpiredGroupLinksWorker + include Sidekiq::Worker + + def perform + ProjectGroupLink.expired.destroy_all + end +end diff --git a/app/workers/remove_expired_members_worker.rb b/app/workers/remove_expired_members_worker.rb new file mode 100644 index 00000000000..cf765af97ce --- /dev/null +++ b/app/workers/remove_expired_members_worker.rb @@ -0,0 +1,13 @@ +class RemoveExpiredMembersWorker + include Sidekiq::Worker + + def perform + Member.expired.find_each do |member| + begin + Members::AuthorizedDestroyService.new(member).execute + rescue => ex + logger.error("Expired Member ID=#{member.id} cannot be removed - #{ex}") + end + end + end +end |