summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6100
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_service.js.es65
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.js.es6104
-rw-r--r--app/assets/javascripts/environments/environments_bundle.js.es62
-rw-r--r--app/assets/javascripts/environments/vue_resource_interceptor.js.es612
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipelines.js.es64
-rw-r--r--app/assets/javascripts/vue_pipelines_index/time_ago.js.es62
-rw-r--r--app/assets/javascripts/vue_shared/components/pipelines_table.js.es624
-rw-r--r--app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6102
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.js.es6 (renamed from app/assets/javascripts/vue_pagination/index.js.es6)0
-rw-r--r--app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es611
-rw-r--r--app/controllers/projects/commit_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml6
-rw-r--r--app/views/projects/merge_requests/_show.html.haml1
-rw-r--r--spec/javascripts/commit/pipelines/mock_data.js.es690
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_spec.js.es6107
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_store_spec.js.es631
-rw-r--r--spec/javascripts/fixtures/pipelines_table.html.haml2
-rw-r--r--spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es690
-rw-r--r--spec/javascripts/vue_shared/components/pipelines_table_spec.js.es667
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 (renamed from spec/javascripts/vue_pagination/pagination_spec.js.es6)3
22 files changed, 591 insertions, 174 deletions
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6
index a06aad17824..b21d13842a4 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6
+++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6
@@ -3,10 +3,6 @@
//= require vue
//= require_tree .
-//= require vue
-//= require vue-resource
-//= require vue_shared/vue_resource_interceptor
-//= require vue_shared/components/pipelines_table
/**
* Commits View > Pipelines Tab > Pipelines Table.
@@ -14,13 +10,6 @@
*
* Renders Pipelines table in pipelines tab in the commits show view.
* Renders Pipelines table in pipelines tab in the merge request show view.
- *
- * Uses `pipelines-table-component` to render Pipelines table with an API call.
- * Endpoint is provided in HTML and passed as scope.
- * We need a store to make the request and store the received environemnts.
- *
- * 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.
*/
$(() => {
@@ -28,94 +17,11 @@ $(() => {
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
- if (gl.commits.PipelinesTableView) {
- gl.commits.PipelinesTableView.$destroy(true);
+ if (gl.commits.PipelinesTableBundle) {
+ gl.commits.PipelinesTableBundle.$destroy(true);
}
- gl.commits.pipelines.PipelinesTableView = new Vue({
-
+ gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView({
el: document.querySelector('#commit-pipeline-table-view'),
-
- 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} Props for `pipelines-table-component`
- */
- data() {
- const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset;
- const svgsData = document.querySelector('.pipeline-svgs').dataset;
- const store = gl.commits.pipelines.PipelinesStore.create();
-
- // Transform svgs DOMStringMap to a plain Object.
- const svgsObject = Object.keys(svgsData).reduce((acc, element) => {
- acc[element] = svgsData[element];
- return acc;
- }, {});
-
- return {
- endpoint: pipelinesTableData.endpoint,
- svgs: svgsObject,
- store,
- state: store.state,
- isLoading: false,
- error: 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() {
- gl.pipelines.pipelinesService = new PipelinesService(this.endpoint);
-
- this.isLoading = true;
-
- return gl.pipelines.pipelinesService.all()
- .then(response => response.json())
- .then((json) => {
- this.store.store(json);
- this.isLoading = false;
- this.error = false;
- }).catch(() => {
- this.error = true;
- this.isLoading = false;
- new Flash('An error occurred while fetching the pipelines.', '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 && !error && state.pipelines.length === 0">
- <h2 class="blank-state-title js-blank-state-title">
- You don't have any pipelines.
- </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>
- `,
});
});
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6
index 1e6aa73d9cf..f4ed986b0c5 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6
+++ b/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6
@@ -32,3 +32,8 @@ class PipelinesService {
return this.pipelines.get();
}
}
+
+window.gl = window.gl || {};
+gl.commits = gl.commits || {};
+gl.commits.pipelines = gl.commits.pipelines || {};
+gl.commits.pipelines.PipelinesService = PipelinesService;
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6
new file mode 100644
index 00000000000..df7a6455eed
--- /dev/null
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6
@@ -0,0 +1,104 @@
+/* eslint-disable no-new, no-param-reassign */
+/* global Vue, CommitsPipelineStore, PipelinesService, Flash */
+
+//= require vue
+//= require vue-resource
+//= require vue_shared/vue_resource_interceptor
+//= require vue_shared/components/pipelines_table
+
+/**
+ *
+ * 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 = gl.commits.pipelines.PipelinesStore.create();
+
+ // Transform svgs DOMStringMap to a plain Object.
+ const svgsObject = Object.keys(svgsData).reduce((acc, element) => {
+ acc[element] = svgsData[element];
+ return acc;
+ }, {});
+
+ 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() {
+ gl.pipelines.pipelinesService = new PipelinesService(this.endpoint);
+
+ this.isLoading = true;
+ return gl.pipelines.pipelinesService.all()
+ .then(response => response.json())
+ .then((json) => {
+ this.store.store(json);
+ this.isLoading = false;
+ }).catch(() => {
+ this.isLoading = false;
+ new Flash('An error occurred while fetching the pipelines.', '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>
+ `,
+ });
+})();
diff --git a/app/assets/javascripts/environments/environments_bundle.js.es6 b/app/assets/javascripts/environments/environments_bundle.js.es6
index 3b003f6f661..cd205617a97 100644
--- a/app/assets/javascripts/environments/environments_bundle.js.es6
+++ b/app/assets/javascripts/environments/environments_bundle.js.es6
@@ -1,7 +1,7 @@
//= require vue
//= require_tree ./stores/
//= require ./components/environment
-//= require ./vue_resource_interceptor
+//= require vue_shared/vue_resource_interceptor
$(() => {
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/environments/vue_resource_interceptor.js.es6 b/app/assets/javascripts/environments/vue_resource_interceptor.js.es6
deleted file mode 100644
index 406bdbc1c7d..00000000000
--- a/app/assets/javascripts/environments/vue_resource_interceptor.js.es6
+++ /dev/null
@@ -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
- });
-});
diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6
index 34d93ce1b7f..c1daf816060 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6
@@ -1,7 +1,7 @@
/* global Vue, Turbolinks, gl */
/* eslint-disable no-param-reassign */
-//= require vue_pagination/index
+//= require vue_shared/components/table_pagination
//= require ./store.js.es6
//= require vue_shared/components/pipelines_table
@@ -9,7 +9,7 @@
gl.VuePipelines = Vue.extend({
components: {
- glPagination: gl.VueGlPagination,
+ 'gl-pagination': gl.VueGlPagination,
'pipelines-table-component': gl.pipelines.PipelinesTableComponent,
},
diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6
index 655110feba1..61417b28630 100644
--- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6
@@ -1,6 +1,8 @@
/* global Vue, gl */
/* eslint-disable no-param-reassign */
+//= require lib/utils/datetime_utility
+
((gl) => {
gl.VueTimeAgo = Vue.extend({
data() {
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6
index 4b6bba461d7..9bc1ea65e53 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6
+++ b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6
@@ -4,10 +4,9 @@
//= require ./pipelines_table_row
/**
- * Pipelines Table Component
- *
- * Given an array of pipelines, renders a table.
+ * Pipelines Table Component.
*
+ * Given an array of objects, renders a table.
*/
(() => {
@@ -20,11 +19,11 @@
pipelines: {
type: Array,
required: true,
- default: [],
+ default: () => ([]),
},
/**
- * Remove this. Find a better way to do this. don't want to provide this 3 times.
+ * TODO: Remove this when we have webpack.
*/
svgs: {
type: Object,
@@ -41,19 +40,18 @@
<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>
+ <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"
+ <tr is="pipelines-table-row-component"
:pipeline="model"
:svgs="svgs"></tr>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6
index c0ff0c90e4e..375516e3804 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6
+++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6
@@ -7,6 +7,12 @@
//= require vue_shared/components/commit
//= require vue_pipelines_index/pipeline_actions
//= require vue_pipelines_index/time_ago
+
+/**
+ * Pipeline table row.
+ *
+ * Given the received object renders a table row in the pipelines' table.
+ */
(() => {
window.gl = window.gl || {};
gl.pipelines = gl.pipelines || {};
@@ -21,7 +27,7 @@
},
/**
- * Remove this. Find a better way to do this. don't want to provide this 3 times.
+ * TODO: Remove this when we have webpack;
*/
svgs: {
type: Object,
@@ -32,12 +38,10 @@
components: {
'commit-component': gl.CommitComponent,
- runningPipeline: gl.VueRunningPipeline,
- pipelineActions: gl.VuePipelineActions,
- 'vue-stage': gl.VueStage,
- pipelineUrl: gl.VuePipelineUrl,
- pipelineHead: gl.VuePipelineHead,
- statusScope: gl.VueStatusScope,
+ 'pipeline-actions': gl.VuePipelineActions,
+ 'dropdown-stage': gl.VueStage,
+ 'pipeline-url': gl.VuePipelineUrl,
+ 'status-scope': gl.VueStatusScope,
'time-ago': gl.VueTimeAgo,
},
@@ -46,48 +50,48 @@
* If provided, returns the commit tag.
* Needed to render the commit component column.
*
- * TODO: Document this logic, need to ask @grzesiek and @selfup
+ * 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() {
- if (!this.pipeline.commit) {
- return { avatar_url: '', web_url: '', username: '' };
- }
+ 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) {
- return 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 &&
- this.pipeline.commit.author_gravatar_url &&
- this.pipeline.commit.author_name &&
- this.pipeline.commit.author_email) {
- return {
+ 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 undefined;
- },
-
- /**
- * Figure this out!
- * Needed to render the commit component column.
- */
- 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,
- };
+ return commitAuthorInformation;
},
/**
@@ -108,6 +112,9 @@
* 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() {
@@ -169,6 +176,17 @@
},
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());
},
@@ -177,12 +195,12 @@
template: `
<tr class="commit">
<status-scope
- :pipeline='pipeline'
- :svgs='svgs'
+ :pipeline="pipeline"
+ :svgs="svgs"
:match="match">
</status-scope>
- <pipeline-url :pipeline='pipeline'></pipeline-url>
+ <pipeline-url :pipeline="pipeline"></pipeline-url>
<td>
<commit-component
@@ -197,14 +215,20 @@
</td>
<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 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>
+ <time-ago :pipeline="pipeline" :svgs="svgs"></time-ago>
- <pipeline-actions :pipeline='pipeline' :svgs='svgs'></pipeline-actions>
+ <pipeline-actions :pipeline="pipeline" :svgs="svgs"></pipeline-actions>
</tr>
`,
});
diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_shared/components/table_pagination.js.es6
index 605824fa939..605824fa939 100644
--- a/app/assets/javascripts/vue_pagination/index.js.es6
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.js.es6
diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6
index 54c2b4ad369..d627fa2b88a 100644
--- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6
+++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6
@@ -1,10 +1,15 @@
-/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars */
+/* 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(function (response) {
- Vue.activeResources -= 1;
+ next((response) => {
+ if (typeof response.data === 'string') {
+ response.data = JSON.parse(response.data);
+ }
+
+ Vue.activeResources--;
});
});
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index b5a7078a3a1..f880a9862c6 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -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
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index deb084c2e91..68f6208c2be 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -218,7 +218,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
format.json do
render json: PipelineSerializer
.new(project: @project, user: @current_user)
- .with_pagination(request, response)
.represent(@pipelines)
end
end
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index c1f48837e0e..e00ae629e4b 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -44,9 +44,9 @@
= render "projects/merge_requests/show/commits"
#diffs.diffs.tab-pane
-# This tab is always loaded via AJAX
- #pipelines.pipelines.tab-pane
- //TODO: This needs to make a new request every time is opened!
- = render "projects/merge_requests/show/pipelines", endpoint: link_to url_for(params)
+ - if @pipelines.any?
+ #pipelines.pipelines.tab-pane
+ = render "projects/merge_requests/show/pipelines", endpoint: link_to url_for(params)
.mr-loading-status
= spinner
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 8dfe967a937..f131836058b 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -94,7 +94,6 @@
#commits.commits.tab-pane
-# This tab is always loaded via AJAX
#pipelines.pipelines.tab-pane
- //TODO: This needs to make a new request every time is opened!
= 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
diff --git a/spec/javascripts/commit/pipelines/mock_data.js.es6 b/spec/javascripts/commit/pipelines/mock_data.js.es6
new file mode 100644
index 00000000000..5f0f26a013c
--- /dev/null
+++ b/spec/javascripts/commit/pipelines/mock_data.js.es6
@@ -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',
+};
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js.es6 b/spec/javascripts/commit/pipelines/pipelines_spec.js.es6
new file mode 100644
index 00000000000..3bcc0d1eb18
--- /dev/null
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js.es6
@@ -0,0 +1,107 @@
+/* global pipeline, Vue */
+
+//= require 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);
+ });
+ });
+});
diff --git a/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6 b/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6
new file mode 100644
index 00000000000..46a7df3bb21
--- /dev/null
+++ b/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6
@@ -0,0 +1,31 @@
+//= require vue
+//= 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);
+ });
+});
diff --git a/spec/javascripts/fixtures/pipelines_table.html.haml b/spec/javascripts/fixtures/pipelines_table.html.haml
new file mode 100644
index 00000000000..fbe4a434f76
--- /dev/null
+++ b/spec/javascripts/fixtures/pipelines_table.html.haml
@@ -0,0 +1,2 @@
+#commit-pipeline-table-view{ data: { endpoint: "endpoint" } }
+.pipeline-svgs{ data: { "commit_icon_svg": "svg"} }
diff --git a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6 b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6
new file mode 100644
index 00000000000..6825de069e4
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6
@@ -0,0 +1,90 @@
+/* global pipeline */
+
+//= require vue
+//= require vue_shared/components/pipelines_table_row
+//= require commit/pipelines/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);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6 b/spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6
new file mode 100644
index 00000000000..cb1006d44dc
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6
@@ -0,0 +1,67 @@
+/* global pipeline */
+
+//= require vue
+//= require vue_shared/components/pipelines_table
+//= require commit/pipelines/mock_data
+//= require lib/utils/datetime_utility
+
+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);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_pagination/pagination_spec.js.es6 b/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6
index efb11211ce2..6a0fec43d2e 100644
--- a/spec/javascripts/vue_pagination/pagination_spec.js.es6
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6
@@ -1,6 +1,7 @@
//= require vue
//= require lib/utils/common_utils
-//= require vue_pagination/index
+//= require vue_shared/components/table_pagination
+/* global fixture, gl */
describe('Pagination component', () => {
let component;