Stop redirecting the page in graph main actions
This commit is contained in:
parent
0fff9db5ea
commit
377c8c7846
|
@ -1,60 +1,72 @@
|
||||||
<script>
|
<script>
|
||||||
import tooltip from '../../../vue_shared/directives/tooltip';
|
import $ from 'jquery';
|
||||||
import icon from '../../../vue_shared/components/icon.vue';
|
import tooltip from '../../../vue_shared/directives/tooltip';
|
||||||
import { dasherize } from '../../../lib/utils/text_utility';
|
import Icon from '../../../vue_shared/components/icon.vue';
|
||||||
/**
|
import { dasherize } from '../../../lib/utils/text_utility';
|
||||||
* Renders either a cancel, retry or play icon pointing to the given path.
|
import eventHub from '../../event_hub';
|
||||||
* TODO: Remove UJS from here and use an async request instead.
|
/**
|
||||||
*/
|
* Renders either a cancel, retry or play icon pointing to the given path.
|
||||||
export default {
|
*/
|
||||||
components: {
|
export default {
|
||||||
icon,
|
components: {
|
||||||
|
Icon,
|
||||||
|
},
|
||||||
|
|
||||||
|
directives: {
|
||||||
|
tooltip,
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
tooltipText: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
directives: {
|
link: {
|
||||||
tooltip,
|
type: String,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
actionIcon: {
|
||||||
tooltipText: {
|
type: String,
|
||||||
type: String,
|
required: true,
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
link: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
actionMethod: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
actionIcon: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
buttonDisabled: {
|
||||||
cssClass() {
|
type: String,
|
||||||
const actionIconDash = dasherize(this.actionIcon);
|
required: false,
|
||||||
return `${actionIconDash} js-icon-${actionIconDash}`;
|
default: null,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
|
computed: {
|
||||||
|
cssClass() {
|
||||||
|
const actionIconDash = dasherize(this.actionIcon);
|
||||||
|
return `${actionIconDash} js-icon-${actionIconDash}`;
|
||||||
|
},
|
||||||
|
isDisabled() {
|
||||||
|
return this.buttonDisabled === this.link;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onClickAction() {
|
||||||
|
$(this.$el).tooltip('hide');
|
||||||
|
eventHub.$emit('graphAction', this.link);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<a
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="onClickAction"
|
||||||
v-tooltip
|
v-tooltip
|
||||||
:data-method="actionMethod"
|
|
||||||
:title="tooltipText"
|
:title="tooltipText"
|
||||||
:href="link"
|
class="btn btn-blank btn-transparent ci-action-icon-container ci-action-icon-wrapper"
|
||||||
class="ci-action-icon-container ci-action-icon-wrapper"
|
|
||||||
:class="cssClass"
|
:class="cssClass"
|
||||||
data-container="body"
|
data-container="body"
|
||||||
|
:disabled="isDisabled"
|
||||||
>
|
>
|
||||||
<icon :name="actionIcon" />
|
<icon :name="actionIcon" />
|
||||||
</a>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,54 +1,59 @@
|
||||||
<script>
|
<script>
|
||||||
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
|
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
|
||||||
import stageColumnComponent from './stage_column_component.vue';
|
import StageColumnComponent from './stage_column_component.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
stageColumnComponent,
|
StageColumnComponent,
|
||||||
loadingIcon,
|
LoadingIcon,
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
isLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
pipeline: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
actionDisabled: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
graph() {
|
||||||
|
return this.pipeline.details && this.pipeline.details.stages;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
capitalizeStageName(name) {
|
||||||
|
return name.charAt(0).toUpperCase() + name.slice(1);
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
isFirstColumn(index) {
|
||||||
isLoading: {
|
return index === 0;
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
pipeline: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
stageConnectorClass(index, stage) {
|
||||||
graph() {
|
let className;
|
||||||
return this.pipeline.details && this.pipeline.details.stages;
|
|
||||||
},
|
// If it's the first stage column and only has one job
|
||||||
|
if (index === 0 && stage.groups.length === 1) {
|
||||||
|
className = 'no-margin';
|
||||||
|
} else if (index > 0) {
|
||||||
|
// If it is not the first column
|
||||||
|
className = 'left-margin';
|
||||||
|
}
|
||||||
|
|
||||||
|
return className;
|
||||||
},
|
},
|
||||||
|
},
|
||||||
methods: {
|
};
|
||||||
capitalizeStageName(name) {
|
|
||||||
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
||||||
},
|
|
||||||
|
|
||||||
isFirstColumn(index) {
|
|
||||||
return index === 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
stageConnectorClass(index, stage) {
|
|
||||||
let className;
|
|
||||||
|
|
||||||
// If it's the first stage column and only has one job
|
|
||||||
if (index === 0 && stage.groups.length === 1) {
|
|
||||||
className = 'no-margin';
|
|
||||||
} else if (index > 0) {
|
|
||||||
// If it is not the first column
|
|
||||||
className = 'left-margin';
|
|
||||||
}
|
|
||||||
|
|
||||||
return className;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="build-content middle-block js-pipeline-graph">
|
<div class="build-content middle-block js-pipeline-graph">
|
||||||
|
@ -70,6 +75,7 @@
|
||||||
:key="stage.name"
|
:key="stage.name"
|
||||||
:stage-connector-class="stageConnectorClass(index, stage)"
|
:stage-connector-class="stageConnectorClass(index, stage)"
|
||||||
:is-first-column="isFirstColumn(index)"
|
:is-first-column="isFirstColumn(index)"
|
||||||
|
:action-disabled="actionDisabled"
|
||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,96 +1,102 @@
|
||||||
<script>
|
<script>
|
||||||
import actionComponent from './action_component.vue';
|
import ActionComponent from './action_component.vue';
|
||||||
import dropdownActionComponent from './dropdown_action_component.vue';
|
import DropdownActionComponent from './dropdown_action_component.vue';
|
||||||
import jobNameComponent from './job_name_component.vue';
|
import JobNameComponent from './job_name_component.vue';
|
||||||
import tooltip from '../../../vue_shared/directives/tooltip';
|
import tooltip from '../../../vue_shared/directives/tooltip';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the badge for the pipeline graph and the job's dropdown.
|
* Renders the badge for the pipeline graph and the job's dropdown.
|
||||||
*
|
*
|
||||||
* The following object should be provided as `job`:
|
* The following object should be provided as `job`:
|
||||||
*
|
*
|
||||||
* {
|
* {
|
||||||
* "id": 4256,
|
* "id": 4256,
|
||||||
* "name": "test",
|
* "name": "test",
|
||||||
* "status": {
|
* "status": {
|
||||||
* "icon": "icon_status_success",
|
* "icon": "icon_status_success",
|
||||||
* "text": "passed",
|
* "text": "passed",
|
||||||
* "label": "passed",
|
* "label": "passed",
|
||||||
* "group": "success",
|
* "group": "success",
|
||||||
* "tooltip": "passed",
|
* "tooltip": "passed",
|
||||||
* "details_path": "/root/ci-mock/builds/4256",
|
* "details_path": "/root/ci-mock/builds/4256",
|
||||||
* "action": {
|
* "action": {
|
||||||
* "icon": "retry",
|
* "icon": "retry",
|
||||||
* "title": "Retry",
|
* "title": "Retry",
|
||||||
* "path": "/root/ci-mock/builds/4256/retry",
|
* "path": "/root/ci-mock/builds/4256/retry",
|
||||||
* "method": "post"
|
* "method": "post"
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
actionComponent,
|
ActionComponent,
|
||||||
dropdownActionComponent,
|
DropdownActionComponent,
|
||||||
jobNameComponent,
|
JobNameComponent,
|
||||||
|
},
|
||||||
|
|
||||||
|
directives: {
|
||||||
|
tooltip,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
job: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
directives: {
|
cssClassJobName: {
|
||||||
tooltip,
|
type: String,
|
||||||
},
|
required: false,
|
||||||
props: {
|
default: '',
|
||||||
job: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
cssClassJobName: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
|
|
||||||
isDropdown: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
isDropdown: {
|
||||||
status() {
|
type: Boolean,
|
||||||
return this.job && this.job.status ? this.job.status : {};
|
required: false,
|
||||||
},
|
default: false,
|
||||||
|
|
||||||
tooltipText() {
|
|
||||||
const textBuilder = [];
|
|
||||||
|
|
||||||
if (this.job.name) {
|
|
||||||
textBuilder.push(this.job.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.job.name && this.status.tooltip) {
|
|
||||||
textBuilder.push('-');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.status.tooltip) {
|
|
||||||
textBuilder.push(`${this.job.status.tooltip}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return textBuilder.join(' ');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies if the provided job has an action path
|
|
||||||
*
|
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
|
||||||
hasAction() {
|
|
||||||
return this.job.status && this.job.status.action && this.job.status.action.path;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
|
||||||
|
actionDisabled: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
status() {
|
||||||
|
return this.job && this.job.status ? this.job.status : {};
|
||||||
|
},
|
||||||
|
|
||||||
|
tooltipText() {
|
||||||
|
const textBuilder = [];
|
||||||
|
|
||||||
|
if (this.job.name) {
|
||||||
|
textBuilder.push(this.job.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.job.name && this.status.tooltip) {
|
||||||
|
textBuilder.push('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.status.tooltip) {
|
||||||
|
textBuilder.push(`${this.job.status.tooltip}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return textBuilder.join(' ');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies if the provided job has an action path
|
||||||
|
*
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
hasAction() {
|
||||||
|
return this.job.status && this.job.status.action && this.job.status.action.path;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="ci-job-component">
|
<div class="ci-job-component">
|
||||||
|
@ -132,7 +138,7 @@
|
||||||
:tooltip-text="status.action.title"
|
:tooltip-text="status.action.title"
|
||||||
:link="status.action.path"
|
:link="status.action.path"
|
||||||
:action-icon="status.action.icon"
|
:action-icon="status.action.icon"
|
||||||
:action-method="status.action.method"
|
:button-disabled="actionDisabled"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<dropdown-action-component
|
<dropdown-action-component
|
||||||
|
|
|
@ -1,50 +1,55 @@
|
||||||
<script>
|
<script>
|
||||||
import jobComponent from './job_component.vue';
|
import JobComponent from './job_component.vue';
|
||||||
import dropdownJobComponent from './dropdown_job_component.vue';
|
import DropdownJobComponent from './dropdown_job_component.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
jobComponent,
|
JobComponent,
|
||||||
dropdownJobComponent,
|
DropdownJobComponent,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
|
||||||
|
|
||||||
jobs: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
isFirstColumn: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
stageConnectorClass: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
jobs: {
|
||||||
firstJob(list) {
|
type: Array,
|
||||||
return list[0];
|
required: true,
|
||||||
},
|
|
||||||
|
|
||||||
jobId(job) {
|
|
||||||
return `ci-badge-${job.name}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
buildConnnectorClass(index) {
|
|
||||||
return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
|
||||||
|
isFirstColumn: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
stageConnectorClass: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
actionDisabled: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
firstJob(list) {
|
||||||
|
return list[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
jobId(job) {
|
||||||
|
return `ci-badge-${job.name}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
buildConnnectorClass(index) {
|
||||||
|
return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<li
|
<li
|
||||||
|
@ -69,6 +74,7 @@
|
||||||
v-if="job.size === 1"
|
v-if="job.size === 1"
|
||||||
:job="job"
|
:job="job"
|
||||||
css-class-job-name="build-content"
|
css-class-job-name="build-content"
|
||||||
|
:action-disabled="actionDisabled"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<dropdown-job-component
|
<dropdown-job-component
|
||||||
|
|
|
@ -25,13 +25,36 @@ export default () => {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
mediator,
|
mediator,
|
||||||
|
actionDisabled: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
eventHub.$on('graphAction', this.postAction);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
eventHub.$off('graphAction', this.postAction);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
postAction(action) {
|
||||||
|
this.actionDisabled = action;
|
||||||
|
|
||||||
|
this.mediator.service.postAction(action)
|
||||||
|
.then(() => {
|
||||||
|
this.mediator.refreshPipeline();
|
||||||
|
this.actionDisabled = null;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.actionDisabled = null;
|
||||||
|
Flash(__('An error occurred while making the request.'));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
render(createElement) {
|
render(createElement) {
|
||||||
return createElement('pipeline-graph', {
|
return createElement('pipeline-graph', {
|
||||||
props: {
|
props: {
|
||||||
isLoading: this.mediator.state.isLoading,
|
isLoading: this.mediator.state.isLoading,
|
||||||
pipeline: this.mediator.store.state.pipeline,
|
pipeline: this.mediator.store.state.pipeline,
|
||||||
|
actionDisabled: this.actionDisabled,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -52,8 +52,11 @@ export default class pipelinesMediator {
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshPipeline() {
|
refreshPipeline() {
|
||||||
this.service.getPipeline()
|
this.poll.stop();
|
||||||
|
|
||||||
|
return this.service.getPipeline()
|
||||||
.then(response => this.successCallback(response))
|
.then(response => this.successCallback(response))
|
||||||
.catch(() => this.errorCallback());
|
.catch(() => this.errorCallback())
|
||||||
|
.finally(() => this.poll.restart());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -495,17 +495,17 @@
|
||||||
svg {
|
svg {
|
||||||
fill: $gl-text-color-secondary;
|
fill: $gl-text-color-secondary;
|
||||||
position: relative;
|
position: relative;
|
||||||
left: 5px;
|
left: 1px;
|
||||||
top: 2px;
|
top: -1px;
|
||||||
width: 18px;
|
width: 16px;
|
||||||
height: 18px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.play {
|
&.play {
|
||||||
svg {
|
svg {
|
||||||
width: #{$ci-action-icon-size - 8};
|
width: 16px;
|
||||||
height: #{$ci-action-icon-size - 8};
|
height: 16px;
|
||||||
left: 8px;
|
left: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Stop redirecting the page in pipeline main actions
|
||||||
|
merge_request:
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -1,25 +1,30 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import actionComponent from '~/pipelines/components/graph/action_component.vue';
|
import actionComponent from '~/pipelines/components/graph/action_component.vue';
|
||||||
|
import eventHub from '~/pipelines/event_hub';
|
||||||
|
import mountComponent from '../../helpers/vue_mount_component_helper';
|
||||||
|
|
||||||
describe('pipeline graph action component', () => {
|
describe('pipeline graph action component', () => {
|
||||||
let component;
|
let component;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
const ActionComponent = Vue.extend(actionComponent);
|
const ActionComponent = Vue.extend(actionComponent);
|
||||||
component = new ActionComponent({
|
component = mountComponent(ActionComponent, {
|
||||||
propsData: {
|
tooltipText: 'bar',
|
||||||
tooltipText: 'bar',
|
link: 'foo',
|
||||||
link: 'foo',
|
actionIcon: 'cancel',
|
||||||
actionMethod: 'post',
|
});
|
||||||
actionIcon: 'cancel',
|
|
||||||
},
|
|
||||||
}).$mount();
|
|
||||||
|
|
||||||
Vue.nextTick(done);
|
Vue.nextTick(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render a link', () => {
|
afterEach(() => {
|
||||||
expect(component.$el.getAttribute('href')).toEqual('foo');
|
component.$destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit an event with the provided link', () => {
|
||||||
|
eventHub.$on('graphAction', (link) => {
|
||||||
|
expect(link).toEqual('foo');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render the provided title as a bootstrap tooltip', () => {
|
it('should render the provided title as a bootstrap tooltip', () => {
|
||||||
|
|
Loading…
Reference in New Issue