Prettifies pipeline's javascript code

This commit is contained in:
Filipa Lacerda 2018-06-27 15:28:05 +01:00
parent 07de43a7e0
commit eb2de72c69
No known key found for this signature in database
GPG Key ID: 9CA3FDE4D1E2F1C8
16 changed files with 869 additions and 862 deletions

View File

@ -1,18 +1,18 @@
<script> <script>
export default { export default {
name: 'PipelinesSvgState', name: 'PipelinesSvgState',
props: { props: {
svgPath: { svgPath: {
type: String, type: String,
required: true, required: true,
},
message: {
type: String,
required: true,
},
}, },
};
message: {
type: String,
required: true,
},
},
};
</script> </script>
<template> <template>

View File

@ -1,21 +1,21 @@
<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">

View File

@ -41,7 +41,6 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
}, },
data() { data() {
return { return {
@ -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');

View File

@ -1,28 +1,28 @@
<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">

View File

@ -1,81 +1,81 @@
<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 { data() {
actions: this.getActions(), return {
}; actions: this.getActions(),
};
},
computed: {
status() {
return this.pipeline.details && this.pipeline.details.status;
},
shouldRenderContent() {
return !this.isLoading && Object.keys(this.pipeline).length;
},
},
watch: {
pipeline() {
this.actions = this.getActions();
},
},
methods: {
postAction(action) {
const index = this.actions.indexOf(action);
this.$set(this.actions[index], 'isLoading', true);
eventHub.$emit('headerPostAction', action);
}, },
computed: { getActions() {
status() { const actions = [];
return this.pipeline.details && this.pipeline.details.status;
}, if (this.pipeline.retry_path) {
shouldRenderContent() { actions.push({
return !this.isLoading && Object.keys(this.pipeline).length; label: 'Retry',
}, path: this.pipeline.retry_path,
cssClass: 'js-retry-button btn btn-inverted-secondary',
type: 'button',
isLoading: false,
});
}
if (this.pipeline.cancel_path) {
actions.push({
label: 'Cancel running',
path: this.pipeline.cancel_path,
cssClass: 'js-btn-cancel-pipeline btn btn-danger',
type: 'button',
isLoading: false,
});
}
return actions;
}, },
},
watch: { };
pipeline() {
this.actions = this.getActions();
},
},
methods: {
postAction(action) {
const index = this.actions.indexOf(action);
this.$set(this.actions[index], 'isLoading', true);
eventHub.$emit('headerPostAction', action);
},
getActions() {
const actions = [];
if (this.pipeline.retry_path) {
actions.push({
label: 'Retry',
path: this.pipeline.retry_path,
cssClass: 'js-retry-button btn btn-inverted-secondary',
type: 'button',
isLoading: false,
});
}
if (this.pipeline.cancel_path) {
actions.push({
label: 'Cancel running',
path: this.pipeline.cancel_path,
cssClass: 'js-btn-cancel-pipeline btn btn-danger',
type: 'button',
isLoading: false,
});
}
return actions;
},
},
};
</script> </script>
<template> <template>
<div class="pipeline-header-container"> <div class="pipeline-header-container">

View File

@ -1,42 +1,42 @@
<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: {
type: String,
required: false,
default: null,
},
isResetCacheButtonLoading: {
type: Boolean,
required: false,
default: false,
},
}, },
methods: {
onClickResetCache() { ciLintPath: {
this.$emit('resetRunnersCache', this.resetCachePath); type: String,
}, required: false,
default: null,
}, },
};
isResetCacheButtonLoading: {
type: Boolean,
required: false,
default: false,
},
},
methods: {
onClickResetCache() {
this.$emit('resetRunnersCache', this.resetCachePath);
},
},
};
</script> </script>
<template> <template>
<div class="nav-controls"> <div class="nav-controls">

View File

@ -1,49 +1,49 @@
<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">

View File

@ -1,283 +1,283 @@
<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',
emptyTab: 'emptyTab',
// without tabs
emptyState: 'emptyState',
}, },
scopes: { emptyStateSvgPath: {
all: 'all', type: String,
pending: 'pending', required: true,
running: 'running',
finished: 'finished',
branches: 'branches',
tags: 'tags',
}, },
computed: { errorStateSvgPath: {
/** type: String,
* `hasGitlabCi` handles both internal and external CI. required: true,
* 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) {
return stateMap.loading;
}
if (this.hasError) {
return stateMap.error;
}
if (this.state.pipelines.length) {
return stateMap.tableList;
}
if ((this.scope !== 'all' && this.scope !== null) || this.hasGitlabCi) {
return stateMap.emptyTab;
}
return stateMap.emptyState;
},
/**
* Tabs are rendered in all states except empty state.
* They are not rendered before the first request to avoid a flicker on first load.
*/
shouldRenderTabs() {
const { stateMap } = this.$options;
return (
this.hasMadeRequest &&
[stateMap.loading, stateMap.tableList, stateMap.error, stateMap.emptyTab].includes(
this.stateToRender,
)
);
},
shouldRenderButtons() {
return (
(this.newPipelinePath || this.resetCachePath || this.ciLintPath) && this.shouldRenderTabs
);
},
shouldRenderPagination() {
return (
!this.isLoading &&
this.state.pipelines.length &&
this.state.pageInfo.total > this.state.pageInfo.perPage
);
},
emptyTabMessage() {
const { scopes } = this.$options;
const possibleScopes = [scopes.pending, scopes.running, scopes.finished];
if (possibleScopes.includes(this.scope)) {
return sprintf(s__('Pipelines|There are currently no %{scope} pipelines.'), {
scope: this.scope,
});
}
return s__('Pipelines|There are currently no pipelines.');
},
tabs() {
const { count } = this.state;
const { scopes } = this.$options;
return [
{
name: __('All'),
scope: scopes.all,
count: count.all,
isActive: this.scope === 'all',
},
{
name: __('Pending'),
scope: scopes.pending,
count: count.pending,
isActive: this.scope === 'pending',
},
{
name: __('Running'),
scope: scopes.running,
count: count.running,
isActive: this.scope === 'running',
},
{
name: __('Finished'),
scope: scopes.finished,
count: count.finished,
isActive: this.scope === 'finished',
},
{
name: __('Branches'),
scope: scopes.branches,
isActive: this.scope === 'branches',
},
{
name: __('Tags'),
scope: scopes.tags,
isActive: this.scope === 'tags',
},
];
},
}, },
created() { noPipelinesSvgPath: {
this.service = new PipelinesService(this.endpoint); type: String,
this.requestData = { page: this.page, scope: this.scope }; required: true,
}, },
methods: { autoDevopsPath: {
successCallback(resp) { type: String,
// Because we are polling & the user is interacting verify if the response received required: true,
// 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);
}
},
/**
* 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
return this.service
.getPipelines(this.requestData)
.then(response => {
this.isLoading = false;
this.successCallback(response);
// restart polling
this.poll.restart({ data: this.requestData });
})
.catch(() => {
this.isLoading = false;
this.errorCallback();
// restart polling
this.poll.restart({ data: this.requestData });
});
},
handleResetRunnersCache(endpoint) {
this.isResetCacheButtonLoading = true;
this.service
.postAction(endpoint)
.then(() => {
this.isResetCacheButtonLoading = false;
createFlash(s__('Pipelines|Project cache successfully reset.'), 'notice');
})
.catch(() => {
this.isResetCacheButtonLoading = false;
createFlash(s__('Pipelines|Something went wrong while cleaning runners cache.'));
});
},
}, },
}; 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() {
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) {
return stateMap.loading;
}
if (this.hasError) {
return stateMap.error;
}
if (this.state.pipelines.length) {
return stateMap.tableList;
}
if ((this.scope !== 'all' && this.scope !== null) || this.hasGitlabCi) {
return stateMap.emptyTab;
}
return stateMap.emptyState;
},
/**
* Tabs are rendered in all states except empty state.
* They are not rendered before the first request to avoid a flicker on first load.
*/
shouldRenderTabs() {
const { stateMap } = this.$options;
return (
this.hasMadeRequest &&
[stateMap.loading, stateMap.tableList, stateMap.error, stateMap.emptyTab].includes(
this.stateToRender,
)
);
},
shouldRenderButtons() {
return (
(this.newPipelinePath || this.resetCachePath || this.ciLintPath) && this.shouldRenderTabs
);
},
shouldRenderPagination() {
return (
!this.isLoading &&
this.state.pipelines.length &&
this.state.pageInfo.total > this.state.pageInfo.perPage
);
},
emptyTabMessage() {
const { scopes } = this.$options;
const possibleScopes = [scopes.pending, scopes.running, scopes.finished];
if (possibleScopes.includes(this.scope)) {
return sprintf(s__('Pipelines|There are currently no %{scope} pipelines.'), {
scope: this.scope,
});
}
return s__('Pipelines|There are currently no pipelines.');
},
tabs() {
const { count } = this.state;
const { scopes } = this.$options;
return [
{
name: __('All'),
scope: scopes.all,
count: count.all,
isActive: this.scope === 'all',
},
{
name: __('Pending'),
scope: scopes.pending,
count: count.pending,
isActive: this.scope === 'pending',
},
{
name: __('Running'),
scope: scopes.running,
count: count.running,
isActive: this.scope === 'running',
},
{
name: __('Finished'),
scope: scopes.finished,
count: count.finished,
isActive: this.scope === 'finished',
},
{
name: __('Branches'),
scope: scopes.branches,
isActive: this.scope === 'branches',
},
{
name: __('Tags'),
scope: scopes.tags,
isActive: this.scope === 'tags',
},
];
},
},
created() {
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);
}
},
/**
* 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
return this.service
.getPipelines(this.requestData)
.then(response => {
this.isLoading = false;
this.successCallback(response);
// restart polling
this.poll.restart({ data: this.requestData });
})
.catch(() => {
this.isLoading = false;
this.errorCallback();
// restart polling
this.poll.restart({ data: this.requestData });
});
},
handleResetRunnersCache(endpoint) {
this.isResetCacheButtonLoading = true;
this.service
.postAction(endpoint)
.then(() => {
this.isResetCacheButtonLoading = false;
createFlash(s__('Pipelines|Project cache successfully reset.'), 'notice');
})
.catch(() => {
this.isResetCacheButtonLoading = false;
createFlash(s__('Pipelines|Something went wrong while cleaning runners cache.'));
});
},
},
};
</script> </script>
<template> <template>
<div class="pipelines-container"> <div class="pipelines-container">

View File

@ -1,44 +1,44 @@
<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) {
if (action.playable === undefined) {
return false;
}
return !action.playable;
},
}, },
};
isActionDisabled(action) {
if (action.playable === undefined) {
return false;
}
return !action.playable;
},
},
};
</script> </script>
<template> <template>
<div class="btn-group"> <div class="btn-group">

View File

@ -1,21 +1,21 @@
<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

View File

@ -1,74 +1,82 @@
<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|Youre about to stop pipeline %{pipelineId}.'), { },
modalText() {
return sprintf(
s__('Pipeline|Youre about to stop pipeline %{pipelineId}.'),
{
pipelineId: `<strong>#${this.pipelineId}</strong>`, pipelineId: `<strong>#${this.pipelineId}</strong>`,
}, false); },
}, false,
);
}, },
created() { },
eventHub.$on('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;
}, },
beforeDestroy() { onSubmit() {
eventHub.$off('openConfirmationModal', this.setModalData); eventHub.$emit('postAction', this.endpoint);
this.cancelingPipeline = this.pipelineId;
}, },
methods: { },
setModalData(data) { };
this.pipelineId = data.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">

View File

@ -1,255 +1,253 @@
<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.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitTag() {
if (this.pipeline.ref &&
this.pipeline.ref.tag) {
return this.pipeline.ref.tag;
}
return undefined;
},
/**
* If provided, returns the commit ref.
* Needed to render the commit component column.
*
* Matches `path` prop sent in the API to `ref_url` prop needed
* in the commit component.
*
* @returns {Object|Undefined}
*/
commitRef() {
if (this.pipeline.ref) {
return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
if (prop === 'path') {
// eslint-disable-next-line no-param-reassign
accumulator.ref_url = this.pipeline.ref[prop];
} else {
// eslint-disable-next-line no-param-reassign
accumulator[prop] = this.pipeline.ref[prop];
}
return accumulator;
}, {});
}
return undefined;
},
/**
* If provided, returns the commit url.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitUrl() {
if (this.pipeline.commit &&
this.pipeline.commit.commit_path) {
return this.pipeline.commit.commit_path;
}
return undefined;
},
/**
* If provided, returns the commit short sha.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitShortSha() {
if (this.pipeline.commit &&
this.pipeline.commit.short_id) {
return this.pipeline.commit.short_id;
}
return undefined;
},
/**
* If provided, returns the commit title.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitTitle() {
if (this.pipeline.commit &&
this.pipeline.commit.title) {
return this.pipeline.commit.title;
}
return undefined;
},
/**
* Timeago components expects a number
*
* @return {type} description
*/
pipelineDuration() {
if (this.pipeline.details && this.pipeline.details.duration) {
return this.pipeline.details.duration;
}
return 0;
},
/**
* Timeago component expects a String.
*
* @return {String}
*/
pipelineFinishedAt() {
if (this.pipeline.details && this.pipeline.details.finished_at) {
return this.pipeline.details.finished_at;
}
return '';
},
pipelineStatus() {
if (this.pipeline.details && this.pipeline.details.status) {
return this.pipeline.details.status;
}
return {};
},
displayPipelineActions() {
return this.pipeline.flags.retryable ||
this.pipeline.flags.cancelable ||
this.pipeline.details.manual_actions.length ||
this.pipeline.details.artifacts.length;
},
isChildView() {
return this.viewType === 'child';
},
isCancelling() {
return this.cancelingPipeline === this.pipeline.id;
},
}, },
methods: { /**
handleCancelClick() { * If provided, returns the commit tag.
eventHub.$emit('openConfirmationModal', { * Needed to render the commit component column.
pipelineId: this.pipeline.id, *
endpoint: this.pipeline.cancel_path, * @returns {String|Undefined}
}); */
}, commitTag() {
handleRetryClick() { if (this.pipeline.ref && this.pipeline.ref.tag) {
this.isRetrying = true; return this.pipeline.ref.tag;
eventHub.$emit('retryPipeline', this.pipeline.retry_path); }
}, return undefined;
}, },
};
/**
* If provided, returns the commit ref.
* Needed to render the commit component column.
*
* Matches `path` prop sent in the API to `ref_url` prop needed
* in the commit component.
*
* @returns {Object|Undefined}
*/
commitRef() {
if (this.pipeline.ref) {
return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
if (prop === 'path') {
// eslint-disable-next-line no-param-reassign
accumulator.ref_url = this.pipeline.ref[prop];
} else {
// eslint-disable-next-line no-param-reassign
accumulator[prop] = this.pipeline.ref[prop];
}
return accumulator;
}, {});
}
return undefined;
},
/**
* If provided, returns the commit url.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitUrl() {
if (this.pipeline.commit && this.pipeline.commit.commit_path) {
return this.pipeline.commit.commit_path;
}
return undefined;
},
/**
* If provided, returns the commit short sha.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitShortSha() {
if (this.pipeline.commit && this.pipeline.commit.short_id) {
return this.pipeline.commit.short_id;
}
return undefined;
},
/**
* If provided, returns the commit title.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitTitle() {
if (this.pipeline.commit && this.pipeline.commit.title) {
return this.pipeline.commit.title;
}
return undefined;
},
/**
* Timeago components expects a number
*
* @return {type} description
*/
pipelineDuration() {
if (this.pipeline.details && this.pipeline.details.duration) {
return this.pipeline.details.duration;
}
return 0;
},
/**
* Timeago component expects a String.
*
* @return {String}
*/
pipelineFinishedAt() {
if (this.pipeline.details && this.pipeline.details.finished_at) {
return this.pipeline.details.finished_at;
}
return '';
},
pipelineStatus() {
if (this.pipeline.details && this.pipeline.details.status) {
return this.pipeline.details.status;
}
return {};
},
displayPipelineActions() {
return (
this.pipeline.flags.retryable ||
this.pipeline.flags.cancelable ||
this.pipeline.details.manual_actions.length ||
this.pipeline.details.artifacts.length
);
},
isChildView() {
return this.viewType === 'child';
},
isCancelling() {
return this.cancelingPipeline === this.pipeline.id;
},
},
methods: {
handleCancelClick() {
eventHub.$emit('openConfirmationModal', {
pipelineId: this.pipeline.id,
endpoint: this.pipeline.cancel_path,
});
},
handleRetryClick() {
this.isRetrying = true;
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">

View File

@ -1,60 +1,58 @@
<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">

View File

@ -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 {
} }
}, },
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 {
} }
}, },
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.')));
}, },

View File

@ -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.')));
}, },
}, },

View File

@ -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());