Merge branch 'fe-commit-mr-pipelines' into 'master'
Use vue.js Pipelines table in commit and merge request view Closes #27141 See merge request !8844
This commit is contained in:
commit
3a92287390
44 changed files with 1088 additions and 279 deletions
|
@ -16,7 +16,7 @@ require('./components/board');
|
|||
require('./components/board_sidebar');
|
||||
require('./components/new_list_dropdown');
|
||||
require('./components/modal/index');
|
||||
require('./vue_resource_interceptor');
|
||||
require('../vue_shared/vue_resource_interceptor');
|
||||
|
||||
$(() => {
|
||||
const $boardApp = document.getElementById('board-app');
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars */
|
||||
/* global Vue */
|
||||
|
||||
Vue.http.interceptors.push((request, next) => {
|
||||
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
|
||||
|
||||
next(function (response) {
|
||||
Vue.activeResources -= 1;
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
/* eslint-disable no-new, no-param-reassign */
|
||||
/* global Vue, CommitsPipelineStore, PipelinesService, Flash */
|
||||
|
||||
window.Vue = require('vue');
|
||||
require('./pipelines_table');
|
||||
/**
|
||||
* Commits View > Pipelines Tab > Pipelines Table.
|
||||
* Merge Request View > Pipelines Tab > Pipelines Table.
|
||||
*
|
||||
* Renders Pipelines table in pipelines tab in the commits show view.
|
||||
* Renders Pipelines table in pipelines tab in the merge request show view.
|
||||
*/
|
||||
|
||||
$(() => {
|
||||
window.gl = window.gl || {};
|
||||
gl.commits = gl.commits || {};
|
||||
gl.commits.pipelines = gl.commits.pipelines || {};
|
||||
|
||||
if (gl.commits.PipelinesTableBundle) {
|
||||
gl.commits.PipelinesTableBundle.$destroy(true);
|
||||
}
|
||||
|
||||
gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView({
|
||||
el: document.querySelector('#commit-pipeline-table-view'),
|
||||
});
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
/* globals Vue */
|
||||
/* eslint-disable no-unused-vars, no-param-reassign */
|
||||
|
||||
/**
|
||||
* Pipelines service.
|
||||
*
|
||||
* Used to fetch the data used to render the pipelines table.
|
||||
* Uses Vue.Resource
|
||||
*/
|
||||
class PipelinesService {
|
||||
constructor(endpoint) {
|
||||
this.pipelines = Vue.resource(endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the root param provided when the class is initialized, will
|
||||
* make a GET request.
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
all() {
|
||||
return this.pipelines.get();
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.commits = gl.commits || {};
|
||||
gl.commits.pipelines = gl.commits.pipelines || {};
|
||||
gl.commits.pipelines.PipelinesService = PipelinesService;
|
|
@ -0,0 +1,50 @@
|
|||
/* eslint-disable no-underscore-dangle*/
|
||||
/**
|
||||
* Pipelines' Store for commits view.
|
||||
*
|
||||
* Used to store the Pipelines rendered in the commit view in the pipelines table.
|
||||
*/
|
||||
|
||||
class PipelinesStore {
|
||||
constructor() {
|
||||
this.state = {};
|
||||
this.state.pipelines = [];
|
||||
}
|
||||
|
||||
storePipelines(pipelines = []) {
|
||||
this.state.pipelines = pipelines;
|
||||
|
||||
return pipelines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Once the data is received we will start the time ago loops.
|
||||
*
|
||||
* Everytime a request is made like retry or cancel a pipeline, every 10 seconds we
|
||||
* update the time to show how long as passed.
|
||||
*
|
||||
*/
|
||||
startTimeAgoLoops() {
|
||||
const startTimeLoops = () => {
|
||||
this.timeLoopInterval = setInterval(() => {
|
||||
this.$children[0].$children.reduce((acc, component) => {
|
||||
const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
|
||||
acc.push(timeAgoComponent);
|
||||
return acc;
|
||||
}, []).forEach(e => e.changeTime());
|
||||
}, 10000);
|
||||
};
|
||||
|
||||
startTimeLoops();
|
||||
|
||||
const removeIntervals = () => clearInterval(this.timeLoopInterval);
|
||||
const startIntervals = () => startTimeLoops();
|
||||
|
||||
gl.VueRealtimeListener(removeIntervals, startIntervals);
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.commits = gl.commits || {};
|
||||
gl.commits.pipelines = gl.commits.pipelines || {};
|
||||
gl.commits.pipelines.PipelinesStore = PipelinesStore;
|
107
app/assets/javascripts/commit/pipelines/pipelines_table.js.es6
Normal file
107
app/assets/javascripts/commit/pipelines/pipelines_table.js.es6
Normal file
|
@ -0,0 +1,107 @@
|
|||
/* eslint-disable no-new, no-param-reassign */
|
||||
/* global Vue, CommitsPipelineStore, PipelinesService, Flash */
|
||||
|
||||
window.Vue = require('vue');
|
||||
window.Vue.use(require('vue-resource'));
|
||||
require('../../lib/utils/common_utils');
|
||||
require('../../vue_shared/vue_resource_interceptor');
|
||||
require('../../vue_shared/components/pipelines_table');
|
||||
require('../../vue_realtime_listener/index');
|
||||
require('./pipelines_service');
|
||||
require('./pipelines_store');
|
||||
|
||||
/**
|
||||
*
|
||||
* Uses `pipelines-table-component` to render Pipelines table with an API call.
|
||||
* Endpoint is provided in HTML and passed as `endpoint`.
|
||||
* We need a store to store the received environemnts.
|
||||
* We need a service to communicate with the server.
|
||||
*
|
||||
* Necessary SVG in the table are provided as props. This should be refactored
|
||||
* as soon as we have Webpack and can load them directly into JS files.
|
||||
*/
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
gl.commits = gl.commits || {};
|
||||
gl.commits.pipelines = gl.commits.pipelines || {};
|
||||
|
||||
gl.commits.pipelines.PipelinesTableView = Vue.component('pipelines-table', {
|
||||
|
||||
components: {
|
||||
'pipelines-table-component': gl.pipelines.PipelinesTableComponent,
|
||||
},
|
||||
|
||||
/**
|
||||
* Accesses the DOM to provide the needed data.
|
||||
* Returns the necessary props to render `pipelines-table-component` component.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
data() {
|
||||
const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset;
|
||||
const svgsData = document.querySelector('.pipeline-svgs').dataset;
|
||||
const store = new gl.commits.pipelines.PipelinesStore();
|
||||
|
||||
// Transform svgs DOMStringMap to a plain Object.
|
||||
const svgsObject = gl.utils.DOMStringMapToObject(svgsData);
|
||||
|
||||
return {
|
||||
endpoint: pipelinesTableData.endpoint,
|
||||
svgs: svgsObject,
|
||||
store,
|
||||
state: store.state,
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* When the component is created the service to fetch the data will be
|
||||
* initialized with the correct endpoint.
|
||||
*
|
||||
* A request to fetch the pipelines will be made.
|
||||
* In case of a successfull response we will store the data in the provided
|
||||
* store, in case of a failed response we need to warn the user.
|
||||
*
|
||||
*/
|
||||
created() {
|
||||
const pipelinesService = new gl.commits.pipelines.PipelinesService(this.endpoint);
|
||||
|
||||
this.isLoading = true;
|
||||
return pipelinesService.all()
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
this.store.storePipelines(json);
|
||||
this.store.startTimeAgoLoops.call(this, Vue);
|
||||
this.isLoading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.isLoading = false;
|
||||
new Flash('An error occurred while fetching the pipelines, please reload the page again.', 'alert');
|
||||
});
|
||||
},
|
||||
|
||||
template: `
|
||||
<div>
|
||||
<div class="pipelines realtime-loading" v-if="isLoading">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
|
||||
<div class="blank-state blank-state-no-icon"
|
||||
v-if="!isLoading && state.pipelines.length === 0">
|
||||
<h2 class="blank-state-title js-blank-state-title">
|
||||
No pipelines to show
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="table-holder pipelines"
|
||||
v-if="!isLoading && state.pipelines.length > 0">
|
||||
<pipelines-table-component
|
||||
:pipelines="state.pipelines"
|
||||
:svgs="svgs">
|
||||
</pipelines-table-component>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -159,11 +159,6 @@
|
|||
new ZenMode();
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
break;
|
||||
case 'projects:commit:pipelines':
|
||||
new gl.MiniPipelineGraph({
|
||||
container: '.js-pipeline-table',
|
||||
});
|
||||
break;
|
||||
case 'projects:commits:show':
|
||||
case 'projects:activity':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
window.Vue = require('vue');
|
||||
window.timeago = require('vendor/timeago');
|
||||
require('../../lib/utils/text_utility');
|
||||
require('../../vue_common_component/commit');
|
||||
require('../../vue_shared/components/commit');
|
||||
require('./environment_actions');
|
||||
require('./environment_external_url');
|
||||
require('./environment_stop');
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
window.Vue = require('vue');
|
||||
|
||||
require('./stores/environments_store');
|
||||
require('./components/environment');
|
||||
require('./vue_resource_interceptor');
|
||||
require('../vue_shared/vue_resource_interceptor');
|
||||
|
||||
$(() => {
|
||||
window.gl = window.gl || {};
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
/* global Vue */
|
||||
Vue.http.interceptors.push((request, next) => {
|
||||
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
|
||||
|
||||
next((response) => {
|
||||
if (typeof response.data === 'string') {
|
||||
response.data = JSON.parse(response.data); // eslint-disable-line
|
||||
}
|
||||
|
||||
Vue.activeResources--; // eslint-disable-line
|
||||
});
|
||||
});
|
|
@ -230,5 +230,16 @@
|
|||
|
||||
return upperCaseHeaders;
|
||||
};
|
||||
|
||||
/**
|
||||
* Transforms a DOMStringMap into a plain object.
|
||||
*
|
||||
* @param {DOMStringMap} DOMStringMapObject
|
||||
* @returns {Object}
|
||||
*/
|
||||
w.gl.utils.DOMStringMapToObject = DOMStringMapObject => Object.keys(DOMStringMapObject).reduce((acc, element) => {
|
||||
acc[element] = DOMStringMapObject[element];
|
||||
return acc;
|
||||
}, {});
|
||||
})(window);
|
||||
}).call(this);
|
||||
|
|
|
@ -61,7 +61,6 @@ require('./flash');
|
|||
|
||||
constructor({ action, setUrl, stubLocation } = {}) {
|
||||
this.diffsLoaded = false;
|
||||
this.pipelinesLoaded = false;
|
||||
this.commitsLoaded = false;
|
||||
this.fixedLayoutPref = null;
|
||||
|
||||
|
@ -116,10 +115,6 @@ require('./flash');
|
|||
$.scrollTo('.merge-request-details .merge-request-tabs', {
|
||||
offset: -navBarHeight,
|
||||
});
|
||||
} else if (action === 'pipelines') {
|
||||
this.loadPipelines($target.attr('href'));
|
||||
this.expandView();
|
||||
this.resetViewContainer();
|
||||
} else {
|
||||
this.expandView();
|
||||
this.resetViewContainer();
|
||||
|
@ -244,25 +239,6 @@ require('./flash');
|
|||
});
|
||||
}
|
||||
|
||||
loadPipelines(source) {
|
||||
if (this.pipelinesLoaded) {
|
||||
return;
|
||||
}
|
||||
this.ajaxGet({
|
||||
url: `${source}.json`,
|
||||
success: (data) => {
|
||||
$('#pipelines').html(data.html);
|
||||
gl.utils.localTimeAgo($('.js-timeago', '#pipelines'));
|
||||
this.pipelinesLoaded = true;
|
||||
this.scrollToElement('#pipelines');
|
||||
|
||||
new gl.MiniPipelineGraph({
|
||||
container: '.js-pipeline-table',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Show or hide the loading spinner
|
||||
//
|
||||
// status - Boolean, true to show, false to hide
|
||||
|
|
|
@ -1,41 +1,36 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
/* global Vue, VueResource, gl */
|
||||
window.Vue = require('vue');
|
||||
window.Vue.use(require('vue-resource'));
|
||||
require('../vue_common_component/commit');
|
||||
require('../vue_pagination/index');
|
||||
require('../boards/vue_resource_interceptor');
|
||||
require('./status');
|
||||
require('./store');
|
||||
require('./pipeline_url');
|
||||
require('./stage');
|
||||
require('./stages');
|
||||
require('./pipeline_actions');
|
||||
require('./time_ago');
|
||||
require('../lib/utils/common_utils');
|
||||
require('../vue_shared/vue_resource_interceptor');
|
||||
require('./pipelines');
|
||||
|
||||
(() => {
|
||||
const project = document.querySelector('.pipelines');
|
||||
const entry = document.querySelector('.vue-pipelines-index');
|
||||
const svgs = document.querySelector('.pipeline-svgs');
|
||||
$(() => new Vue({
|
||||
el: document.querySelector('.vue-pipelines-index'),
|
||||
|
||||
if (!entry) return null;
|
||||
return new Vue({
|
||||
el: entry,
|
||||
data: {
|
||||
data() {
|
||||
const project = document.querySelector('.pipelines');
|
||||
const svgs = document.querySelector('.pipeline-svgs').dataset;
|
||||
|
||||
// Transform svgs DOMStringMap to a plain Object.
|
||||
const svgsObject = gl.utils.DOMStringMapToObject(svgs);
|
||||
|
||||
return {
|
||||
scope: project.dataset.url,
|
||||
store: new gl.PipelineStore(),
|
||||
svgs: svgs.dataset,
|
||||
},
|
||||
components: {
|
||||
'vue-pipelines': gl.VuePipelines,
|
||||
},
|
||||
template: `
|
||||
<vue-pipelines
|
||||
:scope='scope'
|
||||
:store='store'
|
||||
:svgs='svgs'
|
||||
>
|
||||
</vue-pipelines>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
svgs: svgsObject,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
'vue-pipelines': gl.VuePipelines,
|
||||
},
|
||||
template: `
|
||||
<vue-pipelines
|
||||
:scope='scope'
|
||||
:store='store'
|
||||
:svgs='svgs'
|
||||
>
|
||||
</vue-pipelines>
|
||||
`,
|
||||
}));
|
||||
|
|
|
@ -50,9 +50,9 @@
|
|||
<button
|
||||
v-if='artifacts'
|
||||
class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
|
||||
data-toggle="dropdown"
|
||||
title="Artifacts"
|
||||
data-placement="top"
|
||||
data-toggle="dropdown"
|
||||
aria-label="Artifacts"
|
||||
>
|
||||
<i class="fa fa-download" aria-hidden="true"></i>
|
||||
|
@ -81,8 +81,7 @@
|
|||
data-placement="top"
|
||||
data-toggle="dropdown"
|
||||
:href='pipeline.retry_path'
|
||||
aria-label="Retry"
|
||||
>
|
||||
aria-label="Retry">
|
||||
<i class="fa fa-repeat" aria-hidden="true"></i>
|
||||
</a>
|
||||
<a
|
||||
|
@ -94,8 +93,7 @@
|
|||
data-placement="top"
|
||||
data-toggle="dropdown"
|
||||
:href='pipeline.cancel_path'
|
||||
aria-label="Cancel"
|
||||
>
|
||||
aria-label="Cancel">
|
||||
<i class="fa fa-remove" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
/* global Vue, gl */
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
window.Vue = require('vue');
|
||||
require('../vue_shared/components/table_pagination');
|
||||
require('./store');
|
||||
require('../vue_shared/components/pipelines_table');
|
||||
|
||||
((gl) => {
|
||||
gl.VuePipelines = Vue.extend({
|
||||
|
||||
components: {
|
||||
runningPipeline: gl.VueRunningPipeline,
|
||||
pipelineActions: gl.VuePipelineActions,
|
||||
stages: gl.VueStages,
|
||||
commit: gl.CommitComponent,
|
||||
pipelineUrl: gl.VuePipelineUrl,
|
||||
pipelineHead: gl.VuePipelineHead,
|
||||
glPagination: gl.VueGlPagination,
|
||||
statusScope: gl.VueStatusScope,
|
||||
timeAgo: gl.VueTimeAgo,
|
||||
'gl-pagination': gl.VueGlPagination,
|
||||
'pipelines-table-component': gl.pipelines.PipelinesTableComponent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
pipelines: [],
|
||||
|
@ -38,87 +38,29 @@
|
|||
change(pagenum, apiScope) {
|
||||
gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`);
|
||||
},
|
||||
author(pipeline) {
|
||||
if (!pipeline.commit) return { avatar_url: '', web_url: '', username: '' };
|
||||
if (pipeline.commit.author) return pipeline.commit.author;
|
||||
return {
|
||||
avatar_url: pipeline.commit.author_gravatar_url,
|
||||
web_url: `mailto:${pipeline.commit.author_email}`,
|
||||
username: pipeline.commit.author_name,
|
||||
};
|
||||
},
|
||||
ref(pipeline) {
|
||||
const { ref } = pipeline;
|
||||
return { name: ref.name, tag: ref.tag, ref_url: ref.path };
|
||||
},
|
||||
commitTitle(pipeline) {
|
||||
return pipeline.commit ? pipeline.commit.title : '';
|
||||
},
|
||||
commitSha(pipeline) {
|
||||
return pipeline.commit ? pipeline.commit.short_id : '';
|
||||
},
|
||||
commitUrl(pipeline) {
|
||||
return pipeline.commit ? pipeline.commit.commit_path : '';
|
||||
},
|
||||
match(string) {
|
||||
return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase());
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="pipelines realtime-loading" v-if='pipelines.length < 1'>
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
<div class="table-holder" v-if='pipelines.length'>
|
||||
<table class="table ci-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="pipeline-status">Status</th>
|
||||
<th class="pipeline-info">Pipeline</th>
|
||||
<th class="pipeline-commit">Commit</th>
|
||||
<th class="pipeline-stages">Stages</th>
|
||||
<th class="pipeline-date"></th>
|
||||
<th class="pipeline-actions hidden-xs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="commit" v-for='pipeline in pipelines'>
|
||||
<status-scope
|
||||
:pipeline='pipeline'
|
||||
:match='match'
|
||||
:svgs='svgs'
|
||||
>
|
||||
</status-scope>
|
||||
<pipeline-url :pipeline='pipeline'></pipeline-url>
|
||||
<td>
|
||||
<commit
|
||||
:commit-icon-svg='svgs.commitIconSvg'
|
||||
:author='author(pipeline)'
|
||||
:tag="pipeline.ref.tag"
|
||||
:title='commitTitle(pipeline)'
|
||||
:commit-ref='ref(pipeline)'
|
||||
:short-sha='commitSha(pipeline)'
|
||||
:commit-url='commitUrl(pipeline)'
|
||||
>
|
||||
</commit>
|
||||
</td>
|
||||
<stages
|
||||
:pipeline='pipeline'
|
||||
:svgs='svgs'
|
||||
:match='match'
|
||||
>
|
||||
</stages>
|
||||
<time-ago :pipeline='pipeline' :svgs='svgs'></time-ago>
|
||||
<pipeline-actions :pipeline='pipeline' :svgs='svgs'></pipeline-actions>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pipelines realtime-loading" v-if='pageRequest'>
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
|
||||
<div class="blank-state blank-state-no-icon"
|
||||
v-if="!pageRequest && pipelines.length === 0">
|
||||
<h2 class="blank-state-title js-blank-state-title">
|
||||
No pipelines to show
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="table-holder" v-if='!pageRequest && pipelines.length'>
|
||||
<pipelines-table-component
|
||||
:pipelines='pipelines'
|
||||
:svgs='svgs'>
|
||||
</pipelines-table-component>
|
||||
</div>
|
||||
|
||||
<gl-pagination
|
||||
v-if='pageInfo.total > pageInfo.perPage'
|
||||
v-if='!pageRequest && pipelines.length && pageInfo.total > pageInfo.perPage'
|
||||
:pagenum='pagenum'
|
||||
:change='change'
|
||||
:count='count.all'
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
required: true,
|
||||
},
|
||||
svgs: {
|
||||
type: DOMStringMap,
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
match: {
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
/* global Vue, gl */
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
((gl) => {
|
||||
gl.VueStages = Vue.extend({
|
||||
components: {
|
||||
'vue-stage': gl.VueStage,
|
||||
},
|
||||
props: ['pipeline', 'svgs', 'match'],
|
||||
template: `
|
||||
<td class="stage-cell">
|
||||
<div
|
||||
class="stage-container dropdown js-mini-pipeline-graph"
|
||||
v-for='stage in pipeline.details.stages'
|
||||
>
|
||||
<vue-stage :stage='stage' :svgs='svgs' :match='match'></vue-stage>
|
||||
</div>
|
||||
</td>
|
||||
`,
|
||||
});
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -20,6 +20,7 @@ require('../vue_realtime_listener');
|
|||
|
||||
gl.PipelineStore = class {
|
||||
fetchDataLoop(Vue, pageNum, url, apiScope) {
|
||||
this.pageRequest = true;
|
||||
const updatePipelineNums = (count) => {
|
||||
const { all } = count;
|
||||
const running = count.running_or_pending;
|
||||
|
@ -41,16 +42,18 @@ require('../vue_realtime_listener');
|
|||
this.pageRequest = false;
|
||||
}, () => {
|
||||
this.pageRequest = false;
|
||||
return new Flash('Something went wrong on our end.');
|
||||
return new Flash('An error occurred while fetching the pipelines, please reload the page again.');
|
||||
});
|
||||
|
||||
goFetch();
|
||||
|
||||
const startTimeLoops = () => {
|
||||
this.timeLoopInterval = setInterval(() => {
|
||||
this.$children
|
||||
.filter(e => e.$options._componentTag === 'time-ago')
|
||||
.forEach(e => e.changeTime());
|
||||
this.$children[0].$children.reduce((acc, component) => {
|
||||
const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
|
||||
acc.push(timeAgoComponent);
|
||||
return acc;
|
||||
}, []).forEach(e => e.changeTime());
|
||||
}, 10000);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
/* global Vue, gl */
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
window.Vue = require('vue');
|
||||
require('../lib/utils/datetime_utility');
|
||||
|
||||
((gl) => {
|
||||
gl.VueTimeAgo = Vue.extend({
|
||||
data() {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
/* global Vue */
|
||||
|
||||
window.Vue = require('vue');
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
/* global Vue */
|
||||
|
||||
require('./pipelines_table_row');
|
||||
/**
|
||||
* Pipelines Table Component.
|
||||
*
|
||||
* Given an array of objects, renders a table.
|
||||
*/
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
gl.pipelines = gl.pipelines || {};
|
||||
|
||||
gl.pipelines.PipelinesTableComponent = Vue.component('pipelines-table-component', {
|
||||
|
||||
props: {
|
||||
pipelines: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => ([]),
|
||||
},
|
||||
|
||||
/**
|
||||
* TODO: Remove this when we have webpack.
|
||||
*/
|
||||
svgs: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
'pipelines-table-row-component': gl.pipelines.PipelinesTableRowComponent,
|
||||
},
|
||||
|
||||
template: `
|
||||
<table class="table ci-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="js-pipeline-status pipeline-status">Status</th>
|
||||
<th class="js-pipeline-info pipeline-info">Pipeline</th>
|
||||
<th class="js-pipeline-commit pipeline-commit">Commit</th>
|
||||
<th class="js-pipeline-stages pipeline-stages">Stages</th>
|
||||
<th class="js-pipeline-date pipeline-date"></th>
|
||||
<th class="js-pipeline-actions pipeline-actions hidden-xs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="model in pipelines"
|
||||
v-bind:model="model">
|
||||
<tr is="pipelines-table-row-component"
|
||||
:pipeline="model"
|
||||
:svgs="svgs"></tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,234 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
/* global Vue */
|
||||
|
||||
require('../../vue_pipelines_index/status');
|
||||
require('../../vue_pipelines_index/pipeline_url');
|
||||
require('../../vue_pipelines_index/stage');
|
||||
require('../../vue_pipelines_index/pipeline_actions');
|
||||
require('../../vue_pipelines_index/time_ago');
|
||||
require('./commit');
|
||||
/**
|
||||
* Pipeline table row.
|
||||
*
|
||||
* Given the received object renders a table row in the pipelines' table.
|
||||
*/
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
gl.pipelines = gl.pipelines || {};
|
||||
|
||||
gl.pipelines.PipelinesTableRowComponent = Vue.component('pipelines-table-row-component', {
|
||||
|
||||
props: {
|
||||
pipeline: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
|
||||
/**
|
||||
* TODO: Remove this when we have webpack;
|
||||
*/
|
||||
svgs: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
'commit-component': gl.CommitComponent,
|
||||
'pipeline-actions': gl.VuePipelineActions,
|
||||
'dropdown-stage': gl.VueStage,
|
||||
'pipeline-url': gl.VuePipelineUrl,
|
||||
'status-scope': gl.VueStatusScope,
|
||||
'time-ago': gl.VueTimeAgo,
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* If provided, returns the commit tag.
|
||||
* Needed to render the commit component column.
|
||||
*
|
||||
* This field needs a lot of verification, because of different possible cases:
|
||||
*
|
||||
* 1. person who is an author of a commit might be a GitLab user
|
||||
* 2. if person who is an author of a commit is a GitLab user he/she can have a GitLab avatar
|
||||
* 3. If GitLab user does not have avatar he/she might have a Gravatar
|
||||
* 4. If committer is not a GitLab User he/she can have a Gravatar
|
||||
* 5. We do not have consistent API object in this case
|
||||
* 6. We should improve API and the code
|
||||
*
|
||||
* @returns {Object|Undefined}
|
||||
*/
|
||||
commitAuthor() {
|
||||
let commitAuthorInformation;
|
||||
|
||||
// 1. person who is an author of a commit might be a GitLab user
|
||||
if (this.pipeline &&
|
||||
this.pipeline.commit &&
|
||||
this.pipeline.commit.author) {
|
||||
// 2. if person who is an author of a commit is a GitLab user
|
||||
// he/she can have a GitLab avatar
|
||||
if (this.pipeline.commit.author.avatar_url) {
|
||||
commitAuthorInformation = this.pipeline.commit.author;
|
||||
|
||||
// 3. If GitLab user does not have avatar he/she might have a Gravatar
|
||||
} else if (this.pipeline.commit.author_gravatar_url) {
|
||||
commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, {
|
||||
avatar_url: this.pipeline.commit.author_gravatar_url,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 4. If committer is not a GitLab User he/she can have a Gravatar
|
||||
if (this.pipeline &&
|
||||
this.pipeline.commit) {
|
||||
commitAuthorInformation = {
|
||||
avatar_url: this.pipeline.commit.author_gravatar_url,
|
||||
web_url: `mailto:${this.pipeline.commit.author_email}`,
|
||||
username: this.pipeline.commit.author_name,
|
||||
};
|
||||
}
|
||||
|
||||
return commitAuthorInformation;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit tag.
|
||||
* Needed to render the commit component column.
|
||||
*
|
||||
* @returns {String|Undefined}
|
||||
*/
|
||||
commitTag() {
|
||||
if (this.pipeline.ref &&
|
||||
this.pipeline.ref.tag) {
|
||||
return this.pipeline.ref.tag;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit ref.
|
||||
* Needed to render the commit component column.
|
||||
*
|
||||
* Matched `url` prop sent in the API to `path` prop needed
|
||||
* in the commit component.
|
||||
*
|
||||
* @returns {Object|Undefined}
|
||||
*/
|
||||
commitRef() {
|
||||
if (this.pipeline.ref) {
|
||||
return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
|
||||
if (prop === 'url') {
|
||||
accumulator.path = this.pipeline.ref[prop];
|
||||
} else {
|
||||
accumulator[prop] = this.pipeline.ref[prop];
|
||||
}
|
||||
return accumulator;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit url.
|
||||
* Needed to render the commit component column.
|
||||
*
|
||||
* @returns {String|Undefined}
|
||||
*/
|
||||
commitUrl() {
|
||||
if (this.pipeline.commit &&
|
||||
this.pipeline.commit.commit_path) {
|
||||
return this.pipeline.commit.commit_path;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit short sha.
|
||||
* Needed to render the commit component column.
|
||||
*
|
||||
* @returns {String|Undefined}
|
||||
*/
|
||||
commitShortSha() {
|
||||
if (this.pipeline.commit &&
|
||||
this.pipeline.commit.short_id) {
|
||||
return this.pipeline.commit.short_id;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit title.
|
||||
* Needed to render the commit component column.
|
||||
*
|
||||
* @returns {String|Undefined}
|
||||
*/
|
||||
commitTitle() {
|
||||
if (this.pipeline.commit &&
|
||||
this.pipeline.commit.title) {
|
||||
return this.pipeline.commit.title;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* FIXME: This should not be in this component but in the components that
|
||||
* need this function.
|
||||
*
|
||||
* Used to render SVGs in the following components:
|
||||
* - status-scope
|
||||
* - dropdown-stage
|
||||
*
|
||||
* @param {String} string
|
||||
* @return {String}
|
||||
*/
|
||||
match(string) {
|
||||
return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase());
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<tr class="commit">
|
||||
<status-scope
|
||||
:pipeline="pipeline"
|
||||
:svgs="svgs"
|
||||
:match="match">
|
||||
</status-scope>
|
||||
|
||||
<pipeline-url :pipeline="pipeline"></pipeline-url>
|
||||
|
||||
<td>
|
||||
<commit-component
|
||||
:tag="commitTag"
|
||||
:commit-ref="commitRef"
|
||||
:commit-url="commitUrl"
|
||||
:short-sha="commitShortSha"
|
||||
:title="commitTitle"
|
||||
:author="commitAuthor"
|
||||
:commit-icon-svg="svgs.commitIconSvg">
|
||||
</commit-component>
|
||||
</td>
|
||||
|
||||
<td class="stage-cell">
|
||||
<div class="stage-container dropdown js-mini-pipeline-graph"
|
||||
v-if="pipeline.details.stages.length > 0"
|
||||
v-for="stage in pipeline.details.stages">
|
||||
<dropdown-stage
|
||||
:stage="stage"
|
||||
:svgs="svgs"
|
||||
:match="match">
|
||||
</dropdown-stage>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<time-ago :pipeline="pipeline" :svgs="svgs"></time-ago>
|
||||
|
||||
<pipeline-actions :pipeline="pipeline" :svgs="svgs"></pipeline-actions>
|
||||
</tr>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,23 @@
|
|||
/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars,
|
||||
no-param-reassign, no-plusplus */
|
||||
/* global Vue */
|
||||
|
||||
Vue.http.interceptors.push((request, next) => {
|
||||
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
|
||||
|
||||
next((response) => {
|
||||
if (typeof response.data === 'string') {
|
||||
response.data = JSON.parse(response.data);
|
||||
}
|
||||
|
||||
Vue.activeResources--;
|
||||
});
|
||||
});
|
||||
|
||||
Vue.http.interceptors.push((request, next) => {
|
||||
// needed in order to not break the tests.
|
||||
if ($.rails) {
|
||||
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
|
||||
}
|
||||
next();
|
||||
});
|
|
@ -37,7 +37,6 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
format.json do
|
||||
render json: PipelineSerializer
|
||||
.new(project: @project, user: @current_user)
|
||||
.with_pagination(request, response)
|
||||
.represent(@pipelines)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -216,19 +216,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
format.json do
|
||||
render json: {
|
||||
html: view_to_html_string('projects/merge_requests/show/_pipelines'),
|
||||
pipelines: PipelineSerializer
|
||||
.new(project: @project, user: @current_user)
|
||||
.with_pagination(request, response)
|
||||
.represent(@pipelines)
|
||||
}
|
||||
render json: PipelineSerializer
|
||||
.new(project: @project, user: @current_user)
|
||||
.represent(@pipelines)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
define_new_vars
|
||||
respond_to do |format|
|
||||
format.html { define_new_vars }
|
||||
format.json do
|
||||
render json: { pipelines: PipelineSerializer
|
||||
.new(project: @project, user: @current_user)
|
||||
.represent(@pipelines) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def new_diffs
|
||||
|
|
|
@ -1,15 +1,25 @@
|
|||
%div
|
||||
- if pipelines.blank?
|
||||
%div
|
||||
.nothing-here-block No pipelines to show
|
||||
- else
|
||||
.table-holder.pipelines
|
||||
%table.table.ci-table.js-pipeline-table
|
||||
%thead
|
||||
%th.pipeline-status Status
|
||||
%th.pipeline-info Pipeline
|
||||
%th.pipeline-commit Commit
|
||||
%th.pipeline-stages Stages
|
||||
%th.pipeline-date
|
||||
%th.pipeline-actions
|
||||
= render pipelines, commit_sha: true, stage: true, allow_retry: true, show_commit: false
|
||||
#commit-pipeline-table-view{ data: { endpoint: endpoint } }
|
||||
.pipeline-svgs{ data: { "commit_icon_svg" => custom_icon("icon_commit"),
|
||||
"icon_status_canceled" => custom_icon("icon_status_canceled"),
|
||||
"icon_status_running" => custom_icon("icon_status_running"),
|
||||
"icon_status_skipped" => custom_icon("icon_status_skipped"),
|
||||
"icon_status_created" => custom_icon("icon_status_created"),
|
||||
"icon_status_pending" => custom_icon("icon_status_pending"),
|
||||
"icon_status_success" => custom_icon("icon_status_success"),
|
||||
"icon_status_failed" => custom_icon("icon_status_failed"),
|
||||
"icon_status_warning" => custom_icon("icon_status_warning"),
|
||||
"stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"),
|
||||
"stage_icon_status_running" => custom_icon("icon_status_running_borderless"),
|
||||
"stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"),
|
||||
"stage_icon_status_created" => custom_icon("icon_status_created_borderless"),
|
||||
"stage_icon_status_pending" => custom_icon("icon_status_pending_borderless"),
|
||||
"stage_icon_status_success" => custom_icon("icon_status_success_borderless"),
|
||||
"stage_icon_status_failed" => custom_icon("icon_status_failed_borderless"),
|
||||
"stage_icon_status_warning" => custom_icon("icon_status_warning_borderless"),
|
||||
"icon_play" => custom_icon("icon_play"),
|
||||
"icon_timer" => custom_icon("icon_timer"),
|
||||
"icon_status_manual" => custom_icon("icon_status_manual"),
|
||||
} }
|
||||
|
||||
- content_for :page_specific_javascripts do
|
||||
= page_specific_javascript_bundle_tag('commit_pipelines')
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
= render 'commit_box'
|
||||
= render 'ci_menu'
|
||||
= render 'pipelines_list', pipelines: @pipelines
|
||||
= render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id)
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
-# This tab is always loaded via AJAX
|
||||
- if @pipelines.any?
|
||||
#pipelines.pipelines.tab-pane
|
||||
= render "projects/merge_requests/show/pipelines"
|
||||
= render "projects/merge_requests/show/pipelines", endpoint: link_to(url_for(params))
|
||||
|
||||
.mr-loading-status
|
||||
= spinner
|
||||
|
|
|
@ -94,7 +94,8 @@
|
|||
#commits.commits.tab-pane
|
||||
-# This tab is always loaded via AJAX
|
||||
#pipelines.pipelines.tab-pane
|
||||
-# This tab is always loaded via AJAX
|
||||
- if @pipelines.any?
|
||||
= render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
|
||||
#diffs.diffs.tab-pane
|
||||
-# This tab is always loaded via AJAX
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
= render "projects/commit/pipelines_list", pipelines: @pipelines, link_to_commit: true
|
||||
= render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
|
||||
|
|
|
@ -36,31 +36,27 @@
|
|||
= link_to ci_lint_path, class: 'btn btn-default' do
|
||||
%span CI Lint
|
||||
.content-list.pipelines{ data: { url: namespace_project_pipelines_path(@project.namespace, @project, format: :json) } }
|
||||
- if @pipelines.blank?
|
||||
%div
|
||||
.nothing-here-block No pipelines to show
|
||||
- else
|
||||
.pipeline-svgs{ "data" => {"commit_icon_svg" => custom_icon("icon_commit"),
|
||||
"icon_status_canceled" => custom_icon("icon_status_canceled"),
|
||||
"icon_status_running" => custom_icon("icon_status_running"),
|
||||
"icon_status_skipped" => custom_icon("icon_status_skipped"),
|
||||
"icon_status_created" => custom_icon("icon_status_created"),
|
||||
"icon_status_pending" => custom_icon("icon_status_pending"),
|
||||
"icon_status_success" => custom_icon("icon_status_success"),
|
||||
"icon_status_failed" => custom_icon("icon_status_failed"),
|
||||
"icon_status_warning" => custom_icon("icon_status_warning"),
|
||||
"stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"),
|
||||
"stage_icon_status_running" => custom_icon("icon_status_running_borderless"),
|
||||
"stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"),
|
||||
"stage_icon_status_created" => custom_icon("icon_status_created_borderless"),
|
||||
"stage_icon_status_pending" => custom_icon("icon_status_pending_borderless"),
|
||||
"stage_icon_status_success" => custom_icon("icon_status_success_borderless"),
|
||||
"stage_icon_status_failed" => custom_icon("icon_status_failed_borderless"),
|
||||
"stage_icon_status_warning" => custom_icon("icon_status_warning_borderless"),
|
||||
"icon_play" => custom_icon("icon_play"),
|
||||
"icon_timer" => custom_icon("icon_timer"),
|
||||
"icon_status_manual" => custom_icon("icon_status_manual"),
|
||||
} }
|
||||
.pipeline-svgs{ "data" => {"commit_icon_svg" => custom_icon("icon_commit"),
|
||||
"icon_status_canceled" => custom_icon("icon_status_canceled"),
|
||||
"icon_status_running" => custom_icon("icon_status_running"),
|
||||
"icon_status_skipped" => custom_icon("icon_status_skipped"),
|
||||
"icon_status_created" => custom_icon("icon_status_created"),
|
||||
"icon_status_pending" => custom_icon("icon_status_pending"),
|
||||
"icon_status_success" => custom_icon("icon_status_success"),
|
||||
"icon_status_failed" => custom_icon("icon_status_failed"),
|
||||
"icon_status_warning" => custom_icon("icon_status_warning"),
|
||||
"stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"),
|
||||
"stage_icon_status_running" => custom_icon("icon_status_running_borderless"),
|
||||
"stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"),
|
||||
"stage_icon_status_created" => custom_icon("icon_status_created_borderless"),
|
||||
"stage_icon_status_pending" => custom_icon("icon_status_pending_borderless"),
|
||||
"stage_icon_status_success" => custom_icon("icon_status_success_borderless"),
|
||||
"stage_icon_status_failed" => custom_icon("icon_status_failed_borderless"),
|
||||
"stage_icon_status_warning" => custom_icon("icon_status_warning_borderless"),
|
||||
"icon_play" => custom_icon("icon_play"),
|
||||
"icon_timer" => custom_icon("icon_timer"),
|
||||
"icon_status_manual" => custom_icon("icon_status_manual"),
|
||||
} }
|
||||
|
||||
.vue-pipelines-index
|
||||
|
||||
|
|
4
changelogs/unreleased/fe-commit-mr-pipelines.yml
Normal file
4
changelogs/unreleased/fe-commit-mr-pipelines.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Use vue.js Pipelines table in commit and merge request view
|
||||
merge_request: 8844
|
||||
author:
|
|
@ -19,6 +19,7 @@ var config = {
|
|||
boards: './boards/boards_bundle.js',
|
||||
boards_test: './boards/test_utils/simulate_drag.js',
|
||||
cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js',
|
||||
commit_pipelines: './commit/pipelines/pipelines_bundle.js',
|
||||
diff_notes: './diff_notes/diff_notes_bundle.js',
|
||||
environments: './environments/environments_bundle.js',
|
||||
filtered_search: './filtered_search/filtered_search_bundle.js',
|
||||
|
|
|
@ -22,23 +22,35 @@ describe Projects::MergeRequestsController do
|
|||
render_views
|
||||
|
||||
let(:fork_project) { create(:forked_project_with_submodules) }
|
||||
before { fork_project.team << [user, :master] }
|
||||
|
||||
before do
|
||||
fork_project.team << [user, :master]
|
||||
context 'when rendering HTML response' do
|
||||
it 'renders new merge request widget template' do
|
||||
submit_new_merge_request
|
||||
|
||||
expect(response).to be_success
|
||||
end
|
||||
end
|
||||
|
||||
it 'renders it' do
|
||||
get :new,
|
||||
namespace_id: fork_project.namespace.to_param,
|
||||
project_id: fork_project.to_param,
|
||||
merge_request: {
|
||||
source_branch: 'remove-submodule',
|
||||
target_branch: 'master'
|
||||
}
|
||||
context 'when rendering JSON response' do
|
||||
it 'renders JSON including serialized pipelines' do
|
||||
submit_new_merge_request(format: :json)
|
||||
|
||||
expect(response).to be_success
|
||||
expect(json_response).to have_key('pipelines')
|
||||
expect(response).to be_ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def submit_new_merge_request(format: :html)
|
||||
get :new,
|
||||
namespace_id: fork_project.namespace.to_param,
|
||||
project_id: fork_project.to_param,
|
||||
merge_request: {
|
||||
source_branch: 'remove-submodule',
|
||||
target_branch: 'master' },
|
||||
format: format
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples "loads labels" do |action|
|
||||
|
@ -689,15 +701,8 @@ describe Projects::MergeRequestsController do
|
|||
format: :json
|
||||
end
|
||||
|
||||
it 'responds with a rendered HTML partial' do
|
||||
expect(response)
|
||||
.to render_template('projects/merge_requests/show/_pipelines')
|
||||
expect(json_response).to have_key 'html'
|
||||
end
|
||||
|
||||
it 'responds with serialized pipelines' do
|
||||
expect(json_response).to have_key 'pipelines'
|
||||
expect(json_response['pipelines']).not_to be_empty
|
||||
expect(json_response).not_to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'project commit pipelines' do
|
||||
feature 'project commit pipelines', js: true do
|
||||
given(:project) { create(:project) }
|
||||
|
||||
background do
|
||||
|
|
90
spec/javascripts/commit/pipelines/mock_data.js.es6
Normal file
90
spec/javascripts/commit/pipelines/mock_data.js.es6
Normal file
|
@ -0,0 +1,90 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
const pipeline = {
|
||||
id: 73,
|
||||
user: {
|
||||
name: 'Administrator',
|
||||
username: 'root',
|
||||
id: 1,
|
||||
state: 'active',
|
||||
avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
|
||||
web_url: 'http://localhost:3000/root',
|
||||
},
|
||||
path: '/root/review-app/pipelines/73',
|
||||
details: {
|
||||
status: {
|
||||
icon: 'icon_status_failed',
|
||||
text: 'failed',
|
||||
label: 'failed',
|
||||
group: 'failed',
|
||||
has_details: true,
|
||||
details_path: '/root/review-app/pipelines/73',
|
||||
},
|
||||
duration: null,
|
||||
finished_at: '2017-01-25T00:00:17.130Z',
|
||||
stages: [{
|
||||
name: 'build',
|
||||
title: 'build: failed',
|
||||
status: {
|
||||
icon: 'icon_status_failed',
|
||||
text: 'failed',
|
||||
label: 'failed',
|
||||
group: 'failed',
|
||||
has_details: true,
|
||||
details_path: '/root/review-app/pipelines/73#build',
|
||||
},
|
||||
path: '/root/review-app/pipelines/73#build',
|
||||
dropdown_path: '/root/review-app/pipelines/73/stage.json?stage=build',
|
||||
}],
|
||||
artifacts: [],
|
||||
manual_actions: [
|
||||
{
|
||||
name: 'stop_review',
|
||||
path: '/root/review-app/builds/1463/play',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
path: '/root/review-app/builds/1490/play',
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: {
|
||||
latest: true,
|
||||
triggered: false,
|
||||
stuck: false,
|
||||
yaml_errors: false,
|
||||
retryable: true,
|
||||
cancelable: false,
|
||||
},
|
||||
ref:
|
||||
{
|
||||
name: 'master',
|
||||
path: '/root/review-app/tree/master',
|
||||
tag: false,
|
||||
branch: true,
|
||||
},
|
||||
commit: {
|
||||
id: 'fbd79f04fa98717641deaaeb092a4d417237c2e4',
|
||||
short_id: 'fbd79f04',
|
||||
title: 'Update .gitlab-ci.yml',
|
||||
author_name: 'Administrator',
|
||||
author_email: 'admin@example.com',
|
||||
created_at: '2017-01-16T12:13:57.000-05:00',
|
||||
committer_name: 'Administrator',
|
||||
committer_email: 'admin@example.com',
|
||||
message: 'Update .gitlab-ci.yml',
|
||||
author: {
|
||||
name: 'Administrator',
|
||||
username: 'root',
|
||||
id: 1,
|
||||
state: 'active',
|
||||
avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
|
||||
web_url: 'http://localhost:3000/root',
|
||||
},
|
||||
author_gravatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
|
||||
commit_url: 'http://localhost:3000/root/review-app/commit/fbd79f04fa98717641deaaeb092a4d417237c2e4',
|
||||
commit_path: '/root/review-app/commit/fbd79f04fa98717641deaaeb092a4d417237c2e4',
|
||||
},
|
||||
retry_path: '/root/review-app/pipelines/73/retry',
|
||||
created_at: '2017-01-16T17:13:59.800Z',
|
||||
updated_at: '2017-01-25T00:00:17.132Z',
|
||||
};
|
106
spec/javascripts/commit/pipelines/pipelines_spec.js.es6
Normal file
106
spec/javascripts/commit/pipelines/pipelines_spec.js.es6
Normal file
|
@ -0,0 +1,106 @@
|
|||
/* global pipeline, Vue */
|
||||
|
||||
require('vue-resource');
|
||||
require('flash');
|
||||
require('~/commit/pipelines/pipelines_store');
|
||||
require('~/commit/pipelines/pipelines_service');
|
||||
require('~/commit/pipelines/pipelines_table');
|
||||
require('~vue_shared/vue_resource_interceptor');
|
||||
require('./mock_data');
|
||||
|
||||
describe('Pipelines table in Commits and Merge requests', () => {
|
||||
preloadFixtures('pipelines_table');
|
||||
|
||||
beforeEach(() => {
|
||||
loadFixtures('pipelines_table');
|
||||
});
|
||||
|
||||
describe('successfull request', () => {
|
||||
describe('without pipelines', () => {
|
||||
const pipelinesEmptyResponse = (request, next) => {
|
||||
next(request.respondWith(JSON.stringify([]), {
|
||||
status: 200,
|
||||
}));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
Vue.http.interceptors.push(pipelinesEmptyResponse);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Vue.http.interceptors = _.without(
|
||||
Vue.http.interceptors, pipelinesEmptyResponse,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render the empty state', (done) => {
|
||||
const component = new gl.commits.pipelines.PipelinesTableView({
|
||||
el: document.querySelector('#commit-pipeline-table-view'),
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain('No pipelines to show');
|
||||
done();
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with pipelines', () => {
|
||||
const pipelinesResponse = (request, next) => {
|
||||
next(request.respondWith(JSON.stringify([pipeline]), {
|
||||
status: 200,
|
||||
}));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
Vue.http.interceptors.push(pipelinesResponse);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Vue.http.interceptors = _.without(
|
||||
Vue.http.interceptors, pipelinesResponse,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a table with the received pipelines', (done) => {
|
||||
const component = new gl.commits.pipelines.PipelinesTableView({
|
||||
el: document.querySelector('#commit-pipeline-table-view'),
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(component.$el.querySelectorAll('table > tbody > tr').length).toEqual(1);
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unsuccessfull request', () => {
|
||||
const pipelinesErrorResponse = (request, next) => {
|
||||
next(request.respondWith(JSON.stringify([]), {
|
||||
status: 500,
|
||||
}));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
Vue.http.interceptors.push(pipelinesErrorResponse);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Vue.http.interceptors = _.without(
|
||||
Vue.http.interceptors, pipelinesErrorResponse,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render empty state', (done) => {
|
||||
const component = new gl.commits.pipelines.PipelinesTableView({
|
||||
el: document.querySelector('#commit-pipeline-table-view'),
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain('No pipelines to show');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
require('~commit/pipelines/pipelines_store');
|
||||
|
||||
describe('Store', () => {
|
||||
const store = gl.commits.pipelines.PipelinesStore;
|
||||
|
||||
beforeEach(() => {
|
||||
store.create();
|
||||
});
|
||||
|
||||
it('should start with a blank state', () => {
|
||||
expect(store.state.pipelines.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should store an array of pipelines', () => {
|
||||
const pipelines = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'pipeline',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'pipeline_2',
|
||||
},
|
||||
];
|
||||
|
||||
store.store(pipelines);
|
||||
|
||||
expect(store.state.pipelines.length).toBe(pipelines.length);
|
||||
});
|
||||
});
|
2
spec/javascripts/fixtures/pipelines_table.html.haml
Normal file
2
spec/javascripts/fixtures/pipelines_table.html.haml
Normal file
|
@ -0,0 +1,2 @@
|
|||
#commit-pipeline-table-view{ data: { endpoint: "endpoint" } }
|
||||
.pipeline-svgs{ data: { "commit_icon_svg": "svg"} }
|
|
@ -1,4 +1,4 @@
|
|||
require('~/vue_common_component/commit');
|
||||
require('~/vue_shared/components/committ');
|
||||
|
||||
describe('Commit component', () => {
|
||||
let props;
|
|
@ -0,0 +1,89 @@
|
|||
/* global pipeline */
|
||||
|
||||
require('~vue_shared/components/pipelines_table_row');
|
||||
require('./mock_data');
|
||||
|
||||
describe('Pipelines Table Row', () => {
|
||||
let component;
|
||||
preloadFixtures('static/environments/element.html.raw');
|
||||
|
||||
beforeEach(() => {
|
||||
loadFixtures('static/environments/element.html.raw');
|
||||
|
||||
component = new gl.pipelines.PipelinesTableRowComponent({
|
||||
el: document.querySelector('.test-dom-element'),
|
||||
propsData: {
|
||||
pipeline,
|
||||
svgs: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should render a table row', () => {
|
||||
expect(component.$el).toEqual('TR');
|
||||
});
|
||||
|
||||
describe('status column', () => {
|
||||
it('should render a pipeline link', () => {
|
||||
expect(
|
||||
component.$el.querySelector('td.commit-link a').getAttribute('href'),
|
||||
).toEqual(pipeline.path);
|
||||
});
|
||||
|
||||
it('should render status text', () => {
|
||||
expect(
|
||||
component.$el.querySelector('td.commit-link a').textContent,
|
||||
).toContain(pipeline.details.status.text);
|
||||
});
|
||||
});
|
||||
|
||||
describe('information column', () => {
|
||||
it('should render a pipeline link', () => {
|
||||
expect(
|
||||
component.$el.querySelector('td:nth-child(2) a').getAttribute('href'),
|
||||
).toEqual(pipeline.path);
|
||||
});
|
||||
|
||||
it('should render pipeline ID', () => {
|
||||
expect(
|
||||
component.$el.querySelector('td:nth-child(2) a > span').textContent,
|
||||
).toEqual(`#${pipeline.id}`);
|
||||
});
|
||||
|
||||
describe('when a user is provided', () => {
|
||||
it('should render user information', () => {
|
||||
expect(
|
||||
component.$el.querySelector('td:nth-child(2) a:nth-child(3)').getAttribute('href'),
|
||||
).toEqual(pipeline.user.web_url);
|
||||
|
||||
expect(
|
||||
component.$el.querySelector('td:nth-child(2) img').getAttribute('title'),
|
||||
).toEqual(pipeline.user.name);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('commit column', () => {
|
||||
it('should render link to commit', () => {
|
||||
expect(
|
||||
component.$el.querySelector('td:nth-child(3) .commit-id').getAttribute('href'),
|
||||
).toEqual(pipeline.commit.commit_path);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stages column', () => {
|
||||
it('should render an icon for each stage', () => {
|
||||
expect(
|
||||
component.$el.querySelectorAll('td:nth-child(4) .js-builds-dropdown-button').length,
|
||||
).toEqual(pipeline.details.stages.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions column', () => {
|
||||
it('should render the provided actions', () => {
|
||||
expect(
|
||||
component.$el.querySelectorAll('td:nth-child(6) ul li').length,
|
||||
).toEqual(pipeline.details.manual_actions.length);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,66 @@
|
|||
/* global pipeline */
|
||||
|
||||
require('~vue_shared/components/pipelines_table');
|
||||
require('~lib/utils/datetime_utility');
|
||||
require('./mock_data');
|
||||
|
||||
describe('Pipelines Table', () => {
|
||||
preloadFixtures('static/environments/element.html.raw');
|
||||
|
||||
beforeEach(() => {
|
||||
loadFixtures('static/environments/element.html.raw');
|
||||
});
|
||||
|
||||
describe('table', () => {
|
||||
let component;
|
||||
beforeEach(() => {
|
||||
component = new gl.pipelines.PipelinesTableComponent({
|
||||
el: document.querySelector('.test-dom-element'),
|
||||
propsData: {
|
||||
pipelines: [],
|
||||
svgs: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should render a table', () => {
|
||||
expect(component.$el).toEqual('TABLE');
|
||||
});
|
||||
|
||||
it('should render table head with correct columns', () => {
|
||||
expect(component.$el.querySelector('th.js-pipeline-status').textContent).toEqual('Status');
|
||||
expect(component.$el.querySelector('th.js-pipeline-info').textContent).toEqual('Pipeline');
|
||||
expect(component.$el.querySelector('th.js-pipeline-commit').textContent).toEqual('Commit');
|
||||
expect(component.$el.querySelector('th.js-pipeline-stages').textContent).toEqual('Stages');
|
||||
expect(component.$el.querySelector('th.js-pipeline-date').textContent).toEqual('');
|
||||
expect(component.$el.querySelector('th.js-pipeline-actions').textContent).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('without data', () => {
|
||||
it('should render an empty table', () => {
|
||||
const component = new gl.pipelines.PipelinesTableComponent({
|
||||
el: document.querySelector('.test-dom-element'),
|
||||
propsData: {
|
||||
pipelines: [],
|
||||
svgs: {},
|
||||
},
|
||||
});
|
||||
expect(component.$el.querySelectorAll('tbody tr').length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with data', () => {
|
||||
it('should render rows', () => {
|
||||
const component = new gl.pipelines.PipelinesTableComponent({
|
||||
el: document.querySelector('.test-dom-element'),
|
||||
propsData: {
|
||||
pipelines: [pipeline],
|
||||
svgs: {},
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.$el.querySelectorAll('tbody tr').length).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
require('~/lib/utils/common_utils');
|
||||
require('~/vue_pagination/index');
|
||||
require('~/vue_shared/components/table_pagination');
|
||||
|
||||
describe('Pagination component', () => {
|
||||
let component;
|
Loading…
Reference in a new issue