Merge branch 'ci-build-pipeline-header-vue' into 'master'
Creates CI Header component to use in pipelines and job details pages See merge request !11726
This commit is contained in:
commit
7911f1c087
|
@ -0,0 +1,122 @@
|
|||
<script>
|
||||
import ciIconBadge from './ci_badge_link.vue';
|
||||
import timeagoTooltip from './time_ago_tooltip.vue';
|
||||
import tooltipMixin from '../mixins/tooltip';
|
||||
import userAvatarLink from './user_avatar/user_avatar_link.vue';
|
||||
|
||||
/**
|
||||
* Renders header component for job and pipeline page based on UI mockups
|
||||
*
|
||||
* Used in:
|
||||
* - job show page
|
||||
* - pipeline show page
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
status: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
itemName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
itemId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
time: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
actions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
|
||||
mixins: [
|
||||
tooltipMixin,
|
||||
],
|
||||
|
||||
components: {
|
||||
ciIconBadge,
|
||||
timeagoTooltip,
|
||||
userAvatarLink,
|
||||
},
|
||||
|
||||
computed: {
|
||||
userAvatarAltText() {
|
||||
return `${this.user.name}'s avatar`;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClickAction(action) {
|
||||
this.$emit('postAction', action);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<header class="page-content-header top-area">
|
||||
<section class="header-main-content">
|
||||
|
||||
<ci-icon-badge :status="status" />
|
||||
|
||||
<strong>
|
||||
{{itemName}} #{{itemId}}
|
||||
</strong>
|
||||
|
||||
triggered
|
||||
|
||||
<timeago-tooltip :time="time" />
|
||||
|
||||
by
|
||||
|
||||
<user-avatar-link
|
||||
:link-href="user.web_url"
|
||||
:img-src="user.avatar_url"
|
||||
:img-alt="userAvatarAltText"
|
||||
:tooltip-text="user.name"
|
||||
:img-size="24"
|
||||
/>
|
||||
|
||||
<a
|
||||
:href="user.web_url"
|
||||
:title="user.email"
|
||||
class="js-user-link commit-committer-link"
|
||||
ref="tooltip">
|
||||
{{user.name}}
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="header-action-button nav-controls"
|
||||
v-if="actions.length">
|
||||
<template
|
||||
v-for="action in actions">
|
||||
<a
|
||||
v-if="action.type === 'link'"
|
||||
:href="action.path"
|
||||
:class="action.cssClass">
|
||||
{{action.label}}
|
||||
</a>
|
||||
|
||||
<button
|
||||
v-else="action.type === 'button'"
|
||||
@click="onClickAction(action)"
|
||||
:class="action.cssClass"
|
||||
type="button">
|
||||
{{action.label}}
|
||||
</button>
|
||||
|
||||
</template>
|
||||
</section>
|
||||
</header>
|
||||
</template>
|
|
@ -0,0 +1,58 @@
|
|||
<script>
|
||||
import tooltipMixin from '../mixins/tooltip';
|
||||
import timeagoMixin from '../mixins/timeago';
|
||||
import '../../lib/utils/datetime_utility';
|
||||
|
||||
/**
|
||||
* Port of ruby helper time_ago_with_tooltip
|
||||
*/
|
||||
|
||||
export default {
|
||||
props: {
|
||||
time: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
tooltipPlacement: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'top',
|
||||
},
|
||||
|
||||
shortFormat: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
cssClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
mixins: [
|
||||
tooltipMixin,
|
||||
timeagoMixin,
|
||||
],
|
||||
|
||||
computed: {
|
||||
timeagoCssClass() {
|
||||
return this.shortFormat ? 'js-short-timeago' : 'js-timeago';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<time
|
||||
:class="[timeagoCssClass, cssClass]"
|
||||
class="js-timeago js-timeago-render"
|
||||
:title="tooltipTitle(time)"
|
||||
:data-placement="tooltipPlacement"
|
||||
data-container="body"
|
||||
ref="tooltip">
|
||||
{{timeFormated(time)}}
|
||||
</time>
|
||||
</template>
|
|
@ -0,0 +1,18 @@
|
|||
import '../../lib/utils/datetime_utility';
|
||||
|
||||
/**
|
||||
* Mixin with time ago methods used in some vue components
|
||||
*/
|
||||
export default {
|
||||
methods: {
|
||||
timeFormated(time) {
|
||||
const timeago = gl.utils.getTimeago();
|
||||
|
||||
return timeago.format(time);
|
||||
},
|
||||
|
||||
tooltipTitle(time) {
|
||||
return gl.utils.formatDate(time);
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Creates CI Header component for Pipelines and Jobs details pages
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,82 @@
|
|||
import Vue from 'vue';
|
||||
import headerCi from '~/vue_shared/components/header_ci_component.vue';
|
||||
|
||||
describe('Header CI Component', () => {
|
||||
let HeaderCi;
|
||||
let vm;
|
||||
let props;
|
||||
|
||||
beforeEach(() => {
|
||||
HeaderCi = Vue.extend(headerCi);
|
||||
|
||||
props = {
|
||||
status: {
|
||||
group: 'failed',
|
||||
icon: 'ci-status-failed',
|
||||
label: 'failed',
|
||||
text: 'failed',
|
||||
details_path: 'path',
|
||||
},
|
||||
itemName: 'job',
|
||||
itemId: 123,
|
||||
time: '2017-05-08T14:57:39.781Z',
|
||||
user: {
|
||||
web_url: 'path',
|
||||
name: 'Foo',
|
||||
username: 'foobar',
|
||||
email: 'foo@bar.com',
|
||||
avatar_url: 'link',
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
label: 'Retry',
|
||||
path: 'path',
|
||||
type: 'button',
|
||||
cssClass: 'btn',
|
||||
},
|
||||
{
|
||||
label: 'Go',
|
||||
path: 'path',
|
||||
type: 'link',
|
||||
cssClass: 'link',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
vm = new HeaderCi({
|
||||
propsData: props,
|
||||
}).$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
it('should render status badge', () => {
|
||||
expect(vm.$el.querySelector('.ci-failed')).toBeDefined();
|
||||
expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined();
|
||||
expect(
|
||||
vm.$el.querySelector('.ci-failed').getAttribute('href'),
|
||||
).toEqual(props.status.details_path);
|
||||
});
|
||||
|
||||
it('should render item name and id', () => {
|
||||
expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123');
|
||||
});
|
||||
|
||||
it('should render timeago date', () => {
|
||||
expect(vm.$el.querySelector('time')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render user icon and name', () => {
|
||||
expect(vm.$el.querySelector('.js-user-link').textContent.trim()).toEqual(props.user.name);
|
||||
});
|
||||
|
||||
it('should render provided actions', () => {
|
||||
expect(vm.$el.querySelector('.btn').tagName).toEqual('BUTTON');
|
||||
expect(vm.$el.querySelector('.btn').textContent.trim()).toEqual(props.actions[0].label);
|
||||
expect(vm.$el.querySelector('.link').tagName).toEqual('A');
|
||||
expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label);
|
||||
expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,68 @@
|
|||
import Vue from 'vue';
|
||||
import timeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import '~/lib/utils/datetime_utility';
|
||||
|
||||
describe('Time ago with tooltip component', () => {
|
||||
let TimeagoTooltip;
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
TimeagoTooltip = Vue.extend(timeagoTooltip);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
it('should render timeago with a bootstrap tooltip', () => {
|
||||
vm = new TimeagoTooltip({
|
||||
propsData: {
|
||||
time: '2017-05-08T14:57:39.781Z',
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
expect(vm.$el.tagName).toEqual('TIME');
|
||||
expect(vm.$el.classList.contains('js-timeago')).toEqual(true);
|
||||
expect(
|
||||
vm.$el.getAttribute('data-original-title'),
|
||||
).toEqual(gl.utils.formatDate('2017-05-08T14:57:39.781Z'));
|
||||
expect(vm.$el.getAttribute('data-placement')).toEqual('top');
|
||||
|
||||
const timeago = gl.utils.getTimeago();
|
||||
|
||||
expect(vm.$el.textContent.trim()).toEqual(timeago.format('2017-05-08T14:57:39.781Z'));
|
||||
});
|
||||
|
||||
it('should render tooltip placed in bottom', () => {
|
||||
vm = new TimeagoTooltip({
|
||||
propsData: {
|
||||
time: '2017-05-08T14:57:39.781Z',
|
||||
tooltipPlacement: 'bottom',
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
expect(vm.$el.getAttribute('data-placement')).toEqual('bottom');
|
||||
});
|
||||
|
||||
it('should render short format class', () => {
|
||||
vm = new TimeagoTooltip({
|
||||
propsData: {
|
||||
time: '2017-05-08T14:57:39.781Z',
|
||||
shortFormat: true,
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
expect(vm.$el.classList.contains('js-short-timeago')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should render provided html class', () => {
|
||||
vm = new TimeagoTooltip({
|
||||
propsData: {
|
||||
time: '2017-05-08T14:57:39.781Z',
|
||||
cssClass: 'foo',
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
expect(vm.$el.classList.contains('foo')).toEqual(true);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue