Commit a60020e4 authored by Phil Hughes's avatar Phil Hughes

Merge branch '31849-pipeline-show-view-realtime' into 'master'

Creates a mediator for pipeline details vue in order to mount several vue apps with the same data

Closes #31849

See merge request !11732
parents 1d8de967 c7e6eff4
<script>
/* global Flash */
import Visibility from 'visibilityjs';
import Poll from '../../../lib/utils/poll';
import PipelineService from '../../services/pipeline_service';
import PipelineStore from '../../stores/pipeline_store';
import stageColumnComponent from './stage_column_component.vue';
import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
import '../../../flash';
export default {
props: {
isLoading: {
type: Boolean,
required: true,
},
pipeline: {
type: Object,
required: true,
},
},
components: {
stageColumnComponent,
loadingIcon,
},
data() {
const DOMdata = document.getElementById('js-pipeline-graph-vue').dataset;
const store = new PipelineStore();
return {
isLoading: false,
endpoint: DOMdata.endpoint,
store,
state: store.state,
};
},
created() {
this.service = new PipelineService(this.endpoint);
const poll = new Poll({
resource: this.service,
method: 'getPipeline',
successCallback: this.successCallback,
errorCallback: this.errorCallback,
});
if (!Visibility.hidden()) {
this.isLoading = true;
poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
poll.restart();
} else {
poll.stop();
}
});
computed: {
graph() {
return this.pipeline.details && this.pipeline.details.stages;
},
},
methods: {
successCallback(response) {
const data = response.json();
this.isLoading = false;
this.store.storeGraph(data.details.stages);
},
errorCallback() {
this.isLoading = false;
return new Flash('An error occurred while fetching the pipeline.');
},
capitalizeStageName(name) {
return name.charAt(0).toUpperCase() + name.slice(1);
},
......@@ -101,7 +65,7 @@
v-if="!isLoading"
class="stage-column-list">
<stage-column-component
v-for="(stage, index) in state.graph"
v-for="(stage, index) in graph"
:title="capitalizeStageName(stage.name)"
:jobs="stage.groups"
:key="stage.name"
......
import Vue from 'vue';
import pipelineGraph from './components/graph/graph_component.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#js-pipeline-graph-vue',
components: {
pipelineGraph,
},
render: createElement => createElement('pipeline-graph'),
}));
import Vue from 'vue';
import PipelinesMediator from './pipeline_details_mediatior';
import pipelineGraph from './components/graph/graph_component.vue';
document.addEventListener('DOMContentLoaded', () => {
const dataset = document.querySelector('.js-pipeline-details-vue').dataset;
const mediator = new PipelinesMediator({ endpoint: dataset.endpoint });
mediator.fetchPipeline();
const pipelineGraphApp = new Vue({
el: '#js-pipeline-graph-vue',
data() {
return {
mediator,
};
},
components: {
pipelineGraph,
},
render(createElement) {
return createElement('pipeline-graph', {
props: {
isLoading: this.mediator.state.isLoading,
pipeline: this.mediator.store.state.pipeline,
},
});
},
});
return pipelineGraphApp;
});
/* global Flash */
import Visibility from 'visibilityjs';
import Poll from '../lib/utils/poll';
import PipelineStore from './stores/pipeline_store';
import PipelineService from './services/pipeline_service';
export default class pipelinesMediator {
constructor(options = {}) {
this.options = options;
this.store = new PipelineStore();
this.service = new PipelineService(options.endpoint);
this.state = {};
this.state.isLoading = false;
}
fetchPipeline() {
this.poll = new Poll({
resource: this.service,
method: 'getPipeline',
successCallback: this.successCallback.bind(this),
errorCallback: this.errorCallback.bind(this),
});
if (!Visibility.hidden()) {
this.state.isLoading = true;
this.poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
}
successCallback(response) {
const data = response.json();
this.state.isLoading = false;
this.store.storePipeline(data);
}
errorCallback() {
this.state.isLoading = false;
return new Flash('An error occurred while fetching the pipeline.');
}
}
......@@ -2,10 +2,10 @@ export default class PipelineStore {
constructor() {
this.state = {};
this.state.graph = [];
this.state.pipeline = {};
}
storeGraph(graph = []) {
this.state.graph = graph;
storePipeline(pipeline = {}) {
this.state.pipeline = pipeline;
}
}
- failed_builds = @pipeline.statuses.latest.failed
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('pipelines_graph')
.tabs-holder
%ul.pipelines-tabs.nav-links.no-top.no-bottom
%li.js-pipeline-tab-link
......@@ -21,7 +17,7 @@
.tab-content
#js-tab-pipeline.tab-pane
#js-pipeline-graph-vue{ data: { endpoint: namespace_project_pipeline_path(@project.namespace, @project, @pipeline, format: :json) } }
#js-pipeline-graph-vue
#js-tab-builds.tab-pane
- if pipeline.yaml_errors.present?
......
......@@ -7,3 +7,9 @@
= render "projects/pipelines/info"
= render "projects/pipelines/with_tabs", pipeline: @pipeline
.js-pipeline-details-vue{ data: { endpoint: namespace_project_pipeline_path(@project.namespace, @project, @pipeline, format: :json) } }
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue')
= webpack_bundle_tag('pipelines_details')
---
title: Creates a mediator for pipeline details vue in order to mount several vue apps
with the same data
merge_request:
author:
......@@ -24,6 +24,7 @@ var config = {
},
context: path.join(ROOT_PATH, 'app/assets/javascripts'),
entry: {
balsamiq_viewer: './blob/balsamiq_viewer.js',
blob: './blob_edit/blob_bundle.js',
boards: './boards/boards_bundle.js',
common: './commons/index.js',
......@@ -48,8 +49,7 @@ var config = {
notebook_viewer: './blob/notebook_viewer.js',
pdf_viewer: './blob/pdf_viewer.js',
pipelines: './pipelines/index.js',
balsamiq_viewer: './blob/balsamiq_viewer.js',
pipelines_graph: './pipelines/graph_bundle.js',
pipelines_details: './pipelines/pipeline_details_bundle.js',
profile: './profile/profile_bundle.js',
protected_branches: './protected_branches/protected_branches_bundle.js',
protected_tags: './protected_tags',
......@@ -160,7 +160,7 @@ var config = {
'notebook_viewer',
'pdf_viewer',
'pipelines',
'pipelines_graph',
'pipelines_details',
'schedule_form',
'schedules_index',
'sidebar',
......
......@@ -14,49 +14,42 @@ describe('graph component', () => {
describe('while is loading', () => {
it('should render a loading icon', () => {
const component = new GraphComponent().$mount('#js-pipeline-graph-vue');
const component = new GraphComponent({
propsData: {
isLoading: true,
pipeline: {},
},
}).$mount('#js-pipeline-graph-vue');
expect(component.$el.querySelector('.loading-icon')).toBeDefined();
});
});
describe('with a successfull response', () => {
const interceptor = (request, next) => {
next(request.respondWith(JSON.stringify(graphJSON), {
status: 200,
}));
};
describe('with data', () => {
it('should render the graph', () => {
const component = new GraphComponent({
propsData: {
isLoading: false,
pipeline: graphJSON,
},
}).$mount('#js-pipeline-graph-vue');
beforeEach(() => {
Vue.http.interceptors.push(interceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
});
it('should render the graph', (done) => {
const component = new GraphComponent().$mount('#js-pipeline-graph-vue');
setTimeout(() => {
expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true);
expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true);
expect(
component.$el.querySelector('.stage-column:first-child').classList.contains('no-margin'),
).toEqual(true);
expect(
component.$el.querySelector('.stage-column:first-child').classList.contains('no-margin'),
).toEqual(true);
expect(
component.$el.querySelector('.stage-column:nth-child(2)').classList.contains('left-margin'),
).toEqual(true);
expect(
component.$el.querySelector('.stage-column:nth-child(2)').classList.contains('left-margin'),
).toEqual(true);
expect(
component.$el.querySelector('.stage-column:nth-child(2) .build:nth-child(1)').classList.contains('left-connector'),
).toEqual(true);
expect(
component.$el.querySelector('.stage-column:nth-child(2) .build:nth-child(1)').classList.contains('left-connector'),
).toEqual(true);
expect(component.$el.querySelector('loading-icon')).toBe(null);
expect(component.$el.querySelector('loading-icon')).toBe(null);
expect(component.$el.querySelector('.stage-column-list')).toBeDefined();
done();
}, 0);
expect(component.$el.querySelector('.stage-column-list')).toBeDefined();
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment