Commit 2f22890d authored by Luke "Jared" Bennett's avatar Luke "Jared" Bennett

Merge branch 'update-droplab-to-webpack-version' into new-resolvable-discussion

parents 907b7541 fb7b0d6e
...@@ -20,6 +20,7 @@ import eventHub from '../eventhub'; ...@@ -20,6 +20,7 @@ import eventHub from '../eventhub';
list: { list: {
type: Object, type: Object,
required: false, required: false,
default: () => ({}),
}, },
rootPath: { rootPath: {
type: String, type: String,
...@@ -31,6 +32,26 @@ import eventHub from '../eventhub'; ...@@ -31,6 +32,26 @@ import eventHub from '../eventhub';
default: false, 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: { methods: {
showLabel(label) { showLabel(label) {
if (!this.list) return true; if (!this.list) return true;
...@@ -67,35 +88,41 @@ import eventHub from '../eventhub'; ...@@ -67,35 +88,41 @@ import eventHub from '../eventhub';
}, },
template: ` template: `
<div> <div>
<h4 class="card-title"> <div class="card-header">
<i <h4 class="card-title">
class="fa fa-eye-slash confidential-icon" <i
v-if="issue.confidential"></i> class="fa fa-eye-slash confidential-icon"
<a v-if="issue.confidential"
:href="issueLinkBase + '/' + issue.id" aria-hidden="true"
:title="issue.title"> />
{{ issue.title }} <a
</a> class="js-no-trigger"
</h4> :href="cardUrl"
<div class="card-footer"> :title="issue.title">{{ issue.title }}</a>
<span <span
class="card-number" class="card-number"
v-if="issue.id"> v-if="issue.id"
#{{ issue.id }} >
</span> {{ issueId }}
</span>
</h4>
<a <a
class="card-assignee has-tooltip js-no-trigger" class="card-assignee has-tooltip js-no-trigger"
:href="rootPath + issue.assignee.username" :href="assigneeUrl"
:title="'Assigned to ' + issue.assignee.name" :title="assigneeUrlTitle"
v-if="issue.assignee" v-if="issue.assignee"
data-container="body"> data-container="body"
>
<img <img
class="avatar avatar-inline s20 js-no-trigger" class="avatar avatar-inline s20 js-no-trigger"
:src="issue.assignee.avatar" :src="issue.assignee.avatar"
width="20" width="20"
height="20" height="20"
:alt="'Avatar for ' + issue.assignee.name" /> :alt="avatarUrlTitle"
/>
</a> </a>
</div>
<div class="card-footer" v-if="showLabelFooter">
<button <button
class="label color-label has-tooltip js-no-trigger" class="label color-label has-tooltip js-no-trigger"
v-for="label in issue.labels" v-for="label in issue.labels"
......
...@@ -8,10 +8,8 @@ Vue.use(VueResource); ...@@ -8,10 +8,8 @@ Vue.use(VueResource);
/** /**
* Commits View > Pipelines Tab > Pipelines Table. * Commits View > Pipelines Tab > Pipelines Table.
* Merge Request View > Pipelines Tab > Pipelines Table.
* *
* Renders Pipelines table in pipelines tab in the commits show view. * Renders Pipelines table in pipelines tab in the commits show view.
* Renders Pipelines table in pipelines tab in the merge request show view.
*/ */
$(() => { $(() => {
...@@ -20,13 +18,14 @@ $(() => { ...@@ -20,13 +18,14 @@ $(() => {
gl.commits.pipelines = gl.commits.pipelines || {}; gl.commits.pipelines = gl.commits.pipelines || {};
if (gl.commits.PipelinesTableBundle) { if (gl.commits.PipelinesTableBundle) {
document.querySelector('#commit-pipeline-table-view').removeChild(this.pipelinesTableBundle.$el);
gl.commits.PipelinesTableBundle.$destroy(true); gl.commits.PipelinesTableBundle.$destroy(true);
} }
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view'); const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable();
if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) { if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl); gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable().$mount();
document.querySelector('#commit-pipeline-table-view').appendChild(gl.commits.pipelines.PipelinesTableBundle.$el);
} }
}); });
import Vue from 'vue'; import Vue from 'vue';
import Visibility from 'visibilityjs';
import PipelinesTableComponent from '../../vue_shared/components/pipelines_table'; import PipelinesTableComponent from '../../vue_shared/components/pipelines_table';
import PipelinesService from '../../vue_pipelines_index/services/pipelines_service'; import PipelinesService from '../../vue_pipelines_index/services/pipelines_service';
import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store'; import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store';
...@@ -7,6 +8,7 @@ import EmptyState from '../../vue_pipelines_index/components/empty_state'; ...@@ -7,6 +8,7 @@ import EmptyState from '../../vue_pipelines_index/components/empty_state';
import ErrorState from '../../vue_pipelines_index/components/error_state'; import ErrorState from '../../vue_pipelines_index/components/error_state';
import '../../lib/utils/common_utils'; import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor'; import '../../vue_shared/vue_resource_interceptor';
import Poll from '../../lib/utils/poll';
/** /**
* *
...@@ -20,6 +22,7 @@ import '../../vue_shared/vue_resource_interceptor'; ...@@ -20,6 +22,7 @@ import '../../vue_shared/vue_resource_interceptor';
*/ */
export default Vue.component('pipelines-table', { export default Vue.component('pipelines-table', {
components: { components: {
'pipelines-table-component': PipelinesTableComponent, 'pipelines-table-component': PipelinesTableComponent,
'error-state': ErrorState, 'error-state': ErrorState,
...@@ -42,6 +45,7 @@ export default Vue.component('pipelines-table', { ...@@ -42,6 +45,7 @@ export default Vue.component('pipelines-table', {
state: store.state, state: store.state,
isLoading: false, isLoading: false,
hasError: false, hasError: false,
isMakingRequest: false,
}; };
}, },
...@@ -64,17 +68,41 @@ export default Vue.component('pipelines-table', { ...@@ -64,17 +68,41 @@ export default Vue.component('pipelines-table', {
* *
*/ */
beforeMount() { beforeMount() {
this.endpoint = this.$el.dataset.endpoint; const element = document.querySelector('#commit-pipeline-table-view');
this.helpPagePath = this.$el.dataset.helpPagePath;
this.endpoint = element.dataset.endpoint;
this.helpPagePath = element.dataset.helpPagePath;
this.service = new PipelinesService(this.endpoint); this.service = new PipelinesService(this.endpoint);
this.fetchPipelines(); this.poll = new Poll({
resource: this.service,
method: 'getPipelines',
successCallback: this.successCallback,
errorCallback: this.errorCallback,
notificationCallback: this.setIsMakingRequest,
});
if (!Visibility.hidden()) {
this.isLoading = true;
this.poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
eventHub.$on('refreshPipelines', this.fetchPipelines); eventHub.$on('refreshPipelines', this.fetchPipelines);
}, },
beforeUpdate() { beforeUpdate() {
if (this.state.pipelines.length && this.$children) { if (this.state.pipelines.length &&
this.$children &&
!this.isMakingRequest &&
!this.isLoading) {
this.store.startTimeAgoLoops.call(this, Vue); this.store.startTimeAgoLoops.call(this, Vue);
} }
}, },
...@@ -83,21 +111,35 @@ export default Vue.component('pipelines-table', { ...@@ -83,21 +111,35 @@ export default Vue.component('pipelines-table', {
eventHub.$off('refreshPipelines'); eventHub.$off('refreshPipelines');
}, },
destroyed() {
this.poll.stop();
},
methods: { methods: {
fetchPipelines() { fetchPipelines() {
this.isLoading = true; this.isLoading = true;
return this.service.getPipelines() return this.service.getPipelines()
.then(response => response.json()) .then(response => this.successCallback(response))
.then((json) => { .catch(() => this.errorCallback());
// depending of the endpoint the response can either bring a `pipelines` key or not. },
const pipelines = json.pipelines || json;
this.store.storePipelines(pipelines); successCallback(resp) {
this.isLoading = false; const response = resp.json();
})
.catch(() => { // depending of the endpoint the response can either bring a `pipelines` key or not.
this.hasError = true; const pipelines = response.pipelines || response;
this.isLoading = false; this.store.storePipelines(pipelines);
}); this.isLoading = false;
},
errorCallback() {
this.hasError = true;
this.isLoading = false;
},
setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest;
}, },
}, },
......
...@@ -38,9 +38,35 @@ showTooltip = function(target, title) { ...@@ -38,9 +38,35 @@ showTooltip = function(target, title) {
}; };
$(function() { $(function() {
var clipboard; const clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
clipboard.on('success', genericSuccess); 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 { ...@@ -13,10 +13,6 @@ class Diff {
$diffFile.each((index, file) => new gl.ImageFile(file)); $diffFile.each((index, file) => new gl.ImageFile(file));
if (this.diffViewType() === 'parallel') {
$('.content-wrapper .container-fluid').removeClass('container-limited');
}
if (!isBound) { if (!isBound) {
$(document) $(document)
.on('click', '.js-unfold', this.handleClickUnfold.bind(this)) .on('click', '.js-unfold', this.handleClickUnfold.bind(this))
......
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
/* global Search */ /* global Search */
/* global Admin */ /* global Admin */
/* global NamespaceSelects */ /* global NamespaceSelects */
/* global ShortcutsDashboardNavigation */
/* global Project */ /* global Project */
/* global ProjectAvatar */ /* global ProjectAvatar */
/* global CompareAutocomplete */ /* global CompareAutocomplete */
...@@ -378,7 +377,6 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -378,7 +377,6 @@ const ShortcutsBlob = require('./shortcuts_blob');
break; break;
case 'dashboard': case 'dashboard':
case 'root': case 'root':
shortcut_handler = new ShortcutsDashboardNavigation();
new UserCallout(); new UserCallout();
break; break;
case 'groups': case 'groups':
......
...@@ -31,12 +31,6 @@ export default Vue.component('environment-folder-view', { ...@@ -31,12 +31,6 @@ export default Vue.component('environment-folder-view', {
cssContainerClass: environmentsData.cssClass, cssContainerClass: environmentsData.cssClass,
canCreateDeployment: environmentsData.canCreateDeployment, canCreateDeployment: environmentsData.canCreateDeployment,
canReadEnvironment: environmentsData.canReadEnvironment, canReadEnvironment: environmentsData.canReadEnvironment,
// svgs
commitIconSvg: environmentsData.commitIconSvg,
playIconSvg: environmentsData.playIconSvg,
terminalIconSvg: environmentsData.terminalIconSvg,
// Pagination Properties, // Pagination Properties,
paginationInformation: {}, paginationInformation: {},
pageNumber: 1, pageNumber: 1,
...@@ -163,9 +157,6 @@ export default Vue.component('environment-folder-view', { ...@@ -163,9 +157,6 @@ export default Vue.component('environment-folder-view', {
:environments="state.environments" :environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed" :can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed" :can-read-environment="canReadEnvironmentParsed"
:play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg"
:commit-icon-svg="commitIconSvg"
:service="service"/> :service="service"/>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1" <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
......
...@@ -55,6 +55,7 @@ require('./filtered_search_dropdown'); ...@@ -55,6 +55,7 @@ require('./filtered_search_dropdown');
renderContent() { renderContent() {
const dropdownData = []; const dropdownData = [];
[].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => { [].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
const { icon, hint, tag, type } = dropdownMenu.dataset; const { icon, hint, tag, type } = dropdownMenu.dataset;
if (icon && hint && tag) { if (icon && hint && tag) {
......
...@@ -65,7 +65,6 @@ export default class Poll { ...@@ -65,7 +65,6 @@ export default class Poll {
this.makeRequest(); this.makeRequest();
}, pollInterval); }, pollInterval);
} }
this.options.successCallback(response); this.options.successCallback(response);
} }
...@@ -76,8 +75,14 @@ export default class Poll { ...@@ -76,8 +75,14 @@ export default class Poll {
notificationCallback(true); notificationCallback(true);
return resource[method](data) return resource[method](data)
.then(response => this.checkConditions(response)) .then((response) => {
.catch(error => errorCallback(error)); this.checkConditions(response);
notificationCallback(false);
})
.catch((error) => {
notificationCallback(false);
errorCallback(error);
});
} }
/** /**
......
...@@ -90,6 +90,7 @@ import './flash'; ...@@ -90,6 +90,7 @@ import './flash';
.on('click', this.clickTab); .on('click', this.clickTab);
} }
// Used in tests
unbindEvents() { unbindEvents() {
$(document) $(document)
.off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown) .off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
...@@ -99,9 +100,11 @@ import './flash'; ...@@ -99,9 +100,11 @@ import './flash';
.off('click', this.clickTab); .off('click', this.clickTab);
} }
destroy() { destroyPipelinesView() {
this.unbindEvents();
if (this.commitPipelinesTable) { if (this.commitPipelinesTable) {
document.querySelector('#commit-pipeline-table-view')
.removeChild(this.commitPipelinesTable.$el);
this.commitPipelinesTable.$destroy(); this.commitPipelinesTable.$destroy();
} }
} }
...@@ -128,6 +131,7 @@ import './flash'; ...@@ -128,6 +131,7 @@ import './flash';
this.loadCommits($target.attr('href')); this.loadCommits($target.attr('href'));
this.expandView(); this.expandView();
this.resetViewContainer(); this.resetViewContainer();
this.destroyPipelinesView();
} else if (this.isDiffAction(action)) { } else if (this.isDiffAction(action)) {
this.loadDiff($target.attr('href')); this.loadDiff($target.attr('href'));
if (Breakpoints.get().getBreakpointSize() !== 'lg') { if (Breakpoints.get().getBreakpointSize() !== 'lg') {
...@@ -136,12 +140,14 @@ import './flash'; ...@@ -136,12 +140,14 @@ import './flash';
if (this.diffViewType() === 'parallel') { if (this.diffViewType() === 'parallel') {
this.expandViewContainer(); this.expandViewContainer();
} }
this.destroyPipelinesView();
} else if (action === 'pipelines') { } else if (action === 'pipelines') {
this.resetViewContainer(); this.resetViewContainer();
this.loadPipelines(); this.mountPipelinesView();
} else { } else {
this.expandView(); this.expandView();
this.resetViewContainer(); this.resetViewContainer();
this.destroyPipelinesView();
} }
if (this.setUrl) { if (this.setUrl) {
this.setCurrentAction(action); this.setCurrentAction(action);
...@@ -227,16 +233,12 @@ import './flash'; ...@@ -227,16 +233,12 @@ import './flash';
}); });
} }
loadPipelines() { mountPipelinesView() {
if (this.pipelinesLoaded) { this.commitPipelinesTable = new CommitPipelinesTable().$mount();
return; // $mount(el) replaces the el with the new rendered component. We need it in order to mount
} // it everytime this tab is clicked - https://vuejs.org/v2/api/#vm-mount
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view'); document.querySelector('#commit-pipeline-table-view')
// Could already be mounted from the `pipelines_bundle` .appendChild(this.commitPipelinesTable.$el);
if (pipelineTableViewEl) {
this.commitPipelinesTable = new CommitPipelinesTable().$mount(pipelineTableViewEl);
}
this.pipelinesLoaded = true;
} }
loadDiff(source) { loadDiff(source) {
......
/* 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 */ /* 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 Mousetrap */
/* global findFileURL */ /* global findFileURL */
import findAndFollowLink from './shortcuts_dashboard_navigation';
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
...@@ -14,11 +15,33 @@ ...@@ -14,11 +15,33 @@
} }
Mousetrap.bind('?', this.onToggleHelp); Mousetrap.bind('?', this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch); Mousetrap.bind('s', Shortcuts.focusSearch);
Mousetrap.bind('f', (function(_this) { Mousetrap.bind('f', (e => this.focusFilter(e)));
return function(e) {
return _this.focusFilter(e); const $globalDropdownMenu = $('.global-dropdown-menu');
}; const $globalDropdownToggle = $('.global-dropdown-toggle');
})(this));
$('.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); Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], this.toggleMarkdownPreview);
if (typeof findFileURL !== "undefined" && findFileURL !== null) { if (typeof findFileURL !== "undefined" && findFileURL !== null) {
Mousetrap.bind('t', function() { 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 */ * Helper function that finds the href of the fiven selector and updates the location.
/* global Shortcuts */ *
* @param {String} selector
require('./shortcuts'); */
export default (selector) => {
(function() { const link = document.querySelector(selector).getAttribute('href');
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; if (link) {
window.location = link;
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);
/* 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 */ /* 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 Mousetrap */
/* global Shortcuts */ /* global Shortcuts */
import findAndFollowLink from './shortcuts_dashboard_navigation';
require('./shortcuts'); require('./shortcuts');
...@@ -13,59 +14,23 @@ require('./shortcuts'); ...@@ -13,59 +14,23 @@ require('./shortcuts');
function ShortcutsNavigation() { function ShortcutsNavigation() {
ShortcutsNavigation.__super__.constructor.call(this); ShortcutsNavigation.__super__.constructor.call(this);
Mousetrap.bind('g p', function() { Mousetrap.bind('g p', () => findAndFollowLink('.shortcuts-project'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project'); Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-project-activity'));
}); Mousetrap.bind('g f', () => findAndFollowLink('.shortcuts-tree'));
Mousetrap.bind('g e', function() { Mousetrap.bind('g c', () => findAndFollowLink('.shortcuts-commits'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'); Mousetrap.bind('g j', () => findAndFollowLink('.shortcuts-builds'));
}); Mousetrap.bind('g n', () => findAndFollowLink('.shortcuts-network'));
Mousetrap.bind('g f', function() { Mousetrap.bind('g d', () => findAndFollowLink('.shortcuts-repository-charts'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'); Mousetrap.bind('g i', () => findAndFollowLink('.shortcuts-issues'));
}); Mousetrap.bind('g b', () => findAndFollowLink('.shortcuts-issue-boards'));
Mousetrap.bind('g c', function() { Mousetrap.bind('g m', () => findAndFollowLink('.shortcuts-merge_requests'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'); Mousetrap.bind('g t', () => findAndFollowLink('.shortcuts-todos'));
}); Mousetrap.bind('g w', () => findAndFollowLink('.shortcuts-wiki'));
Mousetrap.bind('g b', function() { Mousetrap.bind('g s', () => findAndFollowLink('.shortcuts-snippets'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-builds'); Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue'));
});
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');
});
this.enabledHelp.push('.hidden-shortcut.project'); this.enabledHelp.push('.hidden-shortcut.project');
} }
ShortcutsNavigation.findAndFollowLink = function(selector) {
var link;
link = $(selector).attr('href');
if (link) {
return window.location = link;
}
};
return ShortcutsNavigation; return ShortcutsNavigation;
})(Shortcuts); })(Shortcuts);
}).call(window); }).call(window);
import Vue from 'vue'; import Vue from 'vue';
import Visibility from 'visibilityjs';
import PipelinesService from './services/pipelines_service'; import PipelinesService from './services/pipelines_service';
import eventHub from './event_hub'; import eventHub from './event_hub';
import PipelinesTableComponent from '../vue_shared/components/pipelines_table'; import PipelinesTableComponent from '../vue_shared/components/pipelines_table';
...@@ -7,6 +8,7 @@ import EmptyState from './components/empty_state'; ...@@ -7,6 +8,7 @@ import EmptyState from './components/empty_state';
import ErrorState from './components/error_state'; import ErrorState from './components/error_state';
import NavigationTabs from './components/navigation_tabs'; import NavigationTabs from './components/navigation_tabs';
import NavigationControls from './components/nav_controls'; import NavigationControls from './components/nav_controls';
import Poll from '../lib/utils/poll';
export default { export default {
props: { props: {
...@@ -47,6 +49,7 @@ export default { ...@@ -47,6 +49,7 @@ export default {
pagenum: 1, pagenum: 1,
isLoading: false, isLoading: false,
hasError: false, hasError: false,
isMakingRequest: false,
}; };
}, },
...@@ -120,18 +123,49 @@ export default { ...@@ -120,18 +123,49 @@ export default {
tagsPath: this.tagsPath, tagsPath: this.tagsPath,
}; };
}, },
pageParameter() {
return gl.utils.getParameterByName('page') || this.pagenum;
},
scopeParameter() {
return gl.utils.getParameterByName('scope') || this.apiScope;
},
}, },
created() { created() {
this.service = new PipelinesService(this.endpoint); this.service = new PipelinesService(this.endpoint);
this.fetchPipelines(); const poll = new Poll({
resource: this.service,
method: 'getPipelines',
data: { page: this.pageParameter, scope: this.scopeParameter },
successCallback: this.successCallback,
errorCallback: this.errorCallback,
notificationCallback: this.setIsMakingRequest,
});
if (!Visibility.hidden()) {
this.isLoading = true;
poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
poll.restart();
} else {
poll.stop();
}
});
eventHub.$on('refreshPipelines', this.fetchPipelines); eventHub.$on('refreshPipelines', this.fetchPipelines);
}, },
beforeUpdate() { beforeUpdate() {
if (this.state.pipelines.length && this.$children) { if (this.state.pipelines.length &&
this.$children &&
!this.isMakingRequest &&
!this.isLoading) {
this.store.startTimeAgoLoops.call(this, Vue); this.store.startTimeAgoLoops.call(this, Vue);
} }
}, },
...@@ -154,27 +188,35 @@ export default { ...@@ -154,27 +188,35 @@ export default {
}, },
fetchPipelines() { fetchPipelines() {
const pageNumber = gl.utils.getParameterByName('page') || this.pagenum; if (!this.isMakingRequest) {
const scope = gl.utils.getParameterByName('scope') || this.apiScope; this.isLoading = true;
this.isLoading = true; this.service.getPipelines({ scope: this.scopeParameter, page: this.pageParameter })
return this.service.getPipelines(scope, pageNumber) .then(response => this.successCallback(response))
.then(resp => ({ .catch(() => this.errorCallback());
headers: resp.headers, }
body: resp.json(), },
}))
.then((response) => { successCallback(resp) {
this.store.storeCount(response.body.count); const response = {
this.store.storePipelines(response.body.pipelines); headers: resp.headers,
this.store.storePagination(response.headers); body: resp.json(),
}) };
.then(() => {
this.isLoading = false; this.store.storeCount(response.body.count);
}) this.store.storePipelines(response.body.pipelines);
.catch(() => { this.store.storePagination(response.headers);
this.hasError = true;
this.isLoading = false; this.isLoading = false;
}); },
errorCallback() {
this.hasError = true;
this.isLoading = false;
},
setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest;
}, },
}, },
......
...@@ -26,7 +26,8 @@ export default class PipelinesService { ...@@ -26,7 +26,8 @@ export default class PipelinesService {
this.pipelines = Vue.resource(endpoint); this.pipelines = Vue.resource(endpoint);
} }
getPipelines(scope, page) { getPipelines(data = {}) {
const { scope, page } = data;
return this.pipelines.get({ scope, page }); return this.pipelines.get({ scope, page });
} }
......
...@@ -187,6 +187,15 @@ ...@@ -187,6 +187,15 @@
} }
} }
.shortcut-mappings {
display: none;
}
&.shortcuts .shortcut-mappings {
display: inline-block;
margin-right: 5px;
}
ul { ul {
margin: 0; margin: 0;
padding: 0; padding: 0;
......
...@@ -446,10 +446,8 @@ ...@@ -446,10 +446,8 @@
} }
} }
.filter-dropdown-item.droplab-item-active { .filter-dropdown-item.droplab-item-active .btn {
.btn { @extend %filter-dropdown-item-btn-hover;
@extend %filter-dropdown-item-btn-hover;
}
} }
.filter-dropdown-loading { .filter-dropdown-loading {
......
.timeline { .timeline {
@include basic-list; @include basic-list;
margin: 0; margin: 0;
padding: 0; padding: 0;
.timeline-entry { .timeline-entry {
padding: $gl-padding $gl-btn-padding 11px; padding: $gl-padding $gl-btn-padding 14px;
border-color: $white-normal; border-color: $white-normal;
color: $gl-text-color; color: $gl-text-color;
border-bottom: 1px solid $border-white-light; border-bottom: 1px solid $border-white-light;
.timeline-entry-inner {
position: relative;
}
&:target { &:target {
background: $line-target-blue; background: $line-target-blue;
} }
......
...@@ -197,7 +197,7 @@ ...@@ -197,7 +197,7 @@
.card { .card {
position: relative; position: relative;
padding: 10px $gl-padding; padding: 11px 10px 11px $gl-padding;
background: $white-light; background: $white-light;
border-radius: $border-radius-default; border-radius: $border-radius-default;
box-shadow: 0 1px 2px $issue-boards-card-shadow; box-shadow: 0 1px 2px $issue-boards-card-shadow;
...@@ -217,6 +217,8 @@ ...@@ -217,6 +217,8 @@
} }
.confidential-icon { .confidential-icon {
position: relative;
top: 1px;
margin-right: 5px; margin-right: 5px;
} }
} }
...@@ -224,34 +226,43 @@ ...@@ -224,34 +226,43 @@
.card-title { .card-title {
margin: 0; margin: 0;
font-size: 1em; font-size: 1em;
line-height: inherit;
a { a {
color: inherit; color: $gl-text-color;
word-wrap: break-word; word-wrap: break-word;
margin-right: 2px;
} }
} }
.card-footer { .card-header {
margin-top: 5px; display: flex;
line-height: 25px; min-height: 20px;
.label {
margin-right: 5px;
font-size: (14px / $issue-boards-font-size) * 1em;
}
.card-assignee { .card-assignee {
margin-left: auto;
margin-right: 5px; margin-right: 5px;
padding-left: 10px;
height: 20px;
} }
.avatar { .avatar {
margin-left: 0; margin: 0;
margin-right: 0; }
}
.card-footer {
margin: 0 0 5px;
.label {
margin-top: 5px;
margin-right: 6px;
} }
} }
.card-number { .card-number {
margin-right: 5px; font-size: 12px;
color: $gl-text-color-secondary;
} }
.issue-boards-search { .issue-boards-search {
......
...@@ -16,6 +16,15 @@ ul.notes { ...@@ -16,6 +16,15 @@ ul.notes {
.timeline-icon { .timeline-icon {
float: left; float: left;
svg {
width: 18px;
height: auto;
fill: $gray-darkest;
position: absolute;
left: 30px;
top: 15px;
}
} }
.timeline-content { .timeline-content {
...@@ -33,6 +42,103 @@ ul.notes { ...@@ -33,6 +42,103 @@ ul.notes {
white-space: nowrap; 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 { .system-note {
font-size: 14px; font-size: 14px;
padding: 0; padding: 0;
...@@ -68,6 +174,10 @@ ul.notes { ...@@ -68,6 +174,10 @@ ul.notes {
padding: 14px 10px; padding: 14px 10px;
} }
.note-header {
padding-bottom: 0;
}
.note-body { .note-body {
overflow: hidden; overflow: hidden;
...@@ -130,116 +240,6 @@ ul.notes { ...@@ -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;
}
}
}
} }
} }
......
...@@ -36,6 +36,8 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -36,6 +36,8 @@ class Projects::CommitController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: PipelineSerializer render json: PipelineSerializer
.new(project: @project, user: @current_user) .new(project: @project, user: @current_user)
.represent(@pipelines) .represent(@pipelines)
......
...@@ -233,6 +233,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -233,6 +233,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
format.json do format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: PipelineSerializer render json: PipelineSerializer
.new(project: @project, user: @current_user) .new(project: @project, user: @current_user)
.represent(@pipelines) .represent(@pipelines)
...@@ -246,6 +248,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -246,6 +248,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
format.json do format.json do
define_pipelines_vars define_pipelines_vars
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: { render json: {
pipelines: PipelineSerializer pipelines: PipelineSerializer
.new(project: @project, user: @current_user) .new(project: @project, user: @current_user)
......
...@@ -29,6 +29,8 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -29,6 +29,8 @@ class Projects::PipelinesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: { render json: {
pipelines: PipelineSerializer pipelines: PipelineSerializer
.new(project: @project, user: @current_user) .new(project: @project, user: @current_user)
...@@ -114,7 +116,7 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -114,7 +116,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end end
def pipeline def pipeline
@pipeline ||= project.pipelines.find_by!(id: params[:id]) @pipeline ||= project.pipelines.find_by!(id: params[:id]).present(current_user: current_user)
end end
def commit def commit
......
...@@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController ...@@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def update_params def update_params
params.require(:project).permit( params.require(:project).permit(
:runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, :runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds :public_builds, :auto_cancel_pending_pipelines
) )
end end
end end
...@@ -102,7 +102,7 @@ module BlobHelper ...@@ -102,7 +102,7 @@ module BlobHelper
if Gitlab::MarkupHelper.previewable?(filename) if Gitlab::MarkupHelper.previewable?(filename)
'Preview' 'Preview'
else else
'Preview Changes' 'Preview changes'
end end
end end
...@@ -210,13 +210,13 @@ module BlobHelper ...@@ -210,13 +210,13 @@ module BlobHelper
end end
def copy_file_path_button(file_path) 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 end
def copy_blob_content_button(blob) def copy_blob_content_button(blob)
return if markup?(blob.name) 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 end
def open_raw_file_button(path) def open_raw_file_button(path)
......
module ButtonHelper module ButtonHelper
# Output a "Copy to Clipboard" button # 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: # Examples:
# #
# # Define the clipboard's text # # Define the clipboard's text
# clipboard_button(clipboard_text: "Foo") # clipboard_button(text: "Foo")
# # => "<button class='...' data-clipboard-text='Foo'>...</button>" # # => "<button class='...' data-clipboard-text='Foo'>...</button>"
# #
# # Define the target element # # Define the target element
# clipboard_button(clipboard_target: "div#foo") # clipboard_button(target: "div#foo")
# # => "<button class='...' data-clipboard-target='div#foo'>...</button>" # # => "<button class='...' data-clipboard-target='div#foo'>...</button>"
# #
# See http://clipboardjs.com/#usage # See http://clipboardjs.com/#usage
def clipboard_button(data = {}) def clipboard_button(data = {})
css_class = data[:class] || 'btn-clipboard btn-transparent' css_class = data[:class] || 'btn-clipboard btn-transparent'
title = data[:title] || 'Copy to clipboard' 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) data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button, content_tag :button,
icon('clipboard', 'aria-hidden': 'true'), icon('clipboard', 'aria-hidden': 'true'),
class: "btn #{css_class}", 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 ...@@ -103,18 +103,13 @@ module Ci
end end
def playable? def playable?
project.builds_enabled? && has_commands? && action? && manual?
action? && manual?
end end
def action? def action?
self.when == 'manual' self.when == 'manual'
end end
def has_commands?
commands.present?
end
def play(current_user) def play(current_user)
# Try to queue a current build # Try to queue a current build
if self.enqueue if self.enqueue
...@@ -131,8 +126,7 @@ module Ci ...@@ -131,8 +126,7 @@ module Ci
end end
def retryable? def retryable?
project.builds_enabled? && has_commands? && success? || failed? || canceled?
(success? || failed? || canceled?)
end end
def retried? def retried?
......
...@@ -4,14 +4,25 @@ module Ci ...@@ -4,14 +4,25 @@ module Ci
include HasStatus include HasStatus
include Importable include Importable
include AfterCommitQueue include AfterCommitQueue
include Presentable
belongs_to :project belongs_to :project
belongs_to :user belongs_to :user
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id'
has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id'
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
has_many :builds, foreign_key: :commit_id has_many :builds, foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, 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 delegate :id, to: :project, prefix: true
validates :sha, presence: { unless: :importing? } validates :sha, presence: { unless: :importing? }
...@@ -65,6 +76,10 @@ module Ci ...@@ -65,6 +76,10 @@ module Ci
pipeline.update_duration pipeline.update_duration
end end
before_transition canceled: any - [:canceled] do |pipeline|
pipeline.auto_canceled_by = nil
end
after_transition [:created, :pending] => :running do |pipeline| after_transition [:created, :pending] => :running do |pipeline|
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(id) } pipeline.run_after_commit { PipelineMetricsWorker.perform_async(id) }
end end
...@@ -82,6 +97,8 @@ module Ci ...@@ -82,6 +97,8 @@ module Ci
pipeline.run_after_commit do pipeline.run_after_commit do
PipelineHooksWorker.perform_async(id) PipelineHooksWorker.perform_async(id)
Ci::ExpirePipelineCacheService.new(project, nil)
.execute(pipeline)
end end
end end
...@@ -160,10 +177,6 @@ module Ci ...@@ -160,10 +177,6 @@ module Ci
end end
end end
def artifacts
builds.latest.with_artifacts_not_expired.includes(project: [:namespace])
end
def valid_commit_sha def valid_commit_sha
if self.sha == Gitlab::Git::BLANK_SHA if self.sha == Gitlab::Git::BLANK_SHA
self.errors.add(:sha, " cant be 00000000 (branch removal)") self.errors.add(:sha, " cant be 00000000 (branch removal)")
...@@ -200,27 +213,37 @@ module Ci ...@@ -200,27 +213,37 @@ module Ci
!tag? !tag?
end end
def manual_actions
builds.latest.manual_actions.includes(project: [:namespace])
end
def stuck? def stuck?
builds.pending.includes(:project).any?(&:stuck?) pending_builds.any?(&:stuck?)
end end
def retryable? def retryable?
builds.latest.failed_or_canceled.any?(&:retryable?) retryable_builds.any?
end end
def cancelable? def cancelable?
statuses.cancelable.any? cancelable_statuses.any?
end
def auto_canceled?
canceled? && auto_canceled_by_id?
end end
def cancel_running def cancel_running
Gitlab::OptimisticLocking.retry_lock( Gitlab::OptimisticLocking.retry_lock(cancelable_statuses) do |cancelable|
statuses.cancelable) do |cancelable| cancelable.find_each do |job|
cancelable.find_each(&:cancel) yield(job) if block_given?
job.cancel
end end
end
end
def auto_cancel_running(pipeline)
update(auto_canceled_by: pipeline)
cancel_running do |job|
job.auto_canceled_by = pipeline
end
end end
def retry_failed(current_user) def retry_failed(current_user)
......
...@@ -7,6 +7,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -7,6 +7,7 @@ class CommitStatus < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
belongs_to :user belongs_to :user
delegate :commit, to: :pipeline delegate :commit, to: :pipeline
...@@ -137,6 +138,10 @@ class CommitStatus < ActiveRecord::Base ...@@ -137,6 +138,10 @@ class CommitStatus < ActiveRecord::Base
false false
end end
def auto_canceled?
canceled? && auto_canceled_by_id?
end
# Added in 9.0 to keep backward compatibility for projects exported in 8.17 # Added in 9.0 to keep backward compatibility for projects exported in 8.17
# and prior. # and prior.
def gl_project_id def gl_project_id
......
...@@ -76,6 +76,7 @@ module HasStatus ...@@ -76,6 +76,7 @@ module HasStatus
scope :canceled, -> { where(status: 'canceled') } scope :canceled, -> { where(status: 'canceled') }
scope :skipped, -> { where(status: 'skipped') } scope :skipped, -> { where(status: 'skipped') }
scope :manual, -> { where(status: 'manual') } scope :manual, -> { where(status: 'manual') }
scope :created_or_pending, -> { where(status: [:created, :pending]) }
scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :running_or_pending, -> { where(status: [:running, :pending]) }
scope :finished, -> { where(status: [:success, :failed, :canceled]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) }
scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) } scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) }
......
...@@ -172,6 +172,8 @@ class Project < ActiveRecord::Base ...@@ -172,6 +172,8 @@ class Project < ActiveRecord::Base
has_many :environments, dependent: :destroy has_many :environments, dependent: :destroy
has_many :deployments, 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 :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature accepts_nested_attributes_for :project_feature
...@@ -260,6 +262,8 @@ class Project < ActiveRecord::Base ...@@ -260,6 +262,8 @@ class Project < ActiveRecord::Base
scope :with_builds_enabled, -> { with_feature_enabled(:builds) } scope :with_builds_enabled, -> { with_feature_enabled(:builds) }
scope :with_issues_enabled, -> { with_feature_enabled(:issues) } scope :with_issues_enabled, -> { with_feature_enabled(:issues) }
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
# project features may be "disabled", "internal" or "enabled". If "internal", # project features may be "disabled", "internal" or "enabled". If "internal",
# they are only available to team members. This scope returns projects where # they are only available to team members. This scope returns projects where
# the feature is either enabled, or internal with permission for the user. # the feature is either enabled, or internal with permission for the user.
...@@ -1085,15 +1089,15 @@ class Project < ActiveRecord::Base ...@@ -1085,15 +1089,15 @@ class Project < ActiveRecord::Base
end end
def shared_runners def shared_runners
shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none @shared_runners ||= shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none
end end
def any_runners?(&block) def active_shared_runners
if runners.active.any?(&block) @active_shared_runners ||= shared_runners.active
return true end
end
shared_runners.active.any?(&block) def any_runners?(&block)
active_runners.any?(&block) || active_shared_runners.any?(&block)
end end
def valid_runners_token?(token) def valid_runners_token?(token)
......
...@@ -11,5 +11,11 @@ module Ci ...@@ -11,5 +11,11 @@ module Ci
def erased_by_name def erased_by_name
erased_by.name if erased_by_user? erased_by.name if erased_by_user?
end end
def status_title
if auto_canceled?
"Job is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}"
end
end
end end
end end
module Ci
class PipelinePresenter < Gitlab::View::Presenter::Delegated
presents :pipeline
def status_title
if auto_canceled?
"Pipeline is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}"
end
end
end
end
...@@ -69,13 +69,13 @@ class PipelineEntity < Grape::Entity ...@@ -69,13 +69,13 @@ class PipelineEntity < Grape::Entity
alias_method :pipeline, :object alias_method :pipeline, :object
def can_retry? def can_retry?
pipeline.retryable? && can?(request.user, :update_pipeline, pipeline) &&
can?(request.user, :update_pipeline, pipeline) pipeline.retryable?
end end
def can_cancel? def can_cancel?
pipeline.cancelable? && can?(request.user, :update_pipeline, pipeline) &&
can?(request.user, :update_pipeline, pipeline) pipeline.cancelable?
end end
def detailed_status def detailed_status
......
...@@ -13,7 +13,15 @@ class PipelineSerializer < BaseSerializer ...@@ -13,7 +13,15 @@ class PipelineSerializer < BaseSerializer
def represent(resource, opts = {}) def represent(resource, opts = {})
if resource.is_a?(ActiveRecord::Relation) 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 end
if paginated? if paginated?
......
...@@ -53,6 +53,8 @@ module Ci ...@@ -53,6 +53,8 @@ module Ci
.execute(pipeline) .execute(pipeline)
end end
cancel_pending_pipelines if project.auto_cancel_pending_pipelines?
pipeline.tap(&:process!) pipeline.tap(&:process!)
end end
...@@ -63,6 +65,22 @@ module Ci ...@@ -63,6 +65,22 @@ module Ci
pipeline.git_commit_message =~ /\[(ci[ _-]skip|skip[ _-]ci)\]/i pipeline.git_commit_message =~ /\[(ci[ _-]skip|skip[ _-]ci)\]/i
end end
def cancel_pending_pipelines
Gitlab::OptimisticLocking.retry_lock(auto_cancelable_pipelines) do |cancelables|
cancelables.find_each do |cancelable|
cancelable.auto_cancel_running(pipeline)
end
end
end
def auto_cancelable_pipelines
project.pipelines
.where(ref: pipeline.ref)
.where.not(id: pipeline.id)
.where.not(sha: project.repository.sha_from_ref(pipeline.ref))
.created_or_pending
end
def commit def commit
@commit ||= project.commit(origin_sha || origin_ref) @commit ||= project.commit(origin_sha || origin_ref)
end end
......
module Ci
class ExpirePipelineCacheService < BaseService
attr_reader :pipeline
def execute(pipeline)
@pipeline = pipeline
store = Gitlab::EtagCaching::Store.new
store.touch(project_pipelines_path)
store.touch(commit_pipelines_path) if pipeline.commit
store.touch(new_merge_request_pipelines_path)
merge_requests_pipelines_paths.each { |path| store.touch(path) }
end
private
def project_pipelines_path
Gitlab::Routing.url_helpers.namespace_project_pipelines_path(
project.namespace,
project,
format: :json)
end
def commit_pipelines_path
Gitlab::Routing.url_helpers.pipelines_namespace_project_commit_path(
project.namespace,
project,
pipeline.commit.id,
format: :json)
end
def new_merge_request_pipelines_path
Gitlab::Routing.url_helpers.new_namespace_project_merge_request_path(
project.namespace,
project,
format: :json)
end
def merge_requests_pipelines_paths
pipeline.merge_requests.collect do |merge_request|
Gitlab::Routing.url_helpers.pipelines_namespace_project_merge_request_path(
project.namespace,
project,
merge_request,
format: :json)
end
end
end
end
...@@ -7,9 +7,7 @@ module Ci ...@@ -7,9 +7,7 @@ module Ci
raise Gitlab::Access::AccessDeniedError raise Gitlab::Access::AccessDeniedError
end end
pipeline.builds.latest.failed_or_canceled.find_each do |build| pipeline.retryable_builds.find_each do |build|
next unless build.retryable?
Ci::RetryBuildService.new(project, current_user) Ci::RetryBuildService.new(project, current_user)
.reprocess(build) .reprocess(build)
end end
......
...@@ -30,5 +30,5 @@ ...@@ -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" = 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 - else
.btn.btn-sm.disabled.btn-block .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" = 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 @@ ...@@ -148,7 +148,7 @@
Sign-in enabled Sign-in enabled
- if omniauth_enabled? && button_based_providers.any? - if omniauth_enabled? && button_based_providers.any?
.form-group .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 .col-sm-10
.btn-group{ data: { toggle: 'buttons' } } .btn-group{ data: { toggle: 'buttons' } }
- oauth_providers_checkboxes.each do |source| - oauth_providers_checkboxes.each do |source|
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
%p.light %p.light
System OAuth applications don't belong to any user and can only be managed by admins System OAuth applications don't belong to any user and can only be managed by admins
%hr %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 %table.table.table-striped
%thead %thead
%tr %tr
......
...@@ -125,7 +125,7 @@ ...@@ -125,7 +125,7 @@
= link_to admin_projects_path do = link_to admin_projects_path do
%h1= number_with_delimiter(Project.cached_count) %h1= number_with_delimiter(Project.cached_count)
%hr %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 .col-sm-4
.light-well.well-centered .light-well.well-centered
%h4 Users %h4 Users
...@@ -133,7 +133,7 @@ ...@@ -133,7 +133,7 @@
= link_to admin_users_path do = link_to admin_users_path do
%h1= number_with_delimiter(User.count) %h1= number_with_delimiter(User.count)
%hr %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 .col-sm-4
.light-well.well-centered .light-well.well-centered
%h4 Groups %h4 Groups
...@@ -141,7 +141,7 @@ ...@@ -141,7 +141,7 @@
= link_to admin_groups_path do = link_to admin_groups_path do
%h1= number_with_delimiter(Group.count) %h1= number_with_delimiter(Group.count)
%hr %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 .row.prepend-top-10
.col-md-4 .col-md-4
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
%h3.page-title.deploy-keys-title %h3.page-title.deploy-keys-title
Public deploy keys (#{@deploy_keys.count}) Public deploy keys (#{@deploy_keys.count})
.pull-right .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? - if @deploy_keys.any?
.table-holder.deploy-keys-list .table-holder.deploy-keys-list
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
= link_to admin_groups_path(sort: sort_value_largest_group, name: project_name) do = link_to admin_groups_path(sort: sort_value_largest_group, name: project_name) do
= sort_title_largest_group = sort_title_largest_group
= link_to new_admin_group_path, class: "btn btn-new" do = link_to new_admin_group_path, class: "btn btn-new" do
New Group New group
%ul.content-list %ul.content-list
= render @groups = render @groups
......
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
group members group members
%span.badge= @group.members.size %span.badge= @group.members.size
.pull-right .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 %ul.well-list.group-users-list.content-list
= render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false } = render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
.panel-footer .panel-footer
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
= f.check_box :enable_ssl_verification = f.check_box :enable_ssl_verification
%strong Enable SSL verification %strong Enable SSL verification
.form-actions .form-actions
= f.submit "Add System Hook", class: "btn btn-create" = f.submit "Add system hook", class: "btn btn-create"
%hr %hr
- if @hooks.any? - if @hooks.any?
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
- @hooks.each do |hook| - @hooks.each do |hook|
%li %li
.controls .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" = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
.monospace= hook.url .monospace= hook.url
%div %div
......
- page_title "Identities", @user.name, "Users" - page_title "Identities", @user.name, "Users"
= render 'admin/users/head' = 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? - if @identities.present?
.table-holder .table-holder
%table.table %table.table
......
...@@ -159,7 +159,7 @@ ...@@ -159,7 +159,7 @@
%span.badge= @group_members.size %span.badge= @group_members.size
.pull-right .pull-right
= link_to admin_group_path(@group), class: 'btn btn-xs' do = 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 %ul.well-list.content-list
= render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false } = render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false }
.panel-footer .panel-footer
...@@ -173,7 +173,7 @@ ...@@ -173,7 +173,7 @@
project members project members
%span.badge= @project.users.size %span.badge= @project.users.size
.pull-right .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 %ul.well-list.project_members.content-list
= render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false } = render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
.panel-footer .panel-footer
......
...@@ -35,5 +35,5 @@ ...@@ -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" = 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 - else
.btn.btn-xs.disabled .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" = link_to 'Remove log', [:admin, spam_log], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr"
...@@ -37,6 +37,6 @@ ...@@ -37,6 +37,6 @@
- if user.can_be_removed? && can?(current_user, :destroy_user, @user) - if user.can_be_removed? && can?(current_user, :destroy_user, @user)
%li.divider %li.divider
%li %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', class: 'btn btn-remove btn-block',
method: :delete method: :delete
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
= sort_title_recently_updated = sort_title_recently_updated
= link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do = link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do
= sort_title_oldest_updated = 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 .nav-block
%ul.nav-links.wide.scrolling-tabs.white.scrolling-tabs %ul.nav-links.wide.scrolling-tabs.white.scrolling-tabs
......
- status = local_assigns.fetch(:status) - status = local_assigns.fetch(:status)
- link = local_assigns.fetch(:link, true) - link = local_assigns.fetch(:link, true)
- css_classes = "ci-status ci-#{status.group}" - title = local_assigns.fetch(:title, nil)
- css_classes = "ci-status ci-#{status.group} #{'has-tooltip' if title.present?}"
- if link && status.has_details? - if link && status.has_details?
= link_to status.details_path, class: css_classes do = link_to status.details_path, class: css_classes, title: title do
= custom_icon(status.icon) = custom_icon(status.icon)
= status.text = status.text
- else - else
%span{ class: css_classes } %span{ class: css_classes, title: title }
= custom_icon(status.icon) = custom_icon(status.icon)
= status.text = status.text
...@@ -11,4 +11,4 @@ ...@@ -11,4 +11,4 @@
= render 'shared/groups/dropdown' = render 'shared/groups/dropdown'
- if current_user.can_create_group? - if current_user.can_create_group?
= link_to new_group_path, class: "btn btn-new" do = link_to new_group_path, class: "btn btn-new" do
New Group New group
...@@ -19,4 +19,4 @@ ...@@ -19,4 +19,4 @@
= render 'shared/projects/dropdown' = render 'shared/projects/dropdown'
- if current_user.can_create_project? - if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-new' do = link_to new_project_path, class: 'btn btn-new' do
New Project New project
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
.nav-controls .nav-controls
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do = link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do
= icon('rss') = 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/issuable/filter', type: :issues
= render 'shared/issues' = render 'shared/issues'
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.top-area .top-area
= render 'shared/issuable/nav', type: :merge_requests = render 'shared/issuable/nav', type: :merge_requests
.nav-controls .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/issuable/filter', type: :merge_requests
= render 'shared/merge_requests' = render 'shared/merge_requests'
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
= render 'shared/milestones_filter', counts: @milestone_states = render 'shared/milestones_filter', counts: @milestone_states
.nav-controls .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 .milestones
%ul.content-list %ul.content-list
......
...@@ -10,5 +10,5 @@ ...@@ -10,5 +10,5 @@
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
.pull-right .pull-right
= link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do = link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do
Create Merge Request Create merge request
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
= custom_icon("icon_status_closed") = custom_icon("icon_status_closed")
- else - else
.profile-icon.fork-icon .profile-icon.fork-icon
= custom_icon("code_fork") = custom_icon("icon_code_fork")
.event-title .event-title
%span{ class: event.action_name } %span{ class: event.action_name }
......
.profile-icon .profile-icon
= custom_icon("comment_o") = custom_icon("icon_comment_o")
.event-title .event-title
= event.action_name = event.action_name
......
...@@ -51,4 +51,4 @@ ...@@ -51,4 +51,4 @@
%strong Removed group can not be restored! %strong Removed group can not be restored!
.form-actions .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 @@ ...@@ -11,7 +11,7 @@
= icon('rss') = icon('rss')
%span.icon-label %span.icon-label
Subscribe 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 = render 'shared/issuable/filter', type: :issues
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.nav-controls .nav-controls
- if can?(current_user, :admin_milestones, @group) - if can?(current_user, :admin_milestones, @group)
= link_to new_group_milestone_path(@group), class: "btn btn-new" do = link_to new_group_milestone_path(@group), class: "btn btn-new" do
New Milestone New milestone
.row-content-block .row-content-block
Only milestones from Only milestones from
......
...@@ -39,5 +39,5 @@ ...@@ -39,5 +39,5 @@
= render "shared/milestones/form_dates", f: f = render "shared/milestones/form_dates", f: f
.form-actions .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" = link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel"
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
- if can? current_user, :admin_group, @group - if can? current_user, :admin_group, @group
.controls .controls
= link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do = link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do
New Project New project
%ul.well-list %ul.well-list
- @projects.each do |project| - @projects.each do |project|
%li %li
......
...@@ -15,6 +15,10 @@ ...@@ -15,6 +15,10 @@
%tr %tr
%th %th
%th Global Shortcuts %th Global Shortcuts
%tr
%td.shortcut
.key n
%td Main Navigation
%tr %tr
%td.shortcut %td.shortcut
.key s .key s
...@@ -39,24 +43,46 @@ ...@@ -39,24 +43,46 @@
.key .key
%i.fa.fa-arrow-up %i.fa.fa-arrow-up
%td Edit last comment (when focused on an empty textarea) %td Edit last comment (when focused on an empty textarea)
%tbody
%tr %tr
%th %td.shortcut
%th Project Files browsing .key shift t
%td
Go to todos
%tr %tr
%td.shortcut %td.shortcut
.key .key shift a
%i.fa.fa-arrow-up %td
%td Move selection up Go to the activity feed
%tr %tr
%td.shortcut %td.shortcut
.key .key shift p
%i.fa.fa-arrow-down %td
%td Move selection down Go to projects
%tr %tr
%td.shortcut %td.shortcut
.key enter .key shift i
%td Open Selection %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 %tbody
%tr %tr
%th %th
...@@ -79,51 +105,8 @@ ...@@ -79,51 +105,8 @@
%td.shortcut %td.shortcut
.key esc .key esc
%td Go back %td Go back
%tbody
%tr
%th
%th Project File
%tr
%td.shortcut
.key y
%td Go to file permalink
.col-lg-4 .col-lg-4
%table.shortcut-mappings %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 %tbody
%tr %tr
%th %th
...@@ -155,7 +138,7 @@ ...@@ -155,7 +138,7 @@
%tr %tr
%td.shortcut %td.shortcut
.key g .key g
.key b .key j
%td %td
Go to jobs Go to jobs
%tr %tr
...@@ -167,7 +150,7 @@ ...@@ -167,7 +150,7 @@
%tr %tr
%td.shortcut %td.shortcut
.key g .key g
.key g .key d
%td %td
Go to repository charts Go to repository charts
%tr %tr
...@@ -179,7 +162,7 @@ ...@@ -179,7 +162,7 @@
%tr %tr
%td.shortcut %td.shortcut
.key g .key g
.key l .key b
%td %td
Go to issue boards Go to issue boards
%tr %tr
...@@ -194,6 +177,12 @@ ...@@ -194,6 +177,12 @@
.key s .key s
%td %td
Go to snippets Go to snippets
%tr
%td.shortcut
.key g
.key w
%td
Go to wiki
%tr %tr
%td.shortcut %td.shortcut
.key t .key t
...@@ -202,6 +191,33 @@ ...@@ -202,6 +191,33 @@
%td.shortcut %td.shortcut
.key i .key i
%td New issue %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 .col-lg-4
%table.shortcut-mappings %table.shortcut-mappings
%tbody.hidden-shortcut.network{ style: 'display:none' } %tbody.hidden-shortcut.network{ style: 'display:none' }
......
...@@ -225,7 +225,7 @@ ...@@ -225,7 +225,7 @@
%ul.dropdown-menu %ul.dropdown-menu
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
.dropdown.inline.pull-right .dropdown.inline.pull-right
%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
Dropdown Dropdown
...@@ -233,7 +233,7 @@ ...@@ -233,7 +233,7 @@
%ul.dropdown-menu.dropdown-menu-align-right %ul.dropdown-menu.dropdown-menu-align-right
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
.example .example
%div %div
.dropdown.inline .dropdown.inline
...@@ -243,7 +243,7 @@ ...@@ -243,7 +243,7 @@
%ul.dropdown-menu.dropdown-menu-selectable %ul.dropdown-menu.dropdown-menu-selectable
%li %li
%a.is-active{ href: "#" } %a.is-active{ href: "#" }
Dropdown Option Dropdown option
.example .example
%div %div
.dropdown.inline .dropdown.inline
...@@ -262,26 +262,26 @@ ...@@ -262,26 +262,26 @@
%ul %ul
%li %li
%a.is-active{ href: "#" } %a.is-active{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li.divider %li.divider
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
.dropdown-footer .dropdown-footer
%strong Tip: %strong Tip:
If an author is not a member of this project, you can still filter by his name while using the search field. 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 @@ ...@@ -301,26 +301,26 @@
%ul %ul
%li %li
%a.is-active{ href: "#" } %a.is-active{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li.divider %li.divider
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
%li %li
%a{ href: "#" } %a{ href: "#" }
Dropdown Option Dropdown option
.dropdown-footer .dropdown-footer
%strong Tip: %strong Tip:
If an author is not a member of this project, you can still filter by his name while using the search field. 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 @@ ...@@ -9,7 +9,7 @@
To import a GitHub project, you first need to authorize GitLab to access To import a GitHub project, you first need to authorize GitLab to access
the list of your GitHub repositories: 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 %hr
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
= form_tag personal_access_token_import_github_path, method: :post, class: 'form-inline' do = form_tag personal_access_token_import_github_path, method: :post, class: 'form-inline' do
.form-group .form-group
= text_field_tag :personal_access_token, '', class: 'form-control', placeholder: "Personal Access Token", size: 40 = 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? - unless github_import_configured?
%hr %hr
......
...@@ -29,11 +29,11 @@ ...@@ -29,11 +29,11 @@
- if current_user - if current_user
- if session[:impersonator_id] - if session[:impersonator_id]
%li.impersonation %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') = icon('user-secret fw')
- if current_user.is_admin? - if current_user.is_admin?
%li %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') = icon('wrench fw')
- if current_user.can_create_project? - if current_user.can_create_project?
%li %li
......
%ul %ul
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do = 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 = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
P
%span %span
Projects Projects
= nav_link(path: 'dashboard#activity') do = nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: '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 %span
Activity Activity
- if koding_enabled? - if koding_enabled?
...@@ -13,25 +21,45 @@ ...@@ -13,25 +21,45 @@
%span %span
Koding Koding
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = 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 %span
Groups Groups
= nav_link(controller: 'dashboard/milestones') do = 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 %span
Milestones Milestones
= nav_link(path: 'dashboard#issues') do = nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-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 %span
Issues Issues
.badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened)) .badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened))
= nav_link(path: 'dashboard#merge_requests') do = nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-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 %span
Merge Requests Merge Requests
.badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened)) .badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened))
= nav_link(controller: 'dashboard/snippets') do = 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 %span
Snippets Snippets
%li.divider %li.divider
......
...@@ -49,14 +49,14 @@ ...@@ -49,14 +49,14 @@
%p %p
Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'} Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'}
- if current_user.two_factor_enabled? - 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, = link_to 'Disable', profile_two_factor_auth_path,
method: :delete, method: :delete,
data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." }, data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." },
class: 'btn btn-danger' class: 'btn btn-danger'
- else - else
.append-bottom-10 .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 %hr
- if button_based_providers.any? - if button_based_providers.any?
......
...@@ -33,17 +33,17 @@ ...@@ -33,17 +33,17 @@
%li %li
= @primary = @primary
%span.pull-right %span.pull-right
%span.label.label-success Primary Email %span.label.label-success Primary email
- if @primary === current_user.public_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 - if @primary === current_user.notification_email
%span.label.label-info Notification Email %span.label.label-info Notification email
- @emails.each do |email| - @emails.each do |email|
%li %li
= email.email = email.email
%span.pull-right %span.pull-right
- if email.email === current_user.public_email - 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 - 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' = 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 @@ ...@@ -19,7 +19,7 @@
Your New Personal Access Token Your New Personal Access Token
.form-group .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" = 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. %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 %hr
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
= label_tag :pin_code, nil, class: "label-light" = label_tag :pin_code, nil, class: "label-light"
= text_field_tag :pin_code, nil, class: "form-control", required: true = text_field_tag :pin_code, nil, class: "form-control", required: true
.prepend-top-default .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 %hr
......
.form-actions .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, = link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message} 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 = link_to namespace_project_find_file_path(@project.namespace, @project, @ref), class: 'btn btn-grouped shortcuts-find-file', rel: 'nofollow' do
= icon('search') = icon('search')
%span Find File %span Find file
...@@ -10,9 +10,9 @@ ...@@ -10,9 +10,9 @@
- if @project && event.project != @project - if @project && event.project != @project
%span at %span at
%strong= link_to_project event.project %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)} #{time_ago_with_tooltip(event.created_at)}
.pull-right .pull-right
= link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do = link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do
Create Merge Request Create merge request
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
-# only show normal/blame view links for text files -# only show normal/blame view links for text files
- if blob_text_viewable?(blob) - if blob_text_viewable?(blob)
- if current_page? namespace_project_blame_path(@project.namespace, @project, @id) - 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' class: 'btn btn-sm'
- else - else
= link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id), = link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id),
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
.template-type-selector.js-template-type-selector-wrap.hidden .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" } ) = 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 .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 .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 } } ) = 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 .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
.controls.hidden-xs< .controls.hidden-xs<
- if merge_project && create_mr_button?(@repository.root_ref, branch.name) - 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 = 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 - 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 = 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
......
- pipeline = @build.pipeline
.content-block.build-header.top-area .content-block.build-header.top-area
.header-content .header-content
= render 'ci/status/badge', status: @build.detailed_status(current_user), link: false = render 'ci/status/badge', status: @build.detailed_status(current_user), link: false, title: @build.status_title
Job Job
%strong.js-build-id ##{@build.id} %strong.js-build-id ##{@build.id}
in pipeline in pipeline
= link_to pipeline_path(@build.pipeline) do = link_to pipeline_path(pipeline) do
%strong ##{@build.pipeline.id} %strong ##{pipeline.id}
for commit for commit
= link_to namespace_project_commit_path(@project.namespace, @project, @build.pipeline.sha) do = link_to namespace_project_commit_path(@project.namespace, @project, pipeline.sha) do
%strong= @build.pipeline.short_sha %strong= pipeline.short_sha
from from
= link_to namespace_project_commits_path(@project.namespace, @project, @build.ref) do = link_to namespace_project_commits_path(@project.namespace, @project, @build.ref) do
%code %code
......
...@@ -17,7 +17,7 @@ ...@@ -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 '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 = link_to ci_lint_path, class: 'btn btn-default' do
%span CI Lint %span CI lint
.content-list.builds-content-list .content-list.builds-content-list
= render "table", builds: @builds, project: @project = render "table", builds: @builds, project: @project
- job = build.present(current_user: current_user)
- pipeline = job.pipeline
- admin = local_assigns.fetch(:admin, false) - admin = local_assigns.fetch(:admin, false)
- ref = local_assigns.fetch(:ref, nil) - ref = local_assigns.fetch(:ref, nil)
- commit_sha = local_assigns.fetch(:commit_sha, nil) - commit_sha = local_assigns.fetch(:commit_sha, nil)
...@@ -8,101 +10,101 @@ ...@@ -8,101 +10,101 @@
%tr.build.commit{ class: ('retried' if retried) } %tr.build.commit{ class: ('retried' if retried) }
%td.status %td.status
= render "ci/status/badge", status: build.detailed_status(current_user) = render "ci/status/badge", status: job.detailed_status(current_user), title: job.status_title
%td.branch-commit %td.branch-commit
- if can?(current_user, :read_build, build) - if can?(current_user, :read_build, job)
= link_to namespace_project_build_url(build.project.namespace, build.project, build) do = link_to namespace_project_build_url(job.project.namespace, job.project, job) do
%span.build-link ##{build.id} %span.build-link ##{job.id}
- else - else
%span.build-link ##{build.id} %span.build-link ##{job.id}
- if ref - if ref
- if build.ref - if job.ref
.icon-container .icon-container
= build.tag? ? icon('tag') : icon('code-fork') = job.tag? ? icon('tag') : icon('code-fork')
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" = link_to job.ref, namespace_project_commits_path(job.project.namespace, job.project, job.ref), class: "monospace branch-name"
- else - else
.light none .light none
.icon-container.commit-icon .icon-container.commit-icon
= custom_icon("icon_commit") = custom_icon("icon_commit")
- if commit_sha - if commit_sha
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" = link_to job.short_sha, namespace_project_commit_path(job.project.namespace, job.project, job.sha), class: "commit-id monospace"
- if build.stuck? - if job.stuck?
= icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.') = icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.')
- if retried - if retried
= icon('refresh', class: 'text-warning has-tooltip', title: 'Job was retried') = icon('refresh', class: 'text-warning has-tooltip', title: 'Job was retried')
.label-container .label-container
- if build.tags.any? - if job.tags.any?
- build.tags.each do |tag| - job.tags.each do |tag|
%span.label.label-primary %span.label.label-primary
= tag = tag
- if build.try(:trigger_request) - if job.try(:trigger_request)
%span.label.label-info triggered %span.label.label-info triggered
- if build.try(:allow_failure) - if job.try(:allow_failure)
%span.label.label-danger allowed to fail %span.label.label-danger allowed to fail
- if build.action? - if job.action?
%span.label.label-info manual %span.label.label-info manual
- if pipeline_link - if pipeline_link
%td %td
= link_to pipeline_path(build.pipeline) do = link_to pipeline_path(pipeline) do
%span.pipeline-id ##{build.pipeline.id} %span.pipeline-id ##{pipeline.id}
%span by %span by
- if build.pipeline.user - if pipeline.user
= user_avatar(user: build.pipeline.user, size: 20) = user_avatar(user: pipeline.user, size: 20)
- else - else
%span.monospace API %span.monospace API
- if admin - if admin
%td %td
- if build.project - if job.project
= link_to build.project.name_with_namespace, admin_namespace_project_path(build.project.namespace, build.project) = link_to job.project.name_with_namespace, admin_namespace_project_path(job.project.namespace, job.project)
%td %td
- if build.try(:runner) - if job.try(:runner)
= runner_link(build.runner) = runner_link(job.runner)
- else - else
.light none .light none
- if stage - if stage
%td %td
= build.stage = job.stage
%td %td
= build.name = job.name
%td %td
- if build.duration - if job.duration
%p.duration %p.duration
= custom_icon("icon_timer") = custom_icon("icon_timer")
= duration_in_numbers(build.duration) = duration_in_numbers(job.duration)
- if build.finished_at - if job.finished_at
%p.finished-at %p.finished-at
= icon("calendar") = icon("calendar")
%span= time_ago_with_tooltip(build.finished_at) %span= time_ago_with_tooltip(job.finished_at)
%td.coverage %td.coverage
- if build.try(:coverage) - if job.try(:coverage)
#{build.coverage}% #{job.coverage}%
%td %td
.pull-right .pull-right
- if can?(current_user, :read_build, build) && build.artifacts? - if can?(current_user, :read_build, job) && job.artifacts?
= link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do = link_to download_namespace_project_build_artifacts_path(job.project.namespace, job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
= icon('download') = icon('download')
- if can?(current_user, :update_build, build) - if can?(current_user, :update_build, job)
- if build.active? - if job.active?
= link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do = link_to cancel_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
= icon('remove', class: 'cred') = icon('remove', class: 'cred')
- elsif allow_retry - elsif allow_retry
- if build.playable? && !admin - if job.playable? && !admin
= link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do = link_to play_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
= custom_icon('icon_play') = custom_icon('icon_play')
- elsif build.retryable? - elsif job.retryable?
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do = link_to retry_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
= icon('repeat') = icon('repeat')
.page-content-header .page-content-header
.header-main-content .header-main-content
%strong Commit #{@commit.short_id} %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 %span.hidden-xs authored
#{time_ago_with_tooltip(@commit.authored_date)} #{time_ago_with_tooltip(@commit.authored_date)}
%span by %span by
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
= icon('comment') = icon('comment')
= @notes_count = @notes_count
= link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do = 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 .dropdown.inline
%a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } } %a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } }
%span Options %span Options
......
...@@ -37,6 +37,6 @@ ...@@ -37,6 +37,6 @@
.commit-actions.flex-row.hidden-xs .commit-actions.flex-row.hidden-xs
- if commit.status(ref) - if commit.status(ref)
= render_commit_status(commit, ref: 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 commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
= link_to_browse_code(project, commit) = link_to_browse_code(project, commit)
...@@ -18,16 +18,16 @@ ...@@ -18,16 +18,16 @@
.block-controls.hidden-xs.hidden-sm .block-controls.hidden-xs.hidden-sm
- if @merge_request.present? - if @merge_request.present?
.control .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) - elsif create_mr_button?(@repository.root_ref, @ref)
.control .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 .control
= form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do = 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 } = 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 .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") = icon("rss")
%div{ id: dom_id(@project) } %div{ id: dom_id(@project) }
......
...@@ -21,6 +21,6 @@ ...@@ -21,6 +21,6 @@
&nbsp; &nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn" = button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if @merge_request.present? - 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? - 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 @@ ...@@ -8,7 +8,4 @@
#environments-folder-list-view{ data: { "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s, #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, "can-read-environment" => can?(current_user, :read_environment, @project).to_s,
"css-class" => container_class, "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") } }
...@@ -22,4 +22,4 @@ ...@@ -22,4 +22,4 @@
%p %p
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do
%i.fa.fa-code-fork %i.fa.fa-code-fork
Try to Fork again Try to fork again
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
.email-modal-input-group.input-group .email-modal-input-group.input-group
= text_field_tag :issue_email, email, class: "monospace js-select-on-focus form-control", readonly: true = text_field_tag :issue_email, email, class: "monospace js-select-on-focus form-control", readonly: true
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#issue_email') = clipboard_button(target: '#issue_email')
%p %p
The subject will be used as the title of the new issue, and the message will be the description. The subject will be used as the title of the new issue, and the message will be the description.
......
...@@ -24,9 +24,9 @@ ...@@ -24,9 +24,9 @@
issue: { assignee_id: issues_finder.assignee.try(:id), issue: { assignee_id: issues_finder.assignee.try(:id),
milestone_id: issues_finder.milestones.first.try(:id) }), milestone_id: issues_finder.milestones.first.try(:id) }),
class: "btn btn-new", class: "btn btn-new",
title: "New Issue", title: "New issue",
id: "new_issue_link" do id: "new_issue_link" do
New Issue New issue
= render 'shared/issuable/search_bar', type: :issues = render 'shared/issuable/search_bar', type: :issues
.issues-holder .issues-holder
......
...@@ -53,5 +53,6 @@ ...@@ -53,5 +53,6 @@
:javascript :javascript
var merge_request = new MergeRequest({ 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 @@ ...@@ -8,7 +8,7 @@
%p %p
%strong Step 1. %strong Step 1.
Fetch and check out the branch for this merge request 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 %pre.dark#merge-info-1
- if @merge_request.for_fork? - if @merge_request.for_fork?
:preserve :preserve
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
%p %p
%strong Step 3. %strong Step 3.
Merge the branch and fix any conflicts that come up 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 %pre.dark#merge-info-3
- if @merge_request.for_fork? - if @merge_request.for_fork?
:preserve :preserve
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
%p %p
%strong Step 4. %strong Step 4.
Push the result of the merge to GitLab 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 %pre.dark#merge-info-4
:preserve :preserve
git push origin #{h @merge_request.target_branch} git push origin #{h @merge_request.target_branch}
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
- if can_remove_source_branch - 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 = 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') = icon('trash-o')
Remove Source Branch Remove source branch
- if mr_can_be_reverted - 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") = 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 - if mr_can_be_cherry_picked
......
...@@ -10,24 +10,24 @@ ...@@ -10,24 +10,24 @@
- if @pipeline && @pipeline.active? - if @pipeline && @pipeline.active?
%span.btn-group %span.btn-group
= button_tag class: "btn btn-info js-merge-when-pipeline-succeeds-button merge-when-pipeline-succeeds" do = 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? - unless @project.only_allow_merge_if_pipeline_succeeds?
= button_tag class: "btn btn-info dropdown-toggle", 'data-toggle' => 'dropdown' do = button_tag class: "btn btn-info dropdown-toggle", 'data-toggle' => 'dropdown' do
= icon('caret-down') = icon('caret-down')
%span.sr-only %span.sr-only
Select Merge Moment Select merge moment
%ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' } %ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' }
%li %li
= link_to "#", class: "merge_when_pipeline_succeeds" do = link_to "#", class: "merge_when_pipeline_succeeds" do
= icon('check fw') = icon('check fw')
Merge When Pipeline Succeeds Merge when pipeline succeeds
%li %li
= link_to "#", class: "accept-merge-request" do = link_to "#", class: "accept-merge-request" do
= icon('warning fw') = icon('warning fw')
Merge Immediately Merge immediately
- else - else
= f.button class: "btn btn-grouped js-merge-button accept-merge-request" do = 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? - if @merge_request.force_remove_source_branch?
.accept-control .accept-control
The source branch will be removed. The source branch will be removed.
......
...@@ -26,8 +26,8 @@ ...@@ -26,8 +26,8 @@
- if remove_source_branch_button - 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 = 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') = icon('times')
Remove Source Branch When Merged Remove source branch when merged
- if user_can_cancel_automatic_merge - 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 = 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 @@ ...@@ -9,8 +9,8 @@
.nav-controls .nav-controls
= render 'shared/milestones_sort_dropdown' = render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project) - 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 = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone' do
New Milestone New milestone
.milestones .milestones
%ul.content-list %ul.content-list
......
...@@ -23,9 +23,9 @@ ...@@ -23,9 +23,9 @@
.milestone-buttons .milestone-buttons
- if can?(current_user, :admin_milestone, @project) - if can?(current_user, :admin_milestone, @project)
- if @milestone.active? - 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 - 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 = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do
Edit Edit
......
...@@ -9,6 +9,6 @@ ...@@ -9,6 +9,6 @@
.note-form-actions.clearfix .note-form-actions.clearfix
.settings-message.note-edit-warning.js-edit-warning .settings-message.note-edit-warning.js-edit-warning
Finish editing this message first! 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' } %button.btn.btn-nr.btn-cancel.note-edit-cancel{ type: 'button' }
Cancel Cancel
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.
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