diff options
author | Lin Jen-Shin <godfat@godfat.org> | 2016-10-04 02:38:25 +0800 |
---|---|---|
committer | Lin Jen-Shin <godfat@godfat.org> | 2016-10-04 02:38:25 +0800 |
commit | f39ba1bb5ed9c2421e60a618f71373c5d8dc94e9 (patch) | |
tree | e5ad67c48bdc2d520b84d735225ff7eac4c7da84 /app | |
parent | db6b2b18990297d98bd74af1d2f475d0d42ec443 (diff) | |
parent | a1aea3266e4b90869d5a9bcc334272996ab80fda (diff) | |
download | gitlab-ce-f39ba1bb5ed9c2421e60a618f71373c5d8dc94e9.tar.gz |
Merge remote-tracking branch 'upstream/master' into pipeline-emails
* upstream/master: (372 commits)
Enable Lint/StringConversionInInterpolation cop and autocorrect offenses
resolve duplicated changelog entry
credit myself :smile:
change determine conditions
override subject method in devise mailer
follow the styleguide: Don't use parentheses around a literal
wrap subject with method subject
move spec back into shared example `an email sent from GitLab`
stub config settings in spec
remove empty line at block body end
remove extra entry
create new test in `spec/mailers/notify_spec.rb`
move changelog to 8.13
add configurable email subject suffix
Fixes sidebar navigation.
Convert "SSH Keys" Spinach features to RSpec
Enable import/export back for non-admins
Update gitlab-shell to 3.6.3
Updated artwork of empty group state.
Better empty state for Groups view.
...
Diffstat (limited to 'app')
197 files changed, 1699 insertions, 971 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 6df2ecf57a2..1cd2302111e 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -16,9 +16,6 @@ .replace(':id', group_id); return $.ajax({ url: url, - data: { - private_token: gon.api_token - }, dataType: "json" }).done(function(group) { return callback(group); @@ -31,7 +28,6 @@ return $.ajax({ url: url, data: { - private_token: gon.api_token, search: query, per_page: 20 }, @@ -46,7 +42,6 @@ return $.ajax({ url: url, data: { - private_token: gon.api_token, search: query, per_page: 20 }, @@ -61,7 +56,6 @@ return $.ajax({ url: url, data: { - private_token: gon.api_token, search: query, order_by: order, per_page: 20 @@ -74,7 +68,6 @@ newLabel: function(project_id, data, callback) { var url = Api.buildUrl(Api.labelsPath) .replace(':id', project_id); - data.private_token = gon.api_token; return $.ajax({ url: url, type: "POST", @@ -93,7 +86,6 @@ return $.ajax({ url: url, data: { - private_token: gon.api_token, search: query, per_page: 20 }, diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index c029bf3b5ca..8a61669822c 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -247,7 +247,7 @@ $this.toggleClass('active'); var notesHolders = $this.closest('.diff-file').find('.notes_holder'); if ($this.hasClass('active')) { - notesHolders.show(); + notesHolders.show().find('.hide').show(); } else { notesHolders.hide(); } diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 0decc6d09e6..44af1c135a0 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -357,7 +357,7 @@ $('ul.emoji-menu-search, h5.emoji-search').remove(); if (term) { // Generate a search result block - h5 = $('<h5>').text('Search results'); + h5 = $('<h5 class="emoji-search" />').text('Search results'); found_emojis = _this.searchEmojis(term).show(); ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis); $('.emoji-menu-content ul, .emoji-menu-content h5').hide(); diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index b846bab0424..de6cdd851be 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -22,6 +22,7 @@ // submitted textarea })(this)); this.initModePanesAndLinks(); + this.initSoftWrap(); new BlobLicenseSelectors({ editor: this.editor }); @@ -50,6 +51,7 @@ this.$editModePanes.hide(); currentPane.fadeIn(200); if (paneId === "#preview") { + this.$toggleButton.hide(); return $.post(currentLink.data("preview-url"), { content: this.editor.getValue() }, function(response) { @@ -57,10 +59,23 @@ return currentPane.syntaxHighlight(); }); } else { + this.$toggleButton.show(); return this.editor.focus(); } }; + EditBlob.prototype.initSoftWrap = function() { + this.isSoftWrapped = false; + this.$toggleButton = $('.soft-wrap-toggle'); + this.$toggleButton.on('click', this.toggleSoftWrap.bind(this)); + }; + + EditBlob.prototype.toggleSoftWrap = function(e) { + this.isSoftWrapped = !this.isSoftWrapped; + this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped); + this.editor.getSession().setUseWrapMode(this.isSoftWrapped); + }; + return EditBlob; })(); diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js index 4e3a28cd163..294d2c9052c 100644 --- a/app/assets/javascripts/compare_autocomplete.js +++ b/app/assets/javascripts/compare_autocomplete.js @@ -23,8 +23,9 @@ selectable: true, filterable: true, filterByText: true, - fieldName: $dropdown.attr('name'), - filterInput: 'input[type="text"]', + toggleLabel: true, + fieldName: $dropdown.data('field-name'), + filterInput: 'input[type="search"]', renderRow: function(ref) { var link; if (ref.header != null) { diff --git a/app/assets/javascripts/cycle-analytics.js.es6 b/app/assets/javascripts/cycle-analytics.js.es6 index afaed7c4f60..cd9886ba58d 100644 --- a/app/assets/javascripts/cycle-analytics.js.es6 +++ b/app/assets/javascripts/cycle-analytics.js.es6 @@ -1,17 +1,22 @@ ((global) => { const COOKIE_NAME = 'cycle_analytics_help_dismissed'; + const store = gl.cycleAnalyticsStore = { + isLoading: true, + hasError: false, + isHelpDismissed: $.cookie(COOKIE_NAME), + analytics: {} + }; gl.CycleAnalytics = class CycleAnalytics { constructor() { const that = this; - this.isHelpDismissed = $.cookie(COOKIE_NAME); this.vue = new Vue({ el: '#cycle-analytics', name: 'CycleAnalytics', created: this.fetchData(), - data: this.decorateData({ isLoading: true }), + data: store, methods: { dismissLanding() { that.dismissLanding(); @@ -21,6 +26,7 @@ } fetchData(options) { + store.isLoading = true; options = options || { startDate: 30 }; $.ajax({ @@ -30,22 +36,20 @@ contentType: 'application/json', data: { start_date: options.startDate } }).done((data) => { - this.vue.$data = this.decorateData(data); + this.decorateData(data); this.initDropdown(); }) .error((data) => { this.handleError(data); }) .always(() => { - this.vue.isLoading = false; + store.isLoading = false; }) } decorateData(data) { data.summary = data.summary || []; data.stats = data.stats || []; - data.isHelpDismissed = this.isHelpDismissed; - data.isLoading = data.isLoading || false; data.summary.forEach((item) => { item.value = item.value || '-'; @@ -53,23 +57,21 @@ data.stats.forEach((item) => { item.value = item.value || '- - -'; - }) + }); - return data; + store.analytics = data; } handleError(data) { - this.vue.$data = { - hasError: true, - isHelpDismissed: this.isHelpDismissed - }; - + store.hasError = true; new Flash('There was an error while fetching cycle analytics data.', 'alert'); } dismissLanding() { - this.vue.isHelpDismissed = true; - $.cookie(COOKIE_NAME, true); + store.isHelpDismissed = true; + $.cookie(COOKIE_NAME, true, { + path: gon.relative_url_root || '/' + }); } initDropdown() { @@ -82,7 +84,6 @@ const value = $target.data('value'); $label.text($target.text().trim()); - this.vue.isLoading = true; this.fetchData({ startDate: value }); }) } diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 index be6ebc77947..cdedfd1af15 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 +++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 @@ -1,13 +1,9 @@ ((w) => { w.ResolveBtn = Vue.extend({ - mixins: [ - ButtonMixins - ], props: { noteId: Number, discussionId: String, resolved: Boolean, - namespacePath: String, projectPath: String, canResolve: Boolean, resolvedBy: String @@ -69,10 +65,10 @@ if (this.isResolved) { promise = ResolveService - .unresolve(this.namespace, this.noteId); + .unresolve(this.projectPath, this.noteId); } else { promise = ResolveService - .resolve(this.namespace, this.noteId); + .resolve(this.projectPath, this.noteId); } promise.then((response) => { diff --git a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6 index e373b06b1eb..0a617034502 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6 +++ b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6 @@ -1,12 +1,8 @@ ((w) => { w.ResolveDiscussionBtn = Vue.extend({ - mixins: [ - ButtonMixins - ], props: { discussionId: String, mergeRequestId: Number, - namespacePath: String, projectPath: String, canResolve: Boolean, }, @@ -50,7 +46,7 @@ }, methods: { resolve: function () { - ResolveService.toggleResolveForDiscussion(this.namespace, this.mergeRequestId, this.discussionId); + ResolveService.toggleResolveForDiscussion(this.projectPath, this.mergeRequestId, this.discussionId); } }, created: function () { diff --git a/app/assets/javascripts/diff_notes/mixins/namespace.js.es6 b/app/assets/javascripts/diff_notes/mixins/namespace.js.es6 deleted file mode 100644 index d278678085b..00000000000 --- a/app/assets/javascripts/diff_notes/mixins/namespace.js.es6 +++ /dev/null @@ -1,9 +0,0 @@ -((w) => { - w.ButtonMixins = { - computed: { - namespace: function () { - return `${this.namespacePath}/${this.projectPath}`; - } - } - }; -})(window); diff --git a/app/assets/javascripts/diff_notes/services/resolve.js.es6 b/app/assets/javascripts/diff_notes/services/resolve.js.es6 index de771ff814b..2a55f739b31 100644 --- a/app/assets/javascripts/diff_notes/services/resolve.js.es6 +++ b/app/assets/javascripts/diff_notes/services/resolve.js.es6 @@ -9,32 +9,32 @@ Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken(); } - prepareRequest(namespace) { + prepareRequest(root) { this.setCSRF(); - Vue.http.options.root = `/${namespace}`; + Vue.http.options.root = root; } - resolve(namespace, noteId) { - this.prepareRequest(namespace); + resolve(projectPath, noteId) { + this.prepareRequest(projectPath); return this.noteResource.save({ noteId }, {}); } - unresolve(namespace, noteId) { - this.prepareRequest(namespace); + unresolve(projectPath, noteId) { + this.prepareRequest(projectPath); return this.noteResource.delete({ noteId }, {}); } - toggleResolveForDiscussion(namespace, mergeRequestId, discussionId) { + toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId) { const discussion = CommentsStore.state[discussionId], isResolved = discussion.isResolved(); let promise; if (isResolved) { - promise = this.unResolveAll(namespace, mergeRequestId, discussionId); + promise = this.unResolveAll(projectPath, mergeRequestId, discussionId); } else { - promise = this.resolveAll(namespace, mergeRequestId, discussionId); + promise = this.resolveAll(projectPath, mergeRequestId, discussionId); } promise.then((response) => { @@ -57,10 +57,10 @@ }) } - resolveAll(namespace, mergeRequestId, discussionId) { + resolveAll(projectPath, mergeRequestId, discussionId) { const discussion = CommentsStore.state[discussionId]; - this.prepareRequest(namespace); + this.prepareRequest(projectPath); discussion.loading = true; @@ -70,10 +70,10 @@ }, {}); } - unResolveAll(namespace, mergeRequestId, discussionId) { + unResolveAll(projectPath, mergeRequestId, discussionId) { const discussion = CommentsStore.state[discussionId]; - this.prepareRequest(namespace); + this.prepareRequest(projectPath); discussion.loading = true; diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index c05cda25bbd..1b6db641200 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -608,27 +608,28 @@ } } field = []; - 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); } else if(value) { field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']"); } - if (field.length && el.hasClass(ACTIVE_CLASS)) { + if (el.hasClass(ACTIVE_CLASS)) { el.removeClass(ACTIVE_CLASS); - if (isInput) { - field.val(''); - } else { - field.remove(); + if (field && field.length) { + if (isInput) { + field.val(''); + } else { + field.remove(); + } } } else if (el.hasClass(INDETERMINATE_CLASS)) { el.addClass(ACTIVE_CLASS); el.removeClass(INDETERMINATE_CLASS); - if (field.length && value == null) { + if (field && field.length && value == null) { field.remove(); } - if (!field.length && fieldName) { + if ((!field || !field.length) && fieldName) { this.addInput(fieldName, value, selectedObject); } } else { @@ -638,15 +639,15 @@ this.dropdown.parent().find("input[name='" + fieldName + "']").remove(); } } - if (field.length && value == null) { + if (field && field.length && value == null) { field.remove(); } // Toggle active class for the tick mark el.addClass(ACTIVE_CLASS); if (value != null) { - if (!field.length && fieldName) { + if ((!field || !field.length) && fieldName) { this.addInput(fieldName, value, selectedObject); - } else if (field.length) { + } else if (field && field.length) { field.val(value).trigger('change'); } } @@ -796,4 +797,4 @@ }); }; -}).call(this);
\ No newline at end of file +}).call(this); diff --git a/app/assets/javascripts/issuable.js.es6 b/app/assets/javascripts/issuable.js.es6 index 81d89a48227..73e2664e9c0 100644 --- a/app/assets/javascripts/issuable.js.es6 +++ b/app/assets/javascripts/issuable.js.es6 @@ -15,25 +15,32 @@ return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>'); }, initSearch: function() { - this.timer = null; - return $('#issuable_search').off('keyup').on('keyup', function() { - clearTimeout(this.timer); - return this.timer = setTimeout(function() { - var $form, $input, $search; - $search = $('#issuable_search'); - $form = $('.js-filter-form'); - $input = $("input[name='" + ($search.attr('name')) + "']", $form); - if ($input.length === 0) { - $form.append("<input type='hidden' name='" + ($search.attr('name')) + "' value='" + (_.escape($search.val())) + "'/>"); - } else { - $input.val($search.val()); - } - if ($search.val() !== '') { - return Issuable.filterResults($form); - } - }, 500); + // `immediate` param set to false debounces on the `trailing` edge, lets user finish typing + const debouncedExecSearch = _.debounce(Issuable.executeSearch, 500, false); + + $('#issuable_search').off('keyup').on('keyup', debouncedExecSearch); + + // ensures existing filters are preserved when manually submitted + $('#issue_search_form').on('submit', (e) => { + e.preventDefault(); + debouncedExecSearch(e); }); }, + executeSearch: function(e) { + const $search = $('#issuable_search'); + const $searchName = $search.attr('name'); + const $searchValue = $search.val(); + const $filtersForm = $('.js-filter-form'); + const $input = $(`input[name='${$searchName}']`, $filtersForm); + + if (!$input.length) { + $filtersForm.append(`<input type='hidden' name='${$searchName}' value='${_.escape($searchValue)}'/>`); + } else { + $input.val($searchValue); + } + + Issuable.filterResults($filtersForm); + }, initLabelFilterRemove: function() { return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) { var $button; diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 3f15a117ca8..ce79e2e348a 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, $sidebarLabelTooltip; + var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected; $dropdown = $(dropdown); projectId = $dropdown.data('project-id'); labelUrl = $dropdown.data('labels'); @@ -24,6 +24,11 @@ $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip'); $value = $block.find('.value'); $loading = $block.find('.block-loading').fadeOut(); + initialSelected = $selectbox + .find('input[name="' + $dropdown.data('field-name') + '"]') + .map(function () { + return this.value; + }).get(); if (issueUpdateURL != null) { issueURLSplit = issueUpdateURL.split('/'); } @@ -43,6 +48,10 @@ selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").map(function() { return this.value; }).get(); + + if (_.isEqual(initialSelected, selected)) return; + initialSelected = selected; + data = {}; data[abilityName] = {}; data[abilityName].label_ids = selected; @@ -280,12 +289,12 @@ if (page === 'projects:boards:show') { if (label.isAny) { gl.issueBoards.BoardsStore.state.filters['label_name'] = []; - } else if (label.title) { + } else if ($el.hasClass('is-active')) { gl.issueBoards.BoardsStore.state.filters['label_name'].push(label.title); } else { var filters = gl.issueBoards.BoardsStore.state.filters['label_name']; - filters = filters.filter(function (label) { - return label !== $el.text().trim(); + filters = filters.filter(function (filteredLabel) { + return filteredLabel !== label.title; }); gl.issueBoards.BoardsStore.state.filters['label_name'] = filters; } diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index f84a20cf0fe..b8d52becb3f 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -19,7 +19,7 @@ while (i < sURLVariables.length) { sParameterName = sURLVariables[i].split('='); if (sParameterName[0] === sParam) { - values.push(sParameterName[1]); + values.push(sParameterName[1].replace(/\+/g, ' ')); } i++; } diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index c6854f703fb..866a04d3e21 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -432,14 +432,12 @@ var $form = $(xhr.target); if ($form.attr('data-resolve-all') != null) { - var namespacePath = $form.attr('data-namespace-path'), - projectPath = $form.attr('data-project-path') - discussionId = $form.attr('data-discussion-id'), - mergeRequestId = $form.attr('data-noteable-iid'), - namespace = namespacePath + '/' + projectPath; + var projectPath = $form.data('project-path') + discussionId = $form.data('discussion-id'), + mergeRequestId = $form.data('noteable-iid'); if (ResolveService != null) { - ResolveService.toggleResolveForDiscussion(namespace, mergeRequestId, discussionId); + ResolveService.toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId); } } @@ -854,7 +852,6 @@ .closest('form') .attr('data-discussion-id', discussionId) .attr('data-resolve-all', 'true') - .attr('data-namespace-path', $this.attr('data-namespace-path')) .attr('data-project-path', $this.attr('data-project-path')); }; diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index 5bf900f3e1d..8e38ccf7e44 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -7,7 +7,6 @@ function ProjectFindFile(element1, options) { this.element = element1; this.options = options; - this.goToBlob = bind(this.goToBlob, this); this.goToTree = bind(this.goToTree, this); this.selectRowDown = bind(this.selectRowDown, this); this.selectRowUp = bind(this.selectRowUp, this); @@ -36,16 +35,6 @@ } }; })(this)); - return this.element.find(".tree-content-holder .tree-table").on("click", function(event) { - var path; - if (event.target.nodeName !== "A") { - path = this.element.find(".tree-item-file-name a", this).attr("href"); - if (path) { - return location.href = path; - } - } - }); - // init event }; ProjectFindFile.prototype.findFile = function() { @@ -121,11 +110,12 @@ // make tbody row html ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) { var $tr; - $tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>"); + $tr = $("<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>"); if (matches) { $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl)); } else { - $tr.find("a").attr("href", blobItemUrl).text(filePath); + $tr.find("a").attr("href", blobItemUrl); + $tr.find(".str-truncated").text(filePath); } return $tr; }; @@ -164,14 +154,6 @@ return location.href = this.options.treeUrl; }; - ProjectFindFile.prototype.goToBlob = function() { - var path; - path = this.element.find(".tree-item.selected .tree-item-file-name a").attr("href"); - if (path) { - return location.href = path; - } - }; - return ProjectFindFile; })(); diff --git a/app/assets/javascripts/protected_branch_edit.js.es6 b/app/assets/javascripts/protected_branch_edit.js.es6 index 40bc4adb71b..15a6dca2875 100644 --- a/app/assets/javascripts/protected_branch_edit.js.es6 +++ b/app/assets/javascripts/protected_branch_edit.js.es6 @@ -40,7 +40,6 @@ dataType: 'json', data: { _method: 'PATCH', - id: this.$wrap.data('banchId'), protected_branch: { merge_access_levels_attributes: [{ id: this.$allowedToMergeDropdown.data('access-level-id'), diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 8abb09c626f..678d836f56f 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -389,4 +389,41 @@ })(); + $(function() { + var $projectOptionsDataEl = $('.js-search-project-options'); + var $groupOptionsDataEl = $('.js-search-group-options'); + var $dashboardOptionsDataEl = $('.js-search-dashboard-options'); + + if ($projectOptionsDataEl.length) { + gl.projectOptions = gl.projectOptions || {}; + + var projectPath = $projectOptionsDataEl.data('project-path'); + + gl.projectOptions[projectPath] = { + name: $projectOptionsDataEl.data('name'), + issuesPath: $projectOptionsDataEl.data('issues-path'), + mrPath: $projectOptionsDataEl.data('mr-path') + }; + } + + if ($groupOptionsDataEl.length) { + gl.groupOptions = gl.groupOptions || {}; + + var groupPath = $groupOptionsDataEl.data('group-path'); + + gl.groupOptions[groupPath] = { + name: $groupOptionsDataEl.data('name'), + issuesPath: $groupOptionsDataEl.data('issues-path'), + mrPath: $groupOptionsDataEl.data('mr-path') + }; + } + + if ($dashboardOptionsDataEl.length) { + gl.dashboardOptions = { + issuesPath: $dashboardOptionsDataEl.data('issues-path'), + mrPath: $dashboardOptionsDataEl.data('mr-path') + }; + } + }); + }).call(this); diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index 156b9b8abec..ee6af123268 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -10,12 +10,13 @@ ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>'; - COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. Click to expand it.</div>'; + COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>'; function SingleFileDiff(file) { this.file = file; this.toggleDiff = bind(this.toggleDiff, this); this.content = $('.diff-content', this.file); + this.$toggleIcon = $('.diff-toggle-caret', this.file); this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path'); this.isOpen = !this.diffForPath; if (this.diffForPath) { @@ -23,18 +24,22 @@ this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide(); this.content = null; this.collapsedContent.after(this.loadingContent); + this.$toggleIcon.addClass('fa-caret-right'); } else { this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide(); this.content.after(this.collapsedContent); + this.$toggleIcon.addClass('fa-caret-down'); } - this.collapsedContent.on('click', this.toggleDiff); - $('.file-title > a', this.file).on('click', this.toggleDiff); + $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff); } SingleFileDiff.prototype.toggleDiff = function(e) { + var $target = $(e.target); + if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return; this.isOpen = !this.isOpen; if (!this.isOpen && !this.hasError) { this.content.hide(); + this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down'); this.collapsedContent.show(); if (typeof DiffNotesApp !== 'undefined') { DiffNotesApp.compileComponents(); @@ -42,10 +47,12 @@ } else if (this.content) { this.collapsedContent.hide(); this.content.show(); + this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); if (typeof DiffNotesApp !== 'undefined') { DiffNotesApp.compileComponents(); } } else { + this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); return this.getContentHTML(); } }; diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index f5223207f3a..d315db4cb32 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -19,10 +19,8 @@ &.diff-collapsed { padding: 5px; - cursor: pointer; - - &:hover { - background-color: $row-hover; + .click-to-expand { + cursor: pointer; } } } @@ -129,8 +127,6 @@ position: relative; .avatar-holder { - margin-bottom: 16px; - .avatar, .identicon { margin: 0 auto; float: none; @@ -143,13 +139,7 @@ .cover-title { color: $gl-header-color; - margin: 0; - font-size: 24px; - font-weight: normal; - margin-bottom: 10px; - color: #4c4e54; font-size: 23px; - line-height: 1.1; h1 { color: $gl-gray-dark; @@ -213,6 +203,9 @@ } } } + &.user-cover-block { + padding: 24px 0 0; + } .group-info { diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 4618687a4be..ce489f7c3de 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -336,3 +336,9 @@ box-shadow: inset 0 0 0 white; } } + +@media (max-width: $screen-xs-max) { + .btn-wide-on-xs { + width: 100%; + } +} diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 76a3c083697..81520500594 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -26,6 +26,15 @@ padding: 10px $gl-padding; word-wrap: break-word; border-radius: 3px 3px 0 0; + cursor: pointer; + + &:hover { + background-color: $dark-background-color; + } + + .diff-toggle-caret { + padding-right: 6px; + } &.file-title-clear { padding-left: 0; diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index 0c21d0240b3..3ac1678dd05 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -3,7 +3,8 @@ margin: 0; margin-bottom: $gl-padding; font-size: 14px; - z-index: 100; + position: relative; + z-index: 1; .flash-notice { @extend .alert; @@ -34,6 +35,12 @@ } } +.content-wrapper { + .flash-notice .container-fluid { + background-color: transparent; + } +} + @media (max-width: $screen-md-min) { ul.notes { .flash-container.timeline-content { @@ -41,4 +48,3 @@ } } } - diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index d4a030f7f7a..c748f856501 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -112,11 +112,15 @@ header { .header-logo { position: absolute; left: 50%; - margin-left: -18px; top: 7px; transition-duration: .3s; z-index: 999; + #logo { + position: relative; + left: -50%; + } + svg, img { height: 36px; } @@ -126,8 +130,12 @@ header { } @media (max-width: $screen-xs-max) { - right: 25px; + right: 20px; left: auto; + + #logo { + left: auto; + } } } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 46af18580d5..efc348214c2 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -164,7 +164,7 @@ ul.content-list { } .no-comments { - opacity: 0.5; + opacity: .5; } } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 553768b2e68..ea43f4afc37 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -99,8 +99,7 @@ .top-area { @include clearfix; - - border-bottom: 1px solid #eee; + border-bottom: 1px solid $btn-gray-hover; .nav-text { padding-top: 16px; diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 3b7de4b57bb..557ef7291cf 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -142,6 +142,7 @@ transition-duration: .3s; position: absolute; top: 0; + cursor: pointer; &:hover, &:focus { diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 2582cde5a71..9f2d53d5206 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -204,7 +204,7 @@ body { } h1, h2, h3, h4, h5, h6 { - color: $gl-header-color; + color: $gl-title-color; font-weight: 600; } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 9f563a4de35..14ec310de2d 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -102,7 +102,7 @@ $gl-grayish-blue: #7f8fa4; $gl-gray: $gl-text-color; $gl-gray-dark: #313236; $gl-gray-light: $gl-placeholder-color; -$gl-header-color: $gl-title-color; +$gl-header-color: #4c4e54; /* * Lists @@ -270,6 +270,12 @@ $calendar-border-color: rgba(#000, .1); $calendar-unselectable-bg: $gray-light; /* + * Cycle Analytics + */ +$cycle-analytics-box-padding: 30px; +$cycle-analytics-box-text-color: #8c8c8c; + +/* * Personal Access Tokens */ $personal-access-tokens-disabled-label-color: #bbb; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 9c84dceed05..ecc5b24e360 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -197,6 +197,7 @@ lex a { color: inherit; + word-wrap: break-word; } } diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index c879074c7fe..194a39a8377 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -107,6 +107,14 @@ .block { width: 100%; + + &.coverage { + padding: 0 16px 11px; + } + + .btn-group-justified { + margin-top: 5px; + } } .js-build-variable { @@ -210,6 +218,9 @@ .build-detail-row { margin-bottom: 5px; + &:last-of-type { + margin-bottom: 0; + } } .build-light-text { diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index 21e19c97632..778471a34d7 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -1,6 +1,6 @@ #cycle-analytics { margin: 24px auto 0; - width: 800px; + max-width: 800px; position: relative; .panel { @@ -9,10 +9,18 @@ padding: 24px 0; border-bottom: none; position: relative; + + @media (max-width: $screen-sm-min) { + padding: 6px 0 24px; + } } .column { text-align: center; + + @media (max-width: $screen-sm-min) { + padding: 15px 0; + } .header { font-size: 30px; @@ -28,11 +36,14 @@ &:last-child { text-align: right; + + @media (max-width: $screen-sm-min) { + text-align: center; + } } } .dropdown { - position: relative; top: 13px; } } @@ -40,7 +51,7 @@ .bordered-box { border: 1px solid $border-color; @include border-radius($border-radius-default); - position: relative; + } .content-list { @@ -60,9 +71,15 @@ line-height: 19px; font-size: 15px; font-weight: 600; + color: $gl-title-color; } - &:text { - color: #8c8c8c; + + &.text { + color: $layout-link-gray; + + &.value-col { + color: $gl-title-color; + } } } } @@ -71,7 +88,9 @@ text-align: right; span { - line-height: 42px; + position: relative; + vertical-align: middle; + top: 3px; } } } @@ -82,21 +101,25 @@ .dismiss-icon { position: absolute; - right: $gl-padding; + right: $cycle-analytics-box-padding; cursor: pointer; color: #b2b2b2; } - svg { - margin: 0 20px; - float: left; - width: 136px; - height: 136px; + .svg-container { + text-align: center; + + svg { + width: 136px; + height: 136px; + } } - + .inner-content { - width: 480px; - float: left; + @media (max-width: $screen-sm-min) { + padding: 0 28px; + text-align: center; + } h4 { color: $gl-text-color; @@ -104,7 +127,7 @@ } p { - color: #8c8c8c; + color: $cycle-analytics-box-text-color; margin-bottom: $gl-padding; } } diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 1aa4e06d975..e1304335271 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -59,6 +59,7 @@ } .encoding-selector, + .soft-wrap-toggle, .license-selector, .gitignore-selector, .gitlab-ci-yml-selector { @@ -67,6 +68,24 @@ font-family: $regular_font; } + .soft-wrap-toggle { + margin: 0 $btn-side-margin; + .soft-wrap { + display: block; + } + .no-wrap { + display: none; + } + &.soft-wrap-active { + .soft-wrap { + display: none; + } + .no-wrap { + display: block; + } + } + } + .gitignore-selector, .license-selector, .gitlab-ci-yml-selector { .dropdown { line-height: 21px; diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index b657ca47d38..185ce970e71 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -55,3 +55,50 @@ } } } + +.groups-header { + @media (min-width: $screen-sm-min) { + .nav-links { + width: 35%; + } + + .nav-controls { + width: 65%; + } + } +} + +.groups-empty-state { + padding: 50px 100px; + overflow: hidden; + + @media (max-width: $screen-md-min) { + padding: 50px 0; + } + + svg { + float: right; + + @media (max-width: $screen-md-min) { + float: none; + display: block; + width: 250px; + position: relative; + left: 50%; + margin-left: -125px; + } + } + + .text-content { + float: left; + width: 460px; + margin-top: 120px; + + @media (max-width: $screen-md-min) { + float: none; + margin-top: 60px; + width: auto; + text-align: center; + } + } +} diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index b94f524b513..8c2ba3ed58c 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -2,13 +2,17 @@ max-width: 90%; } -li.milestone { - h4 { - font-weight: bold; - } +.milestones { + .milestone { + padding: 10px 16px; + + h4 { + font-weight: bold; + } - .progress { - height: 6px; + .progress { + height: 6px; + } } } @@ -29,6 +33,7 @@ li.milestone { // Issue title span a { color: $gl-text-color; + word-wrap: break-word; } } } @@ -64,3 +69,14 @@ li.milestone { border-bottom: 1px solid $border-color; padding: 20px 0; } + +@media (max-width: $screen-sm-min) { + .milestone-actions { + @include clearfix(); + padding-top: $gl-vert-padding; + + .btn:first-child { + margin-left: 0; + } + } +} diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 1b4d12d3053..b035bfc9f3c 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -177,6 +177,10 @@ border-bottom: 2px solid $border-color; } } + + a { + display: block; + } } } diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 6f58203f49c..0fcdaf94a21 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -93,8 +93,9 @@ .profile-user-bio { // Limits the width of the user bio for readability. - max-width: 750px; - margin: auto; + max-width: 600px; + margin: 15px auto 0; + padding: 0 16px; } .user-avatar-button { @@ -212,6 +213,28 @@ } .user-profile { + .cover-controls a { + margin-left: 5px; + } + .profile-header { + margin: 0 auto; + .avatar-holder { + width: 90px; + display: inline-block; + } + .user-info { + display: inline-block; + text-align: left; + vertical-align: middle; + margin-left: 15px; + .handle { + color: $gl-gray-light; + } + .member-date { + margin-bottom: 4px; + } + } + } @media (max-width: $screen-xs-max) { .cover-block { padding-top: 20px; @@ -219,16 +242,26 @@ .cover-controls { position: static; + padding: 0 16px; margin-bottom: 20px; + display: -webkit-flex; + display: flex; .btn { - display: inline-block; - width: 46%; + -webkit-flex-grow: 1; + flex-grow: 1; + &:first-child { + margin-left: 0; + } } } } } +.user-profile-nav { + margin-top: 15px; +} + table.u2f-registrations { th:not(:last-child), td:not(:last-child) { border-right: solid 1px transparent; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 8c8c403244e..78bc4b79e86 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -743,6 +743,62 @@ pre.light-well { .dropdown-menu { width: 300px; } + + &.from .compare-dropdown-toggle { + width: 237px; + } + + &.to .compare-dropdown-toggle { + width: 254px; + } + + .dropdown-toggle-text { + display: block; + height: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 100%; + } +} + +.compare-ellipsis { + display: inline; +} + +@media (max-width: $screen-xs-max) { + .compare-form-group { + .input-group { + width: 100%; + + & > .compare-dropdown-toggle { + width: 100%; + } + } + + .dropdown-menu { + width: 100%; + } + } + + .compare-switch-container { + text-align: center; + padding: 0 0 $gl-padding; + + .commits-compare-switch { + float: none; + } + } + + .compare-ellipsis { + display: block; + text-align: center; + padding: 0 0 $gl-padding; + } + + .commits-compare-btn { + width: 100%; + } } .clearable-input { @@ -779,4 +835,4 @@ pre.light-well { border-top-right-radius: 0; border-bottom-right-radius: 0; } -}
\ No newline at end of file +} diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 436fb00ba2e..e77f9816d8a 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -103,7 +103,7 @@ // Custom dropdown positioning .dropdown-menu { - top: 30px; + top: 37px; left: -5px; padding: 0; diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss index 5270aea4e79..857eb76131a 100644 --- a/app/assets/stylesheets/pages/snippets.scss +++ b/app/assets/stylesheets/pages/snippets.scss @@ -12,11 +12,18 @@ .snippet-file-content { border-radius: 3px; + margin-bottom: $gl-padding; + .btn-clipboard { @extend .btn; } } +.project-snippets .awards { + border-bottom: 1px solid $table-border-color; + padding-bottom: $gl-padding; +} + .snippet-title { font-size: 24px; font-weight: 600; @@ -29,3 +36,7 @@ float: right; } } + +.snippet-scope-menu .btn-new { + margin-top: 15px; +} diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 1778c069706..41ad10f07bd 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -27,7 +27,12 @@ } .last-commit { - @include str-truncated(60%); + @include str-truncated(506px); + + @media (min-width: $screen-sm-max) and (max-width: $screen-md-max) { + @include str-truncated(450px); + } + } .commit-history-link-spacer { @@ -55,6 +60,15 @@ } .tree-item { + .link-container { + padding: 0; + + a { + padding: 10px $gl-padding; + display: block; + } + } + .tree-item-file-name { max-width: 320px; vertical-align: middle; diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index aed77d0358a..aa7570cd896 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -10,7 +10,7 @@ class Admin::GroupsController < Admin::ApplicationController def show @members = @group.members.order("access_level DESC").page(params[:members_page]) - @requesters = @group.requesters + @requesters = AccessRequestsFinder.new(@group).execute(current_user) @projects = @group.projects.page(params[:projects_page]) end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 0d2f4f6eb38..1d963bdf7d5 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -22,7 +22,7 @@ class Admin::ProjectsController < Admin::ApplicationController end @project_members = @project.members.page(params[:project_members_page]) - @requesters = @project.requesters + @requesters = AccessRequestsFinder.new(@project).execute(current_user) end def transfer diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb index e06d12cfce1..78012960252 100644 --- a/app/controllers/ci/lints_controller.rb +++ b/app/controllers/ci/lints_controller.rb @@ -14,6 +14,7 @@ module Ci @config_processor = Ci::GitlabCiYamlProcessor.new(@content) @stages = @config_processor.stages @builds = @config_processor.builds + @jobs = @config_processor.jobs end rescue @error = 'Undefined error' diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 4a447735fa7..b5e79099e39 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -13,18 +13,10 @@ module IssuableCollections issues_finder.execute end - def all_issues_collection - IssuesFinder.new(current_user, filter_params_all).execute - end - def merge_requests_collection merge_requests_finder.execute end - def all_merge_requests_collection - MergeRequestsFinder.new(current_user, filter_params_all).execute - end - def issues_finder @issues_finder ||= issuable_finder_for(IssuesFinder) end @@ -62,10 +54,6 @@ module IssuableCollections @filter_params end - def filter_params_all - @filter_params_all ||= filter_params.merge(state: 'all', sort: nil) - end - def set_default_scope params[:scope] = 'all' if params[:scope].blank? end diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb index eced9d9d678..b89fb94be6e 100644 --- a/app/controllers/concerns/issues_action.rb +++ b/app/controllers/concerns/issues_action.rb @@ -10,8 +10,6 @@ module IssuesAction .preload(:author, :project) .page(params[:page]) - @all_issues = all_issues_collection.non_archived - respond_to do |format| format.html format.atom { render layout: false } diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index 52682ef9dc9..b8ed2c159a7 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -1,6 +1,5 @@ module MembershipActions extend ActiveSupport::Concern - include MembersHelper def request_access membershipable.request_access(current_user) @@ -10,11 +9,7 @@ module MembershipActions end def approve_access_request - @member = membershipable.requesters.find(params[:id]) - - return render_403 unless can?(current_user, action_member_permission(:update, @member), @member) - - @member.accept_request + Members::ApproveAccessRequestService.new(membershipable, current_user, params).execute redirect_to polymorphic_url([membershipable, :members]) end diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb index 729763169e2..a1b0eee37f9 100644 --- a/app/controllers/concerns/merge_requests_action.rb +++ b/app/controllers/concerns/merge_requests_action.rb @@ -9,7 +9,5 @@ module MergeRequestsAction .non_archived .preload(:author, :target_project) .page(params[:page]) - - @all_merge_requests = all_merge_requests_collection.non_archived end end diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb index 29e243c66a3..99acd98ae13 100644 --- a/app/controllers/concerns/spammable_actions.rb +++ b/app/controllers/concerns/spammable_actions.rb @@ -7,7 +7,7 @@ module SpammableActions def mark_as_spam if SpamService.new(spammable).mark_as_spam! - redirect_to spammable, notice: "#{spammable.class.to_s} was submitted to Akismet successfully." + redirect_to spammable, notice: "#{spammable.class} was submitted to Akismet successfully." else redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.' end diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb index 172d5344b7a..3717c49f272 100644 --- a/app/controllers/concerns/toggle_award_emoji.rb +++ b/app/controllers/concerns/toggle_award_emoji.rb @@ -10,7 +10,9 @@ module ToggleAwardEmoji if awardable.user_can_award?(current_user, name) awardable.toggle_award_emoji(name, current_user) - TodoService.new.new_award_emoji(to_todoable(awardable), current_user) + + todoable = to_todoable(awardable) + TodoService.new.new_award_emoji(todoable, current_user) if todoable render json: { ok: true } else @@ -24,8 +26,10 @@ module ToggleAwardEmoji case awardable when Note awardable.noteable - else + when MergeRequest, Issue awardable + when Snippet + nil end end diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 272164cd0cc..9c323d7705a 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -15,7 +15,7 @@ class Groups::GroupMembersController < Groups::ApplicationController end @members = @members.order('access_level DESC').page(params[:page]).per(50) - @requesters = @group.requesters if can?(current_user, :admin_group, @group) + @requesters = AccessRequestsFinder.new(@group).execute(current_user) @group_member = @group.group_members.new end diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb index 7d0eff37635..3ec173abcdb 100644 --- a/app/controllers/import/gitlab_projects_controller.rb +++ b/app/controllers/import/gitlab_projects_controller.rb @@ -1,6 +1,5 @@ class Import::GitlabProjectsController < Import::BaseController before_action :verify_gitlab_project_import_enabled - before_action :authenticate_admin! def new @namespace_id = project_params[:namespace_id] @@ -48,8 +47,4 @@ class Import::GitlabProjectsController < Import::BaseController :path, :namespace_id, :file ) end - - def authenticate_admin! - render_404 unless current_user.is_admin? - end end diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 06d96774754..7e4da73bc11 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -11,10 +11,8 @@ class JwtController < ApplicationController service = SERVICES[params[:service]] return head :not_found unless service - @authentication_result ||= Gitlab::Auth::Result.new - result = service.new(@authentication_result.project, @authentication_result.actor, auth_params). - execute(authentication_abilities: @authentication_result.authentication_abilities) + execute(authentication_abilities: @authentication_result.authentication_abilities || []) render json: result, status: result[:http_status] end @@ -22,10 +20,12 @@ class JwtController < ApplicationController private def authenticate_project_or_user + @authentication_result = Gitlab::Auth::Result.new + authenticate_with_http_basic do |login, password| @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip) - render_403 unless @authentication_result.success? && + render_unauthorized unless @authentication_result.success? && (@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User)) end rescue Gitlab::Auth::MissingPersonalTokenError @@ -33,10 +33,21 @@ class JwtController < ApplicationController end def render_missing_personal_token - render plain: "HTTP Basic: Access denied\n" \ - "You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \ - "You can generate one at #{profile_personal_access_tokens_url}", - status: 401 + render json: { + errors: [ + { code: 'UNAUTHORIZED', + message: "HTTP Basic: Access denied\n" \ + "You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \ + "You can generate one at #{profile_personal_access_tokens_url}" } + ] }, status: 401 + end + + def render_unauthorized + render json: { + errors: [ + { code: 'UNAUTHORIZED', + message: 'HTTP Basic: Access denied' } + ] }, status: 401 end def auth_params diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index c5fa756d02b..f71e0a1302b 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -73,7 +73,8 @@ class ProfilesController < Profiles::ApplicationController :skype, :twitter, :username, - :website_url + :website_url, + :organization ) end end diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 9404612a993..4aa7982eab4 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -33,7 +33,7 @@ module Projects def issue @issue ||= - IssuesFinder.new(current_user, project_id: project.id, state: 'all') + IssuesFinder.new(current_user, project_id: project.id) .execute .where(iid: params[:id]) .first! diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 02fb3f56890..cdfc1ba7b92 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -10,10 +10,11 @@ class Projects::CommitController < Projects::ApplicationController before_action :require_non_empty_project before_action :authorize_download_code!, except: [:cancel_builds, :retry_builds] before_action :authorize_update_build!, only: [:cancel_builds, :retry_builds] + before_action :authorize_read_pipeline!, only: [:pipelines] before_action :authorize_read_commit_status!, only: [:builds] before_action :commit - before_action :define_commit_vars, only: [:show, :diff_for_path, :builds] - before_action :define_status_vars, only: [:show, :builds] + before_action :define_commit_vars, only: [:show, :diff_for_path, :builds, :pipelines] + before_action :define_status_vars, only: [:show, :builds, :pipelines] before_action :define_note_vars, only: [:show, :diff_for_path] before_action :authorize_edit_tree!, only: [:revert, :cherry_pick] @@ -31,6 +32,9 @@ class Projects::CommitController < Projects::ApplicationController render_diff_for_path(@commit.diffs(diff_options)) end + def pipelines + end + def builds end @@ -96,10 +100,6 @@ class Projects::CommitController < Projects::ApplicationController @noteable = @commit ||= @project.commit(params[:id]) end - def pipelines - @pipelines ||= project.pipelines.where(sha: commit.sha) - end - def ci_builds @ci_builds ||= Ci::Build.where(pipeline: pipelines) end @@ -134,8 +134,9 @@ class Projects::CommitController < Projects::ApplicationController end def define_status_vars - @statuses = CommitStatus.where(pipeline: pipelines).relevant - @builds = Ci::Build.where(pipeline: pipelines).relevant + @ci_pipelines = project.pipelines.where(sha: commit.sha) + @statuses = CommitStatus.where(pipeline: @ci_pipelines).relevant + @builds = Ci::Build.where(pipeline: @ci_pipelines).relevant end def assign_change_commit_vars(mr_source_branch) diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index cbfd3cab3dd..383e184d796 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -32,11 +32,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController return # Allow access end elsif allow_kerberos_spnego_auth? && spnego_provided? - user = find_kerberos_user + kerberos_user = find_kerberos_user - if user + if kerberos_user @authentication_result = Gitlab::Auth::Result.new( - user, nil, :kerberos, Gitlab::Auth.full_authentication_abilities) + kerberos_user, nil, :kerberos, Gitlab::Auth.full_authentication_abilities) send_final_spnego_response return # Allow access diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 19b8b1576c4..ef13e0677d2 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -28,8 +28,6 @@ class Projects::IssuesController < Projects::ApplicationController @labels = @project.labels.where(title: params[:label_name]) - @all_issues = all_issues_collection - respond_to do |format| format.html format.atom { render layout: false } @@ -56,7 +54,7 @@ class Projects::IssuesController < Projects::ApplicationController end def show - raw_notes = @issue.notes_with_associations.fresh + raw_notes = @issue.notes.inc_relations_for_view.fresh @notes = Banzai::NoteRenderer. render(raw_notes, @project, current_user, @path, @project_wiki, @ref) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index e972376df4c..8c8c56228ad 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -18,6 +18,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :define_commit_vars, only: [:diffs] before_action :define_diff_comment_vars, only: [:diffs] before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines] + before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines] # Allow read any merge_request before_action :authorize_read_merge_request! @@ -37,8 +38,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController @labels = @project.labels.where(title: params[:label_name]) - @all_merge_requests = all_merge_requests_collection - respond_to do |format| format.html format.json do @@ -277,7 +276,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def remove_wip - MergeRequests::UpdateService.new(project, current_user, title: @merge_request.wipless_title).execute(@merge_request) + MergeRequests::UpdateService.new(project, current_user, wip_event: 'unwip').execute(@merge_request) redirect_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), notice: "The merge request can now be merged." @@ -310,8 +309,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController return end - TodoService.new.merge_merge_request(merge_request, current_user) - @merge_request.update(merge_error: nil) if params[:merge_when_build_succeeds].present? @@ -420,10 +417,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def validates_merge_request - # If source project was removed and merge request for some reason - # wasn't close (Ex. mr from fork to origin) - return invalid_mr if !@merge_request.source_project && @merge_request.open? - # Show git not found page # if there is no saved commits between source & target branch if @merge_request.commits.blank? @@ -498,7 +491,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def invalid_mr - # Render special view for MR with removed source or target branch + # Render special view for MR with removed target branch render 'invalid' end @@ -540,4 +533,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController @diff_notes_disabled = !@merge_request_diff.latest? @diffs = @merge_request_diff.diffs(diff_options) end + + def close_merge_request_without_source_project + if !@merge_request.source_project && @merge_request.open? + @merge_request.close + end + end end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 42a7e5a2c30..2343c7d20ec 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -29,7 +29,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController @group_members = @group_members.order('access_level DESC') end - @requesters = @project.requesters if can?(current_user, :admin_project, @project) + @requesters = AccessRequestsFinder.new(@project).execute(current_user) @project_member = @project.project_members.new @project_group_links = @project.project_group_links diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 17ceefec3b8..e290a0eadda 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -1,6 +1,8 @@ class Projects::SnippetsController < Projects::ApplicationController + include ToggleAwardEmoji + before_action :module_enabled - before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] + before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji] # Allow read any snippet before_action :authorize_read_project_snippet!, except: [:new, :create, :index] @@ -80,6 +82,7 @@ class Projects::SnippetsController < Projects::ApplicationController def snippet @snippet ||= @project.snippets.find(params[:id]) end + alias_method :awardable, :snippet def authorize_read_project_snippet! return render_404 unless can?(current_user, :read_project_snippet, @snippet) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index eaa38fa6c98..62916270172 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -137,10 +137,10 @@ class ProjectsController < Projects::ApplicationController noteable = case params[:type] when 'Issue' - IssuesFinder.new(current_user, project_id: @project.id, state: 'all'). + IssuesFinder.new(current_user, project_id: @project.id). execute.find_by(iid: params[:type_id]) when 'MergeRequest' - MergeRequestsFinder.new(current_user, project_id: @project.id, state: 'all'). + MergeRequestsFinder.new(current_user, project_id: @project.id). execute.find_by(iid: params[:type_id]) when 'Commit' @project.commit(params[:type_id]) @@ -324,7 +324,12 @@ class ProjectsController < Projects::ApplicationController end def repo_exists? - project.repository_exists? && !project.empty_repo? + project.repository_exists? && !project.empty_repo? && project.repo + + rescue Gitlab::Git::Repository::NoRepository + project.repository.expire_exists_cache + + false end def project_view_files? diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 61517d21f9f..d01e0dedf52 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -6,8 +6,6 @@ class SearchController < ApplicationController layout 'search' def show - return if params[:search].nil? || params[:search].blank? - if params[:project_id].present? @project = Project.find_by(id: params[:project_id]) @project = nil unless can?(current_user, :download_code, @project) @@ -18,6 +16,8 @@ class SearchController < ApplicationController @group = nil unless can?(current_user, :read_group, @group) end + return if params[:search].nil? || params[:search].blank? + @search_term = params[:search] @scope = params[:scope] diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 2a17c1f34db..d198782138a 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -1,4 +1,6 @@ class SnippetsController < ApplicationController + include ToggleAwardEmoji + before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] # Allow read snippet @@ -85,6 +87,7 @@ class SnippetsController < ApplicationController PersonalSnippet.find(params[:id]) end end + alias_method :awardable, :snippet def authorize_read_snippet! authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index a4bedb3bfe6..838ecc837e4 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -65,7 +65,7 @@ class UsersController < ApplicationController format.html { render 'show' } format.json do render json: { - html: view_to_html_string("snippets/_snippets", collection: @snippets) + html: view_to_html_string("snippets/_snippets", collection: @snippets, remote: true) } end end diff --git a/app/finders/access_requests_finder.rb b/app/finders/access_requests_finder.rb new file mode 100644 index 00000000000..b6ee49df99b --- /dev/null +++ b/app/finders/access_requests_finder.rb @@ -0,0 +1,27 @@ +class AccessRequestsFinder + attr_accessor :source + + # Arguments: + # source - a Group or Project + def initialize(source) + @source = source + end + + def execute(*args) + execute!(*args) + rescue Gitlab::Access::AccessDeniedError + [] + end + + def execute!(current_user) + raise Gitlab::Access::AccessDeniedError unless can_see_access_requests?(current_user) + + source.requesters + end + + private + + def can_see_access_requests?(current_user) + source && Ability.allowed?(current_user, :"admin_#{source.class.to_s.underscore}", source) + end +end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 8f9ef8f725c..9f170428100 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -183,17 +183,12 @@ class IssuableFinder end def by_state(items) - case params[:state] - when 'closed' - items.closed - when 'merged' - items.respond_to?(:merged) ? items.merged : items.closed - when 'all' - items - when 'opened' - items.opened + params[:state] ||= 'all' + + if items.respond_to?(params[:state]) + items.public_send(params[:state]) else - raise 'You must specify default state' + items end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ed41bf04fc0..ebd78bf9888 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -280,23 +280,6 @@ module ApplicationHelper end end - def state_filters_text_for(state, records) - titles = { - opened: "Open" - } - - state_title = titles[state] || state.to_s.humanize - count = records.public_send(state).size - html = content_tag :span, state_title - - if count.present? - html += " " - html += content_tag :span, number_with_delimiter(count), class: 'badge' - end - - html.html_safe - end - def truncate_first_line(message, length = 50) truncate(message.each_line.first.chomp, length: length) if message end diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb new file mode 100644 index 00000000000..aa134cea31c --- /dev/null +++ b/app/helpers/award_emoji_helper.rb @@ -0,0 +1,9 @@ +module AwardEmojiHelper + def toggle_award_url(awardable) + if @project + url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) + else + url_for([:toggle_award_emoji, awardable]) + end + end +end diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 639deb7c521..b7f48630bd4 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -56,7 +56,7 @@ module CiStatusHelper def render_commit_status(commit, tooltip_placement: 'auto left') project = commit.project - path = builds_namespace_project_commit_path(project.namespace, project, commit) + path = pipelines_namespace_project_commit_path(project.namespace, project, commit) render_status_with_link('commit', commit.status, path, tooltip_placement: tooltip_placement) end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 5b71113feb9..670a7ca36f4 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -70,6 +70,10 @@ module GitlabRoutingHelper namespace_project_runner_path(@project.namespace, @project, runner, *args) end + def environment_path(environment, *args) + namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args) + end + def issue_path(entity, *args) namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args) end @@ -102,6 +106,14 @@ module GitlabRoutingHelper end end + def toggle_award_emoji_personal_snippet_path(*args) + toggle_award_emoji_snippet_path(*args) + end + + def toggle_award_emoji_namespace_project_project_snippet_path(*args) + toggle_award_emoji_namespace_project_snippet_path(*args) + end + ## Members def project_members_url(project, *args) namespace_project_project_members_url(project.namespace, project) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 5c04bba323f..8c04200fab9 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -94,6 +94,24 @@ module IssuablesHelper label_names.join(', ') end + def issuables_state_counter_text(issuable_type, state) + titles = { + opened: "Open" + } + + state_title = titles[state] || state.to_s.humanize + + count = + Rails.cache.fetch(issuables_state_counter_cache_key(issuable_type, state), expires_in: 2.minutes) do + issuables_count_for_state(issuable_type, state) + end + + html = content_tag(:span, state_title) + html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge') + + html.html_safe + end + private def sidebar_gutter_collapsed? @@ -111,4 +129,22 @@ module IssuablesHelper issuable.open? ? :opened : :closed end end + + def issuables_count_for_state(issuable_type, state) + issuables_finder = public_send("#{issuable_type}_finder") + issuables_finder.params[:state] = state + + issuables_finder.execute.page(1).total_count + end + + IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page] + private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY + + def issuables_state_counter_cache_key(issuable_type, state) + opts = params.with_indifferent_access + opts[:state] = state + opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY) + + hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-')) + end end diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb index c15ecc8f86e..95b60aeab5f 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/helpers/lfs_helper.rb @@ -1,11 +1,13 @@ module LfsHelper + include Gitlab::Routing.url_helpers + def require_lfs_enabled! return if Gitlab.config.lfs.enabled render( json: { message: 'Git LFS is not enabled on this GitLab server, contact your admin.', - documentation_url: "#{Gitlab.config.gitlab.url}/help", + documentation_url: help_url, }, status: 501 ) @@ -46,7 +48,7 @@ module LfsHelper render( json: { message: 'Access forbidden. Check your access level.', - documentation_url: "#{Gitlab.config.gitlab.url}/help", + documentation_url: help_url, }, content_type: "application/vnd.git-lfs+json", status: 403 @@ -57,7 +59,7 @@ module LfsHelper render( json: { message: 'Not found.', - documentation_url: "#{Gitlab.config.gitlab.url}/help", + documentation_url: help_url, }, content_type: "application/vnd.git-lfs+json", status: 404 diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index b3e6e468ecd..a11c313a6b8 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -35,6 +35,30 @@ module MilestonesHelper milestone.issues.with_label(label.title).send(state).size end + # Returns count of milestones for different states + # Uses explicit hash keys as the 'opened' state URL params differs from the db value + # and we need to add the total + def milestone_counts(milestones) + counts = milestones.reorder(nil).group(:state).count + + { + opened: counts['active'] || 0, + closed: counts['closed'] || 0, + all: counts.values.sum || 0 + } + end + + # Show 'active' class if provided GET param matches check + # `or_blank` allows the function to return 'active' when given an empty param + # Could be refactored to be simpler but that may make it harder to read + def milestone_class_for_state(param, check, match_blank_param = false) + if match_blank_param + 'active' if param.blank? || param == check + else + 'active' if param == check + end + end + def milestone_progress_bar(milestone) options = { class: 'progress-bar progress-bar-success', diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 56477733ea2..e667c9e4e2e 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -139,7 +139,7 @@ module ProjectsHelper end options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field)) - content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control", data: { field: field }).html_safe + content_tag(:select, options, name: "project[project_feature_attributes][#{field}]", id: "project_project_feature_attributes_#{field}", class: "pull-right form-control", data: { field: field }).html_safe end private diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb index 415f6e12885..f7ed61625f4 100644 --- a/app/mailers/devise_mailer.rb +++ b/app/mailers/devise_mailer.rb @@ -3,4 +3,12 @@ class DeviseMailer < Devise::Mailer default reply_to: Gitlab.config.gitlab.email_reply_to layout 'devise_mailer' + + protected + + def subject_for(key) + subject = super + subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present? + subject + end end diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb index 45311690293..7b617b359ea 100644 --- a/app/mailers/emails/members.rb +++ b/app/mailers/emails/members.rb @@ -45,7 +45,7 @@ module Emails @token = token mail(to: member.invite_email, - subject: "Invitation to join the #{member_source.human_name} #{member_source.model_name.singular}") + subject: subject("Invitation to join the #{member_source.human_name} #{member_source.model_name.singular}")) end def member_invite_accepted_email(member_source_type, member_id) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index aa6b9da82dd..eca6ec29767 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -93,6 +93,7 @@ class Notify < BaseMailer subject = "" subject << "#{@project.name} | " if @project subject << extra.join(' | ') if extra.present? + subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present? subject end @@ -110,7 +111,7 @@ class Notify < BaseMailer headers['X-GitLab-Reply-Key'] = reply_key if !@labels_url && @sent_notification && @sent_notification.unsubscribable? - headers['List-Unsubscribe'] = unsubscribe_sent_notification_url(@sent_notification, force: true) + headers['List-Unsubscribe'] = "<#{unsubscribe_sent_notification_url(@sent_notification, force: true)}>" @sent_notification_url = unsubscribe_sent_notification_url(@sent_notification) end diff --git a/app/models/board.rb b/app/models/board.rb index 3240c4bede3..c56422914a9 100644 --- a/app/models/board.rb +++ b/app/models/board.rb @@ -4,4 +4,12 @@ class Board < ActiveRecord::Base has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all validates :project, presence: true + + def backlog_list + lists.merge(List.backlog).take + end + + def done_list + lists.merge(List.done).take + end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 416ea820e0c..2911dcaa6a2 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -91,7 +91,7 @@ module Ci sha: build.sha, ref: build.ref, tag: build.tag, - options: build.options[:environment], + options: build.options.to_h[:environment], variables: build.variables) service.execute(build) end @@ -378,7 +378,7 @@ module Ci end def artifacts? - !artifacts_expired? && self[:artifacts_file].present? + !artifacts_expired? && artifacts_file.exists? end def artifacts_metadata? @@ -498,8 +498,11 @@ module Ci end def hide_secrets(trace) - trace = Ci::MaskSecret.mask(trace, project.runners_token) if project - trace = Ci::MaskSecret.mask(trace, token) + return unless trace + + trace = trace.dup + Ci::MaskSecret.mask!(trace, project.runners_token) if project + Ci::MaskSecret.mask!(trace, token) trace end end diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index 656a242c265..ac2477fd973 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -80,7 +80,7 @@ class CommitRange end def inspect - %(#<#{self.class}:#{object_id} #{to_s}>) + %(#<#{self.class}:#{object_id} #{self}>) end def to_s diff --git a/app/models/concerns/access_requestable.rb b/app/models/concerns/access_requestable.rb index eedd32a729f..62bc6b809f4 100644 --- a/app/models/concerns/access_requestable.rb +++ b/app/models/concerns/access_requestable.rb @@ -8,9 +8,6 @@ module AccessRequestable extend ActiveSupport::Concern def request_access(user) - members.create( - access_level: Gitlab::Access::DEVELOPER, - user: user, - requested_at: Time.now.utc) + Members::RequestAccessService.new(self, user).execute end end diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb index d8d4575bb4d..073ac4c1b65 100644 --- a/app/models/concerns/awardable.rb +++ b/app/models/concerns/awardable.rb @@ -71,6 +71,12 @@ module Awardable end end + def user_authored?(current_user) + author = self.respond_to?(:author) ? self.author : self.user + + author == current_user + end + def awarded_emoji?(emoji_name, current_user) award_emoji.where(name: emoji_name, user: current_user).exists? end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 1650ac9fcbe..ff465d2c745 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -200,10 +200,6 @@ module Issuable end end - def user_authored?(user) - user == author - end - def subscribed_without_subscriptions?(user) participants(user).include?(user) end diff --git a/app/models/cycle_analytics/summary.rb b/app/models/cycle_analytics/summary.rb index 53b2cacb131..b46db449bf3 100644 --- a/app/models/cycle_analytics/summary.rb +++ b/app/models/cycle_analytics/summary.rb @@ -10,15 +10,33 @@ class CycleAnalytics end def commits - repository = @project.repository.raw_repository - - if @project.default_branch - repository.log(ref: @project.default_branch, after: @from).count - end + ref = @project.default_branch.presence + count_commits_for(ref) end def deploys @project.deployments.where("created_at > ?", @from).count end + + private + + # Don't use the `Gitlab::Git::Repository#log` method, because it enforces + # a limit. Since we need a commit count, we _can't_ enforce a limit, so + # the easiest way forward is to replicate the relevant portions of the + # `log` function here. + def count_commits_for(ref) + return unless ref + + repository = @project.repository.raw_repository + sha = @project.repository.commit(ref).sha + + cmd = %W(git --git-dir=#{repository.path} log) + cmd << '--format=%H' + cmd << "--after=#{@from.iso8601}" + cmd << sha + + raw_output = IO.popen(cmd) { |io| io.read } + raw_output.lines.count + end end end diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index da7c265a371..bda2b5c5d5d 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -8,7 +8,8 @@ class GlobalMilestone milestones = milestones.group_by(&:title) milestones.map do |title, milestones| - new(title, milestones) + milestones_relation = Milestone.where(id: milestones.map(&:id)) + new(title, milestones_relation) end end @@ -31,7 +32,7 @@ class GlobalMilestone end def projects - @projects ||= Project.for_milestones(milestones.map(&:id)) + @projects ||= Project.for_milestones(milestones.select(:id)) end def state @@ -53,19 +54,19 @@ class GlobalMilestone end def issues - @issues ||= Issue.of_milestones(milestones.map(&:id)).includes(:project) + @issues ||= Issue.of_milestones(milestones.select(:id)).includes(:project, :assignee, :labels) end def merge_requests - @merge_requests ||= MergeRequest.of_milestones(milestones.map(&:id)).includes(:target_project) + @merge_requests ||= MergeRequest.of_milestones(milestones.select(:id)).includes(:target_project, :assignee, :labels) end def participants - @participants ||= milestones.map(&:participants).flatten.compact.uniq + @participants ||= milestones.includes(:participants).map(&:participants).flatten.compact.uniq end def labels - @labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten) + @labels ||= GlobalLabel.build_collection(milestones.includes(:labels).map(&:labels).flatten) .sort_by!(&:title) end diff --git a/app/models/group.rb b/app/models/group.rb index aefb94b2ada..a2f88cca828 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -102,40 +102,44 @@ class Group < Namespace self[:lfs_enabled] end - 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: current_user, - expires_at: expires_at - ) - end + def add_users(users, access_level, current_user: nil, expires_at: nil) + GroupMember.add_users_to_group( + self, + users, + access_level, + current_user: current_user, + expires_at: expires_at + ) end 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) + GroupMember.add_user( + self, + 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: current_user) + add_user(user, :guest, current_user: current_user) end def add_reporter(user, current_user = nil) - add_user(user, Gitlab::Access::REPORTER, current_user: current_user) + add_user(user, :reporter, current_user: current_user) end def add_developer(user, current_user = nil) - add_user(user, Gitlab::Access::DEVELOPER, current_user: current_user) + add_user(user, :developer, current_user: current_user) end def add_master(user, current_user = nil) - add_user(user, Gitlab::Access::MASTER, current_user: current_user) + add_user(user, :master, current_user: current_user) end def add_owner(user, current_user = nil) - add_user(user, Gitlab::Access::OWNER, current_user: current_user) + add_user(user, :owner, current_user: current_user) end def has_owner?(user) diff --git a/app/models/member.rb b/app/models/member.rb index 69406379948..38a278ea559 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -80,49 +80,70 @@ class Member < ActiveRecord::Base find_by(invite_token: invite_token) end - # This method is used to find users that have been entered into the "Add members" field. - # These can be the User objects directly, their IDs, their emails, or new emails to be invited. - def user_for_id(user_id) - return user_id if user_id.is_a?(User) - - user = User.find_by(id: user_id) - user ||= User.find_by(email: user_id) - user ||= user_id - user - end - - def add_user(members, user_id, access_level, current_user: nil, expires_at: nil) - user = user_for_id(user_id) + def add_user(source, user, access_level, current_user: nil, expires_at: nil) + user = retrieve_user(user) + access_level = retrieve_access_level(access_level) # `user` can be either a User object or an email to be invited - if user.is_a?(User) - member = members.find_or_initialize_by(user_id: user.id) + member = + if user.is_a?(User) + source.members.find_by(user_id: user.id) || + source.requesters.find_by(user_id: user.id) || + source.members.build(user_id: user.id) + else + source.members.build(invite_email: user) + end + + return member unless can_update_member?(current_user, member) + + member.attributes = { + created_by: member.created_by || current_user, + access_level: access_level, + expires_at: expires_at + } + + if member.request? + ::Members::ApproveAccessRequestService.new(source, current_user, id: member.id).execute else - member = members.build - member.invite_email = user + member.save end - 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 + end - member.save - end + def access_levels + Gitlab::Access.sym_options end private + # This method is used to find users that have been entered into the "Add members" field. + # These can be the User objects directly, their IDs, their emails, or new emails to be invited. + def retrieve_user(user) + return user if user.is_a?(User) + + User.find_by(id: user) || User.find_by(email: user) || user + end + + def retrieve_access_level(access_level) + access_levels.fetch(access_level) { access_level.to_i } + end + def can_update_member?(current_user, member) # There is no current user for bulk actions, in which case anything is allowed - !current_user || - current_user.can?(:update_group_member, member) || - current_user.can?(:update_project_member, member) + !current_user || current_user.can?(:"update_#{member.type.underscore}", member) end - def project_creator?(member, access_level) - member.new_record? && member.owner? && - access_level.to_i == ProjectMember::MASTER + def add_users_to_source(source, users, access_level, current_user: nil, expires_at: nil) + users.each do |user| + add_user( + source, + user, + access_level, + current_user: current_user, + expires_at: expires_at + ) + end end end diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 2f13d339c89..1b54a85d064 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -12,6 +12,22 @@ class GroupMember < Member Gitlab::Access.options_with_owner end + def self.access_levels + Gitlab::Access.sym_options_with_owner + end + + def self.add_users_to_group(group, users, access_level, current_user: nil, expires_at: nil) + self.transaction do + add_users_to_source( + group, + users, + access_level, + current_user: current_user, + expires_at: expires_at + ) + end + end + def group source end diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index ec2d40eb11c..125f26369d7 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -34,36 +34,20 @@ class ProjectMember < Member # :master # ) # - 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) - access - else - raise "Non valid access" - end - - users = user_ids.map { |user_id| Member.user_for_id(user_id) } - - ProjectMember.transaction do + def add_users_to_projects(project_ids, users, access_level, current_user: nil, expires_at: nil) + self.transaction do project_ids.each do |project_id| project = Project.find(project_id) - users.each do |user| - Member.add_user( - project.project_members, - user, - access_level, - current_user: current_user, - expires_at: expires_at - ) - end + add_users_to_source( + project, + users, + access_level, + current_user: current_user, + expires_at: expires_at + ) end end - - true - rescue - false end def truncate_teams(project_ids) @@ -84,13 +68,15 @@ class ProjectMember < Member truncate_teams [project.id] end - def roles_hash - Gitlab::Access.sym_options - end - def access_level_roles Gitlab::Access.options end + + private + + def can_update_member?(current_user, member) + super || (member.owner? && member.new_record?) + end end def access_field diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 616efaf3c42..a431d46cc9e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -155,6 +155,20 @@ class MergeRequest < ActiveRecord::Base where("merge_requests.id IN (#{union.to_sql})") end + WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze + + def self.work_in_progress?(title) + !!(title =~ WIP_REGEX) + end + + def self.wipless_title(title) + title.sub(WIP_REGEX, "") + end + + def self.wip_title(title) + work_in_progress?(title) ? title : "WIP: #{title}" + end + def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" @@ -389,14 +403,16 @@ class MergeRequest < ActiveRecord::Base @closed_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last end - WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze - def work_in_progress? - !!(title =~ WIP_REGEX) + self.class.work_in_progress?(title) end def wipless_title - self.title.sub(WIP_REGEX, "") + self.class.wipless_title(self.title) + end + + def wip_title + self.class.wip_title(self.title) end def mergeable?(skip_ci_check: false) @@ -590,13 +606,11 @@ class MergeRequest < ActiveRecord::Base end def merge_commit_message - message = "Merge branch '#{source_branch}' into '#{target_branch}'" - message << "\n\n" - message << title.to_s - message << "\n\n" - message << description.to_s - message << "\n\n" - message << "See merge request !#{iid}" + message = "Merge branch '#{source_branch}' into '#{target_branch}'\n\n" + message << "#{title}\n\n" + message << "#{description}\n\n" if description.present? + message << "See merge request #{to_reference}" + message end @@ -670,9 +684,12 @@ class MergeRequest < ActiveRecord::Base def environments return [] unless diff_head_commit - target_project.environments.select do |environment| - environment.includes_commit?(diff_head_commit) - end + environments = source_project.environments_for( + source_branch, diff_head_commit) + environments += target_project.environments_for( + target_branch, diff_head_commit, with_tags: true) + + environments.uniq end def state_human_name @@ -761,10 +778,23 @@ class MergeRequest < ActiveRecord::Base end def all_pipelines - @all_pipelines ||= - if diff_head_sha && source_project - source_project.pipelines.order(id: :desc).where(sha: commits_sha, ref: source_branch) - end + return unless source_project + + @all_pipelines ||= begin + sha = if persisted? + all_commits_sha + else + diff_head_sha + end + + source_project.pipelines.order(id: :desc). + where(sha: sha, ref: source_branch) + end + end + + # Note that this could also return SHA from now dangling commits + def all_commits_sha + merge_request_diffs.flat_map(&:commits_sha).uniq end def merge_commit diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 18c583add88..36b8b70870b 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -30,6 +30,10 @@ class MergeRequestDiff < ActiveRecord::Base select(column_names - ['st_diffs']) end + def st_commits + super || [] + end + # Collect information about commits and diff from repository # and save it to the database as serialized data def save_git_content @@ -83,7 +87,7 @@ class MergeRequestDiff < ActiveRecord::Base end def commits - @commits ||= load_commits(st_commits || []) + @commits ||= load_commits(st_commits) end def reload_commits @@ -117,6 +121,14 @@ class MergeRequestDiff < ActiveRecord::Base project.commit(head_commit_sha) end + def commits_sha + if @commits + commits.map(&:sha) + else + st_commits.map { |commit| commit[:id] } + end + end + def diff_refs return unless start_commit_sha || base_commit_sha diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 2bd7f198030..44c3cbb2c73 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -158,7 +158,7 @@ class Milestone < ActiveRecord::Base end def title=(value) - write_attribute(:title, Sanitize.clean(value.to_s)) if value.present? + write_attribute(:title, sanitize_title(value)) if value.present? end # Sorts the issues for the given IDs. @@ -204,4 +204,8 @@ class Milestone < ActiveRecord::Base iid end end + + def sanitize_title(value) + CGI.unescape_html(Sanitize.clean(value.to_s)) + end end diff --git a/app/models/note.rb b/app/models/note.rb index b94e3cff2ce..f2656df028b 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -223,10 +223,6 @@ class Note < ActiveRecord::Base end end - def user_authored?(user) - user == author - end - def award_emoji? can_be_award_emoji? && contains_emoji_only? end diff --git a/app/models/project.rb b/app/models/project.rb index e4b8e708884..b34e320cd34 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -147,6 +147,7 @@ class Project < ActiveRecord::Base delegate :name, to: :owner, allow_nil: true, prefix: true delegate :members, to: :team, prefix: true + delegate :add_user, to: :team # Validations validates :creator, presence: true, on: :create @@ -1017,10 +1018,6 @@ class Project < ActiveRecord::Base project_members.find_by(user_id: user) end - 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 @default_branch ||= repository.root_ref if repository.exists? end @@ -1294,6 +1291,22 @@ class Project < ActiveRecord::Base Gitlab::Redis.with { |redis| redis.del(pushes_since_gc_redis_key) } end + def environments_for(ref, commit, with_tags: false) + environment_ids = deployments.group(:environment_id). + select(:environment_id) + + environment_ids = + if with_tags + environment_ids.where('ref=? OR tag IS TRUE', ref) + else + environment_ids.where(ref: ref) + end + + environments.where(id: environment_ids).select do |environment| + environment.includes_commit?(commit) + end + end + private def pushes_since_gc_redis_key diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index 9c602c582bd..8c9534c3565 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -22,6 +22,12 @@ class ProjectFeature < ActiveRecord::Base belongs_to :project + default_value_for :builds_access_level, value: ENABLED, allows_nil: false + default_value_for :issues_access_level, value: ENABLED, allows_nil: false + default_value_for :merge_requests_access_level, value: ENABLED, allows_nil: false + default_value_for :snippets_access_level, value: ENABLED, allows_nil: false + default_value_for :wiki_access_level, value: ENABLED, allows_nil: false + def feature_available?(feature, user) raise ArgumentError, 'invalid project feature' unless FEATURES.include?(feature) diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb index 63a5ed14484..d9fba3d4a41 100644 --- a/app/models/project_services/custom_issue_tracker_service.rb +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -9,6 +9,10 @@ class CustomIssueTrackerService < IssueTrackerService end end + def title=(value) + self.properties['title'] = value if self.properties + end + def description if self.properties && self.properties['description'].present? self.properties['description'] diff --git a/app/models/project_services/slack_service/issue_message.rb b/app/models/project_services/slack_service/issue_message.rb index 88e053ec192..cd87a79d0c6 100644 --- a/app/models/project_services/slack_service/issue_message.rb +++ b/app/models/project_services/slack_service/issue_message.rb @@ -11,7 +11,7 @@ class SlackService attr_reader :description def initialize(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] diff --git a/app/models/project_services/slack_service/merge_message.rb b/app/models/project_services/slack_service/merge_message.rb index 11fc691022b..b7615c96068 100644 --- a/app/models/project_services/slack_service/merge_message.rb +++ b/app/models/project_services/slack_service/merge_message.rb @@ -10,7 +10,7 @@ class SlackService attr_reader :title def initialize(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] diff --git a/app/models/project_services/slack_service/note_message.rb b/app/models/project_services/slack_service/note_message.rb index 89ba51cb662..9e84e90f38c 100644 --- a/app/models/project_services/slack_service/note_message.rb +++ b/app/models/project_services/slack_service/note_message.rb @@ -10,7 +10,7 @@ class SlackService def initialize(params) params = HashWithIndifferentAccess.new(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] diff --git a/app/models/project_services/slack_service/wiki_page_message.rb b/app/models/project_services/slack_service/wiki_page_message.rb index f336d9e7691..160ca3ac115 100644 --- a/app/models/project_services/slack_service/wiki_page_message.rb +++ b/app/models/project_services/slack_service/wiki_page_message.rb @@ -9,7 +9,7 @@ class SlackService attr_reader :description def initialize(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] diff --git a/app/models/project_team.rb b/app/models/project_team.rb index ab6ea2aae36..79d041d2775 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -33,18 +33,24 @@ class ProjectTeam member end - def add_users(users, access, current_user: nil, expires_at: nil) + def add_users(users, access_level, current_user: nil, expires_at: nil) ProjectMember.add_users_to_projects( [project.id], users, - access, + access_level, current_user: current_user, expires_at: expires_at ) end - def add_user(user, access, current_user: nil, expires_at: nil) - add_users([user], access, current_user: current_user, expires_at: expires_at) + def add_user(user, access_level, current_user: nil, expires_at: nil) + ProjectMember.add_user( + project, + user, + access_level, + current_user: current_user, + expires_at: expires_at + ) end # Remove all users from project team @@ -163,7 +169,7 @@ class ProjectTeam # Each group produces a list of maximum access level per user. We take the # max of the values produced by each group. - if project.invited_groups.any? && project.allowed_to_share_with_group? + if project_shared_with_group? project.project_group_links.each do |group_link| invited_access = max_invited_level_for_users(group_link, user_ids) merge_max!(access, invited_access) @@ -200,43 +206,17 @@ class ProjectTeam def fetch_members(level = nil) project_members = project.members group_members = group ? group.members : [] - invited_members = [] - - if project.invited_groups.any? && project.allowed_to_share_with_group? - project.project_group_links.includes(group: [:group_members]).each do |group_link| - invited_group = group_link.group - im = invited_group.members - - if level - int_level = GroupMember.access_level_roles[level.to_s.singularize.titleize] - - # Skip group members if we ask for masters - # but max group access is developers - next if int_level > group_link.group_access - - # If we ask for developers and max - # group access is developers we need to provide - # both group master, developers as devs - if int_level == group_link.group_access - im.where("access_level >= ?)", group_link.group_access) - else - im.send(level) - end - end - - invited_members << im - end - - invited_members = invited_members.flatten.compact - end if level - project_members = project_members.send(level) - group_members = group_members.send(level) if group + project_members = project_members.public_send(level) + group_members = group_members.public_send(level) if group end user_ids = project_members.pluck(:user_id) + + invited_members = fetch_invited_members(level) user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? + user_ids.push(*group_members.pluck(:user_id)) if group User.where(id: user_ids) @@ -249,4 +229,38 @@ class ProjectTeam def merge_max!(first_hash, second_hash) first_hash.merge!(second_hash) { |_key, old, new| old > new ? old : new } end + + def project_shared_with_group? + project.invited_groups.any? && project.allowed_to_share_with_group? + end + + def fetch_invited_members(level = nil) + invited_members = [] + + return invited_members unless project_shared_with_group? + + project.project_group_links.includes(group: [:group_members]).each do |link| + invited_group_members = link.group.members + + if level + numeric_level = GroupMember.access_level_roles[level.to_s.singularize.titleize] + + # If we're asked for a level that's higher than the group's access, + # there's nothing left to do + next if numeric_level > link.group_access + + # Make sure we include everyone _above_ the requested level as well + invited_group_members = + if numeric_level == link.group_access + invited_group_members.where("access_level >= ?", link.group_access) + else + invited_group_members.public_send(level) + end + end + + invited_members << invited_group_members + end + + invited_members.flatten.compact + end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 772c62a4124..51557228ab9 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -840,7 +840,7 @@ class Repository def get_committer_and_author(user, email: nil, name: nil) committer = user_to_committer(user) - author = name && email ? Gitlab::Git::committer_hash(email: email, name: name) : committer + author = Gitlab::Git::committer_hash(email: email, name: name) || committer { author: author, diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 5ec933601ac..8a1730f3f36 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -4,6 +4,7 @@ class Snippet < ActiveRecord::Base include Participable include Referable include Sortable + include Awardable default_value_for :visibility_level, Snippet::PRIVATE diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 98da6563947..8ea88da8a53 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -5,12 +5,12 @@ module Auth AUDIENCE = 'container_registry' def execute(authentication_abilities:) - @authentication_abilities = authentication_abilities || [] + @authentication_abilities = authentication_abilities - return error('not found', 404) unless registry.enabled + return error('UNAVAILABLE', status: 404, message: 'registry not enabled') unless registry.enabled unless current_user || project - return error('forbidden', 403) unless scope + return error('DENIED', status: 403, message: 'access forbidden') unless scope end { token: authorized_token(scope).encoded } @@ -111,5 +111,12 @@ module Auth @authentication_abilities.include?(:create_container_image) && can?(current_user, :create_container_image, requested_project) end + + def error(code, status:, message: '') + { + errors: [{ code: code, message: message }], + http_status: status + } + end end end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index fbce46769f7..57d521f2fea 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -50,6 +50,7 @@ class IssuableBaseService < BaseService params.delete(:remove_label_ids) params.delete(:label_ids) params.delete(:assignee_id) + params.delete(:due_date) end end diff --git a/app/services/members/approve_access_request_service.rb b/app/services/members/approve_access_request_service.rb new file mode 100644 index 00000000000..416aee2ab51 --- /dev/null +++ b/app/services/members/approve_access_request_service.rb @@ -0,0 +1,31 @@ +module Members + class ApproveAccessRequestService < BaseService + include MembersHelper + + attr_accessor :source + + def initialize(source, current_user, params = {}) + @source = source + @current_user = current_user + @params = params + end + + def execute + condition = params[:user_id] ? { user_id: params[:user_id] } : { id: params[:id] } + access_requester = source.requesters.find_by!(condition) + + raise Gitlab::Access::AccessDeniedError unless can_update_access_requester?(access_requester) + + access_requester.access_level = params[:access_level] if params[:access_level] + access_requester.accept_request + + access_requester + end + + private + + def can_update_access_requester?(access_requester) + access_requester && can?(current_user, action_member_permission(:update, access_requester), access_requester) + end + end +end diff --git a/app/services/members/request_access_service.rb b/app/services/members/request_access_service.rb new file mode 100644 index 00000000000..2614153d900 --- /dev/null +++ b/app/services/members/request_access_service.rb @@ -0,0 +1,25 @@ +module Members + class RequestAccessService < BaseService + attr_accessor :source + + def initialize(source, current_user) + @source = source + @current_user = current_user + end + + def execute + raise Gitlab::Access::AccessDeniedError unless can_request_access?(source) + + source.members.create( + access_level: Gitlab::Access::DEVELOPER, + user: current_user, + requested_at: Time.now.utc) + end + + private + + def can_request_access?(source) + source && can?(current_user, :request_access, source) + end + end +end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index ba424b09463..d0d155b7ee1 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -5,16 +5,17 @@ module MergeRequests end def create_title_change_note(issuable, old_title) - removed_wip = old_title =~ MergeRequest::WIP_REGEX && !issuable.work_in_progress? - added_wip = old_title !~ MergeRequest::WIP_REGEX && issuable.work_in_progress? + removed_wip = MergeRequest.work_in_progress?(old_title) && !issuable.work_in_progress? + added_wip = !MergeRequest.work_in_progress?(old_title) && issuable.work_in_progress? + changed_title = MergeRequest.wipless_title(old_title) != issuable.wipless_title if removed_wip SystemNoteService.remove_merge_request_wip(issuable, issuable.project, current_user) elsif added_wip SystemNoteService.add_merge_request_wip(issuable, issuable.project, current_user) - else - super end + + super if changed_title end def hook_data(merge_request, action, oldrev = nil) diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb index 8437d9b8b43..e8fb1b59752 100644 --- a/app/services/merge_requests/post_merge_service.rb +++ b/app/services/merge_requests/post_merge_service.rb @@ -7,6 +7,7 @@ module MergeRequests class PostMergeService < MergeRequests::BaseService def execute(merge_request) close_issues(merge_request) + todo_service.merge_merge_request(merge_request, current_user) merge_request.mark_as_merged create_merge_event(merge_request, current_user) create_note(merge_request) diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index f14f9e4b327..9dbec49d163 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -16,7 +16,7 @@ module MergeRequests end merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) - + handle_wip_event(merge_request) update(merge_request) end @@ -81,5 +81,18 @@ module MergeRequests def after_update(issuable) issuable.cache_merge_request_closes_issues!(current_user) end + + private + + def handle_wip_event(merge_request) + if wip_event = params.delete(:wip_event) + # We update the title that is provided in the params or we use the mr title + title = params[:title] || merge_request.title + params[:title] = case wip_event + when 'wip' then MergeRequest.wip_title(title) + when 'unwip' then MergeRequest.wipless_title(title) + end + end + end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 6139ed56e25..de8049b8e2e 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -134,7 +134,8 @@ class NotificationService merge_request, merge_request.target_project, current_user, - :merged_merge_request_email + :merged_merge_request_email, + skip_current_user: !merge_request.merge_when_build_succeeds? ) end @@ -514,9 +515,16 @@ class NotificationService end end - def close_resource_email(target, project, current_user, method) + def close_resource_email(target, project, current_user, method, skip_current_user: true) action = method == :merged_merge_request_email ? "merge" : "close" - recipients = build_recipients(target, project, current_user, action: action) + + recipients = build_recipients( + target, + project, + current_user, + action: action, + skip_current_user: skip_current_user + ) recipients.each do |recipient| mailer.send(method, recipient.id, target.id, current_user.id).deliver_later @@ -557,7 +565,7 @@ class NotificationService end end - def build_recipients(target, project, current_user, action: nil, previous_assignee: nil) + def build_recipients(target, project, current_user, action: nil, previous_assignee: nil, skip_current_user: true) custom_action = build_custom_key(action, target) recipients = target.participants(current_user) @@ -586,7 +594,8 @@ class NotificationService recipients = reject_unsubscribed_users(recipients, target) recipients = reject_users_without_access(recipients, target) - recipients.delete(current_user) + recipients.delete(current_user) if skip_current_user + recipients.uniq end diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index cdad0426b02..e466ffa60eb 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -44,6 +44,11 @@ module Projects begin gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url) rescue => e + # Expire cache to prevent scenarios such as: + # 1. First import failed, but the repo was imported successfully, so +exists?+ returns true + # 2. Retried import, repo is broken or not imported but +exists?+ still returns true + project.repository.before_import if project.repository_exists? + raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}" end end diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb index 9ac1124abc1..1725a30fae5 100644 --- a/app/services/slash_commands/interpret_service.rb +++ b/app/services/slash_commands/interpret_service.rb @@ -195,7 +195,7 @@ module SlashCommands params '<in 2 days | this Friday | December 31st>' condition do issuable.respond_to?(:due_date) && - current_user.can?(:"update_#{issuable.to_ability_name}", issuable) + current_user.can?(:"admin_#{issuable.to_ability_name}", project) end command :due do |due_date_param| due_date = Chronic.parse(due_date_param).try(:to_date) @@ -208,12 +208,24 @@ module SlashCommands issuable.persisted? && issuable.respond_to?(:due_date) && issuable.due_date? && - current_user.can?(:"update_#{issuable.to_ability_name}", issuable) + current_user.can?(:"admin_#{issuable.to_ability_name}", project) end command :remove_due_date do @updates[:due_date] = nil end + desc do + "Toggle the Work In Progress status" + end + condition do + issuable.persisted? && + issuable.respond_to?(:work_in_progress?) && + current_user.can?(:"update_#{issuable.to_ability_name}", issuable) + end + command :wip do + @updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip' + end + # This is a dummy command, so that it appears in the autocomplete commands desc 'CC' params '@user' diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 1fb72cf89e9..a2bfa422c9d 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -72,7 +72,7 @@ class SystemHooksService return 'user_add_to_group' if event == :create return 'user_remove_from_group' if event == :destroy else - "#{model.class.name.downcase}_#{event.to_s}" + "#{model.class.name.downcase}_#{event}" end end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 0c8446e7c3d..bf251816e7e 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -24,6 +24,7 @@ module SystemNoteService body = "Added #{commits_text}:\n\n" body << existing_commit_summary(noteable, existing_commits, oldrev) body << new_commit_summary(new_commits).join("\n") + body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -245,7 +246,7 @@ module SystemNoteService 'deleted' end - body = "#{verb} #{branch_type.to_s} branch `#{branch}`".capitalize + body = "#{verb} #{branch_type} branch `#{branch}`".capitalize create_note(noteable: noteable, project: project, author: author, note: body) end @@ -254,8 +255,7 @@ module SystemNoteService # # "Started branch `201-issue-branch-button`" def new_issue_branch(issue, project, author, branch) - h = Gitlab::Routing.url_helpers - link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch) + link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch) body = "Started branch [`#{branch}`](#{link})" create_note(noteable: issue, project: project, author: author, note: body) @@ -466,4 +466,20 @@ module SystemNoteService def escape_html(text) Rack::Utils.escape_html(text) end + + def url_helpers + @url_helpers ||= Gitlab::Routing.url_helpers + end + + def diff_comparison_url(merge_request, project, oldrev) + diff_id = merge_request.merge_request_diff.id + + url_helpers.diffs_namespace_project_merge_request_url( + project.namespace, + project, + merge_request.iid, + diff_id: diff_id, + start_sha: oldrev + ) + end end diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb index 7a35958cc5f..4dc3b2ab9a0 100644 --- a/app/validators/namespace_validator.rb +++ b/app/validators/namespace_validator.rb @@ -5,7 +5,8 @@ # Values are checked for formatting and exclusion from a list of reserved path # names. class NamespaceValidator < ActiveModel::EachValidator - RESERVED = %w( + RESERVED = %w[ + .well-known admin all assets @@ -31,7 +32,7 @@ class NamespaceValidator < ActiveModel::EachValidator u unsubscribes users - ).freeze + ].freeze def validate_each(record, attribute, value) unless value =~ Gitlab::Regex.namespace_regex diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index d929364fc96..0d79ca7dc52 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -49,28 +49,6 @@ = select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control') %span.help-block#clone-protocol-help Allow only the selected protocols to be used for Git access. - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox - = f.label :version_check_enabled do - = f.check_box :version_check_enabled - Version check enabled - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox - = f.label :email_author_in_body do - = f.check_box :email_author_in_body - Include author name in notification email body - .help-block - Some email servers do not support overriding the email sender name. - Enable this option to include the name of the author of the issue, - merge request or comment in the email body instead. - .form-group - = f.label :admin_notification_email, class: 'control-label col-sm-2' - .col-sm-10 - = f.text_field :admin_notification_email, class: 'form-control' - .help-block - Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area. %fieldset %legend Account and Limit Settings @@ -341,6 +319,15 @@ %a{ href: 'http://www.akismet.com', target: 'blank'} http://www.akismet.com %fieldset + %legend Abuse reports + .form-group + = f.label :admin_notification_email, 'Abuse reports notification email', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_field :admin_notification_email, class: 'form-control' + .help-block + Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area. + + %fieldset %legend Error Reporting and Logging %p These settings require a restart to take effect. @@ -407,6 +394,29 @@ = succeed "." do = link_to "Koding administration documentation", help_page_path("administration/integration/koding") + %fieldset + %legend Usage statistics + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :version_check_enabled do + = f.check_box :version_check_enabled + Version check enabled + .help-block + Let GitLab inform you when an update is available. + + %fieldset + %legend Email + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :email_author_in_body do + = f.check_box :email_author_in_body + Include author name in notification email body + .help-block + Some email servers do not support overriding the email sender name. + Enable this option to include the name of the author of the issue, + merge request or comment in the email body instead. .form-actions = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/admin/background_jobs/_head.html.haml b/app/views/admin/background_jobs/_head.html.haml index 107fc25244a..b3530915068 100644 --- a/app/views/admin/background_jobs/_head.html.haml +++ b/app/views/admin/background_jobs/_head.html.haml @@ -1,24 +1,25 @@ -.scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } - = nav_link(controller: :system_info) do - = link_to admin_system_info_path, title: 'System Info' do - %span - System Info - = nav_link(controller: :background_jobs) do - = link_to admin_background_jobs_path, title: 'Background Jobs' do - %span - Background Jobs - = nav_link(controller: :logs) do - = link_to admin_logs_path, title: 'Logs' do - %span - Logs - = nav_link(controller: :health_check) do - = link_to admin_health_check_path, title: 'Health Check' do - %span - Health Check - = nav_link(controller: :requests_profiles) do - = link_to admin_requests_profiles_path, title: 'Requests Profiles' do - %span - Requests Profiles += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + = nav_link(controller: :system_info) do + = link_to admin_system_info_path, title: 'System Info' do + %span + System Info + = nav_link(controller: :background_jobs) do + = link_to admin_background_jobs_path, title: 'Background Jobs' do + %span + Background Jobs + = nav_link(controller: :logs) do + = link_to admin_logs_path, title: 'Logs' do + %span + Logs + = nav_link(controller: :health_check) do + = link_to admin_health_check_path, title: 'Health Check' do + %span + Health Check + = nav_link(controller: :requests_profiles) do + = link_to admin_requests_profiles_path, title: 'Requests Profiles' do + %span + Requests Profiles diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml index 6b157abf842..f952d2e9aa1 100644 --- a/app/views/admin/broadcast_messages/_form.html.haml +++ b/app/views/admin/broadcast_messages/_form.html.haml @@ -18,11 +18,11 @@ .form-group.js-toggle-colors-container.hide = f.label :color, "Background Color", class: 'control-label' .col-sm-10 - = f.color_field :color, class: "form-control" + = f.text_field :color, class: "form-control" .form-group.js-toggle-colors-container.hide = f.label :font, "Font Color", class: 'control-label' .col-sm-10 - = f.color_field :font, class: "form-control" + = f.text_field :font, class: "form-control" .form-group = f.label :starts_at, class: 'control-label' .col-sm-10.datetime-controls diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml index c91ab4cb946..ec40391a3e3 100644 --- a/app/views/admin/dashboard/_head.html.haml +++ b/app/views/admin/dashboard/_head.html.haml @@ -1,28 +1,29 @@ -.scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } - = nav_link(controller: :dashboard, html_options: {class: 'home'}) do - = link_to admin_root_path, title: 'Overview' do - %span - Overview - = nav_link(controller: [:admin, :projects]) do - = link_to admin_namespaces_projects_path, title: 'Projects' do - %span - Projects - = nav_link(controller: :users) do - = link_to admin_users_path, title: 'Users' do - %span - Users - = nav_link(controller: :groups) do - = link_to admin_groups_path, title: 'Groups' do - %span - Groups - = nav_link path: 'builds#index' do - = link_to admin_builds_path, title: 'Builds' do - %span - Builds - = nav_link path: ['runners#index', 'runners#show'] do - = link_to admin_runners_path, title: 'Runners' do - %span - Runners += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + = nav_link(controller: :dashboard, html_options: {class: 'home'}) do + = link_to admin_root_path, title: 'Overview' do + %span + Overview + = nav_link(controller: [:admin, :projects]) do + = link_to admin_namespaces_projects_path, title: 'Projects' do + %span + Projects + = nav_link(controller: :users) do + = link_to admin_users_path, title: 'Users' do + %span + Users + = nav_link(controller: :groups) do + = link_to admin_groups_path, title: 'Groups' do + %span + Groups + = nav_link path: 'builds#index' do + = link_to admin_builds_path, title: 'Builds' do + %span + Builds + = nav_link path: ['runners#index', 'runners#show'] do + = link_to admin_runners_path, title: 'Runners' do + %span + Runners diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml index 602cfa9b6fc..d5e6bede36a 100644 --- a/app/views/admin/labels/_form.html.haml +++ b/app/views/admin/labels/_form.html.haml @@ -14,7 +14,7 @@ .col-sm-10 .input-group .input-group-addon.label-color-preview - = f.color_field :color, class: "form-control" + = f.text_field :color, class: "form-control" .help-block Choose any color. %br diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index a53876d6757..b760b42fde0 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -5,8 +5,10 @@ %p.prepend-top-default %span - To register a new runner you should enter the following registration token. - With this token the runner will request a unique runner token and use that for future communication. + To register a new Runner you should enter the following registration + token. + With this token the Runner will request a unique Runner token and use + that for future communication. %br Registration token is %code{ id: 'runners-token' } #{current_application_settings.runners_registration_token} @@ -24,27 +26,27 @@ .bs-callout %p - A 'runner' is a process which runs a build. - You can setup as many runners as you need. + A 'Runner' is a process which runs a build. + You can setup as many Runners as you need. %br - Runners can be placed on separate users, servers, and even on your local machine. + Runners can be placed on separate users, servers, even on your local machine. %br %div - %span Each runner can be in one of the following states: + %span Each Runner can be in one of the following states: %ul %li %span.label.label-success shared - \- run builds from all unassigned projects + \- Runner runs builds from all unassigned projects %li %span.label.label-info specific - \- run builds from assigned projects + \- Runner runs builds from assigned projects %li %span.label.label-warning locked - \- runner cannot be assigned to other projects + \- Runner cannot be assigned to other projects %li %span.label.label-danger paused - \- runner will not receive any new builds + \- Runner will not receive any new builds .append-bottom-20.clearfix .pull-left diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 61abfc6ecbe..a5e82e55cc1 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -11,14 +11,14 @@ - if @runner.shared? .bs-callout.bs-callout-success - %h4 This runner will process builds from ALL UNASSIGNED projects + %h4 This Runner will process builds from ALL UNASSIGNED projects %p - If you want runners to build only specific projects, enable them in the table below. + If you want Runners to build only specific projects, enable them in the table below. Keep in mind that this is a one way transition. - else .bs-callout.bs-callout-info - %h4 This runner will process builds only from ASSIGNED projects - %p You can't make this a shared runner. + %h4 This Runner will process builds only from ASSIGNED projects + %p You can't make this a shared Runner. %hr .append-bottom-20 @@ -26,7 +26,7 @@ .row .col-md-6 - %h4 Restrict projects for this runner + %h4 Restrict projects for this Runner - if @runner.projects.any? %table.table.assigned-projects %thead @@ -70,7 +70,7 @@ = paginate @projects .col-md-6 - %h4 Recent builds served by this runner + %h4 Recent builds served by this Runner %table.table.builds.runner-builds %thead %tr diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml index 02efcecc889..fbe3ab912b6 100644 --- a/app/views/award_emoji/_awards_block.html.haml +++ b/app/views/award_emoji/_awards_block.html.haml @@ -1,5 +1,5 @@ - grouped_emojis = awardable.grouped_awards(with_thumbs: inline) -.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) } } +.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } } - awards_sort(grouped_emojis).each do |emoji, awards| %button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", class: (award_active_class(awards, current_user)), data: { placement: "bottom", title: award_user_list(awards, current_user) } } = emoji_icon(emoji, sprite: false) diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml index f7875e68b7e..d5c21c6dffe 100644 --- a/app/views/ci/lints/_create.html.haml +++ b/app/views/ci/lints/_create.html.haml @@ -21,13 +21,16 @@ %br %b Tag list: - = build[:tags] + = build[:tag_list].to_a.join(", ") %br %b Refs only: - = build[:only] && build[:only].join(", ") + = @jobs[build[:name].to_sym][:only].to_a.join(", ") %br %b Refs except: - = build[:except] && build[:except].join(", ") + = @jobs[build[:name].to_sym][:except].to_a.join(", ") + %br + %b Environment: + = build[:environment] %br %b When: = build[:when] diff --git a/app/views/dashboard/groups/_empty_state.html.haml b/app/views/dashboard/groups/_empty_state.html.haml new file mode 100644 index 00000000000..f5222fe631e --- /dev/null +++ b/app/views/dashboard/groups/_empty_state.html.haml @@ -0,0 +1,7 @@ +.groups-empty-state + = custom_icon("icon_empty_groups") + + .text-content + %h4 A group is a collection of several projects. + %p If you organize your projects under a group, it works like a folder. + %p You can manage your group member’s permissions and access to each project in the group. diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index caca91af536..1a679c51774 100644 --- a/app/views/dashboard/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -2,9 +2,12 @@ - header_title "Groups", dashboard_groups_path = render 'dashboard/groups_head' -%ul.content-list - - @group_members.each do |group_member| - - group = group_member.group - = render 'shared/groups/group', group: group, group_member: group_member +- if @group_members.empty? + = render 'empty_state' +- else + %ul.content-list + - @group_members.each do |group_member| + - group = group_member.group + = render 'shared/groups/group', group: group, group_member: group_member -= paginate @group_members, theme: 'gitlab' + = paginate @group_members, theme: 'gitlab' diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index d4e7862981c..b2af438ea57 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -4,10 +4,10 @@ = render 'dashboard/snippets_head' .nav-block - .controls - = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do + .controls.hidden-xs + = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do = icon('plus') - New Snippet + New snippet .nav-links.snippet-scope-menu %li{ class: ("active" unless params[:scope]) } @@ -34,5 +34,9 @@ %span.badge = current_user.snippets.are_public.count -= render 'snippets/snippets' + .visible-xs + = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do + = icon('plus') + New snippet += render 'snippets/snippets' diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 689cd6ed665..2ef383960f4 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,4 +1,4 @@ -= form_tag(user_omniauth_callback_path(server['provider_name']), id: 'new_ldap_user' ) do += form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user') do = text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"} = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} - if devise_mapping.rememberable? diff --git a/app/views/discussions/_jump_to_next.html.haml b/app/views/discussions/_jump_to_next.html.haml index da970792b4d..7ed09dd1a98 100644 --- a/app/views/discussions/_jump_to_next.html.haml +++ b/app/views/discussions/_jump_to_next.html.haml @@ -1,4 +1,3 @@ -- diff_notes_disabled = (@merge_request_diff.latest? && !!@start_sha) if @merge_request_diff - discussion = local_assigns.fetch(:discussion, nil) - if current_user %jump-to-discussion{ "inline-template" => true, ":discussion-id" => "'#{discussion.try(:id)}'" } @@ -6,6 +5,5 @@ %button.btn.btn-default.discussion-next-btn.has-tooltip{ "@click" => "jumpToNextUnresolvedDiscussion", title: "Jump to next unresolved discussion", "aria-label" => "Jump to next unresolved discussion", - data: { container: "body" }, - disabled: diff_notes_disabled } + data: { container: "body" }} = custom_icon("next_discussion") diff --git a/app/views/discussions/_resolve_all.html.haml b/app/views/discussions/_resolve_all.html.haml index 7a8767ddba0..f0b61e0f7de 100644 --- a/app/views/discussions/_resolve_all.html.haml +++ b/app/views/discussions/_resolve_all.html.haml @@ -1,11 +1,10 @@ - if discussion.for_merge_request? - %resolve-discussion-btn{ ":namespace-path" => "'#{discussion.project.namespace.path}'", - ":project-path" => "'#{discussion.project.path}'", + %resolve-discussion-btn{ ":project-path" => "'#{project_path(discussion.project)}'", ":discussion-id" => "'#{discussion.id}'", ":merge-request-id" => discussion.noteable.iid, ":can-resolve" => discussion.can_resolve?(current_user), "inline-template" => true } .btn-group{ role: "group", "v-if" => "showButton" } - %button.btn.btn-default{ type: "button", "@click" => "resolve", ":disabled" => "loading" } + %button.btn.btn-default{ type: "button", "@click" => "resolve", ":disabled" => "loading", "v-cloak" => "true" } = icon("spinner spin", "v-show" => "loading") {{ buttonText }} diff --git a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml index bfa95ce79a7..9f02a8d2ed9 100644 --- a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml +++ b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml @@ -6,4 +6,4 @@ = form_tag path do %input{:name => "_method", :type => "hidden", :value => "delete"}/ - = submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-link btn-remove btn-sm' + = submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-remove btn-sm' diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index cd485da5104..132bbe26fe0 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -8,7 +8,7 @@ - else Any %b.caret - %ul.dropdown-menu + %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_projects_path(visibility_level: nil) do Any @@ -28,7 +28,7 @@ - else Any %b.caret - %ul.dropdown-menu + %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_projects_path(tag: nil) do Any diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml index 6306fe6d0bf..7def9eacdc9 100644 --- a/app/views/explore/snippets/index.html.haml +++ b/app/views/explore/snippets/index.html.haml @@ -8,9 +8,8 @@ .row-content-block - if current_user - .pull-right - = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do - New Snippet + = link_to new_snippet_path, class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do + New snippet .oneline Public snippets created by you and other users are listed here diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 53ed4fa991d..31db6ee0cad 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -23,7 +23,7 @@ .cover-desc.description = markdown(@group.description, pipeline: :description) -%div{ class: container_class } +%div.groups-header{ class: container_class } .top-area %ul.nav-links %li.active diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml index 3612f1ce5c6..baa8036de10 100644 --- a/app/views/layouts/_flash.html.haml +++ b/app/views/layouts/_flash.html.haml @@ -1,8 +1,10 @@ .flash-container.flash-container-page - if alert .flash-alert - = alert + %div{ class: (container_class) } + %span= alert - elsif notice .flash-notice - = notice + %div{ class: (container_class) } + %span= notice diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 4f7839a881f..8aefdcb3d9b 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -1,10 +1,11 @@ .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } .sidebar-wrapper.nicescroll .sidebar-action-buttons - = link_to '#', class: 'nav-header-btn toggle-nav-collapse', title: "Open/Close" do + .nav-header-btn.toggle-nav-collapse{ title: "Open/Close" } %span.sr-only Toggle navigation = icon('bars') - = link_to '#', class: "nav-header-btn pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: {placement: 'right', container: 'body'} do + + %div{ class: "nav-header-btn pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: { placement: 'right', container: 'body' } } %span.sr-only Toggle navigation pinning = icon('fw thumb-tack') @@ -20,6 +21,7 @@ .container-fluid = render "layouts/nav/#{nav}" .content-wrapper{ class: "#{layout_nav_class}" } + = yield :sub_nav = render "layouts/broadcast" = render "layouts/flash" = yield :flash_message diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index f7580f00159..d7386105b7d 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -2,15 +2,18 @@ - label = 'This group' - if controller.controller_path =~ /^projects/ && @project.persisted? - label = 'This project' - +- if @group && @group.persisted? && @group.path + - group_data_attrs = { group_path: j(@group.path), name: @group.name, issues_path: issues_group_path(j(@group.path)), mr_path: merge_requests_group_path(j(@group.path)) } +- if @project && @project.persisted? + - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: namespace_project_issues_path(@project.namespace, @project), mr_path: namespace_project_merge_requests_path(@project.namespace, @project) } .search.search-form{class: "#{'has-location-badge' if label.present?}"} = form_tag search_path, method: :get, class: 'navbar-form' do |f| .search-input-container - if label.present? .location-badge= label .search-input-wrap - .dropdown{ data: {url: search_autocomplete_path } } - = search_field_tag "search", nil, placeholder: 'Search', class: "search-input dropdown-menu-toggle", spellcheck: false, tabindex: "1", autocomplete: 'off', data: { toggle: 'dropdown' } + .dropdown{ data: { url: search_autocomplete_path } } + = search_field_tag 'search', nil, placeholder: 'Search', class: 'search-input dropdown-menu-toggle js-search-dashboard-options', spellcheck: false, tabindex: '1', autocomplete: 'off', data: { toggle: 'dropdown', issues_path: issues_dashboard_url, mr_path: merge_requests_dashboard_url } .dropdown-menu.dropdown-select = dropdown_content do %ul @@ -21,8 +24,9 @@ %i.search-icon %i.clear-icon.js-clear-input - = hidden_field_tag :group_id, @group.try(:id) - = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id' + = hidden_field_tag :group_id, @group.try(:id), class: 'js-search-group-options', data: group_data_attrs + + = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id', class: 'js-search-project-options', data: project_data_attrs - if @project && @project.persisted? - if current_controller?(:issues) @@ -36,31 +40,6 @@ - else = hidden_field_tag :search_code, true - :javascript - gl.projectOptions = gl.projectOptions || {}; - gl.projectOptions["#{j(@project.path)}"] = { - issuesPath: "#{namespace_project_issues_path(@project.namespace, @project)}", - mrPath: "#{namespace_project_merge_requests_path(@project.namespace, @project)}", - name: "#{j(@project.name)}" - }; - - - if @group && @group.persisted? && @group.path - :javascript - gl.groupOptions = gl.groupOptions || {}; - gl.groupOptions["#{j(@group.path)}"] = { - name: "#{j(@group.name)}", - issuesPath: "#{issues_group_path(j(@group.path))}", - mrPath: "#{merge_requests_group_path(j(@group.path))}" - }; - - - :javascript - gl.dashboardOptions = { - issuesPath: "#{issues_dashboard_url}", - mrPath: "#{merge_requests_dashboard_url}" - }; - - - if @snippet || @snippets = hidden_field_tag :snippets, true = hidden_field_tag :repository_ref, @ref diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index d9fa74fad90..578af9fe98d 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -87,6 +87,9 @@ = f.label :location, 'Location', class: "label-light" = f.text_field :location, class: "form-control" .form-group + = f.label :organization, 'Organization', class: "label-light" + = f.text_field :organization, class: "form-control" + .form-group = f.label :bio, class: "label-light" = f.text_area :bio, rows: 4, class: "form-control", maxlength: 250 %span.help-block Tell us about yourself in fewer than 250 characters. diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index ac50ce83f6a..d011e51e696 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -1,13 +1,16 @@ -.nav-block.activity-filter-block - - if current_user - .controls - = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do - %i.fa.fa-rss +- @no_container = true - = render 'shared/event_filter' +%div{ class: container_class } + .nav-block.activity-filter-block + - if current_user + .controls + = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do + = icon('rss') -.content_list.project-activity{:"data-href" => activity_project_path(@project)} -= spinner + = render 'shared/event_filter' + + .content_list.project-activity{:"data-href" => activity_project_path(@project)} + = spinner :javascript var activity = new Activities(); diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml index 3c6b931f41a..1c3bccccb5c 100644 --- a/app/views/projects/_last_push.html.haml +++ b/app/views/projects/_last_push.html.haml @@ -1,6 +1,6 @@ - if event = last_push_event - if show_last_push_widget?(event) - .row-content-block.top-block.clear-block.hidden-xs + .row-content-block.top-block.hidden-xs.white %div{ class: container_class } .event-last-push .event-last-push-text diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 0237e152b54..d4f59764a70 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -21,6 +21,13 @@ = dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } ) .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden = dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } ) + = button_tag class: 'soft-wrap-toggle btn', type: 'button' do + %span.no-wrap + = custom_icon('icon_no_wrap') + No wrap + %span.soft-wrap + = custom_icon('icon_soft_wrap') + Soft wrap .encoding-selector = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 0aa3092baa2..f5344091cae 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -8,7 +8,7 @@ %a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" } = icon('angle-double-right') - if @build.coverage - .block.block-first + .block.coverage .title Test coverage %p.build-detail-row @@ -95,7 +95,7 @@ - @build.trigger_request.variables.each do |key, value| .hide.js-build - .js-build-variable= key + .js-build-variable= key .js-build-value= value .block @@ -128,7 +128,7 @@ - builds.select{|build| build.status == build_status}.each do |build| .build-job{class: ('active' if build == @build), data: {stage: build.stage}} = link_to namespace_project_build_path(@project.namespace, @project, build) do - = icon('check') + = icon('right-arrow') = ci_icon_for_status(build.status) %span - if build.name diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index 61eff73da26..c2bcfb773a6 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -9,7 +9,7 @@ %thead %tr %th Status - %th Commit + %th Build - if admin %th Project %th Runner diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 6391c67021b..04e48a4dc17 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -1,4 +1,7 @@ - status = pipeline.status +- show_commit = local_assigns.fetch(:show_commit, true) +- show_branch = local_assigns.fetch(:show_branch, true) + %tr.commit %td.commit-link = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do @@ -10,14 +13,14 @@ .branch-commit = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do %span ##{pipeline.id} - - if pipeline.ref - - unless defined?(hide_branch) && hide_branch - .icon-container - = pipeline.tag? ? icon('tag') : icon('code-fork') - = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name" - .icon-container - = custom_icon("icon_commit") - = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace" + - if pipeline.ref && show_branch + .icon-container + = pipeline.tag? ? icon('tag') : icon('code-fork') + = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name" + - if show_commit + .icon-container + = custom_icon("icon_commit") + = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace" - if pipeline.latest? %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest - if pipeline.triggered? diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml index a508382578a..b7087749428 100644 --- a/app/views/projects/commit/_builds.html.haml +++ b/app/views/projects/commit/_builds.html.haml @@ -1,2 +1,2 @@ -- @pipelines.each do |pipeline| +- @ci_pipelines.each do |pipeline| = render "pipeline", pipeline: pipeline, pipeline_details: true diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml index 935433306ea..cbfd99ca448 100644 --- a/app/views/projects/commit/_ci_menu.html.haml +++ b/app/views/projects/commit/_ci_menu.html.haml @@ -3,6 +3,11 @@ = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do Changes %span.badge= @diffs.size + - if can?(current_user, :read_pipeline, @project) + = nav_link(path: 'commit#pipelines') do + = link_to pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id) do + Pipelines + %span.badge= @ci_pipelines.count = nav_link(path: 'commit#builds') do = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id) do Builds diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index f41a11a056d..998812793a2 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -7,14 +7,8 @@ %table.table.builds %tbody %th Status - %th Commit - - pipelines.stages.each do |stage| - %th.stage - - if stage.titleize.length > 12 - %span.has-tooltip{ title: "#{stage.titleize}" } - = stage.titleize - - else - = stage.titleize + %th Pipeline + %th Stages %th %th - = render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, status_icon_only: true, hide_branch: true + = render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, status_icon_only: true, show_commit: false diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml new file mode 100644 index 00000000000..d85d6729a81 --- /dev/null +++ b/app/views/projects/commit/pipelines.html.haml @@ -0,0 +1,7 @@ +- page_title "Pipelines", "#{@commit.title} (#{@commit.short_id})", "Commits" + +.prepend-top-default + = render "commit_box" + += render "ci_menu" += render "pipelines_list", pipelines: @ci_pipelines diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index 4d1ee1c5318..80763ce67ca 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -1,27 +1,28 @@ -.scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } - = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do - = link_to project_files_path(@project) do - Files += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do + = link_to project_files_path(@project) do + Files - = nav_link(controller: [:commit, :commits]) do - = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do - Commits + = nav_link(controller: [:commit, :commits]) do + = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do + Commits - = nav_link(controller: %w(network)) do - = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do - Network + = nav_link(controller: %w(network)) do + = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do + Network - = nav_link(controller: :compare) do - = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do - Compare + = nav_link(controller: :compare) do + = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do + Compare - = nav_link(html_options: {class: branches_tab_class}) do - = link_to namespace_project_branches_path(@project.namespace, @project) do - Branches + = nav_link(html_options: {class: branches_tab_class}) do + = link_to namespace_project_branches_path(@project.namespace, @project) do + Branches - = nav_link(controller: [:tags, :releases]) do - = link_to namespace_project_tags_path(@project.namespace, @project) do - Tags + = nav_link(controller: [:tags, :releases]) do + = link_to namespace_project_tags_path(@project.namespace, @project) do + Tags diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 9a44ba94970..876c8002627 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -5,7 +5,8 @@ - if current_user = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") -= render "head" += content_for :sub_nav do + = render "head" %div{ class: container_class } .row-content-block.second-block.content-component-block diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index d79336f5a60..76b68c544aa 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -1,17 +1,22 @@ = form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do .clearfix - if params[:to] && params[:from] - = link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'} - .form-group.dropdown.compare-form-group.js-compare-from-dropdown + .compare-switch-container + = link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'} + .form-group.dropdown.compare-form-group.from.js-compare-from-dropdown .input-group.inline-input-group %span.input-group-addon from - = text_field_tag :from, params[:from], class: "form-control js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from].presence } + = hidden_field_tag :from, params[:from] + = button_tag type: 'button', class: "form-control compare-dropdown-toggle js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do + .dropdown-toggle-text= params[:from] || 'Select branch/tag' = render "ref_dropdown" - = "..." - .form-group.dropdown.compare-form-group.js-compare-to-dropdown + .compare-ellipsis ... + .form-group.dropdown.compare-form-group.to.js-compare-to-dropdown .input-group.inline-input-group %span.input-group-addon to - = text_field_tag :to, params[:to], class: "form-control js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to].presence } + = hidden_field_tag :to, params[:to] + = button_tag type: 'button', class: "form-control compare-dropdown-toggle js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do + .dropdown-toggle-text= params[:to] || 'Select branch/tag' = render "ref_dropdown" = button_tag "Compare", class: "btn btn-create commits-compare-btn" diff --git a/app/views/projects/compare/_ref_dropdown.html.haml b/app/views/projects/compare/_ref_dropdown.html.haml index c604c6d0135..27d928c87a0 100644 --- a/app/views/projects/compare/_ref_dropdown.html.haml +++ b/app/views/projects/compare/_ref_dropdown.html.haml @@ -1,4 +1,5 @@ .dropdown-menu.dropdown-menu-selectable = dropdown_title "Select branch/tag" + = dropdown_filter "Filter by branch/tag" = dropdown_content = dropdown_loading diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index 5dcb2a17873..7f346df8797 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -2,18 +2,20 @@ - page_title "Cycle Analytics" = render "projects/pipelines/head" -#cycle-analytics{"v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}} +#cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}} .bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"} = icon('times', class: 'dismiss-icon', "@click": "dismissLanding()") - = custom_icon('icon_cycle_analytics_splash') - .inner-content - %h4 - Introducing Cycle Analytics - %p - Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project. + .row + .col-sm-3.col-xs-12.svg-container + = custom_icon('icon_cycle_analytics_splash') + .col-sm-8.col-xs-12.inner-content + %h4 + Introducing Cycle Analytics + %p + Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project. - = link_to "Read more", help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn' + = link_to "Read more", help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn' = icon("spinner spin", "v-show" => "isLoading") @@ -25,11 +27,11 @@ .content-block .container-fluid .row - .col-xs-3.column{"v-for" => "item in summary"} + .col-sm-3.col-xs-12.column{"v-for" => "item in analytics.summary"} %h3.header {{item.value}} %p.text {{item.title}} - .col-xs-3.column + .col-sm-3.col-xs-12.column .dropdown.inline.js-ca-dropdown %button.dropdown-menu-toggle{"data-toggle" => "dropdown", :type => "button"} %span.dropdown-label Last 30 days @@ -44,14 +46,14 @@ .bordered-box %ul.content-list - %li{"v-for" => "item in stats"} + %li{"v-for" => "item in analytics.stats"} .container-fluid .row - .col-xs-10.title-col + .col-xs-8.title-col %p.title {{item.title}} %p.text {{item.description}} - .col-xs-2.value-col + .col-xs-4.value-col %span {{item.value}} diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml index d37961c4e40..779c8ea0104 100644 --- a/app/views/projects/diffs/_content.html.haml +++ b/app/views/projects/diffs/_content.html.haml @@ -11,7 +11,9 @@ - elsif diff_file.collapsed? - url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path)) .nothing-here-block.diff-collapsed{data: { diff_for_path: url } } - This diff is collapsed. Click to expand it. + This diff is collapsed. + %a.click-to-expand + Click to expand it. - elsif diff_file.diff_lines.length > 0 - if diff_view == :parallel = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 1a51ccd4c7d..d07de45fdde 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -5,7 +5,7 @@ - unless diff_file.submodule? .file-actions.hidden-xs - if blob_text_viewable?(blob) - = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this files", disabled: @diff_notes_disabled do + = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file", disabled: @diff_notes_disabled do = icon('comment') \ diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml index 95a2772fd0b..a6a2e5690b5 100644 --- a/app/views/projects/diffs/_file_header.html.haml +++ b/app/views/projects/diffs/_file_header.html.haml @@ -1,3 +1,4 @@ +%i.fa.diff-toggle-caret - if defined?(blob) && blob && diff_file.submodule? %span = icon('archive fw') diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 636beb73ec2..7a39064adc5 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -23,6 +23,8 @@ or a = link_to '.gitignore', add_special_file_path(@project, file_name: '.gitignore'), class: 'underlined-link' to this project. + %p + You will need to be owner or have the master permission level for the initial push, as the master branch is automatically protected. - if can?(current_user, :push_code, @project) %div{ class: container_class } diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml index 082e2cb4d8c..1a62a6a809c 100644 --- a/app/views/projects/graphs/_head.html.haml +++ b/app/views/projects/graphs/_head.html.haml @@ -1,18 +1,19 @@ -.scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } - - content_for :page_specific_javascripts do - = page_specific_javascript_tag('lib/chart.js') - = page_specific_javascript_tag('graphs/graphs_bundle.js') - = nav_link(action: :show) do - = link_to 'Contributors', namespace_project_graph_path - = nav_link(action: :commits) do - = link_to 'Commits', commits_namespace_project_graph_path - = nav_link(action: :languages) do - = link_to 'Languages', languages_namespace_project_graph_path - - if @project.feature_available?(:builds, current_user) - = nav_link(action: :ci) do - = link_to ci_namespace_project_graph_path do - Continuous Integration + - content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/chart.js') + = page_specific_javascript_tag('graphs/graphs_bundle.js') + = nav_link(action: :show) do + = link_to 'Contributors', namespace_project_graph_path + = nav_link(action: :commits) do + = link_to 'Commits', commits_namespace_project_graph_path + = nav_link(action: :languages) do + = link_to 'Languages', languages_namespace_project_graph_path + - if @project.feature_available?(:builds, current_user) + = nav_link(action: :ci) do + = link_to ci_namespace_project_graph_path do + Continuous Integration diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index f88b33018d0..509b01c548a 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -1,32 +1,33 @@ -.scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } - - if project_nav_tab?(:issues) && !current_controller?(:merge_requests) - = nav_link(controller: :issues) do - = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do - %span - Issues += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + - if project_nav_tab?(:issues) && !current_controller?(:merge_requests) + = nav_link(controller: :issues) do + = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do + %span + Issues - = nav_link(controller: :boards) do - = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do - %span - Board + = nav_link(controller: :boards) do + = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do + %span + Board - - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests) - = nav_link(controller: :merge_requests) do - = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do - %span - Merge Requests + - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests) + = nav_link(controller: :merge_requests) do + = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do + %span + Merge Requests - - if project_nav_tab? :labels - = nav_link(controller: :labels) do - = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do - %span - Labels + - if project_nav_tab? :labels + = nav_link(controller: :labels) do + = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do + %span + Labels - - if project_nav_tab? :milestones - = nav_link(controller: :milestones) do - = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do - %span - Milestones
\ No newline at end of file + - if project_nav_tab? :milestones + = nav_link(controller: :milestones) do + = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do + %span + Milestones diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 8da9f2100e9..cc57cfdb342 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -3,7 +3,8 @@ - page_title "Issues" - new_issue_email = @project.new_issue_address(current_user) -= render "projects/issues/head" += content_for :sub_nav do + = render "projects/issues/head" = content_for :meta_tags do - if current_user diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index aa143e54ffe..6ab6ae50389 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -14,7 +14,7 @@ .col-sm-10 .input-group .input-group-addon.label-color-preview - = f.color_field :color, class: "form-control" + = f.text_field :color, class: "form-control" .help-block Choose any color. %br diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index 3900b4f6f17..cfb44bd206c 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -5,7 +5,7 @@ - if @merge_request.reopenable? = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"} %comment-and-resolve-btn{ "inline-template" => true, ":discussion-id" => "" } - %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { namespace_path: "#{@merge_request.project.namespace.path}", project_path: "#{@merge_request.project.path}" } } + %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { project_path: "#{project_path(@merge_request.project)}" } } {{ buttonText }} #notes= render "projects/notes/notes_with_form" diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 8f7b5d1543e..904452fcc4f 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -10,23 +10,25 @@ - else version #{version_index(@merge_request_diff)} %span.caret - %ul.dropdown-menu.dropdown-menu-selectable + .dropdown-menu.dropdown-select.dropdown-menu-selectable .dropdown-title %span Version: - %button.dropdown-title-button.dropdown-menu-close - %i.fa.fa-times.dropdown-menu-close-icon - - @merge_request_diffs.each do |merge_request_diff| - %li - = link_to merge_request_version_path(@project, @merge_request, merge_request_diff), class: ('is-active' if merge_request_diff == @merge_request_diff) do - %strong - - if merge_request_diff.latest? - latest version - - else - version #{version_index(merge_request_diff)} - .monospace #{short_sha(merge_request_diff.head_commit_sha)} - %small - #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)}, - = time_ago_with_tooltip(merge_request_diff.created_at) + %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}} + = icon('times', class: 'dropdown-menu-close-icon') + .dropdown-content + %ul + - @merge_request_diffs.each do |merge_request_diff| + %li + = link_to merge_request_version_path(@project, @merge_request, merge_request_diff), class: ('is-active' if merge_request_diff == @merge_request_diff) do + %strong + - if merge_request_diff.latest? + latest version + - else + version #{version_index(merge_request_diff)} + .monospace #{short_sha(merge_request_diff.head_commit_sha)} + %small + #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)}, + = time_ago_with_tooltip(merge_request_diff.created_at) - if @merge_request_diff.base_commit_sha and @@ -38,27 +40,29 @@ - else #{@merge_request.target_branch} %span.caret - %ul.dropdown-menu.dropdown-menu-selectable + .dropdown-menu.dropdown-select.dropdown-menu-selectable .dropdown-title %span Compared with: - %button.dropdown-title-button.dropdown-menu-close - %i.fa.fa-times.dropdown-menu-close-icon - - @comparable_diffs.each do |merge_request_diff| - %li - = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff == @start_version) do - %strong - - if merge_request_diff.latest? - latest version - - else - version #{version_index(merge_request_diff)} - .monospace #{short_sha(merge_request_diff.head_commit_sha)} - %small - = time_ago_with_tooltip(merge_request_diff.created_at) - %li - = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do - %strong - #{@merge_request.target_branch} (base) - .monospace #{short_sha(@merge_request_diff.base_commit_sha)} + %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}} + = icon('times', class: 'dropdown-menu-close-icon') + .dropdown-content + %ul + - @comparable_diffs.each do |merge_request_diff| + %li + = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff == @start_version) do + %strong + - if merge_request_diff.latest? + latest version + - else + version #{version_index(merge_request_diff)} + .monospace #{short_sha(merge_request_diff.head_commit_sha)} + %small + = time_ago_with_tooltip(merge_request_diff.created_at) + %li + = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do + %strong + #{@merge_request.target_branch} (base) + .monospace #{short_sha(@merge_request_diff.base_commit_sha)} - unless @merge_request_diff.latest? && !@start_sha .comments-disabled-notif.content-block @@ -67,4 +71,4 @@ Comments are disabled because you're comparing two versions of this merge request. - else Comments are disabled because you're viewing an old version of this merge request. - = link_to 'Show latest version', merge_request_version_path(@project, @merge_request, @merge_request_diff), class: 'btn btn-sm' + = link_to 'Show latest version', diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-sm' diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 494695a03a5..44e645a7e81 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -43,15 +43,16 @@ = icon("times-circle") Could not connect to the CI server. Please check your settings and try again. -- @merge_request.environments.each do |environment| - .mr-widget-heading - .ci_widget.ci-success - = ci_icon_for_status("success") - %span.hidden-sm - Deployed to - = succeed '.' do - = link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment), class: 'environment' - - external_url = environment.external_url - - if external_url - = link_to external_url, target: '_blank' do - = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true) +- @merge_request.environments.sort_by(&:name).each do |environment| + - if can?(current_user, :read_environment, environment) + .mr-widget-heading + .ci_widget.ci-success + = ci_icon_for_status("success") + %span.hidden-sm + Deployed to + = succeed '.' do + = link_to environment.name, environment_path(environment), class: 'environment' + - external_url = environment.external_url + - if external_url + = link_to external_url, target: '_blank' do + = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true) diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index fda0592dd41..cc8cb134fb8 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -72,7 +72,7 @@ = link_to "#", class: 'btn js-toggle-button import_git' do = icon('git', text: 'Repo by URL') %div{ class: 'import_gitlab_project' } - - if gitlab_project_import_enabled? && current_user.is_admin? + - if gitlab_project_import_enabled? = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do = icon('gitlab', text: 'GitLab export') diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 9ec17cf6e76..788be4a0047 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -24,14 +24,12 @@ - if note.resolvable? - can_resolve = can?(current_user, :resolve_note, note) - - %resolve-btn{ ":namespace-path" => "'#{note.project.namespace.path}'", - ":project-path" => "'#{note.project.path}'", - ":discussion-id" => "'#{note.discussion_id}'", + %resolve-btn{ "project-path" => "#{project_path(note.project)}", + "discussion-id" => "#{note.discussion_id}", ":note-id" => note.id, ":resolved" => note.resolved?, ":can-resolve" => can_resolve, - ":resolved-by" => "'#{note.resolved_by.try(:name)}'", + "resolved-by" => "#{note.resolved_by.try(:name)}", "v-show" => "#{can_resolve || note.resolved?}", "inline-template" => true, "v-ref:note_#{note.id}" => true } diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index 74538a9723e..8352eba7446 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -14,9 +14,9 @@ .disabled-comment.text-center .disabled-comment-text.inline Please - = link_to "register", new_session_path(:user, redirect_to_referer: 'yes') + = link_to "sign up", new_session_path(:user, redirect_to_referer: 'yes') or - = link_to "login", new_session_path(:user, redirect_to_referer: 'yes') + = link_to "sign in", new_session_path(:user, redirect_to_referer: 'yes') to post a comment :javascript diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index 5f571499e80..7d421c0e740 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -1,27 +1,28 @@ -.scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } - - if project_nav_tab? :pipelines - = nav_link(controller: :pipelines) do - = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do - %span - Pipelines += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + - if project_nav_tab? :pipelines + = nav_link(controller: :pipelines) do + = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do + %span + Pipelines - - if project_nav_tab? :builds - = nav_link(controller: %w(builds)) do - = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do - %span - Builds + - if project_nav_tab? :builds + = nav_link(controller: %w(builds)) do + = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do + %span + Builds - - if project_nav_tab? :environments - = nav_link(controller: %w(environments)) do - = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do - %span - Environments + - if project_nav_tab? :environments + = nav_link(controller: %w(environments)) do + = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do + %span + Environments - - if can?(current_user, :read_cycle_analytics, @project) - = nav_link(controller: %w(cycle_analytics)) do - = link_to project_cycle_analytics_path(@project), title: 'Cycle Analytics' do - %span - Cycle Analytics + - if can?(current_user, :read_cycle_analytics, @project) + = nav_link(controller: %w(cycle_analytics)) do + = link_to project_cycle_analytics_path(@project), title: 'Cycle Analytics' do + %span + Cycle Analytics diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index faf28db68d1..50c7e5044b2 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -46,7 +46,7 @@ %table.table.builds %tbody %th Status - %th Commit + %th Pipeline %th Stages %th %th diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml index 0628134b1bb..0193800dedf 100644 --- a/app/views/projects/protected_branches/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_protected_branch.html.haml @@ -1,4 +1,4 @@ -%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch), branch_id: protected_branch.id } } +%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } } %td = protected_branch.name - if @project.root_ref?(protected_branch.name) diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index c45a9d4f81f..33a9a96183c 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -5,7 +5,7 @@ .col-sm-10 .checkbox = f.check_box :active - %span.light Paused runners don't accept new builds + %span.light Paused Runners don't accept new builds .form-group = label :run_untagged, 'Run untagged jobs', class: 'control-label' .col-sm-10 @@ -33,6 +33,6 @@ Tags .col-sm-10 = f.text_field :tag_list, value: runner.tag_list.to_s, class: 'form-control' - .help-block You can setup jobs to only use runners with specific tags + .help-block You can setup jobs to only use Runners with specific tags .form-actions = f.submit 'Save changes', class: 'btn btn-save' diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index 85225857758..6e58e5a0c78 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -15,7 +15,7 @@ .pull-right - if @project_runners.include?(runner) - if runner.belongs_to_one_project? - = link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' + = link_to 'Remove Runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' - else - runner_project = @project.runner_projects.find_by(runner_id: runner) = link_to 'Disable for this project', namespace_project_runner_project_path(@project.namespace, @project, runner_project), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml index 9fa4127c948..752b9e060d5 100644 --- a/app/views/projects/runners/_shared_runners.html.haml +++ b/app/views/projects/runners/_shared_runners.html.haml @@ -1,24 +1,26 @@ -%h3 Shared runners +%h3 Shared Runners .bs-callout.bs-callout-warning.shared-runners-description - if shared_runners_text.present? = markdown(shared_runners_text, pipeline: 'plain_markdown') - else - Shared runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com). + GitLab Shared Runners execute code of different projects on the same Runner + unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is + on GitLab.com). %hr - if @project.shared_runners_enabled? = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do - Disable shared runners + Disable shared Runners - else = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-success', method: :post do - Enable shared runners + Enable shared Runners for this project - if @shared_runners_count.zero? - This GitLab server does not provide any shared runners yet. - Please use specific runners or ask the administrator to create one. + This GitLab server does not provide any shared Runners yet. + Please use the specific Runners or ask your administrator to create one. - else - %h4.underlined-title Available shared runners - #{@shared_runners_count} + %h4.underlined-title Available shared Runners : #{@shared_runners_count} %ul.bordered-list.available-shared-runners = render partial: 'runner', collection: @shared_runners, as: :runner - if @shared_runners_count > 10 diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml index d469dda5b81..858af78f7bf 100644 --- a/app/views/projects/runners/_specific_runners.html.haml +++ b/app/views/projects/runners/_specific_runners.html.haml @@ -1,20 +1,20 @@ -%h3 Specific runners +%h3 Specific Runners .bs-callout.help-callout - %h4 How to setup a new project specific runner + %h4 How to setup a specific Runner for a new project %ol %li - Install GitLab Runner software. - Checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} to install it + Install a Runner compatible with GitLab CI + (checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} for information on how to install it). %li - Specify the following URL during runner setup: + Specify the following URL during the Runner setup: %code #{ci_root_url(only_path: false)} %li Use the following registration token during setup: %code #{@project.runners_token} %li - Start runner! + Start the Runner! - if @project_runners.any? diff --git a/app/views/projects/runners/index.html.haml b/app/views/projects/runners/index.html.haml index 2d5b9f43c24..92957470070 100644 --- a/app/views/projects/runners/index.html.haml +++ b/app/views/projects/runners/index.html.haml @@ -2,24 +2,24 @@ .light.prepend-top-default %p - A 'runner' is a process which runs a build. - You can setup as many runners as you need. + A 'Runner' is a process which runs a build. + You can setup as many Runners as you need. %br Runners can be placed on separate users, servers, and even on your local machine. - %p Each runner can be in one of the following states: + %p Each Runner can be in one of the following states: %div %ul %li %span.label.label-success active - \- runner is active and can process any new build + \- Runner is active and can process any new builds %li %span.label.label-danger paused - \- runner is paused and will not receive any new build + \- Runner is paused and will not receive any new builds %hr -%p.lead To start serving your builds you can either add specific runners to your project or use shared runners +%p.lead To start serving your builds you can either add specific Runners to your project or use shared Runners .row .col-sm-6 = render 'specific_runners' diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml index a5a5619fa12..9773b8438ec 100644 --- a/app/views/projects/snippets/_actions.html.haml +++ b/app/views/projects/snippets/_actions.html.haml @@ -1,9 +1,9 @@ .hidden-xs - if can?(current_user, :create_project_snippet, @project) - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do - New Snippet + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New snippet" do + New snippet - if can?(current_user, :update_project_snippet, @snippet) - = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do + = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do Delete - if can?(current_user, :update_project_snippet, @snippet) = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do @@ -17,8 +17,8 @@ %ul - if can?(current_user, :create_project_snippet, @project) %li - = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do - New Snippet + = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New snippet" do + New snippet - if can?(current_user, :update_project_snippet, @snippet) %li = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index 1646bcf4b8a..e77e1b026f6 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,10 +1,9 @@ - page_title "Snippets" .sub-header-block - .pull-right - - if can?(current_user, :create_project_snippet, @project) - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do - New Snippet + - if can?(current_user, :create_project_snippet, @project) + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do + New snippet .oneline Share code pastes with others out of git repository diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml index b70fda88a79..9503dbded13 100644 --- a/app/views/projects/snippets/show.html.haml +++ b/app/views/projects/snippets/show.html.haml @@ -2,13 +2,16 @@ = render 'shared/snippets/header' -%article.file-holder.snippet-file-content - .file-title - = blob_icon 0, @snippet.file_name - = @snippet.file_name - .file-actions - = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") - = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank" - = render 'shared/snippets/blob' - -%div#notes= render "projects/notes/notes_with_form" +.project-snippets + %article.file-holder.snippet-file-content + .file-title + = blob_icon 0, @snippet.file_name + = @snippet.file_name + .file-actions + = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") + = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank" + = render 'shared/snippets/blob' + + = render 'award_emoji/awards_block', awardable: @snippet, inline: true + + %div#notes= render "projects/notes/notes_with_form" diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 37d341212af..9864be3562a 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -4,8 +4,8 @@ = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") -= render 'projects/last_push' = render "projects/commits/head" += render 'projects/last_push' %div{ class: container_class } .tree-controls diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 643f7c589e6..6624d5cb427 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -24,7 +24,7 @@ = succeed '.' do More examples are in the - = link_to 'documentation', help_page_path("user/project/markdown", anchor: "wiki-specific-markdown") + = link_to 'documentation', help_page_path("user/markdown", anchor: "wiki-specific-markdown") .form-group = f.label :commit_message, class: 'control-label' diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index 551a20c1044..09c4411d67e 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -1,15 +1,16 @@ -.scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } - = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do - = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do + = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) - = nav_link(path: 'wikis#pages') do - = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project) + = nav_link(path: 'wikis#pages') do + = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project) - = nav_link(path: 'wikis#git_access') do - = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do - Git Access + = nav_link(path: 'wikis#git_access') do + = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do + Git Access - = render 'projects/wikis/new' + = render 'projects/wikis/new' diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml index cf16c203f9c..73d288e2236 100644 --- a/app/views/shared/_milestones_filter.html.haml +++ b/app/views/shared/_milestones_filter.html.haml @@ -1,10 +1,19 @@ +- if @project + - counts = milestone_counts(@project.milestones) + %ul.nav-links - %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')} + %li{class: milestone_class_for_state(params[:state], 'opened', true)} = link_to milestones_filter_path(state: 'opened') do Open - %li{class: ("active" if params[:state] == 'closed')} + - if @project + %span.badge #{counts[:opened]} + %li{class: milestone_class_for_state(params[:state], 'closed')} = link_to milestones_filter_path(state: 'closed') do Closed - %li{class: ("active" if params[:state] == 'all')} + - if @project + %span.badge #{counts[:closed]} + %li{class: milestone_class_for_state(params[:state], 'all')} = link_to milestones_filter_path(state: 'all') do All + - if @project + %span.badge #{counts[:all]} diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index 249bce926ce..36bbac6fbf5 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -8,26 +8,26 @@ %b.caret %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort %li - = link_to page_filter_path(sort: sort_value_priority) do + = link_to page_filter_path(sort: sort_value_priority, label: true) do = sort_title_priority - = link_to page_filter_path(sort: sort_value_recently_created) do + = link_to page_filter_path(sort: sort_value_recently_created, label: true) do = sort_title_recently_created - = link_to page_filter_path(sort: sort_value_oldest_created) do + = link_to page_filter_path(sort: sort_value_oldest_created, label: true) do = sort_title_oldest_created - = link_to page_filter_path(sort: sort_value_recently_updated) do + = link_to page_filter_path(sort: sort_value_recently_updated, label: true) do = sort_title_recently_updated - = link_to page_filter_path(sort: sort_value_oldest_updated) do + = link_to page_filter_path(sort: sort_value_oldest_updated, label: true) do = sort_title_oldest_updated - = link_to page_filter_path(sort: sort_value_milestone_soon) do + = link_to page_filter_path(sort: sort_value_milestone_soon, label: true) do = sort_title_milestone_soon - = link_to page_filter_path(sort: sort_value_milestone_later) do + = link_to page_filter_path(sort: sort_value_milestone_later, label: true) do = sort_title_milestone_later - if controller.controller_name == 'issues' || controller.action_name == 'issues' - = link_to page_filter_path(sort: sort_value_due_date_soon) do + = link_to page_filter_path(sort: sort_value_due_date_soon, label: true) do = sort_title_due_date_soon - = link_to page_filter_path(sort: sort_value_due_date_later) do + = link_to page_filter_path(sort: sort_value_due_date_later, label: true) do = sort_title_due_date_later - = link_to page_filter_path(sort: sort_value_upvotes) do + = link_to page_filter_path(sort: sort_value_upvotes, label: true) do = sort_title_upvotes - = link_to page_filter_path(sort: sort_value_downvotes) do + = link_to page_filter_path(sort: sort_value_downvotes, label: true) do = sort_title_downvotes diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml index add4536a0a2..b11257ee0e6 100644 --- a/app/views/shared/_visibility_level.html.haml +++ b/app/views/shared/_visibility_level.html.haml @@ -6,7 +6,7 @@ - if can_change_visibility_level = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model) - else - .col-sm-10 + %div %span.info = visibility_level_icon(visibility_level) %strong diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml index ebe2eb0433d..182c4eebd50 100644 --- a/app/views/shared/_visibility_radios.html.haml +++ b/app/views/shared/_visibility_radios.html.haml @@ -10,6 +10,6 @@ .option-descr = visibility_level_description(level, form_model) - unless restricted_visibility_levels.empty? - .col-sm-10 + %div %span.info Some visibility level settings have been restricted by the administrator. diff --git a/app/views/shared/icons/_icon_empty_groups.svg b/app/views/shared/icons/_icon_empty_groups.svg new file mode 100644 index 00000000000..9228be05f03 --- /dev/null +++ b/app/views/shared/icons/_icon_empty_groups.svg @@ -0,0 +1 @@ +<svg width="249" height="368" viewBox="891 156 249 368" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="131" height="162" rx="10"/><mask id="e" x="0" y="0" width="131" height="162" fill="#fff"><use xlink:href="#a"/></mask><path d="M223.616 127.958V108.96c0-4.416-3.584-8-8.005-8h-23.985c-2.778 0-5.98 2.014-7.18 4.5l-5.07 10.5h-49.763c-5.527 0-9.996 4.475-9.996 9.997v53.005c0 5.513 4.475 9.997 9.996 9.997h84.01c5.525 0 9.994-4.477 9.994-9.998v-51.004z" id="b"/><mask id="f" x="0" y="0" width="104" height="88" fill="#fff"><use xlink:href="#b"/></mask><path d="M47 25h.996C53.52 25 58 29.472 58 34.99v20.02C58 60.526 53.52 65 47.996 65H10.004C4.48 65 0 60.528 0 55.01V34.99C0 29.474 4.48 25 10.004 25H11v-7c0-9.94 8.06-18 18-18s18 8.06 18 18v7zm-6 0H17v-7c0-6.627 5.373-12 12-12s12 5.373 12 12v7z" id="c"/><mask id="g" x="0" y="0" width="58" height="65" fill="#fff"><use xlink:href="#c"/></mask><path d="M0 10.008C0 4.48 4.476 0 10 0h218c5.523 0 10 4.473 10 10.008v140.94c0 5.53-4.062 11.882-9.08 14.196l-100.84 46.5c-5.015 2.31-13.142 2.312-18.16 0l-100.84-46.5C4.064 162.832 0 156.484 0 150.95V10.007z" id="d"/><mask id="h" x="0" y="0" width="238" height="213.417" fill="#fff"><use xlink:href="#d"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(891 156)"><g transform="rotate(8 -266.528 490.3)"><use stroke="#E5E5E5" mask="url(#e)" stroke-width="8" fill="#FFF" xlink:href="#a"/><rect fill="#FC8A51" x="20" y="31" width="12" height="4" rx="2"/><rect fill="#FC8A51" x="60" y="31" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="36" y="31" width="20" height="4" rx="2"/><rect fill="#6B4FBB" x="20" y="65" width="20" height="4" rx="2"/><rect fill="#FDE5D8" x="44" y="65" width="20" height="4" rx="2"/><rect fill="#FC8A51" x="36" y="80" width="20" height="4" rx="2"/><rect fill="#FDE5D8" x="20" y="80" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="20" y="48" width="12" height="4" rx="2"/><rect fill="#FC8A51" x="36" y="48" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="60" y="80" width="12" height="4" rx="2"/><rect fill="#6B4FBB" x="52" y="48" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="68" y="48" width="12" height="4" rx="2"/></g><use stroke="#B5A7DD" mask="url(#f)" stroke-width="8" fill="#FFF" transform="rotate(5 171.616 144.96)" xlink:href="#b"/><path d="M58 132c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#C1E7D0"/><path d="M90.143 132c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M74.686 133.875l-3.18-3.18c-.29-.29-.77-.296-1.06-.005l-1.55 1.55c-.287.287-.29.766.004 1.06l4.92 4.92c.504.504 1.32.504 1.823 0l.654-.653 7.804-7.804c.3-.3.29-.77-.005-1.067l-1.578-1.58c-.302-.3-.775-.298-1.068-.004l-6.764 6.763z" fill="#31AF64"/><path d="M4 66c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18S4 75.94 4 66z" fill="#D5ECF7"/><path d="M36.143 66c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M22 55.714c5.68 0 10.286 4.605 10.286 10.286 0 5.68-4.605 10.286-10.286 10.286-3.45 0-6.505-1.7-8.37-4.307L22 66V55.714z" fill="#2D9FD8"/><g transform="rotate(-8 748.533 18.147)"><use stroke="#FDE5D8" mask="url(#g)" stroke-width="8" fill="#FFF" xlink:href="#c"/><path d="M31 46.584c1.766-.772 3-2.534 3-4.584 0-2.76-2.24-5-5-5s-5 2.24-5 5c0 2.05 1.234 3.812 3 4.584v3.42c0 1.1.895 1.996 2 1.996 1.112 0 2-.894 2-1.997v-3.42z" fill="#FC8A51"/></g><g transform="translate(0 154)"><use stroke="#E5E5E5" mask="url(#h)" stroke-width="8" fill="#FFF" xlink:href="#d"/><g opacity=".3"><path d="M141.837 104.53l-2.56-7.993-5.074-15.843c-.26-.815-1.398-.815-1.66 0l-5.074 15.843h-16.85l-5.075-15.843c-.26-.815-1.398-.815-1.66 0l-5.073 15.843-2.56 7.993c-.234.73.022 1.528.633 1.98l22.16 16.33 22.16-16.33c.61-.452.866-1.25.632-1.98" fill="#A1A1A1"/><path fill="#5C5C5C" d="M119.044 122.84l8.425-26.303h-16.85l8.424 26.304"/><path fill="#787878" d="M119.044 122.84l-8.425-26.303H98.81l20.232 26.304"/><path fill="#787878" d="M119.044 122.84l8.425-26.303h11.807l-20.233 26.304"/><path d="M98.812 96.537l-2.56 7.993c-.234.73.022 1.528.633 1.98l22.16 16.33L98.81 96.538z" fill="#A1A1A1"/><path d="M98.812 96.537h11.807l-5.075-15.843c-.26-.815-1.398-.815-1.66 0l-5.073 15.843z" fill="#5C5C5C"/><path d="M139.277 96.537l2.56 7.993c.234.73-.022 1.528-.634 1.98l-22.16 16.33 20.234-26.303z" fill="#A1A1A1"/><path d="M139.277 96.537H127.47l5.074-15.843c.26-.815 1.398-.815 1.66 0l5.073 15.843z" fill="#5C5C5C"/></g><path d="M57 18.29c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83H41c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83H77c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm17 24.693c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V28.35c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.633zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V28.35c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.633zm202 32.923c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V61.274c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V61.274c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm202 32.923c0 1.01.895 1.828 2 1.828s2-.82 2-1.83V94.2c0-1.012-.895-1.83-2-1.83s-2 .818-2 1.83v14.63zm-202 0c0 1.01.895 1.828 2 1.828s2-.82 2-1.83V94.2c0-1.012-.895-1.83-2-1.83s-2 .818-2 1.83v14.63zm202 32.922c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V127.12c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V127.12c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm179.023 19.555c-.988.452-1.388 1.55-.894 2.454.493.904 1.694 1.27 2.682.82l14.31-6.545c.99-.452 1.39-1.55.896-2.454-.494-.902-1.696-1.27-2.684-.817l-14.31 6.544zm-32.2 14.723c-.987.452-1.388 1.55-.894 2.454.493.904 1.695 1.27 2.683.818l14.31-6.544c.99-.45 1.39-1.55.895-2.454-.494-.903-1.695-1.27-2.683-.818l-14.31 6.544zm-32.2 14.724c-.987.45-1.387 1.55-.893 2.454.494.903 1.695 1.27 2.683.818l14.31-6.544c.99-.452 1.39-1.55.896-2.454-.495-.904-1.697-1.27-2.685-.818l-14.31 6.544zm-23.67-2.023l-12.186-5.57c-.987-.452-2.19-.086-2.683.817-.494.904-.093 2.003.895 2.454l12.185 5.573c.754.345 1.57.645 2.438.898 1.052.307 2.177-.224 2.513-1.187.335-.962-.246-1.99-1.298-2.298-.677-.197-1.302-.426-1.864-.684zM62.57 168.437c-.988-.452-2.19-.086-2.683.818-.494.903-.094 2.002.894 2.454l14.31 6.544c.988.45 2.19.085 2.683-.818.494-.904.094-2.003-.894-2.454l-14.312-6.544zm-32.2-14.723c-.988-.452-2.19-.086-2.683.818-.494.904-.093 2.003.895 2.454l14.31 6.544c.988.452 2.19.086 2.684-.818.494-.903.093-2.002-.895-2.454l-14.312-6.543z" fill="#EEE"/></g><g><path d="M104 18c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#FADFD9"/><path d="M136.143 18c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M119.43 8.994c0-.707.57-1.28 1.283-1.28h2.574c.71 0 1.284.57 1.284 1.28v10.298c0 .706-.57 1.28-1.283 1.28h-2.574c-.71 0-1.284-.57-1.284-1.28V8.994zm0 15.433c0-.71.57-1.284 1.283-1.284h2.574c.71 0 1.284.57 1.284 1.284V27c0 .71-.57 1.286-1.283 1.286h-2.574c-.71 0-1.284-.57-1.284-1.285v-2.573z" fill="#E75E40"/></g><g><path d="M213 89c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#F6D4DC"/><path d="M245.143 89c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M231 86.348l-3.603-3.602c-.288-.29-.766-.286-1.063.01l-1.578 1.578c-.3.302-.3.773-.01 1.063L228.348 89l-3.602 3.603c-.29.288-.286.766.01 1.063l1.578 1.578c.302.3.773.3 1.063.01L231 91.652l3.603 3.602c.288.29.766.286 1.063-.01l1.578-1.578c.3-.302.3-.773.01-1.063L233.652 89l3.602-3.603c.29-.288.286-.766-.01-1.063l-1.578-1.578c-.302-.3-.773-.3-1.063-.01L231 86.348z" fill="#D22852"/></g></g></svg>
\ No newline at end of file diff --git a/app/views/shared/icons/_icon_fork.svg b/app/views/shared/icons/_icon_fork.svg index fc970e4ce50..ce22b6cdaea 100644 --- a/app/views/shared/icons/_icon_fork.svg +++ b/app/views/shared/icons/_icon_fork.svg @@ -1,3 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="30" height="40" viewBox="5 0 30 40"> - <path fill="#7E7E7E" fill-rule="evenodd" d="M22,29.5351288 L22,22.7193602 C26.1888699,21.5098039 29.3985457,16.802989 29.3985457,16.802989 C29.740988,16.3567547 30,15.5559546 30,15.0081969 L30,10.4648712 C31.1956027,9.77325238 32,8.48056471 32,7 C32,4.790861 30.209139,3 28,3 C25.790861,3 24,4.790861 24,7 C24,8.48056471 24.8043973,9.77325238 26,10.4648712 L26,14.7083871 C26,14.8784435 25.9055559,15.0987329 25.7890533,15.2104147 C25.7890533,15.2104147 24.5373893,16.4126202 23.9488702,16.9515733 C22.5015398,18.2770075 21.1191354,19 20.090554,19 C19.0477772,19 17.6172728,18.2608988 16.1128852,16.9142923 C15.5030182,16.3683886 14.3672121,15.3403307 14.3672121,15.3403307 C14.1659605,15.1583364 14.0000086,14.7846305 14.0000192,14.5088473 C14.0000192,14.5088473 14.0000932,12.7539451 14.0001308,10.4647956 C15.1956614,9.77315812 16,8.48051074 16,7 C16,4.790861 14.209139,3 12,3 C9.790861,3 8,4.790861 8,7 C8,8.48056471 8.80439726,9.77325238 10,10.4648712 L10,15.0081969 C10,15.5446944 10.2736352,16.3534183 10.6111812,16.7893819 C10.6111812,16.7893819 13.8599776,21.3779363 18,22.6668724 L18,29.5351288 C16.8043973,30.2267476 16,31.5194353 16,33 C16,35.209139 17.790861,37 20,37 C22.209139,37 24,35.209139 24,33 C24,31.5194353 23.1956027,30.2267476 22,29.5351288 Z M14,7 C14,5.8954305 13.1045695,5 12,5 C10.8954305,5 10,5.8954305 10,7 C10,8.1045695 10.8954305,9 12,9 C13.1045695,9 14,8.1045695 14,7 Z M30,7 C30,5.8954305 29.1045695,5 28,5 C26.8954305,5 26,5.8954305 26,7 C26,8.1045695 26.8954305,9 28,9 C29.1045695,9 30,8.1045695 30,7 Z M22,33 C22,31.8954305 21.1045695,31 20,31 C18.8954305,31 18,31.8954305 18,33 C18,34.1045695 18.8954305,35 20,35 C21.1045695,35 22,34.1045695 22,33 Z"/> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" width="30" height="40" viewBox="5 0 30 40"><path fill="#7E7E7E" fill-rule="evenodd" d="M22 29.535V22.72c4.19-1.21 7.4-5.917 7.4-5.917.34-.446.6-1.247.6-1.795v-4.543C31.196 9.773 32 8.48 32 7c0-2.21-1.79-4-4-4s-4 1.79-4 4c0 1.48.804 2.773 2 3.465v4.243c0 .17-.094.39-.21.502 0 0-1.253 1.203-1.84 1.742C22.5 18.277 21.12 19 20.09 19c-1.042 0-2.473-.74-3.977-2.086-.61-.546-1.746-1.574-1.746-1.574-.2-.182-.367-.555-.367-.83v-4.045C15.196 9.773 16 8.48 16 7c0-2.21-1.79-4-4-4S8 4.79 8 7c0 1.48.804 2.773 2 3.465v4.543c0 .537.274 1.345.61 1.78 0 0 3.25 4.59 7.39 5.88v6.867c-1.196.692-2 1.984-2 3.465 0 2.21 1.79 4 4 4s4-1.79 4-4c0-1.48-.804-2.773-2-3.465zM14 7c0-1.105-.895-2-2-2s-2 .895-2 2 .895 2 2 2 2-.895 2-2zm16 0c0-1.105-.895-2-2-2s-2 .895-2 2 .895 2 2 2 2-.895 2-2zm-8 26c0-1.105-.895-2-2-2s-2 .895-2 2 .895 2 2 2 2-.895 2-2z"/></svg> diff --git a/app/views/shared/icons/_icon_no_wrap.svg b/app/views/shared/icons/_icon_no_wrap.svg new file mode 100644 index 00000000000..fe34cada002 --- /dev/null +++ b/app/views/shared/icons/_icon_no_wrap.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="m6 11h-4.509c-.263 0-.491.226-.491.505v.991c0 .291.22.505.491.505h4.509v.679c0 .301.194.413.454.236l2.355-1.607c.251-.171.259-.442 0-.619l-2.355-1.607c-.251-.171-.454-.07-.454.236v.681m-5-7.495c0-.279.22-.505.498-.505h13c.275 0 .498.214.498.505v.991c0 .279-.22.505-.498.505h-13c-.275 0-.498-.214-.498-.505v-.991m10 8c0-.279.215-.505.49-.505h3.02c.271 0 .49.214.49.505v.991c0 .279-.215.505-.49.505h-3.02c-.271 0-.49-.214-.49-.505v-.991m-10-4c0-.279.22-.505.498-.505h13c.275 0 .498.214.498.505v.991c0 .279-.22.505-.498.505h-13c-.275 0-.498-.214-.498-.505v-.991"/> +</svg> diff --git a/app/views/shared/icons/_icon_soft_wrap.svg b/app/views/shared/icons/_icon_soft_wrap.svg new file mode 100644 index 00000000000..ea27a2024b1 --- /dev/null +++ b/app/views/shared/icons/_icon_soft_wrap.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="m12 11h-2v-.681c0-.307-.203-.407-.454-.236l-2.355 1.607c-.259.177-.251.448 0 .619l2.355 1.607c.259.177.454.065.454-.236v-.679h2c0 0 0 0 0 0 1.657 0 3-1.343 3-3 0-.828-.336-1.578-.879-2.121-.543-.543-1.293-.879-2.121-.879-.001 0-.002 0-.002 0h-10.497c-.271 0-.5.226-.5.505v.991c0 .291.224.505.5.505h10.497c.001 0 .002 0 .002 0 .552 0 1 .448 1 1 0 .276-.112.526-.293.707-.181.181-.431.293-.707.293m-11-7.495c0-.279.22-.505.498-.505h13c.275 0 .498.214.498.505v.991c0 .279-.22.505-.498.505h-13c-.275 0-.498-.214-.498-.505v-.991m0 8c0-.279.215-.505.49-.505h3.02c.271 0 .49.214.49.505v.991c0 .279-.215.505-.49.505h-3.02c-.271 0-.49-.214-.49-.505v-.991"/> +</svg> diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml index fb592c2b1e2..5527a2f889a 100644 --- a/app/views/shared/issuable/_nav.html.haml +++ b/app/views/shared/issuable/_nav.html.haml @@ -1,27 +1,25 @@ +- type = local_assigns.fetch(:type, :issues) +- page_context_word = type.to_s.humanize(capitalize: false) +- issuables = @issues || @merge_requests + %ul.nav-links.issues-state-filters - - if defined?(type) && type == :merge_requests - - page_context_word = 'merge requests' - - records = @all_merge_requests - - else - - page_context_word = 'issues' - - records = @all_issues %li{class: ("active" if params[:state] == 'opened')} = link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do - #{state_filters_text_for(:opened, records)} + #{issuables_state_counter_text(type, :opened)} - - if defined?(type) && type == :merge_requests + - if type == :merge_requests %li{class: ("active" if params[:state] == 'merged')} = link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do - #{state_filters_text_for(:merged, records)} + #{issuables_state_counter_text(type, :merged)} %li{class: ("active" if params[:state] == 'closed')} = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do - #{state_filters_text_for(:closed, records)} + #{issuables_state_counter_text(type, :closed)} - else %li{class: ("active" if params[:state] == 'closed')} = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do - #{state_filters_text_for(:closed, records)} + #{issuables_state_counter_text(type, :closed)} %li{class: ("active" if params[:state] == 'all')} = link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do - #{state_filters_text_for(:all, records)} + #{issuables_state_counter_text(type, :all)} diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index acc3ccf4dcf..3dccfb147bf 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -33,7 +33,7 @@ - if @project .row .col-sm-6= render('shared/milestone_expired', milestone: milestone) - .col-sm-6 + .col-sm-6.milestone-actions - if can?(current_user, :admin_milestone, milestone.project) and milestone.active? = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs btn-grouped" do Edit diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index fdaca199218..c446dc3bdc1 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -1,7 +1,7 @@ .hidden-xs - if current_user - = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do - New Snippet + = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New snippet" do + New snippet - if can?(current_user, :admin_personal_snippet, @snippet) = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do Delete @@ -16,8 +16,8 @@ .dropdown-menu.dropdown-menu-full-width %ul %li - = link_to new_snippet_path, title: "New Snippet" do - New Snippet + = link_to new_snippet_path, title: "New snippet" do + New snippet - if can?(current_user, :admin_personal_snippet, @snippet) %li = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml index 7be4a471579..77b66ca74b6 100644 --- a/app/views/snippets/_snippets.html.haml +++ b/app/views/snippets/_snippets.html.haml @@ -1,3 +1,5 @@ +- remote = local_assigns.fetch(:remote, false) + .snippets-list-holder %ul.content-list = render partial: 'shared/snippets/snippet', collection: @snippets @@ -5,7 +7,7 @@ %li .nothing-here-block Nothing here. - = paginate @snippets, theme: 'gitlab', remote: true + = paginate @snippets, theme: 'gitlab', remote: remote :javascript gl.SnippetsList(); diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index fa403da8f79..cd89155c616 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -10,3 +10,5 @@ = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank" = render 'shared/snippets/blob' + += render 'award_emoji/awards_block', awardable: @snippet, inline: true
\ No newline at end of file diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 9a052abe40a..60fc0c0daf6 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -10,72 +10,76 @@ = auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity") .user-profile - .cover-block + .cover-block.user-cover-block .cover-controls - if @user == current_user = link_to profile_path, class: 'btn btn-gray' do = icon('pencil') - elsif current_user - %span.report-abuse - - if @user.abuse_report - %button.btn.btn-danger{ title: 'Already reported for abuse', - data: { toggle: 'tooltip', placement: 'left', container: 'body' }} - = icon('exclamation-circle') - - else - = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn btn-gray', - title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do - = icon('exclamation-circle') + - if @user.abuse_report + %button.btn.btn-danger{ title: 'Already reported for abuse', + data: { toggle: 'tooltip', placement: 'bottom', container: 'body' }} + = icon('exclamation-circle') + - else + = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn btn-gray', + title: 'Report abuse', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = icon('exclamation-circle') - if current_user - = link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do = icon('rss') - if current_user.admin? - = link_to [:admin, @user], class: 'btn btn-gray', title: 'View user in admin area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('users') - .avatar-holder - = link_to avatar_icon(@user, 400), target: '_blank' do - = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: '' - .cover-title - = @user.name - - .cover-desc - %span.middle-dot-divider - @#{@user.username} - %span.middle-dot-divider - Member since #{@user.created_at.to_s(:medium)} + .profile-header + .avatar-holder + = link_to avatar_icon(@user, 400), target: '_blank' do + = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: '' + + .user-info + .cover-title + = @user.name + %span.handle + @#{@user.username} + + .cover-desc.member-date + %span.middle-dot-divider + Member since #{@user.created_at.to_s(:medium)} + + .cover-desc + - unless @user.public_email.blank? + .profile-link-holder.middle-dot-divider + = link_to @user.public_email, "mailto:#{@user.public_email}" + - unless @user.skype.blank? + .profile-link-holder.middle-dot-divider + = link_to "skype:#{@user.skype}", title: "Skype" do + = icon('skype') + - unless @user.linkedin.blank? + .profile-link-holder.middle-dot-divider + = link_to "https://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do + = icon('linkedin-square') + - unless @user.twitter.blank? + .profile-link-holder.middle-dot-divider + = link_to "https://twitter.com/#{@user.twitter}", title: "Twitter" do + = icon('twitter-square') + - unless @user.website_url.blank? + .profile-link-holder.middle-dot-divider + = link_to @user.short_website_url, @user.full_website_url + - unless @user.location.blank? + .profile-link-holder.middle-dot-divider + = icon('map-marker') + = @user.location + - unless @user.organization.blank? + .profile-link-holder.middle-dot-divider + = icon('building') + = @user.organization - if @user.bio.present? .cover-desc %p.profile-user-bio = @user.bio - .cover-desc - - unless @user.public_email.blank? - .profile-link-holder.middle-dot-divider - = link_to @user.public_email, "mailto:#{@user.public_email}" - - unless @user.skype.blank? - .profile-link-holder.middle-dot-divider - = link_to "skype:#{@user.skype}", title: "Skype" do - = icon('skype') - - unless @user.linkedin.blank? - .profile-link-holder.middle-dot-divider - = link_to "https://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do - = icon('linkedin-square') - - unless @user.twitter.blank? - .profile-link-holder.middle-dot-divider - = link_to "https://twitter.com/#{@user.twitter}", title: "Twitter" do - = icon('twitter-square') - - unless @user.website_url.blank? - .profile-link-holder.middle-dot-divider - = link_to @user.short_website_url, @user.full_website_url - - unless @user.location.blank? - .profile-link-holder.middle-dot-divider - = icon('map-marker') - = @user.location - %ul.nav-links.center.user-profile-nav %li.js-activity-tab = link_to user_calendar_activities_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do |