Commit f8fe64a6 authored by Lin Jen-Shin's avatar Lin Jen-Shin

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
  ...
parents e28fc7b1 6ea35f98
......@@ -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"
......
......@@ -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);
});
});
......@@ -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))
......
......@@ -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':
......
......@@ -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"
......
/* 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() {
......
/* 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;
}
};
/* 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);
......@@ -187,6 +187,15 @@
}
}
.shortcut-mappings {
display: none;
}
&.shortcuts .shortcut-mappings {
display: inline-block;
margin-right: 5px;
}
ul {
margin: 0;
padding: 0;
......
.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;
}
......
......@@ -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 {
......
......@@ -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;
}
}
}
}
}
......
......@@ -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)
......
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}",
......
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
......@@ -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?
......
......@@ -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?
......
......@@ -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)
......
......@@ -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
......
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
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
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
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
......@@ -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
......
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
......
......@@ -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"
......
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
......@@ -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]
......
......@@ -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
......
......@@ -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?
......
......@@ -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
......
......@@ -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"
......@@ -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|
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
- 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
......
......@@ -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
......
......@@ -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"
......@@ -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
......@@ -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
......
......@@ -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
......@@ -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
......@@ -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'
......@@ -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'
......@@ -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
......
......@@ -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
......@@ -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 }
......
.profile-icon
= custom_icon("comment_o")
= custom_icon("icon_comment_o")
.event-title
= event.action_name
......
......@@ -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"
......@@ -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
......
......@@ -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
......
......@@ -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"
......@@ -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
......
......@@ -15,6 +15,10 @@
%tr
%th
%th Global Shortcuts
%tr
%td.shortcut
.key n
%td Main Navigation
%tr
%td.shortcut
.key s
......@@ -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
......@@ -194,6 +177,12 @@
.key s
%td
Go to snippets
%tr
%td.shortcut
.key g
.key w
%td
Go to wiki
%tr
%td.shortcut
.key t
......@@ -202,6 +191,33 @@
%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' }
......
......@@ -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.
......
......@@ -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
......
......@@ -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
......
%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
......
......@@ -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?
......
......@@ -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'
......@@ -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
......
......@@ -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
......
.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}
......
= 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
......@@ -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
......@@ -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),
......
......@@ -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
......
......@@ -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
......
......@@ -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
.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
......
......@@ -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)
......@@ -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) }
......
......@@ -21,6 +21,6 @@
&nbsp;
= 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'
......@@ -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 } }
......@@ -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
......@@ -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.
......
......@@ -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
......
......@@ -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,
});
......@@ -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}
......
......@@ -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
......
......@@ -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.
......
......@@ -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
......@@ -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
......
......@@ -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
......
......@@ -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
......@@ -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) }
......
......@@ -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")
%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}" }
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
- 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
......@@ -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'
......@@ -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
......
......@@ -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){
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment