BigW Consortium Gitlab
Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
G
gitlab-ce
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Forest Godfrey
gitlab-ce
Commits
3a922873
Commit
3a922873
authored
Feb 06, 2017
by
Fatih Acet
Browse files
Options
Browse Files
Download
Plain Diff
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
parents
ee43dcd5
183ff875
Hide whitespace changes
Inline
Side-by-side
Showing
43 changed files
with
1077 additions
and
268 deletions
+1077
-268
boards_bundle.js.es6
app/assets/javascripts/boards/boards_bundle.js.es6
+1
-1
vue_resource_interceptor.js.es6
...assets/javascripts/boards/vue_resource_interceptor.js.es6
+0
-10
pipelines_bundle.js.es6
...sets/javascripts/commit/pipelines/pipelines_bundle.js.es6
+26
-0
pipelines_service.js.es6
...ets/javascripts/commit/pipelines/pipelines_service.js.es6
+29
-0
pipelines_store.js.es6
...ssets/javascripts/commit/pipelines/pipelines_store.js.es6
+50
-0
pipelines_table.js.es6
...ssets/javascripts/commit/pipelines/pipelines_table.js.es6
+107
-0
dispatcher.js.es6
app/assets/javascripts/dispatcher.js.es6
+0
-5
environment_item.js.es6
...vascripts/environments/components/environment_item.js.es6
+1
-1
environments_bundle.js.es6
...ssets/javascripts/environments/environments_bundle.js.es6
+1
-2
common_utils.js.es6
app/assets/javascripts/lib/utils/common_utils.js.es6
+11
-0
merge_request_tabs.js.es6
app/assets/javascripts/merge_request_tabs.js.es6
+0
-24
index.js.es6
app/assets/javascripts/vue_pipelines_index/index.js.es6
+28
-33
pipeline_actions.js.es6
...s/javascripts/vue_pipelines_index/pipeline_actions.js.es6
+3
-5
pipelines.js.es6
app/assets/javascripts/vue_pipelines_index/pipelines.js.es6
+24
-82
stage.js.es6
app/assets/javascripts/vue_pipelines_index/stage.js.es6
+1
-1
stages.js.es6
app/assets/javascripts/vue_pipelines_index/stages.js.es6
+0
-21
store.js.es6
app/assets/javascripts/vue_pipelines_index/store.js.es6
+7
-4
time_ago.js.es6
app/assets/javascripts/vue_pipelines_index/time_ago.js.es6
+3
-0
commit.js.es6
app/assets/javascripts/vue_shared/components/commit.js.es6
+0
-2
pipelines_table.js.es6
.../javascripts/vue_shared/components/pipelines_table.js.es6
+61
-0
pipelines_table_row.js.es6
...ascripts/vue_shared/components/pipelines_table_row.js.es6
+234
-0
table_pagination.js.es6
...javascripts/vue_shared/components/table_pagination.js.es6
+0
-0
vue_resource_interceptor.js.es6
...ts/javascripts/vue_shared/vue_resource_interceptor.js.es6
+13
-2
commit_controller.rb
app/controllers/projects/commit_controller.rb
+0
-1
merge_requests_controller.rb
app/controllers/projects/merge_requests_controller.rb
+11
-8
_pipelines_list.haml
app/views/projects/commit/_pipelines_list.haml
+25
-15
pipelines.html.haml
app/views/projects/commit/pipelines.html.haml
+1
-1
_new_submit.html.haml
app/views/projects/merge_requests/_new_submit.html.haml
+1
-1
_show.html.haml
app/views/projects/merge_requests/_show.html.haml
+2
-1
_pipelines.html.haml
app/views/projects/merge_requests/show/_pipelines.html.haml
+1
-1
index.html.haml
app/views/projects/pipelines/index.html.haml
+21
-25
fe-commit-mr-pipelines.yml
changelogs/unreleased/fe-commit-mr-pipelines.yml
+4
-0
webpack.config.js
config/webpack.config.js
+1
-0
merge_requests_controller_spec.rb
spec/controllers/projects/merge_requests_controller_spec.rb
+24
-19
builds_spec.rb
spec/features/projects/commit/builds_spec.rb
+1
-1
mock_data.js.es6
spec/javascripts/commit/pipelines/mock_data.js.es6
+90
-0
pipelines_spec.js.es6
spec/javascripts/commit/pipelines/pipelines_spec.js.es6
+106
-0
pipelines_store_spec.js.es6
.../javascripts/commit/pipelines/pipelines_store_spec.js.es6
+30
-0
pipelines_table.html.haml
spec/javascripts/fixtures/pipelines_table.html.haml
+2
-0
commit_spec.js.es6
spec/javascripts/vue_shared/components/commit_spec.js.es6
+1
-1
pipelines_table_row_spec.js.es6
...pts/vue_shared/components/pipelines_table_row_spec.js.es6
+89
-0
pipelines_table_spec.js.es6
...scripts/vue_shared/components/pipelines_table_spec.js.es6
+66
-0
table_pagination_spec.js.es6
...cripts/vue_shared/components/table_pagination_spec.js.es6
+1
-1
No files found.
app/assets/javascripts/boards/boards_bundle.js.es6
View file @
3a922873
...
@@ -16,7 +16,7 @@ require('./components/board');
...
@@ -16,7 +16,7 @@ require('./components/board');
require('./components/board_sidebar');
require('./components/board_sidebar');
require('./components/new_list_dropdown');
require('./components/new_list_dropdown');
require('./components/modal/index');
require('./components/modal/index');
require('./vue_resource_interceptor');
require('.
./vue_shared
/vue_resource_interceptor');
$(() => {
$(() => {
const $boardApp = document.getElementById('board-app');
const $boardApp = document.getElementById('board-app');
...
...
app/assets/javascripts/boards/vue_resource_interceptor.js.es6
deleted
100644 → 0
View file @
ee43dcd5
/* 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;
});
});
app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6
0 → 100644
View file @
3a922873
/* 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'),
});
});
app/assets/javascripts/commit/pipelines/pipelines_service.js.es6
0 → 100644
View file @
3a922873
/* 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;
app/assets/javascripts/commit/pipelines/pipelines_store.js.es6
0 → 100644
View file @
3a922873
/* 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;
app/assets/javascripts/commit/pipelines/pipelines_table.js.es6
0 → 100644
View file @
3a922873
/* 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>
`,
});
})();
app/assets/javascripts/dispatcher.js.es6
View file @
3a922873
...
@@ -159,11 +159,6 @@
...
@@ -159,11 +159,6 @@
new ZenMode();
new ZenMode();
shortcut_handler = new ShortcutsNavigation();
shortcut_handler = new ShortcutsNavigation();
break;
break;
case 'projects:commit:pipelines':
new gl.MiniPipelineGraph({
container: '.js-pipeline-table',
});
break;
case 'projects:commits:show':
case 'projects:commits:show':
case 'projects:activity':
case 'projects:activity':
shortcut_handler = new ShortcutsNavigation();
shortcut_handler = new ShortcutsNavigation();
...
...
app/assets/javascripts/environments/components/environment_item.js.es6
View file @
3a922873
...
@@ -4,7 +4,7 @@
...
@@ -4,7 +4,7 @@
window.Vue = require('vue');
window.Vue = require('vue');
window.timeago = require('vendor/timeago');
window.timeago = require('vendor/timeago');
require('../../lib/utils/text_utility');
require('../../lib/utils/text_utility');
require('../../vue_
common_component
/commit');
require('../../vue_
shared/components
/commit');
require('./environment_actions');
require('./environment_actions');
require('./environment_external_url');
require('./environment_external_url');
require('./environment_stop');
require('./environment_stop');
...
...
app/assets/javascripts/environments/environments_bundle.js.es6
View file @
3a922873
window.Vue = require('vue');
window.Vue = require('vue');
require('./stores/environments_store');
require('./stores/environments_store');
require('./components/environment');
require('./components/environment');
require('./vue_resource_interceptor');
require('.
./vue_shared
/vue_resource_interceptor');
$(() => {
$(() => {
window.gl = window.gl || {};
window.gl = window.gl || {};
...
...
app/assets/javascripts/lib/utils/common_utils.js.es6
View file @
3a922873
...
@@ -230,5 +230,16 @@
...
@@ -230,5 +230,16 @@
return upperCaseHeaders;
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);
})(window);
}).call(this);
}).call(this);
app/assets/javascripts/merge_request_tabs.js.es6
View file @
3a922873
...
@@ -61,7 +61,6 @@ require('./flash');
...
@@ -61,7 +61,6 @@ require('./flash');
constructor({ action, setUrl, stubLocation } = {}) {
constructor({ action, setUrl, stubLocation } = {}) {
this.diffsLoaded = false;
this.diffsLoaded = false;
this.pipelinesLoaded = false;
this.commitsLoaded = false;
this.commitsLoaded = false;
this.fixedLayoutPref = null;
this.fixedLayoutPref = null;
...
@@ -116,10 +115,6 @@ require('./flash');
...
@@ -116,10 +115,6 @@ require('./flash');
$.scrollTo('.merge-request-details .merge-request-tabs', {
$.scrollTo('.merge-request-details .merge-request-tabs', {
offset: -navBarHeight,
offset: -navBarHeight,
});
});
} else if (action === 'pipelines') {
this.loadPipelines($target.attr('href'));
this.expandView();
this.resetViewContainer();
} else {
} else {
this.expandView();
this.expandView();
this.resetViewContainer();
this.resetViewContainer();
...
@@ -244,25 +239,6 @@ require('./flash');
...
@@ -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
// Show or hide the loading spinner
//
//
// status - Boolean, true to show, false to hide
// status - Boolean, true to show, false to hide
...
...
app/assets/javascripts/vue_pipelines_index/index.js.es6
View file @
3a922873
/* eslint-disable no-param-reassign */
/* global Vue, VueResource, gl */
/* global Vue, VueResource, gl */
window.Vue = require('vue');
window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
window.Vue.use(require('vue-resource'));
require('../vue_common_component/commit');
require('../lib/utils/common_utils');
require('../vue_pagination/index');
require('../vue_shared/vue_resource_interceptor');
require('../boards/vue_resource_interceptor');
require('./status');
require('./store');
require('./pipeline_url');
require('./stage');
require('./stages');
require('./pipeline_actions');
require('./time_ago');
require('./pipelines');
require('./pipelines');
(() => {
$(() => new Vue({
const project = document.querySelector('.pipelines');
el: document.querySelector('.vue-pipelines-index'),
const entry = document.querySelector('.vue-pipelines-index');
const svgs = document.querySelector('.pipeline-svgs');
if (!entry) return null;
data() {
return new Vue({
const project = document.querySelector('.pipelines');
el: entry,
const svgs = document.querySelector('.pipeline-svgs').dataset;
data: {
// Transform svgs DOMStringMap to a plain Object.
const svgsObject = gl.utils.DOMStringMapToObject(svgs);
return {
scope: project.dataset.url,
scope: project.dataset.url,
store: new gl.PipelineStore(),
store: new gl.PipelineStore(),
svgs: svgs
.datase
t,
svgs: svgs
Objec
t,
}
,
}
;
components: {
},
'vue-pipelines': gl.VuePipelines,
components: {
}
,
'vue-pipelines': gl.VuePipelines
,
template: `
},
<vue-pipelines
template: `
:scope='scope'
<vue-pipelines
:store='stor
e'
:scope='scop
e'
:svgs='svgs
'
:store='store
'
>
:svgs='svgs'
</vue-pipelines
>
>
`,
</vue-pipelines>
});
`,
})
(
);
}));
app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
View file @
3a922873
...
@@ -50,9 +50,9 @@
...
@@ -50,9 +50,9 @@
<button
<button
v-if='artifacts'
v-if='artifacts'
class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
data-toggle="dropdown"
title="Artifacts"
title="Artifacts"
data-placement="top"
data-placement="top"
data-toggle="dropdown"
aria-label="Artifacts"
aria-label="Artifacts"
>
>
<i class="fa fa-download" aria-hidden="true"></i>
<i class="fa fa-download" aria-hidden="true"></i>
...
@@ -81,8 +81,7 @@
...
@@ -81,8 +81,7 @@
data-placement="top"
data-placement="top"
data-toggle="dropdown"
data-toggle="dropdown"
:href='pipeline.retry_path'
:href='pipeline.retry_path'
aria-label="Retry"
aria-label="Retry">
>
<i class="fa fa-repeat" aria-hidden="true"></i>
<i class="fa fa-repeat" aria-hidden="true"></i>
</a>
</a>
<a
<a
...
@@ -94,8 +93,7 @@
...
@@ -94,8 +93,7 @@
data-placement="top"
data-placement="top"
data-toggle="dropdown"
data-toggle="dropdown"
:href='pipeline.cancel_path'
:href='pipeline.cancel_path'
aria-label="Cancel"
aria-label="Cancel">
>
<i class="fa fa-remove" aria-hidden="true"></i>
<i class="fa fa-remove" aria-hidden="true"></i>
</a>
</a>
</div>
</div>
...
...
app/assets/javascripts/vue_pipelines_index/pipelines.js.es6
View file @
3a922873
/* global Vue, gl */
/* global Vue, gl */
/* eslint-disable no-param-reassign */
/* 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) => {
gl.VuePipelines = Vue.extend({
gl.VuePipelines = Vue.extend({
components: {
components: {
runningPipeline: gl.VueRunningPipeline,
'gl-pagination': gl.VueGlPagination,
pipelineActions: gl.VuePipelineActions,
'pipelines-table-component': gl.pipelines.PipelinesTableComponent,
stages: gl.VueStages,
commit: gl.CommitComponent,
pipelineUrl: gl.VuePipelineUrl,
pipelineHead: gl.VuePipelineHead,
glPagination: gl.VueGlPagination,
statusScope: gl.VueStatusScope,
timeAgo: gl.VueTimeAgo,
},
},
data() {
data() {
return {
return {
pipelines: [],
pipelines: [],
...
@@ -38,87 +38,29 @@
...
@@ -38,87 +38,29 @@
change(pagenum, apiScope) {
change(pagenum, apiScope) {
gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`);
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: `
template: `
<div>
<div>
<div class="pipelines realtime-loading" v-if='p
ipelines.length < 1
'>
<div class="pipelines realtime-loading" v-if='p
ageRequest
'>
<i class="fa fa-spinner fa-spin"></i>
<i class="fa fa-spinner fa-spin"></i>
</div>
</div>
<div class="table-holder" v-if='pipelines.length'>
<table class="table ci-table">
<div class="blank-state blank-state-no-icon"
<thead>
v-if="!pageRequest && pipelines.length === 0">
<tr>
<h2 class="blank-state-title js-blank-state-title">
<th class="pipeline-status">Status</th>
No pipelines to show
<th class="pipeline-info">Pipeline</th>
</h2>
<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>
<div class="pipelines realtime-loading" v-if='pageRequest'>
<i class="fa fa-spinner fa-spin"></i>
<div class="table-holder" v-if='!pageRequest && pipelines.length'>
<pipelines-table-component
:pipelines='pipelines'
:svgs='svgs'>
</pipelines-table-component>
</div>
</div>
<gl-pagination
<gl-pagination
v-if='pageInfo.total > pageInfo.perPage'
v-if='
!pageRequest && pipelines.length &&
pageInfo.total > pageInfo.perPage'
:pagenum='pagenum'
:pagenum='pagenum'
:change='change'
:change='change'
:count='count.all'
:count='count.all'
...
...
app/assets/javascripts/vue_pipelines_index/stage.js.es6
View file @
3a922873
...
@@ -15,7 +15,7 @@
...
@@ -15,7 +15,7 @@
required: true,
required: true,
},
},
svgs: {
svgs: {
type:
DOMStringMap
,
type:
Object
,
required: true,
required: true,
},
},
match: {
match: {
...
...
app/assets/javascripts/vue_pipelines_index/stages.js.es6
deleted
100644 → 0
View file @
ee43dcd5
/* 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 = {}));
app/assets/javascripts/vue_pipelines_index/store.js.es6
View file @
3a922873
...
@@ -20,6 +20,7 @@ require('../vue_realtime_listener');
...
@@ -20,6 +20,7 @@ require('../vue_realtime_listener');
gl.PipelineStore = class {
gl.PipelineStore = class {
fetchDataLoop(Vue, pageNum, url, apiScope) {
fetchDataLoop(Vue, pageNum, url, apiScope) {
this.pageRequest = true;
const updatePipelineNums = (count) => {
const updatePipelineNums = (count) => {
const { all } = count;
const { all } = count;
const running = count.running_or_pending;
const running = count.running_or_pending;
...
@@ -41,16 +42,18 @@ require('../vue_realtime_listener');
...
@@ -41,16 +42,18 @@ require('../vue_realtime_listener');
this.pageRequest = false;
this.pageRequest = false;
}, () => {
}, () => {
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();
goFetch();
const startTimeLoops = () => {
const startTimeLoops = () => {
this.timeLoopInterval = setInterval(() => {
this.timeLoopInterval = setInterval(() => {
this.$children
this.$children[0].$children.reduce((acc, component) => {
.filter(e => e.$options._componentTag === 'time-ago')
const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
.forEach(e => e.changeTime());
acc.push(timeAgoComponent);
return acc;
}, []).forEach(e => e.changeTime());
}, 10000);
}, 10000);
};
};
...
...
app/assets/javascripts/vue_pipelines_index/time_ago.js.es6
View file @
3a922873
/* global Vue, gl */
/* global Vue, gl */
/* eslint-disable no-param-reassign */
/* eslint-disable no-param-reassign */
window.Vue = require('vue');
require('../lib/utils/datetime_utility');
((gl) => {
((gl) => {
gl.VueTimeAgo = Vue.extend({
gl.VueTimeAgo = Vue.extend({
data() {
data() {
...
...
app/assets/javascripts/vue_
common_component
/commit.js.es6
→
app/assets/javascripts/vue_
shared/components
/commit.js.es6
View file @
3a922873
/* global Vue */
/* global Vue */
window.Vue = require('vue');
(() => {
(() => {
window.gl = window.gl || {};
window.gl = window.gl || {};
...
...
app/assets/javascripts/vue_shared/components/pipelines_table.js.es6
0 → 100644
View file @
3a922873
/* 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>
`,
});
})();
app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6
0 → 100644
View file @
3a922873
/* 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>
`,
});
})();
app/assets/javascripts/vue_
pagination/index
.js.es6
→
app/assets/javascripts/vue_
shared/components/table_pagination
.js.es6
View file @
3a922873
File moved
app/assets/javascripts/
environments
/vue_resource_interceptor.js.es6
→
app/assets/javascripts/
vue_shared
/vue_resource_interceptor.js.es6
View file @
3a922873
/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars,
no-param-reassign, no-plusplus */
/* global Vue */
/* global Vue */
Vue.http.interceptors.push((request, next) => {
Vue.http.interceptors.push((request, next) => {
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
next((response) => {
next((response) => {
if (typeof response.data === 'string') {
if (typeof response.data === 'string') {
response.data = JSON.parse(response.data);
// eslint-disable-line
response.data = JSON.parse(response.data);
}
}
Vue.activeResources--;
// eslint-disable-line
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();
});
app/controllers/projects/commit_controller.rb
View file @
3a922873
...
@@ -37,7 +37,6 @@ class Projects::CommitController < Projects::ApplicationController
...
@@ -37,7 +37,6 @@ class Projects::CommitController < Projects::ApplicationController
format
.
json
do
format
.
json
do
render
json:
PipelineSerializer
render
json:
PipelineSerializer
.
new
(
project:
@project
,
user:
@current_user
)
.
new
(
project:
@project
,
user:
@current_user
)
.
with_pagination
(
request
,
response
)
.
represent
(
@pipelines
)
.
represent
(
@pipelines
)
end
end
end
end
...
...
app/controllers/projects/merge_requests_controller.rb
View file @
3a922873
...
@@ -216,19 +216,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController
...
@@ -216,19 +216,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
format
.
json
do
format
.
json
do
render
json:
{
render
json:
PipelineSerializer
html:
view_to_html_string
(
'projects/merge_requests/show/_pipelines'
),
.
new
(
project:
@project
,
user:
@current_user
)
pipelines:
PipelineSerializer
.
represent
(
@pipelines
)
.
new
(
project:
@project
,
user:
@current_user
)
.
with_pagination
(
request
,
response
)
.
represent
(
@pipelines
)
}
end
end
end
end
end
end
def
new
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
end
def
new_diffs
def
new_diffs
...
...
app/views/projects/commit/_pipelines_list.haml
View file @
3a922873
%div
#commit-pipeline-table-view
{
data:
{
endpoint:
endpoint
}
}
-
if
pipelines
.
blank?
.pipeline-svgs
{
data:
{
"commit_icon_svg"
=>
custom_icon
(
"icon_commit"
),
%div
"icon_status_canceled"
=>
custom_icon
(
"icon_status_canceled"
),
.nothing-here-block
No pipelines to show
"icon_status_running"
=>
custom_icon
(
"icon_status_running"
),
-
else
"icon_status_skipped"
=>
custom_icon
(
"icon_status_skipped"
),
.table-holder.pipelines
"icon_status_created"
=>
custom_icon
(
"icon_status_created"
),
%table
.table.ci-table.js-pipeline-table
"icon_status_pending"
=>
custom_icon
(
"icon_status_pending"
),
%thead
"icon_status_success"
=>
custom_icon
(
"icon_status_success"
),
%th
.pipeline-status
Status
"icon_status_failed"
=>
custom_icon
(
"icon_status_failed"
),
%th
.pipeline-info
Pipeline
"icon_status_warning"
=>
custom_icon
(
"icon_status_warning"
),
%th
.pipeline-commit
Commit
"stage_icon_status_canceled"
=>
custom_icon
(
"icon_status_canceled_borderless"
),
%th
.pipeline-stages
Stages
"stage_icon_status_running"
=>
custom_icon
(
"icon_status_running_borderless"
),
%th
.pipeline-date
"stage_icon_status_skipped"
=>
custom_icon
(
"icon_status_skipped_borderless"
),
%th
.pipeline-actions
"stage_icon_status_created"
=>
custom_icon
(
"icon_status_created_borderless"
),
=
render
pipelines
,
commit_sha:
true
,
stage:
true
,
allow_retry:
true
,
show_commit:
false
"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'
)
app/views/projects/commit/pipelines.html.haml
View file @
3a922873
...
@@ -2,4 +2,4 @@
...
@@ -2,4 +2,4 @@
=
render
'commit_box'
=
render
'commit_box'
=
render
'ci_menu'
=
render
'ci_menu'
=
render
'p
ipelines_list'
,
pipelines:
@pipelines
=
render
'p
rojects/commit/pipelines_list'
,
endpoint:
pipelines_namespace_project_commit_path
(
@project
.
namespace
,
@project
,
@commit
.
id
)
app/views/projects/merge_requests/_new_submit.html.haml
View file @
3a922873
...
@@ -46,7 +46,7 @@
...
@@ -46,7 +46,7 @@
-# This tab is always loaded via AJAX
-# This tab is always loaded via AJAX
-
if
@pipelines
.
any?
-
if
@pipelines
.
any?
#pipelines
.pipelines.tab-pane
#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
.mr-loading-status
=
spinner
=
spinner
...
...
app/views/projects/merge_requests/_show.html.haml
View file @
3a922873
...
@@ -94,7 +94,8 @@
...
@@ -94,7 +94,8 @@
#commits
.commits.tab-pane
#commits
.commits.tab-pane
-# This tab is always loaded via AJAX
-# This tab is always loaded via AJAX
#pipelines
.pipelines.tab-pane
#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
#diffs
.diffs.tab-pane
-# This tab is always loaded via AJAX
-# This tab is always loaded via AJAX
...
...
app/views/projects/merge_requests/show/_pipelines.html.haml
View file @
3a922873
=
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
)
app/views/projects/pipelines/index.html.haml
View file @
3a922873
...
@@ -36,31 +36,27 @@
...
@@ -36,31 +36,27 @@
=
link_to
ci_lint_path
,
class:
'btn btn-default'
do
=
link_to
ci_lint_path
,
class:
'btn btn-default'
do
%span
CI Lint
%span
CI Lint
.content-list.pipelines
{
data:
{
url:
namespace_project_pipelines_path
(
@project
.
namespace
,
@project
,
format: :json
)
}
}
.content-list.pipelines
{
data:
{
url:
namespace_project_pipelines_path
(
@project
.
namespace
,
@project
,
format: :json
)
}
}
-
if
@pipelines
.
blank?
.pipeline-svgs
{
"data"
=>
{
"commit_icon_svg"
=>
custom_icon
(
"icon_commit"
),
%div
"icon_status_canceled"
=>
custom_icon
(
"icon_status_canceled"
),
.nothing-here-block
No pipelines to show
"icon_status_running"
=>
custom_icon
(
"icon_status_running"
),
-
else
"icon_status_skipped"
=>
custom_icon
(
"icon_status_skipped"
),
.pipeline-svgs
{
"data"
=>
{
"commit_icon_svg"
=>
custom_icon
(
"icon_commit"
),
"icon_status_created"
=>
custom_icon
(
"icon_status_created"
),
"icon_status_canceled"
=>
custom_icon
(
"icon_status_canceled"
),
"icon_status_pending"
=>
custom_icon
(
"icon_status_pending"
),
"icon_status_running"
=>
custom_icon
(
"icon_status_running"
),
"icon_status_success"
=>
custom_icon
(
"icon_status_success"
),
"icon_status_skipped"
=>
custom_icon
(
"icon_status_skipped"
),
"icon_status_failed"
=>
custom_icon
(
"icon_status_failed"
),
"icon_status_created"
=>
custom_icon
(
"icon_status_created"
),
"icon_status_warning"
=>
custom_icon
(
"icon_status_warning"
),
"icon_status_pending"
=>
custom_icon
(
"icon_status_pending"
),
"stage_icon_status_canceled"
=>
custom_icon
(
"icon_status_canceled_borderless"
),
"icon_status_success"
=>
custom_icon
(
"icon_status_success"
),
"stage_icon_status_running"
=>
custom_icon
(
"icon_status_running_borderless"
),
"icon_status_failed"
=>
custom_icon
(
"icon_status_failed"
),
"stage_icon_status_skipped"
=>
custom_icon
(
"icon_status_skipped_borderless"
),
"icon_status_warning"
=>
custom_icon
(
"icon_status_warning"
),
"stage_icon_status_created"
=>
custom_icon
(
"icon_status_created_borderless"
),
"stage_icon_status_canceled"
=>
custom_icon
(
"icon_status_canceled_borderless"
),
"stage_icon_status_pending"
=>
custom_icon
(
"icon_status_pending_borderless"
),
"stage_icon_status_running"
=>
custom_icon
(
"icon_status_running_borderless"
),
"stage_icon_status_success"
=>
custom_icon
(
"icon_status_success_borderless"
),
"stage_icon_status_skipped"
=>
custom_icon
(
"icon_status_skipped_borderless"
),
"stage_icon_status_failed"
=>
custom_icon
(
"icon_status_failed_borderless"
),
"stage_icon_status_created"
=>
custom_icon
(
"icon_status_created_borderless"
),
"stage_icon_status_warning"
=>
custom_icon
(
"icon_status_warning_borderless"
),
"stage_icon_status_pending"
=>
custom_icon
(
"icon_status_pending_borderless"
),
"icon_play"
=>
custom_icon
(
"icon_play"
),
"stage_icon_status_success"
=>
custom_icon
(
"icon_status_success_borderless"
),
"icon_timer"
=>
custom_icon
(
"icon_timer"
),
"stage_icon_status_failed"
=>
custom_icon
(
"icon_status_failed_borderless"
),
"icon_status_manual"
=>
custom_icon
(
"icon_status_manual"
),
"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
.vue-pipelines-index
...
...
changelogs/unreleased/fe-commit-mr-pipelines.yml
0 → 100644
View file @
3a922873
---
title
:
Use vue.js Pipelines table in commit and merge request view
merge_request
:
8844
author
:
config/webpack.config.js
View file @
3a922873
...
@@ -19,6 +19,7 @@ var config = {
...
@@ -19,6 +19,7 @@ var config = {
boards
:
'./boards/boards_bundle.js'
,
boards
:
'./boards/boards_bundle.js'
,
boards_test
:
'./boards/test_utils/simulate_drag.js'
,
boards_test
:
'./boards/test_utils/simulate_drag.js'
,
cycle_analytics
:
'./cycle_analytics/cycle_analytics_bundle.js'
,
cycle_analytics
:
'./cycle_analytics/cycle_analytics_bundle.js'
,
commit_pipelines
:
'./commit/pipelines/pipelines_bundle.js'
,
diff_notes
:
'./diff_notes/diff_notes_bundle.js'
,
diff_notes
:
'./diff_notes/diff_notes_bundle.js'
,
environments
:
'./environments/environments_bundle.js'
,
environments
:
'./environments/environments_bundle.js'
,
filtered_search
:
'./filtered_search/filtered_search_bundle.js'
,
filtered_search
:
'./filtered_search/filtered_search_bundle.js'
,
...
...
spec/controllers/projects/merge_requests_controller_spec.rb
View file @
3a922873
...
@@ -22,23 +22,35 @@ describe Projects::MergeRequestsController do
...
@@ -22,23 +22,35 @@ describe Projects::MergeRequestsController do
render_views
render_views
let
(
:fork_project
)
{
create
(
:forked_project_with_submodules
)
}
let
(
:fork_project
)
{
create
(
:forked_project_with_submodules
)
}
before
{
fork_project
.
team
<<
[
user
,
:master
]
}
before
do
context
'when rendering HTML response'
do
fork_project
.
team
<<
[
user
,
:master
]
it
'renders new merge request widget template'
do
submit_new_merge_request
expect
(
response
).
to
be_success
end
end
end
it
'renders it'
do
context
'when rendering JSON response'
do
get
:new
,
it
'renders JSON including serialized pipelines'
do
namespace_id:
fork_project
.
namespace
.
to_param
,
submit_new_merge_request
(
format: :json
)
project_id:
fork_project
.
to_param
,
merge_request:
{
source_branch:
'remove-submodule'
,
target_branch:
'master'
}
expect
(
response
).
to
be_success
expect
(
json_response
).
to
have_key
(
'pipelines'
)
expect
(
response
).
to
be_ok
end
end
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
end
shared_examples
"loads labels"
do
|
action
|
shared_examples
"loads labels"
do
|
action
|
...
@@ -689,15 +701,8 @@ describe Projects::MergeRequestsController do
...
@@ -689,15 +701,8 @@ describe Projects::MergeRequestsController do
format: :json
format: :json
end
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
it
'responds with serialized pipelines'
do
expect
(
json_response
).
to
have_key
'pipelines'
expect
(
json_response
).
not_to
be_empty
expect
(
json_response
[
'pipelines'
]).
not_to
be_empty
end
end
end
end
end
end
...
...
spec/features/projects/commit/builds_spec.rb
View file @
3a922873
require
'spec_helper'
require
'spec_helper'
feature
'project commit pipelines'
do
feature
'project commit pipelines'
,
js:
true
do
given
(
:project
)
{
create
(
:project
)
}
given
(
:project
)
{
create
(
:project
)
}
background
do
background
do
...
...
spec/javascripts/commit/pipelines/mock_data.js.es6
0 → 100644
View file @
3a922873
/* 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',
};
spec/javascripts/commit/pipelines/pipelines_spec.js.es6
0 → 100644
View file @
3a922873
/* 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);
});
});
});
spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6
0 → 100644
View file @
3a922873
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);
});
});
spec/javascripts/fixtures/pipelines_table.html.haml
0 → 100644
View file @
3a922873
#commit-pipeline-table-view
{
data:
{
endpoint:
"endpoint"
}
}
.pipeline-svgs
{
data:
{
"commit_icon_svg"
:
"svg"
}
}
spec/javascripts/vue_
common_
components/commit_spec.js.es6
→
spec/javascripts/vue_
shared/
components/commit_spec.js.es6
View file @
3a922873
require('~/vue_
common_component/commi
t');
require('~/vue_
shared/components/commit
t');
describe('Commit component', () => {
describe('Commit component', () => {
let props;
let props;
...
...
spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6
0 → 100644
View file @
3a922873
/* 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);
});
});
});
spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6
0 → 100644
View file @
3a922873
/* 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);
});
});
});
spec/javascripts/vue_
pagination/
pagination_spec.js.es6
→
spec/javascripts/vue_
shared/components/table_
pagination_spec.js.es6
View file @
3a922873
require('~/lib/utils/common_utils');
require('~/lib/utils/common_utils');
require('~/vue_
pagination/index
');
require('~/vue_
shared/components/table_pagination
');
describe('Pagination component', () => {
describe('Pagination component', () => {
let component;
let component;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment