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

This commit is contained in:
Luke "Jared" Bennett 2017-04-07 12:09:20 +01:00
commit 2f22890d42
No known key found for this signature in database
GPG key ID: 402ED51FB5D306C2
229 changed files with 1849 additions and 1048 deletions

View file

@ -20,6 +20,7 @@ import eventHub from '../eventhub';
list: {
type: Object,
required: false,
default: () => ({}),
},
rootPath: {
type: String,
@ -31,6 +32,26 @@ import eventHub from '../eventhub';
default: false,
},
},
computed: {
cardUrl() {
return `${this.issueLinkBase}/${this.issue.id}`;
},
assigneeUrl() {
return `${this.rootPath}${this.issue.assignee.username}`;
},
assigneeUrlTitle() {
return `Assigned to ${this.issue.assignee.name}`;
},
avatarUrlTitle() {
return `Avatar for ${this.issue.assignee.name}`;
},
issueId() {
return `#${this.issue.id}`;
},
showLabelFooter() {
return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
},
},
methods: {
showLabel(label) {
if (!this.list) return true;
@ -67,35 +88,41 @@ import eventHub from '../eventhub';
},
template: `
<div>
<h4 class="card-title">
<i
class="fa fa-eye-slash confidential-icon"
v-if="issue.confidential"></i>
<a
:href="issueLinkBase + '/' + issue.id"
:title="issue.title">
{{ issue.title }}
</a>
</h4>
<div class="card-footer">
<span
class="card-number"
v-if="issue.id">
#{{ issue.id }}
</span>
<div class="card-header">
<h4 class="card-title">
<i
class="fa fa-eye-slash confidential-icon"
v-if="issue.confidential"
aria-hidden="true"
/>
<a
class="js-no-trigger"
:href="cardUrl"
:title="issue.title">{{ issue.title }}</a>
<span
class="card-number"
v-if="issue.id"
>
{{ issueId }}
</span>
</h4>
<a
class="card-assignee has-tooltip js-no-trigger"
:href="rootPath + issue.assignee.username"
:title="'Assigned to ' + issue.assignee.name"
:href="assigneeUrl"
:title="assigneeUrlTitle"
v-if="issue.assignee"
data-container="body">
data-container="body"
>
<img
class="avatar avatar-inline s20 js-no-trigger"
:src="issue.assignee.avatar"
width="20"
height="20"
:alt="'Avatar for ' + issue.assignee.name" />
:alt="avatarUrlTitle"
/>
</a>
</div>
<div class="card-footer" v-if="showLabelFooter">
<button
class="label color-label has-tooltip js-no-trigger"
v-for="label in issue.labels"

View file

@ -8,10 +8,8 @@ Vue.use(VueResource);
/**
* Commits View > Pipelines Tab > Pipelines Table.
* Merge Request View > Pipelines Tab > Pipelines Table.
*
* Renders Pipelines table in pipelines tab in the commits show view.
* Renders Pipelines table in pipelines tab in the merge request show view.
*/
$(() => {
@ -20,13 +18,14 @@ $(() => {
gl.commits.pipelines = gl.commits.pipelines || {};
if (gl.commits.PipelinesTableBundle) {
document.querySelector('#commit-pipeline-table-view').removeChild(this.pipelinesTableBundle.$el);
gl.commits.PipelinesTableBundle.$destroy(true);
}
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable();
if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl);
gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable().$mount();
document.querySelector('#commit-pipeline-table-view').appendChild(gl.commits.pipelines.PipelinesTableBundle.$el);
}
});

View file

@ -1,4 +1,5 @@
import Vue from 'vue';
import Visibility from 'visibilityjs';
import PipelinesTableComponent from '../../vue_shared/components/pipelines_table';
import PipelinesService from '../../vue_pipelines_index/services/pipelines_service';
import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store';
@ -7,6 +8,7 @@ import EmptyState from '../../vue_pipelines_index/components/empty_state';
import ErrorState from '../../vue_pipelines_index/components/error_state';
import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor';
import Poll from '../../lib/utils/poll';
/**
*
@ -20,6 +22,7 @@ import '../../vue_shared/vue_resource_interceptor';
*/
export default Vue.component('pipelines-table', {
components: {
'pipelines-table-component': PipelinesTableComponent,
'error-state': ErrorState,
@ -42,6 +45,7 @@ export default Vue.component('pipelines-table', {
state: store.state,
isLoading: false,
hasError: false,
isMakingRequest: false,
};
},
@ -64,17 +68,41 @@ export default Vue.component('pipelines-table', {
*
*/
beforeMount() {
this.endpoint = this.$el.dataset.endpoint;
this.helpPagePath = this.$el.dataset.helpPagePath;
const element = document.querySelector('#commit-pipeline-table-view');
this.endpoint = element.dataset.endpoint;
this.helpPagePath = element.dataset.helpPagePath;
this.service = new PipelinesService(this.endpoint);
this.fetchPipelines();
this.poll = new Poll({
resource: this.service,
method: 'getPipelines',
successCallback: this.successCallback,
errorCallback: this.errorCallback,
notificationCallback: this.setIsMakingRequest,
});
if (!Visibility.hidden()) {
this.isLoading = true;
this.poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
beforeUpdate() {
if (this.state.pipelines.length && this.$children) {
if (this.state.pipelines.length &&
this.$children &&
!this.isMakingRequest &&
!this.isLoading) {
this.store.startTimeAgoLoops.call(this, Vue);
}
},
@ -83,21 +111,35 @@ export default Vue.component('pipelines-table', {
eventHub.$off('refreshPipelines');
},
destroyed() {
this.poll.stop();
},
methods: {
fetchPipelines() {
this.isLoading = true;
return this.service.getPipelines()
.then(response => response.json())
.then((json) => {
// depending of the endpoint the response can either bring a `pipelines` key or not.
const pipelines = json.pipelines || json;
this.store.storePipelines(pipelines);
this.isLoading = false;
})
.catch(() => {
this.hasError = true;
this.isLoading = false;
});
.then(response => this.successCallback(response))
.catch(() => this.errorCallback());
},
successCallback(resp) {
const response = resp.json();
// depending of the endpoint the response can either bring a `pipelines` key or not.
const pipelines = response.pipelines || response;
this.store.storePipelines(pipelines);
this.isLoading = false;
},
errorCallback() {
this.hasError = true;
this.isLoading = false;
},
setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest;
},
},

View file

@ -38,9 +38,35 @@ showTooltip = function(target, title) {
};
$(function() {
var clipboard;
clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
const clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
clipboard.on('success', genericSuccess);
return clipboard.on('error', genericError);
clipboard.on('error', genericError);
// This a workaround around ClipboardJS limitations to allow the context-specific copy/pasting of plain text or GFM.
// The Ruby `clipboard_button` helper sneaks a JSON hash with `text` and `gfm` keys into the `data-clipboard-text`
// attribute that ClipboardJS reads from.
// When ClipboardJS creates a new `textarea` (directly inside `body`, with a `readonly` attribute`), sets its value
// to the value of this data attribute, focusses on it, and finally programmatically issues the 'Copy' command,
// this code intercepts the copy command/event at the last minute to deconstruct this JSON hash and set the
// `text/plain` and `text/x-gfm` copy data types to the intended values.
$(document).on('copy', 'body > textarea[readonly]', function(e) {
const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return;
const text = e.target.value;
let json;
try {
json = JSON.parse(text);
} catch (ex) {
return;
}
if (!json.text || !json.gfm) return;
e.preventDefault();
clipboardData.setData('text/plain', json.text);
clipboardData.setData('text/x-gfm', json.gfm);
});
});

View file

@ -13,10 +13,6 @@ class Diff {
$diffFile.each((index, file) => new gl.ImageFile(file));
if (this.diffViewType() === 'parallel') {
$('.content-wrapper .container-fluid').removeClass('container-limited');
}
if (!isBound) {
$(document)
.on('click', '.js-unfold', this.handleClickUnfold.bind(this))

View file

@ -24,7 +24,6 @@
/* global Search */
/* global Admin */
/* global NamespaceSelects */
/* global ShortcutsDashboardNavigation */
/* global Project */
/* global ProjectAvatar */
/* global CompareAutocomplete */
@ -378,7 +377,6 @@ const ShortcutsBlob = require('./shortcuts_blob');
break;
case 'dashboard':
case 'root':
shortcut_handler = new ShortcutsDashboardNavigation();
new UserCallout();
break;
case 'groups':

View file

@ -31,12 +31,6 @@ export default Vue.component('environment-folder-view', {
cssContainerClass: environmentsData.cssClass,
canCreateDeployment: environmentsData.canCreateDeployment,
canReadEnvironment: environmentsData.canReadEnvironment,
// svgs
commitIconSvg: environmentsData.commitIconSvg,
playIconSvg: environmentsData.playIconSvg,
terminalIconSvg: environmentsData.terminalIconSvg,
// Pagination Properties,
paginationInformation: {},
pageNumber: 1,
@ -163,9 +157,6 @@ export default Vue.component('environment-folder-view', {
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
:play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg"
:commit-icon-svg="commitIconSvg"
:service="service"/>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"

View file

@ -55,6 +55,7 @@ require('./filtered_search_dropdown');
renderContent() {
const dropdownData = [];
[].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
const { icon, hint, tag, type } = dropdownMenu.dataset;
if (icon && hint && tag) {

View file

@ -65,7 +65,6 @@ export default class Poll {
this.makeRequest();
}, pollInterval);
}
this.options.successCallback(response);
}
@ -76,8 +75,14 @@ export default class Poll {
notificationCallback(true);
return resource[method](data)
.then(response => this.checkConditions(response))
.catch(error => errorCallback(error));
.then((response) => {
this.checkConditions(response);
notificationCallback(false);
})
.catch((error) => {
notificationCallback(false);
errorCallback(error);
});
}
/**

View file

@ -90,6 +90,7 @@ import './flash';
.on('click', this.clickTab);
}
// Used in tests
unbindEvents() {
$(document)
.off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
@ -99,9 +100,11 @@ import './flash';
.off('click', this.clickTab);
}
destroy() {
this.unbindEvents();
destroyPipelinesView() {
if (this.commitPipelinesTable) {
document.querySelector('#commit-pipeline-table-view')
.removeChild(this.commitPipelinesTable.$el);
this.commitPipelinesTable.$destroy();
}
}
@ -128,6 +131,7 @@ import './flash';
this.loadCommits($target.attr('href'));
this.expandView();
this.resetViewContainer();
this.destroyPipelinesView();
} else if (this.isDiffAction(action)) {
this.loadDiff($target.attr('href'));
if (Breakpoints.get().getBreakpointSize() !== 'lg') {
@ -136,12 +140,14 @@ import './flash';
if (this.diffViewType() === 'parallel') {
this.expandViewContainer();
}
this.destroyPipelinesView();
} else if (action === 'pipelines') {
this.resetViewContainer();
this.loadPipelines();
this.mountPipelinesView();
} else {
this.expandView();
this.resetViewContainer();
this.destroyPipelinesView();
}
if (this.setUrl) {
this.setCurrentAction(action);
@ -227,16 +233,12 @@ import './flash';
});
}
loadPipelines() {
if (this.pipelinesLoaded) {
return;
}
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
// Could already be mounted from the `pipelines_bundle`
if (pipelineTableViewEl) {
this.commitPipelinesTable = new CommitPipelinesTable().$mount(pipelineTableViewEl);
}
this.pipelinesLoaded = true;
mountPipelinesView() {
this.commitPipelinesTable = new CommitPipelinesTable().$mount();
// $mount(el) replaces the el with the new rendered component. We need it in order to mount
// it everytime this tab is clicked - https://vuejs.org/v2/api/#vm-mount
document.querySelector('#commit-pipeline-table-view')
.appendChild(this.commitPipelinesTable.$el);
}
loadDiff(source) {

View file

@ -1,6 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */
/* global Mousetrap */
/* global findFileURL */
import findAndFollowLink from './shortcuts_dashboard_navigation';
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
@ -14,11 +15,33 @@
}
Mousetrap.bind('?', this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch);
Mousetrap.bind('f', (function(_this) {
return function(e) {
return _this.focusFilter(e);
};
})(this));
Mousetrap.bind('f', (e => this.focusFilter(e)));
const $globalDropdownMenu = $('.global-dropdown-menu');
const $globalDropdownToggle = $('.global-dropdown-toggle');
$('.global-dropdown').on('hide.bs.dropdown', () => {
$globalDropdownMenu.removeClass('shortcuts');
});
Mousetrap.bind('n', () => {
$globalDropdownMenu.toggleClass('shortcuts');
$globalDropdownToggle.trigger('click');
if (!$globalDropdownMenu.is(':visible')) {
$globalDropdownToggle.blur();
}
});
Mousetrap.bind('shift+t', () => findAndFollowLink('.shortcuts-todos'));
Mousetrap.bind('shift+a', () => findAndFollowLink('.dashboard-shortcuts-activity'));
Mousetrap.bind('shift+i', () => findAndFollowLink('.dashboard-shortcuts-issues'));
Mousetrap.bind('shift+m', () => findAndFollowLink('.dashboard-shortcuts-merge_requests'));
Mousetrap.bind('shift+p', () => findAndFollowLink('.dashboard-shortcuts-projects'));
Mousetrap.bind('shift+g', () => findAndFollowLink('.dashboard-shortcuts-groups'));
Mousetrap.bind('shift+l', () => findAndFollowLink('.dashboard-shortcuts-milestones'));
Mousetrap.bind('shift+s', () => findAndFollowLink('.dashboard-shortcuts-snippets'));
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], this.toggleMarkdownPreview);
if (typeof findFileURL !== "undefined" && findFileURL !== null) {
Mousetrap.bind('t', function() {

View file

@ -1,43 +1,12 @@
/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */
/* global Mousetrap */
/* global Shortcuts */
/**
* Helper function that finds the href of the fiven selector and updates the location.
*
* @param {String} selector
*/
export default (selector) => {
const link = document.querySelector(selector).getAttribute('href');
require('./shortcuts');
(function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
this.ShortcutsDashboardNavigation = (function(superClass) {
extend(ShortcutsDashboardNavigation, superClass);
function ShortcutsDashboardNavigation() {
ShortcutsDashboardNavigation.__super__.constructor.call(this);
Mousetrap.bind('g a', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-activity');
});
Mousetrap.bind('g i', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-issues');
});
Mousetrap.bind('g m', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-merge_requests');
});
Mousetrap.bind('g t', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-todos');
});
Mousetrap.bind('g p', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-projects');
});
}
ShortcutsDashboardNavigation.findAndFollowLink = function(selector) {
var link;
link = $(selector).attr('href');
if (link) {
return window.location = link;
}
};
return ShortcutsDashboardNavigation;
})(Shortcuts);
}).call(window);
if (link) {
window.location = link;
}
};

View file

@ -1,6 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */
/* global Mousetrap */
/* global Shortcuts */
import findAndFollowLink from './shortcuts_dashboard_navigation';
require('./shortcuts');
@ -13,59 +14,23 @@ require('./shortcuts');
function ShortcutsNavigation() {
ShortcutsNavigation.__super__.constructor.call(this);
Mousetrap.bind('g p', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project');
});
Mousetrap.bind('g e', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity');
});
Mousetrap.bind('g f', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-tree');
});
Mousetrap.bind('g c', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-commits');
});
Mousetrap.bind('g b', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-builds');
});
Mousetrap.bind('g n', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-network');
});
Mousetrap.bind('g g', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-repository-charts');
});
Mousetrap.bind('g i', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues');
});
Mousetrap.bind('g l', function() {
ShortcutsNavigation.findAndFollowLink('.shortcuts-issue-boards');
});
Mousetrap.bind('g m', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests');
});
Mousetrap.bind('g t', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-todos');
});
Mousetrap.bind('g w', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki');
});
Mousetrap.bind('g s', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets');
});
Mousetrap.bind('i', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue');
});
Mousetrap.bind('g p', () => findAndFollowLink('.shortcuts-project'));
Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-project-activity'));
Mousetrap.bind('g f', () => findAndFollowLink('.shortcuts-tree'));
Mousetrap.bind('g c', () => findAndFollowLink('.shortcuts-commits'));
Mousetrap.bind('g j', () => findAndFollowLink('.shortcuts-builds'));
Mousetrap.bind('g n', () => findAndFollowLink('.shortcuts-network'));
Mousetrap.bind('g d', () => findAndFollowLink('.shortcuts-repository-charts'));
Mousetrap.bind('g i', () => findAndFollowLink('.shortcuts-issues'));
Mousetrap.bind('g b', () => findAndFollowLink('.shortcuts-issue-boards'));
Mousetrap.bind('g m', () => findAndFollowLink('.shortcuts-merge_requests'));
Mousetrap.bind('g t', () => findAndFollowLink('.shortcuts-todos'));
Mousetrap.bind('g w', () => findAndFollowLink('.shortcuts-wiki'));
Mousetrap.bind('g s', () => findAndFollowLink('.shortcuts-snippets'));
Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue'));
this.enabledHelp.push('.hidden-shortcut.project');
}
ShortcutsNavigation.findAndFollowLink = function(selector) {
var link;
link = $(selector).attr('href');
if (link) {
return window.location = link;
}
};
return ShortcutsNavigation;
})(Shortcuts);
}).call(window);

View file

@ -1,4 +1,5 @@
import Vue from 'vue';
import Visibility from 'visibilityjs';
import PipelinesService from './services/pipelines_service';
import eventHub from './event_hub';
import PipelinesTableComponent from '../vue_shared/components/pipelines_table';
@ -7,6 +8,7 @@ import EmptyState from './components/empty_state';
import ErrorState from './components/error_state';
import NavigationTabs from './components/navigation_tabs';
import NavigationControls from './components/nav_controls';
import Poll from '../lib/utils/poll';
export default {
props: {
@ -47,6 +49,7 @@ export default {
pagenum: 1,
isLoading: false,
hasError: false,
isMakingRequest: false,
};
},
@ -120,18 +123,49 @@ export default {
tagsPath: this.tagsPath,
};
},
pageParameter() {
return gl.utils.getParameterByName('page') || this.pagenum;
},
scopeParameter() {
return gl.utils.getParameterByName('scope') || this.apiScope;
},
},
created() {
this.service = new PipelinesService(this.endpoint);
this.fetchPipelines();
const poll = new Poll({
resource: this.service,
method: 'getPipelines',
data: { page: this.pageParameter, scope: this.scopeParameter },
successCallback: this.successCallback,
errorCallback: this.errorCallback,
notificationCallback: this.setIsMakingRequest,
});
if (!Visibility.hidden()) {
this.isLoading = true;
poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
poll.restart();
} else {
poll.stop();
}
});
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
beforeUpdate() {
if (this.state.pipelines.length && this.$children) {
if (this.state.pipelines.length &&
this.$children &&
!this.isMakingRequest &&
!this.isLoading) {
this.store.startTimeAgoLoops.call(this, Vue);
}
},
@ -154,27 +188,35 @@ export default {
},
fetchPipelines() {
const pageNumber = gl.utils.getParameterByName('page') || this.pagenum;
const scope = gl.utils.getParameterByName('scope') || this.apiScope;
if (!this.isMakingRequest) {
this.isLoading = true;
this.isLoading = true;
return this.service.getPipelines(scope, pageNumber)
.then(resp => ({
headers: resp.headers,
body: resp.json(),
}))
.then((response) => {
this.store.storeCount(response.body.count);
this.store.storePipelines(response.body.pipelines);
this.store.storePagination(response.headers);
})
.then(() => {
this.isLoading = false;
})
.catch(() => {
this.hasError = true;
this.isLoading = false;
});
this.service.getPipelines({ scope: this.scopeParameter, page: this.pageParameter })
.then(response => this.successCallback(response))
.catch(() => this.errorCallback());
}
},
successCallback(resp) {
const response = {
headers: resp.headers,
body: resp.json(),
};
this.store.storeCount(response.body.count);
this.store.storePipelines(response.body.pipelines);
this.store.storePagination(response.headers);
this.isLoading = false;
},
errorCallback() {
this.hasError = true;
this.isLoading = false;
},
setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest;
},
},

View file

@ -26,7 +26,8 @@ export default class PipelinesService {
this.pipelines = Vue.resource(endpoint);
}
getPipelines(scope, page) {
getPipelines(data = {}) {
const { scope, page } = data;
return this.pipelines.get({ scope, page });
}

View file

@ -187,6 +187,15 @@
}
}
.shortcut-mappings {
display: none;
}
&.shortcuts .shortcut-mappings {
display: inline-block;
margin-right: 5px;
}
ul {
margin: 0;
padding: 0;

View file

@ -446,10 +446,8 @@
}
}
.filter-dropdown-item.droplab-item-active {
.btn {
@extend %filter-dropdown-item-btn-hover;
}
.filter-dropdown-item.droplab-item-active .btn {
@extend %filter-dropdown-item-btn-hover;
}
.filter-dropdown-loading {

View file

@ -1,15 +1,18 @@
.timeline {
@include basic-list;
margin: 0;
padding: 0;
.timeline-entry {
padding: $gl-padding $gl-btn-padding 11px;
padding: $gl-padding $gl-btn-padding 14px;
border-color: $white-normal;
color: $gl-text-color;
border-bottom: 1px solid $border-white-light;
.timeline-entry-inner {
position: relative;
}
&:target {
background: $line-target-blue;
}

View file

@ -197,7 +197,7 @@
.card {
position: relative;
padding: 10px $gl-padding;
padding: 11px 10px 11px $gl-padding;
background: $white-light;
border-radius: $border-radius-default;
box-shadow: 0 1px 2px $issue-boards-card-shadow;
@ -217,6 +217,8 @@
}
.confidential-icon {
position: relative;
top: 1px;
margin-right: 5px;
}
}
@ -224,34 +226,43 @@
.card-title {
margin: 0;
font-size: 1em;
line-height: inherit;
a {
color: inherit;
color: $gl-text-color;
word-wrap: break-word;
margin-right: 2px;
}
}
.card-header {
display: flex;
min-height: 20px;
.card-assignee {
margin-left: auto;
margin-right: 5px;
padding-left: 10px;
height: 20px;
}
.avatar {
margin: 0;
}
}
.card-footer {
margin-top: 5px;
line-height: 25px;
margin: 0 0 5px;
.label {
margin-right: 5px;
font-size: (14px / $issue-boards-font-size) * 1em;
}
.card-assignee {
margin-right: 5px;
}
.avatar {
margin-left: 0;
margin-right: 0;
margin-top: 5px;
margin-right: 6px;
}
}
.card-number {
margin-right: 5px;
font-size: 12px;
color: $gl-text-color-secondary;
}
.issue-boards-search {

View file

@ -16,6 +16,15 @@ ul.notes {
.timeline-icon {
float: left;
svg {
width: 18px;
height: auto;
fill: $gray-darkest;
position: absolute;
left: 30px;
top: 15px;
}
}
.timeline-content {
@ -33,117 +42,6 @@ ul.notes {
white-space: nowrap;
}
.system-note {
font-size: 14px;
padding: 0;
clear: both;
&.timeline-entry::after {
clear: none;
}
.system-note-message {
display: inline;
&::first-letter {
text-transform: lowercase;
}
a {
color: $gl-link-color;
text-decoration: none;
}
p {
display: inline;
margin: 0;
&::first-letter {
text-transform: lowercase;
}
}
}
.timeline-content {
padding: 14px 10px;
}
.note-body {
overflow: hidden;
.system-note-commit-list-toggler {
color: $gl-link-color;
display: none;
padding: 10px 0 0;
cursor: pointer;
position: relative;
z-index: 2;
&:hover {
color: $gl-link-color;
text-decoration: underline;
}
}
.note-text {
& p:first-child {
display: none;
}
&.system-note-commit-list {
max-height: 70px;
overflow: hidden;
display: block;
ul {
margin: 3px 0 3px 16px !important;
.gfm-commit {
font-family: $monospace_font;
font-size: 12px;
}
}
p:first-child {
display: none;
}
&::after {
content: '';
width: 100%;
height: 67px;
position: absolute;
left: 0;
bottom: 0;
background: linear-gradient(rgba($white-light, 0.1) -100px, $white-light 100%);
}
&.hide-shade {
max-height: 100%;
overflow: auto;
&::after {
display: none;
background: transparent;
}
}
}
}
}
.timeline-icon {
display: none;
.avatar {
visibility: hidden;
.discussion-body & {
visibility: visible;
}
}
}
}
.discussion-body {
padding-top: 15px;
}
@ -239,7 +137,109 @@ ul.notes {
}
}
}
}
.system-note {
font-size: 14px;
padding: 0;
clear: both;
&.timeline-entry::after {
clear: none;
}
.system-note-message {
display: inline;
&::first-letter {
text-transform: lowercase;
}
a {
color: $gl-link-color;
text-decoration: none;
}
p {
display: inline;
margin: 0;
&::first-letter {
text-transform: lowercase;
}
}
}
.timeline-content {
padding: 14px 10px;
}
.note-header {
padding-bottom: 0;
}
.note-body {
overflow: hidden;
.system-note-commit-list-toggler {
color: $gl-link-color;
display: none;
padding: 10px 0 0;
cursor: pointer;
position: relative;
z-index: 2;
&:hover {
color: $gl-link-color;
text-decoration: underline;
}
}
.note-text {
& p:first-child {
display: none;
}
&.system-note-commit-list {
max-height: 70px;
overflow: hidden;
display: block;
ul {
margin: 3px 0 3px 16px !important;
.gfm-commit {
font-family: $monospace_font;
font-size: 12px;
}
}
p:first-child {
display: none;
}
&::after {
content: '';
width: 100%;
height: 67px;
position: absolute;
left: 0;
bottom: 0;
background: linear-gradient(rgba($white-light, 0.1) -100px, $white-light 100%);
}
&.hide-shade {
max-height: 100%;
overflow: auto;
&::after {
display: none;
background: transparent;
}
}
}
}
}
}
}

View file

@ -36,6 +36,8 @@ class Projects::CommitController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: PipelineSerializer
.new(project: @project, user: @current_user)
.represent(@pipelines)

View file

@ -233,6 +233,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: PipelineSerializer
.new(project: @project, user: @current_user)
.represent(@pipelines)
@ -246,6 +248,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
format.json do
define_pipelines_vars
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: {
pipelines: PipelineSerializer
.new(project: @project, user: @current_user)

View file

@ -29,6 +29,8 @@ class Projects::PipelinesController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: {
pipelines: PipelineSerializer
.new(project: @project, user: @current_user)
@ -114,7 +116,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def pipeline
@pipeline ||= project.pipelines.find_by!(id: params[:id])
@pipeline ||= project.pipelines.find_by!(id: params[:id]).present(current_user: current_user)
end
def commit

View file

@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def update_params
params.require(:project).permit(
:runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds
:public_builds, :auto_cancel_pending_pipelines
)
end
end

View file

@ -102,7 +102,7 @@ module BlobHelper
if Gitlab::MarkupHelper.previewable?(filename)
'Preview'
else
'Preview Changes'
'Preview changes'
end
end
@ -210,13 +210,13 @@ module BlobHelper
end
def copy_file_path_button(file_path)
clipboard_button(clipboard_text: file_path, class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy file path to clipboard')
clipboard_button(text: file_path, gfm: "`#{file_path}`", class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy file path to clipboard')
end
def copy_blob_content_button(blob)
return if markup?(blob.name)
clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{blob.id}']", class: "btn btn-sm", title: "Copy content to clipboard")
clipboard_button(target: ".blob-content[data-blob-id='#{blob.id}']", class: "btn btn-sm", title: "Copy content to clipboard")
end
def open_raw_file_button(path)

View file

@ -1,23 +1,42 @@
module ButtonHelper
# Output a "Copy to Clipboard" button
#
# data - Data attributes passed to `content_tag`
# data - Data attributes passed to `content_tag` (default: {}):
# :text - Text to copy (optional)
# :gfm - GitLab Flavored Markdown to copy, if different from `text` (optional)
# :target - Selector for target element to copy from (optional)
#
# Examples:
#
# # Define the clipboard's text
# clipboard_button(clipboard_text: "Foo")
# clipboard_button(text: "Foo")
# # => "<button class='...' data-clipboard-text='Foo'>...</button>"
#
# # Define the target element
# clipboard_button(clipboard_target: "div#foo")
# clipboard_button(target: "div#foo")
# # => "<button class='...' data-clipboard-target='div#foo'>...</button>"
#
# See http://clipboardjs.com/#usage
def clipboard_button(data = {})
css_class = data[:class] || 'btn-clipboard btn-transparent'
title = data[:title] || 'Copy to clipboard'
# This supports code in app/assets/javascripts/copy_to_clipboard.js that
# works around ClipboardJS limitations to allow the context-specific copy/pasting of plain text or GFM.
if text = data.delete(:text)
data[:clipboard_text] =
if gfm = data.delete(:gfm)
{ text: text, gfm: gfm }
else
text
end
end
target = data.delete(:target)
data[:clipboard_target] = target if target
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button,
icon('clipboard', 'aria-hidden': 'true'),
class: "btn #{css_class}",

View file

@ -0,0 +1,26 @@
module SystemNoteHelper
ICON_NAMES_BY_ACTION = {
'commit' => 'icon_commit',
'merge' => 'icon_merge',
'merged' => 'icon_merged',
'opened' => 'icon_status_open',
'closed' => 'icon_status_closed',
'time_tracking' => 'icon_stopwatch',
'assignee' => 'icon_user',
'title' => 'icon_pencil',
'task' => 'icon_check_square_o',
'label' => 'icon_tags',
'cross_reference' => 'icon_random',
'branch' => 'icon_code_fork',
'confidential' => 'icon_eye_slash',
'visible' => 'icon_eye',
'milestone' => 'icon_clock_o',
'discussion' => 'icon_comment_o',
'moved' => 'icon_arrow_circle_o_right'
}.freeze
def icon_for_system_note(note)
icon_name = ICON_NAMES_BY_ACTION[note.system_note_metadata&.action]
custom_icon(icon_name) if icon_name
end
end

View file

@ -103,18 +103,13 @@ module Ci
end
def playable?
project.builds_enabled? && has_commands? &&
action? && manual?
action? && manual?
end
def action?
self.when == 'manual'
end
def has_commands?
commands.present?
end
def play(current_user)
# Try to queue a current build
if self.enqueue
@ -131,8 +126,7 @@ module Ci
end
def retryable?
project.builds_enabled? && has_commands? &&
(success? || failed? || canceled?)
success? || failed? || canceled?
end
def retried?

View file

@ -4,14 +4,25 @@ module Ci
include HasStatus
include Importable
include AfterCommitQueue
include Presentable
belongs_to :project
belongs_to :user
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id'
has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id'
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
has_many :builds, foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id
has_many :pending_builds, -> { pending }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :retryable_builds, -> { latest.failed_or_canceled }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :cancelable_statuses, -> { cancelable }, foreign_key: :commit_id, class_name: 'CommitStatus'
has_many :manual_actions, -> { latest.manual_actions }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :artifacts, -> { latest.with_artifacts_not_expired }, foreign_key: :commit_id, class_name: 'Ci::Build'
delegate :id, to: :project, prefix: true
validates :sha, presence: { unless: :importing? }
@ -65,6 +76,10 @@ module Ci
pipeline.update_duration
end
before_transition canceled: any - [:canceled] do |pipeline|
pipeline.auto_canceled_by = nil
end
after_transition [:created, :pending] => :running do |pipeline|
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(id) }
end
@ -82,6 +97,8 @@ module Ci
pipeline.run_after_commit do
PipelineHooksWorker.perform_async(id)
Ci::ExpirePipelineCacheService.new(project, nil)
.execute(pipeline)
end
end
@ -160,10 +177,6 @@ module Ci
end
end
def artifacts
builds.latest.with_artifacts_not_expired.includes(project: [:namespace])
end
def valid_commit_sha
if self.sha == Gitlab::Git::BLANK_SHA
self.errors.add(:sha, " cant be 00000000 (branch removal)")
@ -200,27 +213,37 @@ module Ci
!tag?
end
def manual_actions
builds.latest.manual_actions.includes(project: [:namespace])
end
def stuck?
builds.pending.includes(:project).any?(&:stuck?)
pending_builds.any?(&:stuck?)
end
def retryable?
builds.latest.failed_or_canceled.any?(&:retryable?)
retryable_builds.any?
end
def cancelable?
statuses.cancelable.any?
cancelable_statuses.any?
end
def auto_canceled?
canceled? && auto_canceled_by_id?
end
def cancel_running
Gitlab::OptimisticLocking.retry_lock(
statuses.cancelable) do |cancelable|
cancelable.find_each(&:cancel)
Gitlab::OptimisticLocking.retry_lock(cancelable_statuses) do |cancelable|
cancelable.find_each do |job|
yield(job) if block_given?
job.cancel
end
end
end
def auto_cancel_running(pipeline)
update(auto_canceled_by: pipeline)
cancel_running do |job|
job.auto_canceled_by = pipeline
end
end
def retry_failed(current_user)

View file

@ -7,6 +7,7 @@ class CommitStatus < ActiveRecord::Base
belongs_to :project
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
belongs_to :user
delegate :commit, to: :pipeline
@ -137,6 +138,10 @@ class CommitStatus < ActiveRecord::Base
false
end
def auto_canceled?
canceled? && auto_canceled_by_id?
end
# Added in 9.0 to keep backward compatibility for projects exported in 8.17
# and prior.
def gl_project_id

View file

@ -76,6 +76,7 @@ module HasStatus
scope :canceled, -> { where(status: 'canceled') }
scope :skipped, -> { where(status: 'skipped') }
scope :manual, -> { where(status: 'manual') }
scope :created_or_pending, -> { where(status: [:created, :pending]) }
scope :running_or_pending, -> { where(status: [:running, :pending]) }
scope :finished, -> { where(status: [:success, :failed, :canceled]) }
scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) }

View file

@ -172,6 +172,8 @@ class Project < ActiveRecord::Base
has_many :environments, dependent: :destroy
has_many :deployments, dependent: :destroy
has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature
@ -260,6 +262,8 @@ class Project < ActiveRecord::Base
scope :with_builds_enabled, -> { with_feature_enabled(:builds) }
scope :with_issues_enabled, -> { with_feature_enabled(:issues) }
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
# project features may be "disabled", "internal" or "enabled". If "internal",
# they are only available to team members. This scope returns projects where
# the feature is either enabled, or internal with permission for the user.
@ -1085,15 +1089,15 @@ class Project < ActiveRecord::Base
end
def shared_runners
shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none
@shared_runners ||= shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none
end
def active_shared_runners
@active_shared_runners ||= shared_runners.active
end
def any_runners?(&block)
if runners.active.any?(&block)
return true
end
shared_runners.active.any?(&block)
active_runners.any?(&block) || active_shared_runners.any?(&block)
end
def valid_runners_token?(token)

View file

@ -11,5 +11,11 @@ module Ci
def erased_by_name
erased_by.name if erased_by_user?
end
def status_title
if auto_canceled?
"Job is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}"
end
end
end
end

View file

@ -0,0 +1,11 @@
module Ci
class PipelinePresenter < Gitlab::View::Presenter::Delegated
presents :pipeline
def status_title
if auto_canceled?
"Pipeline is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}"
end
end
end
end

View file

@ -69,13 +69,13 @@ class PipelineEntity < Grape::Entity
alias_method :pipeline, :object
def can_retry?
pipeline.retryable? &&
can?(request.user, :update_pipeline, pipeline)
can?(request.user, :update_pipeline, pipeline) &&
pipeline.retryable?
end
def can_cancel?
pipeline.cancelable? &&
can?(request.user, :update_pipeline, pipeline)
can?(request.user, :update_pipeline, pipeline) &&
pipeline.cancelable?
end
def detailed_status

View file

@ -13,7 +13,15 @@ class PipelineSerializer < BaseSerializer
def represent(resource, opts = {})
if resource.is_a?(ActiveRecord::Relation)
resource = resource.includes(project: :namespace)
resource = resource.preload([
:retryable_builds,
:cancelable_statuses,
:trigger_requests,
:project,
{ pending_builds: :project },
{ manual_actions: :project },
{ artifacts: :project }
])
end
if paginated?

View file

@ -53,6 +53,8 @@ module Ci
.execute(pipeline)
end
cancel_pending_pipelines if project.auto_cancel_pending_pipelines?
pipeline.tap(&:process!)
end
@ -63,6 +65,22 @@ module Ci
pipeline.git_commit_message =~ /\[(ci[ _-]skip|skip[ _-]ci)\]/i
end
def cancel_pending_pipelines
Gitlab::OptimisticLocking.retry_lock(auto_cancelable_pipelines) do |cancelables|
cancelables.find_each do |cancelable|
cancelable.auto_cancel_running(pipeline)
end
end
end
def auto_cancelable_pipelines
project.pipelines
.where(ref: pipeline.ref)
.where.not(id: pipeline.id)
.where.not(sha: project.repository.sha_from_ref(pipeline.ref))
.created_or_pending
end
def commit
@commit ||= project.commit(origin_sha || origin_ref)
end

View file

@ -0,0 +1,49 @@
module Ci
class ExpirePipelineCacheService < BaseService
attr_reader :pipeline
def execute(pipeline)
@pipeline = pipeline
store = Gitlab::EtagCaching::Store.new
store.touch(project_pipelines_path)
store.touch(commit_pipelines_path) if pipeline.commit
store.touch(new_merge_request_pipelines_path)
merge_requests_pipelines_paths.each { |path| store.touch(path) }
end
private
def project_pipelines_path
Gitlab::Routing.url_helpers.namespace_project_pipelines_path(
project.namespace,
project,
format: :json)
end
def commit_pipelines_path
Gitlab::Routing.url_helpers.pipelines_namespace_project_commit_path(
project.namespace,
project,
pipeline.commit.id,
format: :json)
end
def new_merge_request_pipelines_path
Gitlab::Routing.url_helpers.new_namespace_project_merge_request_path(
project.namespace,
project,
format: :json)
end
def merge_requests_pipelines_paths
pipeline.merge_requests.collect do |merge_request|
Gitlab::Routing.url_helpers.pipelines_namespace_project_merge_request_path(
project.namespace,
project,
merge_request,
format: :json)
end
end
end
end

View file

@ -7,9 +7,7 @@ module Ci
raise Gitlab::Access::AccessDeniedError
end
pipeline.builds.latest.failed_or_canceled.find_each do |build|
next unless build.retryable?
pipeline.retryable_builds.find_each do |build|
Ci::RetryBuildService.new(project, current_user)
.reprocess(build)
end

View file

@ -30,5 +30,5 @@
= link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-sm btn-block"
- else
.btn.btn-sm.disabled.btn-block
Already Blocked
Already blocked
= link_to 'Remove report', [:admin, abuse_report], remote: true, method: :delete, class: "btn btn-sm btn-block btn-close js-remove-tr"

View file

@ -148,7 +148,7 @@
Sign-in enabled
- if omniauth_enabled? && button_based_providers.any?
.form-group
= f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth Sign-In sources', class: 'control-label col-sm-2'
= f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2'
.col-sm-10
.btn-group{ data: { toggle: 'buttons' } }
- oauth_providers_checkboxes.each do |source|

View file

@ -4,7 +4,7 @@
%p.light
System OAuth applications don't belong to any user and can only be managed by admins
%hr
%p= link_to 'New Application', new_admin_application_path, class: 'btn btn-success'
%p= link_to 'New application', new_admin_application_path, class: 'btn btn-success'
%table.table.table-striped
%thead
%tr

View file

@ -125,7 +125,7 @@
= link_to admin_projects_path do
%h1= number_with_delimiter(Project.cached_count)
%hr
= link_to('New Project', new_project_path, class: "btn btn-new")
= link_to('New project', new_project_path, class: "btn btn-new")
.col-sm-4
.light-well.well-centered
%h4 Users
@ -133,7 +133,7 @@
= link_to admin_users_path do
%h1= number_with_delimiter(User.count)
%hr
= link_to 'New User', new_admin_user_path, class: "btn btn-new"
= link_to 'New user', new_admin_user_path, class: "btn btn-new"
.col-sm-4
.light-well.well-centered
%h4 Groups
@ -141,7 +141,7 @@
= link_to admin_groups_path do
%h1= number_with_delimiter(Group.count)
%hr
= link_to 'New Group', new_admin_group_path, class: "btn btn-new"
= link_to 'New group', new_admin_group_path, class: "btn btn-new"
.row.prepend-top-10
.col-md-4

View file

@ -3,7 +3,7 @@
%h3.page-title.deploy-keys-title
Public deploy keys (#{@deploy_keys.count})
.pull-right
= link_to 'New Deploy Key', new_admin_deploy_key_path, class: 'btn btn-new btn-sm btn-inverted'
= link_to 'New deploy key', new_admin_deploy_key_path, class: 'btn btn-new btn-sm btn-inverted'
- if @deploy_keys.any?
.table-holder.deploy-keys-list

View file

@ -30,7 +30,7 @@
= link_to admin_groups_path(sort: sort_value_largest_group, name: project_name) do
= sort_title_largest_group
= link_to new_admin_group_path, class: "btn btn-new" do
New Group
New group
%ul.content-list
= render @groups

View file

@ -116,7 +116,7 @@
group members
%span.badge= @group.members.size
.pull-right
= link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@group, :members]), class: "btn btn-xs"
= link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-xs"
%ul.well-list.group-users-list.content-list
= render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
.panel-footer

View file

@ -51,7 +51,7 @@
= f.check_box :enable_ssl_verification
%strong Enable SSL verification
.form-actions
= f.submit "Add System Hook", class: "btn btn-create"
= f.submit "Add system hook", class: "btn btn-create"
%hr
- if @hooks.any?
@ -62,7 +62,7 @@
- @hooks.each do |hook|
%li
.controls
= link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm"
= link_to 'Test hook', admin_hook_test_path(hook), class: "btn btn-sm"
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
.monospace= hook.url
%div

View file

@ -1,7 +1,7 @@
- page_title "Identities", @user.name, "Users"
= render 'admin/users/head'
= link_to 'New Identity', new_admin_user_identity_path, class: 'pull-right btn btn-new'
= link_to 'New identity', new_admin_user_identity_path, class: 'pull-right btn btn-new'
- if @identities.present?
.table-holder
%table.table

View file

@ -159,7 +159,7 @@
%span.badge= @group_members.size
.pull-right
= link_to admin_group_path(@group), class: 'btn btn-xs' do
= icon('pencil-square-o', text: 'Manage Access')
= icon('pencil-square-o', text: 'Manage access')
%ul.well-list.content-list
= render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false }
.panel-footer
@ -173,7 +173,7 @@
project members
%span.badge= @project.users.size
.pull-right
= link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@project, :members]), class: "btn btn-xs"
= link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-xs"
%ul.well-list.project_members.content-list
= render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
.panel-footer

View file

@ -35,5 +35,5 @@
= link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs"
- else
.btn.btn-xs.disabled
Already Blocked
Already blocked
= link_to 'Remove log', [:admin, spam_log], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr"

View file

@ -37,6 +37,6 @@
- if user.can_be_removed? && can?(current_user, :destroy_user, @user)
%li.divider
%li
= link_to 'Delete User', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Consider cancelling this deletion and blocking the user instead. Are you sure?" },
= link_to 'Delete user', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Consider cancelling this deletion and blocking the user instead. Are you sure?" },
class: 'btn btn-remove btn-block',
method: :delete

View file

@ -33,7 +33,7 @@
= sort_title_recently_updated
= link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do
= sort_title_oldest_updated
= link_to 'New User', new_admin_user_path, class: 'btn btn-new btn-search'
= link_to 'New user', new_admin_user_path, class: 'btn btn-new btn-search'
.nav-block
%ul.nav-links.wide.scrolling-tabs.white.scrolling-tabs

View file

@ -1,12 +1,13 @@
- status = local_assigns.fetch(:status)
- link = local_assigns.fetch(:link, true)
- css_classes = "ci-status ci-#{status.group}"
- link = local_assigns.fetch(:link, true)
- title = local_assigns.fetch(:title, nil)
- css_classes = "ci-status ci-#{status.group} #{'has-tooltip' if title.present?}"
- if link && status.has_details?
= link_to status.details_path, class: css_classes do
= link_to status.details_path, class: css_classes, title: title do
= custom_icon(status.icon)
= status.text
- else
%span{ class: css_classes }
%span{ class: css_classes, title: title }
= custom_icon(status.icon)
= status.text

View file

@ -11,4 +11,4 @@
= render 'shared/groups/dropdown'
- if current_user.can_create_group?
= link_to new_group_path, class: "btn btn-new" do
New Group
New group

View file

@ -19,4 +19,4 @@
= render 'shared/projects/dropdown'
- if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-new' do
New Project
New project

View file

@ -8,7 +8,7 @@
.nav-controls
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue"
= render 'shared/issuable/filter', type: :issues
= render 'shared/issues'

View file

@ -4,7 +4,7 @@
.top-area
= render 'shared/issuable/nav', type: :merge_requests
.nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request"
= render 'shared/issuable/filter', type: :merge_requests
= render 'shared/merge_requests'

View file

@ -5,7 +5,7 @@
= render 'shared/milestones_filter', counts: @milestone_states
.nav-controls
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New Milestone', include_groups: true
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true
.milestones
%ul.content-list

View file

@ -10,5 +10,5 @@
#{time_ago_with_tooltip(event.created_at)}
.pull-right
= link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do
Create Merge Request
= link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do
Create merge request

View file

@ -7,7 +7,7 @@
= custom_icon("icon_status_closed")
- else
.profile-icon.fork-icon
= custom_icon("code_fork")
= custom_icon("icon_code_fork")
.event-title
%span{ class: event.action_name }

View file

@ -1,5 +1,5 @@
.profile-icon
= custom_icon("comment_o")
= custom_icon("icon_comment_o")
.event-title
= event.action_name

View file

@ -51,4 +51,4 @@
%strong Removed group can not be restored!
.form-actions
= link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
= link_to 'Remove group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"

View file

@ -11,7 +11,7 @@
= icon('rss')
%span.icon-label
Subscribe
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue"
= render 'shared/issuable/filter', type: :issues

View file

@ -7,7 +7,7 @@
.nav-controls
- if can?(current_user, :admin_milestones, @group)
= link_to new_group_milestone_path(@group), class: "btn btn-new" do
New Milestone
New milestone
.row-content-block
Only milestones from

View file

@ -39,5 +39,5 @@
= render "shared/milestones/form_dates", f: f
.form-actions
= f.submit 'Create Milestone', class: "btn-create btn"
= f.submit 'Create milestone', class: "btn-create btn"
= link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel"

View file

@ -7,7 +7,7 @@
- if can? current_user, :admin_group, @group
.controls
= link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do
New Project
New project
%ul.well-list
- @projects.each do |project|
%li

View file

@ -15,6 +15,10 @@
%tr
%th
%th Global Shortcuts
%tr
%td.shortcut
.key n
%td Main Navigation
%tr
%td.shortcut
.key s
@ -39,24 +43,46 @@
.key
%i.fa.fa-arrow-up
%td Edit last comment (when focused on an empty textarea)
%tbody
%tr
%th
%th Project Files browsing
%tr
%td.shortcut
.key
%i.fa.fa-arrow-up
%td Move selection up
.key shift t
%td
Go to todos
%tr
%td.shortcut
.key
%i.fa.fa-arrow-down
%td Move selection down
.key shift a
%td
Go to the activity feed
%tr
%td.shortcut
.key enter
%td Open Selection
.key shift p
%td
Go to projects
%tr
%td.shortcut
.key shift i
%td
Go to issues
%tr
%td.shortcut
.key shift m
%td
Go to merge requests
%tr
%td.shortcut
.key shift g
%td
Go to groups
%tr
%td.shortcut
.key shift l
%td
Go to milestones
%tr
%td.shortcut
.key shift s
%td
Go to snippets
%tbody
%tr
%th
@ -79,51 +105,8 @@
%td.shortcut
.key esc
%td Go back
%tbody
%tr
%th
%th Project File
%tr
%td.shortcut
.key y
%td Go to file permalink
.col-lg-4
%table.shortcut-mappings
%tbody.hidden-shortcut.project{ style: 'display:none' }
%tr
%th
%th Global Dashboard
%tr
%td.shortcut
.key g
.key a
%td
Go to the activity feed
%tr
%td.shortcut
.key g
.key p
%td
Go to projects
%tr
%td.shortcut
.key g
.key i
%td
Go to issues
%tr
%td.shortcut
.key g
.key m
%td
Go to merge requests
%tr
%td.shortcut
.key g
.key t
%td
Go to todos
%tbody
%tr
%th
@ -155,7 +138,7 @@
%tr
%td.shortcut
.key g
.key b
.key j
%td
Go to jobs
%tr
@ -167,7 +150,7 @@
%tr
%td.shortcut
.key g
.key g
.key d
%td
Go to repository charts
%tr
@ -179,7 +162,7 @@
%tr
%td.shortcut
.key g
.key l
.key b
%td
Go to issue boards
%tr
@ -194,6 +177,12 @@
.key s
%td
Go to snippets
%tr
%td.shortcut
.key g
.key w
%td
Go to wiki
%tr
%td.shortcut
.key t
@ -202,6 +191,33 @@
%td.shortcut
.key i
%td New issue
%tbody
%tr
%th
%th Project Files browsing
%tr
%td.shortcut
.key
%i.fa.fa-arrow-up
%td Move selection up
%tr
%td.shortcut
.key
%i.fa.fa-arrow-down
%td Move selection down
%tr
%td.shortcut
.key enter
%td Open Selection
%tbody
%tr
%th
%th Project File
%tr
%td.shortcut
.key y
%td Go to file permalink
.col-lg-4
%table.shortcut-mappings
%tbody.hidden-shortcut.network{ style: 'display:none' }

View file

@ -225,7 +225,7 @@
%ul.dropdown-menu
%li
%a{ href: "#" }
Dropdown Option
Dropdown option
.dropdown.inline.pull-right
%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
Dropdown
@ -233,7 +233,7 @@
%ul.dropdown-menu.dropdown-menu-align-right
%li
%a{ href: "#" }
Dropdown Option
Dropdown option
.example
%div
.dropdown.inline
@ -243,7 +243,7 @@
%ul.dropdown-menu.dropdown-menu-selectable
%li
%a.is-active{ href: "#" }
Dropdown Option
Dropdown option
.example
%div
.dropdown.inline
@ -262,26 +262,26 @@
%ul
%li
%a.is-active{ href: "#" }
Dropdown Option
Dropdown option
%li
%a{ href: "#" }
Dropdown Option
Dropdown option
%li.divider
%li
%a{ href: "#" }
Dropdown Option
Dropdown option
%li
%a{ href: "#" }
Dropdown Option
Dropdown option
%li
%a{ href: "#" }
Dropdown Option
Dropdown option
%li
%a{ href: "#" }
Dropdown Option
Dropdown option
%li
%a{ href: "#" }
Dropdown Option
Dropdown option
.dropdown-footer
%strong Tip:
If an author is not a member of this project, you can still filter by his name while using the search field.
@ -301,26 +301,26 @@
%ul
%li
%a.is-active{ href: "#" }
Dropdown Option
Dropdown option
%li
%a{ href: "#" }
Dropdown Option
Dropdown option
%li.divider
%li
%a{ href: "#" }
Dropdown Option
Dropdown option
%li
%a{ href: "#" }
Dropdown Option
Dropdown option
%li
%a{ href: "#" }
Dropdown Option
Dropdown option
%li
%a{ href: "#" }
Dropdown Option
Dropdown option
%li
%a{ href: "#" }
Dropdown Option
Dropdown option
.dropdown-footer
%strong Tip:
If an author is not a member of this project, you can still filter by his name while using the search field.

View file

@ -9,7 +9,7 @@
To import a GitHub project, you first need to authorize GitLab to access
the list of your GitHub repositories:
= link_to 'List Your GitHub Repositories', status_import_github_path, class: 'btn btn-success'
= link_to 'List your GitHub repositories', status_import_github_path, class: 'btn btn-success'
%hr
@ -28,7 +28,7 @@
= form_tag personal_access_token_import_github_path, method: :post, class: 'form-inline' do
.form-group
= text_field_tag :personal_access_token, '', class: 'form-control', placeholder: "Personal Access Token", size: 40
= submit_tag 'List Your GitHub Repositories', class: 'btn btn-success'
= submit_tag 'List your GitHub repositories', class: 'btn btn-success'
- unless github_import_configured?
%hr

View file

@ -29,11 +29,11 @@
- if current_user
- if session[:impersonator_id]
%li.impersonation
= link_to admin_impersonation_path, method: :delete, title: "Stop Impersonation", aria: { label: 'Stop Impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= link_to admin_impersonation_path, method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- if current_user.is_admin?
%li
= link_to admin_root_path, title: 'Admin Area', aria: { label: "Admin Area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= link_to admin_root_path, title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw')
- if current_user.can_create_project?
%li

View file

@ -1,10 +1,18 @@
%ul
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
P
%span
Projects
= nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
A
%span
Activity
- if koding_enabled?
@ -13,25 +21,45 @@
%span
Koding
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do
= link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
G
%span
Groups
= nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do
= link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: 'Milestones' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
L
%span
Milestones
= nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
I
%span
Issues
.badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened))
= nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
M
%span
Merge Requests
.badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened))
= nav_link(controller: 'dashboard/snippets') do
= link_to dashboard_snippets_path, title: 'Snippets' do
= link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
S
%span
Snippets
%li.divider

View file

@ -49,14 +49,14 @@
%p
Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'}
- if current_user.two_factor_enabled?
= link_to 'Manage Two-Factor Authentication', profile_two_factor_auth_path, class: 'btn btn-info'
= link_to 'Manage two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-info'
= link_to 'Disable', profile_two_factor_auth_path,
method: :delete,
data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." },
class: 'btn btn-danger'
- else
.append-bottom-10
= link_to 'Enable Two-Factor Authentication', profile_two_factor_auth_path, class: 'btn btn-success'
= link_to 'Enable two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-success'
%hr
- if button_based_providers.any?

View file

@ -33,17 +33,17 @@
%li
= @primary
%span.pull-right
%span.label.label-success Primary Email
%span.label.label-success Primary email
- if @primary === current_user.public_email
%span.label.label-info Public Email
%span.label.label-info Public email
- if @primary === current_user.notification_email
%span.label.label-info Notification Email
%span.label.label-info Notification email
- @emails.each do |email|
%li
= email.email
%span.pull-right
- if email.email === current_user.public_email
%span.label.label-info Public Email
%span.label.label-info Public email
- if email.email === current_user.notification_email
%span.label.label-info Notification Email
%span.label.label-info Notification email
= link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10'

View file

@ -19,7 +19,7 @@
Your New Personal Access Token
.form-group
= text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control", 'aria-describedby' => "created-personal-access-token-help-block"
= clipboard_button(clipboard_text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left")
= clipboard_button(text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left")
%span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again.
%hr

View file

@ -44,7 +44,7 @@
= label_tag :pin_code, nil, class: "label-light"
= text_field_tag :pin_code, nil, class: "form-control", required: true
.prepend-top-default
= submit_tag 'Register with Two-Factor App', class: 'btn btn-success'
= submit_tag 'Register with two-factor app', class: 'btn btn-success'
%hr

View file

@ -1,5 +1,5 @@
.form-actions
= button_tag 'Commit Changes', class: 'btn commit-btn js-commit-button btn-create'
= button_tag 'Commit changes', class: 'btn commit-btn js-commit-button btn-create'
= link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message}

View file

@ -1,3 +1,3 @@
= link_to namespace_project_find_file_path(@project.namespace, @project, @ref), class: 'btn btn-grouped shortcuts-find-file', rel: 'nofollow' do
= icon('search')
%span Find File
%span Find file

View file

@ -10,9 +10,9 @@
- if @project && event.project != @project
%span at
%strong= link_to_project event.project
= clipboard_button(clipboard_text: event.ref_name, class: 'btn-clipboard btn-transparent', title: 'Copy branch to clipboard')
= clipboard_button(text: event.ref_name, class: 'btn-clipboard btn-transparent', title: 'Copy branch to clipboard')
#{time_ago_with_tooltip(event.created_at)}
.pull-right
= link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do
Create Merge Request
= link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do
Create merge request

View file

@ -20,7 +20,7 @@
-# only show normal/blame view links for text files
- if blob_text_viewable?(blob)
- if current_page? namespace_project_blame_path(@project.namespace, @project, @id)
= link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id),
= link_to 'Normal view', namespace_project_blob_path(@project.namespace, @project, @id),
class: 'btn btn-sm'
- else
= link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id),

View file

@ -5,7 +5,7 @@
.template-type-selector.js-template-type-selector-wrap.hidden
= dropdown_tag("Choose type", options: { toggle_class: 'btn js-template-type-selector', title: "Choose a template type" } )
.license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag("Apply a License template", options: { toggle_class: 'btn js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
= dropdown_tag("Apply a license template", options: { toggle_class: 'btn js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden

View file

@ -21,7 +21,7 @@
.controls.hidden-xs<
- if merge_project && create_mr_button?(@repository.root_ref, branch.name)
= link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do
Merge Request
Merge request
- if branch.name != @repository.root_ref
= link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do

View file

@ -1,14 +1,16 @@
- pipeline = @build.pipeline
.content-block.build-header.top-area
.header-content
= render 'ci/status/badge', status: @build.detailed_status(current_user), link: false
= render 'ci/status/badge', status: @build.detailed_status(current_user), link: false, title: @build.status_title
Job
%strong.js-build-id ##{@build.id}
in pipeline
= link_to pipeline_path(@build.pipeline) do
%strong ##{@build.pipeline.id}
= link_to pipeline_path(pipeline) do
%strong ##{pipeline.id}
for commit
= link_to namespace_project_commit_path(@project.namespace, @project, @build.pipeline.sha) do
%strong= @build.pipeline.short_sha
= link_to namespace_project_commit_path(@project.namespace, @project, pipeline.sha) do
%strong= pipeline.short_sha
from
= link_to namespace_project_commits_path(@project.namespace, @project, @build.ref) do
%code

View file

@ -17,7 +17,7 @@
= link_to 'Get started with CI/CD Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
= link_to ci_lint_path, class: 'btn btn-default' do
%span CI Lint
%span CI lint
.content-list.builds-content-list
= render "table", builds: @builds, project: @project

View file

@ -1,3 +1,5 @@
- job = build.present(current_user: current_user)
- pipeline = job.pipeline
- admin = local_assigns.fetch(:admin, false)
- ref = local_assigns.fetch(:ref, nil)
- commit_sha = local_assigns.fetch(:commit_sha, nil)
@ -8,101 +10,101 @@
%tr.build.commit{ class: ('retried' if retried) }
%td.status
= render "ci/status/badge", status: build.detailed_status(current_user)
= render "ci/status/badge", status: job.detailed_status(current_user), title: job.status_title
%td.branch-commit
- if can?(current_user, :read_build, build)
= link_to namespace_project_build_url(build.project.namespace, build.project, build) do
%span.build-link ##{build.id}
- if can?(current_user, :read_build, job)
= link_to namespace_project_build_url(job.project.namespace, job.project, job) do
%span.build-link ##{job.id}
- else
%span.build-link ##{build.id}
%span.build-link ##{job.id}
- if ref
- if build.ref
- if job.ref
.icon-container
= build.tag? ? icon('tag') : icon('code-fork')
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
= job.tag? ? icon('tag') : icon('code-fork')
= link_to job.ref, namespace_project_commits_path(job.project.namespace, job.project, job.ref), class: "monospace branch-name"
- else
.light none
.icon-container.commit-icon
= custom_icon("icon_commit")
- if commit_sha
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace"
= link_to job.short_sha, namespace_project_commit_path(job.project.namespace, job.project, job.sha), class: "commit-id monospace"
- if build.stuck?
- if job.stuck?
= icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.')
- if retried
= icon('refresh', class: 'text-warning has-tooltip', title: 'Job was retried')
.label-container
- if build.tags.any?
- build.tags.each do |tag|
- if job.tags.any?
- job.tags.each do |tag|
%span.label.label-primary
= tag
- if build.try(:trigger_request)
- if job.try(:trigger_request)
%span.label.label-info triggered
- if build.try(:allow_failure)
- if job.try(:allow_failure)
%span.label.label-danger allowed to fail
- if build.action?
- if job.action?
%span.label.label-info manual
- if pipeline_link
%td
= link_to pipeline_path(build.pipeline) do
%span.pipeline-id ##{build.pipeline.id}
= link_to pipeline_path(pipeline) do
%span.pipeline-id ##{pipeline.id}
%span by
- if build.pipeline.user
= user_avatar(user: build.pipeline.user, size: 20)
- if pipeline.user
= user_avatar(user: pipeline.user, size: 20)
- else
%span.monospace API
- if admin
%td
- if build.project
= link_to build.project.name_with_namespace, admin_namespace_project_path(build.project.namespace, build.project)
- if job.project
= link_to job.project.name_with_namespace, admin_namespace_project_path(job.project.namespace, job.project)
%td
- if build.try(:runner)
= runner_link(build.runner)
- if job.try(:runner)
= runner_link(job.runner)
- else
.light none
- if stage
%td
= build.stage
= job.stage
%td
= build.name
= job.name
%td
- if build.duration
- if job.duration
%p.duration
= custom_icon("icon_timer")
= duration_in_numbers(build.duration)
= duration_in_numbers(job.duration)
- if build.finished_at
- if job.finished_at
%p.finished-at
= icon("calendar")
%span= time_ago_with_tooltip(build.finished_at)
%span= time_ago_with_tooltip(job.finished_at)
%td.coverage
- if build.try(:coverage)
#{build.coverage}%
- if job.try(:coverage)
#{job.coverage}%
%td
.pull-right
- if can?(current_user, :read_build, build) && build.artifacts?
= link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
- if can?(current_user, :read_build, job) && job.artifacts?
= link_to download_namespace_project_build_artifacts_path(job.project.namespace, job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
= icon('download')
- if can?(current_user, :update_build, build)
- if build.active?
= link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
- if can?(current_user, :update_build, job)
- if job.active?
= link_to cancel_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
= icon('remove', class: 'cred')
- elsif allow_retry
- if build.playable? && !admin
= link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
- if job.playable? && !admin
= link_to play_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
= custom_icon('icon_play')
- elsif build.retryable?
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
- elsif job.retryable?
= link_to retry_namespace_project_build_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
= icon('repeat')

View file

@ -1,7 +1,7 @@
.page-content-header
.header-main-content
%strong Commit #{@commit.short_id}
= clipboard_button(clipboard_text: @commit.id, title: "Copy commit SHA to clipboard")
= clipboard_button(text: @commit.id, title: "Copy commit SHA to clipboard")
%span.hidden-xs authored
#{time_ago_with_tooltip(@commit.authored_date)}
%span by
@ -20,7 +20,7 @@
= icon('comment')
= @notes_count
= link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do
Browse Files
Browse files
.dropdown.inline
%a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } }
%span Options

View file

@ -37,6 +37,6 @@
.commit-actions.flex-row.hidden-xs
- if commit.status(ref)
= render_commit_status(commit, ref: ref)
= clipboard_button(clipboard_text: commit.id, title: "Copy commit SHA to clipboard")
= clipboard_button(text: commit.id, title: "Copy commit SHA to clipboard")
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
= link_to_browse_code(project, commit)

View file

@ -18,16 +18,16 @@
.block-controls.hidden-xs.hidden-sm
- if @merge_request.present?
.control
= link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
= link_to "View open merge request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
- elsif create_mr_button?(@repository.root_ref, @ref)
.control
= link_to "Create Merge Request", create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
= link_to "Create merge request", create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
.control
= form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do
= search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
.control
= link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: "Commits Feed", class: 'btn' do
= link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: "Commits feed", class: 'btn' do
= icon("rss")
%div{ id: dom_id(@project) }

View file

@ -21,6 +21,6 @@
&nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if @merge_request.present?
= link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn'
= link_to "View open merge request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn'
- elsif create_mr_button?
= link_to "Create Merge Request", create_mr_path, class: 'prepend-left-10 btn'
= link_to "Create merge request", create_mr_path, class: 'prepend-left-10 btn'

View file

@ -8,7 +8,4 @@
#environments-folder-list-view{ data: { "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
"can-read-environment" => can?(current_user, :read_environment, @project).to_s,
"css-class" => container_class,
"commit-icon-svg" => custom_icon("icon_commit"),
"terminal-icon-svg" => custom_icon("icon_terminal"),
"play-icon-svg" => custom_icon("icon_play") } }
"css-class" => container_class } }

View file

@ -22,4 +22,4 @@
%p
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do
%i.fa.fa-code-fork
Try to Fork again
Try to fork again

View file

@ -16,7 +16,7 @@
.email-modal-input-group.input-group
= text_field_tag :issue_email, email, class: "monospace js-select-on-focus form-control", readonly: true
.input-group-btn
= clipboard_button(clipboard_target: '#issue_email')
= clipboard_button(target: '#issue_email')
%p
The subject will be used as the title of the new issue, and the message will be the description.

View file

@ -24,9 +24,9 @@
issue: { assignee_id: issues_finder.assignee.try(:id),
milestone_id: issues_finder.milestones.first.try(:id) }),
class: "btn btn-new",
title: "New Issue",
title: "New issue",
id: "new_issue_link" do
New Issue
New issue
= render 'shared/issuable/search_bar', type: :issues
.issues-holder

View file

@ -53,5 +53,6 @@
:javascript
var merge_request = new MergeRequest({
action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}"
action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}",
setUrl: false,
});

View file

@ -8,7 +8,7 @@
%p
%strong Step 1.
Fetch and check out the branch for this merge request
= clipboard_button(clipboard_target: "pre#merge-info-1", title: "Copy commands to clipboard")
= clipboard_button(target: "pre#merge-info-1", title: "Copy commands to clipboard")
%pre.dark#merge-info-1
- if @merge_request.for_fork?
:preserve
@ -25,7 +25,7 @@
%p
%strong Step 3.
Merge the branch and fix any conflicts that come up
= clipboard_button(clipboard_target: "pre#merge-info-3", title: "Copy commands to clipboard")
= clipboard_button(target: "pre#merge-info-3", title: "Copy commands to clipboard")
%pre.dark#merge-info-3
- if @merge_request.for_fork?
:preserve
@ -38,7 +38,7 @@
%p
%strong Step 4.
Push the result of the merge to GitLab
= clipboard_button(clipboard_target: "pre#merge-info-4", title: "Copy commands to clipboard")
= clipboard_button(target: "pre#merge-info-4", title: "Copy commands to clipboard")
%pre.dark#merge-info-4
:preserve
git push origin #{h @merge_request.target_branch}

View file

@ -7,7 +7,7 @@
- if can_remove_source_branch
= link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-default remove_source_branch" do
= icon('trash-o')
Remove Source Branch
Remove source branch
- if mr_can_be_reverted
= revert_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: "close")
- if mr_can_be_cherry_picked

View file

@ -10,24 +10,24 @@
- if @pipeline && @pipeline.active?
%span.btn-group
= button_tag class: "btn btn-info js-merge-when-pipeline-succeeds-button merge-when-pipeline-succeeds" do
Merge When Pipeline Succeeds
Merge when pipeline succeeds
- unless @project.only_allow_merge_if_pipeline_succeeds?
= button_tag class: "btn btn-info dropdown-toggle", 'data-toggle' => 'dropdown' do
= icon('caret-down')
%span.sr-only
Select Merge Moment
Select merge moment
%ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' }
%li
= link_to "#", class: "merge_when_pipeline_succeeds" do
= icon('check fw')
Merge When Pipeline Succeeds
Merge when pipeline succeeds
%li
= link_to "#", class: "accept-merge-request" do
= icon('warning fw')
Merge Immediately
Merge immediately
- else
= f.button class: "btn btn-grouped js-merge-button accept-merge-request" do
Accept Merge Request
Accept merge request
- if @merge_request.force_remove_source_branch?
.accept-control
The source branch will be removed.

View file

@ -26,8 +26,8 @@
- if remove_source_branch_button
= link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_params(@merge_request)), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do
= icon('times')
Remove Source Branch When Merged
Remove source branch when merged
- if user_can_cancel_automatic_merge
= link_to cancel_merge_when_pipeline_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-sm" do
Cancel Automatic Merge
Cancel automatic merge

View file

@ -9,8 +9,8 @@
.nav-controls
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project)
= link_to new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New Milestone' do
New Milestone
= link_to new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone' do
New milestone
.milestones
%ul.content-list

View file

@ -23,9 +23,9 @@
.milestone-buttons
- if can?(current_user, :admin_milestone, @project)
- if @milestone.active?
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped"
= link_to 'Close milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped"
- else
= link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
= link_to 'Reopen milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
= link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do
Edit

View file

@ -9,6 +9,6 @@
.note-form-actions.clearfix
.settings-message.note-edit-warning.js-edit-warning
Finish editing this message first!
= submit_tag 'Save Comment', class: 'btn btn-nr btn-save js-comment-button'
= submit_tag 'Save comment', class: 'btn btn-nr btn-save js-comment-button'
%button.btn.btn-nr.btn-cancel.note-edit-cancel{ type: 'button' }
Cancel

Some files were not shown because too many files have changed in this diff Show more