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> <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 stageColumnComponent from './stage_column_component.vue';
import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
import '../../../flash'; import '../../../flash';
export default { export default {
props: {
isLoading: {
type: Boolean,
required: true,
},
pipeline: {
type: Object,
required: true,
},
},
components: { components: {
stageColumnComponent, stageColumnComponent,
loadingIcon, loadingIcon,
}, },
data() { computed: {
const DOMdata = document.getElementById('js-pipeline-graph-vue').dataset; graph() {
const store = new PipelineStore(); return this.pipeline.details && this.pipeline.details.stages;
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();
}
});
}, },
methods: { 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) { capitalizeStageName(name) {
return name.charAt(0).toUpperCase() + name.slice(1); return name.charAt(0).toUpperCase() + name.slice(1);
}, },
...@@ -101,7 +65,7 @@ ...@@ -101,7 +65,7 @@
v-if="!isLoading" v-if="!isLoading"
class="stage-column-list"> class="stage-column-list">
<stage-column-component <stage-column-component
v-for="(stage, index) in state.graph" v-for="(stage, index) in graph"
:title="capitalizeStageName(stage.name)" :title="capitalizeStageName(stage.name)"
:jobs="stage.groups" :jobs="stage.groups"
:key="stage.name" :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 { ...@@ -2,10 +2,10 @@ export default class PipelineStore {
constructor() { constructor() {
this.state = {}; this.state = {};
this.state.graph = []; this.state.pipeline = {};
} }
storeGraph(graph = []) { storePipeline(pipeline = {}) {
this.state.graph = graph; this.state.pipeline = pipeline;
} }
} }
- failed_builds = @pipeline.statuses.latest.failed - 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 .tabs-holder
%ul.pipelines-tabs.nav-links.no-top.no-bottom %ul.pipelines-tabs.nav-links.no-top.no-bottom
%li.js-pipeline-tab-link %li.js-pipeline-tab-link
...@@ -21,7 +17,7 @@ ...@@ -21,7 +17,7 @@
.tab-content .tab-content
#js-tab-pipeline.tab-pane #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 #js-tab-builds.tab-pane
- if pipeline.yaml_errors.present? - if pipeline.yaml_errors.present?
......
...@@ -7,3 +7,9 @@ ...@@ -7,3 +7,9 @@
= render "projects/pipelines/info" = render "projects/pipelines/info"
= render "projects/pipelines/with_tabs", pipeline: @pipeline = 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 = { ...@@ -24,6 +24,7 @@ var config = {
}, },
context: path.join(ROOT_PATH, 'app/assets/javascripts'), context: path.join(ROOT_PATH, 'app/assets/javascripts'),
entry: { entry: {
balsamiq_viewer: './blob/balsamiq_viewer.js',
blob: './blob_edit/blob_bundle.js', blob: './blob_edit/blob_bundle.js',
boards: './boards/boards_bundle.js', boards: './boards/boards_bundle.js',
common: './commons/index.js', common: './commons/index.js',
...@@ -48,8 +49,7 @@ var config = { ...@@ -48,8 +49,7 @@ var config = {
notebook_viewer: './blob/notebook_viewer.js', notebook_viewer: './blob/notebook_viewer.js',
pdf_viewer: './blob/pdf_viewer.js', pdf_viewer: './blob/pdf_viewer.js',
pipelines: './pipelines/index.js', pipelines: './pipelines/index.js',
balsamiq_viewer: './blob/balsamiq_viewer.js', pipelines_details: './pipelines/pipeline_details_bundle.js',
pipelines_graph: './pipelines/graph_bundle.js',
profile: './profile/profile_bundle.js', profile: './profile/profile_bundle.js',
protected_branches: './protected_branches/protected_branches_bundle.js', protected_branches: './protected_branches/protected_branches_bundle.js',
protected_tags: './protected_tags', protected_tags: './protected_tags',
...@@ -160,7 +160,7 @@ var config = { ...@@ -160,7 +160,7 @@ var config = {
'notebook_viewer', 'notebook_viewer',
'pdf_viewer', 'pdf_viewer',
'pipelines', 'pipelines',
'pipelines_graph', 'pipelines_details',
'schedule_form', 'schedule_form',
'schedules_index', 'schedules_index',
'sidebar', 'sidebar',
......
...@@ -14,30 +14,25 @@ describe('graph component', () => { ...@@ -14,30 +14,25 @@ describe('graph component', () => {
describe('while is loading', () => { describe('while is loading', () => {
it('should render a loading icon', () => { 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(); expect(component.$el.querySelector('.loading-icon')).toBeDefined();
}); });
}); });
describe('with a successfull response', () => { describe('with data', () => {
const interceptor = (request, next) => { it('should render the graph', () => {
next(request.respondWith(JSON.stringify(graphJSON), { const component = new GraphComponent({
status: 200, 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( expect(
...@@ -55,8 +50,6 @@ describe('graph component', () => { ...@@ -55,8 +50,6 @@ describe('graph component', () => {
expect(component.$el.querySelector('loading-icon')).toBe(null); expect(component.$el.querySelector('loading-icon')).toBe(null);
expect(component.$el.querySelector('.stage-column-list')).toBeDefined(); expect(component.$el.querySelector('.stage-column-list')).toBeDefined();
done();
}, 0);
}); });
}); });
}); });
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