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

Pipeline show view real time header section

parent 68112433
No related branches found
No related tags found
No related merge requests found
Showing
with 284 additions and 59 deletions
Loading
Loading
@@ -118,7 +118,7 @@ export default Vue.component('pipelines-table', {
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
 
beforeDestroyed() {
beforeDestroy() {
eventHub.$off('refreshPipelines');
},
 
Loading
Loading
Loading
Loading
@@ -109,7 +109,7 @@ export default {
eventHub.$on('postAction', this.postAction);
},
 
beforeDestroyed() {
beforeDestroy() {
eventHub.$off('toggleFolder');
eventHub.$off('postAction');
},
Loading
Loading
<script>
import ciHeader from '../../vue_shared/components/header_ci_component.vue';
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
name: 'PipelineHeaderSection',
props: {
pipeline: {
type: Object,
required: true,
},
isLoading: {
type: Boolean,
required: true,
},
},
components: {
ciHeader,
loadingIcon,
},
data() {
return {
actions: this.getActions(),
};
},
computed: {
status() {
return this.pipeline.details && this.pipeline.details.status;
},
shouldRenderContent() {
return !this.isLoading && Object.keys(this.pipeline).length;
},
},
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;
},
},
watch: {
pipeline() {
this.actions = this.getActions();
},
},
};
</script>
<template>
<div class="pipeline-header-container">
<ci-header
v-if="shouldRenderContent"
:status="status"
item-name="Pipeline"
:item-id="pipeline.id"
:time="pipeline.created_at"
:user="pipeline.user"
:actions="actions"
@actionClicked="postAction"
/>
<loading-icon
v-else
size="2"/>
</div>
</template>
Loading
Loading
@@ -33,7 +33,7 @@ export default {
<user-avatar-link
v-if="user"
class="js-pipeline-url-user"
:link-href="pipeline.user.web_url"
:link-href="pipeline.user.path"
:img-src="pipeline.user.avatar_url"
:tooltip-text="pipeline.user.name"
/>
Loading
Loading
/* global Flash */
import Vue from 'vue';
import PipelinesMediator from './pipeline_details_mediatior';
import pipelineGraph from './components/graph/graph_component.vue';
import pipelineHeader from './components/header_component.vue';
import eventHub from './event_hub';
 
document.addEventListener('DOMContentLoaded', () => {
const dataset = document.querySelector('.js-pipeline-details-vue').dataset;
Loading
Loading
@@ -9,7 +13,8 @@ document.addEventListener('DOMContentLoaded', () => {
 
mediator.fetchPipeline();
 
const pipelineGraphApp = new Vue({
// eslint-disable-next-line
new Vue({
el: '#js-pipeline-graph-vue',
data() {
return {
Loading
Loading
@@ -29,5 +34,37 @@ document.addEventListener('DOMContentLoaded', () => {
},
});
 
return pipelineGraphApp;
// eslint-disable-next-line
new Vue({
el: '#js-pipeline-header-vue',
data() {
return {
mediator,
};
},
components: {
pipelineHeader,
},
created() {
eventHub.$on('headerPostAction', this.postAction);
},
beforeDestroy() {
eventHub.$off('headerPostAction', this.postAction);
},
methods: {
postAction(action) {
this.mediator.service.postAction(action.path)
.then(() => this.mediator.refreshPipeline())
.catch(() => new Flash('An error occurred while making the request.'));
},
},
render(createElement) {
return createElement('pipeline-header', {
props: {
isLoading: this.mediator.state.isLoading,
pipeline: this.mediator.store.state.pipeline,
},
});
},
});
});
Loading
Loading
@@ -26,6 +26,8 @@ export default class pipelinesMediator {
if (!Visibility.hidden()) {
this.state.isLoading = true;
this.poll.makeRequest();
} else {
this.refreshPipeline();
}
 
Visibility.change(() => {
Loading
Loading
@@ -48,4 +50,10 @@ export default class pipelinesMediator {
this.state.isLoading = false;
return new Flash('An error occurred while fetching the pipeline.');
}
refreshPipeline() {
this.service.getPipeline()
.then(response => this.successCallback(response))
.catch(() => this.errorCallback());
}
}
Loading
Loading
@@ -169,7 +169,7 @@ export default {
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
 
beforeDestroyed() {
beforeDestroy() {
eventHub.$off('refreshPipelines');
},
 
Loading
Loading
Loading
Loading
@@ -11,4 +11,9 @@ export default class PipelineService {
getPipeline() {
return this.pipeline.get();
}
// eslint-disable-next-line
postAction(endpoint) {
return Vue.http.post(`${endpoint}.json`);
}
}
Loading
Loading
@@ -33,8 +33,6 @@ export default class PipelinesService {
 
/**
* Post request for all pipelines actions.
* Endpoint content type needs to be:
* `Content-Type:application/x-www-form-urlencoded`
*
* @param {String} endpoint
* @return {Promise}
Loading
Loading
Loading
Loading
@@ -91,7 +91,7 @@ export default {
hasAuthor() {
return this.author &&
this.author.avatar_url &&
this.author.web_url &&
this.author.path &&
this.author.username;
},
 
Loading
Loading
@@ -140,7 +140,7 @@ export default {
<user-avatar-link
v-if="hasAuthor"
class="avatar-image-container"
:link-href="author.web_url"
:link-href="author.path"
:img-src="author.avatar_url"
:img-alt="userImageAltDescription"
:tooltip-text="author.username"
Loading
Loading
<script>
import ciIconBadge from './ci_badge_link.vue';
import loadingIcon from './loading_icon.vue';
import timeagoTooltip from './time_ago_tooltip.vue';
import tooltipMixin from '../mixins/tooltip';
import userAvatarLink from './user_avatar/user_avatar_link.vue';
import userAvatarImage from './user_avatar/user_avatar_image.vue';
 
/**
* Renders header component for job and pipeline page based on UI mockups
Loading
Loading
@@ -31,7 +32,8 @@ export default {
},
user: {
type: Object,
required: true,
required: false,
default: () => ({}),
},
actions: {
type: Array,
Loading
Loading
@@ -46,8 +48,9 @@ export default {
 
components: {
ciIconBadge,
loadingIcon,
timeagoTooltip,
userAvatarLink,
userAvatarImage,
},
 
computed: {
Loading
Loading
@@ -58,13 +61,13 @@ export default {
 
methods: {
onClickAction(action) {
this.$emit('postAction', action);
this.$emit('actionClicked', action);
},
},
};
</script>
<template>
<header class="page-content-header top-area">
<header class="page-content-header">
<section class="header-main-content">
 
<ci-icon-badge :status="status" />
Loading
Loading
@@ -79,21 +82,23 @@ export default {
 
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>
<template v-if="user">
<a
:href="user.path"
:title="user.email"
class="js-user-link commit-committer-link"
ref="tooltip">
<user-avatar-image
:img-src="user.avatar_url"
:img-alt="userAvatarAltText"
:tooltip-text="user.name"
:img-size="24"
/>
{{user.name}}
</a>
</template>
</section>
 
<section
Loading
Loading
@@ -111,11 +116,17 @@ export default {
<button
v-else="action.type === 'button'"
@click="onClickAction(action)"
:disabled="action.isLoading"
:class="action.cssClass"
type="button">
{{action.label}}
</button>
 
<i
v-show="action.isLoading"
class="fa fa-spin fa-spinner"
aria-hidden="true">
</i>
</button>
</template>
</section>
</header>
Loading
Loading
Loading
Loading
@@ -83,7 +83,7 @@ export default {
} else {
commitAuthorInformation = {
avatar_url: this.pipeline.commit.author_gravatar_url,
web_url: `mailto:${this.pipeline.commit.author_email}`,
path: `mailto:${this.pipeline.commit.author_email}`,
username: this.pipeline.commit.author_name,
};
}
Loading
Loading
Loading
Loading
@@ -60,6 +60,12 @@ export default {
avatarSizeClass() {
return `s${this.size}`;
},
// API response sends null when gravatar is disabled and
// we provide an empty string when we use it inside user avatar link.
// In both cases we should render the defaultAvatarUrl
imageSource() {
return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
},
},
};
</script>
Loading
Loading
@@ -68,7 +74,7 @@ export default {
<img
class="avatar"
:class="[avatarSizeClass, cssClasses]"
:src="imgSrc"
:src="imageSource"
:width="size"
:height="size"
:alt="imgAlt"
Loading
Loading
Loading
Loading
@@ -984,3 +984,11 @@
width: 12px;
}
}
.pipeline-header-container {
min-height: 55px;
.text-center {
padding-top: 12px;
}
}
class UserEntity < API::Entities::UserBasic
include RequestAwareEntity
expose :path do |user|
user_path(user)
end
end
.page-content-header
.header-main-content
= render 'ci/status/badge', status: @pipeline.detailed_status(current_user), title: @pipeline.status_title
%strong Pipeline ##{@pipeline.id}
triggered #{time_ago_with_tooltip(@pipeline.created_at)}
- if @pipeline.user
by
= user_avatar(user: @pipeline.user, size: 24)
= user_link(@pipeline.user)
.header-action-buttons
- if can?(current_user, :update_pipeline, @pipeline.project)
- if @pipeline.retryable?
= link_to "Retry", retry_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'js-retry-button btn btn-inverted-secondary', method: :post
- if @pipeline.cancelable?
= link_to "Cancel running", cancel_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
#js-pipeline-header-vue.pipeline-header-container
 
- if @commit
.commit-box
Loading
Loading
---
title: Makes header information of pipeline show page realtine
merge_request:
author:
Loading
Loading
@@ -76,7 +76,7 @@ describe 'Commits' do
end
end
 
describe 'Commit builds' do
describe 'Commit builds', :feature, :js do
before do
visit ci_status_path(pipeline)
end
Loading
Loading
@@ -85,7 +85,6 @@ describe 'Commits' do
expect(page).to have_content pipeline.sha[0..7]
expect(page).to have_content pipeline.git_commit_message
expect(page).to have_content pipeline.user.name
expect(page).to have_content pipeline.created_at.strftime('%b %d, %Y')
end
end
 
Loading
Loading
@@ -102,7 +101,7 @@ describe 'Commits' do
end
 
describe 'Cancel all builds' do
it 'cancels commit' do
it 'cancels commit', :js do
visit ci_status_path(pipeline)
click_on 'Cancel running'
expect(page).to have_content 'canceled'
Loading
Loading
@@ -110,9 +109,9 @@ describe 'Commits' do
end
 
describe 'Cancel build' do
it 'cancels build' do
it 'cancels build', :js do
visit ci_status_path(pipeline)
find('a.btn[title="Cancel"]').click
find('.js-btn-cancel-pipeline').click
expect(page).to have_content 'canceled'
end
end
Loading
Loading
@@ -152,17 +151,20 @@ describe 'Commits' do
visit ci_status_path(pipeline)
end
 
it do
it 'Renders header', :feature, :js do
expect(page).to have_content pipeline.sha[0..7]
expect(page).to have_content pipeline.git_commit_message
expect(page).to have_content pipeline.user.name
expect(page).to have_link('Download artifacts')
expect(page).not_to have_link('Cancel running')
expect(page).not_to have_link('Retry')
end
it do
expect(page).to have_link('Download artifacts')
end
end
 
context 'when accessing internal project with disallowed access' do
context 'when accessing internal project with disallowed access', :feature, :js do
before do
project.update(
visibility_level: Gitlab::VisibilityLevel::INTERNAL,
Loading
Loading
@@ -175,7 +177,7 @@ describe 'Commits' do
expect(page).to have_content pipeline.sha[0..7]
expect(page).to have_content pipeline.git_commit_message
expect(page).to have_content pipeline.user.name
expect(page).not_to have_link('Download artifacts')
expect(page).not_to have_link('Cancel running')
expect(page).not_to have_link('Retry')
end
Loading
Loading
Loading
Loading
@@ -229,7 +229,6 @@ describe 'Pipeline', :feature, :js do
before { find('.js-retry-button').trigger('click') }
 
it { expect(page).not_to have_content('Retry') }
it { expect(page).to have_selector('.retried') }
end
end
 
Loading
Loading
@@ -240,7 +239,6 @@ describe 'Pipeline', :feature, :js do
before { click_on 'Cancel running' }
 
it { expect(page).not_to have_content('Cancel running') }
it { expect(page).to have_selector('.ci-canceled') }
end
end
 
Loading
Loading
import Vue from 'vue';
import headerComponent from '~/pipelines/components/header_component.vue';
import eventHub from '~/pipelines/event_hub';
describe('Pipeline details header', () => {
let HeaderComponent;
let vm;
let props;
beforeEach(() => {
HeaderComponent = Vue.extend(headerComponent);
props = {
pipeline: {
details: {
status: {
group: 'failed',
icon: 'ci-status-failed',
label: 'failed',
text: 'failed',
details_path: 'path',
},
},
id: 123,
created_at: '2017-05-08T14:57:39.781Z',
user: {
web_url: 'path',
name: 'Foo',
username: 'foobar',
email: 'foo@bar.com',
avatar_url: 'link',
},
retry_path: 'path',
},
isLoading: false,
};
vm = new HeaderComponent({ propsData: props }).$mount();
});
afterEach(() => {
vm.$destroy();
});
it('should render provided pipeline info', () => {
expect(
vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
).toEqual('failed Pipeline #123 triggered 3 weeks ago by Foo');
});
describe('action buttons', () => {
it('should call postAction when button action is clicked', () => {
eventHub.$on('headerPostAction', (action) => {
expect(action.path).toEqual('path');
});
vm.$el.querySelector('button').click();
});
});
});
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