diff options
author | Lin Jen-Shin <godfat@godfat.org> | 2017-04-07 14:40:20 +0800 |
---|---|---|
committer | Lin Jen-Shin <godfat@godfat.org> | 2017-04-07 14:40:20 +0800 |
commit | f8fe64a630b2adff8d260c7cc92f89ff8cfd0519 (patch) | |
tree | 19b2a2bc32c67e136273c986040cb456b6965488 | |
parent | e28fc7b1d366e044cd2f90fe54590966688709c5 (diff) | |
parent | 6ea35f98241b351747dd445738be58e3d65cafa2 (diff) | |
download | gitlab-ce-f8fe64a630b2adff8d260c7cc92f89ff8cfd0519.tar.gz |
Merge remote-tracking branch 'upstream/master' into 8998_skip_pending_commits_if_not_head
* upstream/master: (58 commits)
Fix icon name error
Rewrite system note helper
Change edit icon
Leave icon area blank if legacy note; remove diamond icon
Fix positioning of note icons
Fix newline errors
Add remaining system note icons
Add system notes icon helper; add icons
Fixed specs Updated JS that was causing the hints to appear & then disappear
Update tests
Fix broken spinach test
Reuse code
Improve shortcuts code
Adds ShortcutsDashboardNavigation to globals comment since its a global variable
Fix shortcut specs
Reorganize shortcut help menu
Change todos shortcut to shift
Change shortcuts
Switch global shortcuts to shift; reuse key styles from help menu
Map bindings to lowercase letters; only show key bindings when using keyboard shortcut
...
208 files changed, 2267 insertions, 1080 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/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/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/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/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 603ef461ffe..12ca20a1420 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/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 250ae496b06..134ca5f60a3 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -17,6 +17,12 @@ module Ci 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? } @@ -169,10 +175,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)") @@ -209,20 +211,16 @@ 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? diff --git a/app/models/project.rb b/app/models/project.rb index 8eaf7bc8376..639615b91a2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -116,6 +116,7 @@ class Project < ActiveRecord::Base has_one :mock_ci_service, dependent: :destroy has_one :mock_deployment_service, dependent: :destroy has_one :mock_monitoring_service, dependent: :destroy + has_one :microsoft_teams_service, dependent: :destroy has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_from_project, through: :forked_project_link @@ -171,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 @@ -1086,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/models/project_services/chat_message/base_message.rb b/app/models/project_services/chat_message/base_message.rb index 86d271a3f69..7621a5fa2d8 100644 --- a/app/models/project_services/chat_message/base_message.rb +++ b/app/models/project_services/chat_message/base_message.rb @@ -2,11 +2,23 @@ require 'slack-notifier' module ChatMessage class BaseMessage + attr_reader :markdown + attr_reader :user_name + attr_reader :user_avatar + attr_reader :project_name + attr_reader :project_url + def initialize(params) - raise NotImplementedError + @markdown = params[:markdown] || false + @project_name = params.dig(:project, :path_with_namespace) || params[:project_name] + @project_url = params.dig(:project, :web_url) || params[:project_url] + @user_name = params.dig(:user, :username) || params[:user_name] + @user_avatar = params.dig(:user, :avatar_url) || params[:user_avatar] end def pretext + return message if markdown + format(message) end @@ -17,6 +29,10 @@ module ChatMessage raise NotImplementedError end + def activity + raise NotImplementedError + end + private def message diff --git a/app/models/project_services/chat_message/issue_message.rb b/app/models/project_services/chat_message/issue_message.rb index 791e5b0cec7..4b9a2b1e1f3 100644 --- a/app/models/project_services/chat_message/issue_message.rb +++ b/app/models/project_services/chat_message/issue_message.rb @@ -1,9 +1,6 @@ module ChatMessage class IssueMessage < BaseMessage - attr_reader :user_name attr_reader :title - attr_reader :project_name - attr_reader :project_url attr_reader :issue_iid attr_reader :issue_url attr_reader :action @@ -11,9 +8,7 @@ module ChatMessage attr_reader :description def initialize(params) - @user_name = params[:user][:username] - @project_name = params[:project_name] - @project_url = params[:project_url] + super obj_attr = params[:object_attributes] obj_attr = HashWithIndifferentAccess.new(obj_attr) @@ -27,15 +22,24 @@ module ChatMessage def attachments return [] unless opened_issue? + return description if markdown description_message end + def activity + { + title: "Issue #{state} by #{user_name}", + subtitle: "in #{project_link}", + text: issue_link, + image: user_avatar + } + end + private def message - case state - when "opened" + if state == 'opened' "[#{project_link}] Issue #{state} by #{user_name}" else "[#{project_link}] Issue #{issue_link} #{state} by #{user_name}" @@ -64,7 +68,7 @@ module ChatMessage end def issue_title - "##{issue_iid} #{title}" + "#{Issue.reference_prefix}#{issue_iid} #{title}" end end end diff --git a/app/models/project_services/chat_message/merge_message.rb b/app/models/project_services/chat_message/merge_message.rb index 5e5efca7bec..7d0de81cdf0 100644 --- a/app/models/project_services/chat_message/merge_message.rb +++ b/app/models/project_services/chat_message/merge_message.rb @@ -1,36 +1,36 @@ module ChatMessage class MergeMessage < BaseMessage - attr_reader :user_name - attr_reader :project_name - attr_reader :project_url - attr_reader :merge_request_id + attr_reader :merge_request_iid attr_reader :source_branch attr_reader :target_branch attr_reader :state attr_reader :title def initialize(params) - @user_name = params[:user][:username] - @project_name = params[:project_name] - @project_url = params[:project_url] + super obj_attr = params[:object_attributes] obj_attr = HashWithIndifferentAccess.new(obj_attr) - @merge_request_id = obj_attr[:iid] + @merge_request_iid = obj_attr[:iid] @source_branch = obj_attr[:source_branch] @target_branch = obj_attr[:target_branch] @state = obj_attr[:state] @title = format_title(obj_attr[:title]) end - def pretext - format(message) - end - def attachments [] end + def activity + { + title: "Merge Request #{state} by #{user_name}", + subtitle: "in #{project_link}", + text: merge_request_link, + image: user_avatar + } + end + private def format_title(title) @@ -50,11 +50,15 @@ module ChatMessage end def merge_request_link - link("merge request !#{merge_request_id}", merge_request_url) + link(merge_request_title, merge_request_url) + end + + def merge_request_title + "#{MergeRequest.reference_prefix}#{merge_request_iid} #{title}" end def merge_request_url - "#{project_url}/merge_requests/#{merge_request_id}" + "#{project_url}/merge_requests/#{merge_request_iid}" end end end diff --git a/app/models/project_services/chat_message/note_message.rb b/app/models/project_services/chat_message/note_message.rb index 552113bac29..2da4c244229 100644 --- a/app/models/project_services/chat_message/note_message.rb +++ b/app/models/project_services/chat_message/note_message.rb @@ -1,70 +1,74 @@ module ChatMessage class NoteMessage < BaseMessage - attr_reader :message - attr_reader :user_name - attr_reader :project_name - attr_reader :project_url attr_reader :note attr_reader :note_url + attr_reader :title + attr_reader :target def initialize(params) - params = HashWithIndifferentAccess.new(params) - @user_name = params[:user][:username] - @project_name = params[:project_name] - @project_url = params[:project_url] + super + params = HashWithIndifferentAccess.new(params) obj_attr = params[:object_attributes] - obj_attr = HashWithIndifferentAccess.new(obj_attr) @note = obj_attr[:note] @note_url = obj_attr[:url] - noteable_type = obj_attr[:noteable_type] - - case noteable_type - when "Commit" - create_commit_note(HashWithIndifferentAccess.new(params[:commit])) - when "Issue" - create_issue_note(HashWithIndifferentAccess.new(params[:issue])) - when "MergeRequest" - create_merge_note(HashWithIndifferentAccess.new(params[:merge_request])) - when "Snippet" - create_snippet_note(HashWithIndifferentAccess.new(params[:snippet])) - end + @target, @title = case obj_attr[:noteable_type] + when "Commit" + create_commit_note(params[:commit]) + when "Issue" + create_issue_note(params[:issue]) + when "MergeRequest" + create_merge_note(params[:merge_request]) + when "Snippet" + create_snippet_note(params[:snippet]) + end end def attachments + return note if markdown + description_message end + def activity + { + title: "#{user_name} #{link('commented on ' + target, note_url)}", + subtitle: "in #{project_link}", + text: formatted_title, + image: user_avatar + } + end + private + def message + "#{user_name} #{link('commented on ' + target, note_url)} in #{project_link}: *#{formatted_title}*" + end + def format_title(title) title.lines.first.chomp end - def create_commit_note(commit) - commit_sha = commit[:id] - commit_sha = Commit.truncate_sha(commit_sha) - commented_on_message( - "commit #{commit_sha}", - format_title(commit[:message])) + def formatted_title + format_title(title) end def create_issue_note(issue) - commented_on_message( - "issue ##{issue[:iid]}", - format_title(issue[:title])) + ["issue #{Issue.reference_prefix}#{issue[:iid]}", issue[:title]] + end + + def create_commit_note(commit) + commit_sha = Commit.truncate_sha(commit[:id]) + + ["commit #{commit_sha}", commit[:message]] end def create_merge_note(merge_request) - commented_on_message( - "merge request !#{merge_request[:iid]}", - format_title(merge_request[:title])) + ["merge request #{MergeRequest.reference_prefix}#{merge_request[:iid]}", merge_request[:title]] end def create_snippet_note(snippet) - commented_on_message( - "snippet ##{snippet[:id]}", - format_title(snippet[:title])) + ["snippet #{Snippet.reference_prefix}#{snippet[:id]}", snippet[:title]] end def description_message @@ -74,9 +78,5 @@ module ChatMessage def project_link link(project_name, project_url) end - - def commented_on_message(target, title) - @message = "#{user_name} #{link('commented on ' + target, note_url)} in #{project_link}: *#{title}*" - end end end diff --git a/app/models/project_services/chat_message/pipeline_message.rb b/app/models/project_services/chat_message/pipeline_message.rb index 210027565a8..4628d9b1a7b 100644 --- a/app/models/project_services/chat_message/pipeline_message.rb +++ b/app/models/project_services/chat_message/pipeline_message.rb @@ -1,19 +1,22 @@ module ChatMessage class PipelineMessage < BaseMessage - attr_reader :ref_type, :ref, :status, :project_name, :project_url, - :user_name, :duration, :pipeline_id + attr_reader :ref_type + attr_reader :ref + attr_reader :status + attr_reader :duration + attr_reader :pipeline_id def initialize(data) + super + + @user_name = data.dig(:user, :name) || 'API' + pipeline_attributes = data[:object_attributes] @ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch' @ref = pipeline_attributes[:ref] @status = pipeline_attributes[:status] @duration = pipeline_attributes[:duration] @pipeline_id = pipeline_attributes[:id] - - @project_name = data[:project][:path_with_namespace] - @project_url = data[:project][:web_url] - @user_name = (data[:user] && data[:user][:name]) || 'API' end def pretext @@ -25,17 +28,24 @@ module ChatMessage end def attachments + return message if markdown + [{ text: format(message), color: attachment_color }] end + def activity + { + title: "Pipeline #{pipeline_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status}", + subtitle: "in #{project_link}", + text: "in #{duration} #{time_measure}", + image: user_avatar || '' + } + end + private def message - "#{project_link}: Pipeline #{pipeline_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}" - end - - def format(string) - Slack::Notifier::LinkFormatter.format(string) + "#{project_link}: Pipeline #{pipeline_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{time_measure}" end def humanized_status @@ -74,5 +84,9 @@ module ChatMessage def pipeline_link "[##{pipeline_id}](#{pipeline_url})" end + + def time_measure + 'second'.pluralize(duration) + end end end diff --git a/app/models/project_services/chat_message/push_message.rb b/app/models/project_services/chat_message/push_message.rb index 2d73b71ec37..c52dd6ef8ef 100644 --- a/app/models/project_services/chat_message/push_message.rb +++ b/app/models/project_services/chat_message/push_message.rb @@ -3,33 +3,43 @@ module ChatMessage attr_reader :after attr_reader :before attr_reader :commits - attr_reader :project_name - attr_reader :project_url attr_reader :ref attr_reader :ref_type - attr_reader :user_name def initialize(params) + super + @after = params[:after] @before = params[:before] @commits = params.fetch(:commits, []) - @project_name = params[:project_name] - @project_url = params[:project_url] @ref_type = Gitlab::Git.tag_ref?(params[:ref]) ? 'tag' : 'branch' @ref = Gitlab::Git.ref_name(params[:ref]) - @user_name = params[:user_name] - end - - def pretext - format(message) end def attachments return [] if new_branch? || removed_branch? + return commit_messages if markdown commit_message_attachments end + def activity + action = if new_branch? + "created" + elsif removed_branch? + "removed" + else + "pushed to" + end + + { + title: "#{user_name} #{action} #{ref_type}", + subtitle: "in #{project_link}", + text: compare_link, + image: user_avatar + } + end + private def message @@ -59,7 +69,7 @@ module ChatMessage end def commit_messages - commits.map { |commit| compose_commit_message(commit) }.join("\n") + commits.map { |commit| compose_commit_message(commit) }.join("\n\n") end def commit_message_attachments diff --git a/app/models/project_services/chat_message/wiki_page_message.rb b/app/models/project_services/chat_message/wiki_page_message.rb index 134083e4504..a139a8ee727 100644 --- a/app/models/project_services/chat_message/wiki_page_message.rb +++ b/app/models/project_services/chat_message/wiki_page_message.rb @@ -1,17 +1,12 @@ module ChatMessage class WikiPageMessage < BaseMessage - attr_reader :user_name attr_reader :title - attr_reader :project_name - attr_reader :project_url attr_reader :wiki_page_url attr_reader :action attr_reader :description def initialize(params) - @user_name = params[:user][:username] - @project_name = params[:project_name] - @project_url = params[:project_url] + super obj_attr = params[:object_attributes] obj_attr = HashWithIndifferentAccess.new(obj_attr) @@ -29,9 +24,20 @@ module ChatMessage end def attachments + return description if markdown + description_message end + def activity + { + title: "#{user_name} #{action} #{wiki_page_link}", + subtitle: "in #{project_link}", + text: title, + image: user_avatar + } + end + private def message diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb index 75834103db5..fa782c6fbb7 100644 --- a/app/models/project_services/chat_notification_service.rb +++ b/app/models/project_services/chat_notification_service.rb @@ -49,10 +49,7 @@ class ChatNotificationService < Service object_kind = data[:object_kind] - data = data.merge( - project_url: project_url, - project_name: project_name - ) + data = custom_data(data) # WebHook events often have an 'update' event that follows a 'open' or # 'close' action. Ignore update events for now to prevent duplicate @@ -68,8 +65,7 @@ class ChatNotificationService < Service opts[:channel] = channel_name if channel_name opts[:username] = username if username - notifier = Slack::Notifier.new(webhook, opts) - notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback) + return false unless notify(message, opts) true end @@ -92,6 +88,18 @@ class ChatNotificationService < Service private + def notify(message, opts) + Slack::Notifier.new(webhook, opts).ping( + message.pretext, + attachments: message.attachments, + fallback: message.fallback + ) + end + + def custom_data(data) + data.merge(project_url: project_url, project_name: project_name) + end + def get_message(object_kind, data) case object_kind when "push", "tag_push" diff --git a/app/models/project_services/microsoft_teams_service.rb b/app/models/project_services/microsoft_teams_service.rb new file mode 100644 index 00000000000..9b218fd81b4 --- /dev/null +++ b/app/models/project_services/microsoft_teams_service.rb @@ -0,0 +1,56 @@ +class MicrosoftTeamsService < ChatNotificationService + def title + 'Microsoft Teams Notification' + end + + def description + 'Receive event notifications in Microsoft Teams' + end + + def self.to_param + 'microsoft_teams' + end + + def help + 'This service sends notifications about projects events to Microsoft Teams channels.<br /> + To set up this service: + <ol> + <li><a href="https://msdn.microsoft.com/en-us/microsoft-teams/connectors">Getting started with 365 Office Connectors For Microsoft Teams</a>.</li> + <li>Paste the <strong>Webhook URL</strong> into the field below.</li> + <li>Select events below to enable notifications.</li> + </ol>' + end + + def webhook_placeholder + 'https://outlook.office.com/webhook/…' + end + + def event_field(event) + end + + def default_channel_placeholder + end + + def default_fields + [ + { type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" }, + { type: 'checkbox', name: 'notify_only_broken_pipelines' }, + { type: 'checkbox', name: 'notify_only_default_branch' }, + ] + end + + private + + def notify(message, opts) + MicrosoftTeams::Notifier.new(webhook).ping( + title: message.project_name, + pretext: message.pretext, + activity: message.activity, + attachments: message.attachments + ) + end + + def custom_data(data) + super(data).merge(markdown: true) + end +end diff --git a/app/models/service.rb b/app/models/service.rb index 5a0ec58d193..dc76bf925d3 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -237,6 +237,7 @@ class Service < ActiveRecord::Base slack_slash_commands slack teamcity + microsoft_teams ] if Rails.env.development? service_names += %w[mock_ci mock_deployment mock_monitoring] 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/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/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/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/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 18afa811bad..9130dc128fa 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 c9d83d23d56..ab6baaf35b6 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -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/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/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/microsoft-teams-integration.yml b/changelogs/unreleased/microsoft-teams-integration.yml new file mode 100644 index 00000000000..c01902d3401 --- /dev/null +++ b/changelogs/unreleased/microsoft-teams-integration.yml @@ -0,0 +1,4 @@ +--- +title: Integrates Microsoft Teams webhooks with GitLab +merge_request: 10412 +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/20170124193147_add_two_factor_columns_to_namespaces.rb b/db/migrate/20170124193147_add_two_factor_columns_to_namespaces.rb index ca4429c676c..df5cddeb205 100644 --- a/db/migrate/20170124193147_add_two_factor_columns_to_namespaces.rb +++ b/db/migrate/20170124193147_add_two_factor_columns_to_namespaces.rb @@ -16,6 +16,6 @@ class AddTwoFactorColumnsToNamespaces < ActiveRecord::Migration remove_column(:namespaces, :require_two_factor_authentication) remove_column(:namespaces, :two_factor_grace_period) - remove_index(:namespaces, :require_two_factor_authentication) if index_exists?(:namespaces, :require_two_factor_authentication) + remove_concurrent_index(:namespaces, :require_two_factor_authentication) if index_exists?(:namespaces, :require_two_factor_authentication) end end diff --git a/doc/user/project/integrations/img/microsoft_teams_configuration.png b/doc/user/project/integrations/img/microsoft_teams_configuration.png Binary files differnew file mode 100644 index 00000000000..b5c9efc3dd9 --- /dev/null +++ b/doc/user/project/integrations/img/microsoft_teams_configuration.png diff --git a/doc/user/project/integrations/microsoft_teams.md b/doc/user/project/integrations/microsoft_teams.md new file mode 100644 index 00000000000..fbf9c1de443 --- /dev/null +++ b/doc/user/project/integrations/microsoft_teams.md @@ -0,0 +1,33 @@ +# Microsoft Teams Service + +## On Microsoft Teams + +To enable Microsoft Teams integration you must create an incoming webhook integration on Microsoft Teams by following the steps described in this [document](https://msdn.microsoft.com/en-us/microsoft-teams/connectors) + +## On GitLab + +After you set up Microsoft Teams, it's time to set up GitLab. + +Navigate to the [Integrations page](project_services.md#accessing-the-project-services) +and select the **Microsoft Teams Notification** service to configure it. +There, you will see a checkbox with the following events that can be triggered: + +- Push +- Issue +- Confidential issue +- Merge request +- Note +- Tag push +- Pipeline +- Wiki page + +At the end fill in your Microsoft Teams details: + +| Field | Description | +| ----- | ----------- | +| **Webhook** | The incoming webhook URL which you have to setup on Microsoft Teams. | +| **Notify only broken pipelines** | If you choose to enable the **Pipeline** event and you want to be only notified about failed pipelines. | + +After you are all done, click **Save changes** for the changes to take effect. + +![Microsoft Teams configuration](img/microsoft_teams_configuration.png)
\ No newline at end of file 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 5510c65265a..263ec321a34 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 @@ -378,7 +378,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 @@ -392,7 +392,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/api/services.rb b/lib/api/services.rb index 6802a99311e..65f86caaa51 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -488,6 +488,14 @@ module API desc: 'The channel name' } ], + 'microsoft-teams' => [ + { + required: true, + name: :webhook, + type: String, + desc: 'The Microsoft Teams webhook. e.g. https://outlook.office.com/webhook/…' + } + ], 'mattermost' => [ { required: true, @@ -550,6 +558,7 @@ module API RedmineService, SlackService, MattermostService, + MicrosoftTeamsService, TeamcityService, ] diff --git a/lib/api/v3/services.rb b/lib/api/v3/services.rb index 3bacaeee032..bbe07ed4212 100644 --- a/lib/api/v3/services.rb +++ b/lib/api/v3/services.rb @@ -501,6 +501,12 @@ module API desc: 'The channel name' } ], + 'microsoft-teams' => [ + required: true, + name: :webhook, + type: String, + desc: 'The Microsoft Teams webhook. e.g. https://outlook.office.com/webhook/…' + ], 'mattermost' => [ { required: true, diff --git a/lib/microsoft_teams/activity.rb b/lib/microsoft_teams/activity.rb new file mode 100644 index 00000000000..d2c420efdaf --- /dev/null +++ b/lib/microsoft_teams/activity.rb @@ -0,0 +1,19 @@ +module MicrosoftTeams + class Activity + def initialize(title:, subtitle:, text:, image:) + @title = title + @subtitle = subtitle + @text = text + @image = image + end + + def prepare + { + 'activityTitle' => @title, + 'activitySubtitle' => @subtitle, + 'activityText' => @text, + 'activityImage' => @image + } + end + end +end diff --git a/lib/microsoft_teams/notifier.rb b/lib/microsoft_teams/notifier.rb new file mode 100644 index 00000000000..3bef68a1bcb --- /dev/null +++ b/lib/microsoft_teams/notifier.rb @@ -0,0 +1,46 @@ +module MicrosoftTeams + class Notifier + def initialize(webhook) + @webhook = webhook + @header = { 'Content-type' => 'application/json' } + end + + def ping(options = {}) + result = false + + begin + response = HTTParty.post( + @webhook.to_str, + headers: @header, + body: body(options) + ) + + result = true if response + rescue HTTParty::Error, StandardError => error + Rails.logger.info("#{self.class.name}: Error while connecting to #{@webhook}: #{error.message}") + end + + result + end + + private + + def body(options = {}) + result = { 'sections' => [] } + + result['title'] = options[:title] + result['summary'] = options[:pretext] + result['sections'] << MicrosoftTeams::Activity.new(options[:activity]).prepare + + attachments = options[:attachments] + unless attachments.blank? + result['sections'] << { + 'title' => 'Details', + 'facts' => [{ 'name' => 'Attachments', 'value' => attachments }] + } + end + + result.to_json + end + end +end 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/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..65b2fd53397 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 () { @@ -213,6 +216,10 @@ require('vendor/jquery.scrollTo'); 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 +231,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 +243,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/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 1f7a05833ff..9770d2e3b13 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -92,6 +92,11 @@ pipelines: - auto_canceled_by - auto_canceled_pipelines - auto_canceled_jobs +- pending_builds +- retryable_builds +- cancelable_statuses +- manual_actions +- artifacts statuses: - project - pipeline @@ -153,6 +158,7 @@ project: - asana_service - gemnasium_service - slack_service +- microsoft_teams_service - mattermost_service - buildkite_service - bamboo_service @@ -202,6 +208,7 @@ project: - builds - runner_projects - runners +- active_runners - variables - triggers - trigger_schedules diff --git a/spec/lib/microsoft_teams/activity_spec.rb b/spec/lib/microsoft_teams/activity_spec.rb new file mode 100644 index 00000000000..7890ae2e7b0 --- /dev/null +++ b/spec/lib/microsoft_teams/activity_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe MicrosoftTeams::Activity do + subject { described_class.new(title: 'title', subtitle: 'subtitle', text: 'text', image: 'image') } + + describe '#prepare' do + it 'returns the correct JSON object' do + expect(subject.prepare).to eq({ + 'activityTitle' => 'title', + 'activitySubtitle' => 'subtitle', + 'activityText' => 'text', + 'activityImage' => 'image' + }) + end + end +end diff --git a/spec/lib/microsoft_teams/notifier_spec.rb b/spec/lib/microsoft_teams/notifier_spec.rb new file mode 100644 index 00000000000..3035693812f --- /dev/null +++ b/spec/lib/microsoft_teams/notifier_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe MicrosoftTeams::Notifier do + subject { described_class.new(webhook_url) } + + let(:webhook_url) { 'https://example.gitlab.com/'} + let(:header) { { 'Content-Type' => 'application/json' } } + let(:options) do + { + title: 'JohnDoe4/project2', + pretext: '[[JohnDoe4/project2](http://localhost/namespace2/gitlabhq)] Issue [#1 Awesome issue](http://localhost/namespace2/gitlabhq/issues/1) opened by user6', + activity: { + title: 'Issue opened by user6', + subtitle: 'in [JohnDoe4/project2](http://localhost/namespace2/gitlabhq)', + text: '[#1 Awesome issue](http://localhost/namespace2/gitlabhq/issues/1)', + image: 'http://someimage.com' + }, + attachments: 'please fix' + } + end + + let(:body) do + { + 'sections' => [ + { + 'activityTitle' => 'Issue opened by user6', + 'activitySubtitle' => 'in [JohnDoe4/project2](http://localhost/namespace2/gitlabhq)', + 'activityText' => '[#1 Awesome issue](http://localhost/namespace2/gitlabhq/issues/1)', + 'activityImage' => 'http://someimage.com' + }, + { + 'title' => 'Details', + 'facts' => [ + { + 'name' => 'Attachments', + 'value' => 'please fix' + } + ] + } + ], + 'title' => 'JohnDoe4/project2', + 'summary' => '[[JohnDoe4/project2](http://localhost/namespace2/gitlabhq)] Issue [#1 Awesome issue](http://localhost/namespace2/gitlabhq/issues/1) opened by user6' + } + end + + describe '#ping' do + before do + stub_request(:post, webhook_url).with(body: JSON(body), headers: { 'Content-Type' => 'application/json' }).to_return(status: 200, body: "", headers: {}) + end + + it 'expects to receive successfull answer' do + expect(subject.ping(options)).to be true + end + end +end 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/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/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb index 190ff4c535d..34e2d94b1ed 100644 --- a/spec/models/project_services/chat_message/issue_message_spec.rb +++ b/spec/models/project_services/chat_message/issue_message_spec.rb @@ -7,7 +7,8 @@ describe ChatMessage::IssueMessage, models: true do { user: { name: 'Test User', - username: 'test.user' + username: 'test.user', + avatar_url: 'http://someavatar.com' }, project_name: 'project_name', project_url: 'http://somewhere.com', @@ -25,43 +26,84 @@ describe ChatMessage::IssueMessage, models: true do } end - let(:color) { '#C95823' } + context 'without markdown' do + let(:color) { '#C95823' } - context '#initialize' do - before do - args[:object_attributes][:description] = nil + context '#initialize' do + before do + args[:object_attributes][:description] = nil + end + + it 'returns a non-null description' do + expect(subject.description).to eq('') + end end - it 'returns a non-null description' do - expect(subject.description).to eq('') + context 'open' do + it 'returns a message regarding opening of issues' do + expect(subject.pretext).to eq( + '[<http://somewhere.com|project_name>] Issue opened by test.user') + expect(subject.attachments).to eq([ + { + title: "#100 Issue title", + title_link: "http://url.com", + text: "issue description", + color: color, + } + ]) + end end - end - context 'open' do - it 'returns a message regarding opening of issues' do - expect(subject.pretext).to eq( - '[<http://somewhere.com|project_name>] Issue opened by test.user') - expect(subject.attachments).to eq([ - { - title: "#100 Issue title", - title_link: "http://url.com", - text: "issue description", - color: color, - } - ]) + context 'close' do + before do + args[:object_attributes][:action] = 'close' + args[:object_attributes][:state] = 'closed' + end + + it 'returns a message regarding closing of issues' do + expect(subject.pretext). to eq( + '[<http://somewhere.com|project_name>] Issue <http://url.com|#100 Issue title> closed by test.user') + expect(subject.attachments).to be_empty + end end end - context 'close' do + context 'with markdown' do before do - args[:object_attributes][:action] = 'close' - args[:object_attributes][:state] = 'closed' + args[:markdown] = true end - it 'returns a message regarding closing of issues' do - expect(subject.pretext). to eq( - '[<http://somewhere.com|project_name>] Issue <http://url.com|#100 Issue title> closed by test.user') - expect(subject.attachments).to be_empty + context 'open' do + it 'returns a message regarding opening of issues' do + expect(subject.pretext).to eq( + '[[project_name](http://somewhere.com)] Issue opened by test.user') + expect(subject.attachments).to eq('issue description') + expect(subject.activity).to eq({ + title: 'Issue opened by test.user', + subtitle: 'in [project_name](http://somewhere.com)', + text: '[#100 Issue title](http://url.com)', + image: 'http://someavatar.com' + }) + end + end + + context 'close' do + before do + args[:object_attributes][:action] = 'close' + args[:object_attributes][:state] = 'closed' + end + + it 'returns a message regarding closing of issues' do + expect(subject.pretext). to eq( + '[[project_name](http://somewhere.com)] Issue [#100 Issue title](http://url.com) closed by test.user') + expect(subject.attachments).to be_empty + expect(subject.activity).to eq({ + title: 'Issue closed by test.user', + subtitle: 'in [project_name](http://somewhere.com)', + text: '[#100 Issue title](http://url.com)', + image: 'http://someavatar.com' + }) + end end end end diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb index cc154112e90..fa0a1f4a5b7 100644 --- a/spec/models/project_services/chat_message/merge_message_spec.rb +++ b/spec/models/project_services/chat_message/merge_message_spec.rb @@ -7,45 +7,84 @@ describe ChatMessage::MergeMessage, models: true do { user: { name: 'Test User', - username: 'test.user' + username: 'test.user', + avatar_url: 'http://someavatar.com' }, project_name: 'project_name', project_url: 'http://somewhere.com', object_attributes: { - title: "Issue title\nSecond line", + title: "Merge Request title\nSecond line", id: 10, iid: 100, assignee_id: 1, url: 'http://url.com', state: 'opened', - description: 'issue description', + description: 'merge request description', source_branch: 'source_branch', target_branch: 'target_branch', } } end - let(:color) { '#345' } + context 'without markdown' do + let(:color) { '#345' } - context 'open' do - it 'returns a message regarding opening of merge requests' do - expect(subject.pretext).to eq( - 'test.user opened <http://somewhere.com/merge_requests/100|merge request !100> '\ - 'in <http://somewhere.com|project_name>: *Issue title*') - expect(subject.attachments).to be_empty + context 'open' do + it 'returns a message regarding opening of merge requests' do + expect(subject.pretext).to eq( + 'test.user opened <http://somewhere.com/merge_requests/100|!100 *Merge Request title*> in <http://somewhere.com|project_name>: *Merge Request title*') + expect(subject.attachments).to be_empty + end + end + + context 'close' do + before do + args[:object_attributes][:state] = 'closed' + end + it 'returns a message regarding closing of merge requests' do + expect(subject.pretext).to eq( + 'test.user closed <http://somewhere.com/merge_requests/100|!100 *Merge Request title*> in <http://somewhere.com|project_name>: *Merge Request title*') + expect(subject.attachments).to be_empty + end end end - context 'close' do + context 'with markdown' do before do - args[:object_attributes][:state] = 'closed' + args[:markdown] = true + end + + context 'open' do + it 'returns a message regarding opening of merge requests' do + expect(subject.pretext).to eq( + 'test.user opened [!100 *Merge Request title*](http://somewhere.com/merge_requests/100) in [project_name](http://somewhere.com): *Merge Request title*') + expect(subject.attachments).to be_empty + expect(subject.activity).to eq({ + title: 'Merge Request opened by test.user', + subtitle: 'in [project_name](http://somewhere.com)', + text: '[!100 *Merge Request title*](http://somewhere.com/merge_requests/100)', + image: 'http://someavatar.com' + }) + end end - it 'returns a message regarding closing of merge requests' do - expect(subject.pretext).to eq( - 'test.user closed <http://somewhere.com/merge_requests/100|merge request !100> '\ - 'in <http://somewhere.com|project_name>: *Issue title*') - expect(subject.attachments).to be_empty + + context 'close' do + before do + args[:object_attributes][:state] = 'closed' + end + + it 'returns a message regarding closing of merge requests' do + expect(subject.pretext).to eq( + 'test.user closed [!100 *Merge Request title*](http://somewhere.com/merge_requests/100) in [project_name](http://somewhere.com): *Merge Request title*') + expect(subject.attachments).to be_empty + expect(subject.activity).to eq({ + title: 'Merge Request closed by test.user', + subtitle: 'in [project_name](http://somewhere.com)', + text: '[!100 *Merge Request title*](http://somewhere.com/merge_requests/100)', + image: 'http://someavatar.com' + }) + end end end end diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb index da700a08e57..7cd9c61ee2b 100644 --- a/spec/models/project_services/chat_message/note_message_spec.rb +++ b/spec/models/project_services/chat_message/note_message_spec.rb @@ -1,130 +1,190 @@ require 'spec_helper' describe ChatMessage::NoteMessage, models: true do - let(:color) { '#345' } + subject { described_class.new(args) } - before do - @args = { - user: { - name: 'Test User', - username: 'test.user', - avatar_url: 'http://fakeavatar' - }, - project_name: 'project_name', - project_url: 'http://somewhere.com', - repository: { - name: 'project_name', - url: 'http://somewhere.com', - }, - object_attributes: { - id: 10, - note: 'comment on a commit', - url: 'http://url.com', - noteable_type: 'Commit' - } + let(:color) { '#345' } + let(:args) do + { + user: { + name: 'Test User', + username: 'test.user', + avatar_url: 'http://fakeavatar' + }, + project_name: 'project_name', + project_url: 'http://somewhere.com', + repository: { + name: 'project_name', + url: 'http://somewhere.com', + }, + object_attributes: { + id: 10, + note: 'comment on a commit', + url: 'http://url.com', + noteable_type: 'Commit' + } } end context 'commit notes' do before do - @args[:object_attributes][:note] = 'comment on a commit' - @args[:object_attributes][:noteable_type] = 'Commit' - @args[:commit] = { - id: '5f163b2b95e6f53cbd428f5f0b103702a52b9a23', - message: "Added a commit message\ndetails\n123\n" + args[:object_attributes][:note] = 'comment on a commit' + args[:object_attributes][:noteable_type] = 'Commit' + args[:commit] = { + id: '5f163b2b95e6f53cbd428f5f0b103702a52b9a23', + message: "Added a commit message\ndetails\n123\n" } end - it 'returns a message regarding notes on commits' do - message = described_class.new(@args) - expect(message.pretext).to eq("test.user <http://url.com|commented on " \ - "commit 5f163b2b> in <http://somewhere.com|project_name>: " \ - "*Added a commit message*") - expected_attachments = [ - { - text: "comment on a commit", - color: color, - } - ] - expect(message.attachments).to eq(expected_attachments) + context 'without markdown' do + it 'returns a message regarding notes on commits' do + expect(subject.pretext).to eq("test.user <http://url.com|commented on " \ + "commit 5f163b2b> in <http://somewhere.com|project_name>: " \ + "*Added a commit message*") + expect(subject.attachments).to eq([{ + text: 'comment on a commit', + color: color + }]) + end + end + + context 'with markdown' do + before do + args[:markdown] = true + end + + it 'returns a message regarding notes on commits' do + expect(subject.pretext).to eq( + 'test.user [commented on commit 5f163b2b](http://url.com) in [project_name](http://somewhere.com): *Added a commit message*' + ) + expect(subject.attachments).to eq('comment on a commit') + expect(subject.activity).to eq({ + title: 'test.user [commented on commit 5f163b2b](http://url.com)', + subtitle: 'in [project_name](http://somewhere.com)', + text: 'Added a commit message', + image: 'http://fakeavatar' + }) + end end end context 'merge request notes' do before do - @args[:object_attributes][:note] = 'comment on a merge request' - @args[:object_attributes][:noteable_type] = 'MergeRequest' - @args[:merge_request] = { - id: 1, - iid: 30, - title: "merge request title\ndetails\n" + args[:object_attributes][:note] = 'comment on a merge request' + args[:object_attributes][:noteable_type] = 'MergeRequest' + args[:merge_request] = { + id: 1, + iid: 30, + title: "merge request title\ndetails\n" } end - it 'returns a message regarding notes on a merge request' do - message = described_class.new(@args) - expect(message.pretext).to eq("test.user <http://url.com|commented on " \ - "merge request !30> in <http://somewhere.com|project_name>: " \ - "*merge request title*") - expected_attachments = [ - { - text: "comment on a merge request", - color: color, - } - ] - expect(message.attachments).to eq(expected_attachments) + context 'without markdown' do + it 'returns a message regarding notes on a merge request' do + expect(subject.pretext).to eq("test.user <http://url.com|commented on " \ + "merge request !30> in <http://somewhere.com|project_name>: " \ + "*merge request title*") + expect(subject.attachments).to eq([{ + text: 'comment on a merge request', + color: color + }]) + end + end + + context 'with markdown' do + before do + args[:markdown] = true + end + + it 'returns a message regarding notes on a merge request' do + expect(subject.pretext).to eq( + 'test.user [commented on merge request !30](http://url.com) in [project_name](http://somewhere.com): *merge request title*') + expect(subject.attachments).to eq('comment on a merge request') + expect(subject.activity).to eq({ + title: 'test.user [commented on merge request !30](http://url.com)', + subtitle: 'in [project_name](http://somewhere.com)', + text: 'merge request title', + image: 'http://fakeavatar' + }) + end end end context 'issue notes' do before do - @args[:object_attributes][:note] = 'comment on an issue' - @args[:object_attributes][:noteable_type] = 'Issue' - @args[:issue] = { - id: 1, - iid: 20, - title: "issue title\ndetails\n" + args[:object_attributes][:note] = 'comment on an issue' + args[:object_attributes][:noteable_type] = 'Issue' + args[:issue] = { + id: 1, + iid: 20, + title: "issue title\ndetails\n" } end - it 'returns a message regarding notes on an issue' do - message = described_class.new(@args) - expect(message.pretext).to eq( - "test.user <http://url.com|commented on " \ - "issue #20> in <http://somewhere.com|project_name>: " \ - "*issue title*") - expected_attachments = [ - { - text: "comment on an issue", - color: color, - } - ] - expect(message.attachments).to eq(expected_attachments) + context 'without markdown' do + it 'returns a message regarding notes on an issue' do + expect(subject.pretext).to eq( + "test.user <http://url.com|commented on " \ + "issue #20> in <http://somewhere.com|project_name>: " \ + "*issue title*") + expect(subject.attachments).to eq([{ + text: 'comment on an issue', + color: color + }]) + end + end + + context 'with markdown' do + before do + args[:markdown] = true + end + + it 'returns a message regarding notes on an issue' do + expect(subject.pretext).to eq( + 'test.user [commented on issue #20](http://url.com) in [project_name](http://somewhere.com): *issue title*') + expect(subject.attachments).to eq('comment on an issue') + expect(subject.activity).to eq({ + title: 'test.user [commented on issue #20](http://url.com)', + subtitle: 'in [project_name](http://somewhere.com)', + text: 'issue title', + image: 'http://fakeavatar' + }) + end end end context 'project snippet notes' do before do - @args[:object_attributes][:note] = 'comment on a snippet' - @args[:object_attributes][:noteable_type] = 'Snippet' - @args[:snippet] = { - id: 5, - title: "snippet title\ndetails\n" + args[:object_attributes][:note] = 'comment on a snippet' + args[:object_attributes][:noteable_type] = 'Snippet' + args[:snippet] = { + id: 5, + title: "snippet title\ndetails\n" } end - it 'returns a message regarding notes on a project snippet' do - message = described_class.new(@args) - expect(message.pretext).to eq("test.user <http://url.com|commented on " \ - "snippet #5> in <http://somewhere.com|project_name>: " \ - "*snippet title*") - expected_attachments = [ - { - text: "comment on a snippet", - color: color, - } - ] - expect(message.attachments).to eq(expected_attachments) + context 'without markdown' do + it 'returns a message regarding notes on a project snippet' do + expect(subject.pretext).to eq("test.user <http://url.com|commented on " \ + "snippet $5> in <http://somewhere.com|project_name>: " \ + "*snippet title*") + expect(subject.attachments).to eq([{ + text: 'comment on a snippet', + color: color + }]) + end + end + + context 'with markdown' do + before do + args[:markdown] = true + end + + it 'returns a message regarding notes on a project snippet' do + expect(subject.pretext).to eq( + 'test.user [commented on snippet $5](http://url.com) in [project_name](http://somewhere.com): *snippet title*') + expect(subject.attachments).to eq('comment on a snippet') + end end end end diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb index bf2a9616455..ec5c6c5e0ed 100644 --- a/spec/models/project_services/chat_message/pipeline_message_spec.rb +++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe ChatMessage::PipelineMessage do subject { described_class.new(args) } - let(:user) { { name: 'hacker' } } + let(:user) { { name: 'hacker' } } let(:args) do { object_attributes: { @@ -14,54 +14,122 @@ describe ChatMessage::PipelineMessage do status: status, duration: duration }, - project: { path_with_namespace: 'project_name', - web_url: 'http://example.gitlab.com' }, + project: { + path_with_namespace: 'project_name', + web_url: 'http://example.gitlab.com' + }, user: user } end - let(:message) { build_message } + context 'without markdown' do + context 'pipeline succeeded' do + let(:status) { 'success' } + let(:color) { 'good' } + let(:duration) { 10 } + let(:message) { build_message('passed') } + + it 'returns a message with information about succeeded build' do + expect(subject.pretext).to be_empty + expect(subject.fallback).to eq(message) + expect(subject.attachments).to eq([text: message, color: color]) + end + end - context 'pipeline succeeded' do - let(:status) { 'success' } - let(:color) { 'good' } - let(:duration) { 10 } - let(:message) { build_message('passed') } + context 'pipeline failed' do + let(:status) { 'failed' } + let(:color) { 'danger' } + let(:duration) { 10 } + let(:message) { build_message } - it 'returns a message with information about succeeded build' do - verify_message + it 'returns a message with information about failed build' do + expect(subject.pretext).to be_empty + expect(subject.fallback).to eq(message) + expect(subject.attachments).to eq([text: message, color: color]) + end + + context 'when triggered by API therefore lacking user' do + let(:user) { nil } + let(:message) { build_message(status, 'API') } + + it 'returns a message stating it is by API' do + expect(subject.pretext).to be_empty + expect(subject.fallback).to eq(message) + expect(subject.attachments).to eq([text: message, color: color]) + end + end end - end - context 'pipeline failed' do - let(:status) { 'failed' } - let(:color) { 'danger' } - let(:duration) { 10 } + def build_message(status_text = status, name = user[:name]) + "<http://example.gitlab.com|project_name>:" \ + " Pipeline <http://example.gitlab.com/pipelines/123|#123>" \ + " of <http://example.gitlab.com/commits/develop|develop> branch" \ + " by #{name} #{status_text} in #{duration} #{'second'.pluralize(duration)}" + end + end - it 'returns a message with information about failed build' do - verify_message + context 'with markdown' do + before do + args[:markdown] = true end - context 'when triggered by API therefore lacking user' do - let(:user) { nil } - let(:message) { build_message(status, 'API') } + context 'pipeline succeeded' do + let(:status) { 'success' } + let(:color) { 'good' } + let(:duration) { 10 } + let(:message) { build_markdown_message('passed') } - it 'returns a message stating it is by API' do - verify_message + it 'returns a message with information about succeeded build' do + expect(subject.pretext).to be_empty + expect(subject.attachments).to eq(message) + expect(subject.activity).to eq({ + title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of [develop](http://example.gitlab.com/commits/develop) branch by hacker passed', + subtitle: 'in [project_name](http://example.gitlab.com)', + text: 'in 10 seconds', + image: '' + }) end end - end - def verify_message - expect(subject.pretext).to be_empty - expect(subject.fallback).to eq(message) - expect(subject.attachments).to eq([text: message, color: color]) - end + context 'pipeline failed' do + let(:status) { 'failed' } + let(:color) { 'danger' } + let(:duration) { 10 } + let(:message) { build_markdown_message } + + it 'returns a message with information about failed build' do + expect(subject.pretext).to be_empty + expect(subject.attachments).to eq(message) + expect(subject.activity).to eq({ + title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of [develop](http://example.gitlab.com/commits/develop) branch by hacker failed', + subtitle: 'in [project_name](http://example.gitlab.com)', + text: 'in 10 seconds', + image: '' + }) + end - def build_message(status_text = status, name = user[:name]) - "<http://example.gitlab.com|project_name>:" \ - " Pipeline <http://example.gitlab.com/pipelines/123|#123>" \ - " of <http://example.gitlab.com/commits/develop|develop> branch" \ - " by #{name} #{status_text} in #{duration} #{'second'.pluralize(duration)}" + context 'when triggered by API therefore lacking user' do + let(:user) { nil } + let(:message) { build_markdown_message(status, 'API') } + + it 'returns a message stating it is by API' do + expect(subject.pretext).to be_empty + expect(subject.attachments).to eq(message) + expect(subject.activity).to eq({ + title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of [develop](http://example.gitlab.com/commits/develop) branch by API failed', + subtitle: 'in [project_name](http://example.gitlab.com)', + text: 'in 10 seconds', + image: '' + }) + end + end + end + + def build_markdown_message(status_text = status, name = user[:name]) + "[project_name](http://example.gitlab.com):" \ + " Pipeline [#123](http://example.gitlab.com/pipelines/123)" \ + " of [develop](http://example.gitlab.com/commits/develop)" \ + " branch by #{name} #{status_text} in #{duration} #{'second'.pluralize(duration)}" + end end end diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb index 24928873bad..63eb078c44e 100644 --- a/spec/models/project_services/chat_message/push_message_spec.rb +++ b/spec/models/project_services/chat_message/push_message_spec.rb @@ -10,6 +10,7 @@ describe ChatMessage::PushMessage, models: true do project_name: 'project_name', ref: 'refs/heads/master', user_name: 'test.user', + user_avatar: 'http://someavatar.com', project_url: 'http://url.com' } end @@ -24,18 +25,36 @@ describe ChatMessage::PushMessage, models: true do ] end - it 'returns a message regarding pushes' do - expect(subject.pretext).to eq( - 'test.user pushed to branch <http://url.com/commits/master|master> of '\ - '<http://url.com|project_name> (<http://url.com/compare/before...after|Compare changes>)' - ) - expect(subject.attachments).to eq([ - { - text: "<http://url1.com|abcdefgh>: message1 - author1\n"\ - "<http://url2.com|12345678>: message2 - author2", + context 'without markdown' do + it 'returns a message regarding pushes' do + expect(subject.pretext).to eq( + 'test.user pushed to branch <http://url.com/commits/master|master> of '\ + '<http://url.com|project_name> (<http://url.com/compare/before...after|Compare changes>)') + expect(subject.attachments).to eq([{ + text: "<http://url1.com|abcdefgh>: message1 - author1\n\n"\ + "<http://url2.com|12345678>: message2 - author2", color: color, - } - ]) + }]) + end + end + + context 'with markdown' do + before do + args[:markdown] = true + end + + it 'returns a message regarding pushes' do + expect(subject.pretext).to eq( + 'test.user pushed to branch [master](http://url.com/commits/master) of [project_name](http://url.com) ([Compare changes](http://url.com/compare/before...after))') + expect(subject.attachments).to eq( + "[abcdefgh](http://url1.com): message1 - author1\n\n[12345678](http://url2.com): message2 - author2") + expect(subject.activity).to eq({ + title: 'test.user pushed to branch', + subtitle: 'in [project_name](http://url.com)', + text: '[Compare changes](http://url.com/compare/before...after)', + image: 'http://someavatar.com' + }) + end end end @@ -47,15 +66,36 @@ describe ChatMessage::PushMessage, models: true do project_name: 'project_name', ref: 'refs/tags/new_tag', user_name: 'test.user', + user_avatar: 'http://someavatar.com', project_url: 'http://url.com' } end - it 'returns a message regarding pushes' do - expect(subject.pretext).to eq('test.user pushed new tag ' \ - '<http://url.com/commits/new_tag|new_tag> to ' \ - '<http://url.com|project_name>') - expect(subject.attachments).to be_empty + context 'without markdown' do + it 'returns a message regarding pushes' do + expect(subject.pretext).to eq('test.user pushed new tag ' \ + '<http://url.com/commits/new_tag|new_tag> to ' \ + '<http://url.com|project_name>') + expect(subject.attachments).to be_empty + end + end + + context 'with markdown' do + before do + args[:markdown] = true + end + + it 'returns a message regarding pushes' do + expect(subject.pretext).to eq( + 'test.user pushed new tag [new_tag](http://url.com/commits/new_tag) to [project_name](http://url.com)') + expect(subject.attachments).to be_empty + expect(subject.activity).to eq({ + title: 'test.user created tag', + subtitle: 'in [project_name](http://url.com)', + text: '[Compare changes](http://url.com/compare/0000000000000000000000000000000000000000...after)', + image: 'http://someavatar.com' + }) + end end end @@ -64,12 +104,31 @@ describe ChatMessage::PushMessage, models: true do args[:before] = Gitlab::Git::BLANK_SHA end - it 'returns a message regarding a new branch' do - expect(subject.pretext).to eq( - 'test.user pushed new branch <http://url.com/commits/master|master> to '\ - '<http://url.com|project_name>' - ) - expect(subject.attachments).to be_empty + context 'without markdown' do + it 'returns a message regarding a new branch' do + expect(subject.pretext).to eq( + 'test.user pushed new branch <http://url.com/commits/master|master> to '\ + '<http://url.com|project_name>') + expect(subject.attachments).to be_empty + end + end + + context 'with markdown' do + before do + args[:markdown] = true + end + + it 'returns a message regarding a new branch' do + expect(subject.pretext).to eq( + 'test.user pushed new branch [master](http://url.com/commits/master) to [project_name](http://url.com)') + expect(subject.attachments).to be_empty + expect(subject.activity).to eq({ + title: 'test.user created branch', + subtitle: 'in [project_name](http://url.com)', + text: '[Compare changes](http://url.com/compare/0000000000000000000000000000000000000000...after)', + image: 'http://someavatar.com' + }) + end end end @@ -78,11 +137,30 @@ describe ChatMessage::PushMessage, models: true do args[:after] = Gitlab::Git::BLANK_SHA end - it 'returns a message regarding a removed branch' do - expect(subject.pretext).to eq( - 'test.user removed branch master from <http://url.com|project_name>' - ) - expect(subject.attachments).to be_empty + context 'without markdown' do + it 'returns a message regarding a removed branch' do + expect(subject.pretext).to eq( + 'test.user removed branch master from <http://url.com|project_name>') + expect(subject.attachments).to be_empty + end + end + + context 'with markdown' do + before do + args[:markdown] = true + end + + it 'returns a message regarding a removed branch' do + expect(subject.pretext).to eq( + 'test.user removed branch master from [project_name](http://url.com)') + expect(subject.attachments).to be_empty + expect(subject.activity).to eq({ + title: 'test.user removed branch', + subtitle: 'in [project_name](http://url.com)', + text: '[Compare changes](http://url.com/compare/before...0000000000000000000000000000000000000000)', + image: 'http://someavatar.com' + }) + end end end end diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb index a2ad61e38e7..0df7db2abc2 100644 --- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb +++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb @@ -7,7 +7,8 @@ describe ChatMessage::WikiPageMessage, models: true do { user: { name: 'Test User', - username: 'test.user' + username: 'test.user', + avatar_url: 'http://someavatar.com' }, project_name: 'project_name', project_url: 'http://somewhere.com', @@ -19,54 +20,128 @@ describe ChatMessage::WikiPageMessage, models: true do } end - describe '#pretext' do - context 'when :action == "create"' do - before { args[:object_attributes][:action] = 'create' } + context 'without markdown' do + describe '#pretext' do + context 'when :action == "create"' do + before { args[:object_attributes][:action] = 'create' } - it 'returns a message that a new wiki page was created' do - expect(subject.pretext).to eq( - 'test.user created <http://url.com|wiki page> in <http://somewhere.com|project_name>: '\ - '*Wiki page title*') + it 'returns a message that a new wiki page was created' do + expect(subject.pretext).to eq( + 'test.user created <http://url.com|wiki page> in <http://somewhere.com|project_name>: '\ + '*Wiki page title*') + end + end + + context 'when :action == "update"' do + before { args[:object_attributes][:action] = 'update' } + + it 'returns a message that a wiki page was updated' do + expect(subject.pretext).to eq( + 'test.user edited <http://url.com|wiki page> in <http://somewhere.com|project_name>: '\ + '*Wiki page title*') + end end end - context 'when :action == "update"' do - before { args[:object_attributes][:action] = 'update' } + describe '#attachments' do + let(:color) { '#345' } - it 'returns a message that a wiki page was updated' do - expect(subject.pretext).to eq( - 'test.user edited <http://url.com|wiki page> in <http://somewhere.com|project_name>: '\ - '*Wiki page title*') + context 'when :action == "create"' do + before { args[:object_attributes][:action] = 'create' } + + it 'returns the attachment for a new wiki page' do + expect(subject.attachments).to eq([ + { + text: "Wiki page description", + color: color, + } + ]) + end + end + + context 'when :action == "update"' do + before { args[:object_attributes][:action] = 'update' } + + it 'returns the attachment for an updated wiki page' do + expect(subject.attachments).to eq([ + { + text: "Wiki page description", + color: color, + } + ]) + end end end end - describe '#attachments' do - let(:color) { '#345' } + context 'with markdown' do + before do + args[:markdown] = true + end + + describe '#pretext' do + context 'when :action == "create"' do + before { args[:object_attributes][:action] = 'create' } + + it 'returns a message that a new wiki page was created' do + expect(subject.pretext).to eq( + 'test.user created [wiki page](http://url.com) in [project_name](http://somewhere.com): *Wiki page title*') + end + end - context 'when :action == "create"' do - before { args[:object_attributes][:action] = 'create' } + context 'when :action == "update"' do + before { args[:object_attributes][:action] = 'update' } - it 'returns the attachment for a new wiki page' do - expect(subject.attachments).to eq([ - { - text: "Wiki page description", - color: color, - } - ]) + it 'returns a message that a wiki page was updated' do + expect(subject.pretext).to eq( + 'test.user edited [wiki page](http://url.com) in [project_name](http://somewhere.com): *Wiki page title*') + end end end - context 'when :action == "update"' do - before { args[:object_attributes][:action] = 'update' } + describe '#attachments' do + context 'when :action == "create"' do + before { args[:object_attributes][:action] = 'create' } + + it 'returns the attachment for a new wiki page' do + expect(subject.attachments).to eq('Wiki page description') + end + end + + context 'when :action == "update"' do + before { args[:object_attributes][:action] = 'update' } + + it 'returns the attachment for an updated wiki page' do + expect(subject.attachments).to eq('Wiki page description') + end + end + end + + describe '#activity' do + context 'when :action == "create"' do + before { args[:object_attributes][:action] = 'create' } + + it 'returns the attachment for a new wiki page' do + expect(subject.activity).to eq({ + title: 'test.user created [wiki page](http://url.com)', + subtitle: 'in [project_name](http://somewhere.com)', + text: 'Wiki page title', + image: 'http://someavatar.com' + }) + end + end + + context 'when :action == "update"' do + before { args[:object_attributes][:action] = 'update' } - it 'returns the attachment for an updated wiki page' do - expect(subject.attachments).to eq([ - { - text: "Wiki page description", - color: color, - } - ]) + it 'returns the attachment for an updated wiki page' do + expect(subject.activity).to eq({ + title: 'test.user edited [wiki page](http://url.com)', + subtitle: 'in [project_name](http://somewhere.com)', + text: 'Wiki page title', + image: 'http://someavatar.com' + }) + end end end end diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb new file mode 100644 index 00000000000..facc034f69c --- /dev/null +++ b/spec/models/project_services/microsoft_teams_service_spec.rb @@ -0,0 +1,277 @@ +require 'spec_helper' + +describe MicrosoftTeamsService, models: true do + let(:chat_service) { described_class.new } + let(:webhook_url) { 'https://example.gitlab.com/' } + + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:webhook) } + it_behaves_like 'issue tracker service URL attribute', :webhook + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:webhook) } + end + end + + describe "#execute" do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + + before do + allow(chat_service).to receive_messages( + project: project, + project_id: project.id, + service_hook: true, + webhook: webhook_url + ) + + WebMock.stub_request(:post, webhook_url) + end + + context 'with push events' do + let(:push_sample_data) do + Gitlab::DataBuilder::Push.build_sample(project, user) + end + + it "calls Microsoft Teams API for push events" do + chat_service.execute(push_sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + + it 'specifies the webhook when it is configured' do + expect(MicrosoftTeams::Notifier).to receive(:new).with(webhook_url).and_return(double(:microsoft_teams_service).as_null_object) + + chat_service.execute(push_sample_data) + end + end + + context 'with issue events' do + let(:opts) { { title: 'Awesome issue', description: 'please fix' } } + let(:issues_sample_data) do + service = Issues::CreateService.new(project, user, opts) + issue = service.execute + service.hook_data(issue, 'open') + end + + it "calls Microsoft Teams API" do + chat_service.execute(issues_sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + end + + context 'with merge events' do + let(:opts) do + { + title: 'Awesome merge_request', + description: 'please fix', + source_branch: 'feature', + target_branch: 'master' + } + end + + let(:merge_sample_data) do + service = MergeRequests::CreateService.new(project, user, opts) + merge_request = service.execute + service.hook_data(merge_request, 'open') + end + + it "calls Microsoft Teams API" do + chat_service.execute(merge_sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + end + + context 'with wiki page events' do + let(:opts) do + { + title: "Awesome wiki_page", + content: "Some text describing some thing or another", + format: "md", + message: "user created page: Awesome wiki_page" + } + end + + let(:wiki_page_sample_data) do + service = WikiPages::CreateService.new(project, user, opts) + wiki_page = service.execute + service.hook_data(wiki_page, 'create') + end + + it "calls Microsoft Teams API" do + chat_service.execute(wiki_page_sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + end + end + + describe "Note events" do + let(:user) { create(:user) } + let(:project) { create(:project, :repository, creator: user) } + + before do + allow(chat_service).to receive_messages( + project: project, + project_id: project.id, + service_hook: true, + webhook: webhook_url + ) + + WebMock.stub_request(:post, webhook_url) + end + + context 'when commit comment event executed' do + let(:commit_note) do + create(:note_on_commit, author: user, + project: project, + commit_id: project.repository.commit.id, + note: 'a comment on a commit') + end + + it "calls Microsoft Teams API for commit comment events" do + data = Gitlab::DataBuilder::Note.build(commit_note, user) + + chat_service.execute(data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + end + + context 'when merge request comment event executed' do + let(:merge_request_note) do + create(:note_on_merge_request, project: project, + note: "merge request note") + end + + it "calls Microsoft Teams API for merge request comment events" do + data = Gitlab::DataBuilder::Note.build(merge_request_note, user) + + chat_service.execute(data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + end + + context 'when issue comment event executed' do + let(:issue_note) do + create(:note_on_issue, project: project, note: "issue note") + end + + it "calls Microsoft Teams API for issue comment events" do + data = Gitlab::DataBuilder::Note.build(issue_note, user) + + chat_service.execute(data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + end + + context 'when snippet comment event executed' do + let(:snippet_note) do + create(:note_on_project_snippet, project: project, + note: "snippet note") + end + + it "calls Microsoft Teams API for snippet comment events" do + data = Gitlab::DataBuilder::Note.build(snippet_note, user) + + chat_service.execute(data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + end + end + + describe 'Pipeline events' do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + + let(:pipeline) do + create(:ci_pipeline, + project: project, status: status, + sha: project.commit.sha, ref: project.default_branch) + end + + before do + allow(chat_service).to receive_messages( + project: project, + service_hook: true, + webhook: webhook_url + ) + end + + shared_examples 'call Microsoft Teams API' do + before do + WebMock.stub_request(:post, webhook_url) + end + + it 'calls Microsoft Teams API for pipeline events' do + data = Gitlab::DataBuilder::Pipeline.build(pipeline) + + chat_service.execute(data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + end + + context 'with failed pipeline' do + let(:status) { 'failed' } + + it_behaves_like 'call Microsoft Teams API' + end + + context 'with succeeded pipeline' do + let(:status) { 'success' } + + context 'with default to notify_only_broken_pipelines' do + it 'does not call Microsoft Teams API for pipeline events' do + data = Gitlab::DataBuilder::Pipeline.build(pipeline) + result = chat_service.execute(data) + + expect(result).to be_falsy + end + end + + context 'with setting notify_only_broken_pipelines to false' do + before do + chat_service.notify_only_broken_pipelines = false + end + + it_behaves_like 'call Microsoft Teams API' + end + end + + context 'only notify for the default branch' do + context 'when enabled' do + let(:pipeline) do + create(:ci_pipeline, project: project, status: 'failed', ref: 'not-the-default-branch') + end + + before do + chat_service.notify_only_default_branch = true + end + + it 'does not call the Microsoft Teams API for pipeline events' do + data = Gitlab::DataBuilder::Pipeline.build(pipeline) + result = chat_service.execute(data) + + expect(result).to be_falsy + end + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index da1372fe761..6d4ef78f8ec 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -22,6 +22,7 @@ describe Project, models: true do it { is_expected.to have_many(:protected_branches).dependent(:destroy) } it { is_expected.to have_one(:forked_project_link).dependent(:destroy) } it { is_expected.to have_one(:slack_service).dependent(:destroy) } + it { is_expected.to have_one(:microsoft_teams_service).dependent(:destroy) } it { is_expected.to have_one(:mattermost_service).dependent(:destroy) } it { is_expected.to have_one(:pushover_service).dependent(:destroy) } it { is_expected.to have_one(:asana_service).dependent(:destroy) } @@ -57,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/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/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/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 |