diff options
author | Luke "Jared" Bennett <lbennett@gitlab.com> | 2017-04-07 12:09:20 +0100 |
---|---|---|
committer | Luke "Jared" Bennett <lbennett@gitlab.com> | 2017-04-07 12:09:20 +0100 |
commit | 2f22890d4223c0b524ee1f27e222a24a8f0c9a87 (patch) | |
tree | 79609a077a37cc49d95463b649ff4098cea10b4d | |
parent | 907b754173ed4185574c38ded0a173360d073085 (diff) | |
parent | fb7b0d6e710d462f5bef1df3195fa8b051668bfc (diff) | |
download | gitlab-ce-2f22890d4223c0b524ee1f27e222a24a8f0c9a87.tar.gz |
Merge branch 'update-droplab-to-webpack-version' into new-resolvable-discussion
229 files changed, 1847 insertions, 1046 deletions
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js index a4629b092bf..e48d3344a2b 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.js +++ b/app/assets/javascripts/boards/components/issue_card_inner.js @@ -20,6 +20,7 @@ import eventHub from '../eventhub'; list: { type: Object, required: false, + default: () => ({}), }, rootPath: { type: String, @@ -31,6 +32,26 @@ import eventHub from '../eventhub'; default: false, }, }, + computed: { + cardUrl() { + return `${this.issueLinkBase}/${this.issue.id}`; + }, + assigneeUrl() { + return `${this.rootPath}${this.issue.assignee.username}`; + }, + assigneeUrlTitle() { + return `Assigned to ${this.issue.assignee.name}`; + }, + avatarUrlTitle() { + return `Avatar for ${this.issue.assignee.name}`; + }, + issueId() { + return `#${this.issue.id}`; + }, + showLabelFooter() { + return this.issue.labels.find(l => this.showLabel(l)) !== undefined; + }, + }, methods: { showLabel(label) { if (!this.list) return true; @@ -67,35 +88,41 @@ import eventHub from '../eventhub'; }, template: ` <div> - <h4 class="card-title"> - <i - class="fa fa-eye-slash confidential-icon" - v-if="issue.confidential"></i> - <a - :href="issueLinkBase + '/' + issue.id" - :title="issue.title"> - {{ issue.title }} - </a> - </h4> - <div class="card-footer"> - <span - class="card-number" - v-if="issue.id"> - #{{ issue.id }} - </span> + <div class="card-header"> + <h4 class="card-title"> + <i + class="fa fa-eye-slash confidential-icon" + v-if="issue.confidential" + aria-hidden="true" + /> + <a + class="js-no-trigger" + :href="cardUrl" + :title="issue.title">{{ issue.title }}</a> + <span + class="card-number" + v-if="issue.id" + > + {{ issueId }} + </span> + </h4> <a class="card-assignee has-tooltip js-no-trigger" - :href="rootPath + issue.assignee.username" - :title="'Assigned to ' + issue.assignee.name" + :href="assigneeUrl" + :title="assigneeUrlTitle" v-if="issue.assignee" - data-container="body"> + data-container="body" + > <img class="avatar avatar-inline s20 js-no-trigger" :src="issue.assignee.avatar" width="20" height="20" - :alt="'Avatar for ' + issue.assignee.name" /> + :alt="avatarUrlTitle" + /> </a> + </div> + <div class="card-footer" v-if="showLabelFooter"> <button class="label color-label has-tooltip js-no-trigger" v-for="label in issue.labels" diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js index a92e068ca5a..5f3ed9374bf 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js @@ -8,10 +8,8 @@ Vue.use(VueResource); /** * Commits View > Pipelines Tab > Pipelines Table. - * Merge Request View > Pipelines Tab > Pipelines Table. * * Renders Pipelines table in pipelines tab in the commits show view. - * Renders Pipelines table in pipelines tab in the merge request show view. */ $(() => { @@ -20,13 +18,14 @@ $(() => { gl.commits.pipelines = gl.commits.pipelines || {}; if (gl.commits.PipelinesTableBundle) { + document.querySelector('#commit-pipeline-table-view').removeChild(this.pipelinesTableBundle.$el); gl.commits.PipelinesTableBundle.$destroy(true); } const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view'); - gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable(); if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) { - gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl); + gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable().$mount(); + document.querySelector('#commit-pipeline-table-view').appendChild(gl.commits.pipelines.PipelinesTableBundle.$el); } }); diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js index 4d5a857d705..da9707549f9 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import Visibility from 'visibilityjs'; import PipelinesTableComponent from '../../vue_shared/components/pipelines_table'; import PipelinesService from '../../vue_pipelines_index/services/pipelines_service'; import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store'; @@ -7,6 +8,7 @@ import EmptyState from '../../vue_pipelines_index/components/empty_state'; import ErrorState from '../../vue_pipelines_index/components/error_state'; import '../../lib/utils/common_utils'; import '../../vue_shared/vue_resource_interceptor'; +import Poll from '../../lib/utils/poll'; /** * @@ -20,6 +22,7 @@ import '../../vue_shared/vue_resource_interceptor'; */ export default Vue.component('pipelines-table', { + components: { 'pipelines-table-component': PipelinesTableComponent, 'error-state': ErrorState, @@ -42,6 +45,7 @@ export default Vue.component('pipelines-table', { state: store.state, isLoading: false, hasError: false, + isMakingRequest: false, }; }, @@ -64,17 +68,41 @@ export default Vue.component('pipelines-table', { * */ beforeMount() { - this.endpoint = this.$el.dataset.endpoint; - this.helpPagePath = this.$el.dataset.helpPagePath; + const element = document.querySelector('#commit-pipeline-table-view'); + + this.endpoint = element.dataset.endpoint; + this.helpPagePath = element.dataset.helpPagePath; this.service = new PipelinesService(this.endpoint); - this.fetchPipelines(); + this.poll = new Poll({ + resource: this.service, + method: 'getPipelines', + successCallback: this.successCallback, + errorCallback: this.errorCallback, + notificationCallback: this.setIsMakingRequest, + }); + + if (!Visibility.hidden()) { + this.isLoading = true; + this.poll.makeRequest(); + } + + Visibility.change(() => { + if (!Visibility.hidden()) { + this.poll.restart(); + } else { + this.poll.stop(); + } + }); eventHub.$on('refreshPipelines', this.fetchPipelines); }, beforeUpdate() { - if (this.state.pipelines.length && this.$children) { + if (this.state.pipelines.length && + this.$children && + !this.isMakingRequest && + !this.isLoading) { this.store.startTimeAgoLoops.call(this, Vue); } }, @@ -83,21 +111,35 @@ export default Vue.component('pipelines-table', { eventHub.$off('refreshPipelines'); }, + destroyed() { + this.poll.stop(); + }, + methods: { fetchPipelines() { this.isLoading = true; + return this.service.getPipelines() - .then(response => response.json()) - .then((json) => { - // depending of the endpoint the response can either bring a `pipelines` key or not. - const pipelines = json.pipelines || json; - this.store.storePipelines(pipelines); - this.isLoading = false; - }) - .catch(() => { - this.hasError = true; - this.isLoading = false; - }); + .then(response => this.successCallback(response)) + .catch(() => this.errorCallback()); + }, + + successCallback(resp) { + const response = resp.json(); + + // depending of the endpoint the response can either bring a `pipelines` key or not. + const pipelines = response.pipelines || response; + this.store.storePipelines(pipelines); + this.isLoading = false; + }, + + errorCallback() { + this.hasError = true; + this.isLoading = false; + }, + + setIsMakingRequest(isMakingRequest) { + this.isMakingRequest = isMakingRequest; }, }, diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js index 6dbec50b890..ab9a8e43dd1 100644 --- a/app/assets/javascripts/copy_to_clipboard.js +++ b/app/assets/javascripts/copy_to_clipboard.js @@ -38,9 +38,35 @@ showTooltip = function(target, title) { }; $(function() { - var clipboard; - - clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]'); + const clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]'); clipboard.on('success', genericSuccess); - return clipboard.on('error', genericError); + clipboard.on('error', genericError); + + // This a workaround around ClipboardJS limitations to allow the context-specific copy/pasting of plain text or GFM. + // The Ruby `clipboard_button` helper sneaks a JSON hash with `text` and `gfm` keys into the `data-clipboard-text` + // attribute that ClipboardJS reads from. + // When ClipboardJS creates a new `textarea` (directly inside `body`, with a `readonly` attribute`), sets its value + // to the value of this data attribute, focusses on it, and finally programmatically issues the 'Copy' command, + // this code intercepts the copy command/event at the last minute to deconstruct this JSON hash and set the + // `text/plain` and `text/x-gfm` copy data types to the intended values. + $(document).on('copy', 'body > textarea[readonly]', function(e) { + const clipboardData = e.originalEvent.clipboardData; + if (!clipboardData) return; + + const text = e.target.value; + + let json; + try { + json = JSON.parse(text); + } catch (ex) { + return; + } + + if (!json.text || !json.gfm) return; + + e.preventDefault(); + + clipboardData.setData('text/plain', json.text); + clipboardData.setData('text/x-gfm', json.gfm); + }); }); diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 88180149715..5aa3eb46a69 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -13,10 +13,6 @@ class Diff { $diffFile.each((index, file) => new gl.ImageFile(file)); - if (this.diffViewType() === 'parallel') { - $('.content-wrapper .container-fluid').removeClass('container-limited'); - } - if (!isBound) { $(document) .on('click', '.js-unfold', this.handleClickUnfold.bind(this)) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 307c6a306d1..0a1a62fb012 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -24,7 +24,6 @@ /* global Search */ /* global Admin */ /* global NamespaceSelects */ -/* global ShortcutsDashboardNavigation */ /* global Project */ /* global ProjectAvatar */ /* global CompareAutocomplete */ @@ -378,7 +377,6 @@ const ShortcutsBlob = require('./shortcuts_blob'); break; case 'dashboard': case 'root': - shortcut_handler = new ShortcutsDashboardNavigation(); new UserCallout(); break; case 'groups': diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.js b/app/assets/javascripts/environments/folder/environments_folder_view.js index 8abbcf0c227..d2514593e3a 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.js +++ b/app/assets/javascripts/environments/folder/environments_folder_view.js @@ -31,12 +31,6 @@ export default Vue.component('environment-folder-view', { cssContainerClass: environmentsData.cssClass, canCreateDeployment: environmentsData.canCreateDeployment, canReadEnvironment: environmentsData.canReadEnvironment, - - // svgs - commitIconSvg: environmentsData.commitIconSvg, - playIconSvg: environmentsData.playIconSvg, - terminalIconSvg: environmentsData.terminalIconSvg, - // Pagination Properties, paginationInformation: {}, pageNumber: 1, @@ -163,9 +157,6 @@ export default Vue.component('environment-folder-view', { :environments="state.environments" :can-create-deployment="canCreateDeploymentParsed" :can-read-environment="canReadEnvironmentParsed" - :play-icon-svg="playIconSvg" - :terminal-icon-svg="terminalIconSvg" - :commit-icon-svg="commitIconSvg" :service="service"/> <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1" diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js index 79314dd9c6f..381c40c03d8 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js @@ -55,6 +55,7 @@ require('./filtered_search_dropdown'); renderContent() { const dropdownData = []; + [].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => { const { icon, hint, tag, type } = dropdownMenu.dataset; if (icon && hint && tag) { diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js index 5c22aea51cd..e31cc5fbabe 100644 --- a/app/assets/javascripts/lib/utils/poll.js +++ b/app/assets/javascripts/lib/utils/poll.js @@ -65,7 +65,6 @@ export default class Poll { this.makeRequest(); }, pollInterval); } - this.options.successCallback(response); } @@ -76,8 +75,14 @@ export default class Poll { notificationCallback(true); return resource[method](data) - .then(response => this.checkConditions(response)) - .catch(error => errorCallback(error)); + .then((response) => { + this.checkConditions(response); + notificationCallback(false); + }) + .catch((error) => { + notificationCallback(false); + errorCallback(error); + }); } /** diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 3c4e6102469..8528e0800ae 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -90,6 +90,7 @@ import './flash'; .on('click', this.clickTab); } + // Used in tests unbindEvents() { $(document) .off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown) @@ -99,9 +100,11 @@ import './flash'; .off('click', this.clickTab); } - destroy() { - this.unbindEvents(); + destroyPipelinesView() { if (this.commitPipelinesTable) { + document.querySelector('#commit-pipeline-table-view') + .removeChild(this.commitPipelinesTable.$el); + this.commitPipelinesTable.$destroy(); } } @@ -128,6 +131,7 @@ import './flash'; this.loadCommits($target.attr('href')); this.expandView(); this.resetViewContainer(); + this.destroyPipelinesView(); } else if (this.isDiffAction(action)) { this.loadDiff($target.attr('href')); if (Breakpoints.get().getBreakpointSize() !== 'lg') { @@ -136,12 +140,14 @@ import './flash'; if (this.diffViewType() === 'parallel') { this.expandViewContainer(); } + this.destroyPipelinesView(); } else if (action === 'pipelines') { this.resetViewContainer(); - this.loadPipelines(); + this.mountPipelinesView(); } else { this.expandView(); this.resetViewContainer(); + this.destroyPipelinesView(); } if (this.setUrl) { this.setCurrentAction(action); @@ -227,16 +233,12 @@ import './flash'; }); } - loadPipelines() { - if (this.pipelinesLoaded) { - return; - } - const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view'); - // Could already be mounted from the `pipelines_bundle` - if (pipelineTableViewEl) { - this.commitPipelinesTable = new CommitPipelinesTable().$mount(pipelineTableViewEl); - } - this.pipelinesLoaded = true; + mountPipelinesView() { + this.commitPipelinesTable = new CommitPipelinesTable().$mount(); + // $mount(el) replaces the el with the new rendered component. We need it in order to mount + // it everytime this tab is clicked - https://vuejs.org/v2/api/#vm-mount + document.querySelector('#commit-pipeline-table-view') + .appendChild(this.commitPipelinesTable.$el); } loadDiff(source) { diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js index fd5097696ad..5b6bb2bf3f5 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/shortcuts.js @@ -1,6 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */ /* global Mousetrap */ /* global findFileURL */ +import findAndFollowLink from './shortcuts_dashboard_navigation'; (function() { var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; @@ -14,11 +15,33 @@ } Mousetrap.bind('?', this.onToggleHelp); Mousetrap.bind('s', Shortcuts.focusSearch); - Mousetrap.bind('f', (function(_this) { - return function(e) { - return _this.focusFilter(e); - }; - })(this)); + Mousetrap.bind('f', (e => this.focusFilter(e))); + + const $globalDropdownMenu = $('.global-dropdown-menu'); + const $globalDropdownToggle = $('.global-dropdown-toggle'); + + $('.global-dropdown').on('hide.bs.dropdown', () => { + $globalDropdownMenu.removeClass('shortcuts'); + }); + + Mousetrap.bind('n', () => { + $globalDropdownMenu.toggleClass('shortcuts'); + $globalDropdownToggle.trigger('click'); + + if (!$globalDropdownMenu.is(':visible')) { + $globalDropdownToggle.blur(); + } + }); + + Mousetrap.bind('shift+t', () => findAndFollowLink('.shortcuts-todos')); + Mousetrap.bind('shift+a', () => findAndFollowLink('.dashboard-shortcuts-activity')); + Mousetrap.bind('shift+i', () => findAndFollowLink('.dashboard-shortcuts-issues')); + Mousetrap.bind('shift+m', () => findAndFollowLink('.dashboard-shortcuts-merge_requests')); + Mousetrap.bind('shift+p', () => findAndFollowLink('.dashboard-shortcuts-projects')); + Mousetrap.bind('shift+g', () => findAndFollowLink('.dashboard-shortcuts-groups')); + Mousetrap.bind('shift+l', () => findAndFollowLink('.dashboard-shortcuts-milestones')); + Mousetrap.bind('shift+s', () => findAndFollowLink('.dashboard-shortcuts-snippets')); + Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], this.toggleMarkdownPreview); if (typeof findFileURL !== "undefined" && findFileURL !== null) { Mousetrap.bind('t', function() { diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js b/app/assets/javascripts/shortcuts_dashboard_navigation.js index 4f1a19924a4..25f39e4fdb6 100644 --- a/app/assets/javascripts/shortcuts_dashboard_navigation.js +++ b/app/assets/javascripts/shortcuts_dashboard_navigation.js @@ -1,43 +1,12 @@ -/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */ -/* global Mousetrap */ -/* global Shortcuts */ - -require('./shortcuts'); - -(function() { - var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty; - - this.ShortcutsDashboardNavigation = (function(superClass) { - extend(ShortcutsDashboardNavigation, superClass); - - function ShortcutsDashboardNavigation() { - ShortcutsDashboardNavigation.__super__.constructor.call(this); - Mousetrap.bind('g a', function() { - return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-activity'); - }); - Mousetrap.bind('g i', function() { - return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-issues'); - }); - Mousetrap.bind('g m', function() { - return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-merge_requests'); - }); - Mousetrap.bind('g t', function() { - return ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-todos'); - }); - Mousetrap.bind('g p', function() { - return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-projects'); - }); - } - - ShortcutsDashboardNavigation.findAndFollowLink = function(selector) { - var link; - link = $(selector).attr('href'); - if (link) { - return window.location = link; - } - }; - - return ShortcutsDashboardNavigation; - })(Shortcuts); -}).call(window); +/** + * Helper function that finds the href of the fiven selector and updates the location. + * + * @param {String} selector + */ +export default (selector) => { + const link = document.querySelector(selector).getAttribute('href'); + + if (link) { + window.location = link; + } +}; diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js index 3f5d6724417..c74ab0afd0c 100644 --- a/app/assets/javascripts/shortcuts_navigation.js +++ b/app/assets/javascripts/shortcuts_navigation.js @@ -1,6 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */ /* global Mousetrap */ /* global Shortcuts */ +import findAndFollowLink from './shortcuts_dashboard_navigation'; require('./shortcuts'); @@ -13,59 +14,23 @@ require('./shortcuts'); function ShortcutsNavigation() { ShortcutsNavigation.__super__.constructor.call(this); - Mousetrap.bind('g p', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-project'); - }); - Mousetrap.bind('g e', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'); - }); - Mousetrap.bind('g f', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'); - }); - Mousetrap.bind('g c', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'); - }); - Mousetrap.bind('g b', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-builds'); - }); - Mousetrap.bind('g n', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-network'); - }); - Mousetrap.bind('g g', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-repository-charts'); - }); - Mousetrap.bind('g i', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'); - }); - Mousetrap.bind('g l', function() { - ShortcutsNavigation.findAndFollowLink('.shortcuts-issue-boards'); - }); - Mousetrap.bind('g m', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'); - }); - Mousetrap.bind('g t', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-todos'); - }); - Mousetrap.bind('g w', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki'); - }); - Mousetrap.bind('g s', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets'); - }); - Mousetrap.bind('i', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue'); - }); + Mousetrap.bind('g p', () => findAndFollowLink('.shortcuts-project')); + Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-project-activity')); + Mousetrap.bind('g f', () => findAndFollowLink('.shortcuts-tree')); + Mousetrap.bind('g c', () => findAndFollowLink('.shortcuts-commits')); + Mousetrap.bind('g j', () => findAndFollowLink('.shortcuts-builds')); + Mousetrap.bind('g n', () => findAndFollowLink('.shortcuts-network')); + Mousetrap.bind('g d', () => findAndFollowLink('.shortcuts-repository-charts')); + Mousetrap.bind('g i', () => findAndFollowLink('.shortcuts-issues')); + Mousetrap.bind('g b', () => findAndFollowLink('.shortcuts-issue-boards')); + Mousetrap.bind('g m', () => findAndFollowLink('.shortcuts-merge_requests')); + Mousetrap.bind('g t', () => findAndFollowLink('.shortcuts-todos')); + Mousetrap.bind('g w', () => findAndFollowLink('.shortcuts-wiki')); + Mousetrap.bind('g s', () => findAndFollowLink('.shortcuts-snippets')); + Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue')); this.enabledHelp.push('.hidden-shortcut.project'); } - ShortcutsNavigation.findAndFollowLink = function(selector) { - var link; - link = $(selector).attr('href'); - if (link) { - return window.location = link; - } - }; - return ShortcutsNavigation; })(Shortcuts); }).call(window); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js b/app/assets/javascripts/vue_pipelines_index/pipelines.js index 9bdc232b7da..5575aa72d5e 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import Visibility from 'visibilityjs'; import PipelinesService from './services/pipelines_service'; import eventHub from './event_hub'; import PipelinesTableComponent from '../vue_shared/components/pipelines_table'; @@ -7,6 +8,7 @@ import EmptyState from './components/empty_state'; import ErrorState from './components/error_state'; import NavigationTabs from './components/navigation_tabs'; import NavigationControls from './components/nav_controls'; +import Poll from '../lib/utils/poll'; export default { props: { @@ -47,6 +49,7 @@ export default { pagenum: 1, isLoading: false, hasError: false, + isMakingRequest: false, }; }, @@ -120,18 +123,49 @@ export default { tagsPath: this.tagsPath, }; }, + + pageParameter() { + return gl.utils.getParameterByName('page') || this.pagenum; + }, + + scopeParameter() { + return gl.utils.getParameterByName('scope') || this.apiScope; + }, }, created() { this.service = new PipelinesService(this.endpoint); - this.fetchPipelines(); + const poll = new Poll({ + resource: this.service, + method: 'getPipelines', + data: { page: this.pageParameter, scope: this.scopeParameter }, + successCallback: this.successCallback, + errorCallback: this.errorCallback, + notificationCallback: this.setIsMakingRequest, + }); + + if (!Visibility.hidden()) { + this.isLoading = true; + poll.makeRequest(); + } + + Visibility.change(() => { + if (!Visibility.hidden()) { + poll.restart(); + } else { + poll.stop(); + } + }); eventHub.$on('refreshPipelines', this.fetchPipelines); }, beforeUpdate() { - if (this.state.pipelines.length && this.$children) { + if (this.state.pipelines.length && + this.$children && + !this.isMakingRequest && + !this.isLoading) { this.store.startTimeAgoLoops.call(this, Vue); } }, @@ -154,27 +188,35 @@ export default { }, fetchPipelines() { - const pageNumber = gl.utils.getParameterByName('page') || this.pagenum; - const scope = gl.utils.getParameterByName('scope') || this.apiScope; + if (!this.isMakingRequest) { + this.isLoading = true; - this.isLoading = true; - return this.service.getPipelines(scope, pageNumber) - .then(resp => ({ - headers: resp.headers, - body: resp.json(), - })) - .then((response) => { - this.store.storeCount(response.body.count); - this.store.storePipelines(response.body.pipelines); - this.store.storePagination(response.headers); - }) - .then(() => { - this.isLoading = false; - }) - .catch(() => { - this.hasError = true; - this.isLoading = false; - }); + this.service.getPipelines({ scope: this.scopeParameter, page: this.pageParameter }) + .then(response => this.successCallback(response)) + .catch(() => this.errorCallback()); + } + }, + + successCallback(resp) { + const response = { + headers: resp.headers, + body: resp.json(), + }; + + this.store.storeCount(response.body.count); + this.store.storePipelines(response.body.pipelines); + this.store.storePagination(response.headers); + + this.isLoading = false; + }, + + errorCallback() { + this.hasError = true; + this.isLoading = false; + }, + + setIsMakingRequest(isMakingRequest) { + this.isMakingRequest = isMakingRequest; }, }, diff --git a/app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js b/app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js index 708f5068dd3..255cd513490 100644 --- a/app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js +++ b/app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js @@ -26,7 +26,8 @@ export default class PipelinesService { this.pipelines = Vue.resource(endpoint); } - getPipelines(scope, page) { + getPipelines(data = {}) { + const { scope, page } = data; return this.pipelines.get({ scope, page }); } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 23cba57f83a..7767826b033 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -187,6 +187,15 @@ } } + .shortcut-mappings { + display: none; + } + + &.shortcuts .shortcut-mappings { + display: inline-block; + margin-right: 5px; + } + ul { margin: 0; padding: 0; diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 8be2e3fc70c..12465d4a70b 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -446,10 +446,8 @@ } } -.filter-dropdown-item.droplab-item-active { - .btn { - @extend %filter-dropdown-item-btn-hover; - } +.filter-dropdown-item.droplab-item-active .btn { + @extend %filter-dropdown-item-btn-hover; } .filter-dropdown-loading { diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index ff185cd8767..cd23deb6d75 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -1,15 +1,18 @@ .timeline { @include basic-list; - margin: 0; padding: 0; .timeline-entry { - padding: $gl-padding $gl-btn-padding 11px; + padding: $gl-padding $gl-btn-padding 14px; border-color: $white-normal; color: $gl-text-color; border-bottom: 1px solid $border-white-light; + .timeline-entry-inner { + position: relative; + } + &:target { background: $line-target-blue; } diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 7c0fc1008d0..0be1c215959 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -197,7 +197,7 @@ .card { position: relative; - padding: 10px $gl-padding; + padding: 11px 10px 11px $gl-padding; background: $white-light; border-radius: $border-radius-default; box-shadow: 0 1px 2px $issue-boards-card-shadow; @@ -217,6 +217,8 @@ } .confidential-icon { + position: relative; + top: 1px; margin-right: 5px; } } @@ -224,34 +226,43 @@ .card-title { margin: 0; font-size: 1em; + line-height: inherit; a { - color: inherit; + color: $gl-text-color; word-wrap: break-word; + margin-right: 2px; } } -.card-footer { - margin-top: 5px; - line-height: 25px; - - .label { - margin-right: 5px; - font-size: (14px / $issue-boards-font-size) * 1em; - } +.card-header { + display: flex; + min-height: 20px; .card-assignee { + margin-left: auto; margin-right: 5px; + padding-left: 10px; + height: 20px; } .avatar { - margin-left: 0; - margin-right: 0; + margin: 0; + } +} + +.card-footer { + margin: 0 0 5px; + + .label { + margin-top: 5px; + margin-right: 6px; } } .card-number { - margin-right: 5px; + font-size: 12px; + color: $gl-text-color-secondary; } .issue-boards-search { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 191e3ded8c3..c1be78fcd6e 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -16,6 +16,15 @@ ul.notes { .timeline-icon { float: left; + + svg { + width: 18px; + height: auto; + fill: $gray-darkest; + position: absolute; + left: 30px; + top: 15px; + } } .timeline-content { @@ -33,6 +42,103 @@ ul.notes { white-space: nowrap; } + .discussion-body { + padding-top: 15px; + } + + .discussion { + overflow: hidden; + display: block; + position: relative; + } + + .note { + display: block; + position: relative; + border-bottom: 1px solid $white-normal; + + &.note-discussion { + &.timeline-entry { + padding: 14px 10px; + } + + .system-note { + padding: 0; + } + } + + &.is-editting { + .note-header, + .note-text, + .edited-text { + display: none; + } + + .note-edit-form { + display: block; + + &.current-note-edit-form + .note-awards { + display: none; + } + } + } + + .note-body { + overflow-x: auto; + overflow-y: hidden; + + .note-text { + word-wrap: break-word; + @include md-typography; + // Reset ul style types since we're nested inside a ul already + @include bulleted-list; + ul.task-list { + ul:not(.task-list) { + padding-left: 1.3em; + } + } + } + } + + .note-awards { + .js-awards-block { + padding: 2px; + margin-top: 10px; + } + } + + .note-header { + padding-bottom: 3px; + padding-right: 20px; + + @media (min-width: $screen-sm-min) { + padding-right: 0; + } + + @media (max-width: $screen-xs-min) { + .inline { + display: block; + } + } + } + + .note-emoji-button { + .fa-spinner { + display: none; + } + + &.is-loading { + .fa-smile-o { + display: none; + } + + .fa-spinner { + display: inline-block; + } + } + } + } + .system-note { font-size: 14px; padding: 0; @@ -68,6 +174,10 @@ ul.notes { padding: 14px 10px; } + .note-header { + padding-bottom: 0; + } + .note-body { overflow: hidden; @@ -130,116 +240,6 @@ ul.notes { } } } - - .timeline-icon { - display: none; - - .avatar { - visibility: hidden; - - .discussion-body & { - visibility: visible; - } - } - } - } - - .discussion-body { - padding-top: 15px; - } - - .discussion { - overflow: hidden; - display: block; - position: relative; - } - - .note { - display: block; - position: relative; - border-bottom: 1px solid $white-normal; - - &.note-discussion { - &.timeline-entry { - padding: 14px 10px; - } - - .system-note { - padding: 0; - } - } - - &.is-editting { - .note-header, - .note-text, - .edited-text { - display: none; - } - - .note-edit-form { - display: block; - - &.current-note-edit-form + .note-awards { - display: none; - } - } - } - - .note-body { - overflow-x: auto; - overflow-y: hidden; - - .note-text { - word-wrap: break-word; - @include md-typography; - // Reset ul style types since we're nested inside a ul already - @include bulleted-list; - ul.task-list { - ul:not(.task-list) { - padding-left: 1.3em; - } - } - } - } - - .note-awards { - .js-awards-block { - padding: 2px; - margin-top: 10px; - } - } - - .note-header { - padding-bottom: 3px; - padding-right: 20px; - - @media (min-width: $screen-sm-min) { - padding-right: 0; - } - - @media (max-width: $screen-xs-min) { - .inline { - display: block; - } - } - } - - .note-emoji-button { - .fa-spinner { - display: none; - } - - &.is-loading { - .fa-smile-o { - display: none; - } - - .fa-spinner { - display: inline-block; - } - } - } - } } diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 8362bfc39dd..d25bbddd1bb 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -36,6 +36,8 @@ class Projects::CommitController < Projects::ApplicationController respond_to do |format| format.html format.json do + Gitlab::PollingInterval.set_header(response, interval: 10_000) + render json: PipelineSerializer .new(project: @project, user: @current_user) .represent(@pipelines) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index f5bc75d02c9..5c1f7e69ee8 100755 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -233,6 +233,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController end format.json do + Gitlab::PollingInterval.set_header(response, interval: 10_000) + render json: PipelineSerializer .new(project: @project, user: @current_user) .represent(@pipelines) @@ -246,6 +248,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController format.json do define_pipelines_vars + Gitlab::PollingInterval.set_header(response, interval: 10_000) + render json: { pipelines: PipelineSerializer .new(project: @project, user: @current_user) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 43a1abaa662..1780cc0233c 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -29,6 +29,8 @@ class Projects::PipelinesController < Projects::ApplicationController respond_to do |format| format.html format.json do + Gitlab::PollingInterval.set_header(response, interval: 10_000) + render json: { pipelines: PipelineSerializer .new(project: @project, user: @current_user) @@ -114,7 +116,7 @@ class Projects::PipelinesController < Projects::ApplicationController end def pipeline - @pipeline ||= project.pipelines.find_by!(id: params[:id]) + @pipeline ||= project.pipelines.find_by!(id: params[:id]).present(current_user: current_user) end def commit diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb index c8c80551ac9..ff50602831c 100644 --- a/app/controllers/projects/pipelines_settings_controller.rb +++ b/app/controllers/projects/pipelines_settings_controller.rb @@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController def update_params params.require(:project).permit( :runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, - :public_builds + :public_builds, :auto_cancel_pending_pipelines ) end end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 91d6d1852cf..b0ac1623fbe 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -102,7 +102,7 @@ module BlobHelper if Gitlab::MarkupHelper.previewable?(filename) 'Preview' else - 'Preview Changes' + 'Preview changes' end end @@ -210,13 +210,13 @@ module BlobHelper end def copy_file_path_button(file_path) - clipboard_button(clipboard_text: file_path, class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy file path to clipboard') + clipboard_button(text: file_path, gfm: "`#{file_path}`", class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy file path to clipboard') end def copy_blob_content_button(blob) return if markup?(blob.name) - clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{blob.id}']", class: "btn btn-sm", title: "Copy content to clipboard") + clipboard_button(target: ".blob-content[data-blob-id='#{blob.id}']", class: "btn btn-sm", title: "Copy content to clipboard") end def open_raw_file_button(path) diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 0b30471f2ae..c85e96cf78d 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -1,23 +1,42 @@ module ButtonHelper # Output a "Copy to Clipboard" button # - # data - Data attributes passed to `content_tag` + # data - Data attributes passed to `content_tag` (default: {}): + # :text - Text to copy (optional) + # :gfm - GitLab Flavored Markdown to copy, if different from `text` (optional) + # :target - Selector for target element to copy from (optional) # # Examples: # # # Define the clipboard's text - # clipboard_button(clipboard_text: "Foo") + # clipboard_button(text: "Foo") # # => "<button class='...' data-clipboard-text='Foo'>...</button>" # # # Define the target element - # clipboard_button(clipboard_target: "div#foo") + # clipboard_button(target: "div#foo") # # => "<button class='...' data-clipboard-target='div#foo'>...</button>" # # See http://clipboardjs.com/#usage def clipboard_button(data = {}) css_class = data[:class] || 'btn-clipboard btn-transparent' title = data[:title] || 'Copy to clipboard' + + # This supports code in app/assets/javascripts/copy_to_clipboard.js that + # works around ClipboardJS limitations to allow the context-specific copy/pasting of plain text or GFM. + if text = data.delete(:text) + data[:clipboard_text] = + if gfm = data.delete(:gfm) + { text: text, gfm: gfm } + else + text + end + end + + target = data.delete(:target) + data[:clipboard_target] = target if target + data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) + content_tag :button, icon('clipboard', 'aria-hidden': 'true'), class: "btn #{css_class}", diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb new file mode 100644 index 00000000000..3074921caff --- /dev/null +++ b/app/helpers/system_note_helper.rb @@ -0,0 +1,26 @@ +module SystemNoteHelper + ICON_NAMES_BY_ACTION = { + 'commit' => 'icon_commit', + 'merge' => 'icon_merge', + 'merged' => 'icon_merged', + 'opened' => 'icon_status_open', + 'closed' => 'icon_status_closed', + 'time_tracking' => 'icon_stopwatch', + 'assignee' => 'icon_user', + 'title' => 'icon_pencil', + 'task' => 'icon_check_square_o', + 'label' => 'icon_tags', + 'cross_reference' => 'icon_random', + 'branch' => 'icon_code_fork', + 'confidential' => 'icon_eye_slash', + 'visible' => 'icon_eye', + 'milestone' => 'icon_clock_o', + 'discussion' => 'icon_comment_o', + 'moved' => 'icon_arrow_circle_o_right' + }.freeze + + def icon_for_system_note(note) + icon_name = ICON_NAMES_BY_ACTION[note.system_note_metadata&.action] + custom_icon(icon_name) if icon_name + end +end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 70470414bc4..b426c27afbb 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -103,18 +103,13 @@ module Ci end def playable? - project.builds_enabled? && has_commands? && - action? && manual? + action? && manual? end def action? self.when == 'manual' end - def has_commands? - commands.present? - end - def play(current_user) # Try to queue a current build if self.enqueue @@ -131,8 +126,7 @@ module Ci end def retryable? - project.builds_enabled? && has_commands? && - (success? || failed? || canceled?) + success? || failed? || canceled? end def retried? diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 49dec770096..37a81fa7781 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -4,14 +4,25 @@ module Ci include HasStatus include Importable include AfterCommitQueue + include Presentable belongs_to :project belongs_to :user + belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' + + has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id' + has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id' has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id has_many :builds, foreign_key: :commit_id has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id + has_many :pending_builds, -> { pending }, foreign_key: :commit_id, class_name: 'Ci::Build' + has_many :retryable_builds, -> { latest.failed_or_canceled }, foreign_key: :commit_id, class_name: 'Ci::Build' + has_many :cancelable_statuses, -> { cancelable }, foreign_key: :commit_id, class_name: 'CommitStatus' + has_many :manual_actions, -> { latest.manual_actions }, foreign_key: :commit_id, class_name: 'Ci::Build' + has_many :artifacts, -> { latest.with_artifacts_not_expired }, foreign_key: :commit_id, class_name: 'Ci::Build' + delegate :id, to: :project, prefix: true validates :sha, presence: { unless: :importing? } @@ -65,6 +76,10 @@ module Ci pipeline.update_duration end + before_transition canceled: any - [:canceled] do |pipeline| + pipeline.auto_canceled_by = nil + end + after_transition [:created, :pending] => :running do |pipeline| pipeline.run_after_commit { PipelineMetricsWorker.perform_async(id) } end @@ -82,6 +97,8 @@ module Ci pipeline.run_after_commit do PipelineHooksWorker.perform_async(id) + Ci::ExpirePipelineCacheService.new(project, nil) + .execute(pipeline) end end @@ -160,10 +177,6 @@ module Ci end end - def artifacts - builds.latest.with_artifacts_not_expired.includes(project: [:namespace]) - end - def valid_commit_sha if self.sha == Gitlab::Git::BLANK_SHA self.errors.add(:sha, " cant be 00000000 (branch removal)") @@ -200,27 +213,37 @@ module Ci !tag? end - def manual_actions - builds.latest.manual_actions.includes(project: [:namespace]) - end - def stuck? - builds.pending.includes(:project).any?(&:stuck?) + pending_builds.any?(&:stuck?) end def retryable? - builds.latest.failed_or_canceled.any?(&:retryable?) + retryable_builds.any? end def cancelable? - statuses.cancelable.any? + cancelable_statuses.any? + end + + def auto_canceled? + canceled? && auto_canceled_by_id? end def cancel_running - Gitlab::OptimisticLocking.retry_lock( - statuses.cancelable) do |cancelable| - cancelable.find_each(&:cancel) + Gitlab::OptimisticLocking.retry_lock(cancelable_statuses) do |cancelable| + cancelable.find_each do |job| + yield(job) if block_given? + job.cancel end + end + end + + def auto_cancel_running(pipeline) + update(auto_canceled_by: pipeline) + + cancel_running do |job| + job.auto_canceled_by = pipeline + end end def retry_failed(current_user) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 17b322b5ae3..2c4033146bf 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -7,6 +7,7 @@ class CommitStatus < ActiveRecord::Base belongs_to :project belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id + belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :user delegate :commit, to: :pipeline @@ -137,6 +138,10 @@ class CommitStatus < ActiveRecord::Base false end + def auto_canceled? + canceled? && auto_canceled_by_id? + end + # Added in 9.0 to keep backward compatibility for projects exported in 8.17 # and prior. def gl_project_id diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 0a1a65da05a..2f61709110c 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -76,6 +76,7 @@ module HasStatus scope :canceled, -> { where(status: 'canceled') } scope :skipped, -> { where(status: 'skipped') } scope :manual, -> { where(status: 'manual') } + scope :created_or_pending, -> { where(status: [:created, :pending]) } scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) } scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) } diff --git a/app/models/project.rb b/app/models/project.rb index 19835a72a75..639615b91a2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -172,6 +172,8 @@ class Project < ActiveRecord::Base has_many :environments, dependent: :destroy has_many :deployments, dependent: :destroy + has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' + accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :project_feature @@ -260,6 +262,8 @@ class Project < ActiveRecord::Base scope :with_builds_enabled, -> { with_feature_enabled(:builds) } scope :with_issues_enabled, -> { with_feature_enabled(:issues) } + enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 } + # project features may be "disabled", "internal" or "enabled". If "internal", # they are only available to team members. This scope returns projects where # the feature is either enabled, or internal with permission for the user. @@ -1085,15 +1089,15 @@ class Project < ActiveRecord::Base end def shared_runners - shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none + @shared_runners ||= shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none end - def any_runners?(&block) - if runners.active.any?(&block) - return true - end + def active_shared_runners + @active_shared_runners ||= shared_runners.active + end - shared_runners.active.any?(&block) + def any_runners?(&block) + active_runners.any?(&block) || active_shared_runners.any?(&block) end def valid_runners_token?(token) diff --git a/app/presenters/ci/build_presenter.rb b/app/presenters/ci/build_presenter.rb index ed72ed14d72..c495c3f39bb 100644 --- a/app/presenters/ci/build_presenter.rb +++ b/app/presenters/ci/build_presenter.rb @@ -11,5 +11,11 @@ module Ci def erased_by_name erased_by.name if erased_by_user? end + + def status_title + if auto_canceled? + "Job is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}" + end + end end end diff --git a/app/presenters/ci/pipeline_presenter.rb b/app/presenters/ci/pipeline_presenter.rb new file mode 100644 index 00000000000..a542bdd8295 --- /dev/null +++ b/app/presenters/ci/pipeline_presenter.rb @@ -0,0 +1,11 @@ +module Ci + class PipelinePresenter < Gitlab::View::Presenter::Delegated + presents :pipeline + + def status_title + if auto_canceled? + "Pipeline is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}" + end + end + end +end diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 3f16dd66d54..ad8b4d43e8f 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -69,13 +69,13 @@ class PipelineEntity < Grape::Entity alias_method :pipeline, :object def can_retry? - pipeline.retryable? && - can?(request.user, :update_pipeline, pipeline) + can?(request.user, :update_pipeline, pipeline) && + pipeline.retryable? end def can_cancel? - pipeline.cancelable? && - can?(request.user, :update_pipeline, pipeline) + can?(request.user, :update_pipeline, pipeline) && + pipeline.cancelable? end def detailed_status diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index 7829df9fada..e7a9df8ac4e 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -13,7 +13,15 @@ class PipelineSerializer < BaseSerializer def represent(resource, opts = {}) if resource.is_a?(ActiveRecord::Relation) - resource = resource.includes(project: :namespace) + resource = resource.preload([ + :retryable_builds, + :cancelable_statuses, + :trigger_requests, + :project, + { pending_builds: :project }, + { manual_actions: :project }, + { artifacts: :project } + ]) end if paginated? diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 38a85e9fc42..21350be5557 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -53,6 +53,8 @@ module Ci .execute(pipeline) end + cancel_pending_pipelines if project.auto_cancel_pending_pipelines? + pipeline.tap(&:process!) end @@ -63,6 +65,22 @@ module Ci pipeline.git_commit_message =~ /\[(ci[ _-]skip|skip[ _-]ci)\]/i end + def cancel_pending_pipelines + Gitlab::OptimisticLocking.retry_lock(auto_cancelable_pipelines) do |cancelables| + cancelables.find_each do |cancelable| + cancelable.auto_cancel_running(pipeline) + end + end + end + + def auto_cancelable_pipelines + project.pipelines + .where(ref: pipeline.ref) + .where.not(id: pipeline.id) + .where.not(sha: project.repository.sha_from_ref(pipeline.ref)) + .created_or_pending + end + def commit @commit ||= project.commit(origin_sha || origin_ref) end diff --git a/app/services/ci/expire_pipeline_cache_service.rb b/app/services/ci/expire_pipeline_cache_service.rb new file mode 100644 index 00000000000..c0e4a798f6a --- /dev/null +++ b/app/services/ci/expire_pipeline_cache_service.rb @@ -0,0 +1,49 @@ +module Ci + class ExpirePipelineCacheService < BaseService + attr_reader :pipeline + + def execute(pipeline) + @pipeline = pipeline + store = Gitlab::EtagCaching::Store.new + + store.touch(project_pipelines_path) + store.touch(commit_pipelines_path) if pipeline.commit + store.touch(new_merge_request_pipelines_path) + merge_requests_pipelines_paths.each { |path| store.touch(path) } + end + + private + + def project_pipelines_path + Gitlab::Routing.url_helpers.namespace_project_pipelines_path( + project.namespace, + project, + format: :json) + end + + def commit_pipelines_path + Gitlab::Routing.url_helpers.pipelines_namespace_project_commit_path( + project.namespace, + project, + pipeline.commit.id, + format: :json) + end + + def new_merge_request_pipelines_path + Gitlab::Routing.url_helpers.new_namespace_project_merge_request_path( + project.namespace, + project, + format: :json) + end + + def merge_requests_pipelines_paths + pipeline.merge_requests.collect do |merge_request| + Gitlab::Routing.url_helpers.pipelines_namespace_project_merge_request_path( + project.namespace, + project, + merge_request, + format: :json) + end + end + end +end diff --git a/app/services/ci/retry_pipeline_service.rb b/app/services/ci/retry_pipeline_service.rb index f72ddbf690c..ecc6173a96a 100644 --- a/app/services/ci/retry_pipeline_service.rb +++ b/app/services/ci/retry_pipeline_service.rb @@ -7,9 +7,7 @@ module Ci raise Gitlab::Access::AccessDeniedError end - pipeline.builds.latest.failed_or_canceled.find_each do |build| - next unless build.retryable? - + pipeline.retryable_builds.find_each do |build| Ci::RetryBuildService.new(project, current_user) .reprocess(build) end diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml index 05f3d9a3b50..18c6c559049 100644 --- a/app/views/admin/abuse_reports/_abuse_report.html.haml +++ b/app/views/admin/abuse_reports/_abuse_report.html.haml @@ -30,5 +30,5 @@ = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-sm btn-block" - else .btn.btn-sm.disabled.btn-block - Already Blocked + Already blocked = link_to 'Remove report', [:admin, abuse_report], remote: true, method: :delete, class: "btn btn-sm btn-block btn-close js-remove-tr" diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 5d51a2b5cbc..703f611bb45 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -148,7 +148,7 @@ Sign-in enabled - if omniauth_enabled? && button_based_providers.any? .form-group - = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth Sign-In sources', class: 'control-label col-sm-2' + = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2' .col-sm-10 .btn-group{ data: { toggle: 'buttons' } } - oauth_providers_checkboxes.each do |source| diff --git a/app/views/admin/applications/index.html.haml b/app/views/admin/applications/index.html.haml index b3a3b4c1d45..eb4293c7e37 100644 --- a/app/views/admin/applications/index.html.haml +++ b/app/views/admin/applications/index.html.haml @@ -4,7 +4,7 @@ %p.light System OAuth applications don't belong to any user and can only be managed by admins %hr -%p= link_to 'New Application', new_admin_application_path, class: 'btn btn-success' +%p= link_to 'New application', new_admin_application_path, class: 'btn btn-success' %table.table.table-striped %thead %tr diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index ebca9beb035..8c9fdc9ae42 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -125,7 +125,7 @@ = link_to admin_projects_path do %h1= number_with_delimiter(Project.cached_count) %hr - = link_to('New Project', new_project_path, class: "btn btn-new") + = link_to('New project', new_project_path, class: "btn btn-new") .col-sm-4 .light-well.well-centered %h4 Users @@ -133,7 +133,7 @@ = link_to admin_users_path do %h1= number_with_delimiter(User.count) %hr - = link_to 'New User', new_admin_user_path, class: "btn btn-new" + = link_to 'New user', new_admin_user_path, class: "btn btn-new" .col-sm-4 .light-well.well-centered %h4 Groups @@ -141,7 +141,7 @@ = link_to admin_groups_path do %h1= number_with_delimiter(Group.count) %hr - = link_to 'New Group', new_admin_group_path, class: "btn btn-new" + = link_to 'New group', new_admin_group_path, class: "btn btn-new" .row.prepend-top-10 .col-md-4 diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml index 7b71bb5b287..007da8c1d29 100644 --- a/app/views/admin/deploy_keys/index.html.haml +++ b/app/views/admin/deploy_keys/index.html.haml @@ -3,7 +3,7 @@ %h3.page-title.deploy-keys-title Public deploy keys (#{@deploy_keys.count}) .pull-right - = link_to 'New Deploy Key', new_admin_deploy_key_path, class: 'btn btn-new btn-sm btn-inverted' + = link_to 'New deploy key', new_admin_deploy_key_path, class: 'btn btn-new btn-sm btn-inverted' - if @deploy_keys.any? .table-holder.deploy-keys-list diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 07775247cfd..e5f380c78e2 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -30,7 +30,7 @@ = link_to admin_groups_path(sort: sort_value_largest_group, name: project_name) do = sort_title_largest_group = link_to new_admin_group_path, class: "btn btn-new" do - New Group + New group %ul.content-list = render @groups diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 30b3fabdd7e..9149b8e7fb9 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -116,7 +116,7 @@ group members %span.badge= @group.members.size .pull-right - = link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@group, :members]), class: "btn btn-xs" + = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-xs" %ul.well-list.group-users-list.content-list = render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false } .panel-footer diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index 551edf14361..d9c7948763a 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -51,7 +51,7 @@ = f.check_box :enable_ssl_verification %strong Enable SSL verification .form-actions - = f.submit "Add System Hook", class: "btn btn-create" + = f.submit "Add system hook", class: "btn btn-create" %hr - if @hooks.any? @@ -62,7 +62,7 @@ - @hooks.each do |hook| %li .controls - = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm" + = link_to 'Test hook', admin_hook_test_path(hook), class: "btn btn-sm" = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm" .monospace= hook.url %div diff --git a/app/views/admin/identities/index.html.haml b/app/views/admin/identities/index.html.haml index 741d111fb7d..ff67e59cdac 100644 --- a/app/views/admin/identities/index.html.haml +++ b/app/views/admin/identities/index.html.haml @@ -1,7 +1,7 @@ - page_title "Identities", @user.name, "Users" = render 'admin/users/head' -= link_to 'New Identity', new_admin_user_identity_path, class: 'pull-right btn btn-new' += link_to 'New identity', new_admin_user_identity_path, class: 'pull-right btn btn-new' - if @identities.present? .table-holder %table.table diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 2967da6e692..08a8f627113 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -159,7 +159,7 @@ %span.badge= @group_members.size .pull-right = link_to admin_group_path(@group), class: 'btn btn-xs' do - = icon('pencil-square-o', text: 'Manage Access') + = icon('pencil-square-o', text: 'Manage access') %ul.well-list.content-list = render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false } .panel-footer @@ -173,7 +173,7 @@ project members %span.badge= @project.users.size .pull-right - = link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@project, :members]), class: "btn btn-xs" + = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-xs" %ul.well-list.project_members.content-list = render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false } .panel-footer diff --git a/app/views/admin/spam_logs/_spam_log.html.haml b/app/views/admin/spam_logs/_spam_log.html.haml index 33f6d847782..ea6a0c4fb77 100644 --- a/app/views/admin/spam_logs/_spam_log.html.haml +++ b/app/views/admin/spam_logs/_spam_log.html.haml @@ -35,5 +35,5 @@ = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs" - else .btn.btn-xs.disabled - Already Blocked + Already blocked = link_to 'Remove log', [:admin, spam_log], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr" diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml index a756cb7243a..8862455688f 100644 --- a/app/views/admin/users/_user.html.haml +++ b/app/views/admin/users/_user.html.haml @@ -37,6 +37,6 @@ - if user.can_be_removed? && can?(current_user, :destroy_user, @user) %li.divider %li - = link_to 'Delete User', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Consider cancelling this deletion and blocking the user instead. Are you sure?" }, + = link_to 'Delete user', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Consider cancelling this deletion and blocking the user instead. Are you sure?" }, class: 'btn btn-remove btn-block', method: :delete diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 298cf0fa950..c7cd86527d3 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -33,7 +33,7 @@ = sort_title_recently_updated = link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do = sort_title_oldest_updated - = link_to 'New User', new_admin_user_path, class: 'btn btn-new btn-search' + = link_to 'New user', new_admin_user_path, class: 'btn btn-new btn-search' .nav-block %ul.nav-links.wide.scrolling-tabs.white.scrolling-tabs diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml index c00c7f7407e..39c7fb0eba2 100644 --- a/app/views/ci/status/_badge.html.haml +++ b/app/views/ci/status/_badge.html.haml @@ -1,12 +1,13 @@ - status = local_assigns.fetch(:status) -- link = local_assigns.fetch(:link, true) -- css_classes = "ci-status ci-#{status.group}" +- link = local_assigns.fetch(:link, true) +- title = local_assigns.fetch(:title, nil) +- css_classes = "ci-status ci-#{status.group} #{'has-tooltip' if title.present?}" - if link && status.has_details? - = link_to status.details_path, class: css_classes do + = link_to status.details_path, class: css_classes, title: title do = custom_icon(status.icon) = status.text - else - %span{ class: css_classes } + %span{ class: css_classes, title: title } = custom_icon(status.icon) = status.text diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml index 13eaba41f4c..0e848386ebb 100644 --- a/app/views/dashboard/_groups_head.html.haml +++ b/app/views/dashboard/_groups_head.html.haml @@ -11,4 +11,4 @@ = render 'shared/groups/dropdown' - if current_user.can_create_group? = link_to new_group_path, class: "btn btn-new" do - New Group + New group diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index 4679b9549d1..64b737ee886 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -19,4 +19,4 @@ = render 'shared/projects/dropdown' - if current_user.can_create_project? = link_to new_project_path, class: 'btn btn-new' do - New Project + New project diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 10867140d4f..faa68468043 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -8,7 +8,7 @@ .nav-controls = link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do = icon('rss') - = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" + = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue" = render 'shared/issuable/filter', type: :issues = render 'shared/issues' diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index e64c78c4cb8..12966c01950 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -4,7 +4,7 @@ .top-area = render 'shared/issuable/nav', type: :merge_requests .nav-controls - = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request" + = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request" = render 'shared/issuable/filter', type: :merge_requests = render 'shared/merge_requests' diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml index 505b475f55b..664ec618b79 100644 --- a/app/views/dashboard/milestones/index.html.haml +++ b/app/views/dashboard/milestones/index.html.haml @@ -5,7 +5,7 @@ = render 'shared/milestones_filter', counts: @milestone_states .nav-controls - = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New Milestone', include_groups: true + = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true .milestones %ul.content-list diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml index a1a282178e7..1584695a62b 100644 --- a/app/views/events/_event_last_push.html.haml +++ b/app/views/events/_event_last_push.html.haml @@ -10,5 +10,5 @@ #{time_ago_with_tooltip(event.created_at)} .pull-right - = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do - Create Merge Request + = link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do + Create merge request diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml index 2a98e58a03a..c0c11d00519 100644 --- a/app/views/events/event/_common.html.haml +++ b/app/views/events/event/_common.html.haml @@ -7,7 +7,7 @@ = custom_icon("icon_status_closed") - else .profile-icon.fork-icon - = custom_icon("code_fork") + = custom_icon("icon_code_fork") .event-title %span{ class: event.action_name } diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml index 603bed6d705..cbcfe0ff71f 100644 --- a/app/views/events/event/_note.html.haml +++ b/app/views/events/event/_note.html.haml @@ -1,5 +1,5 @@ .profile-icon - = custom_icon("comment_o") + = custom_icon("icon_comment_o") .event-title = event.action_name diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 00ff40224ba..7d5add3cc1c 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -51,4 +51,4 @@ %strong Removed group can not be restored! .form-actions - = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove" + = link_to 'Remove group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove" diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index f4c17dc2d16..182dbe2f98a 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -11,7 +11,7 @@ = icon('rss') %span.icon-label Subscribe - = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" + = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue" = render 'shared/issuable/filter', type: :issues diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index 6893168f039..f91bee0b610 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -7,7 +7,7 @@ .nav-controls - if can?(current_user, :admin_milestones, @group) = link_to new_group_milestone_path(@group), class: "btn btn-new" do - New Milestone + New milestone .row-content-block Only milestones from diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index 63cadfca530..8d3aa4d1a74 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -39,5 +39,5 @@ = render "shared/milestones/form_dates", f: f .form-actions - = f.submit 'Create Milestone', class: "btn-create btn" + = f.submit 'Create milestone', class: "btn-create btn" = link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel" diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index 83bdd654f27..62ad47972b9 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -7,7 +7,7 @@ - if can? current_user, :admin_group, @group .controls = link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do - New Project + New project %ul.well-list - @projects.each do |project| %li diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 8e6da3fad90..700c5e61a14 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -17,6 +17,10 @@ %th Global Shortcuts %tr %td.shortcut + .key n + %td Main Navigation + %tr + %td.shortcut .key s %td Focus Search %tr @@ -39,24 +43,46 @@ .key %i.fa.fa-arrow-up %td Edit last comment (when focused on an empty textarea) - %tbody %tr - %th - %th Project Files browsing + %td.shortcut + .key shift t + %td + Go to todos %tr %td.shortcut - .key - %i.fa.fa-arrow-up - %td Move selection up + .key shift a + %td + Go to the activity feed %tr %td.shortcut - .key - %i.fa.fa-arrow-down - %td Move selection down + .key shift p + %td + Go to projects %tr %td.shortcut - .key enter - %td Open Selection + .key shift i + %td + Go to issues + %tr + %td.shortcut + .key shift m + %td + Go to merge requests + %tr + %td.shortcut + .key shift g + %td + Go to groups + %tr + %td.shortcut + .key shift l + %td + Go to milestones + %tr + %td.shortcut + .key shift s + %td + Go to snippets %tbody %tr %th @@ -79,51 +105,8 @@ %td.shortcut .key esc %td Go back - %tbody - %tr - %th - %th Project File - %tr - %td.shortcut - .key y - %td Go to file permalink - .col-lg-4 %table.shortcut-mappings - %tbody.hidden-shortcut.project{ style: 'display:none' } - %tr - %th - %th Global Dashboard - %tr - %td.shortcut - .key g - .key a - %td - Go to the activity feed - %tr - %td.shortcut - .key g - .key p - %td - Go to projects - %tr - %td.shortcut - .key g - .key i - %td - Go to issues - %tr - %td.shortcut - .key g - .key m - %td - Go to merge requests - %tr - %td.shortcut - .key g - .key t - %td - Go to todos %tbody %tr %th @@ -155,7 +138,7 @@ %tr %td.shortcut .key g - .key b + .key j %td Go to jobs %tr @@ -167,7 +150,7 @@ %tr %td.shortcut .key g - .key g + .key d %td Go to repository charts %tr @@ -179,7 +162,7 @@ %tr %td.shortcut .key g - .key l + .key b %td Go to issue boards %tr @@ -196,12 +179,45 @@ Go to snippets %tr %td.shortcut + .key g + .key w + %td + Go to wiki + %tr + %td.shortcut .key t %td Go to finding file %tr %td.shortcut .key i %td New issue + + %tbody + %tr + %th + %th Project Files browsing + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + %td Move selection up + %tr + %td.shortcut + .key + %i.fa.fa-arrow-down + %td Move selection down + %tr + %td.shortcut + .key enter + %td Open Selection + %tbody + %tr + %th + %th Project File + %tr + %td.shortcut + .key y + %td Go to file permalink .col-lg-4 %table.shortcut-mappings %tbody.hidden-shortcut.network{ style: 'display:none' } diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 1fb2c6271ad..207f80bedfe 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -225,7 +225,7 @@ %ul.dropdown-menu %li %a{ href: "#" } - Dropdown Option + Dropdown option .dropdown.inline.pull-right %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } Dropdown @@ -233,7 +233,7 @@ %ul.dropdown-menu.dropdown-menu-align-right %li %a{ href: "#" } - Dropdown Option + Dropdown option .example %div .dropdown.inline @@ -243,7 +243,7 @@ %ul.dropdown-menu.dropdown-menu-selectable %li %a.is-active{ href: "#" } - Dropdown Option + Dropdown option .example %div .dropdown.inline @@ -262,26 +262,26 @@ %ul %li %a.is-active{ href: "#" } - Dropdown Option + Dropdown option %li %a{ href: "#" } - Dropdown Option + Dropdown option %li.divider %li %a{ href: "#" } - Dropdown Option + Dropdown option %li %a{ href: "#" } - Dropdown Option + Dropdown option %li %a{ href: "#" } - Dropdown Option + Dropdown option %li %a{ href: "#" } - Dropdown Option + Dropdown option %li %a{ href: "#" } - Dropdown Option + Dropdown option .dropdown-footer %strong Tip: If an author is not a member of this project, you can still filter by his name while using the search field. @@ -301,26 +301,26 @@ %ul %li %a.is-active{ href: "#" } - Dropdown Option + Dropdown option %li %a{ href: "#" } - Dropdown Option + Dropdown option %li.divider %li %a{ href: "#" } - Dropdown Option + Dropdown option %li %a{ href: "#" } - Dropdown Option + Dropdown option %li %a{ href: "#" } - Dropdown Option + Dropdown option %li %a{ href: "#" } - Dropdown Option + Dropdown option %li %a{ href: "#" } - Dropdown Option + Dropdown option .dropdown-footer %strong Tip: If an author is not a member of this project, you can still filter by his name while using the search field. diff --git a/app/views/import/github/new.html.haml b/app/views/import/github/new.html.haml index 4c6af0b7908..9c2da3a3eec 100644 --- a/app/views/import/github/new.html.haml +++ b/app/views/import/github/new.html.haml @@ -9,7 +9,7 @@ To import a GitHub project, you first need to authorize GitLab to access the list of your GitHub repositories: - = link_to 'List Your GitHub Repositories', status_import_github_path, class: 'btn btn-success' + = link_to 'List your GitHub repositories', status_import_github_path, class: 'btn btn-success' %hr @@ -28,7 +28,7 @@ = form_tag personal_access_token_import_github_path, method: :post, class: 'form-inline' do .form-group = text_field_tag :personal_access_token, '', class: 'form-control', placeholder: "Personal Access Token", size: 40 - = submit_tag 'List Your GitHub Repositories', class: 'btn btn-success' + = submit_tag 'List your GitHub repositories', class: 'btn btn-success' - unless github_import_configured? %hr diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 23abf6897d4..7408fe3f6d0 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -29,11 +29,11 @@ - if current_user - if session[:impersonator_id] %li.impersonation - = link_to admin_impersonation_path, method: :delete, title: "Stop Impersonation", aria: { label: 'Stop Impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do + = link_to admin_impersonation_path, method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do = icon('user-secret fw') - if current_user.is_admin? %li - = link_to admin_root_path, title: 'Admin Area', aria: { label: "Admin Area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = link_to admin_root_path, title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('wrench fw') - if current_user.can_create_project? %li diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 15285ee32a3..444ecc414c0 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,10 +1,18 @@ %ul = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do + .shortcut-mappings + .key + = icon('arrow-up', 'aria-label' => 'hidden') + P %span Projects = nav_link(path: 'dashboard#activity') do = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do + .shortcut-mappings + .key + = icon('arrow-up', 'aria-label' => 'hidden') + A %span Activity - if koding_enabled? @@ -13,25 +21,45 @@ %span Koding = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do - = link_to dashboard_groups_path, title: 'Groups' do + = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do + .shortcut-mappings + .key + = icon('arrow-up', 'aria-label' => 'hidden') + G %span Groups = nav_link(controller: 'dashboard/milestones') do - = link_to dashboard_milestones_path, title: 'Milestones' do + = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: 'Milestones' do + .shortcut-mappings + .key + = icon('arrow-up', 'aria-label' => 'hidden') + L %span Milestones = nav_link(path: 'dashboard#issues') do = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do + .shortcut-mappings + .key + = icon('arrow-up', 'aria-label' => 'hidden') + I %span Issues .badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened)) = nav_link(path: 'dashboard#merge_requests') do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do + .shortcut-mappings + .key + = icon('arrow-up', 'aria-label' => 'hidden') + M %span Merge Requests .badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened)) = nav_link(controller: 'dashboard/snippets') do - = link_to dashboard_snippets_path, title: 'Snippets' do + = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do + .shortcut-mappings + .key + = icon('arrow-up', 'aria-label' => 'hidden') + S %span Snippets %li.divider diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 5ce2220c907..d843cacd52d 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -49,14 +49,14 @@ %p Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'} - if current_user.two_factor_enabled? - = link_to 'Manage Two-Factor Authentication', profile_two_factor_auth_path, class: 'btn btn-info' + = link_to 'Manage two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-info' = link_to 'Disable', profile_two_factor_auth_path, method: :delete, data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." }, class: 'btn btn-danger' - else .append-bottom-10 - = link_to 'Enable Two-Factor Authentication', profile_two_factor_auth_path, class: 'btn btn-success' + = link_to 'Enable two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-success' %hr - if button_based_providers.any? diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index dc499be885b..f5a323dbaf8 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -33,17 +33,17 @@ %li = @primary %span.pull-right - %span.label.label-success Primary Email + %span.label.label-success Primary email - if @primary === current_user.public_email - %span.label.label-info Public Email + %span.label.label-info Public email - if @primary === current_user.notification_email - %span.label.label-info Notification Email + %span.label.label-info Notification email - @emails.each do |email| %li = email.email %span.pull-right - if email.email === current_user.public_email - %span.label.label-info Public Email + %span.label.label-info Public email - if email.email === current_user.notification_email - %span.label.label-info Notification Email + %span.label.label-info Notification email = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10' diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml index 0645ecad496..c852107e69a 100644 --- a/app/views/profiles/personal_access_tokens/index.html.haml +++ b/app/views/profiles/personal_access_tokens/index.html.haml @@ -19,7 +19,7 @@ Your New Personal Access Token .form-group = text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control", 'aria-describedby' => "created-personal-access-token-help-block" - = clipboard_button(clipboard_text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left") + = clipboard_button(text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left") %span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again. %hr diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 7ade5f00d47..0ff05098cd7 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -44,7 +44,7 @@ = label_tag :pin_code, nil, class: "label-light" = text_field_tag :pin_code, nil, class: "form-control", required: true .prepend-top-default - = submit_tag 'Register with Two-Factor App', class: 'btn btn-success' + = submit_tag 'Register with two-factor app', class: 'btn btn-success' %hr diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml index 640612ca433..b55dc3dce5c 100644 --- a/app/views/projects/_commit_button.html.haml +++ b/app/views/projects/_commit_button.html.haml @@ -1,5 +1,5 @@ .form-actions - = button_tag 'Commit Changes', class: 'btn commit-btn js-commit-button btn-create' + = button_tag 'Commit changes', class: 'btn commit-btn js-commit-button btn-create' = link_to 'Cancel', cancel_path, class: 'btn btn-cancel', data: {confirm: leave_edit_message} diff --git a/app/views/projects/_find_file_link.html.haml b/app/views/projects/_find_file_link.html.haml index dbb33090670..3feb11645a0 100644 --- a/app/views/projects/_find_file_link.html.haml +++ b/app/views/projects/_find_file_link.html.haml @@ -1,3 +1,3 @@ = link_to namespace_project_find_file_path(@project.namespace, @project, @ref), class: 'btn btn-grouped shortcuts-find-file', rel: 'nofollow' do = icon('search') - %span Find File + %span Find file diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml index a08436715d2..768bc1fb323 100644 --- a/app/views/projects/_last_push.html.haml +++ b/app/views/projects/_last_push.html.haml @@ -10,9 +10,9 @@ - if @project && event.project != @project %span at %strong= link_to_project event.project - = clipboard_button(clipboard_text: event.ref_name, class: 'btn-clipboard btn-transparent', title: 'Copy branch to clipboard') + = clipboard_button(text: event.ref_name, class: 'btn-clipboard btn-transparent', title: 'Copy branch to clipboard') #{time_ago_with_tooltip(event.created_at)} .pull-right - = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do - Create Merge Request + = link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do + Create merge request diff --git a/app/views/projects/blob/_header.html.haml b/app/views/projects/blob/_header.html.haml index 6000e057ec4..828f8e073a9 100644 --- a/app/views/projects/blob/_header.html.haml +++ b/app/views/projects/blob/_header.html.haml @@ -20,7 +20,7 @@ -# only show normal/blame view links for text files - if blob_text_viewable?(blob) - if current_page? namespace_project_blame_path(@project.namespace, @project, @id) - = link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id), + = link_to 'Normal view', namespace_project_blob_path(@project.namespace, @project, @id), class: 'btn btn-sm' - else = link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id), diff --git a/app/views/projects/blob/_template_selectors.html.haml b/app/views/projects/blob/_template_selectors.html.haml index d52733d2bd6..2a178325041 100644 --- a/app/views/projects/blob/_template_selectors.html.haml +++ b/app/views/projects/blob/_template_selectors.html.haml @@ -5,7 +5,7 @@ .template-type-selector.js-template-type-selector-wrap.hidden = dropdown_tag("Choose type", options: { toggle_class: 'btn js-template-type-selector', title: "Choose a template type" } ) .license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden - = dropdown_tag("Apply a License template", options: { toggle_class: 'btn js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } ) + = dropdown_tag("Apply a license template", options: { toggle_class: 'btn js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } ) .gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } ) .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 9eb610ba9c0..724675e659c 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -21,7 +21,7 @@ .controls.hidden-xs< - if merge_project && create_mr_button?(@repository.root_ref, branch.name) = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do - Merge Request + Merge request - if branch.name != @repository.root_ref = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml index 7eb17e887e7..104db85809c 100644 --- a/app/views/projects/builds/_header.html.haml +++ b/app/views/projects/builds/_header.html.haml @@ -1,14 +1,16 @@ +- pipeline = @build.pipeline + .content-block.build-header.top-area .header-content - = render 'ci/status/badge', status: @build.detailed_status(current_user), link: false + = render 'ci/status/badge', status: @build.detailed_status(current_user), link: false, title: @build.status_title Job %strong.js-build-id ##{@build.id} in pipeline - = link_to pipeline_path(@build.pipeline) do - %strong ##{@build.pipeline.id} + = link_to pipeline_path(pipeline) do + %strong ##{pipeline.id} for commit - = link_to namespace_project_commit_path(@project.namespace, @project, @build.pipeline.sha) do - %strong= @build.pipeline.short_sha + = link_to namespace_project_commit_path(@project.namespace, @project, pipeline.sha) do + %strong= pipeline.short_sha from = link_to namespace_project_commits_path(@project.namespace, @project, @build.ref) do %code diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 5ffc0e20d10..65162aacda1 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -17,7 +17,7 @@ = link_to 'Get started with CI/CD Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info' = link_to ci_lint_path, class: 'btn btn-default' do - %span CI Lint + %span CI lint .content-list.builds-content-list = render "table", builds: @builds, project: @project diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 508465cbfb7..4700b7a9a45 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -1,3 +1,5 @@ +- job = build.present(current_user: current_user) +- pipeline = job.pipeline - admin = local_assigns.fetch(:admin, false) - ref = local_assigns.fetch(:ref, nil) - commit_sha = local_assigns.fetch(:commit_sha, nil) @@ -8,101 +10,101 @@ %tr.build.commit{ class: ('retried' if retried) } %td.status - = render "ci/status/badge", status: build.detailed_status(current_user) + = render "ci/status/badge", status: job.detailed_status(current_user), title: job.status_title %td.branch-commit - - if can?(current_user, :read_build, build) - = link_to namespace_project_build_url(build.project.namespace, build.project, build) do - %span.build-link ##{build.id} + - if can?(current_user, :read_build, job) + = link_to namespace_project_build_url(job.project.namespace, job.project, job) do + %span.build-link ##{job.id} - else - %span.build-link ##{build.id} + %span.build-link ##{job.id} - if ref - - if build.ref + - if job.ref .icon-container - = build.tag? ? icon('tag') : icon('code-fork') - = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" + = job.tag? ? icon('tag') : icon('code-fork') + = link_to job.ref, namespace_project_commits_path(job.project.namespace, job.project, job.ref), class: "monospace branch-name" - else .light none .icon-container.commit-icon = custom_icon("icon_commit") - if commit_sha - = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" + = link_to job.short_sha, namespace_project_commit_path(job.project.namespace, job.project, job.sha), class: "commit-id monospace" - - if build.stuck? + - if job.stuck? = icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.') - if retried = icon('refresh', class: 'text-warning has-tooltip', title: 'Job was retried') .label-container - - if build.tags.any? - - build.tags.each do |tag| + - if job.tags.any? + - job.tags.each do |tag| %span.label.label-primary = tag - - if build.try(:trigger_request) + - if job.try(:trigger_request) %span.label.label-info triggered - - if build.try(:allow_failure) + - if job.try(:allow_failure) %span.label.label-danger allowed to fail - - if build.action? + - if job.action? %span.label.label-info manual - if pipeline_link %td - = link_to pipeline_path(build.pipeline) do - %span.pipeline-id ##{build.pipeline.id} + = link_to pipeline_path(pipeline) do + %span.pipeline-id ##{pipeline.id} %span by - - if build.pipeline.user - = user_avatar(user: build.pipeline.user, size: 20) + - if pipeline.user + = user_avatar(user: pipeline.user, size: 20) - else %span.monospace API - if admin %td - - if build.project - = link_to build.project.name_with_namespace, admin_namespace_project_path(build.project.namespace, build.project) + - if job.project + = link_to job.project.name_with_namespace, admin_namespace_project_path(job.project.namespace, job.project) %td - - if build.try(:runner) - = runner_link(build.runner) + - if job.try(:runner) + = runner_link(job.runner) - else .light none - if stage %td - = build.stage + = job.stage %td - = build.name + = job.name %td - - if build.duration + - if job.duration %p.duration = custom_icon("icon_timer") - = duration_in_numbers(build.duration) + = duration_in_numbers(job.duration) - - if build.finished_at + - if job.finished_at %p.finished-at = icon("calendar") - %span= time_ago_with_tooltip(build.finished_at) + %span= time_ago_with_tooltip(job.finished_at) %td.coverage - - if build.try(:coverage) - #{build.coverage}% + - if job.try(:coverage) + #{job.coverage}% %td .pull-right - - if can?(current_user, :read_build, build) && build.artifacts? - = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do + - if can?(current_user, :read_build, job) && job.artifacts? + = link_to download_namespace_project_build_artifacts_path(job.project.namespace, job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do = icon('download') - - if can?(current_user, :update_build, build) - - if build.active? - = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do + - if can?(current_user, :update_build, job) + - if job.active? + = link_to cancel_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do = icon('remove', class: 'cred') - elsif allow_retry - - if build.playable? && !admin - = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do + - if job.playable? && !admin + = link_to play_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do = custom_icon('icon_play') - - elsif build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do + - elsif job.retryable? + = link_to retry_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do = icon('repeat') diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index a0a292d0508..f604d6e5fbb 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -1,7 +1,7 @@ .page-content-header .header-main-content %strong Commit #{@commit.short_id} - = clipboard_button(clipboard_text: @commit.id, title: "Copy commit SHA to clipboard") + = clipboard_button(text: @commit.id, title: "Copy commit SHA to clipboard") %span.hidden-xs authored #{time_ago_with_tooltip(@commit.authored_date)} %span by @@ -20,7 +20,7 @@ = icon('comment') = @notes_count = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do - Browse Files + Browse files .dropdown.inline %a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } } %span Options diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 4b1ff75541a..8f32d2b72e5 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -37,6 +37,6 @@ .commit-actions.flex-row.hidden-xs - if commit.status(ref) = render_commit_status(commit, ref: ref) - = clipboard_button(clipboard_text: commit.id, title: "Copy commit SHA to clipboard") + = clipboard_button(text: commit.id, title: "Copy commit SHA to clipboard") = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent" = link_to_browse_code(project, commit) diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 38dbf2ac10b..c1c2fb3d299 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -18,16 +18,16 @@ .block-controls.hidden-xs.hidden-sm - if @merge_request.present? .control - = link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn' + = link_to "View open merge request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn' - elsif create_mr_button?(@repository.root_ref, @ref) .control - = link_to "Create Merge Request", create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' + = link_to "Create merge request", create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' .control = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do = search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } .control - = link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: "Commits Feed", class: 'btn' do + = link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: "Commits feed", class: 'btn' do = icon("rss") %div{ id: dom_id(@project) } diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index 08236216421..0f080b6acee 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -21,6 +21,6 @@ = button_tag "Compare", class: "btn btn-create commits-compare-btn" - if @merge_request.present? - = link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn' + = link_to "View open merge request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn' - elsif create_mr_button? - = link_to "Create Merge Request", create_mr_path, class: 'prepend-left-10 btn' + = link_to "Create merge request", create_mr_path, class: 'prepend-left-10 btn' diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml index 4b101447bc0..f7e3733ba0b 100644 --- a/app/views/projects/environments/folder.html.haml +++ b/app/views/projects/environments/folder.html.haml @@ -8,7 +8,4 @@ #environments-folder-list-view{ data: { "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s, "can-read-environment" => can?(current_user, :read_environment, @project).to_s, - "css-class" => container_class, - "commit-icon-svg" => custom_icon("icon_commit"), - "terminal-icon-svg" => custom_icon("icon_terminal"), - "play-icon-svg" => custom_icon("icon_play") } } + "css-class" => container_class } } diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml index 98d81308407..524b77783ef 100644 --- a/app/views/projects/forks/error.html.haml +++ b/app/views/projects/forks/error.html.haml @@ -22,4 +22,4 @@ %p = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do %i.fa.fa-code-fork - Try to Fork again + Try to fork again diff --git a/app/views/projects/issues/_issue_by_email.html.haml b/app/views/projects/issues/_issue_by_email.html.haml index d2038a2be68..da65157a10b 100644 --- a/app/views/projects/issues/_issue_by_email.html.haml +++ b/app/views/projects/issues/_issue_by_email.html.haml @@ -16,7 +16,7 @@ .email-modal-input-group.input-group = text_field_tag :issue_email, email, class: "monospace js-select-on-focus form-control", readonly: true .input-group-btn - = clipboard_button(clipboard_target: '#issue_email') + = clipboard_button(target: '#issue_email') %p The subject will be used as the title of the new issue, and the message will be the description. diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index f3a429d12d9..4ac0bc1d028 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -24,9 +24,9 @@ issue: { assignee_id: issues_finder.assignee.try(:id), milestone_id: issues_finder.milestones.first.try(:id) }), class: "btn btn-new", - title: "New Issue", + title: "New issue", id: "new_issue_link" do - New Issue + New issue = render 'shared/issuable/search_bar', type: :issues .issues-holder diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index e7fcac4c477..03069804c86 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -53,5 +53,6 @@ :javascript var merge_request = new MergeRequest({ - action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}" + action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}", + setUrl: false, }); diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml index cde0ce08e14..f3372c7657f 100644 --- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml @@ -8,7 +8,7 @@ %p %strong Step 1. Fetch and check out the branch for this merge request - = clipboard_button(clipboard_target: "pre#merge-info-1", title: "Copy commands to clipboard") + = clipboard_button(target: "pre#merge-info-1", title: "Copy commands to clipboard") %pre.dark#merge-info-1 - if @merge_request.for_fork? :preserve @@ -25,7 +25,7 @@ %p %strong Step 3. Merge the branch and fix any conflicts that come up - = clipboard_button(clipboard_target: "pre#merge-info-3", title: "Copy commands to clipboard") + = clipboard_button(target: "pre#merge-info-3", title: "Copy commands to clipboard") %pre.dark#merge-info-3 - if @merge_request.for_fork? :preserve @@ -38,7 +38,7 @@ %p %strong Step 4. Push the result of the merge to GitLab - = clipboard_button(clipboard_target: "pre#merge-info-4", title: "Copy commands to clipboard") + = clipboard_button(target: "pre#merge-info-4", title: "Copy commands to clipboard") %pre.dark#merge-info-4 :preserve git push origin #{h @merge_request.target_branch} diff --git a/app/views/projects/merge_requests/widget/_merged_buttons.haml b/app/views/projects/merge_requests/widget/_merged_buttons.haml index caf3bf54eef..a0f54bd28ec 100644 --- a/app/views/projects/merge_requests/widget/_merged_buttons.haml +++ b/app/views/projects/merge_requests/widget/_merged_buttons.haml @@ -7,7 +7,7 @@ - if can_remove_source_branch = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-default remove_source_branch" do = icon('trash-o') - Remove Source Branch + Remove source branch - if mr_can_be_reverted = revert_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: "close") - if mr_can_be_cherry_picked diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index e5ec151a61d..cb117d1908c 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -10,24 +10,24 @@ - if @pipeline && @pipeline.active? %span.btn-group = button_tag class: "btn btn-info js-merge-when-pipeline-succeeds-button merge-when-pipeline-succeeds" do - Merge When Pipeline Succeeds + Merge when pipeline succeeds - unless @project.only_allow_merge_if_pipeline_succeeds? = button_tag class: "btn btn-info dropdown-toggle", 'data-toggle' => 'dropdown' do = icon('caret-down') %span.sr-only - Select Merge Moment + Select merge moment %ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' } %li = link_to "#", class: "merge_when_pipeline_succeeds" do = icon('check fw') - Merge When Pipeline Succeeds + Merge when pipeline succeeds %li = link_to "#", class: "accept-merge-request" do = icon('warning fw') - Merge Immediately + Merge immediately - else = f.button class: "btn btn-grouped js-merge-button accept-merge-request" do - Accept Merge Request + Accept merge request - if @merge_request.force_remove_source_branch? .accept-control The source branch will be removed. diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_pipeline_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_pipeline_succeeds.html.haml index 5f347acce4d..76cc1ecd8a5 100644 --- a/app/views/projects/merge_requests/widget/open/_merge_when_pipeline_succeeds.html.haml +++ b/app/views/projects/merge_requests/widget/open/_merge_when_pipeline_succeeds.html.haml @@ -26,8 +26,8 @@ - if remove_source_branch_button = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_params(@merge_request)), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do = icon('times') - Remove Source Branch When Merged + Remove source branch when merged - if user_can_cancel_automatic_merge = link_to cancel_merge_when_pipeline_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-sm" do - Cancel Automatic Merge + Cancel automatic merge diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index b6340a00b29..8e85b2e8a20 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -9,8 +9,8 @@ .nav-controls = render 'shared/milestones_sort_dropdown' - if can?(current_user, :admin_milestone, @project) - = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New Milestone' do - New Milestone + = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone' do + New milestone .milestones %ul.content-list diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 5249d752585..8b62b156853 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -23,9 +23,9 @@ .milestone-buttons - if can?(current_user, :admin_milestone, @project) - if @milestone.active? - = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped" + = link_to 'Close milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped" - else - = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" + = link_to 'Reopen milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do Edit diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml index e8e450742b5..8b4e5928e0d 100644 --- a/app/views/projects/notes/_edit_form.html.haml +++ b/app/views/projects/notes/_edit_form.html.haml @@ -9,6 +9,6 @@ .note-form-actions.clearfix .settings-message.note-edit-warning.js-edit-warning Finish editing this message first! - = submit_tag 'Save Comment', class: 'btn btn-nr btn-save js-comment-button' + = submit_tag 'Save comment', class: 'btn btn-nr btn-save js-comment-button' %button.btn.btn-nr.btn-cancel.note-edit-cancel{ type: 'button' } Cancel diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index b1b1b66b7f2..c12c05eeb73 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -5,8 +5,11 @@ %li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable, note_id: note.id} } .timeline-entry-inner .timeline-icon - %a{ href: user_path(note.author) } - = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' + - if note.system + = icon_for_system_note(note) + - else + %a{ href: user_path(note.author) } + = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' .timeline-content .note-header %a.visible-xs{ href: user_path(note.author) } diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index 4be9a1371ec..ab6baaf35b6 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -1,6 +1,6 @@ .page-content-header .header-main-content - = render 'ci/status/badge', status: @pipeline.detailed_status(current_user) + = render 'ci/status/badge', status: @pipeline.detailed_status(current_user), title: @pipeline.status_title %strong Pipeline ##{@pipeline.id} triggered #{time_ago_with_tooltip(@pipeline.created_at)} - if @pipeline.user @@ -46,4 +46,4 @@ \... %span.js-details-content.hide = link_to @pipeline.sha, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "monospace commit-hash-full" - = clipboard_button(clipboard_text: @pipeline.sha, title: "Copy commit SHA to clipboard") + = clipboard_button(text: @pipeline.sha, title: "Copy commit SHA to clipboard") diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml index 132f6372e40..a3f84476dea 100644 --- a/app/views/projects/pipelines_settings/_show.html.haml +++ b/app/views/projects/pipelines_settings/_show.html.haml @@ -21,7 +21,7 @@ Git strategy for pipelines %p Choose between <code>clone</code> or <code>fetch</code> to get the recent application code - = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'git-strategy') + = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'git-strategy'), target: '_blank' .radio = f.label :build_allow_git_fetch_false do = f.radio_button :build_allow_git_fetch, 'false' @@ -43,7 +43,7 @@ = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0' %p.help-block Per job in minutes. If a job passes this threshold, it will be marked as failed. - = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout') + = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout'), target: '_blank' %hr .form-group @@ -53,7 +53,16 @@ %strong Public pipelines .help-block Allow everyone to access pipelines for public and internal projects - = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'visibility-of-pipelines') + = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'visibility-of-pipelines'), target: '_blank' + %hr + .form-group + .checkbox + = f.label :auto_cancel_pending_pipelines do + = f.check_box :auto_cancel_pending_pipelines, {}, 'enabled', 'disabled' + %strong Auto-cancel redundant, pending pipelines + .help-block + New pipelines will cancel older, pending pipelines on the same branch + = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'auto-cancel-pending-pipelines'), target: '_blank' %hr .form-group @@ -65,7 +74,7 @@ %p.help-block A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable - = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'test-coverage-parsing') + = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'test-coverage-parsing'), target: '_blank' .bs-callout.bs-callout-info %p Below are examples of regex for existing tools: %ul diff --git a/app/views/projects/registry/repositories/_tag.html.haml b/app/views/projects/registry/repositories/_tag.html.haml index ee1ec0e8f9a..854b7d0ebf7 100644 --- a/app/views/projects/registry/repositories/_tag.html.haml +++ b/app/views/projects/registry/repositories/_tag.html.haml @@ -1,7 +1,7 @@ %tr.tag %td = escape_once(tag.name) - = clipboard_button(clipboard_text: "docker pull #{tag.path}") + = clipboard_button(text: "docker pull #{tag.path}") %td - if tag.revision %span.has-tooltip{ title: "#{tag.revision}" } diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml index 2fb88297fb3..ef3599460f1 100644 --- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml @@ -22,14 +22,14 @@ .col-sm-10.col-xs-12.input-group = text_field_tag :display_name, "GitLab / #{@project.name_with_namespace}", class: 'form-control input-sm', readonly: 'readonly' .input-group-btn - = clipboard_button(clipboard_target: '#display_name') + = clipboard_button(target: '#display_name') .form-group = label_tag :description, 'Description', class: 'col-sm-2 col-xs-12 control-label' .col-sm-10.col-xs-12.input-group = text_field_tag :description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly' .input-group-btn - = clipboard_button(clipboard_target: '#description') + = clipboard_button(target: '#description') .form-group = label_tag nil, 'Command trigger word', class: 'col-sm-2 col-xs-12 control-label' @@ -46,7 +46,7 @@ .col-sm-10.col-xs-12.input-group = text_field_tag :request_url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly' .input-group-btn - = clipboard_button(clipboard_target: '#request_url') + = clipboard_button(target: '#request_url') .form-group = label_tag nil, 'Request method', class: 'col-sm-2 col-xs-12 control-label' @@ -57,14 +57,14 @@ .col-sm-10.col-xs-12.input-group = text_field_tag :response_username, 'GitLab', class: 'form-control input-sm', readonly: 'readonly' .input-group-btn - = clipboard_button(clipboard_target: '#response_username') + = clipboard_button(target: '#response_username') .form-group = label_tag :response_icon, 'Response icon', class: 'col-sm-2 col-xs-12 control-label' .col-sm-10.col-xs-12.input-group = text_field_tag :response_icon, asset_url('gitlab_logo.png'), class: 'form-control input-sm', readonly: 'readonly' .input-group-btn - = clipboard_button(clipboard_target: '#response_icon') + = clipboard_button(target: '#response_icon') .form-group = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label' @@ -75,14 +75,14 @@ .col-sm-10.col-xs-12.input-group = text_field_tag :autocomplete_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly' .input-group-btn - = clipboard_button(clipboard_target: '#autocomplete_hint') + = clipboard_button(target: '#autocomplete_hint') .form-group = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-xs-12 control-label' .col-sm-10.col-xs-12.input-group = text_field_tag :autocomplete_description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly' .input-group-btn - = clipboard_button(clipboard_target: '#autocomplete_description') + = clipboard_button(target: '#autocomplete_description') %hr diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml index 078b7be6865..73b99453a4b 100644 --- a/app/views/projects/services/slack_slash_commands/_help.html.haml +++ b/app/views/projects/services/slack_slash_commands/_help.html.haml @@ -40,7 +40,7 @@ .col-sm-10.col-xs-12.input-group = text_field_tag :url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly' .input-group-btn - = clipboard_button(clipboard_target: '#url') + = clipboard_button(target: '#url') .form-group = label_tag nil, 'Method', class: 'col-sm-2 col-xs-12 control-label' @@ -51,7 +51,7 @@ .col-sm-10.col-xs-12.input-group = text_field_tag :customize_name, 'GitLab', class: 'form-control input-sm', readonly: 'readonly' .input-group-btn - = clipboard_button(clipboard_target: '#customize_name') + = clipboard_button(target: '#customize_name') .form-group = label_tag nil, 'Customize icon', class: 'col-sm-2 col-xs-12 control-label' @@ -68,21 +68,21 @@ .col-sm-10.col-xs-12.input-group = text_field_tag :autocomplete_description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly' .input-group-btn - = clipboard_button(clipboard_target: '#autocomplete_description') + = clipboard_button(target: '#autocomplete_description') .form-group = label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-sm-2 col-xs-12 control-label' .col-sm-10.col-xs-12.input-group = text_field_tag :autocomplete_usage_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly' .input-group-btn - = clipboard_button(clipboard_target: '#autocomplete_usage_hint') + = clipboard_button(target: '#autocomplete_usage_hint') .form-group = label_tag :descriptive_label, 'Descriptive label', class: 'col-sm-2 col-xs-12 control-label' .col-sm-10.col-xs-12.input-group = text_field_tag :descriptive_label, 'Perform common operations on GitLab project', class: 'form-control input-sm', readonly: 'readonly' .input-group-btn - = clipboard_button(clipboard_target: '#descriptive_label') + = clipboard_button(target: '#descriptive_label') %hr diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index 6855c463c6d..2497a2d91b1 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -10,7 +10,7 @@ %i.fa.fa-angle-right %small.light = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" - = clipboard_button(clipboard_text: @commit.id, title: "Copy commit SHA to clipboard") + = clipboard_button(text: @commit.id, title: "Copy commit SHA to clipboard") = time_ago_with_tooltip(@commit.committed_date) \- = @commit.full_title diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml index ed68e0ed56d..9b5f63ae81a 100644 --- a/app/views/projects/triggers/_trigger.html.haml +++ b/app/views/projects/triggers/_trigger.html.haml @@ -2,7 +2,7 @@ %td - if can?(current_user, :admin_trigger, trigger) %span= trigger.token - = clipboard_button(clipboard_text: trigger.token, title: "Copy trigger token to clipboard") + = clipboard_button(text: trigger.token, title: "Copy trigger token to clipboard") - else %span= trigger.short_token diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml index 5211ade1a5f..86178257af8 100644 --- a/app/views/projects/wikis/_main_links.html.haml +++ b/app/views/projects/wikis/_main_links.html.haml @@ -1,9 +1,9 @@ - if (@page && @page.persisted?) - if can?(current_user, :create_wiki, @project) = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do - New Page + New page = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do - Page History + Page history - if can?(current_user, :create_wiki, @project) && @page.latest? = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn" do Edit diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml index 3d33679f07d..ba47574563d 100644 --- a/app/views/projects/wikis/_new.html.haml +++ b/app/views/projects/wikis/_new.html.haml @@ -18,4 +18,4 @@ Tip: You can specify the full path for the new file. We will automatically create any missing directories. .form-actions - = button_tag 'Create Page', class: 'build-new-wiki btn btn-create' + = button_tag 'Create page', class: 'build-new-wiki btn btn-create' diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index 8cf018da1b7..b995d08cd02 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -22,10 +22,10 @@ .nav-controls - if can?(current_user, :create_wiki, @project) = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do - New Page + New page - if @page.persisted? = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do - Page History + Page history - if can?(current_user, :admin_wiki, @project) = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do Delete diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 03684389742..34a4d7398bc 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -19,7 +19,7 @@ = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true .input-group-btn - = clipboard_button(clipboard_target: '#project_clone', title: "Copy URL to clipboard") + = clipboard_button(target: '#project_clone', title: "Copy URL to clipboard") :javascript $('ul.clone-options-dropdown a').on('click',function(e){ diff --git a/app/views/shared/_personal_access_tokens_form.html.haml b/app/views/shared/_personal_access_tokens_form.html.haml index af4cc90f4a7..e8062848fc3 100644 --- a/app/views/shared/_personal_access_tokens_form.html.haml +++ b/app/views/shared/_personal_access_tokens_form.html.haml @@ -1,4 +1,4 @@ -- type = impersonation ? "Impersonation" : "Personal Access" +- type = impersonation ? "impersonation" : "personal access" %h5.prepend-top-0 Add a #{type} Token @@ -22,7 +22,7 @@ = render 'shared/tokens/scopes_form', prefix: 'personal_access_token', token: token, scopes: scopes .prepend-top-default - = f.submit "Create #{type} Token", class: "btn btn-create" + = f.submit "Create #{type} token", class: "btn btn-create" :javascript var $dateField = $('.datepicker'); diff --git a/app/views/shared/_personal_access_tokens_table.html.haml b/app/views/shared/_personal_access_tokens_table.html.haml index 67a49815478..ab7a2db002e 100644 --- a/app/views/shared/_personal_access_tokens_table.html.haml +++ b/app/views/shared/_personal_access_tokens_table.html.haml @@ -33,7 +33,7 @@ - if impersonation %td.token-token-container = text_field_tag 'impersonation-token-token', token.token, readonly: true, class: "form-control" - = clipboard_button(clipboard_text: token.token) + = clipboard_button(text: token.token) - path = impersonation ? revoke_admin_user_impersonation_token_path(token.user, token) : revoke_profile_personal_access_token_path(token) %td= link_to "Revoke", path, method: :put, class: "btn btn-danger pull-right", data: { confirm: "Are you sure you want to revoke this #{type} Token? This action cannot be undone." } - else diff --git a/app/views/shared/icons/_icon_arrow_circle_o_right.svg b/app/views/shared/icons/_icon_arrow_circle_o_right.svg new file mode 100644 index 00000000000..db28b5e2d7a --- /dev/null +++ b/app/views/shared/icons/_icon_arrow_circle_o_right.svg @@ -0,0 +1 @@ +<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1280 896q0 14-9 23l-320 320q-9 9-23 9-13 0-22.5-9.5t-9.5-22.5v-192h-352q-13 0-22.5-9.5t-9.5-22.5v-192q0-13 9.5-22.5t22.5-9.5h352v-192q0-14 9-23t23-9q12 0 24 10l319 319q9 9 9 23zm160 0q0-148-73-273t-198-198-273-73-273 73-198 198-73 273 73 273 198 198 273 73 273-73 198-198 73-273zm224 0q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/></svg> diff --git a/app/views/shared/icons/_icon_check_square_o.svg b/app/views/shared/icons/_icon_check_square_o.svg new file mode 100644 index 00000000000..3dfbfc8c0e9 --- /dev/null +++ b/app/views/shared/icons/_icon_check_square_o.svg @@ -0,0 +1 @@ +<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1472 930v318q0 119-84.5 203.5t-203.5 84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q63 0 117 25 15 7 18 23 3 17-9 29l-49 49q-10 10-23 10-3 0-9-2-23-6-45-6h-832q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113v-254q0-13 9-22l64-64q10-10 23-10 6 0 12 3 20 8 20 29zm231-489l-814 814q-24 24-57 24t-57-24l-430-430q-24-24-24-57t24-57l110-110q24-24 57-24t57 24l263 263 647-647q24-24 57-24t57 24l110 110q24 24 24 57t-24 57z"/></svg> diff --git a/app/views/shared/icons/_icon_clock_o.svg b/app/views/shared/icons/_icon_clock_o.svg new file mode 100644 index 00000000000..8ddce62614c --- /dev/null +++ b/app/views/shared/icons/_icon_clock_o.svg @@ -0,0 +1 @@ +<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1024 544v448q0 14-9 23t-23 9h-320q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h224v-352q0-14 9-23t23-9h64q14 0 23 9t9 23zm416 352q0-148-73-273t-198-198-273-73-273 73-198 198-73 273 73 273 198 198 273 73 273-73 198-198 73-273zm224 0q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/></svg> diff --git a/app/views/shared/icons/_code_fork.svg b/app/views/shared/icons/_icon_code_fork.svg index 8347dce2d5b..5a0df2eee19 100644 --- a/app/views/shared/icons/_code_fork.svg +++ b/app/views/shared/icons/_icon_code_fork.svg @@ -1 +1 @@ -<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M672 1472q0-40-28-68t-68-28-68 28-28 68 28 68 68 28 68-28 28-68zm0-1152q0-40-28-68t-68-28-68 28-28 68 28 68 68 28 68-28 28-68zm640 128q0-40-28-68t-68-28-68 28-28 68 28 68 68 28 68-28 28-68zm96 0q0 52-26 96.5t-70 69.5q-2 287-226 414-68 38-203 81-128 40-169.5 71t-41.5 100v26q44 25 70 69.5t26 96.5q0 80-56 136t-136 56-136-56-56-136q0-52 26-96.5t70-69.5v-820q-44-25-70-69.5t-26-96.5q0-80 56-136t136-56 136 56 56 136q0 52-26 96.5t-70 69.5v497q54-26 154-57 55-17 87.5-29.5t70.5-31 59-39.5 40.5-51 28-69.5 8.5-91.5q-44-25-70-69.5t-26-96.5q0-80 56-136t136-56 136 56 56 136z"/></svg>
\ No newline at end of file +<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M672 1472q0-40-28-68t-68-28-68 28-28 68 28 68 68 28 68-28 28-68zm0-1152q0-40-28-68t-68-28-68 28-28 68 28 68 68 28 68-28 28-68zm640 128q0-40-28-68t-68-28-68 28-28 68 28 68 68 28 68-28 28-68zm96 0q0 52-26 96.5t-70 69.5q-2 287-226 414-68 38-203 81-128 40-169.5 71t-41.5 100v26q44 25 70 69.5t26 96.5q0 80-56 136t-136 56-136-56-56-136q0-52 26-96.5t70-69.5v-820q-44-25-70-69.5t-26-96.5q0-80 56-136t136-56 136 56 56 136q0 52-26 96.5t-70 69.5v497q54-26 154-57 55-17 87.5-29.5t70.5-31 59-39.5 40.5-51 28-69.5 8.5-91.5q-44-25-70-69.5t-26-96.5q0-80 56-136t136-56 136 56 56 136z"/></svg> diff --git a/app/views/shared/icons/_comment_o.svg b/app/views/shared/icons/_icon_comment_o.svg index 55807f0840a..b99bd5f42c8 100644 --- a/app/views/shared/icons/_comment_o.svg +++ b/app/views/shared/icons/_icon_comment_o.svg @@ -1 +1 @@ -<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M896 384q-204 0-381.5 69.5t-282 187.5-104.5 255q0 112 71.5 213.5t201.5 175.5l87 50-27 96q-24 91-70 172 152-63 275-171l43-38 57 6q69 8 130 8 204 0 381.5-69.5t282-187.5 104.5-255-104.5-255-282-187.5-381.5-69.5zm896 512q0 174-120 321.5t-326 233-450 85.5q-70 0-145-8-198 175-460 242-49 14-114 22h-5q-15 0-27-10.5t-16-27.5v-1q-3-4-.5-12t2-10 4.5-9.5l6-9 7-8.5 8-9q7-8 31-34.5t34.5-38 31-39.5 32.5-51 27-59 26-76q-157-89-247.5-220t-90.5-281q0-174 120-321.5t326-233 450-85.5 450 85.5 326 233 120 321.5z"/></svg>
\ No newline at end of file +<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M896 384q-204 0-381.5 69.5t-282 187.5-104.5 255q0 112 71.5 213.5t201.5 175.5l87 50-27 96q-24 91-70 172 152-63 275-171l43-38 57 6q69 8 130 8 204 0 381.5-69.5t282-187.5 104.5-255-104.5-255-282-187.5-381.5-69.5zm896 512q0 174-120 321.5t-326 233-450 85.5q-70 0-145-8-198 175-460 242-49 14-114 22h-5q-15 0-27-10.5t-16-27.5v-1q-3-4-.5-12t2-10 4.5-9.5l6-9 7-8.5 8-9q7-8 31-34.5t34.5-38 31-39.5 32.5-51 27-59 26-76q-157-89-247.5-220t-90.5-281q0-174 120-321.5t326-233 450-85.5 450 85.5 326 233 120 321.5z"/></svg> diff --git a/app/views/shared/icons/_icon_commit.svg b/app/views/shared/icons/_icon_commit.svg index 15e83dfdb53..7e9c0ded04e 100644 --- a/app/views/shared/icons/_icon_commit.svg +++ b/app/views/shared/icons/_icon_commit.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 18" enable-background="new 0 0 36 18"><path d="m34 7h-7.2c-.9-4-4.5-7-8.8-7s-7.9 3-8.8 7h-7.2c-1.1 0-2 .9-2 2 0 1.1.9 2 2 2h7.2c.9 4 4.5 7 8.8 7s7.9-3 8.8-7h7.2c1.1 0 2-.9 2-2 0-1.1-.9-2-2-2m-16 7c-2.8 0-5-2.2-5-5s2.2-5 5-5 5 2.2 5 5-2.2 5-5 5"/></svg>
\ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 18" enable-background="new 0 0 36 18"><path d="m34 7h-7.2c-.9-4-4.5-7-8.8-7s-7.9 3-8.8 7h-7.2c-1.1 0-2 .9-2 2 0 1.1.9 2 2 2h7.2c.9 4 4.5 7 8.8 7s7.9-3 8.8-7h7.2c1.1 0 2-.9 2-2 0-1.1-.9-2-2-2m-16 7c-2.8 0-5-2.2-5-5s2.2-5 5-5 5 2.2 5 5-2.2 5-5 5"/></svg> diff --git a/app/views/shared/icons/_icon_edit.svg b/app/views/shared/icons/_icon_edit.svg new file mode 100644 index 00000000000..cd4e34147e1 --- /dev/null +++ b/app/views/shared/icons/_icon_edit.svg @@ -0,0 +1 @@ +<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M888 1184l116-116-152-152-116 116v56h96v96h56zm440-720q-16-16-33 1l-350 350q-17 17-1 33t33-1l350-350q17-17 1-33zm80 594v190q0 119-84.5 203.5t-203.5 84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q63 0 117 25 15 7 18 23 3 17-9 29l-49 49q-14 14-32 8-23-6-45-6h-832q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113v-126q0-13 9-22l64-64q15-15 35-7t20 29zm-96-738l288 288-672 672h-288v-288zm444 132l-92 92-288-288 92-92q28-28 68-28t68 28l152 152q28 28 28 68t-28 68z"/></svg> diff --git a/app/views/shared/icons/_icon_eye.svg b/app/views/shared/icons/_icon_eye.svg new file mode 100644 index 00000000000..2e2ae67142f --- /dev/null +++ b/app/views/shared/icons/_icon_eye.svg @@ -0,0 +1 @@ +<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1664 960q-152-236-381-353 61 104 61 225 0 185-131.5 316.5t-316.5 131.5-316.5-131.5-131.5-316.5q0-121 61-225-229 117-381 353 133 205 333.5 326.5t434.5 121.5 434.5-121.5 333.5-326.5zm-720-384q0-20-14-34t-34-14q-125 0-214.5 89.5t-89.5 214.5q0 20 14 34t34 14 34-14 14-34q0-86 61-147t147-61q20 0 34-14t14-34zm848 384q0 34-20 69-140 230-376.5 368.5t-499.5 138.5-499.5-139-376.5-368q-20-35-20-69t20-69q140-229 376.5-368t499.5-139 499.5 139 376.5 368q20 35 20 69z"/></svg> diff --git a/app/views/shared/icons/_icon_eye_slash.svg b/app/views/shared/icons/_icon_eye_slash.svg new file mode 100644 index 00000000000..a16c5dcb24b --- /dev/null +++ b/app/views/shared/icons/_icon_eye_slash.svg @@ -0,0 +1 @@ +<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M555 1335l78-141q-87-63-136-159t-49-203q0-121 61-225-229 117-381 353 167 258 427 375zm389-759q0-20-14-34t-34-14q-125 0-214.5 89.5t-89.5 214.5q0 20 14 34t34 14 34-14 14-34q0-86 61-147t147-61q20 0 34-14t14-34zm363-191q0 7-1 9-105 188-315 566t-316 567l-49 89q-10 16-28 16-12 0-134-70-16-10-16-28 0-12 44-87-143-65-263.5-173t-208.5-245q-20-31-20-69t20-69q153-235 380-371t496-136q89 0 180 17l54-97q10-16 28-16 5 0 18 6t31 15.5 33 18.5 31.5 18.5 19.5 11.5q16 10 16 27zm37 447q0 139-79 253.5t-209 164.5l280-502q8 45 8 84zm448 128q0 35-20 69-39 64-109 145-150 172-347.5 267t-419.5 95l74-132q212-18 392.5-137t301.5-307q-115-179-282-294l63-112q95 64 182.5 153t144.5 184q20 34 20 69z"/></svg> diff --git a/app/views/shared/icons/_icon_merge.svg b/app/views/shared/icons/_icon_merge.svg new file mode 100644 index 00000000000..451ae12afbc --- /dev/null +++ b/app/views/shared/icons/_icon_merge.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="m5 5.563v4.875c1.024.4 1.75 1.397 1.75 2.563 0 1.519-1.231 2.75-2.75 2.75-1.519 0-2.75-1.231-2.75-2.75 0-1.166.726-2.162 1.75-2.563v-4.875c-1.024-.4-1.75-1.397-1.75-2.563 0-1.519 1.231-2.75 2.75-2.75 1.519 0 2.75 1.231 2.75 2.75 0 1.166-.726 2.162-1.75 2.563m-1 8.687c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25m0-10c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25"/><path d="m10.501 2c1.381.001 2.499 1.125 2.499 2.506v5.931c1.024.4 1.75 1.397 1.75 2.563 0 1.519-1.231 2.75-2.75 2.75-1.519 0-2.75-1.231-2.75-2.75 0-1.166.726-2.162 1.75-2.563v-5.931c0-.279-.225-.506-.499-.506v.926c0 .346-.244.474-.569.271l-2.952-1.844c-.314-.196-.325-.507 0-.71l2.952-1.844c.314-.196.569-.081.569.271v.93m1.499 12.25c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25"/></svg> diff --git a/app/views/shared/icons/_icon_merged.svg b/app/views/shared/icons/_icon_merged.svg new file mode 100644 index 00000000000..d8f96558bea --- /dev/null +++ b/app/views/shared/icons/_icon_merged.svg @@ -0,0 +1 @@ +<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path d="M0 7c0-3.866 3.142-7 7-7 3.866 0 7 3.142 7 7 0 3.866-3.142 7-7 7-3.866 0-7-3.142-7-7z"/><path d="M1 7c0 3.309 2.69 6 6 6 3.309 0 6-2.69 6-6 0-3.309-2.69-6-6-6-3.309 0-6 2.69-6 6z" fill="#FFF"/><path d="M9.427 6.523a.932.932 0 0 0-.808.489v-.01c-.49-.01-1.059-.172-1.46-.489-.35-.278-.7-.772-.882-1.17a.964.964 0 0 0 .35-.744.943.943 0 0 0-.934-.959c-.518 0-.933.432-.933.964 0 .35.191.662.467.825v3.147a.97.97 0 0 0-.467.825c0 .532.415.959.933.959a.943.943 0 0 0 .934-.96.965.965 0 0 0-.467-.824V6.844c.313.336.672.61 1.073.81.402.202.948.303 1.386.308v-.01c.168.293.467.49.808.49a.943.943 0 0 0 .933-.96.943.943 0 0 0-.933-.96z"/></svg> diff --git a/app/views/shared/icons/_icon_pencil.svg b/app/views/shared/icons/_icon_pencil.svg new file mode 100644 index 00000000000..a3b48404f87 --- /dev/null +++ b/app/views/shared/icons/_icon_pencil.svg @@ -0,0 +1 @@ +<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M491 1536l91-91-235-235-91 91v107h128v128h107zm523-928q0-22-22-22-10 0-17 7l-542 542q-7 7-7 17 0 22 22 22 10 0 17-7l542-542q7-7 7-17zm-54-192l416 416-832 832h-416v-416zm683 96q0 53-37 90l-166 166-416-416 166-165q36-38 90-38 53 0 91 38l235 234q37 39 37 91z"/></svg> diff --git a/app/views/shared/icons/_icon_random.svg b/app/views/shared/icons/_icon_random.svg new file mode 100644 index 00000000000..763bd2d3dd8 --- /dev/null +++ b/app/views/shared/icons/_icon_random.svg @@ -0,0 +1 @@ +<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M666 481q-60 92-137 273-22-45-37-72.5t-40.5-63.5-51-56.5-63-35-81.5-14.5h-224q-14 0-23-9t-9-23v-192q0-14 9-23t23-9h224q250 0 410 225zm1126 799q0 14-9 23l-320 320q-9 9-23 9-13 0-22.5-9.5t-9.5-22.5v-192q-32 0-85 .5t-81 1-73-1-71-5-64-10.5-63-18.5-58-28.5-59-40-55-53.5-56-69.5q59-93 136-273 22 45 37 72.5t40.5 63.5 51 56.5 63 35 81.5 14.5h256v-192q0-14 9-23t23-9q12 0 24 10l319 319q9 9 9 23zm0-896q0 14-9 23l-320 320q-9 9-23 9-13 0-22.5-9.5t-9.5-22.5v-192h-256q-48 0-87 15t-69 45-51 61.5-45 77.5q-32 62-78 171-29 66-49.5 111t-54 105-64 100-74 83-90 68.5-106.5 42-128 16.5h-224q-14 0-23-9t-9-23v-192q0-14 9-23t23-9h224q48 0 87-15t69-45 51-61.5 45-77.5q32-62 78-171 29-66 49.5-111t54-105 64-100 74-83 90-68.5 106.5-42 128-16.5h256v-192q0-14 9-23t23-9q12 0 24 10l319 319q9 9 9 23z"/></svg> diff --git a/app/views/shared/icons/_icon_tags.svg b/app/views/shared/icons/_icon_tags.svg new file mode 100644 index 00000000000..fc5acc89c5e --- /dev/null +++ b/app/views/shared/icons/_icon_tags.svg @@ -0,0 +1 @@ +<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M384 448q0-53-37.5-90.5t-90.5-37.5-90.5 37.5-37.5 90.5 37.5 90.5 90.5 37.5 90.5-37.5 37.5-90.5zm1067 576q0 53-37 90l-491 492q-39 37-91 37-53 0-90-37l-715-716q-38-37-64.5-101t-26.5-117v-416q0-52 38-90t90-38h416q53 0 117 26.5t102 64.5l715 714q37 39 37 91zm384 0q0 53-37 90l-491 492q-39 37-91 37-36 0-59-14t-53-45l470-470q37-37 37-90 0-52-37-91l-715-714q-38-38-102-64.5t-117-26.5h224q53 0 117 26.5t102 64.5l715 714q37 39 37 91z"/></svg> diff --git a/app/views/shared/icons/_icon_user.svg b/app/views/shared/icons/_icon_user.svg new file mode 100644 index 00000000000..9b8cd74d62b --- /dev/null +++ b/app/views/shared/icons/_icon_user.svg @@ -0,0 +1 @@ +<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1600 1405q0 120-73 189.5t-194 69.5h-874q-121 0-194-69.5t-73-189.5q0-53 3.5-103.5t14-109 26.5-108.5 43-97.5 62-81 85.5-53.5 111.5-20q9 0 42 21.5t74.5 48 108 48 133.5 21.5 133.5-21.5 108-48 74.5-48 42-21.5q61 0 111.5 20t85.5 53.5 62 81 43 97.5 26.5 108.5 14 109 3.5 103.5zm-320-893q0 159-112.5 271.5t-271.5 112.5-271.5-112.5-112.5-271.5 112.5-271.5 271.5-112.5 271.5 112.5 112.5 271.5z"/></svg> diff --git a/app/views/shared/icons/_trash_o.svg b/app/views/shared/icons/_trash_o.svg index ea073d7fe67..0d7a91ab536 100644 --- a/app/views/shared/icons/_trash_o.svg +++ b/app/views/shared/icons/_trash_o.svg @@ -1 +1 @@ -<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M704 736v576q0 14-9 23t-23 9h-64q-14 0-23-9t-9-23v-576q0-14 9-23t23-9h64q14 0 23 9t9 23zm256 0v576q0 14-9 23t-23 9h-64q-14 0-23-9t-9-23v-576q0-14 9-23t23-9h64q14 0 23 9t9 23zm256 0v576q0 14-9 23t-23 9h-64q-14 0-23-9t-9-23v-576q0-14 9-23t23-9h64q14 0 23 9t9 23zm128 724v-948h-896v948q0 22 7 40.5t14.5 27 10.5 8.5h832q3 0 10.5-8.5t14.5-27 7-40.5zm-672-1076h448l-48-117q-7-9-17-11h-317q-10 2-17 11zm928 32v64q0 14-9 23t-23 9h-96v948q0 83-47 143.5t-113 60.5h-832q-66 0-113-58.5t-47-141.5v-952h-96q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h309l70-167q15-37 54-63t79-26h320q40 0 79 26t54 63l70 167h309q14 0 23 9t9 23z"/></svg>
\ No newline at end of file +<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M704 736v576q0 14-9 23t-23 9h-64q-14 0-23-9t-9-23v-576q0-14 9-23t23-9h64q14 0 23 9t9 23zm256 0v576q0 14-9 23t-23 9h-64q-14 0-23-9t-9-23v-576q0-14 9-23t23-9h64q14 0 23 9t9 23zm256 0v576q0 14-9 23t-23 9h-64q-14 0-23-9t-9-23v-576q0-14 9-23t23-9h64q14 0 23 9t9 23zm128 724v-948h-896v948q0 22 7 40.5t14.5 27 10.5 8.5h832q3 0 10.5-8.5t14.5-27 7-40.5zm-672-1076h448l-48-117q-7-9-17-11h-317q-10 2-17 11zm928 32v64q0 14-9 23t-23 9h-96v948q0 83-47 143.5t-113 60.5h-832q-66 0-113-58.5t-47-141.5v-952h-96q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h309l70-167q15-37 54-63t79-26h320q40 0 79 26t54 63l70 167h309q14 0 23 9t9 23z"/></svg> diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 92d2d93a732..2e0d6a129fb 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -160,13 +160,13 @@ - project_ref = cross_project_reference(@project, issuable) .block.project-reference .sidebar-collapsed-icon.dont-change-state - = clipboard_button(clipboard_text: project_ref, title: "Copy reference to clipboard", placement: "left") + = clipboard_button(text: project_ref, title: "Copy reference to clipboard", placement: "left") .cross-project-reference.hide-collapsed %span Reference: %cite{ title: project_ref } = project_ref - = clipboard_button(clipboard_text: project_ref, title: "Copy reference to clipboard", placement: "left") + = clipboard_button(text: project_ref, title: "Copy reference to clipboard", placement: "left") :javascript gl.IssuableResource = new gl.SubbableResource('#{issuable_json_path(issuable)}'); diff --git a/app/views/shared/labels/_form.html.haml b/app/views/shared/labels/_form.html.haml index 647e05e5ff7..e8b04f56839 100644 --- a/app/views/shared/labels/_form.html.haml +++ b/app/views/shared/labels/_form.html.haml @@ -29,5 +29,5 @@ - if @label.persisted? = f.submit 'Save changes', class: 'btn btn-save js-save-button' - else - = f.submit 'Create Label', class: 'btn btn-create js-save-button' + = f.submit 'Create label', class: 'btn btn-create js-save-button' = link_to 'Cancel', back_path, class: 'btn btn-cancel' diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml index 2810f1377b2..ccc808ff43e 100644 --- a/app/views/shared/milestones/_sidebar.html.haml +++ b/app/views/shared/milestones/_sidebar.html.haml @@ -122,10 +122,10 @@ - if milestone_ref.present? .block.reference .sidebar-collapsed-icon.dont-change-state - = clipboard_button(clipboard_text: milestone_ref, title: "Copy reference to clipboard", placement: "left") + = clipboard_button(text: milestone_ref, title: "Copy reference to clipboard", placement: "left") .cross-project-reference.hide-collapsed %span Reference: %cite{ title: milestone_ref } = milestone_ref - = clipboard_button(clipboard_text: milestone_ref, title: "Copy reference to clipboard", placement: "left") + = clipboard_button(text: milestone_ref, title: "Copy reference to clipboard", placement: "left") diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index 37e2a377a69..ee3be3c789a 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -89,7 +89,7 @@ = f.label :enable_ssl_verification do = f.check_box :enable_ssl_verification %strong Enable SSL verification - = f.submit "Add Webhook", class: "btn btn-create" + = f.submit "Add webhook", class: "btn btn-create" %hr %h5.prepend-top-default Webhooks (#{hooks.count}) diff --git a/app/views/u2f/_register.html.haml b/app/views/u2f/_register.html.haml index adc07bcba73..00788e77b6b 100644 --- a/app/views/u2f/_register.html.haml +++ b/app/views/u2f/_register.html.haml @@ -7,13 +7,13 @@ - if current_user.two_factor_otp_enabled? .row.append-bottom-10 .col-md-3 - %button#js-setup-u2f-device.btn.btn-info Setup New U2F Device + %button#js-setup-u2f-device.btn.btn-info Setup new U2F device .col-md-9 %p Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left. - else .row.append-bottom-10 .col-md-3 - %button#js-setup-u2f-device.btn.btn-info{ disabled: true } Setup New U2F Device + %button#js-setup-u2f-device.btn.btn-info{ disabled: true } Setup new U2F device .col-md-9 %p.text-warning You need to register a two-factor authentication app before you can set up a U2F device. @@ -36,7 +36,7 @@ = text_field_tag 'u2f_registration[name]', nil, class: 'form-control', placeholder: "Pick a name" .col-md-3 = hidden_field_tag 'u2f_registration[device_response]', nil, class: 'form-control', required: true, id: "js-device-response" - = submit_tag "Register U2F Device", class: "btn btn-success" + = submit_tag "Register U2F device", class: "btn btn-success" :javascript var u2fRegister = new U2FRegister($("#js-register-u2f"), gon.u2f); diff --git a/changelogs/unreleased/8998_skip_pending_commits_if_not_head.yml b/changelogs/unreleased/8998_skip_pending_commits_if_not_head.yml new file mode 100644 index 00000000000..9852cd6e4ff --- /dev/null +++ b/changelogs/unreleased/8998_skip_pending_commits_if_not_head.yml @@ -0,0 +1,4 @@ +--- +title: Cancel pending pipelines if commits not HEAD +merge_request: 9362 +author: Rydkin Maxim diff --git a/changelogs/unreleased/button-capitalization.yml b/changelogs/unreleased/button-capitalization.yml new file mode 100644 index 00000000000..13b3beea40c --- /dev/null +++ b/changelogs/unreleased/button-capitalization.yml @@ -0,0 +1,4 @@ +--- +title: Changed capitalisation of buttons across GitLab +merge_request: 10418 +author: diff --git a/changelogs/unreleased/dm-copy-diff-file-title-as-gfm.yml b/changelogs/unreleased/dm-copy-diff-file-title-as-gfm.yml new file mode 100644 index 00000000000..506883bc17d --- /dev/null +++ b/changelogs/unreleased/dm-copy-diff-file-title-as-gfm.yml @@ -0,0 +1,4 @@ +--- +title: After copying a diff file or blob path, pasting it into a comment field will format it as Markdown. +merge_request: 9876 +author: diff --git a/changelogs/unreleased/menu-shortcut.yml b/changelogs/unreleased/menu-shortcut.yml new file mode 100644 index 00000000000..74803498f58 --- /dev/null +++ b/changelogs/unreleased/menu-shortcut.yml @@ -0,0 +1,4 @@ +--- +title: Add keyboard shortcuts to main menu +merge_request: +author: diff --git a/changelogs/unreleased/mr-new-page-changing-url.yml b/changelogs/unreleased/mr-new-page-changing-url.yml new file mode 100644 index 00000000000..39de1eaa523 --- /dev/null +++ b/changelogs/unreleased/mr-new-page-changing-url.yml @@ -0,0 +1,4 @@ +--- +title: Fixed tabs on new merge request page causing incorrect URLs +merge_request: +author: diff --git a/changelogs/unreleased/optimise-pipelines-json.yml b/changelogs/unreleased/optimise-pipelines-json.yml new file mode 100644 index 00000000000..948679dcbeb --- /dev/null +++ b/changelogs/unreleased/optimise-pipelines-json.yml @@ -0,0 +1,4 @@ +--- +title: Optimise pipelines.json endpoint +merge_request: +author: diff --git a/changelogs/unreleased/update-issue-board-cards-design.yml b/changelogs/unreleased/update-issue-board-cards-design.yml new file mode 100644 index 00000000000..5ef94a74e8a --- /dev/null +++ b/changelogs/unreleased/update-issue-board-cards-design.yml @@ -0,0 +1,4 @@ +--- +title: Update issue board cards design +merge_request: 10353 +author: diff --git a/db/migrate/20170305180853_add_auto_cancel_pending_pipelines_to_project.rb b/db/migrate/20170305180853_add_auto_cancel_pending_pipelines_to_project.rb new file mode 100644 index 00000000000..aa64f2dddca --- /dev/null +++ b/db/migrate/20170305180853_add_auto_cancel_pending_pipelines_to_project.rb @@ -0,0 +1,15 @@ +class AddAutoCancelPendingPipelinesToProject < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default(:projects, :auto_cancel_pending_pipelines, :integer, default: 0) + end + + def down + remove_column(:projects, :auto_cancel_pending_pipelines) + end +end diff --git a/db/migrate/20170312114329_add_auto_canceled_by_id_to_pipeline.rb b/db/migrate/20170312114329_add_auto_canceled_by_id_to_pipeline.rb new file mode 100644 index 00000000000..1690ce90564 --- /dev/null +++ b/db/migrate/20170312114329_add_auto_canceled_by_id_to_pipeline.rb @@ -0,0 +1,9 @@ +class AddAutoCanceledByIdToPipeline < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :ci_pipelines, :auto_canceled_by_id, :integer + end +end diff --git a/db/migrate/20170312114529_add_auto_canceled_by_id_foreign_key_to_pipeline.rb b/db/migrate/20170312114529_add_auto_canceled_by_id_foreign_key_to_pipeline.rb new file mode 100644 index 00000000000..1e7b02ecf0e --- /dev/null +++ b/db/migrate/20170312114529_add_auto_canceled_by_id_foreign_key_to_pipeline.rb @@ -0,0 +1,22 @@ +class AddAutoCanceledByIdForeignKeyToPipeline < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + on_delete = + if Gitlab::Database.mysql? + :nullify + else + 'SET NULL' + end + + add_concurrent_foreign_key :ci_pipelines, :ci_pipelines, column: :auto_canceled_by_id, on_delete: on_delete + end + + def down + remove_foreign_key :ci_pipelines, column: :auto_canceled_by_id + end +end diff --git a/db/migrate/20170402231018_remove_index_for_users_current_sign_in_at.rb b/db/migrate/20170402231018_remove_index_for_users_current_sign_in_at.rb index 0237c3189a5..9d4380ef960 100644 --- a/db/migrate/20170402231018_remove_index_for_users_current_sign_in_at.rb +++ b/db/migrate/20170402231018_remove_index_for_users_current_sign_in_at.rb @@ -15,7 +15,7 @@ class RemoveIndexForUsersCurrentSignInAt < ActiveRecord::Migration if Gitlab::Database.postgresql? execute 'DROP INDEX CONCURRENTLY index_users_on_current_sign_in_at;' else - remove_index :users, :current_sign_in_at + remove_concurrent_index :users, :current_sign_in_at end end end diff --git a/db/migrate/20170406114958_add_auto_canceled_by_id_to_ci_builds.rb b/db/migrate/20170406114958_add_auto_canceled_by_id_to_ci_builds.rb new file mode 100644 index 00000000000..c1d803b4308 --- /dev/null +++ b/db/migrate/20170406114958_add_auto_canceled_by_id_to_ci_builds.rb @@ -0,0 +1,9 @@ +class AddAutoCanceledByIdToCiBuilds < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :ci_builds, :auto_canceled_by_id, :integer + end +end diff --git a/db/migrate/20170406115029_add_auto_canceled_by_id_foreign_key_to_ci_builds.rb b/db/migrate/20170406115029_add_auto_canceled_by_id_foreign_key_to_ci_builds.rb new file mode 100644 index 00000000000..3004683933b --- /dev/null +++ b/db/migrate/20170406115029_add_auto_canceled_by_id_foreign_key_to_ci_builds.rb @@ -0,0 +1,22 @@ +class AddAutoCanceledByIdForeignKeyToCiBuilds < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + on_delete = + if Gitlab::Database.mysql? + :nullify + else + 'SET NULL' + end + + add_concurrent_foreign_key :ci_builds, :ci_pipelines, column: :auto_canceled_by_id, on_delete: on_delete + end + + def down + remove_foreign_key :ci_builds, column: :auto_canceled_by_id + end +end diff --git a/db/schema.rb b/db/schema.rb index f1783ab1b05..457957536b0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -222,6 +222,7 @@ ActiveRecord::Schema.define(version: 20170405080720) do t.string "token" t.integer "lock_version" t.string "coverage_regex" + t.integer "auto_canceled_by_id" end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree @@ -250,6 +251,7 @@ ActiveRecord::Schema.define(version: 20170405080720) do t.integer "duration" t.integer "user_id" t.integer "lock_version" + t.integer "auto_canceled_by_id" end add_index "ci_pipelines", ["project_id", "ref", "status"], name: "index_ci_pipelines_on_project_id_and_ref_and_status", using: :btree @@ -945,6 +947,7 @@ ActiveRecord::Schema.define(version: 20170405080720) do t.boolean "lfs_enabled" t.text "description_html" t.boolean "only_allow_merge_if_all_discussions_are_resolved" + t.integer "auto_cancel_pending_pipelines", default: 0, null: false t.boolean "printing_merge_request_link_enabled", default: true, null: false t.string "import_jid" end @@ -1327,6 +1330,8 @@ ActiveRecord::Schema.define(version: 20170405080720) do add_foreign_key "boards", "projects" add_foreign_key "chat_teams", "namespaces", on_delete: :cascade + add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify + add_foreign_key "ci_pipelines", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_262d4c2d19", on_delete: :nullify add_foreign_key "ci_trigger_schedules", "ci_triggers", column: "trigger_id", name: "fk_90a406cc94", on_delete: :cascade add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade add_foreign_key "container_repositories", "projects" diff --git a/doc/api/issues.md b/doc/api/issues.md index 5702cdcf3c1..54c099d4bf8 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -29,17 +29,15 @@ GET /issues?iids[]=42&iids[]=43 GET /issues?search=issue+title+or+description ``` -|-------------+----------------+----------+-----------------------------------------------------------------------------------------------------------------------------| | Attribute | Type | Required | Description | -|-------------+----------------+----------+-----------------------------------------------------------------------------------------------------------------------------| +|-------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------------------| | `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | | `milestone` | string | no | The milestone title | | `iids` | Array[integer] | no | Return only the issues having the given `iid` | | `order_by` | string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | -| `search` | string | no | Search issues against their `title` and `description` | -|-------------+----------------+----------+-----------------------------------------------------------------------------------------------------------------------------| +| `search` | string | no | Search issues against their `title` and `description` | ```bash curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/issues @@ -111,9 +109,8 @@ GET /groups/:id/issues?iids[]=42&iids[]=43 GET /groups/:id/issues?search=issue+title+or+description ``` -|-------------+----------------+----------+-----------------------------------------------------------------------------------------------------------------------------| | Attribute | Type | Required | Description | -|-------------+----------------+----------+-----------------------------------------------------------------------------------------------------------------------------| +|-------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------------------| | `id` | integer | yes | The ID of a group | | `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | @@ -122,7 +119,6 @@ GET /groups/:id/issues?search=issue+title+or+description | `order_by` | string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Search group issues against their `title` and `description` | -|-------------+----------------+----------+-----------------------------------------------------------------------------------------------------------------------------| ```bash @@ -195,9 +191,8 @@ GET /projects/:id/issues?iids[]=42&iids[]=43 GET /projects/:id/issues?search=issue+title+or+description ``` -|-------------+----------------+----------+-----------------------------------------------------------------------------------------------------------------------------| | Attribute | Type | Required | Description | -|-------------+----------------+----------+-----------------------------------------------------------------------------------------------------------------------------| +|-------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------------------| | `id` | integer | yes | The ID of a project | | `iids` | Array[integer] | no | Return only the milestone having the given `iid` | | `state` | string | no | Return all issues or just those that are `opened` or `closed` | @@ -206,7 +201,6 @@ GET /projects/:id/issues?search=issue+title+or+description | `order_by` | string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Search project issues against their `title` and `description` | -|-------------+----------------+----------+-----------------------------------------------------------------------------------------------------------------------------| ```bash @@ -270,12 +264,10 @@ Get a single project issue. GET /projects/:id/issues/:issue_iid ``` -|-------------+---------+----------+--------------------------------------| | Attribute | Type | Required | Description | -|-------------+---------+----------+--------------------------------------| +|-------------|---------|----------|--------------------------------------| | `id` | integer | yes | The ID of a project | | `issue_iid` | integer | yes | The internal ID of a project's issue | -|-------------+---------+----------+--------------------------------------| ```bash curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/41 @@ -337,23 +329,19 @@ Creates a new project issue. POST /projects/:id/issues ``` -|-------------------------------------------+---------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------| -| Attribute | Type | Required | Description | -|-------------------------------------------+---------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------| -| `id` | integer | yes | The ID of a project | -| `title` | string | yes | The title of an issue | -| `description` | string | no | The description of an issue | -| `confidential` | boolean | no | Set an issue to be confidential. Default is `false`. | -| `assignee_id` | integer | no | The ID of a user to assign issue | -| `milestone_id` | integer | no | The ID of a milestone to assign issue | -| `labels` | string | no | Comma-separated label names for an issue | -| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) | -| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | -| `merge_request_to_resolve_discussions_of` | integer | no | The IID of a merge request in which to resolve all issues. This will fill the issue with a default description and mark all discussions as resolved. | -| - | - | - | When passing a description or title, these values will take precedence over the default values. | -| `discussion_to_resolve` | string | no | The ID of a discussion to resolve. This will fill in the issue with a default description and mark the discussion | -| - | - | - | as resolved. Use in combination with `merge_request_to_resolve_discussions_of`. | -|-------------------------------------------+---------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------| +| Attribute | Type | Required | Description | +|-------------------------------------------|---------|----------|--------------| +| `id` | integer | yes | The ID of a project | +| `title` | string | yes | The title of an issue | +| `description` | string | no | The description of an issue | +| `confidential` | boolean | no | Set an issue to be confidential. Default is `false`. | +| `assignee_id` | integer | no | The ID of a user to assign issue | +| `milestone_id` | integer | no | The ID of a milestone to assign issue | +| `labels` | string | no | Comma-separated label names for an issue | +| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) | +| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | +| `merge_request_to_resolve_discussions_of` | integer | no | The IID of a merge request in which to resolve all issues. This will fill the issue with a default description and mark all discussions as resolved. When passing a description or title, these values will take precedence over the default values.| +| `discussion_to_resolve` | string | no | The ID of a discussion to resolve. This will fill in the issue with a default description and mark the discussion as resolved. Use in combination with `merge_request_to_resolve_discussions_of`. | ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues?title=Issues%20with%20auth&labels=bug @@ -401,9 +389,8 @@ closed. PUT /projects/:id/issues/:issue_iid ``` -|----------------+---------+----------+------------------------------------------------------------------------------------------------------------| | Attribute | Type | Required | Description | -|----------------+---------+----------+------------------------------------------------------------------------------------------------------------| +|----------------|---------|----------|------------------------------------------------------------------------------------------------------------| | `id` | integer | yes | The ID of a project | | `issue_iid` | integer | yes | The internal ID of a project's issue | | `title` | string | no | The title of an issue | @@ -415,7 +402,6 @@ PUT /projects/:id/issues/:issue_iid | `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it | | `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) | | `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | -|----------------+---------+----------+------------------------------------------------------------------------------------------------------------| ```bash curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/85?state_event=close @@ -462,12 +448,10 @@ Only for admins and project owners. Soft deletes the issue in question. DELETE /projects/:id/issues/:issue_iid ``` -|-------------+---------+----------+--------------------------------------| | Attribute | Type | Required | Description | -|-------------+---------+----------+--------------------------------------| +|-------------|---------|----------|--------------------------------------| | `id` | integer | yes | The ID of a project | | `issue_iid` | integer | yes | The internal ID of a project's issue | -|-------------+---------+----------+--------------------------------------| ```bash curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/85 @@ -486,13 +470,11 @@ project, it will then be assigned to the issue that is being moved. POST /projects/:id/issues/:issue_iid/move ``` -|-----------------+---------+----------+--------------------------------------| | Attribute | Type | Required | Description | -|-----------------+---------+----------+--------------------------------------| +|-----------------|---------|----------|--------------------------------------| | `id` | integer | yes | The ID of a project | | `issue_iid` | integer | yes | The internal ID of a project's issue | | `to_project_id` | integer | yes | The ID of the new project | -|-----------------+---------+----------+--------------------------------------| ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/85/move @@ -544,12 +526,10 @@ is returned. POST /projects/:id/issues/:issue_iid/subscribe ``` -|-------------+---------+----------+--------------------------------------| | Attribute | Type | Required | Description | -|-------------+---------+----------+--------------------------------------| +|-------------|---------|----------|--------------------------------------| | `id` | integer | yes | The ID of a project | | `issue_iid` | integer | yes | The internal ID of a project's issue | -|-------------+---------+----------+--------------------------------------| ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/subscribe @@ -601,12 +581,10 @@ status code `304` is returned. POST /projects/:id/issues/:issue_iid/unsubscribe ``` -|-------------+---------+----------+--------------------------------------| | Attribute | Type | Required | Description | -|-------------+---------+----------+--------------------------------------| +|-------------|---------|----------|--------------------------------------| | `id` | integer | yes | The ID of a project | | `issue_iid` | integer | yes | The internal ID of a project's issue | -|-------------+---------+----------+--------------------------------------| ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/unsubscribe @@ -622,12 +600,10 @@ returned. POST /projects/:id/issues/:issue_iid/todo ``` -|-------------+---------+----------+--------------------------------------| | Attribute | Type | Required | Description | -|-------------+---------+----------+--------------------------------------| +|-------------|---------|----------|--------------------------------------| | `id` | integer | yes | The ID of a project | | `issue_iid` | integer | yes | The internal ID of a project's issue | -|-------------+---------+----------+--------------------------------------| ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/todo @@ -715,13 +691,11 @@ Sets an estimated time of work for this issue. POST /projects/:id/issues/:issue_iid/time_estimate ``` -|-------------+---------+----------+------------------------------------------| | Attribute | Type | Required | Description | -|-------------+---------+----------+------------------------------------------| +|-------------|---------|----------|------------------------------------------| | `id` | integer | yes | The ID of a project | | `issue_iid` | integer | yes | The internal ID of a project's issue | | `duration` | string | yes | The duration in human format. e.g: 3h30m | -|-------------+---------+----------+------------------------------------------| ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/time_estimate?duration=3h30m @@ -746,12 +720,10 @@ Resets the estimated time for this issue to 0 seconds. POST /projects/:id/issues/:issue_iid/reset_time_estimate ``` -|-------------+---------+----------+--------------------------------------| | Attribute | Type | Required | Description | -|-------------+---------+----------+--------------------------------------| +|-------------|---------|----------|--------------------------------------| | `id` | integer | yes | The ID of a project | | `issue_iid` | integer | yes | The internal ID of a project's issue | -|-------------+---------+----------+--------------------------------------| ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/reset_time_estimate @@ -776,13 +748,11 @@ Adds spent time for this issue POST /projects/:id/issues/:issue_iid/add_spent_time ``` -|-------------+---------+----------+------------------------------------------| | Attribute | Type | Required | Description | -|-------------+---------+----------+------------------------------------------| +|-------------|---------|----------|------------------------------------------| | `id` | integer | yes | The ID of a project | | `issue_iid` | integer | yes | The internal ID of a project's issue | | `duration` | string | yes | The duration in human format. e.g: 3h30m | -|-------------+---------+----------+------------------------------------------| ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/add_spent_time?duration=1h @@ -807,12 +777,10 @@ Resets the total spent time for this issue to 0 seconds. POST /projects/:id/issues/:issue_iid/reset_spent_time ``` -|-------------+---------+----------+--------------------------------------| | Attribute | Type | Required | Description | -|-------------+---------+----------+--------------------------------------| +|-------------|---------|----------|--------------------------------------| | `id` | integer | yes | The ID of a project | | `issue_iid` | integer | yes | The internal ID of a project's issue | -|-------------+---------+----------+--------------------------------------| ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/reset_spent_time @@ -835,12 +803,10 @@ Example response: GET /projects/:id/issues/:issue_iid/time_stats ``` -|-------------+---------+----------+--------------------------------------| | Attribute | Type | Required | Description | -|-------------+---------+----------+--------------------------------------| +|-------------|---------|----------|--------------------------------------| | `id` | integer | yes | The ID of a project | | `issue_iid` | integer | yes | The internal ID of a project's issue | -|-------------+---------+----------+--------------------------------------| ```bash curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/time_stats diff --git a/doc/user/project/integrations/img/prometheus_environment_detail_with_metrics.png b/doc/user/project/integrations/img/prometheus_environment_detail_with_metrics.png Binary files differindex 1f5a44f8820..214b10624a9 100644 --- a/doc/user/project/integrations/img/prometheus_environment_detail_with_metrics.png +++ b/doc/user/project/integrations/img/prometheus_environment_detail_with_metrics.png diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index 12d7700176c..a74014b6b2f 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -160,23 +160,19 @@ The queries utilized by GitLab are shown in the following table. Once configured, GitLab will attempt to retrieve performance metrics for any environment which has had a successful deployment. If monitoring data was -successfully retrieved, a metrics button will appear on the environment's +successfully retrieved, a Monitoring button will appear on the environment's detail page. ![Environment Detail with Metrics](img/prometheus_environment_detail_with_metrics.png) -Clicking on the metrics button will display a new page, showing up to the last +Clicking on the Monitoring button will display a new page, showing up to the last 8 hours of performance data. It may take a minute or two for data to appear after initial deployment. ## Troubleshooting -If the metrics button is not appearing, then one of a few issues may be -occurring: +If the "Attempting to load performance data" screen continues to appear, it could be due to: -- GitLab is not able to reach the Prometheus server. A test request can be sent - to the Prometheus server from the [Prometheus Service](#configuration-in-gitlab) - configuration screen. - No successful deployments have occurred to this environment. - Prometheus does not have performance data for this environment, or the metrics are not labeled correctly. To test this, connect to the Prometheus server and diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md index c398ac2eb25..88246e22391 100644 --- a/doc/user/project/pipelines/settings.md +++ b/doc/user/project/pipelines/settings.md @@ -60,6 +60,14 @@ anyone and those logged in respectively. If you wish to hide it so that only the members of the project or group have access to it, uncheck the **Public pipelines** checkbox and save the changes. +## Auto-cancel pending pipelines + +> [Introduced][ce-9362] in GitLab 9.1. + +If you want to auto-cancel all pending non-HEAD pipelines on branch, when +new pipeline will be created (after your git push or manually from UI), +check **Auto-cancel pending pipelines** checkbox and save the changes. + ## Badges In the pipelines settings page you can find pipeline status and test coverage @@ -111,3 +119,4 @@ into your `README.md`: [var]: ../../../ci/yaml/README.md#git-strategy [coverage report]: #test-coverage-parsing +[ce-9362]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9362 diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature index b47fca31ef2..cbbea237825 100644 --- a/features/project/shortcuts.feature +++ b/features/project/shortcuts.feature @@ -26,7 +26,7 @@ Feature: Project Shortcuts @javascript Scenario: Navigate to repository charts tab - Given I press "g" and "g" + Given I press "g" and "d" Then the active sub tab should be Charts And the active main tab should be Repository diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index 5ede935c562..894c4a96bb8 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -36,7 +36,7 @@ Feature: Project Source Browse Files And I edit code And I fill the new file name And I fill the commit message - And I click on "Commit Changes" + And I click on "Commit changes" Then I am redirected to the new file And I should see its new content @@ -47,7 +47,7 @@ Feature: Project Source Browse Files And I edit code And I fill the new file name And I fill the commit message - And I click on "Commit Changes" + And I click on "Commit changes" Then I am redirected to the fork's new merge request page And I can see the new commit message @@ -57,7 +57,7 @@ Feature: Project Source Browse Files And I edit code with new lines at end of file And I fill the new file name And I fill the commit message - And I click on "Commit Changes" + And I click on "Commit changes" Then I am redirected to the new file And I click button "Edit" And I should see its content with new lines preserved at end of file @@ -69,7 +69,7 @@ Feature: Project Source Browse Files And I fill the new file name And I fill the commit message And I fill the new branch name - And I click on "Commit Changes" + And I click on "Commit changes" Then I am redirected to the new merge request page When I click on "Changes" tab And I should see its new content @@ -173,7 +173,7 @@ Feature: Project Source Browse Files And I click button "Edit" And I edit code And I fill the commit message - And I click on "Commit Changes" + And I click on "Commit changes" Then I am redirected to the ".gitignore" And I should see its new content @@ -186,7 +186,7 @@ Feature: Project Source Browse Files And I click button "Fork" And I edit code And I fill the commit message - And I click on "Commit Changes" + And I click on "Commit changes" Then I am redirected to the fork's new merge request page And I can see the new commit message @@ -197,7 +197,7 @@ Feature: Project Source Browse Files And I edit code And I fill the commit message And I fill the new branch name - And I click on "Commit Changes" + And I click on "Commit changes" Then I am redirected to the new merge request page Then I click on "Changes" tab And I should see its new content diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index 33a1c88e33c..c715c85c43c 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -18,11 +18,11 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps step 'I should see last push widget' do expect(page).to have_content "You pushed to fix" - expect(page).to have_link "Create Merge Request" + expect(page).to have_link "Create merge request" end - step 'I click "Create Merge Request" link' do - click_link "Create Merge Request" + step 'I click "Create merge request" link' do + click_link "Create merge request" end step 'I see prefilled new Merge Request page' do diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb index d4a04f693b8..4fb16d3bb57 100644 --- a/features/steps/dashboard/new_project.rb +++ b/features/steps/dashboard/new_project.rb @@ -3,9 +3,9 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps include SharedPaths include SharedProject - step 'I click "New Project" link' do + step 'I click "New project" link' do page.within('.content') do - click_link "New Project" + click_link "New project" end end diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb index 9996f3baf0d..f8f5e3f2382 100644 --- a/features/steps/group/milestones.rb +++ b/features/steps/group/milestones.rb @@ -46,11 +46,11 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps end step 'I click new milestone button' do - click_link "New Milestone" + click_link "New milestone" end step 'I press create mileston button' do - click_button "Create Milestone" + click_button "Create milestone" end step 'milestone in each project should be created' do diff --git a/features/steps/project/builds/summary.rb b/features/steps/project/builds/summary.rb index 124582de6b9..229e5d7cdf4 100644 --- a/features/steps/project/builds/summary.rb +++ b/features/steps/project/builds/summary.rb @@ -12,7 +12,7 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps step 'I see button to CI Lint' do page.within('.nav-controls') do - ci_lint_tool_link = page.find_link('CI Lint') + ci_lint_tool_link = page.find_link('CI lint') expect(ci_lint_tool_link[:href]).to eq ci_lint_path end end diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index cf75fac8ac6..97ffd4b4ea2 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -13,7 +13,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I click atom feed link' do - click_link "Commits Feed" + click_link "Commits feed" end step 'I see commits atom feed' do @@ -110,16 +110,16 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I see button to create a new merge request' do - expect(page).to have_link 'Create Merge Request' + expect(page).to have_link 'Create merge request' end step 'I should not see button to create a new merge request' do - expect(page).not_to have_link 'Create Merge Request' + expect(page).not_to have_link 'Create merge request' end step 'I should see button to the merge request' do merge_request = MergeRequest.find_by(title: 'Feature') - expect(page).to have_link "View Open Merge Request", href: namespace_project_merge_request_path(@project.namespace, @project, merge_request) + expect(page).to have_link "View open merge request", href: namespace_project_merge_request_path(@project.namespace, @project, merge_request) end step 'I see breadcrumb links' do diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb index 580a19494c2..ec59a2c094e 100644 --- a/features/steps/project/deploy_keys.rb +++ b/features/steps/project/deploy_keys.rb @@ -26,7 +26,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps end step 'I click \'New Deploy Key\'' do - click_link 'New Deploy Key' + click_link 'New deploy key' end step 'I submit new deploy key' do diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb index 0a71833a8a1..945d58a6458 100644 --- a/features/steps/project/hooks.rb +++ b/features/steps/project/hooks.rb @@ -25,14 +25,14 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps step 'I submit new hook' do @url = 'http://example.org/1' fill_in "hook_url", with: @url - expect { click_button "Add Webhook" }.to change(ProjectHook, :count).by(1) + expect { click_button "Add webhook" }.to change(ProjectHook, :count).by(1) end step 'I submit new hook with SSL verification enabled' do @url = 'http://example.org/2' fill_in "hook_url", with: @url check "hook_enable_ssl_verification" - expect { click_button "Add Webhook" }.to change(ProjectHook, :count).by(1) + expect { click_button "Add webhook" }.to change(ProjectHook, :count).by(1) end step 'I should see newly created hook' do diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index aaf0ede67e6..c0dc48f1bb2 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -61,7 +61,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps expect(page).to have_content "Tweet control" end - step 'I click link "New Issue"' do + step 'I click link "New issue"' do page.has_link?('New Issue') ? click_link('New Issue') : click_link('New issue') end diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb index 4a35b71af2f..2828e41f731 100644 --- a/features/steps/project/issues/labels.rb +++ b/features/steps/project/issues/labels.rb @@ -31,19 +31,19 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps step 'I submit new label \'support\'' do fill_in 'Title', with: 'support' fill_in 'Background color', with: '#F95610' - click_button 'Create Label' + click_button 'Create label' end step 'I submit new label \'bug\'' do fill_in 'Title', with: 'bug' fill_in 'Background color', with: '#F95610' - click_button 'Create Label' + click_button 'Create label' end step 'I submit new label with invalid color' do fill_in 'Title', with: 'support' fill_in 'Background color', with: '#12' - click_button 'Create Label' + click_button 'Create label' end step 'I should see label label exist error message' do diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb index 4faa0f4707c..fe94eb03acd 100644 --- a/features/steps/project/issues/milestones.rb +++ b/features/steps/project/issues/milestones.rb @@ -16,7 +16,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps end step 'I click link "New Milestone"' do - click_link "New Milestone" + click_link "New milestone" end step 'I submit new milestone "v2.3"' do diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index a67b3222af3..3985fe8f2f7 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -300,10 +300,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps page.within('.current-note-edit-form', visible: true) do fill_in 'note_note', with: 'Typo, please fix' - click_button 'Save Comment' + click_button 'Save comment' end - expect(page).not_to have_button 'Save Comment', disabled: true, visible: true + expect(page).not_to have_button 'Save comment', disabled: true, visible: true end end @@ -381,7 +381,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'merge request is mergeable' do - expect(page).to have_button 'Accept Merge Request' + expect(page).to have_button 'Accept merge request' end step 'I modify merge commit message' do @@ -395,7 +395,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I accept this merge request' do page.within '.mr-state-widget' do - click_button "Accept Merge Request" + click_button "Accept merge request" end end diff --git a/features/steps/project/merge_requests/acceptance.rb b/features/steps/project/merge_requests/acceptance.rb index 0a3f4649870..bdc7a616ba9 100644 --- a/features/steps/project/merge_requests/acceptance.rb +++ b/features/steps/project/merge_requests/acceptance.rb @@ -15,15 +15,15 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps end step 'I click on Accept Merge Request' do - click_button('Accept Merge Request') + click_button('Accept merge request') end step 'I should see the Remove Source Branch button' do - expect(page).to have_link('Remove Source Branch') + expect(page).to have_link('Remove source branch') end step 'I should not see the Remove Source Branch button' do - expect(page).not_to have_link('Remove Source Branch') + expect(page).not_to have_link('Remove source branch') end step 'There is an open Merge Request' do diff --git a/features/steps/project/merge_requests/revert.rb b/features/steps/project/merge_requests/revert.rb index 31f95b524b3..a8f4e4ef027 100644 --- a/features/steps/project/merge_requests/revert.rb +++ b/features/steps/project/merge_requests/revert.rb @@ -26,7 +26,7 @@ class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps end step 'I click on Accept Merge Request' do - click_button('Accept Merge Request') + click_button('Accept merge request') end step 'I am signed in as a developer of the project' do diff --git a/features/steps/project/project_find_file.rb b/features/steps/project/project_find_file.rb index b8da5e6435d..461160b8430 100644 --- a/features/steps/project/project_find_file.rb +++ b/features/steps/project/project_find_file.rb @@ -9,7 +9,7 @@ class Spinach::Features::ProjectFindFile < Spinach::FeatureSteps end step 'I click Find File button' do - click_link 'Find File' + click_link 'Find file' end step 'I should see "find file" page' do diff --git a/features/steps/project/project_shortcuts.rb b/features/steps/project/project_shortcuts.rb index 8143b01ca40..cebf09750b0 100644 --- a/features/steps/project/project_shortcuts.rb +++ b/features/steps/project/project_shortcuts.rb @@ -20,9 +20,9 @@ class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps find('body').native.send_key('n') end - step 'I press "g" and "g"' do - find('body').native.send_key('g') + step 'I press "g" and "d"' do find('body').native.send_key('g') + find('body').native.send_key('d') end step 'I press "g" and "s"' do diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 29f628763db..5bd3c1a1246 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -105,11 +105,11 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I click link "Diff"' do - click_link 'Preview Changes' + click_link 'Preview changes' end - step 'I click on "Commit Changes"' do - click_button 'Commit Changes' + step 'I click on "Commit changes"' do + click_button 'Commit changes' end step 'I click on "Changes" tab' do diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb index 9183de76881..115b67d98fb 100644 --- a/features/steps/project/source/markdown_render.rb +++ b/features/steps/project/source/markdown_render.rb @@ -214,7 +214,9 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps step 'I add various links to the wiki page' do fill_in "wiki[content]", with: "[test](test)\n[GitLab API doc](api)\n[Rake tasks](raketasks)\n" fill_in "wiki[message]", with: "Adding links to wiki" - click_button "Create page" + page.within '.wiki-form' do + click_button "Create page" + end end step 'Wiki page should have added links' do @@ -225,7 +227,9 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps step 'I add a header to the wiki page' do fill_in "wiki[content]", with: "# Wiki header\n" fill_in "wiki[message]", with: "Add header to wiki" - click_button "Create page" + page.within '.wiki-form' do + click_button "Create page" + end end step 'Wiki header should have correct id and link' do diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb index 4cb0a21fbb4..517c257d892 100644 --- a/features/steps/project/wiki.rb +++ b/features/steps/project/wiki.rb @@ -16,12 +16,16 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps step 'I create the Wiki Home page' do fill_in "wiki_content", with: '[link test](test)' - click_on "Create page" + page.within '.wiki-form' do + click_on "Create page" + end end step 'I create the Wiki Home page with no content' do fill_in "wiki_content", with: '' - click_on "Create page" + page.within '.wiki-form' do + click_on "Create page" + end end step 'I should see the newly created wiki page' do @@ -29,7 +33,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps expect(page).to have_content "link test" click_link "link test" - expect(page).to have_content "Create Page" + expect(page).to have_content "Create page" end step 'I have an existing Wiki page' do @@ -63,7 +67,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps end step 'I click the History button' do - click_on "History" + click_on 'Page history' end step 'I should see both revisions' do @@ -121,15 +125,19 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps step 'I should see the new wiki page form' do expect(current_path).to match('wikis/image.jpg') expect(page).to have_content('New Wiki Page') - expect(page).to have_content('Create Page') + expect(page).to have_content('Create page') end step 'I create a New page with paths' do - click_on 'New Page' + click_on 'New page' fill_in 'Page slug', with: 'one/two/three-test' - click_on 'Create Page' + page.within '#modal-new-wiki' do + click_on 'Create page' + end fill_in "wiki_content", with: 'wiki content' - click_on "Create page" + page.within '.wiki-form' do + click_on "Create page" + end expect(current_path).to include 'one/two/three-test' end @@ -154,11 +162,11 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps step 'I view the page history of a Wiki page that has a path' do click_on 'Three' - click_on 'Page History' + click_on 'Page history' end step 'I click on Page History' do - click_on 'Page History' + click_on 'Page history' end step 'I should see the page history' do diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb index fd925e0d447..7885cc7ab77 100644 --- a/features/steps/shared/note.rb +++ b/features/steps/shared/note.rb @@ -141,7 +141,7 @@ module SharedNote page.within(".current-note-edit-form") do fill_in 'note[note]', with: '+1 Awesome!' - click_button 'Save Comment' + click_button 'Save comment' end end diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb index 630fe4fa849..11167632e07 100644 --- a/lib/gitlab/etag_caching/middleware.rb +++ b/lib/gitlab/etag_caching/middleware.rb @@ -10,6 +10,22 @@ module Gitlab { regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/issues/\d+/rendered_title\z), name: 'issue_title' + }, + { + regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/pipelines\.json\z), + name: 'project_pipelines' + }, + { + regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/commit/\s+/pipelines\.json\z), + name: 'commit_pipelines' + }, + { + regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/merge_requests/new\.json\z), + name: 'new_merge_request_pipelines' + }, + { + regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/merge_requests/\d+/pipelines\.json\z), + name: 'merge_request_pipelines' } ].freeze @@ -65,7 +81,7 @@ module Gitlab status_code = Gitlab::PollingInterval.polling_enabled? ? 304 : 429 - [status_code, { 'ETag' => etag }, ['']] + [status_code, { 'ETag' => etag }, []] end def track_cache_miss(if_none_match, cached_value_present, route) diff --git a/spec/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb index 2295455575d..c3a29d8bf04 100644 --- a/spec/factories/ci/triggers.rb +++ b/spec/factories/ci/triggers.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :ci_trigger_without_token, class: Ci::Trigger do factory :ci_trigger do - token 'token' + sequence(:token) { |n| "token#{n}" } factory :ci_trigger_for_trigger_schedule do token { SecureRandom.hex(15) } diff --git a/spec/features/admin/admin_deploy_keys_spec.rb b/spec/features/admin/admin_deploy_keys_spec.rb index 7ce6cce0a5c..c0b6995a84a 100644 --- a/spec/features/admin/admin_deploy_keys_spec.rb +++ b/spec/features/admin/admin_deploy_keys_spec.rb @@ -18,7 +18,7 @@ RSpec.describe 'admin deploy keys', type: :feature do describe 'create new deploy key' do before do visit admin_deploy_keys_path - click_link 'New Deploy Key' + click_link 'New deploy key' end it 'creates new deploy key' do diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index a871e370ba2..56d5c7c327e 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -24,7 +24,7 @@ feature 'Admin Groups', feature: true do it 'creates new group' do visit admin_groups_path - click_link "New Group" + click_link "New group" fill_in 'group_path', with: 'gitlab' fill_in 'group_description', with: 'Group description' click_button "Create group" diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index 570c374a89b..fb519a9bf12 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -33,7 +33,7 @@ describe "Admin::Hooks", feature: true do fill_in 'hook_url', with: url check 'Enable SSL verification' - expect { click_button 'Add System Hook' }.to change(SystemHook, :count).by(1) + expect { click_button 'Add system hook' }.to change(SystemHook, :count).by(1) expect(page).to have_content 'SSL Verification: enabled' expect(current_path).to eq(admin_hooks_path) expect(page).to have_content(url) @@ -44,7 +44,7 @@ describe "Admin::Hooks", feature: true do before do WebMock.stub_request(:post, @system_hook.url) visit admin_hooks_path - click_link "Test Hook" + click_link "Test hook" end it { expect(current_path).to eq(admin_hooks_path) } diff --git a/spec/features/admin/admin_manage_applications_spec.rb b/spec/features/admin/admin_manage_applications_spec.rb index c2c618b5659..0079125889b 100644 --- a/spec/features/admin/admin_manage_applications_spec.rb +++ b/spec/features/admin/admin_manage_applications_spec.rb @@ -8,7 +8,7 @@ RSpec.describe 'admin manage applications', feature: true do it do visit admin_applications_path - click_on 'New Application' + click_on 'New application' expect(page).to have_content('New application') fill_in :doorkeeper_application_name, with: 'test' diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb index ff23d486355..0fb4baeb71c 100644 --- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb +++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb @@ -30,7 +30,7 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do check "api" check "read_user" - expect { click_on "Create Impersonation Token" }.to change { PersonalAccessTokensFinder.new(impersonation: true).execute.count } + expect { click_on "Create impersonation token" }.to change { PersonalAccessTokensFinder.new(impersonation: true).execute.count } expect(active_impersonation_tokens).to have_text(name) expect(active_impersonation_tokens).to have_text('In') expect(active_impersonation_tokens).to have_text('api') diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb index 009e9c6b04c..6f36c74c911 100644 --- a/spec/features/auto_deploy_spec.rb +++ b/spec/features/auto_deploy_spec.rb @@ -56,7 +56,7 @@ describe 'Auto deploy' do click_on 'OpenShift' end wait_for_ajax - click_button 'Commit Changes' + click_button 'Commit changes' expect(page).to have_content('New Merge Request From auto-deploy into master') end diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb index 1c0f97d8a1c..248c31115ad 100644 --- a/spec/features/boards/add_issues_modal_spec.rb +++ b/spec/features/boards/add_issues_modal_spec.rb @@ -145,7 +145,7 @@ describe 'Issue Boards add issue modal', :feature, :js do context 'selecing issues' do it 'selects single issue' do page.within('.add-issues-modal') do - first('.card').click + first('.card .card-number').click page.within('.nav-links') do expect(page).to have_content('Selected issues 1') @@ -155,7 +155,7 @@ describe 'Issue Boards add issue modal', :feature, :js do it 'changes button text' do page.within('.add-issues-modal') do - first('.card').click + first('.card .card-number').click expect(first('.add-issues-footer .btn')).to have_content('Add 1 issue') end @@ -163,7 +163,7 @@ describe 'Issue Boards add issue modal', :feature, :js do it 'changes button text with plural' do page.within('.add-issues-modal') do - all('.card').each do |el| + all('.card .card-number').each do |el| el.click end @@ -173,7 +173,7 @@ describe 'Issue Boards add issue modal', :feature, :js do it 'shows only selected issues on selected tab' do page.within('.add-issues-modal') do - first('.card').click + first('.card .card-number').click click_link 'Selected issues' @@ -203,7 +203,7 @@ describe 'Issue Boards add issue modal', :feature, :js do it 'selects all that arent already selected' do page.within('.add-issues-modal') do - first('.card').click + first('.card .card-number').click expect(page).to have_selector('.is-active', count: 1) @@ -215,11 +215,11 @@ describe 'Issue Boards add issue modal', :feature, :js do it 'unselects from selected tab' do page.within('.add-issues-modal') do - first('.card').click + first('.card .card-number').click click_link 'Selected issues' - first('.card').click + first('.card .card-number').click expect(page).not_to have_selector('.is-active') end @@ -229,7 +229,7 @@ describe 'Issue Boards add issue modal', :feature, :js do context 'adding issues' do it 'adds to board' do page.within('.add-issues-modal') do - first('.card').click + first('.card .card-number').click click_button 'Add 1 issue' end @@ -241,7 +241,7 @@ describe 'Issue Boards add issue modal', :feature, :js do it 'adds to second list' do page.within('.add-issues-modal') do - first('.card').click + first('.card .card-number').click click_button planning.title diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb index a5fc766401f..a9cc6c49f8e 100644 --- a/spec/features/boards/keyboard_shortcut_spec.rb +++ b/spec/features/boards/keyboard_shortcut_spec.rb @@ -14,7 +14,7 @@ describe 'Issue Boards shortcut', feature: true, js: true do end it 'takes user to issue board index' do - find('body').native.send_keys('gl') + find('body').native.send_keys('gb') expect(page).to have_selector('.boards-list') wait_for_vue_resource diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb index d5f8470fab0..19f5d1b0f30 100644 --- a/spec/features/dashboard/group_spec.rb +++ b/spec/features/dashboard/group_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'Dashboard Group', feature: true do it 'creates new grpup' do visit dashboard_groups_path - click_link 'New Group' + click_link 'New group' fill_in 'group_path', with: 'Samurai' fill_in 'group_description', with: 'Tokugawa Shogunate' diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb index 3642c0bfb5b..fa5524e18d8 100644 --- a/spec/features/dashboard/shortcuts_spec.rb +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -3,27 +3,23 @@ require 'spec_helper' feature 'Dashboard shortcuts', feature: true, js: true do before do login_as :user - visit dashboard_projects_path + visit root_dashboard_path end scenario 'Navigate to tabs' do - find('body').native.send_key('g') - find('body').native.send_key('p') + find('body').native.send_keys([:shift, 'P']) check_page_title('Projects') - find('body').native.send_key('g') - find('body').native.send_key('i') + find('body').native.send_key([:shift, 'I']) check_page_title('Issues') - find('body').native.send_key('g') - find('body').native.send_key('m') + find('body').native.send_key([:shift, 'M']) check_page_title('Merge Requests') - find('body').native.send_key('g') - find('body').native.send_key('t') + find('body').native.send_keys([:shift, 'T']) check_page_title('Todos') end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 8bfe6f4d54b..7670c4caea4 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -153,7 +153,7 @@ feature 'Group', feature: true do end it 'removes group' do - click_link 'Remove Group' + click_link 'Remove group' expect(page).to have_content "scheduled for deletion" end diff --git a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb index 7f11db3c417..77b7ba4ac7a 100644 --- a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb +++ b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb @@ -19,7 +19,7 @@ feature 'Check if mergeable with unresolved discussions', js: true, feature: tru it 'does not allow to merge' do visit_merge_request(merge_request) - expect(page).not_to have_button 'Accept Merge Request' + expect(page).not_to have_button 'Accept merge request' expect(page).to have_content('This merge request has unresolved discussions') end end @@ -32,7 +32,7 @@ feature 'Check if mergeable with unresolved discussions', js: true, feature: tru it 'allows MR to be merged' do visit_merge_request(merge_request) - expect(page).to have_button 'Accept Merge Request' + expect(page).to have_button 'Accept merge request' end end end @@ -46,7 +46,7 @@ feature 'Check if mergeable with unresolved discussions', js: true, feature: tru it 'does not allow to merge' do visit_merge_request(merge_request) - expect(page).to have_button 'Accept Merge Request' + expect(page).to have_button 'Accept merge request' end end @@ -58,7 +58,7 @@ feature 'Check if mergeable with unresolved discussions', js: true, feature: tru it 'allows MR to be merged' do visit_merge_request(merge_request) - expect(page).to have_button 'Accept Merge Request' + expect(page).to have_button 'Accept merge request' end end end diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index d4fe67c224f..3a4ec07b2b0 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -26,7 +26,7 @@ feature 'Create New Merge Request', feature: true, js: true do end it 'selects the target branch sha when a tag with the same name exists' do - visit namespace_project_merge_requests_path(project.namespace, project) + visit namespace_project_merge_requests_path(project.namespace, project) click_link 'New merge request' diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb index 79105b1ee46..497240803d4 100644 --- a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb +++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb @@ -32,7 +32,7 @@ feature 'Merge immediately', :feature, :js do page.within '.mr-widget-body' do find('.dropdown-toggle').click - click_link 'Merge Immediately' + click_link 'Merge immediately' expect(find('.js-merge-when-pipeline-succeeds-button')).to have_content('Merge in progress') diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb index ed7193b9777..646e7bab265 100644 --- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb @@ -28,25 +28,25 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do visit_merge_request(merge_request) end - it 'displays the Merge When Pipeline Succeeds button' do - expect(page).to have_button "Merge When Pipeline Succeeds" + it 'displays the Merge when pipeline succeeds button' do + expect(page).to have_button "Merge when pipeline succeeds" end - describe 'enabling Merge When Pipeline Succeeds' do - shared_examples 'Merge When Pipeline Succeeds activator' do - it 'activates the Merge When Pipeline Succeeds feature' do - click_button "Merge When Pipeline Succeeds" + describe 'enabling Merge when pipeline succeeds' do + shared_examples 'Merge when pipeline succeeds activator' do + it 'activates the Merge when pipeline succeeds feature' do + click_button "Merge when pipeline succeeds" expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds." expect(page).to have_content "The source branch will not be removed." - expect(page).to have_link "Cancel Automatic Merge" + expect(page).to have_link "Cancel automatic merge" visit_merge_request(merge_request) # Needed to refresh the page expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i end end context "when enabled immediately" do - it_behaves_like 'Merge When Pipeline Succeeds activator' + it_behaves_like 'Merge when pipeline succeeds activator' end context 'when enabled after pipeline status changed' do @@ -60,16 +60,16 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do expect(page).to have_content "Pipeline ##{pipeline.id} running" end - it_behaves_like 'Merge When Pipeline Succeeds activator' + it_behaves_like 'Merge when pipeline succeeds activator' end context 'when enabled after it was previously canceled' do before do - click_button "Merge When Pipeline Succeeds" - click_link "Cancel Automatic Merge" + click_button "Merge when pipeline succeeds" + click_link "Cancel automatic merge" end - it_behaves_like 'Merge When Pipeline Succeeds activator' + it_behaves_like 'Merge when pipeline succeeds activator' end context 'when it was enabled and then canceled' do @@ -83,10 +83,10 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do end before do - click_link "Cancel Automatic Merge" + click_link "Cancel automatic merge" end - it_behaves_like 'Merge When Pipeline Succeeds activator' + it_behaves_like 'Merge when pipeline succeeds activator' end end end @@ -110,18 +110,18 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do end it 'allows to cancel the automatic merge' do - click_link "Cancel Automatic Merge" + click_link "Cancel automatic merge" - expect(page).to have_button "Merge When Pipeline Succeeds" + expect(page).to have_button "Merge when pipeline succeeds" visit_merge_request(merge_request) # refresh the page expect(page).to have_content "canceled the automatic merge" end it "allows the user to remove the source branch" do - expect(page).to have_link "Remove Source Branch When Merged" + expect(page).to have_link "Remove source branch when merged" - click_link "Remove Source Branch When Merged" + click_link "Remove source branch when merged" expect(page).to have_content "The source branch will be removed" end @@ -141,7 +141,7 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do it "does not allow to enable merge when pipeline succeeds" do visit_merge_request(merge_request) - expect(page).not_to have_link 'Merge When Pipeline Succeeds' + expect(page).not_to have_link 'Merge when pipeline succeeds' end end diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb index 447764566e0..4a590e3bf68 100644 --- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb +++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb @@ -14,7 +14,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu it 'allows MR to be merged' do visit_merge_request(merge_request) - expect(page).to have_button 'Accept Merge Request' + expect(page).to have_button 'Accept merge request' end end @@ -38,8 +38,8 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu it 'does not allow to merge immediately' do visit_merge_request(merge_request) - expect(page).to have_button 'Merge When Pipeline Succeeds' - expect(page).not_to have_button 'Select Merge Moment' + expect(page).to have_button 'Merge when pipeline succeeds' + expect(page).not_to have_button 'Select merge moment' end end @@ -49,7 +49,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu it 'does not allow MR to be merged' do visit_merge_request(merge_request) - expect(page).not_to have_button 'Accept Merge Request' + expect(page).not_to have_button 'Accept merge request' expect(page).to have_content('Please retry the job or push a new commit to fix the failure.') end end @@ -60,7 +60,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu it 'does not allow MR to be merged' do visit_merge_request(merge_request) - expect(page).not_to have_button 'Accept Merge Request' + expect(page).not_to have_button 'Accept merge request' expect(page).to have_content('Please retry the job or push a new commit to fix the failure.') end end @@ -71,7 +71,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu it 'allows MR to be merged' do visit_merge_request(merge_request) - expect(page).to have_button 'Accept Merge Request' + expect(page).to have_button 'Accept merge request' end end @@ -81,7 +81,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu it 'allows MR to be merged' do visit_merge_request(merge_request) - expect(page).to have_button 'Accept Merge Request' + expect(page).to have_button 'Accept merge request' end end end @@ -97,10 +97,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu it 'allows MR to be merged immediately', js: true do visit_merge_request(merge_request) - expect(page).to have_button 'Merge When Pipeline Succeeds' + expect(page).to have_button 'Merge when pipeline succeeds' - click_button 'Select Merge Moment' - expect(page).to have_content 'Merge Immediately' + click_button 'Select merge moment' + expect(page).to have_content 'Merge immediately' end end @@ -110,7 +110,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu it 'allows MR to be merged' do visit_merge_request(merge_request) - expect(page).to have_button 'Accept Merge Request' + expect(page).to have_button 'Accept merge request' end end @@ -120,7 +120,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu it 'allows MR to be merged' do visit_merge_request(merge_request) - expect(page).to have_button 'Accept Merge Request' + expect(page).to have_button 'Accept merge request' end end end diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb index c2db7d8da3c..a62c5435748 100644 --- a/spec/features/merge_requests/widget_spec.rb +++ b/spec/features/merge_requests/widget_spec.rb @@ -145,7 +145,7 @@ describe 'Merge request', :feature, :js do before do allow_any_instance_of(Repository).to receive(:merge).and_return(false) visit namespace_project_merge_request_path(project.namespace, project, merge_request) - click_button 'Accept Merge Request' + click_button 'Accept merge request' wait_for_ajax end diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb index 99fba594651..27a20e78a43 100644 --- a/spec/features/profiles/personal_access_tokens_spec.rb +++ b/spec/features/profiles/personal_access_tokens_spec.rb @@ -41,7 +41,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do check "api" check "read_user" - click_on "Create Personal Access Token" + click_on "Create personal access token" expect(active_personal_access_tokens).to have_text(name) expect(active_personal_access_tokens).to have_text('In') expect(active_personal_access_tokens).to have_text('api') @@ -54,7 +54,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do visit profile_personal_access_tokens_path fill_in "Name", with: 'My PAT' - expect { click_on "Create Personal Access Token" }.not_to change { PersonalAccessToken.count } + expect { click_on "Create personal access token" }.not_to change { PersonalAccessToken.count } expect(page).to have_content("Name cannot be nil") end end diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index 46f2f487e0c..aab5a72678e 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -22,7 +22,7 @@ feature 'Editing file blob', feature: true, js: true do wait_for_ajax find('.js-edit-blob').click execute_script('ace.edit("editor").setValue("class NextFeature\nend\n")') - click_button 'Commit Changes' + click_button 'Commit changes' end context 'from MR diff' do diff --git a/spec/features/projects/blobs/user_create_spec.rb b/spec/features/projects/blobs/user_create_spec.rb index d214a531138..fa1a753afcb 100644 --- a/spec/features/projects/blobs/user_create_spec.rb +++ b/spec/features/projects/blobs/user_create_spec.rb @@ -22,7 +22,7 @@ feature 'New blob creation', feature: true, js: true do end def commit_file - click_button 'Commit Changes' + click_button 'Commit changes' end context 'with default target branch' do diff --git a/spec/features/projects/files/creating_a_file_spec.rb b/spec/features/projects/files/creating_a_file_spec.rb index ae448706130..5d7bd3dc4ce 100644 --- a/spec/features/projects/files/creating_a_file_spec.rb +++ b/spec/features/projects/files/creating_a_file_spec.rb @@ -19,7 +19,7 @@ feature 'User wants to create a file', feature: true do file_content = find('#file-content') file_content.set options[:file_content] || 'Some content' - click_button 'Commit Changes' + click_button 'Commit changes' end scenario 'file name contains Chinese characters' do diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb index 36a80d7575d..3e544316f28 100644 --- a/spec/features/projects/files/editing_a_file_spec.rb +++ b/spec/features/projects/files/editing_a_file_spec.rb @@ -27,7 +27,7 @@ feature 'User wants to edit a file', feature: true do scenario 'file has been updated since the user opened the edit page' do Files::UpdateService.new(project, user, commit_params).execute - click_button 'Commit Changes' + click_button 'Commit changes' expect(page).to have_content 'Someone edited the file the same time you did.' end diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb index 6b281e6d21d..8ff0f5898ec 100644 --- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb +++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb @@ -29,7 +29,7 @@ feature 'project owner creates a license file', feature: true, js: true do expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}") fill_in :commit_message, with: 'Add a LICENSE file', visible: true - click_button 'Commit Changes' + click_button 'Commit changes' expect(current_path).to eq( namespace_project_blob_path(project.namespace, project, 'master/LICENSE')) @@ -53,7 +53,7 @@ feature 'project owner creates a license file', feature: true, js: true do expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}") fill_in :commit_message, with: 'Add a LICENSE file', visible: true - click_button 'Commit Changes' + click_button 'Commit changes' expect(current_path).to eq( namespace_project_blob_path(project.namespace, project, 'master/LICENSE')) @@ -63,7 +63,7 @@ feature 'project owner creates a license file', feature: true, js: true do def select_template(template) page.within('.js-license-selector-wrap') do - click_button 'Apply a License template' + click_button 'Apply a license template' click_link template wait_for_ajax end diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb index 87322ac2584..1a1910455a1 100644 --- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb +++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb @@ -30,7 +30,7 @@ feature 'project owner sees a link to create a license file in empty project', f fill_in :commit_message, with: 'Add a LICENSE file', visible: true # Remove pre-receive hook so we can push without auth FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive')) - click_button 'Commit Changes' + click_button 'Commit changes' expect(current_path).to eq( namespace_project_blob_path(project.namespace, project, 'master/LICENSE')) @@ -40,7 +40,7 @@ feature 'project owner sees a link to create a license file in empty project', f def select_template(template) page.within('.js-license-selector-wrap') do - click_button 'Apply a License template' + click_button 'Apply a license template' click_link template wait_for_ajax end diff --git a/spec/features/projects/files/template_type_dropdown_spec.rb b/spec/features/projects/files/template_type_dropdown_spec.rb index 5ee5e5b4c4e..9fcf12e6cb9 100644 --- a/spec/features/projects/files/template_type_dropdown_spec.rb +++ b/spec/features/projects/files/template_type_dropdown_spec.rb @@ -48,7 +48,7 @@ feature 'Template type dropdown selector', js: true do context 'user previews changes' do before do - click_link 'Preview Changes' + click_link 'Preview changes' end scenario 'type selector is hidden and shown correctly' do @@ -102,7 +102,7 @@ def check_type_selector_display(is_visible) end def try_selecting_all_types - try_selecting_template_type('LICENSE', 'Apply a License template') + try_selecting_template_type('LICENSE', 'Apply a license template') try_selecting_template_type('Dockerfile', 'Apply a Dockerfile template') try_selecting_template_type('.gitlab-ci.yml', 'Apply a GitLab CI Yaml template') try_selecting_template_type('.gitignore', 'Apply a .gitignore template') @@ -130,6 +130,6 @@ end def create_and_edit_file(file_name) visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: file_name) - click_button "Commit Changes" + click_button "Commit changes" visit namespace_project_edit_blob_path(project.namespace, project, File.join(project.default_branch, file_name)) end diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb index 5479ea34610..c51851d3f94 100644 --- a/spec/features/projects/files/undo_template_spec.rb +++ b/spec/features/projects/files/undo_template_spec.rb @@ -17,7 +17,7 @@ feature 'Template Undo Button', js: true do end scenario 'reverts template application' do - try_template_undo('http://www.apache.org/licenses/', 'Apply a License template') + try_template_undo('http://www.apache.org/licenses/', 'Apply a license template') end end @@ -29,7 +29,7 @@ feature 'Template Undo Button', js: true do end scenario 'reverts template application' do - try_template_undo('http://www.apache.org/licenses/', 'Apply a License template') + try_template_undo('http://www.apache.org/licenses/', 'Apply a license template') end end end diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb index b6728960fb8..05f3162f13c 100644 --- a/spec/features/projects/merge_request_button_spec.rb +++ b/spec/features/projects/merge_request_button_spec.rb @@ -1,13 +1,13 @@ require 'spec_helper' feature 'Merge Request button', feature: true do - shared_examples 'Merge Request button only shown when allowed' do + shared_examples 'Merge request button only shown when allowed' do let(:user) { create(:user) } let(:project) { create(:project, :public) } let(:forked_project) { create(:project, :public, forked_from_project: project) } context 'not logged in' do - it 'does not show Create Merge Request button' do + it 'does not show Create merge request button' do visit url within("#content-body") do @@ -22,7 +22,7 @@ feature 'Merge Request button', feature: true do project.team << [user, :developer] end - it 'shows Create Merge Request button' do + it 'shows Create merge request button' do href = new_namespace_project_merge_request_path(project.namespace, project, merge_request: { source_branch: 'feature', @@ -40,7 +40,7 @@ feature 'Merge Request button', feature: true do project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED) end - it 'does not show Create Merge Request button' do + it 'does not show Create merge request button' do visit url within("#content-body") do @@ -55,7 +55,7 @@ feature 'Merge Request button', feature: true do login_as(user) end - it 'does not show Create Merge Request button' do + it 'does not show Create merge request button' do visit url within("#content-body") do @@ -66,7 +66,7 @@ feature 'Merge Request button', feature: true do context 'on own fork of project' do let(:user) { forked_project.owner } - it 'shows Create Merge Request button' do + it 'shows Create merge request button' do href = new_namespace_project_merge_request_path(forked_project.namespace, forked_project, merge_request: { source_branch: 'feature', @@ -83,24 +83,24 @@ feature 'Merge Request button', feature: true do end context 'on branches page' do - it_behaves_like 'Merge Request button only shown when allowed' do - let(:label) { 'Merge Request' } + it_behaves_like 'Merge request button only shown when allowed' do + let(:label) { 'Merge request' } let(:url) { namespace_project_branches_path(project.namespace, project) } let(:fork_url) { namespace_project_branches_path(forked_project.namespace, forked_project) } end end context 'on compare page' do - it_behaves_like 'Merge Request button only shown when allowed' do - let(:label) { 'Create Merge Request' } + it_behaves_like 'Merge request button only shown when allowed' do + let(:label) { 'Create merge request' } let(:url) { namespace_project_compare_path(project.namespace, project, from: 'master', to: 'feature') } let(:fork_url) { namespace_project_compare_path(forked_project.namespace, forked_project, from: 'master', to: 'feature') } end end context 'on commits page' do - it_behaves_like 'Merge Request button only shown when allowed' do - let(:label) { 'Create Merge Request' } + it_behaves_like 'Merge request button only shown when allowed' do + let(:label) { 'Create merge request' } let(:url) { namespace_project_commits_path(project.namespace, project, 'feature') } let(:fork_url) { namespace_project_commits_path(forked_project.namespace, forked_project, 'feature') } end diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index 76cb240ea98..035c57eaa47 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -32,5 +32,16 @@ feature "Pipelines settings", feature: true do expect(page).to have_button('Save changes', disabled: false) expect(page).to have_field('Test coverage parsing', with: 'coverage_regex') end + + scenario 'updates auto_cancel_pending_pipelines' do + page.check('Auto-cancel redundant, pending pipelines') + click_on 'Save changes' + + expect(page.status_code).to eq(200) + expect(page).to have_button('Save changes', disabled: false) + + checkbox = find_field('project_auto_cancel_pending_pipelines') + expect(checkbox).to be_checked + end end end diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb index a1c386ddc18..43d8b45669e 100644 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ b/spec/features/projects/wiki/markdown_preview_spec.rb @@ -24,12 +24,16 @@ feature 'Projects > Wiki > User previews markdown changes', feature: true, js: t context "while creating a new wiki page" do context "when there are no spaces or hyphens in the page name" do it "rewrites relative links as expected" do - click_link 'New Page' - fill_in :new_wiki_path, with: 'a/b/c/d' - click_button 'Create Page' + click_link 'New page' + page.within '#modal-new-wiki' do + fill_in :new_wiki_path, with: 'a/b/c/d' + click_button 'Create page' + end - fill_in :wiki_content, with: wiki_content - click_on "Preview" + page.within '.wiki-form' do + fill_in :wiki_content, with: wiki_content + click_on "Preview" + end expect(page).to have_content("regular link") @@ -42,12 +46,16 @@ feature 'Projects > Wiki > User previews markdown changes', feature: true, js: t context "when there are spaces in the page name" do it "rewrites relative links as expected" do - click_link 'New Page' - fill_in :new_wiki_path, with: 'a page/b page/c page/d page' - click_button 'Create Page' + click_link 'New page' + page.within '#modal-new-wiki' do + fill_in :new_wiki_path, with: 'a page/b page/c page/d page' + click_button 'Create page' + end - fill_in :wiki_content, with: wiki_content - click_on "Preview" + page.within '.wiki-form' do + fill_in :wiki_content, with: wiki_content + click_on "Preview" + end expect(page).to have_content("regular link") @@ -60,12 +68,16 @@ feature 'Projects > Wiki > User previews markdown changes', feature: true, js: t context "when there are hyphens in the page name" do it "rewrites relative links as expected" do - click_link 'New Page' - fill_in :new_wiki_path, with: 'a-page/b-page/c-page/d-page' - click_button 'Create Page' - - fill_in :wiki_content, with: wiki_content - click_on "Preview" + click_link 'New page' + page.within '#modal-new-wiki' do + fill_in :new_wiki_path, with: 'a-page/b-page/c-page/d-page' + click_button 'Create page' + end + + page.within '.wiki-form' do + fill_in :wiki_content, with: wiki_content + click_on "Preview" + end expect(page).to have_content("regular link") @@ -79,11 +91,17 @@ feature 'Projects > Wiki > User previews markdown changes', feature: true, js: t context "while editing a wiki page" do def create_wiki_page(path) - click_link 'New Page' - fill_in :new_wiki_path, with: path - click_button 'Create Page' - fill_in :wiki_content, with: 'content' - click_on "Create page" + click_link 'New page' + + page.within '#modal-new-wiki' do + fill_in :new_wiki_path, with: path + click_button 'Create page' + end + + page.within '.wiki-form' do + fill_in :wiki_content, with: 'content' + click_on "Create page" + end end context "when there are no spaces or hyphens in the page name" do diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb index 7bdaafd1beb..1ffac8cd542 100644 --- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb @@ -21,8 +21,9 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do scenario 'directly from the wiki home page' do fill_in :wiki_content, with: 'My awesome wiki!' - click_button 'Create page' - + page.within '.wiki-form' do + click_button 'Create page' + end expect(page).to have_content('Home') expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') @@ -36,16 +37,20 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do context 'via the "new wiki page" page' do scenario 'when the wiki page has a single word name', js: true do - click_link 'New Page' + click_link 'New page' - fill_in :new_wiki_path, with: 'foo' - click_button 'Create Page' + page.within '#modal-new-wiki' do + fill_in :new_wiki_path, with: 'foo' + click_button 'Create page' + end # Commit message field should have correct value. expect(page).to have_field('wiki[message]', with: 'Create foo') - fill_in :wiki_content, with: 'My awesome wiki!' - click_button 'Create page' + page.within '.wiki-form' do + fill_in :wiki_content, with: 'My awesome wiki!' + click_button 'Create page' + end expect(page).to have_content('Foo') expect(page).to have_content("Last edited by #{user.name}") @@ -53,16 +58,20 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do end scenario 'when the wiki page has spaces in the name', js: true do - click_link 'New Page' + click_link 'New page' - fill_in :new_wiki_path, with: 'Spaces in the name' - click_button 'Create Page' + page.within '#modal-new-wiki' do + fill_in :new_wiki_path, with: 'Spaces in the name' + click_button 'Create page' + end # Commit message field should have correct value. expect(page).to have_field('wiki[message]', with: 'Create spaces in the name') - fill_in :wiki_content, with: 'My awesome wiki!' - click_button 'Create page' + page.within '.wiki-form' do + fill_in :wiki_content, with: 'My awesome wiki!' + click_button 'Create page' + end expect(page).to have_content('Spaces in the name') expect(page).to have_content("Last edited by #{user.name}") @@ -70,16 +79,20 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do end scenario 'when the wiki page has hyphens in the name', js: true do - click_link 'New Page' + click_link 'New page' - fill_in :new_wiki_path, with: 'hyphens-in-the-name' - click_button 'Create Page' + page.within '#modal-new-wiki' do + fill_in :new_wiki_path, with: 'hyphens-in-the-name' + click_button 'Create page' + end # Commit message field should have correct value. expect(page).to have_field('wiki[message]', with: 'Create hyphens in the name') - fill_in :wiki_content, with: 'My awesome wiki!' - click_button 'Create page' + page.within '.wiki-form' do + fill_in :wiki_content, with: 'My awesome wiki!' + click_button 'Create page' + end expect(page).to have_content('Hyphens in the name') expect(page).to have_content("Last edited by #{user.name}") @@ -99,7 +112,9 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do scenario 'directly from the wiki home page' do fill_in :wiki_content, with: 'My awesome wiki!' - click_button 'Create page' + page.within '.wiki-form' do + click_button 'Create page' + end expect(page).to have_content('Home') expect(page).to have_content("Last edited by #{user.name}") @@ -113,16 +128,20 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do end scenario 'via the "new wiki page" page', js: true do - click_link 'New Page' + click_link 'New page' - fill_in :new_wiki_path, with: 'foo' - click_button 'Create Page' + page.within '#modal-new-wiki' do + fill_in :new_wiki_path, with: 'foo' + click_button 'Create page' + end # Commit message field should have correct value. expect(page).to have_field('wiki[message]', with: 'Create foo') - fill_in :wiki_content, with: 'My awesome wiki!' - click_button 'Create page' + page.within '.wiki-form' do + fill_in :wiki_content, with: 'My awesome wiki!' + click_button 'Create page' + end expect(page).to have_content('Foo') expect(page).to have_content("Last edited by #{user.name}") diff --git a/spec/features/tags/master_views_tags_spec.rb b/spec/features/tags/master_views_tags_spec.rb index 555f84c4772..922ac15a2eb 100644 --- a/spec/features/tags/master_views_tags_spec.rb +++ b/spec/features/tags/master_views_tags_spec.rb @@ -16,7 +16,7 @@ feature 'Master views tags', feature: true do fill_in :commit_message, with: 'Add a README file', visible: true # Remove pre-receive hook so we can push without auth FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive')) - click_button 'Commit Changes' + click_button 'Commit changes' visit namespace_project_tags_path(project.namespace, project) end diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index 28373098123..c877cfdd978 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -6,18 +6,18 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do before { allow_any_instance_of(U2fHelper).to receive(:inject_u2f_api?).and_return(true) } def manage_two_factor_authentication - click_on 'Manage Two-Factor Authentication' - expect(page).to have_content("Setup New U2F Device") + click_on 'Manage two-factor authentication' + expect(page).to have_content("Setup new U2F device") wait_for_ajax end def register_u2f_device(u2f_device = nil, name: 'My device') u2f_device ||= FakeU2fDevice.new(page, name) u2f_device.respond_to_u2f_registration - click_on 'Setup New U2F Device' + click_on 'Setup new U2F device' expect(page).to have_content('Your device was successfully set up') fill_in "Pick a name", with: name - click_on 'Register U2F Device' + click_on 'Register U2F device' u2f_device end @@ -34,9 +34,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do it 'does not allow registering a new device' do visit profile_account_path - click_on 'Enable Two-Factor Authentication' + click_on 'Enable two-factor authentication' - expect(page).to have_button('Setup New U2F Device', disabled: true) + expect(page).to have_button('Setup new U2F device', disabled: true) end end @@ -111,9 +111,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do # Have the "u2f device" respond with bad data page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };") - click_on 'Setup New U2F Device' + click_on 'Setup new U2F device' expect(page).to have_content('Your device was successfully set up') - click_on 'Register U2F Device' + click_on 'Register U2F device' expect(U2fRegistration.count).to eq(0) expect(page).to have_content("The form contains the following error") @@ -126,9 +126,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do # Failed registration page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };") - click_on 'Setup New U2F Device' + click_on 'Setup new U2F device' expect(page).to have_content('Your device was successfully set up') - click_on 'Register U2F Device' + click_on 'Register U2F device' expect(page).to have_content("The form contains the following error") # Successful registration diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index 7b9632be84e..5354e536edc 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -3,6 +3,9 @@ require('~/merge_request_tabs'); require('~/breakpoints'); require('~/lib/utils/common_utils'); +require('~/diff'); +require('~/single_file_diff'); +require('~/files_comment_button'); require('vendor/jquery.scrollTo'); (function () { @@ -39,7 +42,8 @@ require('vendor/jquery.scrollTo'); }); afterEach(function () { - this.class.destroy(); + this.class.unbindEvents(); + this.class.destroyPipelinesView(); }); describe('#activateTab', function () { @@ -65,6 +69,7 @@ require('vendor/jquery.scrollTo'); expect($('#diffs')).toHaveClass('active'); }); }); + describe('#opensInNewTab', function () { var tabUrl; var windowTarget = '_blank'; @@ -116,6 +121,7 @@ require('vendor/jquery.scrollTo'); stopImmediatePropagation: function () {} }); }); + it('opens page tab in a new browser tab with Cmd+Click - Mac', function () { spyOn(window, 'open').and.callFake(function (url, name) { expect(url).toEqual(tabUrl); @@ -129,6 +135,7 @@ require('vendor/jquery.scrollTo'); stopImmediatePropagation: function () {} }); }); + it('opens page tab in a new browser tab with Middle-click - Mac/PC', function () { spyOn(window, 'open').and.callFake(function (url, name) { expect(url).toEqual(tabUrl); @@ -149,6 +156,7 @@ require('vendor/jquery.scrollTo'); spyOn($, 'ajax').and.callFake(function () {}); this.subject = this.class.setCurrentAction; }); + it('changes from commits', function () { setLocation({ pathname: '/foo/bar/merge_requests/1/commits' @@ -156,13 +164,16 @@ require('vendor/jquery.scrollTo'); expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs'); }); + it('changes from diffs', function () { setLocation({ pathname: '/foo/bar/merge_requests/1/diffs' }); + expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); }); + it('changes from diffs.html', function () { setLocation({ pathname: '/foo/bar/merge_requests/1/diffs.html' @@ -170,6 +181,7 @@ require('vendor/jquery.scrollTo'); expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); }); + it('changes from notes', function () { setLocation({ pathname: '/foo/bar/merge_requests/1' @@ -177,6 +189,7 @@ require('vendor/jquery.scrollTo'); expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs'); expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); }); + it('includes search parameters and hash string', function () { setLocation({ pathname: '/foo/bar/merge_requests/1/diffs', @@ -185,6 +198,7 @@ require('vendor/jquery.scrollTo'); }); expect(this.subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35'); }); + it('replaces the current history state', function () { var newState; setLocation({ @@ -197,6 +211,7 @@ require('vendor/jquery.scrollTo'); }, document.title, newState); } }); + it('treats "show" like "notes"', function () { setLocation({ pathname: '/foo/bar/merge_requests/1/commits' @@ -207,12 +222,17 @@ require('vendor/jquery.scrollTo'); describe('#tabShown', () => { beforeEach(function () { + spyOn($, 'ajax').and.callFake(function () {}); loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); }); describe('with "Side-by-side"/parallel diff view', () => { beforeEach(function () { this.class.diffViewType = () => 'parallel'; + gl.Diff.prototype.diffViewType = () => 'parallel'; + spyOn($, 'ajax').and.callFake(function (options) { + options.success({ html: '' }); + }); }); it('maintains `container-limited` for pipelines tab', function (done) { @@ -224,7 +244,6 @@ require('vendor/jquery.scrollTo'); }); }); }; - asyncClick('.merge-request-tabs .pipelines-tab a') .then(() => asyncClick('.merge-request-tabs .diffs-tab a')) .then(() => asyncClick('.merge-request-tabs .pipelines-tab a')) @@ -237,6 +256,28 @@ require('vendor/jquery.scrollTo'); done.fail(`Something went wrong clicking MR tabs: ${err.message}\n${err.stack}`); }); }); + + it('maintains `container-limited` when switching from "Changes" tab before it loads', function (done) { + const asyncClick = function (selector) { + return new Promise((resolve) => { + setTimeout(() => { + document.querySelector(selector).click(); + resolve(); + }); + }); + }; + + asyncClick('.merge-request-tabs .diffs-tab a') + .then(() => asyncClick('.merge-request-tabs .notes-tab a')) + .then(() => { + const hasContainerLimitedClass = document.querySelector('.content-wrapper .container-fluid').classList.contains('container-limited'); + expect(hasContainerLimitedClass).toBe(true); + }) + .then(done) + .catch((err) => { + done.fail(`Something went wrong clicking MR tabs: ${err.message}\n${err.stack}`); + }); + }); }); }); diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js index 0f390c8b980..3960759f7cb 100644 --- a/spec/javascripts/u2f/register_spec.js +++ b/spec/javascripts/u2f/register_spec.js @@ -22,7 +22,7 @@ require('./mock_u2f_device'); it('allows registering a U2F device', function() { var deviceResponse, inProgressMessage, registeredMessage, setupButton; setupButton = this.container.find("#js-setup-u2f-device"); - expect(setupButton.text()).toBe('Setup New U2F Device'); + expect(setupButton.text()).toBe('Setup new U2F device'); setupButton.trigger('click'); inProgressMessage = this.container.children("p"); expect(inProgressMessage.text()).toContain("Trying to communicate with your device"); diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb index c872d8232b0..24df04e985a 100644 --- a/spec/lib/gitlab/etag_caching/middleware_spec.rb +++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb @@ -91,6 +91,12 @@ describe Gitlab::EtagCaching::Middleware do expect(status).to eq 304 end + it 'returns empty body' do + _, _, body = middleware.call(build_env(path, if_none_match)) + + expect(body).to be_empty + end + it 'tracks "etag_caching_cache_hit" event' do expect(Gitlab::Metrics).to receive(:add_event) .with(:etag_caching_middleware_used, endpoint: 'issue_notes') diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 5aa2a6d4b1b..9770d2e3b13 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -89,10 +89,19 @@ pipelines: - statuses - builds - trigger_requests +- auto_canceled_by +- auto_canceled_pipelines +- auto_canceled_jobs +- pending_builds +- retryable_builds +- cancelable_statuses +- manual_actions +- artifacts statuses: - project - pipeline - user +- auto_canceled_by variables: - project triggers: @@ -199,6 +208,7 @@ project: - builds - runner_projects - runners +- active_runners - variables - triggers - trigger_schedules diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 0c43c5662e8..04b5c80f22d 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -183,6 +183,7 @@ Ci::Pipeline: - duration - user_id - lock_version +- auto_canceled_by_id CommitStatus: - id - project_id @@ -223,6 +224,7 @@ CommitStatus: - token - lock_version - coverage_regex +- auto_canceled_by_id Ci::Variable: - id - project_id diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 8601160561f..6e8845cdcf4 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -764,40 +764,6 @@ describe Ci::Build, :models do end end - describe '#has_commands?' do - context 'when build has commands' do - let(:build) do - create(:ci_build, commands: 'rspec') - end - - it 'has commands' do - expect(build).to have_commands - end - end - - context 'when does not have commands' do - context 'when commands are an empty string' do - let(:build) do - create(:ci_build, commands: '') - end - - it 'has no commands' do - expect(build).not_to have_commands - end - end - - context 'when commands are not set at all' do - let(:build) do - create(:ci_build, commands: nil) - end - - it 'has no commands' do - expect(build).not_to have_commands - end - end - end - end - describe '#has_tags?' do context 'when build has tags' do subject { create(:ci_build, tag_list: ['tag']) } diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index e4a24fd63c2..32aa2f4b336 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -12,10 +12,13 @@ describe Ci::Pipeline, models: true do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:auto_canceled_by) } it { is_expected.to have_many(:statuses) } it { is_expected.to have_many(:trigger_requests) } it { is_expected.to have_many(:builds) } + it { is_expected.to have_many(:auto_canceled_pipelines) } + it { is_expected.to have_many(:auto_canceled_jobs) } it { is_expected.to validate_presence_of :sha } it { is_expected.to validate_presence_of :status } @@ -134,6 +137,43 @@ describe Ci::Pipeline, models: true do end end + describe '#auto_canceled?' do + subject { pipeline.auto_canceled? } + + context 'when it is canceled' do + before do + pipeline.cancel + end + + context 'when there is auto_canceled_by' do + before do + pipeline.update(auto_canceled_by: create(:ci_empty_pipeline)) + end + + it 'is auto canceled' do + is_expected.to be_truthy + end + end + + context 'when there is no auto_canceled_by' do + it 'is not auto canceled' do + is_expected.to be_falsey + end + end + + context 'when it is retried and canceled manually' do + before do + pipeline.enqueue + pipeline.cancel + end + + it 'is not auto canceled' do + is_expected.to be_falsey + end + end + end + end + describe 'pipeline stages' do before do create(:commit_status, pipeline: pipeline, @@ -335,6 +375,14 @@ describe Ci::Pipeline, models: true do end end + describe 'pipeline ETag caching' do + it 'executes ExpirePipelinesCacheService' do + expect_any_instance_of(Ci::ExpirePipelineCacheService).to receive(:execute).with(pipeline) + + pipeline.cancel + end + end + def create_build(name, queued_at = current, started_from = 0) create(:ci_build, name: name, diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index 42170de0180..d26121018ce 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -17,8 +17,8 @@ describe Ci::Trigger, models: true do expect(trigger.token).not_to be_nil end - it 'does not set an random token if one provided' do - trigger = create(:ci_trigger, project: project) + it 'does not set a random token if one provided' do + trigger = create(:ci_trigger, project: project, token: 'token') expect(trigger.token).to eq('token') end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 7343b735a74..0ee85489574 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -16,6 +16,7 @@ describe CommitStatus, :models do it { is_expected.to belong_to(:pipeline) } it { is_expected.to belong_to(:user) } it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:auto_canceled_by) } it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_inclusion_of(:status).in_array(%w(pending running failed success canceled)) } @@ -101,6 +102,32 @@ describe CommitStatus, :models do end end + describe '#auto_canceled?' do + subject { commit_status.auto_canceled? } + + context 'when it is canceled' do + before do + commit_status.update(status: 'canceled') + end + + context 'when there is auto_canceled_by' do + before do + commit_status.update(auto_canceled_by: create(:ci_empty_pipeline)) + end + + it 'is auto canceled' do + is_expected.to be_truthy + end + end + + context 'when there is no auto_canceled_by' do + it 'is not auto canceled' do + is_expected.to be_falsey + end + end + end + end + describe '#duration' do subject { commit_status.duration } diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index 82abad0e2f6..67dae7cf4c0 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -231,6 +231,18 @@ describe HasStatus do end end + describe '.created_or_pending' do + subject { CommitStatus.created_or_pending } + + %i[created pending].each do |status| + it_behaves_like 'containing the job', status + end + + %i[running failed success].each do |status| + it_behaves_like 'not containing the job', status + end + end + describe '.finished' do subject { CommitStatus.finished } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e5dd4bcb0d8..6d4ef78f8ec 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -58,6 +58,7 @@ describe Project, models: true do it { is_expected.to have_many(:builds) } it { is_expected.to have_many(:runner_projects) } it { is_expected.to have_many(:runners) } + it { is_expected.to have_many(:active_runners) } it { is_expected.to have_many(:variables) } it { is_expected.to have_many(:triggers) } it { is_expected.to have_many(:pages_domains) } diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb index 7a35da38b2b..2190ab0e82e 100644 --- a/spec/presenters/ci/build_presenter_spec.rb +++ b/spec/presenters/ci/build_presenter_spec.rb @@ -57,6 +57,32 @@ describe Ci::BuildPresenter do end end + describe '#status_title' do + context 'when build is auto-canceled' do + before do + expect(build).to receive(:auto_canceled?).and_return(true) + expect(build).to receive(:auto_canceled_by_id).and_return(1) + end + + it 'shows that the build is auto-canceled' do + status_title = presenter.status_title + + expect(status_title).to include('auto-canceled') + expect(status_title).to include('Pipeline #1') + end + end + + context 'when build is not auto-canceled' do + before do + expect(build).to receive(:auto_canceled?).and_return(false) + end + + it 'does not have a status title' do + expect(presenter.status_title).to be_nil + end + end + end + describe 'quack like a Ci::Build permission-wise' do context 'user is not allowed' do let(:project) { build_stubbed(:empty_project, public_builds: false) } diff --git a/spec/presenters/ci/pipeline_presenter_spec.rb b/spec/presenters/ci/pipeline_presenter_spec.rb new file mode 100644 index 00000000000..9134d1cc31c --- /dev/null +++ b/spec/presenters/ci/pipeline_presenter_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe Ci::PipelinePresenter do + let(:project) { create(:empty_project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + + subject(:presenter) do + described_class.new(pipeline) + end + + it 'inherits from Gitlab::View::Presenter::Delegated' do + expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated) + end + + describe '#initialize' do + it 'takes a pipeline and optional params' do + expect { presenter }.not_to raise_error + end + + it 'exposes pipeline' do + expect(presenter.pipeline).to eq(pipeline) + end + + it 'forwards missing methods to pipeline' do + expect(presenter.ref).to eq(pipeline.ref) + end + end + + describe '#status_title' do + context 'when pipeline is auto-canceled' do + before do + expect(pipeline).to receive(:auto_canceled?).and_return(true) + expect(pipeline).to receive(:auto_canceled_by_id).and_return(1) + end + + it 'shows that the pipeline is auto-canceled' do + status_title = presenter.status_title + + expect(status_title).to include('auto-canceled') + expect(status_title).to include('Pipeline #1') + end + end + + context 'when pipeline is not auto-canceled' do + before do + expect(pipeline).to receive(:auto_canceled?).and_return(false) + end + + it 'does not have a status title' do + expect(presenter.status_title).to be_nil + end + end + end +end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 8642b803844..f6249ab4664 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -93,6 +93,44 @@ describe PipelineSerializer do end end end + + context 'number of queries' do + let(:resource) { Ci::Pipeline.all } + let(:project) { create(:empty_project) } + + before do + Ci::Pipeline::AVAILABLE_STATUSES.each do |status| + create_pipeline(status) + end + + RequestStore.begin! + end + + after do + RequestStore.end! + RequestStore.clear! + end + + it "verifies number of queries" do + recorded = ActiveRecord::QueryRecorder.new { subject } + expect(recorded.count).to be_within(1).of(50) + expect(recorded.cached_count).to eq(0) + end + + def create_pipeline(status) + create(:ci_empty_pipeline, project: project, status: status).tap do |pipeline| + Ci::Build::AVAILABLE_STATUSES.each do |status| + create_build(pipeline, status, status) + end + end + end + + def create_build(pipeline, stage, status) + create(:ci_build, :tags, :triggered, :artifacts, + pipeline: pipeline, stage: stage, + name: stage, status: status) + end + end end describe '#represent_status' do diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index d2f0337c260..fa5014cee07 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -9,72 +9,140 @@ describe Ci::CreatePipelineService, services: true do end describe '#execute' do - def execute(params) + def execute_service(after: project.commit.id, message: 'Message', ref: 'refs/heads/master') + params = { ref: ref, + before: '00000000', + after: after, + commits: [{ message: message }] } + described_class.new(project, user, params).execute end context 'valid params' do - let(:pipeline) do - execute(ref: 'refs/heads/master', - before: '00000000', - after: project.commit.id, - commits: [{ message: "Message" }]) + let(:pipeline) { execute_service } + + let(:pipeline_on_previous_commit) do + execute_service( + after: previous_commit_sha_from_ref('master') + ) end it { expect(pipeline).to be_kind_of(Ci::Pipeline) } it { expect(pipeline).to be_valid } - it { expect(pipeline).to be_persisted } it { expect(pipeline).to eq(project.pipelines.last) } it { expect(pipeline).to have_attributes(user: user) } + it { expect(pipeline).to have_attributes(status: 'pending') } it { expect(pipeline.builds.first).to be_kind_of(Ci::Build) } + + context 'auto-cancel enabled' do + before do + project.update(auto_cancel_pending_pipelines: 'enabled') + end + + it 'does not cancel HEAD pipeline' do + pipeline + pipeline_on_previous_commit + + expect(pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil) + end + + it 'auto cancel pending non-HEAD pipelines' do + pipeline_on_previous_commit + pipeline + + expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id) + end + + it 'does not cancel running outdated pipelines' do + pipeline_on_previous_commit.run + execute_service + + expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'running', auto_canceled_by_id: nil) + end + + it 'cancel created outdated pipelines' do + pipeline_on_previous_commit.update(status: 'created') + pipeline + + expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id) + end + + it 'does not cancel pipelines from the other branches' do + pending_pipeline = execute_service( + ref: 'refs/heads/feature', + after: previous_commit_sha_from_ref('feature') + ) + pipeline + + expect(pending_pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil) + end + end + + context 'auto-cancel disabled' do + before do + project.update(auto_cancel_pending_pipelines: 'disabled') + end + + it 'does not auto cancel pending non-HEAD pipelines' do + pipeline_on_previous_commit + pipeline + + expect(pipeline_on_previous_commit.reload) + .to have_attributes(status: 'pending', auto_canceled_by_id: nil) + end + end + + def previous_commit_sha_from_ref(ref) + project.commit(ref).parent.sha + end end context "skip tag if there is no build for it" do it "creates commit if there is appropriate job" do - result = execute(ref: 'refs/heads/master', - before: '00000000', - after: project.commit.id, - commits: [{ message: "Message" }]) - expect(result).to be_persisted + expect(execute_service).to be_persisted end it "creates commit if there is no appropriate job but deploy job has right ref setting" do config = YAML.dump({ deploy: { script: "ls", only: ["master"] } }) stub_ci_pipeline_yaml_file(config) - result = execute(ref: 'refs/heads/master', - before: '00000000', - after: project.commit.id, - commits: [{ message: "Message" }]) - expect(result).to be_persisted + expect(execute_service).to be_persisted end end it 'skips creating pipeline for refs without .gitlab-ci.yml' do stub_ci_pipeline_yaml_file(nil) - result = execute(ref: 'refs/heads/master', - before: '00000000', - after: project.commit.id, - commits: [{ message: 'Message' }]) - expect(result).not_to be_persisted + expect(execute_service).not_to be_persisted expect(Ci::Pipeline.count).to eq(0) end - it 'fails commits if yaml is invalid' do - message = 'message' - allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message } - stub_ci_pipeline_yaml_file('invalid: file: file') - commits = [{ message: message }] - pipeline = execute(ref: 'refs/heads/master', - before: '00000000', - after: project.commit.id, - commits: commits) - - expect(pipeline).to be_persisted - expect(pipeline.builds.any?).to be false - expect(pipeline.status).to eq('failed') - expect(pipeline.yaml_errors).not_to be_nil + shared_examples 'a failed pipeline' do + it 'creates failed pipeline' do + stub_ci_pipeline_yaml_file(ci_yaml) + + pipeline = execute_service(message: message) + + expect(pipeline).to be_persisted + expect(pipeline.builds.any?).to be false + expect(pipeline.status).to eq('failed') + expect(pipeline.yaml_errors).not_to be_nil + end + end + + context 'when yaml is invalid' do + let(:ci_yaml) { 'invalid: file: fiile' } + let(:message) { 'Message' } + + it_behaves_like 'a failed pipeline' + + context 'when receive git commit' do + before do + allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message } + end + + it_behaves_like 'a failed pipeline' + end end context 'when commit contains a [ci skip] directive' do @@ -97,11 +165,7 @@ describe Ci::CreatePipelineService, services: true do ci_messages.each do |ci_message| it "skips builds creation if the commit message is #{ci_message}" do - commits = [{ message: ci_message }] - pipeline = execute(ref: 'refs/heads/master', - before: '00000000', - after: project.commit.id, - commits: commits) + pipeline = execute_service(message: ci_message) expect(pipeline).to be_persisted expect(pipeline.builds.any?).to be false @@ -109,58 +173,34 @@ describe Ci::CreatePipelineService, services: true do end end - it "does not skips builds creation if there is no [ci skip] or [skip ci] tag in commit message" do - allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { "some message" } + shared_examples 'creating a pipeline' do + it 'does not skip pipeline creation' do + allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { commit_message } - commits = [{ message: "some message" }] - pipeline = execute(ref: 'refs/heads/master', - before: '00000000', - after: project.commit.id, - commits: commits) + pipeline = execute_service(message: commit_message) - expect(pipeline).to be_persisted - expect(pipeline.builds.first.name).to eq("rspec") + expect(pipeline).to be_persisted + expect(pipeline.builds.first.name).to eq("rspec") + end end - it "does not skip builds creation if the commit message is nil" do - allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { nil } - - commits = [{ message: nil }] - pipeline = execute(ref: 'refs/heads/master', - before: '00000000', - after: project.commit.id, - commits: commits) + context 'when commit message does not contain [ci skip] nor [skip ci]' do + let(:commit_message) { 'some message' } - expect(pipeline).to be_persisted - expect(pipeline.builds.first.name).to eq("rspec") + it_behaves_like 'creating a pipeline' end - it "fails builds creation if there is [ci skip] tag in commit message and yaml is invalid" do - stub_ci_pipeline_yaml_file('invalid: file: fiile') - commits = [{ message: message }] - pipeline = execute(ref: 'refs/heads/master', - before: '00000000', - after: project.commit.id, - commits: commits) + context 'when commit message is nil' do + let(:commit_message) { nil } - expect(pipeline).to be_persisted - expect(pipeline.builds.any?).to be false - expect(pipeline.status).to eq("failed") - expect(pipeline.yaml_errors).not_to be_nil + it_behaves_like 'creating a pipeline' end - end - it "creates commit with failed status if yaml is invalid" do - stub_ci_pipeline_yaml_file('invalid: file') - commits = [{ message: "some message" }] - pipeline = execute(ref: 'refs/heads/master', - before: '00000000', - after: project.commit.id, - commits: commits) - - expect(pipeline).to be_persisted - expect(pipeline.status).to eq("failed") - expect(pipeline.builds.any?).to be false + context 'when there is [ci skip] tag in commit message and yaml is invalid' do + let(:ci_yaml) { 'invalid: file: fiile' } + + it_behaves_like 'a failed pipeline' + end end context 'when there are no jobs for this pipeline' do @@ -170,10 +210,7 @@ describe Ci::CreatePipelineService, services: true do end it 'does not create a new pipeline' do - result = execute(ref: 'refs/heads/master', - before: '00000000', - after: project.commit.id, - commits: [{ message: 'some msg' }]) + result = execute_service expect(result).not_to be_persisted expect(Ci::Build.all).to be_empty @@ -188,10 +225,7 @@ describe Ci::CreatePipelineService, services: true do end it 'does not create a new pipeline' do - result = execute(ref: 'refs/heads/master', - before: '00000000', - after: project.commit.id, - commits: [{ message: 'some msg' }]) + result = execute_service expect(result).to be_persisted expect(result.manual_actions).not_to be_empty @@ -205,10 +239,7 @@ describe Ci::CreatePipelineService, services: true do end it 'creates the environment' do - result = execute(ref: 'refs/heads/master', - before: '00000000', - after: project.commit.id, - commits: [{ message: 'some msg' }]) + result = execute_service expect(result).to be_persisted expect(Environment.find_by(name: "review/master")).not_to be_nil diff --git a/spec/services/ci/expire_pipeline_cache_service_spec.rb b/spec/services/ci/expire_pipeline_cache_service_spec.rb new file mode 100644 index 00000000000..3c735872c30 --- /dev/null +++ b/spec/services/ci/expire_pipeline_cache_service_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Ci::ExpirePipelineCacheService, services: true do + let(:user) { create(:user) } + let(:project) { create(:empty_project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + subject { described_class.new(project, user) } + + describe '#execute' do + it 'invalidate Etag caching for project pipelines path' do + pipelines_path = "/#{project.full_path}/pipelines.json" + new_mr_pipelines_path = "/#{project.full_path}/merge_requests/new.json" + + expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(pipelines_path) + expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(new_mr_pipelines_path) + + subject.execute(pipeline) + end + end +end diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index bb98fb37a90..245e19822f3 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -462,7 +462,9 @@ describe Ci::ProcessPipelineService, '#execute', :services do builds.find_by(name: name).play(user) end - delegate :manual_actions, to: :pipeline + def manual_actions + pipeline.manual_actions(true) + end def create_build(name, **opts) create(:ci_build, :created, pipeline: pipeline, name: name, **opts) diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 8567817147b..b2d37657770 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -16,20 +16,21 @@ describe Ci::RetryBuildService, :services do %i[id status user token coverage trace runner artifacts_expire_at artifacts_file artifacts_metadata artifacts_size created_at updated_at started_at finished_at queued_at erased_by - erased_at].freeze + erased_at auto_canceled_by].freeze IGNORE_ACCESSORS = %i[type lock_version target_url base_tags commit_id deployments erased_by_id last_deployment project_id runner_id tag_taggings taggings tags trigger_request_id - user_id].freeze + user_id auto_canceled_by_id].freeze shared_examples 'build duplication' do let(:build) do create(:ci_build, :failed, :artifacts_expired, :erased, :queued, :coverage, :tags, :allowed_to_fail, :on_tag, :teardown_environment, :triggered, :trace, - description: 'some build', pipeline: pipeline) + description: 'some build', pipeline: pipeline, + auto_canceled_by: create(:ci_empty_pipeline)) end describe 'clone accessors' do diff --git a/spec/support/query_recorder.rb b/spec/support/query_recorder.rb index e40d5ebd9a8..55b531b4cf7 100644 --- a/spec/support/query_recorder.rb +++ b/spec/support/query_recorder.rb @@ -1,21 +1,29 @@ module ActiveRecord class QueryRecorder - attr_reader :log + attr_reader :log, :cached def initialize(&block) @log = [] + @cached = [] ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block) end def callback(name, start, finish, message_id, values) - return if %w(CACHE SCHEMA).include?(values[:name]) - @log << values[:sql] + if values[:name]&.include?("CACHE") + @cached << values[:sql] + elsif !values[:name]&.include?("SCHEMA") + @log << values[:sql] + end end def count @log.count end + def cached_count + @cached.count + end + def log_message @log.join("\n\n") end diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb index 55b64808fb3..0f39df0f250 100644 --- a/spec/views/projects/builds/show.html.haml_spec.rb +++ b/spec/views/projects/builds/show.html.haml_spec.rb @@ -9,7 +9,7 @@ describe 'projects/builds/show', :view do end before do - assign(:build, build) + assign(:build, build.present) assign(:project, project) allow(view).to receive(:can?).and_return(true) diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb index dca78dec6df..bb39ec8efbf 100644 --- a/spec/views/projects/pipelines/show.html.haml_spec.rb +++ b/spec/views/projects/pipelines/show.html.haml_spec.rb @@ -5,7 +5,13 @@ describe 'projects/pipelines/show' do let(:user) { create(:user) } let(:project) { create(:project, :repository) } - let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id, user: user) } + + let(:pipeline) do + create(:ci_empty_pipeline, + project: project, + sha: project.commit.id, + user: user) + end before do controller.prepend_view_path('app/views/projects') @@ -21,7 +27,7 @@ describe 'projects/pipelines/show' do create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3) assign(:project, project) - assign(:pipeline, pipeline) + assign(:pipeline, pipeline.present(current_user: user)) assign(:commit, project.commit) allow(view).to receive(:can?).and_return(true) |