Skip to content
Snippets Groups Projects
Commit 0e4f2e01 authored by Alfredo Sumaran's avatar Alfredo Sumaran
Browse files

Merge branch 'fl-remove-ujs-pipelines' into 'master'

Remove UJS actions from pipelines tables

Closes #20450, #28535, and #5580

See merge request !9929
parents bb1620aa b0f2cbce
No related branches found
No related tags found
No related merge requests found
Showing
with 612 additions and 342 deletions
/* eslint-disable no-new, no-param-reassign */
/* global Vue, CommitsPipelineStore, PipelinesService, Flash */
/* eslint-disable no-param-reassign */
import CommitPipelinesTable from './pipelines_table';
 
window.Vue = require('vue');
require('./pipelines_table');
window.Vue.use(require('vue-resource'));
/**
* Commits View > Pipelines Tab > Pipelines Table.
* Merge Request View > Pipelines Tab > Pipelines Table.
Loading
Loading
@@ -21,7 +22,7 @@ $(() => {
}
 
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView();
gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable();
 
if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl);
Loading
Loading
/* globals Vue */
/* eslint-disable no-unused-vars, no-param-reassign */
/**
* Pipelines service.
*
* Used to fetch the data used to render the pipelines table.
* Uses Vue.Resource
*/
class PipelinesService {
/**
* FIXME: The url provided to request the pipelines in the new merge request
* page already has `.json`.
* This should be fixed when the endpoint is improved.
*
* @param {String} root
*/
constructor(root) {
let endpoint;
if (root.indexOf('.json') === -1) {
endpoint = `${root}.json`;
} else {
endpoint = root;
}
this.pipelines = Vue.resource(endpoint);
}
/**
* Given the root param provided when the class is initialized, will
* make a GET request.
*
* @return {Promise}
*/
all() {
return this.pipelines.get();
}
}
window.gl = window.gl || {};
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
gl.commits.pipelines.PipelinesService = PipelinesService;
/* eslint-disable no-new, no-param-reassign */
/* global Vue, CommitsPipelineStore, PipelinesService, Flash */
window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor');
require('../../vue_shared/components/pipelines_table');
require('./pipelines_service');
const PipelineStore = require('./pipelines_store');
/* eslint-disable no-new*/
/* global Flash */
import Vue from 'vue';
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';
import eventHub from '../../vue_pipelines_index/event_hub';
import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor';
 
/**
*
Loading
Loading
@@ -20,48 +19,59 @@ const PipelineStore = require('./pipelines_store');
* as soon as we have Webpack and can load them directly into JS files.
*/
 
(() => {
window.gl = window.gl || {};
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
export default Vue.component('pipelines-table', {
components: {
'pipelines-table-component': PipelinesTableComponent,
},
 
gl.commits.pipelines.PipelinesTableView = Vue.component('pipelines-table', {
/**
* Accesses the DOM to provide the needed data.
* Returns the necessary props to render `pipelines-table-component` component.
*
* @return {Object}
*/
data() {
const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset;
const store = new PipelineStore();
 
components: {
'pipelines-table-component': gl.pipelines.PipelinesTableComponent,
},
return {
endpoint: pipelinesTableData.endpoint,
store,
state: store.state,
isLoading: false,
};
},
 
/**
* Accesses the DOM to provide the needed data.
* Returns the necessary props to render `pipelines-table-component` component.
*
* @return {Object}
*/
data() {
const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset;
const store = new PipelineStore();
/**
* When the component is about to be mounted, tell the service to fetch the data
*
* A request to fetch the pipelines will be made.
* In case of a successfull response we will store the data in the provided
* store, in case of a failed response we need to warn the user.
*
*/
beforeMount() {
this.service = new PipelinesService(this.endpoint);
 
return {
endpoint: pipelinesTableData.endpoint,
store,
state: store.state,
isLoading: false,
};
},
this.fetchPipelines();
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
beforeUpdate() {
if (this.state.pipelines.length && this.$children) {
this.store.startTimeAgoLoops.call(this, Vue);
}
},
 
/**
* When the component is about to be mounted, tell the service to fetch the data
*
* A request to fetch the pipelines will be made.
* In case of a successfull response we will store the data in the provided
* store, in case of a failed response we need to warn the user.
*
*/
beforeMount() {
const pipelinesService = new gl.commits.pipelines.PipelinesService(this.endpoint);
beforeDestroyed() {
eventHub.$off('refreshPipelines');
},
 
methods: {
fetchPipelines() {
this.isLoading = true;
return pipelinesService.all()
return this.service.getPipelines()
.then(response => response.json())
.then((json) => {
// depending of the endpoint the response can either bring a `pipelines` key or not.
Loading
Loading
@@ -71,34 +81,30 @@ const PipelineStore = require('./pipelines_store');
})
.catch(() => {
this.isLoading = false;
new Flash('An error occurred while fetching the pipelines, please reload the page again.', 'alert');
new Flash('An error occurred while fetching the pipelines, please reload the page again.');
});
},
},
 
beforeUpdate() {
if (this.state.pipelines.length && this.$children) {
PipelineStore.startTimeAgoLoops.call(this, Vue);
}
},
template: `
<div class="pipelines">
<div class="realtime-loading" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
</div>
template: `
<div class="pipelines">
<div class="realtime-loading" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
</div>
 
<div class="blank-state blank-state-no-icon"
v-if="!isLoading && state.pipelines.length === 0">
<h2 class="blank-state-title js-blank-state-title">
No pipelines to show
</h2>
</div>
<div class="blank-state blank-state-no-icon"
v-if="!isLoading && state.pipelines.length === 0">
<h2 class="blank-state-title js-blank-state-title">
No pipelines to show
</h2>
</div>
 
<div class="table-holder pipelines"
v-if="!isLoading && state.pipelines.length > 0">
<pipelines-table-component :pipelines="state.pipelines"/>
</div>
<div class="table-holder pipelines"
v-if="!isLoading && state.pipelines.length > 0">
<pipelines-table-component
:pipelines="state.pipelines"
:service="service" />
</div>
`,
});
})();
</div>
`,
});
/* eslint-disable no-param-reassign, no-new */
/* eslint-disable no-new */
/* global Flash */
import Vue from 'vue';
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from './environments_table';
import EnvironmentsStore from '../stores/environments_store';
import TablePaginationComponent from '../../vue_shared/components/table_pagination';
import '../../lib/utils/common_utils';
import eventHub from '../event_hub';
 
const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
require('../../vue_shared/components/table_pagination');
require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor');
export default Vue.component('environment-component', {
 
components: {
'environment-table': EnvironmentTable,
'table-pagination': gl.VueGlPagination,
'table-pagination': TablePaginationComponent,
},
 
data() {
Loading
Loading
@@ -59,7 +56,6 @@ export default Vue.component('environment-component', {
canCreateEnvironmentParsed() {
return gl.utils.convertPermissionToBoolean(this.canCreateEnvironment);
},
},
 
/**
Loading
Loading
import Timeago from 'timeago.js';
import '../../lib/utils/text_utility';
import ActionsComponent from './environment_actions';
import ExternalUrlComponent from './environment_external_url';
import StopComponent from './environment_stop';
import RollbackComponent from './environment_rollback';
import TerminalButtonComponent from './environment_terminal_button';
import '../../lib/utils/text_utility';
import '../../vue_shared/components/commit';
import CommitComponent from '../../vue_shared/components/commit';
 
/**
* Envrionment Item Component
*
* Renders a table row for each environment.
*/
const timeagoInstance = new Timeago();
 
export default {
components: {
'commit-component': gl.CommitComponent,
'commit-component': CommitComponent,
'actions-component': ActionsComponent,
'external-url-component': ExternalUrlComponent,
'stop-component': StopComponent,
Loading
Loading
/**
* Render environments table.
*/
import EnvironmentItem from './environment_item';
import EnvironmentTableRowComponent from './environment_item';
 
export default {
components: {
'environment-item': EnvironmentItem,
'environment-item': EnvironmentTableRowComponent,
},
 
props: {
Loading
Loading
/* eslint-disable no-param-reassign, no-new */
/* eslint-disable no-new */
/* global Flash */
import Vue from 'vue';
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from '../components/environments_table';
import EnvironmentsStore from '../stores/environments_store';
const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
require('../../vue_shared/components/table_pagination');
require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor');
import TablePaginationComponent from '../../vue_shared/components/table_pagination';
import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor';
 
export default Vue.component('environment-folder-view', {
components: {
'environment-table': EnvironmentTable,
'table-pagination': gl.VueGlPagination,
'table-pagination': TablePaginationComponent,
},
 
data() {
Loading
Loading
/* eslint-disable class-methods-use-this */
import Vue from 'vue';
import VueResource from 'vue-resource';
Vue.use(VueResource);
 
export default class EnvironmentsService {
constructor(endpoint) {
Loading
Loading
import '~/lib/utils/common_utils';
/**
* Environments Store.
*
Loading
Loading
/* eslint-disable no-new, no-alert */
/* global Flash */
import '~/flash';
import eventHub from '../event_hub';
export default {
props: {
endpoint: {
type: String,
required: true,
},
service: {
type: Object,
required: true,
},
title: {
type: String,
required: true,
},
icon: {
type: String,
required: true,
},
cssClass: {
type: String,
required: true,
},
confirmActionMessage: {
type: String,
required: false,
},
},
data() {
return {
isLoading: false,
};
},
computed: {
iconClass() {
return `fa fa-${this.icon}`;
},
buttonClass() {
return `btn has-tooltip ${this.cssClass}`;
},
},
methods: {
onClick() {
if (this.confirmActionMessage && confirm(this.confirmActionMessage)) {
this.makeRequest();
} else if (!this.confirmActionMessage) {
this.makeRequest();
}
},
makeRequest() {
this.isLoading = true;
this.service.postAction(this.endpoint)
.then(() => {
this.isLoading = false;
eventHub.$emit('refreshPipelines');
})
.catch(() => {
this.isLoading = false;
new Flash('An error occured while making the request.');
});
},
},
template: `
<button
type="button"
@click="onClick"
:class="buttonClass"
:title="title"
:aria-label="title"
data-placement="top"
:disabled="isLoading">
<i :class="iconClass" aria-hidden="true"/>
<i class="fa fa-spinner fa-spin" aria-hidden="true" v-if="isLoading" />
</button>
`,
};
export default {
props: [
'pipeline',
],
computed: {
user() {
return !!this.pipeline.user;
},
},
template: `
<td>
<a
:href="pipeline.path"
class="js-pipeline-url-link">
<span class="pipeline-id">#{{pipeline.id}}</span>
</a>
<span>by</span>
<a
class="js-pipeline-url-user"
v-if="user"
:href="pipeline.user.web_url">
<img
v-if="user"
class="avatar has-tooltip s20 "
:title="pipeline.user.name"
data-container="body"
:src="pipeline.user.avatar_url"
>
</a>
<span
v-if="!user"
class="js-pipeline-url-api api monospace">
API
</span>
<span
v-if="pipeline.flags.latest"
class="js-pipeline-url-lastest label label-success has-tooltip"
title="Latest pipeline for this branch"
data-original-title="Latest pipeline for this branch">
latest
</span>
<span
v-if="pipeline.flags.yaml_errors"
class="js-pipeline-url-yaml label label-danger has-tooltip"
:title="pipeline.yaml_errors"
:data-original-title="pipeline.yaml_errors">
yaml invalid
</span>
<span
v-if="pipeline.flags.stuck"
class="js-pipeline-url-stuck label label-warning">
stuck
</span>
</td>
`,
};
/* eslint-disable no-new */
/* global Flash */
import '~/flash';
import playIconSvg from 'icons/_icon_play.svg';
import eventHub from '../event_hub';
export default {
props: {
actions: {
type: Array,
required: true,
},
service: {
type: Object,
required: true,
},
},
data() {
return {
playIconSvg,
isLoading: false,
};
},
methods: {
onClickAction(endpoint) {
this.isLoading = true;
this.service.postAction(endpoint)
.then(() => {
this.isLoading = false;
eventHub.$emit('refreshPipelines');
})
.catch(() => {
this.isLoading = false;
new Flash('An error occured while making the request.');
});
},
},
template: `
<div class="btn-group" v-if="actions">
<button
type="button"
class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions"
title="Manual job"
data-toggle="dropdown"
data-placement="top"
aria-label="Manual job"
:disabled="isLoading">
${playIconSvg}
<i class="fa fa-caret-down" aria-hidden="true"></i>
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</button>
<ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions">
<button
type="button"
class="js-pipeline-action-link no-btn"
@click="onClickAction(action.path)">
${playIconSvg}
<span>{{action.name}}</span>
</button>
</li>
</ul>
</div>
`,
};
export default {
props: {
artifacts: {
type: Array,
required: true,
},
},
template: `
<div class="btn-group" role="group">
<button
class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
title="Artifacts"
data-placement="top"
data-toggle="dropdown"
aria-label="Artifacts">
<i class="fa fa-download" aria-hidden="true"></i>
<i class="fa fa-caret-down" aria-hidden="true"></i>
</button>
<ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="artifact in artifacts">
<a
rel="nofollow"
:href="artifact.path">
<i class="fa fa-download" aria-hidden="true"></i>
<span>Download {{artifact.name}} artifacts</span>
</a>
</li>
</ul>
</div>
`,
};
/* global Flash */
import canceledSvg from 'icons/_icon_status_canceled_borderless.svg';
import createdSvg from 'icons/_icon_status_created_borderless.svg';
import failedSvg from 'icons/_icon_status_failed_borderless.svg';
import manualSvg from 'icons/_icon_status_manual_borderless.svg';
import pendingSvg from 'icons/_icon_status_pending_borderless.svg';
import runningSvg from 'icons/_icon_status_running_borderless.svg';
import skippedSvg from 'icons/_icon_status_skipped_borderless.svg';
import successSvg from 'icons/_icon_status_success_borderless.svg';
import warningSvg from 'icons/_icon_status_warning_borderless.svg';
export default {
data() {
const svgsDictionary = {
icon_status_canceled: canceledSvg,
icon_status_created: createdSvg,
icon_status_failed: failedSvg,
icon_status_manual: manualSvg,
icon_status_pending: pendingSvg,
icon_status_running: runningSvg,
icon_status_skipped: skippedSvg,
icon_status_success: successSvg,
icon_status_warning: warningSvg,
};
return {
builds: '',
spinner: '<span class="fa fa-spinner fa-spin"></span>',
svg: svgsDictionary[this.stage.status.icon],
};
},
props: {
stage: {
type: Object,
required: true,
},
},
updated() {
if (this.builds) {
this.stopDropdownClickPropagation();
}
},
methods: {
fetchBuilds(e) {
const ariaExpanded = e.currentTarget.attributes['aria-expanded'];
if (ariaExpanded && (ariaExpanded.textContent === 'true')) return null;
return this.$http.get(this.stage.dropdown_path)
.then((response) => {
this.builds = JSON.parse(response.body).html;
}, () => {
const flash = new Flash('Something went wrong on our end.');
return flash;
});
},
/**
* When the user right clicks or cmd/ctrl + click in the job name
* the dropdown should not be closed and the link should open in another tab,
* so we stop propagation of the click event inside the dropdown.
*
* Since this component is rendered multiple times per page we need to guarantee we only
* target the click event of this component.
*/
stopDropdownClickPropagation() {
$(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')).on('click', (e) => {
e.stopPropagation();
});
},
},
computed: {
buildsOrSpinner() {
return this.builds ? this.builds : this.spinner;
},
dropdownClass() {
if (this.builds) return 'js-builds-dropdown-container';
return 'js-builds-dropdown-loading builds-dropdown-loading';
},
buildStatus() {
return `Build: ${this.stage.status.label}`;
},
tooltip() {
return `has-tooltip ci-status-icon ci-status-icon-${this.stage.status.group}`;
},
triggerButtonClass() {
return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`;
},
},
template: `
<div>
<button
@click="fetchBuilds($event)"
:class="triggerButtonClass"
:title="stage.title"
data-placement="top"
data-toggle="dropdown"
type="button"
:aria-label="stage.title">
<span v-html="svg" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
</button>
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
<div class="arrow-up" aria-hidden="true"></div>
<div
:class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu"
v-html="buildsOrSpinner">
</div>
</ul>
</div>
`,
};
import canceledSvg from 'icons/_icon_status_canceled.svg';
import createdSvg from 'icons/_icon_status_created.svg';
import failedSvg from 'icons/_icon_status_failed.svg';
import manualSvg from 'icons/_icon_status_manual.svg';
import pendingSvg from 'icons/_icon_status_pending.svg';
import runningSvg from 'icons/_icon_status_running.svg';
import skippedSvg from 'icons/_icon_status_skipped.svg';
import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
export default {
props: {
pipeline: {
type: Object,
required: true,
},
},
data() {
const svgsDictionary = {
icon_status_canceled: canceledSvg,
icon_status_created: createdSvg,
icon_status_failed: failedSvg,
icon_status_manual: manualSvg,
icon_status_pending: pendingSvg,
icon_status_running: runningSvg,
icon_status_skipped: skippedSvg,
icon_status_success: successSvg,
icon_status_warning: warningSvg,
};
return {
svg: svgsDictionary[this.pipeline.details.status.icon],
};
},
computed: {
cssClasses() {
return `ci-status ci-${this.pipeline.details.status.group}`;
},
detailsPath() {
const { status } = this.pipeline.details;
return status.has_details ? status.details_path : false;
},
content() {
return `${this.svg} ${this.pipeline.details.status.text}`;
},
},
template: `
<td class="commit-link">
<a
:class="cssClasses"
:href="detailsPath"
v-html="content">
</a>
</td>
`,
};
import iconTimerSvg from 'icons/_icon_timer.svg';
import '../../lib/utils/datetime_utility';
export default {
data() {
return {
currentTime: new Date(),
iconTimerSvg,
};
},
props: ['pipeline'],
computed: {
timeAgo() {
return gl.utils.getTimeago();
},
localTimeFinished() {
return gl.utils.formatDate(this.pipeline.details.finished_at);
},
timeStopped() {
const changeTime = this.currentTime;
const options = {
weekday: 'long',
year: 'numeric',
month: 'short',
day: 'numeric',
};
options.timeZoneName = 'short';
const finished = this.pipeline.details.finished_at;
if (!finished && changeTime) return false;
return ({ words: this.timeAgo.format(finished) });
},
duration() {
const { duration } = this.pipeline.details;
const date = new Date(duration * 1000);
let hh = date.getUTCHours();
let mm = date.getUTCMinutes();
let ss = date.getSeconds();
if (hh < 10) hh = `0${hh}`;
if (mm < 10) mm = `0${mm}`;
if (ss < 10) ss = `0${ss}`;
if (duration !== null) return `${hh}:${mm}:${ss}`;
return false;
},
},
methods: {
changeTime() {
this.currentTime = new Date();
},
},
template: `
<td class="pipelines-time-ago">
<p class="duration" v-if='duration'>
<span v-html="iconTimerSvg"></span>
{{duration}}
</p>
<p class="finished-at" v-if='timeStopped'>
<i class="fa fa-calendar"></i>
<time
data-toggle="tooltip"
data-placement="top"
data-container="body"
:data-original-title='localTimeFinished'>
{{timeStopped.words}}
</time>
</p>
</td>
`,
};
import Vue from 'vue';
export default new Vue();
/* eslint-disable no-param-reassign */
/* global Vue, VueResource, gl */
window.Vue = require('vue');
import PipelinesStore from './stores/pipelines_store';
import PipelinesComponent from './pipelines';
import '../vue_shared/vue_resource_interceptor';
const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
require('../lib/utils/common_utils');
require('../vue_shared/vue_resource_interceptor');
require('./pipelines');
 
$(() => new Vue({
el: document.querySelector('.vue-pipelines-index'),
 
data() {
const project = document.querySelector('.pipelines');
const store = new PipelinesStore();
 
return {
scope: project.dataset.url,
store: new gl.PipelineStore(),
store,
endpoint: project.dataset.url,
};
},
components: {
'vue-pipelines': gl.VuePipelines,
'vue-pipelines': PipelinesComponent,
},
template: `
<vue-pipelines
:scope="scope"
:store="store">
</vue-pipelines>
:endpoint="endpoint"
:store="store" />
`,
}));
/* global Vue, Flash, gl */
/* eslint-disable no-param-reassign, no-alert */
const playIconSvg = require('icons/_icon_play.svg');
((gl) => {
gl.VuePipelineActions = Vue.extend({
props: ['pipeline'],
computed: {
actions() {
return this.pipeline.details.manual_actions.length > 0;
},
artifacts() {
return this.pipeline.details.artifacts.length > 0;
},
},
methods: {
download(name) {
return `Download ${name} artifacts`;
},
/**
* Shows a dialog when the user clicks in the cancel button.
* We need to prevent the default behavior and stop propagation because the
* link relies on UJS.
*
* @param {Event} event
*/
confirmAction(event) {
if (!confirm('Are you sure you want to cancel this pipeline?')) {
event.preventDefault();
event.stopPropagation();
}
},
},
data() {
return { playIconSvg };
},
template: `
<td class="pipeline-actions">
<div class="pull-right">
<div class="btn-group">
<div class="btn-group" v-if="actions">
<button
class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions"
data-toggle="dropdown"
title="Manual job"
data-placement="top"
data-container="body"
aria-label="Manual job">
<span v-html="playIconSvg" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
</button>
<ul class="dropdown-menu dropdown-menu-align-right">
<li v-for='action in pipeline.details.manual_actions'>
<a
rel="nofollow"
data-method="post"
:href="action.path" >
<span v-html="playIconSvg" aria-hidden="true"></span>
<span>{{action.name}}</span>
</a>
</li>
</ul>
</div>
<div class="btn-group" v-if="artifacts">
<button
class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
title="Artifacts"
data-placement="top"
data-container="body"
data-toggle="dropdown"
aria-label="Artifacts">
<i class="fa fa-download" aria-hidden="true"></i>
<i class="fa fa-caret-down" aria-hidden="true"></i>
</button>
<ul class="dropdown-menu dropdown-menu-align-right">
<li v-for='artifact in pipeline.details.artifacts'>
<a
rel="nofollow"
:href="artifact.path">
<i class="fa fa-download" aria-hidden="true"></i>
<span>{{download(artifact.name)}}</span>
</a>
</li>
</ul>
</div>
<div class="btn-group" v-if="pipeline.flags.retryable">
<a
class="btn btn-default btn-retry has-tooltip"
title="Retry"
rel="nofollow"
data-method="post"
data-placement="top"
data-container="body"
data-toggle="dropdown"
:href='pipeline.retry_path'
aria-label="Retry">
<i class="fa fa-repeat" aria-hidden="true"></i>
</a>
</div>
<div class="btn-group" v-if="pipeline.flags.cancelable">
<a
class="btn btn-remove has-tooltip"
title="Cancel"
rel="nofollow"
data-method="post"
data-placement="top"
data-container="body"
data-toggle="dropdown"
:href='pipeline.cancel_path'
aria-label="Cancel">
<i class="fa fa-remove" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
</td>
`,
});
})(window.gl || (window.gl = {}));
/* global Vue, gl */
/* eslint-disable no-param-reassign */
((gl) => {
gl.VuePipelineUrl = Vue.extend({
props: [
'pipeline',
],
computed: {
user() {
return !!this.pipeline.user;
},
},
template: `
<td>
<a :href='pipeline.path'>
<span class="pipeline-id">#{{pipeline.id}}</span>
</a>
<span>by</span>
<a
v-if='user'
:href='pipeline.user.web_url'
>
<img
v-if='user'
class="avatar has-tooltip s20 "
:title='pipeline.user.name'
data-container="body"
:src='pipeline.user.avatar_url'
>
</a>
<span
v-if='!user'
class="api monospace"
>
API
</span>
<span
v-if='pipeline.flags.latest'
class="label label-success has-tooltip"
title="Latest pipeline for this branch"
data-original-title="Latest pipeline for this branch"
>
latest
</span>
<span
v-if='pipeline.flags.yaml_errors'
class="label label-danger has-tooltip"
:title='pipeline.yaml_errors'
:data-original-title='pipeline.yaml_errors'
>
yaml invalid
</span>
<span
v-if='pipeline.flags.stuck'
class="label label-warning"
>
stuck
</span>
</td>
`,
});
})(window.gl || (window.gl = {}));
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment