diff options
author | Kamil Trzcinski <ayufan@ayufan.eu> | 2017-11-03 09:46:53 +0100 |
---|---|---|
committer | Kamil Trzcinski <ayufan@ayufan.eu> | 2017-11-03 09:46:53 +0100 |
commit | 1bbeafc3e1a0e4472a0d8124c9d4711356cde2d0 (patch) | |
tree | 1223af9ebd60b51e1903d0c27d716d1ff5bae639 /app | |
parent | 4477f7bb5925d8d720e3e8272bd882fffcc04b28 (diff) | |
parent | d51ad1ea6407d3cb9eafd9fc891c7348b10b108f (diff) | |
download | gitlab-ce-1bbeafc3e1a0e4472a0d8124c9d4711356cde2d0.tar.gz |
Merge remote-tracking branch 'origin/master' into 38464-k8s-apps
Diffstat (limited to 'app')
102 files changed, 1241 insertions, 1092 deletions
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index 5d27518ac85..9ae5e270a4b 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -1,6 +1,5 @@ /* eslint-disable comma-dangle, space-before-function-paren, no-new */ /* global MilestoneSelect */ -/* global LabelsSelect */ /* global Sidebar */ import Vue from 'vue'; @@ -11,6 +10,7 @@ import Assignees from '../../sidebar/components/assignees/assignees'; import DueDateSelectors from '../../due_date_select'; import './sidebar/remove_issue'; import IssuableContext from '../../issuable_context'; +import LabelsSelect from '../../labels_select'; const Store = gl.issueBoards.BoardsStore; diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index b516fda84b9..760fb0cdf67 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -3,7 +3,7 @@ import IssuableIndex from './issuable_index'; /* global Milestone */ import IssuableForm from './issuable_form'; -/* global LabelsSelect */ +import LabelsSelect from './labels_select'; /* global MilestoneSelect */ /* global NewBranchForm */ /* global NotificationsForm */ @@ -16,7 +16,7 @@ import CILintEditor from './ci_lint_editor'; import groupsSelect from './groups_select'; /* global Search */ /* global Admin */ -/* global NamespaceSelects */ +import NamespaceSelect from './namespace_select'; /* global NewCommitForm */ /* global NewBranchForm */ /* global Project */ @@ -575,7 +575,8 @@ import Diff from './diff'; new UsersSelect(); break; case 'projects': - new NamespaceSelects(); + document.querySelectorAll('.js-namespace-select') + .forEach(dropdown => new NamespaceSelect({ dropdown })); break; case 'labels': switch (path[2]) { diff --git a/app/assets/javascripts/droplab/utils.js b/app/assets/javascripts/droplab/utils.js index 4da7344604e..bfe056a0fcc 100644 --- a/app/assets/javascripts/droplab/utils.js +++ b/app/assets/javascripts/droplab/utils.js @@ -30,7 +30,7 @@ const utils = { }, isDropDownParts(target) { - if (!target || target.tagName === 'HTML') return false; + if (!target || !target.hasAttribute || target.tagName === 'HTML') return false; return target.hasAttribute(DATA_TRIGGER) || target.hasAttribute(DATA_DROPDOWN); }, }; diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index 7a17adcd44e..b7747ee3f83 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -119,11 +119,9 @@ export default function dropzoneInput(form) { // removeAllFiles(true) stops uploading files (if any) // and remove them from dropzone files queue. $cancelButton.on('click', (e) => { - const target = e.target.closest('.js-main-target-form').querySelector('.div-dropzone'); - e.preventDefault(); e.stopPropagation(); - Dropzone.forElement(target).removeAllFiles(true); + Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true); }); // If 'error' event is fired, we store a failed files, diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index 6de01fa53d0..fc0308b81ba 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -421,7 +421,11 @@ export default { </script> <template> <div - :class="{ 'js-child-row environment-child-row': model.isChildren, 'folder-row': model.isFolder, 'gl-responsive-table-row': !model.isFolder }" + class="gl-responsive-table-row" + :class="{ + 'js-child-row environment-child-row': model.isChildren, + 'folder-row': model.isFolder, + }" role="row"> <div class="table-section section-10" role="gridcell"> <div @@ -495,15 +499,16 @@ export default { </a> </div> - <div class="table-section section-25" role="gridcell"> + <div + v-if="!model.isFolder" + class="table-section section-25" role="gridcell"> <div - v-if="!model.isFolder" role="rowheader" class="table-mobile-header"> Commit </div> <div - v-if="!model.isFolder && hasLastDeploymentKey" + v-if="hasLastDeploymentKey" class="js-commit-component table-mobile-content"> <commit-component :tag="commitTag" @@ -514,21 +519,22 @@ export default { :author="commitAuthor"/> </div> <div - v-if="!model.isFolder && !hasLastDeploymentKey" + v-if="!hasLastDeploymentKey" class="commit-title table-mobile-content"> No deployments yet </div> </div> - <div class="table-section section-10" role="gridcell"> + <div + v-if="!model.isFolder" + class="table-section section-10" role="gridcell"> <div - v-if="!model.isFolder" role="rowheader" class="table-mobile-header"> Updated </div> <span - v-if="!model.isFolder && canShowDate" + v-if="canShowDate" class="environment-created-date-timeago table-mobile-content"> {{createdDate}} </span> diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index e8d8fef8579..c4202f92443 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -1,6 +1,7 @@ /* eslint-disable func-names, no-underscore-dangle, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */ /* global fuzzaldrinPlus */ import _ from 'underscore'; +import fuzzaldrinPlus from 'fuzzaldrin-plus'; import { isObject } from './lib/utils/type_utility'; var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, GitLabDropdownInput; diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js index cdc4fcf6573..e7232ca3712 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors.js @@ -4,6 +4,7 @@ import _ from 'underscore'; import d3 from 'd3'; import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph'; import ContributorsStatGraphUtil from './stat_graph_contributors_util'; +import { n__ } from '../locale'; export default (function() { function ContributorsStatGraph() {} @@ -44,7 +45,7 @@ export default (function() { commits = $('<span/>', { "class": 'graph-author-commits-count' }); - commits.text(author.commits + " commits"); + commits.text(n__('%d commit', '%d commits', author.commits)); return $('<span/>').append(commits); }; diff --git a/app/assets/javascripts/init_issuable_sidebar.js b/app/assets/javascripts/init_issuable_sidebar.js index 6f476f96f72..1191e0b895e 100644 --- a/app/assets/javascripts/init_issuable_sidebar.js +++ b/app/assets/javascripts/init_issuable_sidebar.js @@ -1,6 +1,6 @@ /* eslint-disable no-new */ /* global MilestoneSelect */ -/* global LabelsSelect */ +import LabelsSelect from './labels_select'; import IssuableContext from './issuable_context'; /* global Sidebar */ diff --git a/app/assets/javascripts/init_legacy_filters.js b/app/assets/javascripts/init_legacy_filters.js index fcf424408f2..1b265721581 100644 --- a/app/assets/javascripts/init_legacy_filters.js +++ b/app/assets/javascripts/init_legacy_filters.js @@ -1,5 +1,5 @@ /* eslint-disable no-new */ -/* global LabelsSelect */ +import LabelsSelect from './labels_select'; /* global MilestoneSelect */ /* global SubscriptionSelect */ diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js index 4a15ec8b147..af6358953cf 100644 --- a/app/assets/javascripts/issuable_bulk_update_sidebar.js +++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js @@ -1,5 +1,4 @@ /* eslint-disable class-methods-use-this, no-new */ -/* global LabelsSelect */ /* global MilestoneSelect */ /* global SubscriptionSelect */ @@ -7,7 +6,7 @@ import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; import './milestone_select'; import issueStatusSelect from './issue_status_select'; import './subscription_select'; -import './labels_select'; +import LabelsSelect from './labels_select'; const HIDDEN_CLASS = 'hidden'; const DISABLED_CONTENT_CLASS = 'disabled-content'; diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 84602cf9207..9b35efcb499 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -6,474 +6,470 @@ import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; import DropdownUtils from './filtered_search/dropdown_utils'; import CreateLabelDropdown from './create_label'; -(function() { - this.LabelsSelect = (function() { - function LabelsSelect(els) { - var _this, $els; - _this = this; +export default class LabelsSelect { + constructor(els) { + var _this, $els; + _this = this; - $els = $(els); + $els = $(els); - if (!els) { - $els = $('.js-label-select'); - } + if (!els) { + $els = $('.js-label-select'); + } - $els.each(function(i, dropdown) { - var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove, $container, $dropdownContainer; - $dropdown = $(dropdown); - $dropdownContainer = $dropdown.closest('.labels-filter'); - $toggleText = $dropdown.find('.dropdown-toggle-text'); - namespacePath = $dropdown.data('namespace-path'); - projectPath = $dropdown.data('project-path'); - labelUrl = $dropdown.data('labels'); - issueUpdateURL = $dropdown.data('issueUpdate'); - selectedLabel = $dropdown.data('selected'); - if ((selectedLabel != null) && !$dropdown.hasClass('js-multiselect')) { - selectedLabel = selectedLabel.split(','); - } - showNo = $dropdown.data('show-no'); - showAny = $dropdown.data('show-any'); - showMenuAbove = $dropdown.data('showMenuAbove'); - defaultLabel = $dropdown.data('default-label'); - abilityName = $dropdown.data('ability-name'); - $selectbox = $dropdown.closest('.selectbox'); - $block = $selectbox.closest('.block'); - $form = $dropdown.closest('form, .js-issuable-update'); - $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span'); - $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip'); - $value = $block.find('.value'); - $loading = $block.find('.block-loading').fadeOut(); - fieldName = $dropdown.data('field-name'); - useId = $dropdown.is('.js-issuable-form-dropdown, .js-filter-bulk-update, .js-label-sidebar-dropdown'); - propertyName = useId ? 'id' : 'title'; - initialSelected = $selectbox - .find('input[name="' + $dropdown.data('field-name') + '"]') - .map(function () { - return this.value; - }).get(); - if (issueUpdateURL != null) { - issueURLSplit = issueUpdateURL.split('/'); - } - if (issueUpdateURL) { - labelHTMLTemplate = _.template('<% _.each(labels, function(label){ %> <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>"> <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;"> <%- label.title %> </span> </a> <% }); %>'); - labelNoneHTMLTemplate = '<span class="no-value">None</span>'; - } + $els.each(function(i, dropdown) { + var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove, $container, $dropdownContainer; + $dropdown = $(dropdown); + $dropdownContainer = $dropdown.closest('.labels-filter'); + $toggleText = $dropdown.find('.dropdown-toggle-text'); + namespacePath = $dropdown.data('namespace-path'); + projectPath = $dropdown.data('project-path'); + labelUrl = $dropdown.data('labels'); + issueUpdateURL = $dropdown.data('issueUpdate'); + selectedLabel = $dropdown.data('selected'); + if ((selectedLabel != null) && !$dropdown.hasClass('js-multiselect')) { + selectedLabel = selectedLabel.split(','); + } + showNo = $dropdown.data('show-no'); + showAny = $dropdown.data('show-any'); + showMenuAbove = $dropdown.data('showMenuAbove'); + defaultLabel = $dropdown.data('default-label'); + abilityName = $dropdown.data('ability-name'); + $selectbox = $dropdown.closest('.selectbox'); + $block = $selectbox.closest('.block'); + $form = $dropdown.closest('form, .js-issuable-update'); + $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span'); + $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip'); + $value = $block.find('.value'); + $loading = $block.find('.block-loading').fadeOut(); + fieldName = $dropdown.data('field-name'); + useId = $dropdown.is('.js-issuable-form-dropdown, .js-filter-bulk-update, .js-label-sidebar-dropdown'); + propertyName = useId ? 'id' : 'title'; + initialSelected = $selectbox + .find('input[name="' + $dropdown.data('field-name') + '"]') + .map(function () { + return this.value; + }).get(); + if (issueUpdateURL != null) { + issueURLSplit = issueUpdateURL.split('/'); + } + if (issueUpdateURL) { + labelHTMLTemplate = _.template('<% _.each(labels, function(label){ %> <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>"> <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;"> <%- label.title %> </span> </a> <% }); %>'); + labelNoneHTMLTemplate = '<span class="no-value">None</span>'; + } - $sidebarLabelTooltip.tooltip(); + $sidebarLabelTooltip.tooltip(); - if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) { - new CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), namespacePath, projectPath); - } + if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) { + new CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), namespacePath, projectPath); + } - saveLabelData = function() { - var data, selected; - selected = $dropdown.closest('.selectbox').find("input[name='" + fieldName + "']").map(function() { - return this.value; - }).get(); + saveLabelData = function() { + var data, selected; + selected = $dropdown.closest('.selectbox').find("input[name='" + fieldName + "']").map(function() { + return this.value; + }).get(); - if (_.isEqual(initialSelected, selected)) return; - initialSelected = selected; + if (_.isEqual(initialSelected, selected)) return; + initialSelected = selected; - data = {}; - data[abilityName] = {}; - data[abilityName].label_ids = selected; - if (!selected.length) { - data[abilityName].label_ids = ['']; + data = {}; + data[abilityName] = {}; + data[abilityName].label_ids = selected; + if (!selected.length) { + data[abilityName].label_ids = ['']; + } + $loading.removeClass('hidden').fadeIn(); + $dropdown.trigger('loading.gl.dropdown'); + return $.ajax({ + type: 'PUT', + url: issueUpdateURL, + dataType: 'JSON', + data: data + }).done(function(data) { + var labelCount, template, labelTooltipTitle, labelTitles; + $loading.fadeOut(); + $dropdown.trigger('loaded.gl.dropdown'); + $selectbox.hide(); + data.issueURLSplit = issueURLSplit; + labelCount = 0; + if (data.labels.length) { + template = labelHTMLTemplate(data); + labelCount = data.labels.length; } - $loading.removeClass('hidden').fadeIn(); - $dropdown.trigger('loading.gl.dropdown'); - return $.ajax({ - type: 'PUT', - url: issueUpdateURL, - dataType: 'JSON', - data: data - }).done(function(data) { - var labelCount, template, labelTooltipTitle, labelTitles; - $loading.fadeOut(); - $dropdown.trigger('loaded.gl.dropdown'); - $selectbox.hide(); - data.issueURLSplit = issueURLSplit; - labelCount = 0; - if (data.labels.length) { - template = labelHTMLTemplate(data); - labelCount = data.labels.length; - } - else { - template = labelNoneHTMLTemplate; - } - $value.removeAttr('style').html(template); - $sidebarCollapsedValue.text(labelCount); - - if (data.labels.length) { - labelTitles = data.labels.map(function(label) { - return label.title; - }); + else { + template = labelNoneHTMLTemplate; + } + $value.removeAttr('style').html(template); + $sidebarCollapsedValue.text(labelCount); - if (labelTitles.length > 5) { - labelTitles = labelTitles.slice(0, 5); - labelTitles.push('and ' + (data.labels.length - 5) + ' more'); - } + if (data.labels.length) { + labelTitles = data.labels.map(function(label) { + return label.title; + }); - labelTooltipTitle = labelTitles.join(', '); - } - else { - labelTooltipTitle = ''; - $sidebarLabelTooltip.tooltip('destroy'); + if (labelTitles.length > 5) { + labelTitles = labelTitles.slice(0, 5); + labelTitles.push('and ' + (data.labels.length - 5) + ' more'); } - $sidebarLabelTooltip - .attr('title', labelTooltipTitle) - .tooltip('fixTitle'); + labelTooltipTitle = labelTitles.join(', '); + } + else { + labelTooltipTitle = ''; + $sidebarLabelTooltip.tooltip('destroy'); + } - $('.has-tooltip', $value).tooltip({ - container: 'body' - }); + $sidebarLabelTooltip + .attr('title', labelTooltipTitle) + .tooltip('fixTitle'); + + $('.has-tooltip', $value).tooltip({ + container: 'body' }); - }; - $dropdown.glDropdown({ - showMenuAbove: showMenuAbove, - data: function(term, callback) { - return $.ajax({ - url: labelUrl - }).done(function(data) { - data = _.chain(data).groupBy(function(label) { - return label.title; - }).map(function(label) { - var color; - color = _.map(label, function(dup) { - return dup.color; + }); + }; + $dropdown.glDropdown({ + showMenuAbove: showMenuAbove, + data: function(term, callback) { + return $.ajax({ + url: labelUrl + }).done(function(data) { + data = _.chain(data).groupBy(function(label) { + return label.title; + }).map(function(label) { + var color; + color = _.map(label, function(dup) { + return dup.color; + }); + return { + id: label[0].id, + title: label[0].title, + color: color, + duplicate: color.length > 1 + }; + }).value(); + if ($dropdown.hasClass('js-extra-options')) { + var extraData = []; + if (showNo) { + extraData.unshift({ + id: 0, + title: 'No Label' }); - return { - id: label[0].id, - title: label[0].title, - color: color, - duplicate: color.length > 1 - }; - }).value(); - if ($dropdown.hasClass('js-extra-options')) { - var extraData = []; - if (showNo) { - extraData.unshift({ - id: 0, - title: 'No Label' - }); - } - if (showAny) { - extraData.unshift({ - isAny: true, - title: 'Any Label' - }); - } - if (extraData.length) { - extraData.push('divider'); - data = extraData.concat(data); - } - } - - callback(data); - if (showMenuAbove) { - $dropdown.data('glDropdown').positionMenuAbove(); - } - }); - }, - renderRow: function(label, instance) { - var $a, $li, color, colorEl, indeterminate, removesAll, selectedClass, spacing, i, marked, dropdownName, dropdownValue; - $li = $('<li>'); - $a = $('<a href="#">'); - selectedClass = []; - removesAll = label.id <= 0 || (label.id == null); - if ($dropdown.hasClass('js-filter-bulk-update')) { - indeterminate = $dropdown.data('indeterminate') || []; - marked = $dropdown.data('marked') || []; - - if (indeterminate.indexOf(label.id) !== -1) { - selectedClass.push('is-indeterminate'); } - - if (marked.indexOf(label.id) !== -1) { - // Remove is-indeterminate class if the item will be marked as active - i = selectedClass.indexOf('is-indeterminate'); - if (i !== -1) { - selectedClass.splice(i, 1); - } - selectedClass.push('is-active'); + if (showAny) { + extraData.unshift({ + isAny: true, + title: 'Any Label' + }); } - } else { - if (this.id(label)) { - dropdownName = $dropdown.data('fieldName'); - dropdownValue = this.id(label).toString().replace(/'/g, '\\\''); - - if ($form.find("input[type='hidden'][name='" + dropdownName + "'][value='" + dropdownValue + "']").length) { - selectedClass.push('is-active'); - } + if (extraData.length) { + extraData.push('divider'); + data = extraData.concat(data); } + } - if ($dropdown.hasClass('js-multiselect') && removesAll) { - selectedClass.push('dropdown-clear-active'); - } + callback(data); + if (showMenuAbove) { + $dropdown.data('glDropdown').positionMenuAbove(); } - if (label.duplicate) { - color = gl.DropdownUtils.duplicateLabelColor(label.color); + }); + }, + renderRow: function(label, instance) { + var $a, $li, color, colorEl, indeterminate, removesAll, selectedClass, spacing, i, marked, dropdownName, dropdownValue; + $li = $('<li>'); + $a = $('<a href="#">'); + selectedClass = []; + removesAll = label.id <= 0 || (label.id == null); + if ($dropdown.hasClass('js-filter-bulk-update')) { + indeterminate = $dropdown.data('indeterminate') || []; + marked = $dropdown.data('marked') || []; + + if (indeterminate.indexOf(label.id) !== -1) { + selectedClass.push('is-indeterminate'); } - else { - if (label.color != null) { - color = label.color[0]; + + if (marked.indexOf(label.id) !== -1) { + // Remove is-indeterminate class if the item will be marked as active + i = selectedClass.indexOf('is-indeterminate'); + if (i !== -1) { + selectedClass.splice(i, 1); } + selectedClass.push('is-active'); } - if (color) { - colorEl = "<span class='dropdown-label-box' style='background: " + color + "'></span>"; - } - else { - colorEl = ''; - } - // We need to identify which items are actually labels - if (label.id) { - selectedClass.push('label-item'); - $a.attr('data-label-id', label.id); - } - $a.addClass(selectedClass.join(' ')).html(colorEl + " " + label.title); - // Return generated html - return $li.html($a).prop('outerHTML'); - }, - search: { - fields: ['title'] - }, - selectable: true, - filterable: true, - selected: $dropdown.data('selected') || [], - toggleLabel: function(selected, el) { - var isSelected = el !== null ? el.hasClass('is-active') : false; - var title = selected.title; - var selectedLabels = this.selected; - - if (selected.id === 0) { - this.selected = []; - return 'No Label'; + } else { + if (this.id(label)) { + dropdownName = $dropdown.data('fieldName'); + dropdownValue = this.id(label).toString().replace(/'/g, '\\\''); + + if ($form.find("input[type='hidden'][name='" + dropdownName + "'][value='" + dropdownValue + "']").length) { + selectedClass.push('is-active'); + } } - else if (isSelected) { - this.selected.push(title); + + if ($dropdown.hasClass('js-multiselect') && removesAll) { + selectedClass.push('dropdown-clear-active'); } - else { - var index = this.selected.indexOf(title); - this.selected.splice(index, 1); + } + if (label.duplicate) { + color = gl.DropdownUtils.duplicateLabelColor(label.color); + } + else { + if (label.color != null) { + color = label.color[0]; } + } + if (color) { + colorEl = "<span class='dropdown-label-box' style='background: " + color + "'></span>"; + } + else { + colorEl = ''; + } + // We need to identify which items are actually labels + if (label.id) { + selectedClass.push('label-item'); + $a.attr('data-label-id', label.id); + } + $a.addClass(selectedClass.join(' ')).html(colorEl + " " + label.title); + // Return generated html + return $li.html($a).prop('outerHTML'); + }, + search: { + fields: ['title'] + }, + selectable: true, + filterable: true, + selected: $dropdown.data('selected') || [], + toggleLabel: function(selected, el) { + var isSelected = el !== null ? el.hasClass('is-active') : false; + var title = selected.title; + var selectedLabels = this.selected; + + if (selected.id === 0) { + this.selected = []; + return 'No Label'; + } + else if (isSelected) { + this.selected.push(title); + } + else { + var index = this.selected.indexOf(title); + this.selected.splice(index, 1); + } + + if (selectedLabels.length === 1) { + return selectedLabels; + } + else if (selectedLabels.length) { + return selectedLabels[0] + " +" + (selectedLabels.length - 1) + " more"; + } + else { + return defaultLabel; + } + }, + fieldName: $dropdown.data('field-name'), + id: function(label) { + if (label.id <= 0) return label.title; + + if ($dropdown.hasClass('js-issuable-form-dropdown')) { + return label.id; + } - if (selectedLabels.length === 1) { - return selectedLabels; + if ($dropdown.hasClass("js-filter-submit") && (label.isAny == null)) { + return label.title; + } + else { + return label.id; + } + }, + hidden: function() { + var isIssueIndex, isMRIndex, page, selectedLabels; + page = $('body').attr('data-page'); + isIssueIndex = page === 'projects:issues:index'; + isMRIndex = page === 'projects:merge_requests:index'; + $selectbox.hide(); + // display:block overrides the hide-collapse rule + $value.removeAttr('style'); + + if ($dropdown.hasClass('js-issuable-form-dropdown')) { + return; + } + + if ($('html').hasClass('issue-boards-page')) { + return; + } + if ($dropdown.hasClass('js-multiselect')) { + if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { + selectedLabels = $dropdown.closest('form').find("input:hidden[name='" + ($dropdown.data('fieldName')) + "']"); + Issuable.filterResults($dropdown.closest('form')); } - else if (selectedLabels.length) { - return selectedLabels[0] + " +" + (selectedLabels.length - 1) + " more"; + else if ($dropdown.hasClass('js-filter-submit')) { + $dropdown.closest('form').submit(); } else { - return defaultLabel; + if (!$dropdown.hasClass('js-filter-bulk-update')) { + saveLabelData(); + } } - }, - fieldName: $dropdown.data('field-name'), - id: function(label) { - if (label.id <= 0) return label.title; + } + }, + multiSelect: $dropdown.hasClass('js-multiselect'), + vue: $dropdown.hasClass('js-issue-board-sidebar'), + clicked: function(options) { + const { $el, e, isMarking } = options; + const label = options.selectedObj; + + var isIssueIndex, isMRIndex, page, boardsModel; + var fadeOutLoader = () => { + $loading.fadeOut(); + }; - if ($dropdown.hasClass('js-issuable-form-dropdown')) { - return label.id; - } + page = $('body').attr('data-page'); + isIssueIndex = page === 'projects:issues:index'; + isMRIndex = page === 'projects:merge_requests:index'; - if ($dropdown.hasClass("js-filter-submit") && (label.isAny == null)) { - return label.title; - } - else { - return label.id; - } - }, - hidden: function() { - var isIssueIndex, isMRIndex, page, selectedLabels; - page = $('body').attr('data-page'); - isIssueIndex = page === 'projects:issues:index'; - isMRIndex = page === 'projects:merge_requests:index'; - $selectbox.hide(); - // display:block overrides the hide-collapse rule - $value.removeAttr('style'); - - if ($dropdown.hasClass('js-issuable-form-dropdown')) { - return; - } + if ($dropdown.parent().find('.is-active:not(.dropdown-clear-active)').length) { + $dropdown.parent() + .find('.dropdown-clear-active') + .removeClass('is-active'); + } - if ($('html').hasClass('issue-boards-page')) { - return; - } - if ($dropdown.hasClass('js-multiselect')) { - if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { - selectedLabels = $dropdown.closest('form').find("input:hidden[name='" + ($dropdown.data('fieldName')) + "']"); - Issuable.filterResults($dropdown.closest('form')); - } - else if ($dropdown.hasClass('js-filter-submit')) { - $dropdown.closest('form').submit(); - } - else { - if (!$dropdown.hasClass('js-filter-bulk-update')) { - saveLabelData(); - } - } - } - }, - multiSelect: $dropdown.hasClass('js-multiselect'), - vue: $dropdown.hasClass('js-issue-board-sidebar'), - clicked: function(options) { - const { $el, e, isMarking } = options; - const label = options.selectedObj; - - var isIssueIndex, isMRIndex, page, boardsModel; - var fadeOutLoader = () => { - $loading.fadeOut(); - }; - - page = $('body').attr('data-page'); - isIssueIndex = page === 'projects:issues:index'; - isMRIndex = page === 'projects:merge_requests:index'; - - if ($dropdown.parent().find('.is-active:not(.dropdown-clear-active)').length) { - $dropdown.parent() - .find('.dropdown-clear-active') - .removeClass('is-active'); - } + if ($dropdown.hasClass('js-issuable-form-dropdown')) { + return; + } - if ($dropdown.hasClass('js-issuable-form-dropdown')) { - return; - } + if ($dropdown.hasClass('js-filter-bulk-update')) { + _this.enableBulkLabelDropdown(); + _this.setDropdownData($dropdown, isMarking, label.id); + return; + } - if ($dropdown.hasClass('js-filter-bulk-update')) { - _this.enableBulkLabelDropdown(); - _this.setDropdownData($dropdown, isMarking, label.id); - return; - } + if ($dropdown.closest('.add-issues-modal').length) { + boardsModel = gl.issueBoards.ModalStore.store.filter; + } - if ($dropdown.closest('.add-issues-modal').length) { - boardsModel = gl.issueBoards.ModalStore.store.filter; + if (boardsModel) { + if (label.isAny) { + boardsModel['label_name'] = []; + } else if ($el.hasClass('is-active')) { + boardsModel['label_name'].push(label.title); } - if (boardsModel) { - if (label.isAny) { - boardsModel['label_name'] = []; - } else if ($el.hasClass('is-active')) { - boardsModel['label_name'].push(label.title); - } - - e.preventDefault(); - return; + e.preventDefault(); + return; + } + else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { + if (!$dropdown.hasClass('js-multiselect')) { + selectedLabel = label.title; + return Issuable.filterResults($dropdown.closest('form')); } - else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { - if (!$dropdown.hasClass('js-multiselect')) { - selectedLabel = label.title; - return Issuable.filterResults($dropdown.closest('form')); - } + } + else if ($dropdown.hasClass('js-filter-submit')) { + return $dropdown.closest('form').submit(); + } + else if ($dropdown.hasClass('js-issue-board-sidebar')) { + if ($el.hasClass('is-active')) { + gl.issueBoards.BoardsStore.detail.issue.labels.push(new ListLabel({ + id: label.id, + title: label.title, + color: label.color[0], + textColor: '#fff' + })); } - else if ($dropdown.hasClass('js-filter-submit')) { - return $dropdown.closest('form').submit(); + else { + var labels = gl.issueBoards.BoardsStore.detail.issue.labels; + labels = labels.filter(function (selectedLabel) { + return selectedLabel.id !== label.id; + }); + gl.issueBoards.BoardsStore.detail.issue.labels = labels; } - else if ($dropdown.hasClass('js-issue-board-sidebar')) { - if ($el.hasClass('is-active')) { - gl.issueBoards.BoardsStore.detail.issue.labels.push(new ListLabel({ - id: label.id, - title: label.title, - color: label.color[0], - textColor: '#fff' - })); - } - else { - var labels = gl.issueBoards.BoardsStore.detail.issue.labels; - labels = labels.filter(function (selectedLabel) { - return selectedLabel.id !== label.id; - }); - gl.issueBoards.BoardsStore.detail.issue.labels = labels; - } - $loading.fadeIn(); + $loading.fadeIn(); + + gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) + .then(fadeOutLoader) + .catch(fadeOutLoader); + } + else { + if ($dropdown.hasClass('js-multiselect')) { - gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) - .then(fadeOutLoader) - .catch(fadeOutLoader); } else { - if ($dropdown.hasClass('js-multiselect')) { - - } - else { - return saveLabelData(); - } + return saveLabelData(); } - }, - }); - - // Set dropdown data - _this.setOriginalDropdownData($dropdownContainer, $dropdown); + } + }, }); - this.bindEvents(); - } - LabelsSelect.prototype.bindEvents = function() { - return $('body').on('change', '.selected_issue', this.onSelectCheckboxIssue); - }; - - LabelsSelect.prototype.onSelectCheckboxIssue = function() { - if ($('.selected_issue:checked').length) { - return; + // Set dropdown data + _this.setOriginalDropdownData($dropdownContainer, $dropdown); + }); + this.bindEvents(); + } + + bindEvents() { + return $('body').on('change', '.selected_issue', this.onSelectCheckboxIssue); + } + // eslint-disable-next-line class-methods-use-this + onSelectCheckboxIssue() { + if ($('.selected_issue:checked').length) { + return; + } + return $('.issues-bulk-update .labels-filter .dropdown-toggle-text').text('Label'); + } + // eslint-disable-next-line class-methods-use-this + enableBulkLabelDropdown() { + IssuableBulkUpdateActions.willUpdateLabels = true; + } + // eslint-disable-next-line class-methods-use-this + setDropdownData($dropdown, isMarking, value) { + var i, markedIds, unmarkedIds, indeterminateIds; + + markedIds = $dropdown.data('marked') || []; + unmarkedIds = $dropdown.data('unmarked') || []; + indeterminateIds = $dropdown.data('indeterminate') || []; + + if (isMarking) { + markedIds.push(value); + + i = indeterminateIds.indexOf(value); + if (i > -1) { + indeterminateIds.splice(i, 1); } - return $('.issues-bulk-update .labels-filter .dropdown-toggle-text').text('Label'); - }; - LabelsSelect.prototype.enableBulkLabelDropdown = function() { - IssuableBulkUpdateActions.willUpdateLabels = true; - }; - - LabelsSelect.prototype.setDropdownData = function($dropdown, isMarking, value) { - var i, markedIds, unmarkedIds, indeterminateIds; - - markedIds = $dropdown.data('marked') || []; - unmarkedIds = $dropdown.data('unmarked') || []; - indeterminateIds = $dropdown.data('indeterminate') || []; - - if (isMarking) { - markedIds.push(value); - - i = indeterminateIds.indexOf(value); - if (i > -1) { - indeterminateIds.splice(i, 1); - } - - i = unmarkedIds.indexOf(value); - if (i > -1) { - unmarkedIds.splice(i, 1); - } - } else { - // If marked item (not common) is unmarked - i = markedIds.indexOf(value); - if (i > -1) { - markedIds.splice(i, 1); - } - - // If an indeterminate item is being unmarked - if (IssuableBulkUpdateActions.getOriginalIndeterminateIds().indexOf(value) > -1) { - unmarkedIds.push(value); - } - - // If a marked item is being unmarked - // (a marked item could also be a label that is present in all selection) - if (IssuableBulkUpdateActions.getOriginalCommonIds().indexOf(value) > -1) { - unmarkedIds.push(value); - } + i = unmarkedIds.indexOf(value); + if (i > -1) { + unmarkedIds.splice(i, 1); + } + } else { + // If marked item (not common) is unmarked + i = markedIds.indexOf(value); + if (i > -1) { + markedIds.splice(i, 1); } - $dropdown.data('marked', markedIds); - $dropdown.data('unmarked', unmarkedIds); - $dropdown.data('indeterminate', indeterminateIds); - }; + // If an indeterminate item is being unmarked + if (IssuableBulkUpdateActions.getOriginalIndeterminateIds().indexOf(value) > -1) { + unmarkedIds.push(value); + } - LabelsSelect.prototype.setOriginalDropdownData = function($container, $dropdown) { - var labels = []; - $container.find('[name="label_name[]"]').map(function() { - return labels.push(this.value); - }); - $dropdown.data('marked', labels); - }; + // If a marked item is being unmarked + // (a marked item could also be a label that is present in all selection) + if (IssuableBulkUpdateActions.getOriginalCommonIds().indexOf(value) > -1) { + unmarkedIds.push(value); + } + } - return LabelsSelect; - })(); -}).call(window); + $dropdown.data('marked', markedIds); + $dropdown.data('unmarked', unmarkedIds); + $dropdown.data('indeterminate', indeterminateIds); + } + // eslint-disable-next-line class-methods-use-this + setOriginalDropdownData($container, $dropdown) { + const labels = []; + $container.find('[name="label_name[]"]').map(function() { + return labels.push(this.value); + }); + $dropdown.data('marked', labels); + } +} diff --git a/app/assets/javascripts/lazy_loader.js b/app/assets/javascripts/lazy_loader.js index 3d64b121fa7..dbbf1637a47 100644 --- a/app/assets/javascripts/lazy_loader.js +++ b/app/assets/javascripts/lazy_loader.js @@ -1,5 +1,3 @@ -/* eslint-disable one-export, one-var, one-var-declaration-per-line */ - import _ from 'underscore'; export const placeholderImage = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; @@ -21,7 +19,10 @@ export default class LazyLoader { } searchLazyImages() { this.lazyImages = [].slice.call(document.querySelectorAll('.lazy')); - this.checkElementsInView(); + + if (this.lazyImages.length) { + this.checkElementsInView(); + } } startContentObserver() { const contentNode = document.querySelector(this.observerNode) || document.querySelector('body'); @@ -45,15 +46,13 @@ export default class LazyLoader { checkElementsInView() { const scrollTop = pageYOffset; const visHeight = scrollTop + innerHeight + SCROLL_THRESHOLD; - let imgBoundRect, imgTop, imgBound; // Loading Images which are in the current viewport or close to them this.lazyImages = this.lazyImages.filter((selectedImage) => { if (selectedImage.getAttribute('data-src')) { - imgBoundRect = selectedImage.getBoundingClientRect(); - - imgTop = scrollTop + imgBoundRect.top; - imgBound = imgTop + imgBoundRect.height; + const imgBoundRect = selectedImage.getBoundingClientRect(); + const imgTop = scrollTop + imgBoundRect.top; + const imgBound = imgTop + imgBoundRect.height; if (scrollTop < imgBound && visHeight > imgTop) { LazyLoader.loadImage(selectedImage); diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js index 729baa2e1a7..3688a57937e 100644 --- a/app/assets/javascripts/logo.js +++ b/app/assets/javascripts/logo.js @@ -1,7 +1,5 @@ -/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback */ - -(function() { - window.addEventListener('beforeunload', function() { +export default function initLogoAnimation() { + window.addEventListener('beforeunload', () => { $('.tanuki-logo').addClass('animate'); }); -}).call(window); +} diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index d743f20c615..9117f033c9f 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -12,7 +12,6 @@ import svg4everybody from 'svg4everybody'; // libraries with import side-effects import 'mousetrap'; import 'mousetrap/plugins/pause/mousetrap-pause'; -import 'vendor/fuzzaldrin-plus'; // expose common libraries as globals (TODO: remove these) window.jQuery = jQuery; @@ -56,11 +55,10 @@ import './gl_field_errors'; import './gl_form'; import initTodoToggle from './header'; import initImporterStatus from './importer_status'; -import './labels_select'; import './layout_nav'; import LazyLoader from './lazy_loader'; import './line_highlighter'; -import './logo'; +import initLogoAnimation from './logo'; import './merge_request'; import './merge_request_tabs'; import './milestone'; @@ -135,6 +133,7 @@ $(function () { initBreadcrumbs(); initImporterStatus(); initTodoToggle(); + initLogoAnimation(); // Set the default path for all cookies to GitLab's root directory Cookies.defaults.path = gon.relative_url_root || '/'; diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js index 5da2db063a4..1d496c64e53 100644 --- a/app/assets/javascripts/namespace_select.js +++ b/app/assets/javascripts/namespace_select.js @@ -1,85 +1,57 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, vars-on-top, one-var-declaration-per-line, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, no-param-reassign, no-cond-assign, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, max-len */ import Api from './api'; +import './lib/utils/url_utility'; -(function() { - window.NamespaceSelect = (function() { - function NamespaceSelect(opts) { - this.onSelectItem = this.onSelectItem.bind(this); - var fieldName, showAny; - this.dropdown = opts.dropdown; - showAny = true; - fieldName = 'namespace_id'; - if (this.dropdown.attr('data-field-name')) { - fieldName = this.dropdown.data('fieldName'); - } - if (this.dropdown.attr('data-show-any')) { - showAny = this.dropdown.data('showAny'); - } - this.dropdown.glDropdown({ - filterable: true, - selectable: true, - filterRemote: true, - search: { - fields: ['path'] - }, - fieldName: fieldName, - toggleLabel: function(selected) { - if (selected.id == null) { - return selected.text; - } else { - return selected.kind + ": " + selected.full_path; - } - }, - data: function(term, dataCallback) { - return Api.namespaces(term, function(namespaces) { - var anyNamespace; - if (showAny) { - anyNamespace = { - text: 'Any namespace', - id: null - }; - namespaces.unshift(anyNamespace); - namespaces.splice(1, 0, 'divider'); - } - return dataCallback(namespaces); - }); - }, - text: function(namespace) { - if (namespace.id == null) { - return namespace.text; - } else { - return namespace.kind + ": " + namespace.full_path; - } - }, - renderRow: this.renderRow, - clicked: this.onSelectItem - }); - } - - NamespaceSelect.prototype.onSelectItem = function(options) { - const { e } = options; - return e.preventDefault(); - }; +export default class NamespaceSelect { + constructor(opts) { + const isFilter = opts.dropdown.dataset.isFilter === 'true'; + const fieldName = opts.dropdown.dataset.fieldName || 'namespace_id'; - return NamespaceSelect; - })(); - - window.NamespaceSelects = (function() { - function NamespaceSelects(opts) { - var ref; - if (opts == null) { - opts = {}; - } - this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-namespace-select'); - this.$dropdowns.each(function(i, dropdown) { - var $dropdown; - $dropdown = $(dropdown); - return new window.NamespaceSelect({ - dropdown: $dropdown + $(opts.dropdown).glDropdown({ + filterable: true, + selectable: true, + filterRemote: true, + search: { + fields: ['path'] + }, + fieldName: fieldName, + toggleLabel: function(selected) { + if (selected.id == null) { + return selected.text; + } else { + return selected.kind + ": " + selected.full_path; + } + }, + data: function(term, dataCallback) { + return Api.namespaces(term, function(namespaces) { + if (isFilter) { + const anyNamespace = { + text: 'Any namespace', + id: null + }; + namespaces.unshift(anyNamespace); + namespaces.splice(1, 0, 'divider'); + } + return dataCallback(namespaces); }); - }); - } - - return NamespaceSelects; - })(); -}).call(window); + }, + text: function(namespace) { + if (namespace.id == null) { + return namespace.text; + } else { + return namespace.kind + ": " + namespace.full_path; + } + }, + renderRow: this.renderRow, + clicked(options) { + if (!isFilter) { + const { e } = options; + e.preventDefault(); + } + }, + url(namespace) { + return gl.utils.mergeUrlParams({ [fieldName]: namespace.id }, window.location.href); + }, + }); + } +} diff --git a/app/assets/javascripts/notes/components/issue_note.vue b/app/assets/javascripts/notes/components/issue_note.vue index 0ddbd672bed..40318f9a600 100644 --- a/app/assets/javascripts/notes/components/issue_note.vue +++ b/app/assets/javascripts/notes/components/issue_note.vue @@ -122,7 +122,9 @@ // we need to do this to prevent noteForm inconsistent content warning // this is something we intentionally do so we need to recover the content this.note.note = noteText; - this.$refs.noteBody.$refs.noteForm.note = noteText; // TODO: This could be better + if (this.$refs.noteBody) { + this.$refs.noteBody.$refs.noteForm.note = noteText; // TODO: This could be better + } }, }, created() { diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue index 54227425d2a..547140b1a43 100644 --- a/app/assets/javascripts/pipelines/components/graph/action_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue @@ -1,6 +1,6 @@ <script> - import getActionIcon from '../../../vue_shared/ci_action_icons'; import tooltip from '../../../vue_shared/directives/tooltip'; + import icon from '../../../vue_shared/components/icon.vue'; /** * Renders either a cancel, retry or play icon pointing to the given path. @@ -29,17 +29,18 @@ }, }, + components: { + icon, + }, + directives: { tooltip, }, computed: { - actionIconSvg() { - return getActionIcon(this.actionIcon); - }, - cssClass() { - return `js-${gl.text.dasherize(this.actionIcon)}`; + const actionIconDash = gl.text.dasherize(this.actionIcon); + return `${actionIconDash} js-icon-${actionIconDash}`; }, }, }; @@ -50,14 +51,9 @@ :data-method="actionMethod" :title="tooltipText" :href="link" - class="ci-action-icon-container" + class="ci-action-icon-container ci-action-icon-wrapper" + :class="cssClass" data-container="body"> - - <i - class="ci-action-icon-wrapper" - :class="cssClass" - v-html="actionIconSvg" - aria-hidden="true" - /> + <icon :name="actionIcon"/> </a> </template> diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue index 18fe1847eef..1c0944d45fc 100644 --- a/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue @@ -1,5 +1,5 @@ <script> - import getActionIcon from '../../../vue_shared/ci_action_icons'; + import icon from '../../../vue_shared/components/icon.vue'; import tooltip from '../../../vue_shared/directives/tooltip'; /** @@ -29,14 +29,12 @@ }, }, - directives: { - tooltip, + components: { + icon, }, - computed: { - actionIconSvg() { - return getActionIcon(this.actionIcon); - }, + directives: { + tooltip, }, }; </script> @@ -49,7 +47,7 @@ rel="nofollow" class="ci-action-icon-wrapper js-ci-status-icon" data-container="body" - v-html="actionIconSvg" aria-label="Job's action"> + <icon :name="actionIcon"/> </a> </template> diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue index 3e5d6d15909..7006d05e7b2 100644 --- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue @@ -18,7 +18,7 @@ * "group": "success", * "details_path": "/root/ci-mock/builds/4256", * "action": { - * "icon": "icon_action_retry", + * "icon": "retry", * "title": "Retry", * "path": "/root/ci-mock/builds/4256/retry", * "method": "post" diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue index 3933509a6f4..5dea4555515 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue @@ -19,7 +19,7 @@ * "group": "success", * "details_path": "/root/ci-mock/builds/4256", * "action": { - * "icon": "icon_action_retry", + * "icon": "retry", * "title": "Retry", * "path": "/root/ci-mock/builds/4256/retry", * "method": "post" diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index 1a7a5c2a415..ac9d9c901ca 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -14,7 +14,7 @@ */ import Flash from '../../flash'; -import { borderlessStatusIconEntityMap } from '../../vue_shared/ci_status_icons'; +import icon from '../../vue_shared/components/icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import tooltip from '../../vue_shared/directives/tooltip'; @@ -45,6 +45,7 @@ export default { components: { loadingIcon, + icon, }, updated() { @@ -122,8 +123,8 @@ export default { return `ci-status-icon-${this.stage.status.group}`; }, - svgIcon() { - return borderlessStatusIconEntityMap[this.stage.status.icon]; + borderlessIcon() { + return `${this.stage.status.icon}_borderless`; }, }, }; @@ -145,9 +146,10 @@ export default { aria-expanded="false"> <span - v-html="svgIcon" aria-hidden="true" :aria-label="stage.title"> + <icon + :name="borderlessIcon"/> </span> <i diff --git a/app/assets/javascripts/profile/account/components/delete_account_modal.vue b/app/assets/javascripts/profile/account/components/delete_account_modal.vue index b2b34cb83e1..6348a2e331d 100644 --- a/app/assets/javascripts/profile/account/components/delete_account_modal.vue +++ b/app/assets/javascripts/profile/account/components/delete_account_modal.vue @@ -98,7 +98,7 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`), @toggle="toggleOpen" @submit="onSubmit"> - <template slot="body" scope="props"> + <template slot="body" slot-scope="props"> <p v-html="props.text"></p> <form diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index 11f9754780d..19682b20a4a 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -1,5 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, consistent-return, one-var, one-var-declaration-per-line, no-cond-assign, max-len, object-shorthand, no-param-reassign, comma-dangle, prefer-template, no-unused-vars, no-return-assign */ -/* global fuzzaldrinPlus */ + +import fuzzaldrinPlus from 'fuzzaldrin-plus'; (function() { this.ProjectFindFile = (function() { diff --git a/app/assets/javascripts/repo/components/repo_editor.vue b/app/assets/javascripts/repo/components/repo_editor.vue index 0d6729bb99b..1c864b176b1 100644 --- a/app/assets/javascripts/repo/components/repo_editor.vue +++ b/app/assets/javascripts/repo/components/repo_editor.vue @@ -27,6 +27,8 @@ export default { 'changeFileContent', ]), initMonaco() { + if (this.shouldHideEditor) return; + if (this.monacoInstance) { this.monacoInstance.setModel(null); } @@ -94,8 +96,12 @@ export default { <template> <div id="ide" - v-if='!shouldHideEditor' class="blob-viewer-container blob-editor-container" > + <div + v-if="shouldHideEditor" + v-html="activeFile.html" + > + </div> </div> </template> diff --git a/app/assets/javascripts/settings_panels.js b/app/assets/javascripts/settings_panels.js index 8635ccece6e..d34a21b37e1 100644 --- a/app/assets/javascripts/settings_panels.js +++ b/app/assets/javascripts/settings_panels.js @@ -1,34 +1,26 @@ -function expandSectionParent($section, $content) { - $section.addClass('expanded'); - $content.off('animationend.expandSectionParent'); -} - function expandSection($section) { $section.find('.js-settings-toggle').text('Collapse'); - - const $content = $section.find('.settings-content'); - $content.addClass('expanded').off('scroll.expandSection').scrollTop(0); - - if ($content.hasClass('no-animate')) { - expandSectionParent($section, $content); - } else { - $content.on('animationend.expandSectionParent', () => expandSectionParent($section, $content)); + $section.find('.settings-content').off('scroll.expandSection').scrollTop(0); + $section.addClass('expanded'); + if (!$section.hasClass('no-animate')) { + $section.addClass('animating') + .one('animationend.animateSection', () => $section.removeClass('animating')); } } function closeSection($section) { $section.find('.js-settings-toggle').text('Expand'); - - const $content = $section.find('.settings-content'); - $content.removeClass('expanded').on('scroll.expandSection', () => expandSection($section)); - + $section.find('.settings-content').on('scroll.expandSection', () => expandSection($section)); $section.removeClass('expanded'); + if (!$section.hasClass('no-animate')) { + $section.addClass('animating') + .one('animationend.animateSection', () => $section.removeClass('animating')); + } } function toggleSection($section) { - const $content = $section.find('.settings-content'); - $content.removeClass('no-animate'); - if ($content.hasClass('expanded')) { + $section.removeClass('no-animate'); + if ($section.hasClass('expanded')) { closeSection($section); } else { expandSection($section); @@ -39,10 +31,19 @@ export default function initSettingsPanels() { $('.settings').each((i, elm) => { const $section = $(elm); $section.on('click.toggleSection', '.js-settings-toggle', () => toggleSection($section)); - $section.find('.settings-content:not(.expanded)').on('scroll.expandSection', () => expandSection($section)); + + if (!$section.hasClass('expanded')) { + $section.find('.settings-content').on('scroll.expandSection', () => { + $section.removeClass('no-animate'); + expandSection($section); + }); + } }); if (location.hash) { - expandSection($(location.hash)); + const $target = $(location.hash); + if ($target.length && $target.hasClass('.settings')) { + expandSection($target); + } } } diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js index c79b5c720eb..029832bdd27 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js @@ -1,6 +1,6 @@ import PipelineStage from '../../pipelines/components/stage.vue'; import ciIcon from '../../vue_shared/components/ci_icon.vue'; -import { statusIconEntityMap } from '../../vue_shared/ci_status_icons'; +import icon from '../../vue_shared/components/icon.vue'; export default { name: 'MRWidgetPipeline', @@ -10,6 +10,7 @@ export default { components: { 'pipeline-stage': PipelineStage, ciIcon, + icon, }, computed: { hasPipeline() { @@ -20,9 +21,6 @@ export default { return hasCI && !ciStatus; }, - svg() { - return statusIconEntityMap.icon_status_failed; - }, stageText() { return this.mr.pipeline.details.stages.length > 1 ? 'stages' : 'stage'; }, @@ -38,8 +36,10 @@ export default { <template v-if="hasCIError"> <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10"> <span - v-html="svg" - aria-hidden="true"></span> + aria-hidden="true"> + <icon + name="status_failed"/> + </span> </div> <div class="media-body"> Could not connect to the CI server. Please check your settings and try again diff --git a/app/assets/javascripts/vue_shared/ci_action_icons.js b/app/assets/javascripts/vue_shared/ci_action_icons.js deleted file mode 100644 index b21f0ab49fd..00000000000 --- a/app/assets/javascripts/vue_shared/ci_action_icons.js +++ /dev/null @@ -1,21 +0,0 @@ -import cancelSVG from 'icons/_icon_action_cancel.svg'; -import retrySVG from 'icons/_icon_action_retry.svg'; -import playSVG from 'icons/_icon_action_play.svg'; -import stopSVG from 'icons/_icon_action_stop.svg'; - -/** - * For the provided action returns the respective SVG - * - * @param {String} action - * @return {SVG|String} - */ -export default function getActionIcon(action) { - const icons = { - icon_action_cancel: cancelSVG, - icon_action_play: playSVG, - icon_action_retry: retrySVG, - icon_action_stop: stopSVG, - }; - - return icons[action] || ''; -} diff --git a/app/assets/javascripts/vue_shared/ci_status_icons.js b/app/assets/javascripts/vue_shared/ci_status_icons.js deleted file mode 100644 index d9d0cad38e4..00000000000 --- a/app/assets/javascripts/vue_shared/ci_status_icons.js +++ /dev/null @@ -1,43 +0,0 @@ -import BORDERLESS_CANCELED_SVG from 'icons/_icon_status_canceled_borderless.svg'; -import BORDERLESS_CREATED_SVG from 'icons/_icon_status_created_borderless.svg'; -import BORDERLESS_FAILED_SVG from 'icons/_icon_status_failed_borderless.svg'; -import BORDERLESS_MANUAL_SVG from 'icons/_icon_status_manual_borderless.svg'; -import BORDERLESS_PENDING_SVG from 'icons/_icon_status_pending_borderless.svg'; -import BORDERLESS_RUNNING_SVG from 'icons/_icon_status_running_borderless.svg'; -import BORDERLESS_SKIPPED_SVG from 'icons/_icon_status_skipped_borderless.svg'; -import BORDERLESS_SUCCESS_SVG from 'icons/_icon_status_success_borderless.svg'; -import BORDERLESS_WARNING_SVG from 'icons/_icon_status_warning_borderless.svg'; - -import CANCELED_SVG from 'icons/_icon_status_canceled.svg'; -import CREATED_SVG from 'icons/_icon_status_created.svg'; -import FAILED_SVG from 'icons/_icon_status_failed.svg'; -import MANUAL_SVG from 'icons/_icon_status_manual.svg'; -import PENDING_SVG from 'icons/_icon_status_pending.svg'; -import RUNNING_SVG from 'icons/_icon_status_running.svg'; -import SKIPPED_SVG from 'icons/_icon_status_skipped.svg'; -import SUCCESS_SVG from 'icons/_icon_status_success.svg'; -import WARNING_SVG from 'icons/_icon_status_warning.svg'; - -export const borderlessStatusIconEntityMap = { - icon_status_canceled: BORDERLESS_CANCELED_SVG, - icon_status_created: BORDERLESS_CREATED_SVG, - icon_status_failed: BORDERLESS_FAILED_SVG, - icon_status_manual: BORDERLESS_MANUAL_SVG, - icon_status_pending: BORDERLESS_PENDING_SVG, - icon_status_running: BORDERLESS_RUNNING_SVG, - icon_status_skipped: BORDERLESS_SKIPPED_SVG, - icon_status_success: BORDERLESS_SUCCESS_SVG, - icon_status_warning: BORDERLESS_WARNING_SVG, -}; - -export const statusIconEntityMap = { - icon_status_canceled: CANCELED_SVG, - icon_status_created: CREATED_SVG, - icon_status_failed: FAILED_SVG, - icon_status_manual: MANUAL_SVG, - icon_status_pending: PENDING_SVG, - icon_status_running: RUNNING_SVG, - icon_status_skipped: SKIPPED_SVG, - icon_status_success: SUCCESS_SVG, - icon_status_warning: WARNING_SVG, -}; diff --git a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue index 5b6c6e8d0b9..fc795936abf 100644 --- a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue +++ b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue @@ -43,7 +43,6 @@ computed: { cssClass() { const className = this.status.group; - return className ? `ci-status ci-${className}` : 'ci-status'; }, }, diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue index ec88119e16c..2a018f38366 100644 --- a/app/assets/javascripts/vue_shared/components/ci_icon.vue +++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue @@ -1,5 +1,5 @@ <script> - import { statusIconEntityMap } from '../ci_status_icons'; + import icon from '../../vue_shared/components/icon.vue'; /** * Renders CI icon based on API response shared between all places where it is used. @@ -30,11 +30,11 @@ }, }, - computed: { - statusIconSvg() { - return statusIconEntityMap[this.status.icon]; - }, + components: { + icon, + }, + computed: { cssClass() { const status = this.status.group; return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status}`; @@ -44,7 +44,8 @@ </script> <template> <span - :class="cssClass" - v-html="statusIconSvg"> + :class="cssClass"> + <icon + :name="status.icon"/> </span> </template> diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue new file mode 100644 index 00000000000..2e5f9f1088f --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/icon.vue @@ -0,0 +1,52 @@ +<script> + +/* This is a re-usable vue component for rendering a svg sprite + icon + + Sample configuration: + + <icon + :img-src="userAvatarSrc" + :img-alt="tooltipText" + :tooltip-text="tooltipText" + tooltip-placement="top" + /> + +*/ + export default { + props: { + name: { + type: String, + required: true, + }, + + size: { + type: Number, + required: false, + default: 0, + }, + + cssClasses: { + type: String, + required: false, + default: '', + }, + }, + + computed: { + spriteHref() { + return `${gon.sprite_icons}#${this.name}`; + }, + iconSizeClass() { + return this.size ? `s${this.size}` : ''; + }, + }, + }; +</script> +<template> + <svg + :class="[iconSizeClass, cssClasses]"> + <use + v-bind="{'xlink:href':spriteHref}"/> + </svg> +</template> diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 7b1ef003bb2..c334f39f416 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -56,4 +56,4 @@ @import "framework/icons"; @import "framework/snippets"; @import "framework/memory_graph"; -@import "framework/responsive-tables"; +@import "framework/responsive_tables"; diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index 81439c0d2fe..1b944831082 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -23,11 +23,16 @@ @include webkit-prefix(animation-duration, 2s); } - &.spin { + &.spin-cw { transform-origin: center; animation: spin 4s linear infinite; } + &.spin-ccw { + transform-origin: center; + animation: spin 4s linear infinite reverse; + } + &.flipOutX, &.flipOutY, &.bounceIn, diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index dbd990f84c1..8819a0c20f4 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -209,7 +209,6 @@ padding: 24px 0 0; .nav-links { - justify-content: center; width: 100%; float: none; @@ -217,6 +216,14 @@ float: none; } } + + li:first-child { + margin-left: auto; + } + + li:last-child { + margin-right: auto; + } } .group-info { diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 96f9dda26c4..ed84b17af6a 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -5,32 +5,6 @@ .cgreen { color: $common-green; } .cdark { color: $common-gray-dark; } -/** COMMON CLASSES **/ -.prepend-top-0 { margin-top: 0; } -.prepend-top-5 { margin-top: 5px; } -.prepend-top-10 { margin-top: 10px; } -.prepend-top-default { margin-top: $gl-padding !important; } -.prepend-top-20 { margin-top: 20px; } -.prepend-left-4 { margin-left: 4px; } -.prepend-left-5 { margin-left: 5px; } -.prepend-left-10 { margin-left: 10px; } -.prepend-left-default { margin-left: $gl-padding; } -.prepend-left-20 { margin-left: 20px; } -.append-right-5 { margin-right: 5px; } -.append-right-8 { margin-right: 8px; } -.append-right-10 { margin-right: 10px; } -.append-right-default { margin-right: $gl-padding; } -.append-right-20 { margin-right: 20px; } -.append-bottom-0 { margin-bottom: 0; } -.append-bottom-5 { margin-bottom: 5px; } -.append-bottom-10 { margin-bottom: 10px; } -.append-bottom-15 { margin-bottom: 15px; } -.append-bottom-20 { margin-bottom: 20px; } -.append-bottom-default { margin-bottom: $gl-padding; } -.inline { display: inline-block; } -.center { text-align: center; } -.vertical-align-middle { vertical-align: middle; } - .underlined-link { text-decoration: underline; } .hint { font-style: italic; color: $hint-color; } .light { color: $common-gray; } @@ -448,3 +422,30 @@ table { pointer-events: none; opacity: .5; } + +/** COMMON CLASSES **/ +.prepend-top-0 { margin-top: 0; } +.prepend-top-5 { margin-top: 5px; } +.prepend-top-10 { margin-top: 10px; } +.prepend-top-15 { margin-top: 15px; } +.prepend-top-default { margin-top: $gl-padding !important; } +.prepend-top-20 { margin-top: 20px; } +.prepend-left-4 { margin-left: 4px; } +.prepend-left-5 { margin-left: 5px; } +.prepend-left-10 { margin-left: 10px; } +.prepend-left-default { margin-left: $gl-padding; } +.prepend-left-20 { margin-left: 20px; } +.append-right-5 { margin-right: 5px; } +.append-right-8 { margin-right: 8px; } +.append-right-10 { margin-right: 10px; } +.append-right-default { margin-right: $gl-padding; } +.append-right-20 { margin-right: 20px; } +.append-bottom-0 { margin-bottom: 0; } +.append-bottom-5 { margin-bottom: 5px; } +.append-bottom-10 { margin-bottom: 10px; } +.append-bottom-15 { margin-bottom: 15px; } +.append-bottom-20 { margin-bottom: 20px; } +.append-bottom-default { margin-bottom: $gl-padding; } +.inline { display: inline-block; } +.center { text-align: center; } +.vertical-align-middle { vertical-align: middle; } diff --git a/app/assets/stylesheets/framework/responsive-tables.scss b/app/assets/stylesheets/framework/responsive_tables.scss index 8e653c443cf..7adb2f113bb 100644 --- a/app/assets/stylesheets/framework/responsive-tables.scss +++ b/app/assets/stylesheets/framework/responsive_tables.scss @@ -3,57 +3,74 @@ max-width: #{$max + '%'}; } +.gl-responsive-table-row-layout { + width: 100%; + + @media (min-width: $screen-md-min) { + display: flex; + align-items: center; + + & > &:not(:first-child) { + margin-top: $gl-padding; + } + } +} + .gl-responsive-table-row { + @extend .gl-responsive-table-row-layout; margin-top: 10px; border: 1px solid $border-color; @media (min-width: $screen-md-min) { - padding: 15px 0; margin: 0; - display: flex; - align-items: center; + padding: $gl-padding 0; border: none; border-bottom: 1px solid $white-normal; } +} + +.gl-responsive-table-row-col-span { + flex-wrap: wrap; +} - .table-section { - white-space: nowrap; +.table-section { + white-space: nowrap; - $section-widths: 10 15 20 25 30 40; - @each $width in $section-widths { - &.section-#{$width} { - flex: 0 0 #{$width + '%'}; + $section-widths: 10 15 20 25 30 40 100; + @each $width in $section-widths { + &.section-#{$width} { + flex: 0 0 #{$width + '%'}; - @media (min-width: $screen-md-min) { - max-width: #{$width + '%'}; - } + @media (min-width: $screen-md-min) { + max-width: #{$width + '%'}; } } + } - &:not(.table-button-footer) { - @media (max-width: $screen-sm-max) { - display: flex; - align-self: stretch; - padding: 10px; - align-items: center; - min-height: 62px; + @media (max-width: $screen-sm-max) { + display: flex; + align-self: stretch; + padding: 10px; + align-items: center; + min-height: 62px; - &:not(:first-of-type) { - border-top: 1px solid $white-normal; - } - } + &:not(:first-child) { + border-top: 1px solid $white-normal; } + } - &.section-wrap { - white-space: normal; + &.section-wrap { + white-space: normal; - @media (max-width: $screen-sm-max) { - flex-wrap: wrap; - } + @media (max-width: $screen-sm-max) { + flex-wrap: wrap; } } -} + &.section-align-top { + align-self: flex-start; + } +} .table-button-footer { @media (min-width: $screen-md-min) { @@ -61,12 +78,13 @@ } @media (max-width: $screen-sm-max) { - background-color: $gray-normal; + display: block; align-self: stretch; + min-height: 0; + background-color: $gray-normal; border-top: 1px solid $border-color; .table-action-buttons { - padding: 10px 5px; display: flex; .btn { @@ -77,7 +95,14 @@ > .external-url, > .btn { flex: 1 1 28px; - margin: 0 5px; + + &:not(:first-child) { + margin-left: 5px; + } + + &:not(:last-child) { + margin-right: 5px; + } } .dropdown-new { diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 50ec5110bf1..e87ffe4f374 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -333,8 +333,10 @@ svg { position: relative; - top: 2px; + top: 3px; margin-right: 3px; + width: 14px; + height: 14px; } } @@ -348,9 +350,10 @@ svg { position: relative; - top: 2px; + top: 3px; margin-right: 3px; - height: 13px; + height: 14px; + width: 14px; } a { @@ -369,7 +372,7 @@ .build-job { position: relative; - .fa-arrow-right { + .icon-arrow-right { position: absolute; left: 15px; top: 20px; @@ -379,7 +382,7 @@ &.active { font-weight: $gl-font-weight-bold; - .fa-arrow-right { + .icon-arrow-right { display: block; } } @@ -392,8 +395,7 @@ background-color: $row-hover; } - .fa-refresh { - font-size: 13px; + .icon-retry { margin-left: 3px; } } diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index 8d6f30e3b84..5c91579c69c 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -2,8 +2,4 @@ .clipboard-addon { background-color: $white-light; } - - .alert-block { - margin-bottom: 10px; - } } diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 3b5e411e2c5..6c1d32bed2f 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -133,12 +133,11 @@ } .folder-row { - padding: 15px 0; - border-bottom: 1px solid $white-normal; + border-left: none; + border-right: none; - @media (max-width: $screen-sm-max) { - border-top: 1px solid $white-normal; - margin-top: 10px; + @media (min-width: $screen-sm-max) { + border-top: none; } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index d9fb3b44d29..645fc1f3ebb 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -165,8 +165,9 @@ z-index: 300; } - .ci-action-icon-wrapper { - line-height: 16px; + .ci-action-icon-wrapper svg { + width: 16px; + height: 16px; } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 8fc7a5eec9b..6604b471560 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -31,7 +31,6 @@ } .pipeline-actions { - padding-right: 0; min-width: 170px; //Guarantees buttons don't break in several lines. .btn-default { @@ -452,7 +451,7 @@ } // Action Icons in big pipeline-graph nodes - .ci-action-icon-container .ci-action-icon-wrapper { + .ci-action-icon-container.ci-action-icon-wrapper { height: 30px; width: 30px; background: $white-light; @@ -468,8 +467,18 @@ svg { fill: $gl-text-color-secondary; position: relative; - left: -1px; - top: -1px; + left: 5px; + top: 2px; + width: 18px; + height: 18px; + } + + &.play { + svg { + width: #{$ci-action-icon-size - 8}; + height: #{$ci-action-icon-size - 8}; + left: 8px; + } } &:hover svg { @@ -721,17 +730,49 @@ button.mini-pipeline-graph-dropdown-toggle { svg { fill: $gl-text-color-secondary; - width: $ci-action-icon-size; - height: $ci-action-icon-size; - left: -6px; + width: #{$ci-action-icon-size - 6}; + height: #{$ci-action-icon-size - 6}; + left: -3px; position: relative; - top: -3px; + top: -2px; } &:hover svg, &:focus svg { fill: $gl-text-color; } + + &.icon-action-retry, + &.icon-action-play { + svg { + width: #{$ci-action-icon-size - 6}; + height: #{$ci-action-icon-size - 6}; + left: 8px; + } + } + + svg.icon-action-stop, + svg.icon-action-cancel { + width: 12px; + height: 12px; + top: 1px; + left: -1px; + } + + svg.icon-action-play { + width: 11px; + height: 11px; + top: 1px; + left: 1px; + } + + svg.icon-action-retry { + width: 16px; + height: 16px; + top: 0; + left: -3px; + } + } // link to the build diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss index 6cac37a4e28..5fb97b13470 100644 --- a/app/assets/stylesheets/pages/runners.scss +++ b/app/assets/stylesheets/pages/runners.scss @@ -50,3 +50,10 @@ font-size: 11px; } } + +@media (max-width: $screen-md-max) { + .runners-content { + width: 100%; + overflow: auto; + } +} diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 41a6ba2023a..968a94c68cf 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -23,15 +23,14 @@ } .settings { - overflow: hidden; border-bottom: 1px solid $gray-darker; &:first-of-type { margin-top: 10px; } - &.expanded { - overflow: visible; + &.animating { + overflow: hidden; } } @@ -56,14 +55,18 @@ overflow-y: scroll; padding-right: 110px; animation: collapseMaxHeight 300ms ease-out; + // Keep the section from expanding when we scroll over it + pointer-events: none; - &.expanded { + .settings.expanded & { max-height: none; overflow-y: visible; animation: expandMaxHeight 300ms ease-in; + // Reset and allow clicks again when expanded + pointer-events: auto; } - &.no-animate { + .settings.no-animate & { animation: none; } diff --git a/app/controllers/admin/impersonation_tokens_controller.rb b/app/controllers/admin/impersonation_tokens_controller.rb index 07c8bf714fc..7a2c7234a1e 100644 --- a/app/controllers/admin/impersonation_tokens_controller.rb +++ b/app/controllers/admin/impersonation_tokens_controller.rb @@ -44,7 +44,7 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController end def set_index_vars - @scopes = Gitlab::Auth::API_SCOPES + @scopes = Gitlab::Auth.available_scopes(current_user) @impersonation_token ||= finder.build @inactive_impersonation_tokens = finder(state: 'inactive').execute diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 391a0519195..3be7aee69bc 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -11,7 +11,7 @@ class ApplicationController < ActionController::Base include EnforcesTwoFactorAuthentication include WithPerformanceBar - before_action :authenticate_user_from_private_token! + before_action :authenticate_user_from_personal_access_token! before_action :authenticate_user_from_rss_token! before_action :authenticate_user! before_action :validate_user_service_ticket! @@ -100,13 +100,12 @@ class ApplicationController < ActionController::Base return try(:authenticated_user) end - # This filter handles both private tokens and personal access tokens - def authenticate_user_from_private_token! + def authenticate_user_from_personal_access_token! token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence return unless token.present? - user = User.find_by_authentication_token(token) || User.find_by_personal_access_token(token) + user = User.find_by_personal_access_token(token) sessionless_sign_in(user) end diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index 4079072a930..b1ed973d178 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -7,6 +7,54 @@ module IssuableActions before_action :authorize_admin_issuable!, only: :bulk_update end + def show + respond_to do |format| + format.html do + render show_view + end + format.json do + render json: serializer.represent(issuable, serializer: params[:serializer]) + end + end + end + + def update + @issuable = update_service.execute(issuable) + + respond_to do |format| + format.html do + recaptcha_check_with_fallback { render :edit } + end + + format.json do + render_entity_json + end + end + + rescue ActiveRecord::StaleObjectError + render_conflict_response + end + + def realtime_changes + Gitlab::PollingInterval.set_header(response, interval: 3_000) + + response = { + title: view_context.markdown_field(issuable, :title), + title_text: issuable.title, + description: view_context.markdown_field(issuable, :description), + description_text: issuable.description, + task_status: issuable.task_status + } + + if issuable.edited? + response[:updated_at] = issuable.updated_at + response[:updated_by_name] = issuable.last_edited_by.name + response[:updated_by_path] = user_path(issuable.last_edited_by) + end + + render json: response + end + def destroy issuable.destroy destroy_method = "destroy_#{issuable.class.name.underscore}".to_sym @@ -68,6 +116,10 @@ module IssuableActions end end + def authorize_update_issuable! + render_404 unless can?(current_user, :"update_#{resource_name}", issuable) + end + def bulk_update_params permitted_keys = [ :issuable_ids, @@ -92,4 +144,24 @@ module IssuableActions def resource_name @resource_name ||= controller_name.singularize end + + def render_entity_json + if @issuable.valid? + render json: serializer.represent(@issuable) + else + render json: { errors: @issuable.errors.full_messages }, status: :unprocessable_entity + end + end + + def show_view + 'show' + end + + def serializer + raise NotImplementedError + end + + def update_service + raise NotImplementedError + end end diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 4bceb1d67a3..7d6fe6a0232 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -30,11 +30,11 @@ class JwtController < ApplicationController render_unauthorized end end - rescue Gitlab::Auth::MissingPersonalTokenError - render_missing_personal_token + rescue Gitlab::Auth::MissingPersonalAccessTokenError + render_missing_personal_access_token end - def render_missing_personal_token + def render_missing_personal_access_token render json: { errors: [ { code: 'UNAUTHORIZED', diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb index 4146deefa89..6d9873e38df 100644 --- a/app/controllers/profiles/personal_access_tokens_controller.rb +++ b/app/controllers/profiles/personal_access_tokens_controller.rb @@ -39,7 +39,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController end def set_index_vars - @scopes = Gitlab::Auth.available_scopes + @scopes = Gitlab::Auth.available_scopes(current_user) @inactive_personal_access_tokens = finder(state: 'inactive').execute @active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at) diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 5d87037f012..dbf61a17724 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -24,16 +24,6 @@ class ProfilesController < Profiles::ApplicationController end end - def reset_private_token - Users::UpdateService.new(current_user, user: @user).execute! do |user| - user.reset_authentication_token! - end - - flash[:notice] = "Private token was successfully reset" - - redirect_to profile_account_path - end - def reset_incoming_email_token Users::UpdateService.new(current_user, user: @user).execute! do |user| user.reset_incoming_email_token! @@ -41,7 +31,7 @@ class ProfilesController < Profiles::ApplicationController flash[:notice] = "Incoming email token was successfully reset" - redirect_to profile_account_path + redirect_to profile_personal_access_tokens_path end def reset_rss_token @@ -51,7 +41,7 @@ class ProfilesController < Profiles::ApplicationController flash[:notice] = "RSS token was successfully reset" - redirect_to profile_account_path + redirect_to profile_personal_access_tokens_path end def audit_log diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 95d7a02e9e9..dd5e66f60e3 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -53,8 +53,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController send_challenges render plain: "HTTP Basic: Access denied\n", status: 401 - rescue Gitlab::Auth::MissingPersonalTokenError - render_missing_personal_token + rescue Gitlab::Auth::MissingPersonalAccessTokenError + render_missing_personal_access_token end def basic_auth_provided? @@ -78,7 +78,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController @project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}") end - def render_missing_personal_token + def render_missing_personal_access_token render plain: "HTTP Basic: Access denied\n" \ "You must use a personal access token with 'api' scope for Git over HTTP.\n" \ "You can generate one at #{profile_personal_access_tokens_url}", diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index f59200d3b1f..dbc1c8bcc28 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -12,12 +12,7 @@ class Projects::GroupLinksController < Projects::ApplicationController if group return render_404 unless can?(current_user, :read_group, group) - - project.project_group_links.create( - group: group, - group_access: params[:link_group_access], - expires_at: params[:expires_at] - ) + Projects::GroupLinks::CreateService.new(project, current_user, group_link_create_params).execute(group) else flash[:alert] = 'Please select a group.' end @@ -32,7 +27,9 @@ class Projects::GroupLinksController < Projects::ApplicationController end def destroy - project.project_group_links.find(params[:id]).destroy + group_link = project.project_group_links.find(params[:id]) + + ::Projects::GroupLinks::DestroyService.new(project, current_user).execute(group_link) respond_to do |format| format.html do @@ -47,4 +44,8 @@ class Projects::GroupLinksController < Projects::ApplicationController def group_link_params params.require(:group_link).permit(:group_access, :expires_at) end + + def group_link_create_params + params.permit(:link_group_access, :expires_at) + end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 6a5e4538717..d4e763aa5b8 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -16,7 +16,7 @@ class Projects::IssuesController < Projects::ApplicationController before_action :authorize_create_issue!, only: [:new, :create] # Allow modify issue - before_action :authorize_update_issue!, only: [:edit, :update, :move] + before_action :authorize_update_issuable!, only: [:edit, :update, :move] # Allow create a new branch and empty WIP merge request from current issue before_action :authorize_create_merge_request!, only: [:create_merge_request] @@ -67,18 +67,6 @@ class Projects::IssuesController < Projects::ApplicationController respond_with(@issue) end - def show - @noteable = @issue - @note = @project.notes.new(noteable: @issue) - - respond_to do |format| - format.html - format.json do - render json: serializer.represent(@issue, serializer: params[:serializer]) - end - end - end - def discussions notes = @issue.notes .inc_relations_for_view @@ -120,25 +108,6 @@ class Projects::IssuesController < Projects::ApplicationController end end - def update - update_params = issue_params.merge(spammable_params) - - @issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue) - - respond_to do |format| - format.html do - recaptcha_check_with_fallback { render :edit } - end - - format.json do - render_issue_json - end - end - - rescue ActiveRecord::StaleObjectError - render_conflict_response - end - def move params.require(:move_to_project_id) @@ -196,26 +165,6 @@ class Projects::IssuesController < Projects::ApplicationController end end - def realtime_changes - Gitlab::PollingInterval.set_header(response, interval: 3_000) - - response = { - title: view_context.markdown_field(@issue, :title), - title_text: @issue.title, - description: view_context.markdown_field(@issue, :description), - description_text: @issue.description, - task_status: @issue.task_status - } - - if @issue.edited? - response[:updated_at] = @issue.updated_at - response[:updated_by_name] = @issue.last_edited_by.name - response[:updated_by_path] = user_path(@issue.last_edited_by) - end - - render json: response - end - def create_merge_request result = ::MergeRequests::CreateFromIssueService.new(project, current_user, issue_iid: issue.iid).execute @@ -231,7 +180,8 @@ class Projects::IssuesController < Projects::ApplicationController def issue return @issue if defined?(@issue) # The Sortable default scope causes performance issues when used with find_by - @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take! + @issuable = @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take! + @note = @project.notes.new(noteable: @issuable) return render_404 unless can?(current_user, :read_issue, @issue) @@ -246,14 +196,6 @@ class Projects::IssuesController < Projects::ApplicationController project_issue_path(@project, @issue) end - def authorize_update_issue! - render_404 unless can?(current_user, :update_issue, @issue) - end - - def authorize_admin_issues! - render_404 unless can?(current_user, :admin_issue, @project) - end - def authorize_create_merge_request! render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user) end @@ -305,4 +247,9 @@ class Projects::IssuesController < Projects::ApplicationController def serializer IssueSerializer.new(current_user: current_user, project: issue.project) end + + def update_service + update_params = issue_params.merge(spammable_params) + Issues::UpdateService.new(project, current_user, update_params) + end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 2b0294c8387..17cac69e588 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -9,7 +9,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo skip_before_action :merge_request, only: [:index, :bulk_update] skip_before_action :ensure_ref_fetched, only: [:index, :bulk_update] - before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort] + before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort] before_action :authenticate_user!, only: [:assign_related_issues] @@ -256,14 +256,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo alias_method :issuable, :merge_request alias_method :awardable, :merge_request - def authorize_update_merge_request! - return render_404 unless can?(current_user, :update_merge_request, @merge_request) - end - - def authorize_admin_merge_request! - return render_404 unless can?(current_user, :admin_merge_request, @merge_request) - end - def validates_merge_request # Show git not found page # if there is no saved commits between source & target branch diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 8022547a6ad..4dd573c61f1 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -63,34 +63,34 @@ module CiStatusHelper def ci_icon_for_status(status) if detailed_status?(status) - return custom_icon(status.icon) + return sprite_icon(status.icon) end icon_name = case status when 'success' - 'icon_status_success' + 'status_success' when 'success_with_warnings' - 'icon_status_warning' + 'status_warning' when 'failed' - 'icon_status_failed' + 'status_failed' when 'pending' - 'icon_status_pending' + 'status_pending' when 'running' - 'icon_status_running' + 'status_running' when 'play' - 'icon_play' + 'play' when 'created' - 'icon_status_created' + 'status_created' when 'skipped' - 'icon_status_skipped' + 'status_skipped' when 'manual' - 'icon_status_manual' + 'status_manual' else - 'icon_status_canceled' + 'status_canceled' end - custom_icon(icon_name) + sprite_icon(icon_name, size: 16) end def pipeline_status_cache_key(pipeline_status) diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index d4a91e533c1..a77aa0ad2cc 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -71,11 +71,13 @@ module GitlabRoutingHelper project_commit_url(entity.project, entity.sha, *args) end - def preview_markdown_path(project, *args) + def preview_markdown_path(parent, *args) + return group_preview_markdown_path(parent) if parent.is_a?(Group) + if @snippet.is_a?(PersonalSnippet) preview_markdown_snippets_path else - preview_markdown_project_path(project, *args) + preview_markdown_project_path(parent, *args) end end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index d0069cd48cf..85407e38532 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -211,15 +211,13 @@ module IssuablesHelper def issuable_initial_data(issuable) data = { - endpoint: project_issue_path(@project, issuable), - canUpdate: can?(current_user, :update_issue, issuable), - canDestroy: can?(current_user, :destroy_issue, issuable), + endpoint: issuable_path(issuable), + canUpdate: can?(current_user, :"update_#{issuable.to_ability_name}", issuable), + canDestroy: can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable), issuableRef: issuable.to_reference, - markdownPreviewPath: preview_markdown_path(@project), + markdownPreviewPath: preview_markdown_path(parent), markdownDocsPath: help_page_path('user/markdown'), issuableTemplates: issuable_templates(issuable), - projectPath: ref_project.path, - projectNamespace: ref_project.namespace.full_path, initialTitleHtml: markdown_field(issuable, :title), initialTitleText: issuable.title, initialDescriptionHtml: markdown_field(issuable, :description), @@ -227,6 +225,12 @@ module IssuablesHelper initialTaskStatus: issuable.task_status } + if parent.is_a?(Group) + data[:groupPath] = parent.path + else + data.merge!(projectPath: ref_project.path, projectNamespace: ref_project.namespace.full_path) + end + data.merge!(updated_at_by(issuable)) data.to_json @@ -263,12 +267,7 @@ module IssuablesHelper end def issuable_path(issuable, *options) - case issuable - when Issue - issue_path(issuable, *options) - when MergeRequest - merge_request_path(issuable, *options) - end + polymorphic_path(issuable, *options) end def issuable_url(issuable, *options) @@ -369,4 +368,8 @@ module IssuablesHelper fullPath: @project.full_path } end + + def parent + @project || @group + end end diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb index 9417033d1f6..98776eab424 100644 --- a/app/models/concerns/cache_markdown_field.rb +++ b/app/models/concerns/cache_markdown_field.rb @@ -49,7 +49,8 @@ module CacheMarkdownField # Always include a project key, or Banzai complains project = self.project if self.respond_to?(:project) - context = cached_markdown_fields[field].merge(project: project) + group = self.group if self.respond_to?(:group) + context = cached_markdown_fields[field].merge(project: project, group: group) # Banzai is less strict about authors, so don't always have an author key context[:author] = self.author if self.respond_to?(:author) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 27f4dedffd3..a928b9d6367 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -14,7 +14,6 @@ module Issuable include StripAttribute include Awardable include Taskable - include TimeTrackable include Importable include Editable include AfterCommitQueue @@ -95,8 +94,6 @@ module Issuable strip_attributes :title - acts_as_paranoid - after_save :record_metrics, unless: :imported? # We want to use optimistic lock for cases when only title or description are involved diff --git a/app/models/epic.rb b/app/models/epic.rb new file mode 100644 index 00000000000..62898a02e2d --- /dev/null +++ b/app/models/epic.rb @@ -0,0 +1,7 @@ +# Placeholder class for model that is implemented in EE +# It will reserve (ee#3853) '&' as a reference prefix, but the table does not exists in CE +class Epic < ActiveRecord::Base + # TODO: this will be implemented as part of #3853 + def to_reference + end +end diff --git a/app/models/group.rb b/app/models/group.rb index 07fb62bb249..4e8023cdb7f 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -180,6 +180,12 @@ class Group < Namespace add_user(user, :owner, current_user: current_user) end + def member?(user, min_access_level = Gitlab::Access::GUEST) + return false unless user + + max_member_access_for_user(user) >= min_access_level + end + def has_owner?(user) return false unless user diff --git a/app/models/issue.rb b/app/models/issue.rb index 36e4108b9d6..fc590f9257e 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -10,6 +10,7 @@ class Issue < ActiveRecord::Base include FasterCacheKeys include RelativePositioning include CreatedAtFilterable + include TimeTrackable DueDateStruct = Struct.new(:title, :name).freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze @@ -74,6 +75,8 @@ class Issue < ActiveRecord::Base end end + acts_as_paranoid + def self.reference_prefix '#' end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index d45b9c805a4..3133dc9e7eb 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -6,6 +6,7 @@ class MergeRequest < ActiveRecord::Base include Sortable include IgnorableColumn include CreatedAtFilterable + include TimeTrackable ignore_column :locked_at @@ -119,6 +120,8 @@ class MergeRequest < ActiveRecord::Base after_save :keep_around_commit + acts_as_paranoid + def self.reference_prefix '!' end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index faf0b95f842..1eda0f9cbbd 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -48,6 +48,10 @@ class MergeRequestDiff < ActiveRecord::Base # Collect information about commits and diff from repository # and save it to the database as serialized data def save_git_content + MergeRequest + .where('id = ? AND COALESCE(latest_merge_request_diff_id, 0) < ?', self.merge_request_id, self.id) + .update_all(latest_merge_request_diff_id: self.id) + ensure_commit_shas save_commits save_diffs diff --git a/app/models/note.rb b/app/models/note.rb index 8939e590ef1..f9676361072 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -69,7 +69,7 @@ class Note < ActiveRecord::Base delegate :title, to: :noteable, allow_nil: true validates :note, presence: true - validates :project, presence: true, unless: :for_personal_snippet? + validates :project, presence: true, if: :for_project_noteable? # Attachments are deprecated and are handled by Markdown uploader validates :attachment, file_size: { maximum: :max_attachment_size } @@ -114,7 +114,7 @@ class Note < ActiveRecord::Base after_initialize :ensure_discussion_id before_validation :nullify_blank_type, :nullify_blank_line_code before_validation :set_discussion_id, on: :create - after_save :keep_around_commit, unless: :for_personal_snippet? + after_save :keep_around_commit, if: :for_project_noteable? after_save :expire_etag_cache after_destroy :expire_etag_cache @@ -208,6 +208,10 @@ class Note < ActiveRecord::Base noteable.is_a?(PersonalSnippet) end + def for_project_noteable? + !for_personal_snippet? + end + def skip_project_check? for_personal_snippet? end diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb index f89e60ad9f4..e8595b13d6d 100644 --- a/app/models/oauth_access_token.rb +++ b/app/models/oauth_access_token.rb @@ -2,5 +2,13 @@ class OauthAccessToken < Doorkeeper::AccessToken belongs_to :resource_owner, class_name: 'User' belongs_to :application, class_name: 'Doorkeeper::Application' - alias_method :user, :resource_owner + alias_attribute :user, :resource_owner + + def scopes=(value) + if value.is_a?(Array) + super(Doorkeeper::OAuth::Scopes.from_array(value).to_s) + else + super + end + end end diff --git a/app/models/user.rb b/app/models/user.rb index 9459b6d4fa4..6c9349ed9dd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -21,8 +21,8 @@ class User < ActiveRecord::Base ignore_column :external_email ignore_column :email_provider + ignore_column :authentication_token - add_authentication_token_field :authentication_token add_authentication_token_field :incoming_email_token add_authentication_token_field :rss_token @@ -163,7 +163,7 @@ class User < ActiveRecord::Base before_validation :sanitize_attrs before_validation :set_notification_email, if: :email_changed? before_validation :set_public_email, if: :public_email_changed? - before_save :ensure_authentication_token, :ensure_incoming_email_token + before_save :ensure_incoming_email_token before_save :ensure_user_rights_and_limits, if: :external_changed? before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) } before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? } @@ -185,8 +185,6 @@ class User < ActiveRecord::Base # Note: When adding an option, it MUST go on the end of the array. enum project_view: [:readme, :activity, :files] - alias_attribute :private_token, :authentication_token - delegate :path, to: :namespace, allow_nil: true, prefix: true state_machine :state, initial: :active do diff --git a/app/serializers/issuable_entity.rb b/app/serializers/issuable_entity.rb index 61c7a428745..3b5a4fd4f79 100644 --- a/app/serializers/issuable_entity.rb +++ b/app/serializers/issuable_entity.rb @@ -1,20 +1,16 @@ class IssuableEntity < Grape::Entity + include RequestAwareEntity + expose :id expose :iid expose :author_id expose :description expose :lock_version expose :milestone_id - expose :state expose :title expose :updated_by_id expose :created_at expose :updated_at - expose :deleted_at - expose :time_estimate - expose :total_time_spent - expose :human_time_estimate - expose :human_total_time_spent expose :milestone, using: API::Entities::Milestone expose :labels, using: LabelEntity end diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb index 10d3ad0214b..5f47592e4ad 100644 --- a/app/serializers/issue_entity.rb +++ b/app/serializers/issue_entity.rb @@ -1,6 +1,8 @@ class IssueEntity < IssuableEntity - include RequestAwareEntity + include TimeTrackableEntity + expose :state + expose :deleted_at expose :branch_name expose :confidential expose :discussion_locked diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb index 297a459e394..b53a49fe59e 100644 --- a/app/serializers/merge_request_entity.rb +++ b/app/serializers/merge_request_entity.rb @@ -1,6 +1,8 @@ class MergeRequestEntity < IssuableEntity - include RequestAwareEntity + include TimeTrackableEntity + expose :state + expose :deleted_at expose :in_progress_merge_commit_sha expose :merge_commit_sha expose :merge_error diff --git a/app/serializers/time_trackable_entity.rb b/app/serializers/time_trackable_entity.rb new file mode 100644 index 00000000000..e81cd7bec72 --- /dev/null +++ b/app/serializers/time_trackable_entity.rb @@ -0,0 +1,11 @@ +module TimeTrackableEntity + extend ActiveSupport::Concern + extend Grape + + included do + expose :time_estimate + expose :total_time_spent + expose :human_time_estimate + expose :human_total_time_spent + end +end diff --git a/app/services/access_token_validation_service.rb b/app/services/access_token_validation_service.rb index 9c00ea789ec..46e19230328 100644 --- a/app/services/access_token_validation_service.rb +++ b/app/services/access_token_validation_service.rb @@ -39,11 +39,8 @@ class AccessTokenValidationService token_scopes = token.scopes.map(&:to_sym) required_scopes.any? do |scope| - if scope.respond_to?(:sufficient?) - scope.sufficient?(token_scopes, request) - else - API::Scope.new(scope).sufficient?(token_scopes, request) - end + scope = API::Scope.new(scope) unless scope.is_a?(API::Scope) + scope.sufficient?(token_scopes, request) end end end diff --git a/app/services/issuable/common_system_notes_service.rb b/app/services/issuable/common_system_notes_service.rb new file mode 100644 index 00000000000..92eaa5d5115 --- /dev/null +++ b/app/services/issuable/common_system_notes_service.rb @@ -0,0 +1,81 @@ +module Issuable + class CommonSystemNotesService < ::BaseService + attr_reader :issuable + + def execute(issuable, old_labels) + @issuable = issuable + + if issuable.previous_changes.include?('title') + create_title_change_note(issuable.previous_changes['title'].first) + end + + handle_description_change_note + + handle_time_tracking_note if issuable.is_a?(TimeTrackable) + create_labels_note(old_labels) if issuable.labels != old_labels + create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked') + create_milestone_note if issuable.previous_changes.include?('milestone_id') + end + + private + + def handle_time_tracking_note + if issuable.previous_changes.include?('time_estimate') + create_time_estimate_note + end + + if issuable.time_spent? + create_time_spent_note + end + end + + def handle_description_change_note + if issuable.previous_changes.include?('description') + if issuable.tasks? && issuable.updated_tasks.any? + create_task_status_note + else + # TODO: Show this note if non-task content was modified. + # https://gitlab.com/gitlab-org/gitlab-ce/issues/33577 + create_description_change_note + end + end + end + + def create_labels_note(old_labels) + added_labels = issuable.labels - old_labels + removed_labels = old_labels - issuable.labels + + SystemNoteService.change_label(issuable, issuable.project, current_user, added_labels, removed_labels) + end + + def create_title_change_note(old_title) + SystemNoteService.change_title(issuable, issuable.project, current_user, old_title) + end + + def create_description_change_note + SystemNoteService.change_description(issuable, issuable.project, current_user) + end + + def create_task_status_note + issuable.updated_tasks.each do |task| + SystemNoteService.change_task_status(issuable, issuable.project, current_user, task) + end + end + + def create_time_estimate_note + SystemNoteService.change_time_estimate(issuable, issuable.project, current_user) + end + + def create_time_spent_note + SystemNoteService.change_time_spent(issuable, issuable.project, issuable.time_spent_user) + end + + def create_milestone_note + SystemNoteService.change_milestone(issuable, issuable.project, current_user, issuable.milestone) + end + + def create_discussion_lock_note + SystemNoteService.discussion_lock(issuable, current_user) + end + end +end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index d61a342ebad..68b49d880f7 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -1,56 +1,10 @@ class IssuableBaseService < BaseService private - def create_milestone_note(issuable) - SystemNoteService.change_milestone( - issuable, issuable.project, current_user, issuable.milestone) - end - - def create_labels_note(issuable, old_labels) - added_labels = issuable.labels - old_labels - removed_labels = old_labels - issuable.labels - - SystemNoteService.change_label( - issuable, issuable.project, current_user, added_labels, removed_labels) - end - - def create_title_change_note(issuable, old_title) - SystemNoteService.change_title( - issuable, issuable.project, current_user, old_title) - end - - def create_description_change_note(issuable) - SystemNoteService.change_description(issuable, issuable.project, current_user) - end - - def create_branch_change_note(issuable, branch_type, old_branch, new_branch) - SystemNoteService.change_branch( - issuable, issuable.project, current_user, branch_type, - old_branch, new_branch) - end - - def create_task_status_note(issuable) - issuable.updated_tasks.each do |task| - SystemNoteService.change_task_status(issuable, issuable.project, current_user, task) - end - end - - def create_time_estimate_note(issuable) - SystemNoteService.change_time_estimate(issuable, issuable.project, current_user) - end - - def create_time_spent_note(issuable) - SystemNoteService.change_time_spent(issuable, issuable.project, current_user) - end - - def create_discussion_lock_note(issuable) - SystemNoteService.discussion_lock(issuable, current_user) - end - def filter_params(issuable) ability_name = :"admin_#{issuable.to_ability_name}" - unless can?(current_user, ability_name, project) + unless can?(current_user, ability_name, issuable) params.delete(:milestone_id) params.delete(:labels) params.delete(:add_label_ids) @@ -233,15 +187,14 @@ class IssuableBaseService < BaseService # We have to perform this check before saving the issuable as Rails resets # the changed fields upon calling #save. - update_project_counters = issuable.update_project_counter_caches? + update_project_counters = issuable.project && issuable.update_project_counter_caches? if issuable.with_transaction_returning_status { issuable.save } # We do not touch as it will affect a update on updated_at field ActiveRecord::Base.no_touching do - handle_common_system_notes(issuable, old_labels: old_labels) + Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels) end - change_discussion_lock(issuable) handle_changes( issuable, old_labels: old_labels, @@ -300,12 +253,6 @@ class IssuableBaseService < BaseService end end - def change_discussion_lock(issuable) - if issuable.previous_changes.include?('discussion_locked') - create_discussion_lock_note(issuable) - end - end - def toggle_award(issuable) award = params.delete(:emoji_award) if award @@ -328,35 +275,17 @@ class IssuableBaseService < BaseService attrs_changed || labels_changed || assignees_changed end - def handle_common_system_notes(issuable, old_labels: []) - if issuable.previous_changes.include?('title') - create_title_change_note(issuable, issuable.previous_changes['title'].first) - end - - if issuable.previous_changes.include?('description') - if issuable.tasks? && issuable.updated_tasks.any? - create_task_status_note(issuable) - else - # TODO: Show this note if non-task content was modified. - # https://gitlab.com/gitlab-org/gitlab-ce/issues/33577 - create_description_change_note(issuable) - end - end - - if issuable.previous_changes.include?('time_estimate') - create_time_estimate_note(issuable) - end - - if issuable.time_spent? - create_time_spent_note(issuable) - end - - create_labels_note(issuable, old_labels) if issuable.labels != old_labels - end - def invalidate_cache_counts(issuable, users: []) users.each do |user| user.public_send("invalidate_#{issuable.model_name.singular}_cache_counts") # rubocop:disable GitlabSecurity/PublicSend end end + + # override if needed + def handle_changes(issuable, options) + end + + # override if needed + def execute_hooks(issuable, action = 'open', params = {}) + end end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index e0339ddf9bb..1b7b5927c5a 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -27,10 +27,6 @@ module Issues todo_service.update_issue(issue, current_user, old_mentioned_users) end - if issue.previous_changes.include?('milestone_id') - create_milestone_note(issue) - end - if issue.assignees != old_assignees create_assignee_note(issue, old_assignees) notification_service.reassigned_issue(issue, current_user, old_assignees) diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 2832d893e95..1f394cacc64 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -40,10 +40,6 @@ module MergeRequests merge_request.target_branch) end - if merge_request.previous_changes.include?('milestone_id') - create_milestone_note(merge_request) - end - if merge_request.previous_changes.include?('assignee_id') create_assignee_note(merge_request) notification_service.reassigned_merge_request(merge_request, current_user) @@ -111,5 +107,11 @@ module MergeRequests end end end + + def create_branch_change_note(issuable, branch_type, old_branch, new_branch) + SystemNoteService.change_branch( + issuable, issuable.project, current_user, branch_type, + old_branch, new_branch) + end end end diff --git a/app/services/metrics_service.rb b/app/services/metrics_service.rb index a02eee4961b..6b3939aeba5 100644 --- a/app/services/metrics_service.rb +++ b/app/services/metrics_service.rb @@ -6,8 +6,7 @@ class MetricsService Gitlab::HealthChecks::Redis::RedisCheck, Gitlab::HealthChecks::Redis::CacheCheck, Gitlab::HealthChecks::Redis::QueuesCheck, - Gitlab::HealthChecks::Redis::SharedStateCheck, - Gitlab::HealthChecks::FsShardsCheck + Gitlab::HealthChecks::Redis::SharedStateCheck ].freeze def prometheus_metrics_text diff --git a/app/services/projects/group_links/create_service.rb b/app/services/projects/group_links/create_service.rb new file mode 100644 index 00000000000..35624577024 --- /dev/null +++ b/app/services/projects/group_links/create_service.rb @@ -0,0 +1,15 @@ +module Projects + module GroupLinks + class CreateService < BaseService + def execute(group) + return false unless group + + project.project_group_links.create( + group: group, + group_access: params[:link_group_access], + expires_at: params[:expires_at] + ) + end + end + end +end diff --git a/app/services/projects/group_links/destroy_service.rb b/app/services/projects/group_links/destroy_service.rb new file mode 100644 index 00000000000..fbf31214c28 --- /dev/null +++ b/app/services/projects/group_links/destroy_service.rb @@ -0,0 +1,10 @@ +module Projects + module GroupLinks + class DestroyService < BaseService + def execute(group_link) + return false unless group_link + group_link.destroy + end + end + end +end diff --git a/app/views/admin/hook_logs/_index.html.haml b/app/views/admin/hook_logs/_index.html.haml index 7dd9943190f..91a8c0c62fe 100644 --- a/app/views/admin/hook_logs/_index.html.haml +++ b/app/views/admin/hook_logs/_index.html.haml @@ -24,7 +24,7 @@ %td = truncate(hook_log.url, length: 50) %td.light - #{number_with_precision(hook_log.execution_duration, precision: 2)} ms + #{number_with_precision(hook_log.execution_duration, precision: 2)} sec %td.light = time_ago_with_tooltip(hook_log.created_at) %td diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 4d8754afdd2..c37d8ac45b9 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -14,7 +14,7 @@ = hidden_field_tag :namespace_id, params[:namespace_id] - namespace = Namespace.find(params[:namespace_id]) - toggle_text = "#{namespace.kind}: #{namespace.full_path}" - = dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { toggle_class: 'js-namespace-select large' }) + = dropdown_toggle(toggle_text, { toggle: 'dropdown', is_filter: 'true' }, { toggle_class: 'js-namespace-select large' }) .dropdown-menu.dropdown-select.dropdown-menu-align-right = dropdown_title('Namespaces') = dropdown_filter("Search for Namespace") diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index ab4165c0bf2..42f92079d85 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -115,7 +115,7 @@ = f.label :new_namespace_id, "Namespace", class: 'control-label' .col-sm-10 .dropdown - = dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id', show_any: 'false' }, { toggle_class: 'js-namespace-select large' }) + = dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' }) .dropdown-menu.dropdown-select = dropdown_title('Namespaces') = dropdown_filter("Search for Namespace") diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 76f4a817744..4965dffab9d 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -52,22 +52,23 @@ %br - if @runners.any? - .table-holder - %table.table - %thead - %tr - %th Type - %th Runner token - %th Description - %th Version - %th Projects - %th Jobs - %th Tags - %th= link_to 'Last contact', admin_runners_path(params.slice(:search).merge(sort: 'contacted_asc')) - %th + .runners-content + .table-holder + %table.table + %thead + %tr + %th Type + %th Runner token + %th Description + %th Version + %th Projects + %th Jobs + %th Tags + %th Last contact + %th - - @runners.each do |runner| - = render "admin/runners/runner", runner: runner - = paginate @runners, theme: "gitlab" + - @runners.each do |runner| + = render "admin/runners/runner", runner: runner + = paginate @runners, theme: "gitlab" - else .nothing-here-block No runners found diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml index 39c7fb0eba2..35a3563dff1 100644 --- a/app/views/ci/status/_badge.html.haml +++ b/app/views/ci/status/_badge.html.haml @@ -5,9 +5,9 @@ - if link && status.has_details? = link_to status.details_path, class: css_classes, title: title do - = custom_icon(status.icon) + = sprite_icon(status.icon) = status.text - else %span{ class: css_classes, title: title } - = custom_icon(status.icon) + = sprite_icon(status.icon) = status.text diff --git a/app/views/ci/status/_dropdown_graph_badge.html.haml b/app/views/ci/status/_dropdown_graph_badge.html.haml index dcfb7f0c32d..c5b4439e273 100644 --- a/app/views/ci/status/_dropdown_graph_badge.html.haml +++ b/app/views/ci/status/_dropdown_graph_badge.html.haml @@ -7,13 +7,13 @@ - if status.has_details? = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do - %span{ class: klass }= custom_icon(status.icon) + %span{ class: klass }= sprite_icon(status.icon) %span.ci-build-text= subject.name - else .menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', title: tooltip, container: 'body' } } - %span{ class: klass }= custom_icon(status.icon) + %span{ class: klass }= sprite_icon(status.icon) %span.ci-build-text= subject.name - if status.has_action? - = link_to status.action_path, class: 'ci-action-icon-wrapper js-ci-action-icon', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title, container: 'body' } do - = custom_icon(status.action_icon) + = link_to status.action_path, class: "ci-action-icon-wrapper js-ci-action-icon", method: status.action_method, data: { toggle: 'tooltip', title: status.action_title, container: 'body' } do + = sprite_icon(status.action_icon, css_class: "icon-action-#{status.action_icon}") diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index f62a0cd681e..a5686002328 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -8,7 +8,7 @@ %li.todos-pending{ class: active_when(params[:state].blank? || params[:state] == 'pending') }> = link_to todos_filter_path(state: 'pending') do %span - To do + Todos %span.badge = number_with_delimiter(todos_pending_count) %li.todos-done{ class: active_when(params[:state] == 'done') }> diff --git a/app/views/profiles/accounts/_reset_token.html.haml b/app/views/profiles/accounts/_reset_token.html.haml deleted file mode 100644 index c31a4a8ecd4..00000000000 --- a/app/views/profiles/accounts/_reset_token.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -- name = label.parameterize -- attribute = name.underscore - -.reset-action - %p.cgray - = label_tag name, label, class: "label-light" - = text_field_tag name, current_user.send(attribute), class: 'form-control', readonly: true, onclick: 'this.select()' - %p.help-block - = help_text - .prepend-top-default - = link_to button_label, [:reset, attribute, :profile], method: :put, data: { confirm: 'Are you sure?' }, class: 'btn btn-default private-token' diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 7f79168dfb3..ced58dffcdc 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -9,22 +9,6 @@ .row.prepend-top-default .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 - Private Tokens - %p - Keep these tokens secret, anyone with access to them can interact with - GitLab as if they were you. - .col-lg-8.private-tokens-reset - = render partial: 'reset_token', locals: { label: 'Private token', button_label: 'Reset private token', help_text: 'Your private token is used to access the API and Atom feeds without username/password authentication.' } - - = render partial: 'reset_token', locals: { label: 'RSS token', button_label: 'Reset RSS token', help_text: 'Your RSS token is used to create urls for personalized RSS feeds.' } - - - if incoming_email_token_enabled? - = render partial: 'reset_token', locals: { label: 'Incoming email token', button_label: 'Reset incoming email token', help_text: 'Your incoming email token is used to create new issues by email, and is included in your project-specific email addresses.' } - -%hr -.row.prepend-top-default - .col-lg-4.profile-settings-sidebar - %h4.prepend-top-0 Two-Factor Authentication %p Increase your account's security by enabling Two-Factor Authentication (2FA). diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml index 06bb72b9f0d..26c2e4c5936 100644 --- a/app/views/profiles/personal_access_tokens/index.html.haml +++ b/app/views/profiles/personal_access_tokens/index.html.haml @@ -30,3 +30,40 @@ = render "shared/personal_access_tokens_form", path: profile_personal_access_tokens_path, impersonation: false, token: @personal_access_token, scopes: @scopes = render "shared/personal_access_tokens_table", impersonation: false, active_tokens: @active_personal_access_tokens, inactive_tokens: @inactive_personal_access_tokens + +%hr +.row.prepend-top-default + .col-lg-4.profile-settings-sidebar + %h4.prepend-top-0 + RSS token + %p + Your RSS token is used to authenticate you when your RSS reader loads a personalized RSS feed, and is included in your personal RSS feed URLs. + %p + It cannot be used to access any other data. + .col-lg-8.rss-token-reset + = label_tag :rss_token, 'RSS token', class: "label-light" + = text_field_tag :rss_token, current_user.rss_token, class: 'form-control', readonly: true, onclick: 'this.select()' + %p.help-block + Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds as if they were you. + You should + = link_to 'reset it', [:reset, :rss_token, :profile], method: :put, data: { confirm: 'Are you sure? Any RSS URLs currently in use will stop working.' } + if that ever happens. + +- if incoming_email_token_enabled? + %hr + .row.prepend-top-default + .col-lg-4.profile-settings-sidebar + %h4.prepend-top-0 + Incoming email token + %p + Your incoming email token is used to authenticate you when you create a new issue by email, and is included in your personal project-specific email addresses. + %p + It cannot be used to access any other data. + .col-lg-8.incoming-email-token-reset + = label_tag :incoming_email_token, 'Incoming email token', class: "label-light" + = text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control', readonly: true, onclick: 'this.select()' + %p.help-block + Keep this token secret. Anyone who gets ahold of it can create issues as if they were you. + You should + = link_to 'reset it', [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: 'Are you sure? Any issue email addresses currently in use will stop working.' } + if that ever happens. diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml index 623d3bc91c6..c5b1897c492 100644 --- a/app/views/projects/_export.html.haml +++ b/app/views/projects/_export.html.haml @@ -3,7 +3,7 @@ - project = local_assigns.fetch(:project) - expanded = Rails.env.test? -%section.settings +%section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Export project @@ -11,7 +11,7 @@ = expanded ? 'Collapse' : 'Expand' %p Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content .bs-callout.bs-callout-info %p.append-bottom-0 %p diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml index 45985a5ecef..e75ae87e771 100644 --- a/app/views/projects/deploy_keys/_index.html.haml +++ b/app/views/projects/deploy_keys/_index.html.haml @@ -1,5 +1,5 @@ - expanded = Rails.env.test? -%section.settings +%section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Deploy Keys @@ -7,7 +7,7 @@ = expanded ? 'Collapse' : 'Expand' %p Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content %h5.prepend-top-0 Create a new deploy key for this project = render @deploy_keys.form_partial_path diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 893e536e289..5703ef1d4bb 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -4,7 +4,7 @@ - expanded = Rails.env.test? .project-edit-container - %section.settings.general-settings + %section.settings.general-settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 General project settings @@ -12,7 +12,7 @@ = expanded ? 'Collapse' : 'Expand' %p Update your project name, description, avatar, and other general settings. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content .project-edit-errors = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f| %fieldset @@ -61,7 +61,7 @@ = link_to 'Remove avatar', project_avatar_path(@project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" = f.submit 'Save changes', class: "btn btn-save" - %section.settings.sharing-permissions + %section.settings.sharing-permissions.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Permissions @@ -69,13 +69,13 @@ = expanded ? 'Collapse' : 'Expand' %p Enable or disable certain project features and choose access levels. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f| %script.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project) .js-project-permissions-form = f.submit 'Save changes', class: "btn btn-save" - %section.settings.merge-requests-feature{ class: ("hidden" if @project.project_feature.send(:merge_requests_access_level) == 0) } + %section.settings.merge-requests-feature.no-animate{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] } .settings-header %h4 Merge request settings @@ -83,14 +83,14 @@ = expanded ? 'Collapse' : 'Expand' %p Customize your merge request restrictions. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form" }, authenticity_token: true do |f| = render 'merge_request_settings', form: f = f.submit 'Save changes', class: "btn btn-save" = render 'export', project: @project - %section.settings.advanced-settings + %section.settings.advanced-settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Advanced settings @@ -98,7 +98,7 @@ = expanded ? 'Collapse' : 'Expand' %p Perform advanced options such as housekeeping, archiving, renaming, transferring, or removing your project. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content .sub-section %h4 Housekeeping %p diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 70156c03e3c..cce16bc58b3 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,5 +1,5 @@ - @no_container = true -- page_title "Contributors" +- page_title _('Contributors') - content_for :page_specific_javascripts do = webpack_bundle_tag('common_d3') = webpack_bundle_tag('graphs') @@ -7,23 +7,23 @@ .js-graphs-show{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) } .sub-header-block - .tree-ref-holder + .tree-ref-holder.inline.vertical-align-middle = render 'shared/ref_switcher', destination: 'graphs' - %ul.breadcrumb.repo-breadcrumb - = commits_breadcrumbs + = link_to s_('Commits|History'), project_commits_path(@project, current_ref), class: 'btn' .loading-graph .center %h3.page-title %i.fa.fa-spinner.fa-spin - Building repository graph. - %p.slead Please wait a moment, this page will automatically refresh when ready. + = s_('ContributorsPage|Building repository graph.') + %p.slead + = s_('ContributorsPage|Please wait a moment, this page will automatically refresh when ready.') .stat-graph.hide .header.clearfix %h3#date_header.page-title %p.light - Commits to #{@ref}, excluding merge commits. Limited to 6,000 commits. + = s_('ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits.') % { branch_name: @ref } %input#brush_change{ :type => "hidden" } .graphs.row #contributors-master diff --git a/app/views/projects/hook_logs/_index.html.haml b/app/views/projects/hook_logs/_index.html.haml index 05b06cfc8b2..8096d9530c3 100644 --- a/app/views/projects/hook_logs/_index.html.haml +++ b/app/views/projects/hook_logs/_index.html.haml @@ -24,7 +24,7 @@ %td = truncate(hook_log.url, length: 50) %td.light - #{number_with_precision(hook_log.execution_duration, precision: 2)} ms + #{number_with_precision(hook_log.execution_duration, precision: 2)} sec %td.light = time_ago_with_tooltip(hook_log.created_at) %td diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index 7da4ffd5e43..b5067367802 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -91,7 +91,7 @@ - builds.select{|build| build.status == build_status}.each do |build| .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } } = link_to project_job_path(@project, build) do - = icon('arrow-right') + = sprite_icon('arrow-right', size:16, css_class: 'icon-arrow-right') %span{ class: "ci-status-icon-#{build.status}" } = ci_icon_for_status(build.status) %span @@ -100,4 +100,5 @@ - else = build.id - if build.retried? - %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' } + %span.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' } + = sprite_icon('retry', size:16, css_class: 'icon-retry') diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml index cb723fe6a18..72d5c4961ec 100644 --- a/app/views/projects/merge_requests/_mr_title.html.haml +++ b/app/views/projects/merge_requests/_mr_title.html.haml @@ -34,7 +34,7 @@ %li{ class: [merge_request_button_visibility(@merge_request, true), 'js-close-item'] } = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request' %li{ class: merge_request_button_visibility(@merge_request, false) } - = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' - if can_update_merge_request = link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" diff --git a/app/views/projects/protected_branches/shared/_index.html.haml b/app/views/projects/protected_branches/shared/_index.html.haml index 6a47cbdf724..ba7d98228c3 100644 --- a/app/views/projects/protected_branches/shared/_index.html.haml +++ b/app/views/projects/protected_branches/shared/_index.html.haml @@ -1,6 +1,6 @@ - expanded = Rails.env.test? -%section.settings +%section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Protected Branches @@ -8,7 +8,7 @@ = expanded ? 'Collapse' : 'Expand' %p Keep stable branches secure and force developers to use merge requests. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content %p By default, protected branches are designed to: %ul diff --git a/app/views/projects/protected_tags/shared/_index.html.haml b/app/views/projects/protected_tags/shared/_index.html.haml index c07bd454ff6..e764a37bbd7 100644 --- a/app/views/projects/protected_tags/shared/_index.html.haml +++ b/app/views/projects/protected_tags/shared/_index.html.haml @@ -1,6 +1,6 @@ - expanded = Rails.env.test? -%section.settings +%section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Protected Tags @@ -8,7 +8,7 @@ = expanded ? 'Collapse' : 'Expand' %p Limit access to creating and updating tags. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content %p By default, protected tags are designed to: %ul diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 62455d0d40d..664a4554692 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -4,7 +4,7 @@ - expanded = Rails.env.test? -%section.settings#js-general-pipeline-settings +%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 General pipelines settings @@ -12,10 +12,10 @@ = expanded ? 'Collapse' : 'Expand' %p Update your CI/CD configuration, like job timeout or Auto DevOps. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content = render 'projects/pipelines_settings/show' -%section.settings +%section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Runners settings @@ -23,10 +23,10 @@ = expanded ? 'Collapse' : 'Expand' %p Register and see your runners for this project. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content = render 'projects/runners/index' -%section.settings +%section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Secret variables @@ -35,10 +35,10 @@ = expanded ? 'Collapse' : 'Expand' %p = render "ci/variables/content" - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content = render 'ci/variables/index' -%section.settings +%section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Pipeline triggers @@ -48,5 +48,5 @@ Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content = render 'projects/triggers/index' diff --git a/app/views/shared/_mini_pipeline_graph.html.haml b/app/views/shared/_mini_pipeline_graph.html.haml index dff847159d3..901a177323b 100644 --- a/app/views/shared/_mini_pipeline_graph.html.haml +++ b/app/views/shared/_mini_pipeline_graph.html.haml @@ -7,7 +7,7 @@ .stage-container.dropdown{ class: klass } %button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_project_pipeline_path(pipeline.project, pipeline, stage: stage.name) } } - = custom_icon(icon_status) + = sprite_icon(icon_status) = icon('caret-down') %ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container diff --git a/app/views/shared/hook_logs/_content.html.haml b/app/views/shared/hook_logs/_content.html.haml index af6a499fadb..c80b179d525 100644 --- a/app/views/shared/hook_logs/_content.html.haml +++ b/app/views/shared/hook_logs/_content.html.haml @@ -11,7 +11,7 @@ = hook_log.trigger.singularize.titleize %p %strong Elapsed time: - #{number_with_precision(hook_log.execution_duration, precision: 2)} ms + #{number_with_precision(hook_log.execution_duration, precision: 2)} sec %p %strong Request time: = time_ago_with_tooltip(hook_log.created_at) diff --git a/app/views/shared/icons/_icon_autodevops.svg b/app/views/shared/icons/_icon_autodevops.svg index dde84e14048..423ca6d760d 100644 --- a/app/views/shared/icons/_icon_autodevops.svg +++ b/app/views/shared/icons/_icon_autodevops.svg @@ -29,7 +29,7 @@ </g> <g fill-rule="nonzero" transform="rotate(15 -315.035 277.714)"> <path fill="#FFFFFF" d="M12.275,10.57 C13.986216,9.15630755 15.921048,8.03765363 18,7.26 L18,5.5 C18,2.463 20.47,0 23.493,0 L26.507,0 C27.9648848,0.000530018716 29.3628038,0.580386367 30.3930274,1.61192286 C31.4232511,2.64345935 32.0013267,4.04211574 32,5.5 L32,7.26 C34.098,8.043 36.03,9.17 37.725,10.57 L39.253,9.688 C41.8816141,8.17268496 45.2407537,9.07039379 46.763,11.695 L48.27,14.305 C48.9984289,15.5678669 49.1951495,17.0684426 48.8168566,18.4763972 C48.4385638,19.8843518 47.5162683,21.0842673 46.253,21.812 L44.728,22.693 C44.907,23.769 45,24.873 45,26 C45,27.127 44.907,28.231 44.728,29.307 L46.253,30.187 C48.8800379,31.705769 49.7822744,35.0642181 48.27,37.695 L46.763,40.305 C46.0335844,41.5673849 44.8323832,42.4881439 43.4238487,42.8645658 C42.0153143,43.2409877 40.5149245,43.0422119 39.253,42.312 L37.725,41.43 C36.013784,42.8436924 34.078952,43.9623464 32,44.74 L32,46.5 C32,49.537 29.53,52 26.507,52 L23.493,52 C22.0351152,51.99947 20.6371962,51.4196136 19.6069726,50.3880771 C18.5767489,49.3565406 17.9986733,47.9578843 18,46.5 L18,44.74 C15.921048,43.9623464 13.986216,42.8436924 12.275,41.43 L10.747,42.312 C8.11838594,43.827315 4.75924629,42.9296062 3.237,40.305 L1.73,37.695 C1.00157113,36.4321331 0.804850523,34.9315574 1.18314337,33.5236028 C1.56143621,32.1156482 2.48373172,30.9157327 3.747,30.188 L5.272,29.307 C5.09051204,28.2140265 4.9995366,27.107939 5,26 C5,24.873 5.093,23.769 5.272,22.693 L3.747,21.813 C1.11996213,20.294231 0.217725591,16.9357819 1.73,14.305 L3.237,11.695 C3.96641559,10.4326151 5.16761682,9.51185609 6.57615125,9.13543417 C7.98468568,8.75901226 9.48507553,8.95778814 10.747,9.688 L12.275,10.57 Z"/> - <path class="animated spin infinite" fill="#E1DBF1" d="M17.9996486,7.25963195 L18.0000013,5.49772675 C18.0034459,2.46713881 20.4561478,0.00952173148 23.493,0 L26.507,0 C29.542757,0 32,2.46161709 32,5.5 L32,7.25850184 C34.0799663,8.03664754 36.0149544,9.15559094 37.7260175,10.5694605 L39.2547869,9.68691874 C41.8812087,8.17416302 45.2363972,9.06948854 46.7630175,11.6949424 L48.270687,14.3061027 C48.9989901,15.569417 49.1952874,17.0704122 48.816349,18.4785295 C48.4374106,19.8866468 47.5143145,21.0864021 46.2530682,21.8120114 L44.7278655,22.6926677 C44.9091017,23.7802451 45,24.8850821 45,26 C45,27.1144218 44.9091826,28.218078 44.7278653,29.3073326 L46.2547984,30.1889888 C48.8778516,31.7070439 49.7801588,35.0599752 48.2700175,37.6950576 L46.7625317,40.3058986 C46.0327098,41.5684739 44.8309328,42.4891542 43.4219037,42.8651509 C42.0128746,43.2411475 40.512172,43.0416186 39.2533538,42.312255 L37.7244858,41.4299789 C36.013753,42.8435912 34.0794396,43.9622923 32.0003514,44.7403681 L31.9999987,46.5022733 C31.9965541,49.5328612 29.5438522,51.9904783 26.507,52 L23.493,52 C20.457243,52 18,49.5383829 18,46.5 L18,44.7414988 C15.9200337,43.9633525 13.9850456,42.8444091 12.2739825,41.4305395 L10.7452131,42.3130813 C8.11879127,43.825837 4.76360277,42.9305115 3.23698247,40.3050576 L1.72931303,37.6938973 C1.0010099,36.430583 0.804712603,34.9295878 1.18365098,33.5214705 C1.56258936,32.1133532 2.48568546,30.9135979 3.74693178,30.1879886 L5.27213454,29.3073323 C5.09089825,28.2197549 5,27.114918 5.00000019,26.0008761 C4.99951488,24.8930059 5.0904571,23.7869854 5.27213502,22.6926675 L3.74520157,21.8110112 C1.12214836,20.2929561 0.219841192,16.9400248 1.72998247,14.3049424 L3.23746831,11.6941014 C3.96729024,10.4315261 5.16906725,9.51084579 6.5780963,9.13484913 C7.98712536,8.75885247 9.48782803,8.95838137 10.7466462,9.687745 L12.2748018,10.56961 C14.0209791,9.13635584 15.9392199,8.03072455 17.9996486,7.25963195 Z M13.7518374,14.537862 C13.108069,15.069723 12.2016163,15.1456339 11.4783538,14.728255 L8.74433999,13.1505123 C8.40103903,12.9516035 7.99274958,12.8973186 7.60940137,12.9996143 C7.22605315,13.10191 6.89909107,13.3523954 6.70101753,13.6950576 L5.19724591,16.2994454 C4.78547321,17.0179634 5.03203388,17.9341714 5.74706822,18.3479886 L8.47306822,19.9219886 C9.19530115,20.3390079 9.58295216,21.1604138 9.44574883,21.983032 L9.21798321,23.3486236 C9.07251948,24.2246212 8.99961081,25.111131 9,26 C9,26.8953847 9.0728258,27.7804297 9.21774883,28.649968 L9.44574883,30.016968 C9.58295216,30.8395862 9.19530115,31.6609921 8.47306822,32.0780114 L5.74435077,33.6535776 C5.40046982,33.851417 5.14932721,34.1778291 5.04623114,34.5609292 C4.94313508,34.9440294 4.9965408,35.3523984 5.19401753,35.6949424 L6.69795587,38.2996585 C7.11427713,39.0156351 8.03110189,39.260288 8.7470791,38.8479035 L11.4770791,37.2719035 C12.200376,36.8543519 13.1069795,36.9302031 13.7508374,37.462138 L14.8210499,38.3463136 C16.1898549,39.4774943 17.737648,40.3725891 19.3990866,40.9941596 L20.6990866,41.4791596 C21.4813437,41.7710017 22,42.5180761 22,43.353 L22,46.5 C22,47.3308348 22.6679761,48 23.493,48 L26.5007228,48.0000099 C27.328845,47.9974107 27.99906,47.3258525 28,46.5 L28,43.353 C28,42.5185702 28.5180515,41.771829 29.2996486,41.4796319 L30.599003,40.9938734 C32.261836,40.3715765 33.8093225,39.4764853 35.1790197,38.3444304 L36.2490197,37.4614304 C36.8927697,36.9301861 37.798736,36.8545694 38.5216462,37.271745 L41.25566,38.8494877 C41.598961,39.0483965 42.0072504,39.1026814 42.3905986,39.0003857 C42.7739468,38.89809 43.1009089,38.6476046 43.2989825,38.3049424 L44.8027541,35.7005546 C45.2145268,34.9820366 44.9679661,34.0658286 44.2529318,33.6520114 L41.5269318,32.0780114 C40.8046988,31.6609921 40.4170478,30.8395862 40.5542512,30.016968 L40.7821577,28.6505288 C40.9272286,27.7792134 41,26.8950523 41,26 C41,25.1046153 40.9271742,24.2195703 40.7822512,23.350032 L40.5542512,21.983032 C40.4170478,21.1604138 40.8046988,20.3390079 41.5269318,19.9219886 L44.2556492,18.3464224 C44.5995302,18.148583 44.8506728,17.8221709 44.9537689,17.4390708 C45.0568649,17.0559706 45.0034592,16.6476016 44.8059825,16.3050576 L43.3020441,13.7003415 C42.8857229,12.9843649 41.9688981,12.739712 41.2529209,13.1520965 L38.5229209,14.7280965 C37.799624,15.1456481 36.8930205,15.0697969 36.2491626,14.537862 L35.1789501,13.6536864 C33.8101451,12.5225057 32.262352,11.6274109 30.6021792,11.0063122 L29.3021792,10.5223122 C28.5192618,10.230826 28,9.48341836 28,8.648 L28,5.5 C28,4.66916515 27.3320239,4 26.507,4 L23.4992772,3.99999015 C22.671155,4.00258933 22.00094,4.67414748 22,5.5 L22,8.647 C22,9.48142977 21.4819485,10.228171 20.7003514,10.5203681 L19.400997,11.0061266 C17.738164,11.6284235 16.1906775,12.5235147 14.822142,13.6546103 C14.8121128,13.6628994 14.4553446,13.9573166 13.7518374,14.537862 Z"/> + <path class="animated spin-cw infinite" fill="#E1DBF1" d="M17.9996486,7.25963195 L18.0000013,5.49772675 C18.0034459,2.46713881 20.4561478,0.00952173148 23.493,0 L26.507,0 C29.542757,0 32,2.46161709 32,5.5 L32,7.25850184 C34.0799663,8.03664754 36.0149544,9.15559094 37.7260175,10.5694605 L39.2547869,9.68691874 C41.8812087,8.17416302 45.2363972,9.06948854 46.7630175,11.6949424 L48.270687,14.3061027 C48.9989901,15.569417 49.1952874,17.0704122 48.816349,18.4785295 C48.4374106,19.8866468 47.5143145,21.0864021 46.2530682,21.8120114 L44.7278655,22.6926677 C44.9091017,23.7802451 45,24.8850821 45,26 C45,27.1144218 44.9091826,28.218078 44.7278653,29.3073326 L46.2547984,30.1889888 C48.8778516,31.7070439 49.7801588,35.0599752 48.2700175,37.6950576 L46.7625317,40.3058986 C46.0327098,41.5684739 44.8309328,42.4891542 43.4219037,42.8651509 C42.0128746,43.2411475 40.512172,43.0416186 39.2533538,42.312255 L37.7244858,41.4299789 C36.013753,42.8435912 34.0794396,43.9622923 32.0003514,44.7403681 L31.9999987,46.5022733 C31.9965541,49.5328612 29.5438522,51.9904783 26.507,52 L23.493,52 C20.457243,52 18,49.5383829 18,46.5 L18,44.7414988 C15.9200337,43.9633525 13.9850456,42.8444091 12.2739825,41.4305395 L10.7452131,42.3130813 C8.11879127,43.825837 4.76360277,42.9305115 3.23698247,40.3050576 L1.72931303,37.6938973 C1.0010099,36.430583 0.804712603,34.9295878 1.18365098,33.5214705 C1.56258936,32.1133532 2.48568546,30.9135979 3.74693178,30.1879886 L5.27213454,29.3073323 C5.09089825,28.2197549 5,27.114918 5.00000019,26.0008761 C4.99951488,24.8930059 5.0904571,23.7869854 5.27213502,22.6926675 L3.74520157,21.8110112 C1.12214836,20.2929561 0.219841192,16.9400248 1.72998247,14.3049424 L3.23746831,11.6941014 C3.96729024,10.4315261 5.16906725,9.51084579 6.5780963,9.13484913 C7.98712536,8.75885247 9.48782803,8.95838137 10.7466462,9.687745 L12.2748018,10.56961 C14.0209791,9.13635584 15.9392199,8.03072455 17.9996486,7.25963195 Z M13.7518374,14.537862 C13.108069,15.069723 12.2016163,15.1456339 11.4783538,14.728255 L8.74433999,13.1505123 C8.40103903,12.9516035 7.99274958,12.8973186 7.60940137,12.9996143 C7.22605315,13.10191 6.89909107,13.3523954 6.70101753,13.6950576 L5.19724591,16.2994454 C4.78547321,17.0179634 5.03203388,17.9341714 5.74706822,18.3479886 L8.47306822,19.9219886 C9.19530115,20.3390079 9.58295216,21.1604138 9.44574883,21.983032 L9.21798321,23.3486236 C9.07251948,24.2246212 8.99961081,25.111131 9,26 C9,26.8953847 9.0728258,27.7804297 9.21774883,28.649968 L9.44574883,30.016968 C9.58295216,30.8395862 9.19530115,31.6609921 8.47306822,32.0780114 L5.74435077,33.6535776 C5.40046982,33.851417 5.14932721,34.1778291 5.04623114,34.5609292 C4.94313508,34.9440294 4.9965408,35.3523984 5.19401753,35.6949424 L6.69795587,38.2996585 C7.11427713,39.0156351 8.03110189,39.260288 8.7470791,38.8479035 L11.4770791,37.2719035 C12.200376,36.8543519 13.1069795,36.9302031 13.7508374,37.462138 L14.8210499,38.3463136 C16.1898549,39.4774943 17.737648,40.3725891 19.3990866,40.9941596 L20.6990866,41.4791596 C21.4813437,41.7710017 22,42.5180761 22,43.353 L22,46.5 C22,47.3308348 22.6679761,48 23.493,48 L26.5007228,48.0000099 C27.328845,47.9974107 27.99906,47.3258525 28,46.5 L28,43.353 C28,42.5185702 28.5180515,41.771829 29.2996486,41.4796319 L30.599003,40.9938734 C32.261836,40.3715765 33.8093225,39.4764853 35.1790197,38.3444304 L36.2490197,37.4614304 C36.8927697,36.9301861 37.798736,36.8545694 38.5216462,37.271745 L41.25566,38.8494877 C41.598961,39.0483965 42.0072504,39.1026814 42.3905986,39.0003857 C42.7739468,38.89809 43.1009089,38.6476046 43.2989825,38.3049424 L44.8027541,35.7005546 C45.2145268,34.9820366 44.9679661,34.0658286 44.2529318,33.6520114 L41.5269318,32.0780114 C40.8046988,31.6609921 40.4170478,30.8395862 40.5542512,30.016968 L40.7821577,28.6505288 C40.9272286,27.7792134 41,26.8950523 41,26 C41,25.1046153 40.9271742,24.2195703 40.7822512,23.350032 L40.5542512,21.983032 C40.4170478,21.1604138 40.8046988,20.3390079 41.5269318,19.9219886 L44.2556492,18.3464224 C44.5995302,18.148583 44.8506728,17.8221709 44.9537689,17.4390708 C45.0568649,17.0559706 45.0034592,16.6476016 44.8059825,16.3050576 L43.3020441,13.7003415 C42.8857229,12.9843649 41.9688981,12.739712 41.2529209,13.1520965 L38.5229209,14.7280965 C37.799624,15.1456481 36.8930205,15.0697969 36.2491626,14.537862 L35.1789501,13.6536864 C33.8101451,12.5225057 32.262352,11.6274109 30.6021792,11.0063122 L29.3021792,10.5223122 C28.5192618,10.230826 28,9.48341836 28,8.648 L28,5.5 C28,4.66916515 27.3320239,4 26.507,4 L23.4992772,3.99999015 C22.671155,4.00258933 22.00094,4.67414748 22,5.5 L22,8.647 C22,9.48142977 21.4819485,10.228171 20.7003514,10.5203681 L19.400997,11.0061266 C17.738164,11.6284235 16.1906775,12.5235147 14.822142,13.6546103 C14.8121128,13.6628994 14.4553446,13.9573166 13.7518374,14.537862 Z"/> <g transform="rotate(15 -59.137 82.348)"> <circle cx="8" cy="8" r="8" fill="#FFFFFF" transform="translate(.035 6.008)"/> <path fill="#6B4FBB" d="M7.40192379,14.7679492 C2.98364579,14.7679492 -0.598076211,11.1862272 -0.598076211,6.76794919 C-0.598076211,2.34967119 2.98364579,-1.23205081 7.40192379,-1.23205081 C11.8202018,-1.23205081 15.4019238,2.34967119 15.4019238,6.76794919 C15.4019238,11.1862272 11.8202018,14.7679492 7.40192379,14.7679492 Z M7.40192379,10.7679492 C9.61106279,10.7679492 11.4019238,8.97708819 11.4019238,6.76794919 C11.4019238,4.55881019 9.61106279,2.76794919 7.40192379,2.76794919 C5.19278479,2.76794919 3.40192379,4.55881019 3.40192379,6.76794919 C3.40192379,8.97708819 5.19278479,10.7679492 7.40192379,10.7679492 Z"/> @@ -37,7 +37,7 @@ </g> <g fill-rule="nonzero" transform="rotate(15 -402.968 460.884)"> <path fill="#FFFFFF" d="M9.82,8.53730769 C11.1889728,7.39547918 12.7368384,6.49195101 14.4,5.86384615 L14.4,4.44230769 C14.4,1.98934615 16.376,0 18.7944,0 L21.2056,0 C22.3719078,0.00042809204 23.4902431,0.468773604 24.314422,1.30193769 C25.1386009,2.13510179 25.6010613,3.26478579 25.6,4.44230769 L25.6,5.86384615 C27.2784,6.49626923 28.824,7.40653846 30.18,8.53730769 L31.4024,7.82492308 C33.5052912,6.60101478 36.192603,7.32608729 37.4104,9.44596154 L38.616,11.5540385 C39.1987431,12.5740464 39.3561196,13.7860498 39.0534853,14.9232439 C38.750851,16.060438 38.0130146,17.0296006 37.0024,17.6173846 L35.7824,18.3289615 C35.9256,19.1980385 36,20.0897308 36,21 C36,21.9102692 35.9256,22.8019615 35.7824,23.6710385 L37.0024,24.3818077 C39.1040303,25.6085057 39.8258195,28.3210992 38.616,30.4459615 L37.4104,32.5540385 C36.8268675,33.573657 35.8659065,34.317347 34.739079,34.6213801 C33.6122515,34.9254132 32.4119396,34.7648634 31.4024,34.1750769 L30.18,33.4626923 C28.8110272,34.6045208 27.2631616,35.508049 25.6,36.1361538 L25.6,37.5576923 C25.6,40.0106538 23.624,42 21.2056,42 L18.7944,42 C17.6280922,41.9995719 16.5097569,41.5312264 15.685578,40.6980623 C14.8613991,39.8648982 14.3989387,38.7352142 14.4,37.5576923 L14.4,36.1361538 C12.7368384,35.508049 11.1889728,34.6045208 9.82,33.4626923 L8.5976,34.1750769 C6.49470875,35.3989852 3.80739703,34.6739127 2.5896,32.5540385 L1.384,30.4459615 C0.8012569,29.4259536 0.643880418,28.2139502 0.946514692,27.0767561 C1.24914897,25.939562 1.98698538,24.9703994 2.9976,24.3826154 L4.2176,23.6710385 C4.07240963,22.7882521 3.99962928,21.8948738 4,21 C4,20.0897308 4.0744,19.1980385 4.2176,18.3289615 L2.9976,17.6181923 C0.895969702,16.3914943 0.174180473,13.6789008 1.384,11.5540385 L2.5896,9.44596154 C3.17313247,8.42634297 4.13409345,7.682653 5.260921,7.37861991 C6.38774855,7.07458682 7.58806043,7.23513658 8.5976,7.82492308 L9.82,8.53730769 Z"/> - <path class="animated spin infinite" fill="#FEE1D3" d="M14.0000007,5.6038043 L14.0000013,4.44005609 C14.0029906,1.78475013 16.1390906,-0.376211234 18.7944,-0.384615385 L21.2056,-0.384615385 C23.8595941,-0.384615385 26,1.78021801 26,4.44230769 L26,5.60295806 C27.5208716,6.20655954 28.9434678,7.03621848 30.2204219,8.06411282 L31.1970056,7.49492104 C33.4941909,6.15907529 36.4301298,6.95005805 37.7609369,9.26076474 L38.9671983,11.3699991 C39.5988409,12.4761812 39.768854,13.7886936 39.4405746,15.0202941 C39.1116282,16.2543969 38.308799,17.3078735 37.2096539,17.946304 L36.2175721,18.5246428 C36.3390841,19.3401617 36.4,20.1667594 36.4,21 C36.4,21.8329668 36.339124,22.6588262 36.2175401,23.4753391 L37.2113882,24.0547082 C39.4944154,25.3886826 40.276605,28.3232105 38.9665369,30.6311583 L37.7604568,32.7400742 C37.1252608,33.8495148 36.0768547,34.6604208 34.8452776,34.9922248 C33.6111681,35.324711 32.2964469,35.1482289 31.195569,34.5042428 L30.2192355,33.9354047 C28.9426535,34.9630196 27.5206806,35.7924453 25.9999993,36.3961957 L25.9999987,37.5599439 C25.9970094,40.2152499 23.8609094,42.3762112 21.2056,42.3846154 L18.7944,42.3846154 C16.1404059,42.3846154 14,40.219782 14,37.5576923 L14,36.3970419 C12.4791284,35.7934405 11.0565322,34.9637815 9.77957815,33.9358872 L8.80299442,34.505079 C6.50580915,35.8409247 3.56987021,35.049942 2.23906313,32.7392353 L1.03280169,30.6300009 C0.401159146,29.5238188 0.231145999,28.2113064 0.559425405,26.9797059 C0.888371786,25.7456031 1.69120101,24.6921265 2.79034606,24.053696 L3.78242779,23.4753573 C3.66091587,22.6598457 3.60000002,21.8333228 3.60000019,21.0008678 C3.59964068,20.1722851 3.66061719,19.3449468 3.78254167,18.5247085 L2.78861183,17.9452918 C0.505584602,16.6113174 -0.276605002,13.6767895 1.03346313,11.3688417 L2.23954317,9.25992583 C2.87473915,8.15048519 3.92314533,7.33957919 5.15472238,7.00777521 C6.38883187,6.67528896 7.70355311,6.85177112 8.80443097,7.49575721 L9.78076186,8.06459377 C11.0573465,7.03698045 12.4793194,6.20755475 14.0000007,5.6038043 Z M11.2634746,12.0326234 C10.617233,12.5716613 9.7026973,12.6485026 8.97556903,12.2248582 L6.78774825,10.9501716 C6.60754053,10.8447551 6.39506809,10.8162338 6.19527576,10.8700606 C5.99295099,10.9245697 5.8183659,11.0596053 5.71133687,11.246543 L4.50892658,13.3490215 C4.28085652,13.7508163 4.41776119,14.2644394 4.80485394,14.4906191 L6.98565394,15.7619268 C7.70254629,16.1798426 8.08690703,16.9970357 7.95165511,17.8157512 L7.76948523,18.9184706 C7.65638664,19.6061109 7.59969735,20.3020342 7.6,21 C7.6,21.7031066 7.65662064,22.3978283 7.76925511,23.0801334 L7.95165511,24.1842488 C8.08690703,25.0029643 7.70254629,25.8201574 6.98565394,26.2380732 L4.80213007,27.5109659 C4.61772321,27.6180778 4.48116147,27.7972748 4.42448029,28.0099246 C4.36713215,28.2250767 4.39688141,28.454743 4.50573687,28.6453801 L5.70831165,30.7481858 C5.93243371,31.1373303 6.41410538,31.2670993 6.79049373,31.0482253 L8.97449373,29.7753023 C9.7016554,29.3514832 10.6163433,29.4282639 11.2626746,29.9673766 L12.1188867,30.6815536 C13.1796505,31.566598 14.3786867,32.2666727 15.6649769,32.7525215 L16.7049769,33.1442523 C17.4841581,33.4377419 18,34.1832625 18,35.0158846 L18,37.5576923 C18,38.02074 18.3597694,38.3846154 18.7944,38.3846154 L21.1992624,38.3846254 C21.6372484,38.3832375 21.9994819,38.0167881 22,37.5576923 L22,35.0158846 C22,34.18376 22.5152346,33.4385758 23.2937506,33.1447321 L24.3331012,32.7524389 C25.620867,32.2658727 26.8196661,31.5658006 27.8813806,30.679856 L28.7373806,29.9666637 C29.3836087,29.4282468 30.2976553,29.3517028 31.024431,29.7751418 L33.2122517,31.0498284 C33.3924595,31.1552449 33.6049319,31.1837662 33.8047242,31.1299394 C34.007049,31.0754303 34.1816341,30.9403947 34.2886631,30.753457 L35.4910734,28.6509785 C35.7191435,28.2491837 35.5822388,27.7355606 35.1951461,27.5093809 L33.0143461,26.2380732 C32.2974537,25.8201574 31.913093,25.0029643 32.0483449,24.1842488 L32.2306531,23.0806893 C32.3434217,22.3968737 32.4,21.7028459 32.4,21 C32.4,20.2968934 32.3433794,19.6021717 32.2307449,18.9198666 L32.0483449,17.8157512 C31.913093,16.9970357 32.2974537,16.1798426 33.0143461,15.7619268 L35.1978699,14.4890341 C35.3822768,14.3819222 35.5188385,14.2027252 35.5755197,13.9900754 C35.6328679,13.7749233 35.6031186,13.545257 35.4942631,13.3546199 L34.2916883,11.2518142 C34.0675663,10.8626697 33.5858946,10.7329007 33.2095063,10.9517747 L31.0255063,12.2246977 C30.2983446,12.6485168 29.3836567,12.5717361 28.7373254,12.0326234 L27.8811133,11.3184464 C26.8203495,10.433402 25.6213133,9.73332732 24.3362966,9.24795765 L23.2962966,8.85703457 C22.5164499,8.56389992 22,7.81804293 22,6.98492308 L22,4.44230769 C22,3.97925995 21.6402306,3.61538462 21.2056,3.61538462 L18.8007376,3.61537457 C18.3627516,3.61676247 18.0005181,3.98321188 18,4.44230769 L18,6.98411538 C18,7.81623999 17.4847654,8.56142419 16.7062494,8.85526793 L15.6668988,9.24756113 C14.379133,9.73412728 13.1803339,10.4341994 12.1197785,11.3191775 C12.1108094,11.3266617 11.8253748,11.564477 11.2634746,12.0326234 Z"/> + <path class="animated spin-ccw infinite" fill="#FEE1D3" d="M14.0000007,5.6038043 L14.0000013,4.44005609 C14.0029906,1.78475013 16.1390906,-0.376211234 18.7944,-0.384615385 L21.2056,-0.384615385 C23.8595941,-0.384615385 26,1.78021801 26,4.44230769 L26,5.60295806 C27.5208716,6.20655954 28.9434678,7.03621848 30.2204219,8.06411282 L31.1970056,7.49492104 C33.4941909,6.15907529 36.4301298,6.95005805 37.7609369,9.26076474 L38.9671983,11.3699991 C39.5988409,12.4761812 39.768854,13.7886936 39.4405746,15.0202941 C39.1116282,16.2543969 38.308799,17.3078735 37.2096539,17.946304 L36.2175721,18.5246428 C36.3390841,19.3401617 36.4,20.1667594 36.4,21 C36.4,21.8329668 36.339124,22.6588262 36.2175401,23.4753391 L37.2113882,24.0547082 C39.4944154,25.3886826 40.276605,28.3232105 38.9665369,30.6311583 L37.7604568,32.7400742 C37.1252608,33.8495148 36.0768547,34.6604208 34.8452776,34.9922248 C33.6111681,35.324711 32.2964469,35.1482289 31.195569,34.5042428 L30.2192355,33.9354047 C28.9426535,34.9630196 27.5206806,35.7924453 25.9999993,36.3961957 L25.9999987,37.5599439 C25.9970094,40.2152499 23.8609094,42.3762112 21.2056,42.3846154 L18.7944,42.3846154 C16.1404059,42.3846154 14,40.219782 14,37.5576923 L14,36.3970419 C12.4791284,35.7934405 11.0565322,34.9637815 9.77957815,33.9358872 L8.80299442,34.505079 C6.50580915,35.8409247 3.56987021,35.049942 2.23906313,32.7392353 L1.03280169,30.6300009 C0.401159146,29.5238188 0.231145999,28.2113064 0.559425405,26.9797059 C0.888371786,25.7456031 1.69120101,24.6921265 2.79034606,24.053696 L3.78242779,23.4753573 C3.66091587,22.6598457 3.60000002,21.8333228 3.60000019,21.0008678 C3.59964068,20.1722851 3.66061719,19.3449468 3.78254167,18.5247085 L2.78861183,17.9452918 C0.505584602,16.6113174 -0.276605002,13.6767895 1.03346313,11.3688417 L2.23954317,9.25992583 C2.87473915,8.15048519 3.92314533,7.33957919 5.15472238,7.00777521 C6.38883187,6.67528896 7.70355311,6.85177112 8.80443097,7.49575721 L9.78076186,8.06459377 C11.0573465,7.03698045 12.4793194,6.20755475 14.0000007,5.6038043 Z M11.2634746,12.0326234 C10.617233,12.5716613 9.7026973,12.6485026 8.97556903,12.2248582 L6.78774825,10.9501716 C6.60754053,10.8447551 6.39506809,10.8162338 6.19527576,10.8700606 C5.99295099,10.9245697 5.8183659,11.0596053 5.71133687,11.246543 L4.50892658,13.3490215 C4.28085652,13.7508163 4.41776119,14.2644394 4.80485394,14.4906191 L6.98565394,15.7619268 C7.70254629,16.1798426 8.08690703,16.9970357 7.95165511,17.8157512 L7.76948523,18.9184706 C7.65638664,19.6061109 7.59969735,20.3020342 7.6,21 C7.6,21.7031066 7.65662064,22.3978283 7.76925511,23.0801334 L7.95165511,24.1842488 C8.08690703,25.0029643 7.70254629,25.8201574 6.98565394,26.2380732 L4.80213007,27.5109659 C4.61772321,27.6180778 4.48116147,27.7972748 4.42448029,28.0099246 C4.36713215,28.2250767 4.39688141,28.454743 4.50573687,28.6453801 L5.70831165,30.7481858 C5.93243371,31.1373303 6.41410538,31.2670993 6.79049373,31.0482253 L8.97449373,29.7753023 C9.7016554,29.3514832 10.6163433,29.4282639 11.2626746,29.9673766 L12.1188867,30.6815536 C13.1796505,31.566598 14.3786867,32.2666727 15.6649769,32.7525215 L16.7049769,33.1442523 C17.4841581,33.4377419 18,34.1832625 18,35.0158846 L18,37.5576923 C18,38.02074 18.3597694,38.3846154 18.7944,38.3846154 L21.1992624,38.3846254 C21.6372484,38.3832375 21.9994819,38.0167881 22,37.5576923 L22,35.0158846 C22,34.18376 22.5152346,33.4385758 23.2937506,33.1447321 L24.3331012,32.7524389 C25.620867,32.2658727 26.8196661,31.5658006 27.8813806,30.679856 L28.7373806,29.9666637 C29.3836087,29.4282468 30.2976553,29.3517028 31.024431,29.7751418 L33.2122517,31.0498284 C33.3924595,31.1552449 33.6049319,31.1837662 33.8047242,31.1299394 C34.007049,31.0754303 34.1816341,30.9403947 34.2886631,30.753457 L35.4910734,28.6509785 C35.7191435,28.2491837 35.5822388,27.7355606 35.1951461,27.5093809 L33.0143461,26.2380732 C32.2974537,25.8201574 31.913093,25.0029643 32.0483449,24.1842488 L32.2306531,23.0806893 C32.3434217,22.3968737 32.4,21.7028459 32.4,21 C32.4,20.2968934 32.3433794,19.6021717 32.2307449,18.9198666 L32.0483449,17.8157512 C31.913093,16.9970357 32.2974537,16.1798426 33.0143461,15.7619268 L35.1978699,14.4890341 C35.3822768,14.3819222 35.5188385,14.2027252 35.5755197,13.9900754 C35.6328679,13.7749233 35.6031186,13.545257 35.4942631,13.3546199 L34.2916883,11.2518142 C34.0675663,10.8626697 33.5858946,10.7329007 33.2095063,10.9517747 L31.0255063,12.2246977 C30.2983446,12.6485168 29.3836567,12.5717361 28.7373254,12.0326234 L27.8811133,11.3184464 C26.8203495,10.433402 25.6213133,9.73332732 24.3362966,9.24795765 L23.2962966,8.85703457 C22.5164499,8.56389992 22,7.81804293 22,6.98492308 L22,4.44230769 C22,3.97925995 21.6402306,3.61538462 21.2056,3.61538462 L18.8007376,3.61537457 C18.3627516,3.61676247 18.0005181,3.98321188 18,4.44230769 L18,6.98411538 C18,7.81623999 17.4847654,8.56142419 16.7062494,8.85526793 L15.6668988,9.24756113 C14.379133,9.73412728 13.1803339,10.4341994 12.1197785,11.3191775 C12.1108094,11.3266617 11.8253748,11.564477 11.2634746,12.0326234 Z"/> <g transform="rotate(15 -47.892 66.043)"> <ellipse cx="6.4" cy="6.462" fill="#FFFFFF" rx="6.4" ry="6.462" transform="translate(.028 4.853)"/> <path fill="#FC6D26" d="M5.92153903,11.9125743 C2.3834711,11.9125743 -0.478460969,9.0231237 -0.478460969,5.4664205 C-0.478460969,1.9097173 2.3834711,-0.979733345 5.92153903,-0.979733345 C9.45960696,-0.979733345 12.321539,1.9097173 12.321539,5.4664205 C12.321539,9.0231237 9.45960696,11.9125743 5.92153903,11.9125743 Z M5.92153903,8.71257435 C7.6854047,8.71257435 9.12153903,7.26263103 9.12153903,5.4664205 C9.12153903,3.67020997 7.6854047,2.22026666 5.92153903,2.22026666 C4.15767337,2.22026666 2.72153903,3.67020997 2.72153903,5.4664205 C2.72153903,7.26263103 4.15767337,8.71257435 5.92153903,8.71257435 Z"/> diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb index 89ae17cef37..150788ca611 100644 --- a/app/workers/update_merge_requests_worker.rb +++ b/app/workers/update_merge_requests_worker.rb @@ -2,6 +2,10 @@ class UpdateMergeRequestsWorker include Sidekiq::Worker include DedicatedSidekiqQueue + def metrics_tags + @metrics_tags || {} + end + def perform(project_id, user_id, oldrev, newrev, ref) project = Project.find_by(id: project_id) return unless project @@ -9,6 +13,11 @@ class UpdateMergeRequestsWorker user = User.find_by(id: user_id) return unless user + @metrics_tags = { + project_id: project_id, + user_id: user_id + } + MergeRequests::RefreshService.new(project, user).execute(oldrev, newrev, ref) end end |