Commit 4c09fb32 authored by Tim Zallmann's avatar Tim Zallmann

Merge branch 'fl-prettify-pipeline' into 'master'

Prettifies pipeline's javascript code

See merge request gitlab-org/gitlab-ce!20217
parents bcb4250b eb2de72c
<script> <script>
export default { export default {
name: 'PipelinesSvgState', name: 'PipelinesSvgState',
props: { props: {
svgPath: { svgPath: {
type: String, type: String,
required: true, required: true,
}, },
message: { message: {
type: String, type: String,
required: true, required: true,
},
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
export default { export default {
name: 'PipelinesEmptyState', name: 'PipelinesEmptyState',
props: { props: {
helpPagePath: { helpPagePath: {
type: String, type: String,
required: true, required: true,
},
emptyStateSvgPath: {
type: String,
required: true,
},
canSetCi: {
type: Boolean,
required: true,
},
}, },
}; emptyStateSvgPath: {
type: String,
required: true,
},
canSetCi: {
type: Boolean,
required: true,
},
},
};
</script> </script>
<template> <template>
<div class="row empty-state js-empty-state"> <div class="row empty-state js-empty-state">
......
...@@ -41,7 +41,6 @@ export default { ...@@ -41,7 +41,6 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
}, },
data() { data() {
return { return {
...@@ -67,7 +66,8 @@ export default { ...@@ -67,7 +66,8 @@ export default {
this.isDisabled = true; this.isDisabled = true;
axios.post(`${this.link}.json`) axios
.post(`${this.link}.json`)
.then(() => { .then(() => {
this.isDisabled = false; this.isDisabled = false;
this.$emit('pipelineActionRequestComplete'); this.$emit('pipelineActionRequestComplete');
......
<script> <script>
import ciIcon from '../../../vue_shared/components/ci_icon.vue'; import ciIcon from '../../../vue_shared/components/ci_icon.vue';
/** /**
* Component that renders both the CI icon status and the job name. * Component that renders both the CI icon status and the job name.
* Used in * Used in
* - Badge component * - Badge component
* - Dropdown badge components * - Dropdown badge components
*/ */
export default { export default {
components: { components: {
ciIcon, ciIcon,
},
props: {
name: {
type: String,
required: true,
}, },
props: {
name: {
type: String,
required: true,
},
status: { status: {
type: Object, type: Object,
required: true, required: true,
},
}, },
}; },
};
</script> </script>
<template> <template>
<span class="ci-job-name-component"> <span class="ci-job-name-component">
......
<script> <script>
import ciHeader from '../../vue_shared/components/header_ci_component.vue'; import ciHeader from '../../vue_shared/components/header_ci_component.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default { export default {
name: 'PipelineHeaderSection', name: 'PipelineHeaderSection',
components: { components: {
ciHeader, ciHeader,
loadingIcon, loadingIcon,
},
props: {
pipeline: {
type: Object,
required: true,
}, },
props: { isLoading: {
pipeline: { type: Boolean,
type: Object, required: true,
required: true,
},
isLoading: {
type: Boolean,
required: true,
},
},
data() {
return {
actions: this.getActions(),
};
}, },
},
data() {
return {
actions: this.getActions(),
};
},
computed: { computed: {
status() { status() {
return this.pipeline.details && this.pipeline.details.status; return this.pipeline.details && this.pipeline.details.status;
}, },
shouldRenderContent() { shouldRenderContent() {
return !this.isLoading && Object.keys(this.pipeline).length; return !this.isLoading && Object.keys(this.pipeline).length;
},
}, },
},
watch: { watch: {
pipeline() { pipeline() {
this.actions = this.getActions(); this.actions = this.getActions();
},
}, },
},
methods: { methods: {
postAction(action) { postAction(action) {
const index = this.actions.indexOf(action); const index = this.actions.indexOf(action);
this.$set(this.actions[index], 'isLoading', true); this.$set(this.actions[index], 'isLoading', true);
eventHub.$emit('headerPostAction', action); eventHub.$emit('headerPostAction', action);
}, },
getActions() { getActions() {
const actions = []; const actions = [];
if (this.pipeline.retry_path) { if (this.pipeline.retry_path) {
actions.push({ actions.push({
label: 'Retry', label: 'Retry',
path: this.pipeline.retry_path, path: this.pipeline.retry_path,
cssClass: 'js-retry-button btn btn-inverted-secondary', cssClass: 'js-retry-button btn btn-inverted-secondary',
type: 'button', type: 'button',
isLoading: false, isLoading: false,
}); });
} }
if (this.pipeline.cancel_path) { if (this.pipeline.cancel_path) {
actions.push({ actions.push({
label: 'Cancel running', label: 'Cancel running',
path: this.pipeline.cancel_path, path: this.pipeline.cancel_path,
cssClass: 'js-btn-cancel-pipeline btn btn-danger', cssClass: 'js-btn-cancel-pipeline btn btn-danger',
type: 'button', type: 'button',
isLoading: false, isLoading: false,
}); });
} }
return actions; return actions;
},
}, },
}; },
};
</script> </script>
<template> <template>
<div class="pipeline-header-container"> <div class="pipeline-header-container">
......
<script> <script>
import LoadingButton from '../../vue_shared/components/loading_button.vue'; import LoadingButton from '../../vue_shared/components/loading_button.vue';
export default { export default {
name: 'PipelineNavControls', name: 'PipelineNavControls',
components: { components: {
LoadingButton, LoadingButton,
},
props: {
newPipelinePath: {
type: String,
required: false,
default: null,
}, },
props: {
newPipelinePath: {
type: String,
required: false,
default: null,
},
resetCachePath: { resetCachePath: {
type: String, type: String,
required: false, required: false,
default: null, default: null,
}, },
ciLintPath: { ciLintPath: {
type: String, type: String,
required: false, required: false,
default: null, default: null,
}, },
isResetCacheButtonLoading: { isResetCacheButtonLoading: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
},
}, },
methods: { },
onClickResetCache() { methods: {
this.$emit('resetRunnersCache', this.resetCachePath); onClickResetCache() {
}, this.$emit('resetRunnersCache', this.resetCachePath);
}, },
}; },
};
</script> </script>
<template> <template>
<div class="nav-controls"> <div class="nav-controls">
......
<script> <script>
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import popover from '../../vue_shared/directives/popover'; import popover from '../../vue_shared/directives/popover';
export default { export default {
components: { components: {
userAvatarLink, userAvatarLink,
},
directives: {
tooltip,
popover,
},
props: {
pipeline: {
type: Object,
required: true,
}, },
directives: { autoDevopsHelpPath: {
tooltip, type: String,
popover, required: true,
}, },
props: { },
pipeline: { computed: {
type: Object, user() {
required: true, return this.pipeline.user;
},
autoDevopsHelpPath: {
type: String,
required: true,
},
}, },
computed: { popoverOptions() {
user() { return {
return this.pipeline.user; html: true,
}, trigger: 'focus',
popoverOptions() { placement: 'top',
return { title: `<div class="autodevops-title">
html: true,
trigger: 'focus',
placement: 'top',
title: `<div class="autodevops-title">
This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b> This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>
</div>`, </div>`,
content: `<a content: `<a
class="autodevops-link" class="autodevops-link"
href="${this.autoDevopsHelpPath}" href="${this.autoDevopsHelpPath}"
target="_blank" target="_blank"
rel="noopener noreferrer nofollow"> rel="noopener noreferrer nofollow">
Learn more about Auto DevOps Learn more about Auto DevOps
</a>`, </a>`,
}; };
},
}, },
}; },
};
</script> </script>
<template> <template>
<div class="table-section section-15 d-none d-sm-none d-md-block pipeline-tags"> <div class="table-section section-15 d-none d-sm-none d-md-block pipeline-tags">
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import { __, sprintf, s__ } from '../../locale'; import { __, sprintf, s__ } from '../../locale';
import createFlash from '../../flash'; import createFlash from '../../flash';
import PipelinesService from '../services/pipelines_service'; import PipelinesService from '../services/pipelines_service';
import pipelinesMixin from '../mixins/pipelines'; import pipelinesMixin from '../mixins/pipelines';
import TablePagination from '../../vue_shared/components/table_pagination.vue'; import TablePagination from '../../vue_shared/components/table_pagination.vue';
import NavigationTabs from '../../vue_shared/components/navigation_tabs.vue'; import NavigationTabs from '../../vue_shared/components/navigation_tabs.vue';
import NavigationControls from './nav_controls.vue'; import NavigationControls from './nav_controls.vue';
import { getParameterByName } from '../../lib/utils/common_utils'; import { getParameterByName } from '../../lib/utils/common_utils';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default { export default {
components: { components: {
TablePagination, TablePagination,
NavigationTabs, NavigationTabs,
NavigationControls, NavigationControls,
},
mixins: [pipelinesMixin, CIPaginationMixin],
props: {
store: {
type: Object,
required: true,
}, },
mixins: [pipelinesMixin, CIPaginationMixin], // Can be rendered in 3 different places, with some visual differences
props: { // Accepts root | child
store: { // `root` -> main view
type: Object, // `child` -> rendered inside MR or Commit View
required: true, viewType: {
}, type: String,
// Can be rendered in 3 different places, with some visual differences required: false,
// Accepts root | child default: 'root',
// `root` -> main view
// `child` -> rendered inside MR or Commit View
viewType: {
type: String,
required: false,
default: 'root',
},
endpoint: {
type: String,
required: true,
},
helpPagePath: {
type: String,
required: true,
},
emptyStateSvgPath: {
type: String,
required: true,
},
errorStateSvgPath: {
type: String,
required: true,
},
noPipelinesSvgPath: {
type: String,
required: true,
},
autoDevopsPath: {
type: String,
required: true,
},
hasGitlabCi: {
type: Boolean,
required: true,
},
canCreatePipeline: {
type: Boolean,
required: true,
},
ciLintPath: {
type: String,
required: false,
default: null,
},
resetCachePath: {
type: String,
required: false,
default: null,
},
newPipelinePath: {
type: String,
required: false,
default: null,
},
}, },
data() { endpoint: {
return { type: String,
// Start with loading state to avoid a glitch when the empty state will be rendered required: true,
isLoading: true,
state: this.store.state,
scope: getParameterByName('scope') || 'all',
page: getParameterByName('page') || '1',
requestData: {},
isResetCacheButtonLoading: false,
};
}, },
stateMap: { helpPagePath: {
// with tabs type: String,
loading: 'loading', required: true,
tableList: 'tableList', },
error: 'error', emptyStateSvgPath: {
emptyTab: 'emptyTab', type: String,
required: true,
// without tabs },
emptyState: 'emptyState', errorStateSvgPath: {
type: String,
required: true,
},
noPipelinesSvgPath: {
type: String,
required: true,
},
autoDevopsPath: {
type: String,
required: true,
},
hasGitlabCi: {
type: Boolean,
required: true,
},
canCreatePipeline: {
type: Boolean,
required: true,
},
ciLintPath: {
type: String,
required: false,
default: null,
}, },
scopes: { resetCachePath: {
all: 'all', type: String,
pending: 'pending', required: false,
running: 'running', default: null,
finished: 'finished',
branches: 'branches',
tags: 'tags',
}, },
computed: { newPipelinePath: {
/** type: String,
* `hasGitlabCi` handles both internal and external CI. required: false,
* The order on which the checks are made in this method is default: null,
* important to guarantee we handle all the corner cases. },
*/ },
stateToRender() { data() {
const { stateMap } = this.$options; return {
// Start with loading state to avoid a glitch when the empty state will be rendered
isLoading: true,
state: this.store.state,
scope: getParameterByName('scope') || 'all',
page: getParameterByName('page') || '1',
requestData: {},
isResetCacheButtonLoading: false,
};
},
stateMap: {
// with tabs
loading: 'loading',
tableList: 'tableList',
error: 'error',
emptyTab: 'emptyTab',
// without tabs
emptyState: 'emptyState',
},
scopes: {
all: 'all',
pending: 'pending',
running: 'running',
finished: 'finished',
branches: 'branches',
tags: 'tags',
},
computed: {
/**
* `hasGitlabCi` handles both internal and external CI.
* The order on which the checks are made in this method is
* important to guarantee we handle all the corner cases.
*/
stateToRender() {
const { stateMap } = this.$options;
if (this.isLoading) { if (this.isLoading) {
return stateMap.loading; return stateMap.loading;
} }
if (this.hasError) { if (this.hasError) {
return stateMap.error; return stateMap.error;
} }
if (this.state.pipelines.length) { if (this.state.pipelines.length) {
return stateMap.tableList; return stateMap.tableList;
} }
if ((this.scope !== 'all' && this.scope !== null) || this.hasGitlabCi) { if ((this.scope !== 'all' && this.scope !== null) || this.hasGitlabCi) {
return stateMap.emptyTab; return stateMap.emptyTab;
} }
return stateMap.emptyState; return stateMap.emptyState;
}, },
/** /**
* Tabs are rendered in all states except empty state. * Tabs are rendered in all states except empty state.
* They are not rendered before the first request to avoid a flicker on first load. * They are not rendered before the first request to avoid a flicker on first load.
*/ */
shouldRenderTabs() { shouldRenderTabs() {
const { stateMap } = this.$options; const { stateMap } = this.$options;
return ( return (
this.hasMadeRequest && this.hasMadeRequest &&
[stateMap.loading, stateMap.tableList, stateMap.error, stateMap.emptyTab].includes( [stateMap.loading, stateMap.tableList, stateMap.error, stateMap.emptyTab].includes(
this.stateToRender, this.stateToRender,
) )
); );
}, },
shouldRenderButtons() { shouldRenderButtons() {
return ( return (
(this.newPipelinePath || this.resetCachePath || this.ciLintPath) && this.shouldRenderTabs (this.newPipelinePath || this.resetCachePath || this.ciLintPath) && this.shouldRenderTabs
); );
}, },
shouldRenderPagination() { shouldRenderPagination() {
return ( return (
!this.isLoading && !this.isLoading &&
this.state.pipelines.length && this.state.pipelines.length &&
this.state.pageInfo.total > this.state.pageInfo.perPage this.state.pageInfo.total > this.state.pageInfo.perPage
); );
}, },
emptyTabMessage() { emptyTabMessage() {
const { scopes } = this.$options; const { scopes } = this.$options;
const possibleScopes = [scopes.pending, scopes.running, scopes.finished]; const possibleScopes = [scopes.pending, scopes.running, scopes.finished];
if (possibleScopes.includes(this.scope)) { if (possibleScopes.includes(this.scope)) {
return sprintf(s__('Pipelines|There are currently no %{scope} pipelines.'), { return sprintf(s__('Pipelines|There are currently no %{scope} pipelines.'), {
scope: this.scope, scope: this.scope,
}); });
} }
return s__('Pipelines|There are currently no pipelines.'); return s__('Pipelines|There are currently no pipelines.');
}, },
tabs() { tabs() {
const { count } = this.state; const { count } = this.state;
const { scopes } = this.$options; const { scopes } = this.$options;
return [ return [
{ {
name: __('All'), name: __('All'),
scope: scopes.all, scope: scopes.all,
count: count.all, count: count.all,
isActive: this.scope === 'all', isActive: this.scope === 'all',
}, },
{ {
name: __('Pending'), name: __('Pending'),
scope: scopes.pending, scope: scopes.pending,
count: count.pending, count: count.pending,
isActive: this.scope === 'pending', isActive: this.scope === 'pending',
}, },
{ {
name: __('Running'), name: __('Running'),
scope: scopes.running, scope: scopes.running,
count: count.running, count: count.running,
isActive: this.scope === 'running', isActive: this.scope === 'running',
}, },
{ {
name: __('Finished'), name: __('Finished'),
scope: scopes.finished, scope: scopes.finished,
count: count.finished, count: count.finished,
isActive: this.scope === 'finished', isActive: this.scope === 'finished',
}, },
{ {
name: __('Branches'), name: __('Branches'),
scope: scopes.branches, scope: scopes.branches,
isActive: this.scope === 'branches', isActive: this.scope === 'branches',
}, },
{ {
name: __('Tags'), name: __('Tags'),
scope: scopes.tags, scope: scopes.tags,
isActive: this.scope === 'tags', isActive: this.scope === 'tags',
}, },
]; ];
},
}, },
created() { },
this.service = new PipelinesService(this.endpoint); created() {
this.requestData = { page: this.page, scope: this.scope }; this.service = new PipelinesService(this.endpoint);
this.requestData = { page: this.page, scope: this.scope };
},
methods: {
successCallback(resp) {
// Because we are polling & the user is interacting verify if the response received
// matches the last request made
if (_.isEqual(resp.config.params, this.requestData)) {
this.store.storeCount(resp.data.count);
this.store.storePagination(resp.headers);
this.setCommonData(resp.data.pipelines);
}
}, },
methods: { /**
successCallback(resp) { * Handles URL and query parameter changes.
// Because we are polling & the user is interacting verify if the response received * When the user uses the pagination or the tabs,
// matches the last request made * - update URL
if (_.isEqual(resp.config.params, this.requestData)) { * - Make API request to the server with new parameters
this.store.storeCount(resp.data.count); * - Update the polling function
this.store.storePagination(resp.headers); * - Update the internal state
this.setCommonData(resp.data.pipelines); */
} updateContent(parameters) {
}, this.updateInternalState(parameters);
/**
* Handles URL and query parameter changes.
* When the user uses the pagination or the tabs,
* - update URL
* - Make API request to the server with new parameters
* - Update the polling function
* - Update the internal state
*/
updateContent(parameters) {
this.updateInternalState(parameters);
// fetch new data // fetch new data
return this.service return this.service
.getPipelines(this.requestData) .getPipelines(this.requestData)
.then(response => { .then(response => {
this.isLoading = false; this.isLoading = false;
this.successCallback(response); this.successCallback(response);
// restart polling // restart polling
this.poll.restart({ data: this.requestData }); this.poll.restart({ data: this.requestData });
}) })
.catch(() => { .catch(() => {
this.isLoading = false; this.isLoading = false;
this.errorCallback(); this.errorCallback();
// restart polling // restart polling
this.poll.restart({ data: this.requestData }); this.poll.restart({ data: this.requestData });
}); });
}, },
handleResetRunnersCache(endpoint) { handleResetRunnersCache(endpoint) {
this.isResetCacheButtonLoading = true; this.isResetCacheButtonLoading = true;
this.service this.service
.postAction(endpoint) .postAction(endpoint)
.then(() => { .then(() => {
this.isResetCacheButtonLoading = false; this.isResetCacheButtonLoading = false;
createFlash(s__('Pipelines|Project cache successfully reset.'), 'notice'); createFlash(s__('Pipelines|Project cache successfully reset.'), 'notice');
}) })
.catch(() => { .catch(() => {
this.isResetCacheButtonLoading = false; this.isResetCacheButtonLoading = false;
createFlash(s__('Pipelines|Something went wrong while cleaning runners cache.')); createFlash(s__('Pipelines|Something went wrong while cleaning runners cache.'));
}); });
},
}, },
}; },
};
</script> </script>
<template> <template>
<div class="pipelines-container"> <div class="pipelines-container">
......
<script> <script>
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import icon from '../../vue_shared/components/icon.vue'; import icon from '../../vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
directives: { directives: {
tooltip, tooltip,
},
components: {
loadingIcon,
icon,
},
props: {
actions: {
type: Array,
required: true,
}, },
components: { },
loadingIcon, data() {
icon, return {
}, isLoading: false,
props: { };
actions: { },
type: Array, methods: {
required: true, onClickAction(endpoint) {
}, this.isLoading = true;
},
data() {
return {
isLoading: false,
};
},
methods: {
onClickAction(endpoint) {
this.isLoading = true;
eventHub.$emit('postAction', endpoint); eventHub.$emit('postAction', endpoint);
}, },
isActionDisabled(action) { isActionDisabled(action) {
if (action.playable === undefined) { if (action.playable === undefined) {
return false; return false;
} }
return !action.playable; return !action.playable;
},
}, },
}; },
};
</script> </script>
<template> <template>
<div class="btn-group"> <div class="btn-group">
......
<script> <script>
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import icon from '../../vue_shared/components/icon.vue'; import icon from '../../vue_shared/components/icon.vue';
export default { export default {
directives: { directives: {
tooltip, tooltip,
},
components: {
icon,
},
props: {
artifacts: {
type: Array,
required: true,
}, },
components: { },
icon, };
},
props: {
artifacts: {
type: Array,
required: true,
},
},
};
</script> </script>
<template> <template>
<div <div
......
<script> <script>
import Modal from '~/vue_shared/components/gl_modal.vue'; import Modal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import PipelinesTableRowComponent from './pipelines_table_row.vue'; import PipelinesTableRowComponent from './pipelines_table_row.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
/** /**
* Pipelines Table Component. * Pipelines Table Component.
* *
* Given an array of objects, renders a table. * Given an array of objects, renders a table.
*/ */
export default { export default {
components: { components: {
PipelinesTableRowComponent, PipelinesTableRowComponent,
Modal, Modal,
},
props: {
pipelines: {
type: Array,
required: true,
}, },
props: { updateGraphDropdown: {
pipelines: { type: Boolean,
type: Array, required: false,
required: true, default: false,
},
updateGraphDropdown: {
type: Boolean,
required: false,
default: false,
},
autoDevopsHelpPath: {
type: String,
required: true,
},
viewType: {
type: String,
required: true,
},
}, },
data() { autoDevopsHelpPath: {
return { type: String,
pipelineId: '', required: true,
endpoint: '',
cancelingPipeline: null,
};
}, },
computed: { viewType: {
modalTitle() { type: String,
return sprintf(s__('Pipeline|Stop pipeline #%{pipelineId}?'), { required: true,
},
},
data() {
return {
pipelineId: '',
endpoint: '',
cancelingPipeline: null,
};
},
computed: {
modalTitle() {
return sprintf(
s__('Pipeline|Stop pipeline #%{pipelineId}?'),
{
pipelineId: `${this.pipelineId}`, pipelineId: `${this.pipelineId}`,
}, false); },
}, false,
modalText() { );
return sprintf(s__('Pipeline|You’re about to stop pipeline %{pipelineId}.'), {
pipelineId: `<strong>#${this.pipelineId}</strong>`,
}, false);
},
}, },
created() { modalText() {
eventHub.$on('openConfirmationModal', this.setModalData); return sprintf(
s__('Pipeline|You’re about to stop pipeline %{pipelineId}.'),
{
pipelineId: `<strong>#${this.pipelineId}</strong>`,
},
false,
);
}, },
beforeDestroy() { },
eventHub.$off('openConfirmationModal', this.setModalData); created() {
eventHub.$on('openConfirmationModal', this.setModalData);
},
beforeDestroy() {
eventHub.$off('openConfirmationModal', this.setModalData);
},
methods: {
setModalData(data) {
this.pipelineId = data.pipelineId;
this.endpoint = data.endpoint;
}, },
methods: { onSubmit() {
setModalData(data) { eventHub.$emit('postAction', this.endpoint);
this.pipelineId = data.pipelineId; this.cancelingPipeline = this.pipelineId;
this.endpoint = data.endpoint;
},
onSubmit() {
eventHub.$emit('postAction', this.endpoint);
this.cancelingPipeline = this.pipelineId;
},
}, },
}; },
};
</script> </script>
<template> <template>
<div class="ci-table"> <div class="ci-table">
......
<script> <script>
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import PipelinesActionsComponent from './pipelines_actions.vue'; import PipelinesActionsComponent from './pipelines_actions.vue';
import PipelinesArtifactsComponent from './pipelines_artifacts.vue'; import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
import CiBadge from '../../vue_shared/components/ci_badge_link.vue'; import CiBadge from '../../vue_shared/components/ci_badge_link.vue';
import PipelineStage from './stage.vue'; import PipelineStage from './stage.vue';
import PipelineUrl from './pipeline_url.vue'; import PipelineUrl from './pipeline_url.vue';
import PipelinesTimeago from './time_ago.vue'; import PipelinesTimeago from './time_ago.vue';
import CommitComponent from '../../vue_shared/components/commit.vue'; import CommitComponent from '../../vue_shared/components/commit.vue';
import LoadingButton from '../../vue_shared/components/loading_button.vue'; import LoadingButton from '../../vue_shared/components/loading_button.vue';
import Icon from '../../vue_shared/components/icon.vue'; import Icon from '../../vue_shared/components/icon.vue';
import { PIPELINES_TABLE } from '../constants'; import { PIPELINES_TABLE } from '../constants';
/** /**
* Pipeline table row. * Pipeline table row.
* *
* Given the received object renders a table row in the pipelines' table. * Given the received object renders a table row in the pipelines' table.
*/ */
export default { export default {
components: { components: {
PipelinesActionsComponent, PipelinesActionsComponent,
PipelinesArtifactsComponent, PipelinesArtifactsComponent,
CommitComponent, CommitComponent,
PipelineStage, PipelineStage,
PipelineUrl, PipelineUrl,
CiBadge, CiBadge,
PipelinesTimeago, PipelinesTimeago,
LoadingButton, LoadingButton,
Icon, Icon,
},
props: {
pipeline: {
type: Object,
required: true,
}, },
props: { updateGraphDropdown: {
pipeline: { type: Boolean,
type: Object, required: false,
required: true, default: false,
},
updateGraphDropdown: {
type: Boolean,
required: false,
default: false,
},
autoDevopsHelpPath: {
type: String,
required: true,
},
viewType: {
type: String,
required: true,
},
cancelingPipeline: {
type: String,
required: false,
default: null,
},
}, },
pipelinesTable: PIPELINES_TABLE, autoDevopsHelpPath: {
data() { type: String,
return { required: true,
isRetrying: false,
};
}, },
computed: { viewType: {
/** type: String,
* If provided, returns the commit tag. required: true,
* Needed to render the commit component column. },
* cancelingPipeline: {
* This field needs a lot of verification, because of different possible cases: type: String,
* required: false,
* 1. person who is an author of a commit might be a GitLab user default: null,
* 2. if person who is an author of a commit is a GitLab user he/she can have a GitLab avatar },
* 3. If GitLab user does not have avatar he/she might have a Gravatar },
* 4. If committer is not a GitLab User he/she can have a Gravatar pipelinesTable: PIPELINES_TABLE,
* 5. We do not have consistent API object in this case data() {
* 6. We should improve API and the code return {
* isRetrying: false,
* @returns {Object|Undefined} };
*/ },
commitAuthor() { computed: {
let commitAuthorInformation; /**
* If provided, returns the commit tag.
* Needed to render the commit component column.
*
* This field needs a lot of verification, because of different possible cases:
*
* 1. person who is an author of a commit might be a GitLab user
* 2. if person who is an author of a commit is a GitLab user he/she can have a GitLab avatar
* 3. If GitLab user does not have avatar he/she might have a Gravatar
* 4. If committer is not a GitLab User he/she can have a Gravatar
* 5. We do not have consistent API object in this case
* 6. We should improve API and the code
*
* @returns {Object|Undefined}
*/
commitAuthor() {
let commitAuthorInformation;
if (!this.pipeline || !this.pipeline.commit) { if (!this.pipeline || !this.pipeline.commit) {
return null; return null;
} }
// 1. person who is an author of a commit might be a GitLab user // 1. person who is an author of a commit might be a GitLab user
if (this.pipeline.commit.author) { if (this.pipeline.commit.author) {
// 2. if person who is an author of a commit is a GitLab user // 2. if person who is an author of a commit is a GitLab user
// he/she can have a GitLab avatar // he/she can have a GitLab avatar
if (this.pipeline.commit.author.avatar_url) { if (this.pipeline.commit.author.avatar_url) {
commitAuthorInformation = this.pipeline.commit.author; commitAuthorInformation = this.pipeline.commit.author;
// 3. If GitLab user does not have avatar he/she might have a Gravatar // 3. If GitLab user does not have avatar he/she might have a Gravatar
} else if (this.pipeline.commit.author_gravatar_url) { } else if (this.pipeline.commit.author_gravatar_url) {
commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, { commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, {
avatar_url: this.pipeline.commit.author_gravatar_url,
});
}
// 4. If committer is not a GitLab User he/she can have a Gravatar
} else {
commitAuthorInformation = {
avatar_url: this.pipeline.commit.author_gravatar_url, avatar_url: this.pipeline.commit.author_gravatar_url,
path: `mailto:${this.pipeline.commit.author_email}`, });
username: this.pipeline.commit.author_name,
};
} }
// 4. If committer is not a GitLab User he/she can have a Gravatar
} else {
commitAuthorInformation = {
avatar_url: this.pipeline.commit.author_gravatar_url,
path: `mailto:${this.pipeline.commit.author_email}`,
username: this.pipeline.commit.author_name,
};
}
return commitAuthorInformation; return commitAuthorInformation;
}, },
/** /**
* If provided, returns the commit tag. * If provided, returns the commit tag.
* Needed to render the commit component column. * Needed to render the commit component column.
* *
* @returns {String|Undefined} * @returns {String|Undefined}
*/ */
commitTag() { commitTag() {
if (this.pipeline.ref && if (this.pipeline.ref && this.pipeline.ref.tag) {
this.pipeline.ref.tag) { return this.pipeline.ref.tag;
return this.pipeline.ref.tag; }
} return undefined;
return undefined; },
},
/** /**
* If provided, returns the commit ref. * If provided, returns the commit ref.
* Needed to render the commit component column. * Needed to render the commit component column.
* *
* Matches `path` prop sent in the API to `ref_url` prop needed * Matches `path` prop sent in the API to `ref_url` prop needed
* in the commit component. * in the commit component.
* *
* @returns {Object|Undefined} * @returns {Object|Undefined}
*/ */
commitRef() { commitRef() {
if (this.pipeline.ref) { if (this.pipeline.ref) {
return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => { return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
if (prop === 'path') { if (prop === 'path') {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
accumulator.ref_url = this.pipeline.ref[prop]; accumulator.ref_url = this.pipeline.ref[prop];
} else { } else {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
accumulator[prop] = this.pipeline.ref[prop]; accumulator[prop] = this.pipeline.ref[prop];
} }
return accumulator; return accumulator;
}, {}); }, {});
} }
return undefined; return undefined;
}, },
/** /**
* If provided, returns the commit url. * If provided, returns the commit url.
* Needed to render the commit component column. * Needed to render the commit component column.
* *
* @returns {String|Undefined} * @returns {String|Undefined}
*/ */
commitUrl() { commitUrl() {
if (this.pipeline.commit && if (this.pipeline.commit && this.pipeline.commit.commit_path) {
this.pipeline.commit.commit_path) { return this.pipeline.commit.commit_path;
return this.pipeline.commit.commit_path; }
} return undefined;
return undefined; },
},
/** /**
* If provided, returns the commit short sha. * If provided, returns the commit short sha.
* Needed to render the commit component column. * Needed to render the commit component column.
* *
* @returns {String|Undefined} * @returns {String|Undefined}
*/ */
commitShortSha() { commitShortSha() {
if (this.pipeline.commit && if (this.pipeline.commit && this.pipeline.commit.short_id) {
this.pipeline.commit.short_id) { return this.pipeline.commit.short_id;
return this.pipeline.commit.short_id; }
} return undefined;
return undefined; },
},
/** /**
* If provided, returns the commit title. * If provided, returns the commit title.
* Needed to render the commit component column. * Needed to render the commit component column.
* *
* @returns {String|Undefined} * @returns {String|Undefined}
*/ */
commitTitle() { commitTitle() {
if (this.pipeline.commit && if (this.pipeline.commit && this.pipeline.commit.title) {
this.pipeline.commit.title) { return this.pipeline.commit.title;
return this.pipeline.commit.title; }
} return undefined;
return undefined; },
},
/** /**
* Timeago components expects a number * Timeago components expects a number
* *
* @return {type} description * @return {type} description
*/ */
pipelineDuration() { pipelineDuration() {
if (this.pipeline.details && this.pipeline.details.duration) { if (this.pipeline.details && this.pipeline.details.duration) {
return this.pipeline.details.duration; return this.pipeline.details.duration;
} }
return 0; return 0;
}, },
/** /**
* Timeago component expects a String. * Timeago component expects a String.
* *
* @return {String} * @return {String}
*/ */
pipelineFinishedAt() { pipelineFinishedAt() {
if (this.pipeline.details && this.pipeline.details.finished_at) { if (this.pipeline.details && this.pipeline.details.finished_at) {
return this.pipeline.details.finished_at; return this.pipeline.details.finished_at;
} }
return ''; return '';
}, },
pipelineStatus() { pipelineStatus() {
if (this.pipeline.details && this.pipeline.details.status) { if (this.pipeline.details && this.pipeline.details.status) {
return this.pipeline.details.status; return this.pipeline.details.status;
} }
return {}; return {};
}, },
displayPipelineActions() { displayPipelineActions() {
return this.pipeline.flags.retryable || return (
this.pipeline.flags.cancelable || this.pipeline.flags.retryable ||
this.pipeline.details.manual_actions.length || this.pipeline.flags.cancelable ||
this.pipeline.details.artifacts.length; this.pipeline.details.manual_actions.length ||
}, this.pipeline.details.artifacts.length
);
},
isChildView() { isChildView() {
return this.viewType === 'child'; return this.viewType === 'child';
}, },
isCancelling() { isCancelling() {
return this.cancelingPipeline === this.pipeline.id; return this.cancelingPipeline === this.pipeline.id;
},
}, },
},
methods: { methods: {
handleCancelClick() { handleCancelClick() {
eventHub.$emit('openConfirmationModal', { eventHub.$emit('openConfirmationModal', {
pipelineId: this.pipeline.id, pipelineId: this.pipeline.id,
endpoint: this.pipeline.cancel_path, endpoint: this.pipeline.cancel_path,
}); });
}, },
handleRetryClick() { handleRetryClick() {
this.isRetrying = true; this.isRetrying = true;
eventHub.$emit('retryPipeline', this.pipeline.retry_path); eventHub.$emit('retryPipeline', this.pipeline.retry_path);
},
}, },
}; },
};
</script> </script>
<template> <template>
<div class="commit gl-responsive-table-row"> <div class="commit gl-responsive-table-row">
......
<script> <script>
import iconTimerSvg from 'icons/_icon_timer.svg'; import iconTimerSvg from 'icons/_icon_timer.svg';
import '../../lib/utils/datetime_utility'; import '../../lib/utils/datetime_utility';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import timeagoMixin from '../../vue_shared/mixins/timeago'; import timeagoMixin from '../../vue_shared/mixins/timeago';
export default { export default {
directives: { directives: {
tooltip, tooltip,
},
mixins: [timeagoMixin],
props: {
finishedTime: {
type: String,
required: true,
}, },
mixins: [ duration: {
timeagoMixin, type: Number,
], required: true,
props: {
finishedTime: {
type: String,
required: true,
},
duration: {
type: Number,
required: true,
},
}, },
data() { },
return { data() {
iconTimerSvg, return {
}; iconTimerSvg,
};
},
computed: {
hasDuration() {
return this.duration > 0;
}, },
computed: { hasFinishedTime() {
hasDuration() { return this.finishedTime !== '';
return this.duration > 0; },
}, durationFormated() {
hasFinishedTime() { const date = new Date(this.duration * 1000);
return this.finishedTime !== '';
},
durationFormated() {
const date = new Date(this.duration * 1000);
let hh = date.getUTCHours(); let hh = date.getUTCHours();
let mm = date.getUTCMinutes(); let mm = date.getUTCMinutes();
let ss = date.getSeconds(); let ss = date.getSeconds();
// left pad // left pad
if (hh < 10) { if (hh < 10) {
hh = `0${hh}`; hh = `0${hh}`;
} }
if (mm < 10) { if (mm < 10) {
mm = `0${mm}`; mm = `0${mm}`;
} }
if (ss < 10) { if (ss < 10) {
ss = `0${ss}`; ss = `0${ss}`;
} }
return `${hh}:${mm}:${ss}`; return `${hh}:${mm}:${ss}`;
},
}, },
}; },
};
</script> </script>
<template> <template>
<div class="table-section section-15 pipelines-time-ago"> <div class="table-section section-15 pipelines-time-ago">
......
...@@ -75,8 +75,7 @@ export default { ...@@ -75,8 +75,7 @@ export default {
// Stop polling // Stop polling
this.poll.stop(); this.poll.stop();
// Update the table // Update the table
return this.getPipelines() return this.getPipelines().then(() => this.poll.restart());
.then(() => this.poll.restart());
}, },
fetchPipelines() { fetchPipelines() {
if (!this.isMakingRequest) { if (!this.isMakingRequest) {
...@@ -86,9 +85,10 @@ export default { ...@@ -86,9 +85,10 @@ export default {
} }
}, },
getPipelines() { getPipelines() {
return this.service.getPipelines(this.requestData) return this.service
.getPipelines(this.requestData)
.then(response => this.successCallback(response)) .then(response => this.successCallback(response))
.catch((error) => this.errorCallback(error)); .catch(error => this.errorCallback(error));
}, },
setCommonData(pipelines) { setCommonData(pipelines) {
this.store.storePipelines(pipelines); this.store.storePipelines(pipelines);
...@@ -118,7 +118,8 @@ export default { ...@@ -118,7 +118,8 @@ export default {
} }
}, },
postAction(endpoint) { postAction(endpoint) {
this.service.postAction(endpoint) this.service
.postAction(endpoint)
.then(() => this.fetchPipelines()) .then(() => this.fetchPipelines())
.catch(() => Flash(__('An error occurred while making the request.'))); .catch(() => Flash(__('An error occurred while making the request.')));
}, },
......
...@@ -31,7 +31,8 @@ export default () => { ...@@ -31,7 +31,8 @@ export default () => {
requestRefreshPipelineGraph() { requestRefreshPipelineGraph() {
// When an action is clicked // When an action is clicked
// (wether in the dropdown or in the main nodes, we refresh the big graph) // (wether in the dropdown or in the main nodes, we refresh the big graph)
this.mediator.refreshPipeline() this.mediator
.refreshPipeline()
.catch(() => Flash(__('An error occurred while making the request.'))); .catch(() => Flash(__('An error occurred while making the request.')));
}, },
}, },
......
...@@ -52,7 +52,8 @@ export default class pipelinesMediator { ...@@ -52,7 +52,8 @@ export default class pipelinesMediator {
refreshPipeline() { refreshPipeline() {
this.poll.stop(); this.poll.stop();
return this.service.getPipeline() return this.service
.getPipeline()
.then(response => this.successCallback(response)) .then(response => this.successCallback(response))
.catch(() => this.errorCallback()) .catch(() => this.errorCallback())
.finally(() => this.poll.restart()); .finally(() => this.poll.restart());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment