BigW Consortium Gitlab

Commit 93b08c8c by Eric Eastwood

Reset container width when switching to pipelines MR tab

Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/29539 Conflicts: app/assets/javascripts/commit/pipelines/pipelines_table.js app/assets/javascripts/merge_request_tabs.js spec/javascripts/commit/pipelines/pipelines_spec.js
parent 36cfb0a7
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
/* global Vue, CommitsPipelineStore, PipelinesService, Flash */ /* global Vue, CommitsPipelineStore, PipelinesService, Flash */
window.Vue = require('vue'); window.Vue = require('vue');
require('./pipelines_table'); const PipelinesTable = require('./pipelines_table');
/** /**
* Commits View > Pipelines Tab > Pipelines Table. * Commits View > Pipelines Tab > Pipelines Table.
* Merge Request View > Pipelines Tab > Pipelines Table. * Merge Request View > Pipelines Tab > Pipelines Table.
...@@ -21,7 +22,7 @@ $(() => { ...@@ -21,7 +22,7 @@ $(() => {
} }
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view'); const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView(); gl.commits.pipelines.PipelinesTableBundle = new PipelinesTable();
if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) { if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl); gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl);
......
...@@ -5,9 +5,9 @@ window.Vue = require('vue'); ...@@ -5,9 +5,9 @@ window.Vue = require('vue');
window.Vue.use(require('vue-resource')); window.Vue.use(require('vue-resource'));
require('../../lib/utils/common_utils'); require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor'); require('../../vue_shared/vue_resource_interceptor');
require('../../vue_shared/components/pipelines_table');
require('./pipelines_service'); require('./pipelines_service');
const PipelineStore = require('./pipelines_store'); const PipelineStore = require('./pipelines_store');
const PipelinesTableComponent = require('../../vue_shared/components/pipelines_table');
/** /**
* *
...@@ -19,86 +19,78 @@ const PipelineStore = require('./pipelines_store'); ...@@ -19,86 +19,78 @@ const PipelineStore = require('./pipelines_store');
* Necessary SVG in the table are provided as props. This should be refactored * 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. * as soon as we have Webpack and can load them directly into JS files.
*/ */
module.exports = Vue.component('pipelines-table', {
components: {
'pipelines-table-component': PipelinesTableComponent,
},
(() => { /**
window.gl = window.gl || {}; * Accesses the DOM to provide the needed data.
gl.commits = gl.commits || {}; * Returns the necessary props to render `pipelines-table-component` component.
gl.commits.pipelines = gl.commits.pipelines || {}; *
* @return {Object}
*/
data() {
const store = new PipelineStore();
gl.commits.pipelines.PipelinesTableView = Vue.component('pipelines-table', { return {
endpoint: null,
store,
state: store.state,
isLoading: false,
};
},
components: { /**
'pipelines-table-component': gl.pipelines.PipelinesTableComponent, * When the component is about to be mounted, tell the service to fetch the data
}, *
* 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.
*
*/
beforeMount() {
this.endpoint = this.$el.dataset.endpoint;
const pipelinesService = new gl.commits.pipelines.PipelinesService(this.endpoint);
/** this.isLoading = true;
* Accesses the DOM to provide the needed data. return pipelinesService.all()
* Returns the necessary props to render `pipelines-table-component` component. .then(response => response.json())
* .then((json) => {
* @return {Object} // depending of the endpoint the response can either bring a `pipelines` key or not.
*/ const pipelines = json.pipelines || json;
data() { this.store.storePipelines(pipelines);
const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset; this.isLoading = false;
const store = new PipelineStore(); })
.catch(() => {
this.isLoading = false;
new Flash('An error occurred while fetching the pipelines, please reload the page again.', 'alert');
});
},
return { beforeUpdate() {
endpoint: pipelinesTableData.endpoint, if (this.state.pipelines.length && this.$children) {
store, PipelineStore.startTimeAgoLoops.call(this, Vue);
state: store.state, }
isLoading: false, },
};
},
/** template: `
* When the component is about to be mounted, tell the service to fetch the data <div class="pipelines">
* <div class="realtime-loading" v-if="isLoading">
* A request to fetch the pipelines will be made. <i class="fa fa-spinner fa-spin"></i>
* In case of a successfull response we will store the data in the provided </div>
* store, in case of a failed response we need to warn the user.
*
*/
beforeMount() {
const pipelinesService = new gl.commits.pipelines.PipelinesService(this.endpoint);
this.isLoading = true;
return pipelinesService.all()
.then(response => response.json())
.then((json) => {
// depending of the endpoint the response can either bring a `pipelines` key or not.
const pipelines = json.pipelines || json;
this.store.storePipelines(pipelines);
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
new Flash('An error occurred while fetching the pipelines, please reload the page again.', 'alert');
});
},
beforeUpdate() {
if (this.state.pipelines.length && this.$children) {
PipelineStore.startTimeAgoLoops.call(this, Vue);
}
},
template: `
<div class="pipelines">
<div class="realtime-loading" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
</div>
<div class="blank-state blank-state-no-icon" <div class="blank-state blank-state-no-icon"
v-if="!isLoading && state.pipelines.length === 0"> v-if="!isLoading && state.pipelines.length === 0">
<h2 class="blank-state-title js-blank-state-title"> <h2 class="blank-state-title js-blank-state-title">
No pipelines to show No pipelines to show
</h2> </h2>
</div> </div>
<div class="table-holder pipelines" <div class="table-holder pipelines"
v-if="!isLoading && state.pipelines.length > 0"> v-if="!isLoading && state.pipelines.length > 0">
<pipelines-table-component :pipelines="state.pipelines"/> <pipelines-table-component :pipelines="state.pipelines"/>
</div>
</div> </div>
`, </div>
}); `,
})(); });
...@@ -3,9 +3,12 @@ ...@@ -3,9 +3,12 @@
/* global Cookies */ /* global Cookies */
/* global Flash */ /* global Flash */
require('./breakpoints'); import Cookies from 'js-cookie';
window.Cookies = require('js-cookie');
require('./flash'); import './breakpoints';
import './flash';
const PipelinesTable = require('./commit/pipelines/pipelines_table');
/* eslint-disable max-len */ /* eslint-disable max-len */
// MergeRequestTabs // MergeRequestTabs
...@@ -97,6 +100,13 @@ require('./flash'); ...@@ -97,6 +100,13 @@ require('./flash');
.off('click', this.clickTab); .off('click', this.clickTab);
} }
destroy() {
this.unbindEvents();
if (this.commitPipelinesTable) {
this.commitPipelinesTable.$destroy();
}
}
showTab(e) { showTab(e) {
e.preventDefault(); e.preventDefault();
this.activateTab($(e.target).data('action')); this.activateTab($(e.target).data('action'));
...@@ -131,12 +141,8 @@ require('./flash'); ...@@ -131,12 +141,8 @@ require('./flash');
offset: 0, offset: 0,
}); });
} else if (action === 'pipelines') { } else if (action === 'pipelines') {
if (this.pipelinesLoaded) { this.resetViewContainer();
return; this.loadPipelines();
}
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl);
this.pipelinesLoaded = true;
} else { } else {
this.expandView(); this.expandView();
this.resetViewContainer(); this.resetViewContainer();
...@@ -225,6 +231,18 @@ require('./flash'); ...@@ -225,6 +231,18 @@ require('./flash');
}); });
} }
loadPipelines() {
if (this.pipelinesLoaded) {
return;
}
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
// Could already be mounted from the `pipelines_bundle`
if (pipelineTableViewEl) {
this.commitPipelinesTable = new PipelinesTable().$mount(pipelineTableViewEl);
}
this.pipelinesLoaded = true;
}
loadDiff(source) { loadDiff(source) {
if (this.diffsLoaded) { if (this.diffsLoaded) {
return; return;
......
...@@ -2,17 +2,18 @@ ...@@ -2,17 +2,18 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
window.Vue = require('vue'); window.Vue = require('vue');
require('../vue_shared/components/table_pagination'); require('../vue_shared/components/table_pagination');
require('./store'); require('./store');
require('../vue_shared/components/pipelines_table');
const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_store'); const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_store');
const PipelinesTableComponent = require('../vue_shared/components/pipelines_table');
((gl) => { ((gl) => {
gl.VuePipelines = Vue.extend({ gl.VuePipelines = Vue.extend({
components: { components: {
'gl-pagination': gl.VueGlPagination, 'gl-pagination': gl.VueGlPagination,
'pipelines-table-component': gl.pipelines.PipelinesTableComponent, 'pipelines-table-component': PipelinesTableComponent,
}, },
data() { data() {
......
...@@ -8,45 +8,39 @@ require('./pipelines_table_row'); ...@@ -8,45 +8,39 @@ require('./pipelines_table_row');
* Given an array of objects, renders a table. * Given an array of objects, renders a table.
*/ */
(() => { module.exports = {
window.gl = window.gl || {}; props: {
gl.pipelines = gl.pipelines || {}; pipelines: {
type: Array,
gl.pipelines.PipelinesTableComponent = Vue.component('pipelines-table-component', { required: true,
default: () => ([]),
props: {
pipelines: {
type: Array,
required: true,
default: () => ([]),
},
}, },
components: { },
'pipelines-table-row-component': gl.pipelines.PipelinesTableRowComponent,
}, components: {
'pipelines-table-row-component': gl.pipelines.PipelinesTableRowComponent,
},
template: ` template: `
<table class="table ci-table"> <table class="table ci-table">
<thead> <thead>
<tr> <tr>
<th class="js-pipeline-status pipeline-status">Status</th> <th class="js-pipeline-status pipeline-status">Status</th>
<th class="js-pipeline-info pipeline-info">Pipeline</th> <th class="js-pipeline-info pipeline-info">Pipeline</th>
<th class="js-pipeline-commit pipeline-commit">Commit</th> <th class="js-pipeline-commit pipeline-commit">Commit</th>
<th class="js-pipeline-stages pipeline-stages">Stages</th> <th class="js-pipeline-stages pipeline-stages">Stages</th>
<th class="js-pipeline-date pipeline-date"></th> <th class="js-pipeline-date pipeline-date"></th>
<th class="js-pipeline-actions pipeline-actions"></th> <th class="js-pipeline-actions pipeline-actions"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<template v-for="model in pipelines" <template v-for="model in pipelines"
v-bind:model="model"> v-bind:model="model">
<tr is="pipelines-table-row-component" <tr is="pipelines-table-row-component"
:pipeline="model"></tr> :pipeline="model"></tr>
</template> </template>
</tbody> </tbody>
</table> </table>
`, `,
}); };
})();
unless Rails.env.production? unless Rails.env.production?
namespace :karma do namespace :karma do
desc 'GitLab | Karma | Generate fixtures for JavaScript tests' desc 'GitLab | Karma | Generate fixtures for JavaScript tests'
RSpec::Core::RakeTask.new(:fixtures) do |t| RSpec::Core::RakeTask.new(:fixtures, [:pattern]) do |t, args|
args.with_defaults(pattern: 'spec/javascripts/fixtures/*.rb')
ENV['NO_KNAPSACK'] = 'true' ENV['NO_KNAPSACK'] = 'true'
t.pattern = 'spec/javascripts/fixtures/*.rb' t.pattern = args[:pattern]
t.rspec_opts = '--format documentation' t.rspec_opts = '--format documentation'
end end
......
/* global pipeline, Vue */ /* global pipeline, Vue */
const PipelinesTable = require('~/commit/pipelines/pipelines_table');
require('~/flash'); require('~/flash');
require('~/commit/pipelines/pipelines_store'); require('~/commit/pipelines/pipelines_store');
require('~/commit/pipelines/pipelines_service'); require('~/commit/pipelines/pipelines_service');
require('~/commit/pipelines/pipelines_table');
require('~/vue_shared/vue_resource_interceptor'); require('~/vue_shared/vue_resource_interceptor');
const pipeline = require('./mock_data'); const pipeline = require('./mock_data');
describe('Pipelines table in Commits and Merge requests', () => { describe('Pipelines table in Commits and Merge requests', () => {
preloadFixtures('static/pipelines_table.html.raw'); preloadFixtures('static/pipelines_table.html.raw');
let component;
beforeEach(() => { beforeEach(() => {
loadFixtures('static/pipelines_table.html.raw'); loadFixtures('static/pipelines_table.html.raw');
}); });
...@@ -24,19 +27,20 @@ describe('Pipelines table in Commits and Merge requests', () => { ...@@ -24,19 +27,20 @@ describe('Pipelines table in Commits and Merge requests', () => {
beforeEach(() => { beforeEach(() => {
Vue.http.interceptors.push(pipelinesEmptyResponse); Vue.http.interceptors.push(pipelinesEmptyResponse);
component = new PipelinesTable({
el: document.querySelector('#commit-pipeline-table-view'),
});
}); });
afterEach(() => { afterEach(() => {
Vue.http.interceptors = _.without( Vue.http.interceptors = _.without(
Vue.http.interceptors, pipelinesEmptyResponse, Vue.http.interceptors, pipelinesEmptyResponse,
); );
component.$destroy();
}); });
it('should render the empty state', (done) => { it('should render the empty state', (done) => {
const component = new gl.commits.pipelines.PipelinesTableView({
el: document.querySelector('#commit-pipeline-table-view'),
});
setTimeout(() => { setTimeout(() => {
expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain('No pipelines to show'); expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain('No pipelines to show');
done(); done();
...@@ -53,19 +57,20 @@ describe('Pipelines table in Commits and Merge requests', () => { ...@@ -53,19 +57,20 @@ describe('Pipelines table in Commits and Merge requests', () => {
beforeEach(() => { beforeEach(() => {
Vue.http.interceptors.push(pipelinesResponse); Vue.http.interceptors.push(pipelinesResponse);
component = new PipelinesTable({
el: document.querySelector('#commit-pipeline-table-view'),
});
}); });
afterEach(() => { afterEach(() => {
Vue.http.interceptors = _.without( Vue.http.interceptors = _.without(
Vue.http.interceptors, pipelinesResponse, Vue.http.interceptors, pipelinesResponse,
); );
component.$destroy();
}); });
it('should render a table with the received pipelines', (done) => { 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(() => { setTimeout(() => {
expect(component.$el.querySelectorAll('table > tbody > tr').length).toEqual(1); expect(component.$el.querySelectorAll('table > tbody > tr').length).toEqual(1);
done(); done();
...@@ -83,19 +88,20 @@ describe('Pipelines table in Commits and Merge requests', () => { ...@@ -83,19 +88,20 @@ describe('Pipelines table in Commits and Merge requests', () => {
beforeEach(() => { beforeEach(() => {
Vue.http.interceptors.push(pipelinesErrorResponse); Vue.http.interceptors.push(pipelinesErrorResponse);
component = new PipelinesTable({
el: document.querySelector('#commit-pipeline-table-view'),
});
}); });
afterEach(() => { afterEach(() => {
Vue.http.interceptors = _.without( Vue.http.interceptors = _.without(
Vue.http.interceptors, pipelinesErrorResponse, Vue.http.interceptors, pipelinesErrorResponse,
); );
component.$destroy();
}); });
it('should render empty state', (done) => { it('should render empty state', (done) => {
const component = new gl.commits.pipelines.PipelinesTableView({
el: document.querySelector('#commit-pipeline-table-view'),
});
setTimeout(() => { setTimeout(() => {
expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain('No pipelines to show'); expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain('No pipelines to show');
done(); done();
......
...@@ -6,6 +6,15 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont ...@@ -6,6 +6,15 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') } let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') }
let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
let(:pipeline) do
create(
:ci_pipeline,
project: merge_request.source_project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha
)
end
render_views render_views
...@@ -18,7 +27,8 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont ...@@ -18,7 +27,8 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
end end
it 'merge_requests/merge_request_with_task_list.html.raw' do |example| it 'merge_requests/merge_request_with_task_list.html.raw' do |example|
merge_request = create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') create(:ci_build, :pending, pipeline: pipeline)
render_merge_request(example.description, merge_request) render_merge_request(example.description, merge_request)
end end
......
...@@ -38,6 +38,10 @@ require('vendor/jquery.scrollTo'); ...@@ -38,6 +38,10 @@ require('vendor/jquery.scrollTo');
} }
}); });
afterEach(function () {
this.class.destroy();
});
describe('#activateTab', function () { describe('#activateTab', function () {
beforeEach(function () { beforeEach(function () {
spyOn($, 'ajax').and.callFake(function () {}); spyOn($, 'ajax').and.callFake(function () {});
...@@ -200,6 +204,42 @@ require('vendor/jquery.scrollTo'); ...@@ -200,6 +204,42 @@ require('vendor/jquery.scrollTo');
expect(this.subject('show')).toBe('/foo/bar/merge_requests/1'); expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
}); });
}); });
describe('#tabShown', () => {
beforeEach(function () {
loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
});
describe('with "Side-by-side"/parallel diff view', () => {
beforeEach(function () {
this.class.diffViewType = () => 'parallel';
});
it('maintains `container-limited` for pipelines tab', function (done) {
const asyncClick = function (selector) {
return new Promise((resolve) => {
setTimeout(() => {
document.querySelector(selector).click();
resolve();
});
});
};
asyncClick('.merge-request-tabs .pipelines-tab a')
.then(() => asyncClick('.merge-request-tabs .diffs-tab a'))
.then(() => asyncClick('.merge-request-tabs .pipelines-tab a'))
.then(() => {
const hasContainerLimitedClass = document.querySelector('.content-wrapper .container-fluid').classList.contains('container-limited');
expect(hasContainerLimitedClass).toBe(true);
})
.then(done)
.catch((err) => {
done.fail(`Something went wrong clicking MR tabs: ${err.message}\n${err.stack}`);
});
});
});
});
describe('#loadDiff', function () { describe('#loadDiff', function () {
it('requires an absolute pathname', function () { it('requires an absolute pathname', function () {
spyOn($, 'ajax').and.callFake(function (options) { spyOn($, 'ajax').and.callFake(function (options) {
......
require('~/vue_shared/components/pipelines_table');
require('~/lib/utils/datetime_utility'); require('~/lib/utils/datetime_utility');
const Vue = require('vue');
const pipeline = require('../../commit/pipelines/mock_data'); const pipeline = require('../../commit/pipelines/mock_data');
const PipelinesTable = require('~/vue_shared/components/pipelines_table');
const PipelinesTableComponent = Vue.extend(PipelinesTable);
describe('Pipelines Table', () => { describe('Pipelines Table', () => {
preloadFixtures('static/environments/element.html.raw'); preloadFixtures('static/environments/element.html.raw');
...@@ -12,7 +15,7 @@ describe('Pipelines Table', () => { ...@@ -12,7 +15,7 @@ describe('Pipelines Table', () => {
describe('table', () => { describe('table', () => {
let component; let component;
beforeEach(() => { beforeEach(() => {
component = new gl.pipelines.PipelinesTableComponent({ component = new PipelinesTableComponent({
el: document.querySelector('.test-dom-element'), el: document.querySelector('.test-dom-element'),
propsData: { propsData: {
pipelines: [], pipelines: [],
...@@ -21,6 +24,10 @@ describe('Pipelines Table', () => { ...@@ -21,6 +24,10 @@ describe('Pipelines Table', () => {
}); });
}); });
afterEach(() => {
component.$destroy();
});
it('should render a table', () => { it('should render a table', () => {
expect(component.$el).toEqual('TABLE'); expect(component.$el).toEqual('TABLE');
}); });
...@@ -37,7 +44,7 @@ describe('Pipelines Table', () => { ...@@ -37,7 +44,7 @@ describe('Pipelines Table', () => {
describe('without data', () => { describe('without data', () => {
it('should render an empty table', () => { it('should render an empty table', () => {
const component = new gl.pipelines.PipelinesTableComponent({ const component = new PipelinesTableComponent({
el: document.querySelector('.test-dom-element'), el: document.querySelector('.test-dom-element'),
propsData: { propsData: {
pipelines: [], pipelines: [],
...@@ -50,7 +57,7 @@ describe('Pipelines Table', () => { ...@@ -50,7 +57,7 @@ describe('Pipelines Table', () => {
describe('with data', () => { describe('with data', () => {
it('should render rows', () => { it('should render rows', () => {
const component = new gl.pipelines.PipelinesTableComponent({ const component = new PipelinesTableComponent({
el: document.querySelector('.test-dom-element'), el: document.querySelector('.test-dom-element'),
propsData: { propsData: {
pipelines: [pipeline], pipelines: [pipeline],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment