Skip to content
Snippets Groups Projects
Commit 377c8c78 authored by Filipa Lacerda's avatar Filipa Lacerda Committed by Phil Hughes
Browse files

Stop redirecting the page in graph main actions

parent 0fff9db5
No related branches found
No related tags found
No related merge requests found
<script>
import tooltip from '../../../vue_shared/directives/tooltip';
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.
* TODO: Remove UJS from here and use an async request instead.
*/
export default {
components: {
icon,
},
import $ from 'jquery';
import tooltip from '../../../vue_shared/directives/tooltip';
import Icon from '../../../vue_shared/components/icon.vue';
import { dasherize } from '../../../lib/utils/text_utility';
import eventHub from '../../event_hub';
/**
* Renders either a cancel, retry or play icon pointing to the given path.
*/
export default {
components: {
Icon,
},
 
directives: {
tooltip,
},
directives: {
tooltip,
},
 
props: {
tooltipText: {
type: String,
required: true,
},
props: {
tooltipText: {
type: String,
required: true,
},
 
link: {
type: String,
required: true,
},
link: {
type: String,
required: true,
},
 
actionMethod: {
type: String,
required: true,
},
actionIcon: {
type: String,
required: true,
},
 
actionIcon: {
type: String,
required: true,
},
buttonDisabled: {
type: String,
required: false,
default: null,
},
},
computed: {
cssClass() {
const actionIconDash = dasherize(this.actionIcon);
return `${actionIconDash} js-icon-${actionIconDash}`;
},
isDisabled() {
return this.buttonDisabled === this.link;
},
},
 
computed: {
cssClass() {
const actionIconDash = dasherize(this.actionIcon);
return `${actionIconDash} js-icon-${actionIconDash}`;
},
methods: {
onClickAction() {
$(this.$el).tooltip('hide');
eventHub.$emit('graphAction', this.link);
},
};
},
};
</script>
<template>
<a
<button
type="button"
@click="onClickAction"
v-tooltip
:data-method="actionMethod"
:title="tooltipText"
:href="link"
class="ci-action-icon-container ci-action-icon-wrapper"
class="btn btn-blank btn-transparent ci-action-icon-container ci-action-icon-wrapper"
:class="cssClass"
data-container="body"
:disabled="isDisabled"
>
<icon :name="actionIcon" />
</a>
</button>
</template>
<script>
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import stageColumnComponent from './stage_column_component.vue';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import StageColumnComponent from './stage_column_component.vue';
 
export default {
components: {
stageColumnComponent,
loadingIcon,
},
export default {
components: {
StageColumnComponent,
LoadingIcon,
},
 
props: {
isLoading: {
type: Boolean,
required: true,
},
pipeline: {
type: Object,
required: true,
},
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;
},
computed: {
graph() {
return this.pipeline.details && this.pipeline.details.stages;
},
},
 
methods: {
capitalizeStageName(name) {
return name.charAt(0).toUpperCase() + name.slice(1);
},
methods: {
capitalizeStageName(name) {
return name.charAt(0).toUpperCase() + name.slice(1);
},
 
isFirstColumn(index) {
return index === 0;
},
isFirstColumn(index) {
return index === 0;
},
 
stageConnectorClass(index, stage) {
let className;
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';
}
// 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;
},
return className;
},
};
},
};
</script>
<template>
<div class="build-content middle-block js-pipeline-graph">
Loading
Loading
@@ -70,6 +75,7 @@
:key="stage.name"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"
:action-disabled="actionDisabled"
/>
</ul>
</div>
Loading
Loading
<script>
import actionComponent from './action_component.vue';
import dropdownActionComponent from './dropdown_action_component.vue';
import jobNameComponent from './job_name_component.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
/**
* Renders the badge for the pipeline graph and the job's dropdown.
*
* The following object should be provided as `job`:
*
* {
* "id": 4256,
* "name": "test",
* "status": {
* "icon": "icon_status_success",
* "text": "passed",
* "label": "passed",
* "group": "success",
* "tooltip": "passed",
* "details_path": "/root/ci-mock/builds/4256",
* "action": {
* "icon": "retry",
* "title": "Retry",
* "path": "/root/ci-mock/builds/4256/retry",
* "method": "post"
* }
* }
* }
*/
export default {
components: {
actionComponent,
dropdownActionComponent,
jobNameComponent,
import ActionComponent from './action_component.vue';
import DropdownActionComponent from './dropdown_action_component.vue';
import JobNameComponent from './job_name_component.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
/**
* Renders the badge for the pipeline graph and the job's dropdown.
*
* The following object should be provided as `job`:
*
* {
* "id": 4256,
* "name": "test",
* "status": {
* "icon": "icon_status_success",
* "text": "passed",
* "label": "passed",
* "group": "success",
* "tooltip": "passed",
* "details_path": "/root/ci-mock/builds/4256",
* "action": {
* "icon": "retry",
* "title": "Retry",
* "path": "/root/ci-mock/builds/4256/retry",
* "method": "post"
* }
* }
* }
*/
export default {
components: {
ActionComponent,
DropdownActionComponent,
JobNameComponent,
},
directives: {
tooltip,
},
props: {
job: {
type: Object,
required: true,
},
 
directives: {
tooltip,
cssClassJobName: {
type: String,
required: false,
default: '',
},
props: {
job: {
type: Object,
required: true,
},
cssClassJobName: {
type: String,
required: false,
default: '',
},
isDropdown: {
type: Boolean,
required: false,
default: false,
},
isDropdown: {
type: Boolean,
required: false,
default: false,
},
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(' ');
},
 
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;
},
/**
* 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>
<template>
<div class="ci-job-component">
Loading
Loading
@@ -132,7 +138,7 @@
:tooltip-text="status.action.title"
:link="status.action.path"
:action-icon="status.action.icon"
:action-method="status.action.method"
:button-disabled="actionDisabled"
/>
 
<dropdown-action-component
Loading
Loading
<script>
import jobComponent from './job_component.vue';
import dropdownJobComponent from './dropdown_job_component.vue';
import JobComponent from './job_component.vue';
import DropdownJobComponent from './dropdown_job_component.vue';
 
export default {
components: {
jobComponent,
dropdownJobComponent,
export default {
components: {
JobComponent,
DropdownJobComponent,
},
props: {
title: {
type: String,
required: true,
},
props: {
title: {
type: String,
required: true,
},
 
jobs: {
type: Array,
required: true,
},
jobs: {
type: Array,
required: true,
},
 
isFirstColumn: {
type: Boolean,
required: false,
default: false,
},
isFirstColumn: {
type: Boolean,
required: false,
default: false,
},
 
stageConnectorClass: {
type: String,
required: false,
default: '',
},
stageConnectorClass: {
type: String,
required: false,
default: '',
},
actionDisabled: {
type: String,
required: false,
default: null,
},
},
 
methods: {
firstJob(list) {
return list[0];
},
methods: {
firstJob(list) {
return list[0];
},
 
jobId(job) {
return `ci-badge-${job.name}`;
},
jobId(job) {
return `ci-badge-${job.name}`;
},
 
buildConnnectorClass(index) {
return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
},
buildConnnectorClass(index) {
return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
},
};
},
};
</script>
<template>
<li
Loading
Loading
@@ -69,6 +74,7 @@
v-if="job.size === 1"
:job="job"
css-class-job-name="build-content"
:action-disabled="actionDisabled"
/>
 
<dropdown-job-component
Loading
Loading
Loading
Loading
@@ -25,13 +25,36 @@ export default () => {
data() {
return {
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) {
return createElement('pipeline-graph', {
props: {
isLoading: this.mediator.state.isLoading,
pipeline: this.mediator.store.state.pipeline,
actionDisabled: this.actionDisabled,
},
});
},
Loading
Loading
Loading
Loading
@@ -52,8 +52,11 @@ export default class pipelinesMediator {
}
 
refreshPipeline() {
this.service.getPipeline()
this.poll.stop();
return this.service.getPipeline()
.then(response => this.successCallback(response))
.catch(() => this.errorCallback());
.catch(() => this.errorCallback())
.finally(() => this.poll.restart());
}
}
Loading
Loading
@@ -495,17 +495,17 @@
svg {
fill: $gl-text-color-secondary;
position: relative;
left: 5px;
top: 2px;
width: 18px;
height: 18px;
left: 1px;
top: -1px;
width: 16px;
height: 16px;
}
 
&.play {
svg {
width: #{$ci-action-icon-size - 8};
height: #{$ci-action-icon-size - 8};
left: 8px;
width: 16px;
height: 16px;
left: 3px;
}
}
}
Loading
Loading
---
title: Stop redirecting the page in pipeline main actions
merge_request:
author:
type: fixed
import Vue from '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', () => {
let component;
 
beforeEach((done) => {
const ActionComponent = Vue.extend(actionComponent);
component = new ActionComponent({
propsData: {
tooltipText: 'bar',
link: 'foo',
actionMethod: 'post',
actionIcon: 'cancel',
},
}).$mount();
component = mountComponent(ActionComponent, {
tooltipText: 'bar',
link: 'foo',
actionIcon: 'cancel',
});
 
Vue.nextTick(done);
});
 
it('should render a link', () => {
expect(component.$el.getAttribute('href')).toEqual('foo');
afterEach(() => {
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', () => {
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment