Commit 2fdae334 authored by Nathan Friend's avatar Nathan Friend

Merge branch '218725-refactor-test-report-store' into 'master'

Refactor pipeline test report store

See merge request gitlab-org/gitlab!35789
parents 3fc14674 a7998139
...@@ -4,7 +4,6 @@ import { GlLoadingIcon } from '@gitlab/ui'; ...@@ -4,7 +4,6 @@ import { GlLoadingIcon } from '@gitlab/ui';
import TestSuiteTable from './test_suite_table.vue'; import TestSuiteTable from './test_suite_table.vue';
import TestSummary from './test_summary.vue'; import TestSummary from './test_summary.vue';
import TestSummaryTable from './test_summary_table.vue'; import TestSummaryTable from './test_summary_table.vue';
import store from '~/pipelines/stores/test_reports';
export default { export default {
name: 'TestReports', name: 'TestReports',
...@@ -14,7 +13,6 @@ export default { ...@@ -14,7 +13,6 @@ export default {
TestSummary, TestSummary,
TestSummaryTable, TestSummaryTable,
}, },
store,
computed: { computed: {
...mapState(['isLoading', 'selectedSuite', 'testReports']), ...mapState(['isLoading', 'selectedSuite', 'testReports']),
showSuite() { showSuite() {
...@@ -25,8 +23,11 @@ export default { ...@@ -25,8 +23,11 @@ export default {
return testSuites.length > 0; return testSuites.length > 0;
}, },
}, },
created() {
this.fetchSummary();
},
methods: { methods: {
...mapActions(['setSelectedSuite', 'removeSelectedSuite']), ...mapActions(['fetchSummary', 'setSelectedSuite', 'removeSelectedSuite']),
summaryBackClick() { summaryBackClick() {
this.removeSelectedSuite(); this.removeSelectedSuite();
}, },
......
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import store from '~/pipelines/stores/test_reports';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { GlTooltipDirective } from '@gitlab/ui'; import { GlTooltipDirective } from '@gitlab/ui';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue'; import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
...@@ -15,7 +14,6 @@ export default { ...@@ -15,7 +14,6 @@ export default {
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
store,
props: { props: {
heading: { heading: {
type: String, type: String,
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import store from '~/pipelines/stores/test_reports';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue'; import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
export default { export default {
...@@ -14,7 +13,6 @@ export default { ...@@ -14,7 +13,6 @@ export default {
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
store,
props: { props: {
heading: { heading: {
type: String, type: String,
......
...@@ -10,8 +10,7 @@ import PipelinesMediator from './pipeline_details_mediator'; ...@@ -10,8 +10,7 @@ import PipelinesMediator from './pipeline_details_mediator';
import pipelineHeader from './components/header_component.vue'; import pipelineHeader from './components/header_component.vue';
import eventHub from './event_hub'; import eventHub from './event_hub';
import TestReports from './components/test_reports/test_reports.vue'; import TestReports from './components/test_reports/test_reports.vue';
import testReportsStore from './stores/test_reports'; import createTestReportsStore from './stores/test_reports';
import axios from '~/lib/utils/axios_utils';
Vue.use(Translate); Vue.use(Translate);
...@@ -93,15 +92,11 @@ const createPipelineHeaderApp = mediator => { ...@@ -93,15 +92,11 @@ const createPipelineHeaderApp = mediator => {
}); });
}; };
const createPipelinesTabs = dataset => { const createPipelinesTabs = testReportsStore => {
const tabsElement = document.querySelector('.pipelines-tabs'); const tabsElement = document.querySelector('.pipelines-tabs');
const testReportsEnabled =
window.gon && window.gon.features && window.gon.features.junitPipelineView;
if (tabsElement && testReportsEnabled) {
const fetchReportsAction = 'fetchReports';
testReportsStore.dispatch('setEndpoint', dataset.testReportEndpoint);
if (tabsElement) {
const fetchReportsAction = 'fetchFullReport';
const isTestTabActive = Boolean( const isTestTabActive = Boolean(
document.querySelector('.pipelines-tabs > li > a.test-tab.active'), document.querySelector('.pipelines-tabs > li > a.test-tab.active'),
); );
...@@ -121,28 +116,25 @@ const createPipelinesTabs = dataset => { ...@@ -121,28 +116,25 @@ const createPipelinesTabs = dataset => {
} }
}; };
const createTestDetails = detailsEndpoint => { const createTestDetails = (fullReportEndpoint, summaryEndpoint) => {
if (!window.gon?.features?.junitPipelineView) {
return;
}
const testReportsStore = createTestReportsStore({ fullReportEndpoint, summaryEndpoint });
createPipelinesTabs(testReportsStore);
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
el: '#js-pipeline-tests-detail', el: '#js-pipeline-tests-detail',
components: { components: {
TestReports, TestReports,
}, },
store: testReportsStore,
render(createElement) { render(createElement) {
return createElement('test-reports'); return createElement('test-reports');
}, },
}); });
axios
.get(detailsEndpoint)
.then(({ data }) => {
if (!data.total_count) {
return;
}
document.querySelector('.js-test-report-badge-counter').innerHTML = data.total_count;
})
.catch(() => {});
}; };
const createDagApp = () => { const createDagApp = () => {
...@@ -178,7 +170,6 @@ export default () => { ...@@ -178,7 +170,6 @@ export default () => {
createPipelinesDetailApp(mediator); createPipelinesDetailApp(mediator);
createPipelineHeaderApp(mediator); createPipelineHeaderApp(mediator);
createPipelinesTabs(dataset); createTestDetails(dataset.testReportEndpoint, dataset.testReportsCountEndpoint);
createTestDetails(dataset.testReportsCountEndpoint);
createDagApp(); createDagApp();
}; };
...@@ -3,17 +3,30 @@ import * as types from './mutation_types'; ...@@ -3,17 +3,30 @@ import * as types from './mutation_types';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
export const setEndpoint = ({ commit }, data) => commit(types.SET_ENDPOINT, data); export const fetchSummary = ({ state, commit }) => {
return axios
.get(state.summaryEndpoint)
.then(({ data }) => {
commit(types.SET_SUMMARY, data);
export const fetchReports = ({ state, commit, dispatch }) => { // Set the tab counter badge to total_count
// This is temporary until we can server-side render that count number
// (see https://gitlab.com/gitlab-org/gitlab/-/issues/223134)
if (data.total_count !== undefined) {
document.querySelector('.js-test-report-badge-counter').innerHTML = data.total_count;
}
})
.catch(() => {
createFlash(s__('TestReports|There was an error fetching the summary.'));
});
};
export const fetchFullReport = ({ state, commit, dispatch }) => {
dispatch('toggleLoading'); dispatch('toggleLoading');
return axios return axios
.get(state.endpoint) .get(state.fullReportEndpoint)
.then(response => { .then(({ data }) => commit(types.SET_REPORTS, data))
const { data } = response;
commit(types.SET_REPORTS, data);
})
.catch(() => { .catch(() => {
createFlash(s__('TestReports|There was an error fetching the test reports.')); createFlash(s__('TestReports|There was an error fetching the test reports.'));
}) })
......
...@@ -7,9 +7,10 @@ import mutations from './mutations'; ...@@ -7,9 +7,10 @@ import mutations from './mutations';
Vue.use(Vuex); Vue.use(Vuex);
export default new Vuex.Store({ export default initialState =>
new Vuex.Store({
actions, actions,
getters, getters,
mutations, mutations,
state, state: state(initialState),
}); });
export const SET_ENDPOINT = 'SET_ENDPOINT';
export const SET_REPORTS = 'SET_REPORTS'; export const SET_REPORTS = 'SET_REPORTS';
export const SET_SELECTED_SUITE = 'SET_SELECTED_SUITE'; export const SET_SELECTED_SUITE = 'SET_SELECTED_SUITE';
export const SET_SUMMARY = 'SET_SUMMARY';
export const TOGGLE_LOADING = 'TOGGLE_LOADING'; export const TOGGLE_LOADING = 'TOGGLE_LOADING';
import * as types from './mutation_types'; import * as types from './mutation_types';
export default { export default {
[types.SET_ENDPOINT](state, endpoint) {
Object.assign(state, { endpoint });
},
[types.SET_REPORTS](state, testReports) { [types.SET_REPORTS](state, testReports) {
Object.assign(state, { testReports }); Object.assign(state, { testReports });
}, },
...@@ -13,6 +9,10 @@ export default { ...@@ -13,6 +9,10 @@ export default {
Object.assign(state, { selectedSuite }); Object.assign(state, { selectedSuite });
}, },
[types.SET_SUMMARY](state, summary) {
Object.assign(state, { summary });
},
[types.TOGGLE_LOADING](state) { [types.TOGGLE_LOADING](state) {
Object.assign(state, { isLoading: !state.isLoading }); Object.assign(state, { isLoading: !state.isLoading });
}, },
......
export default () => ({ export default ({ fullReportEndpoint = '', summaryEndpoint = '' }) => ({
endpoint: '', summaryEndpoint,
fullReportEndpoint,
testReports: {}, testReports: {},
selectedSuite: {}, selectedSuite: {},
summary: {},
isLoading: false, isLoading: false,
}); });
...@@ -22626,6 +22626,9 @@ msgstr "" ...@@ -22626,6 +22626,9 @@ msgstr ""
msgid "TestReports|There are no tests to show." msgid "TestReports|There are no tests to show."
msgstr "" msgstr ""
msgid "TestReports|There was an error fetching the summary."
msgstr ""
msgid "TestReports|There was an error fetching the test reports." msgid "TestReports|There was an error fetching the test reports."
msgstr "" msgstr ""
......
...@@ -383,8 +383,8 @@ RSpec.describe 'Pipeline', :js do ...@@ -383,8 +383,8 @@ RSpec.describe 'Pipeline', :js do
context 'without test reports' do context 'without test reports' do
let(:pipeline) { create(:ci_pipeline, project: project) } let(:pipeline) { create(:ci_pipeline, project: project) }
it 'shows nothing' do it 'shows zero' do
expect(page.find('.js-test-report-badge-counter', visible: :all).text).to eq("") expect(page.find('.js-test-report-badge-counter', visible: :all).text).to eq("0")
end end
end end
end end
......
...@@ -14,12 +14,16 @@ describe('Actions TestReports Store', () => { ...@@ -14,12 +14,16 @@ describe('Actions TestReports Store', () => {
let state; let state;
const testReports = getJSONFixture('pipelines/test_report.json'); const testReports = getJSONFixture('pipelines/test_report.json');
const summary = { total_count: 1 };
const endpoint = `${TEST_HOST}/test_reports.json`; const fullReportEndpoint = `${TEST_HOST}/test_reports.json`;
const summaryEndpoint = `${TEST_HOST}/test_reports/summary.json`;
const defaultState = { const defaultState = {
endpoint, fullReportEndpoint,
summaryEndpoint,
testReports: {}, testReports: {},
selectedSuite: {}, selectedSuite: {},
summary: {},
}; };
beforeEach(() => { beforeEach(() => {
...@@ -31,14 +35,47 @@ describe('Actions TestReports Store', () => { ...@@ -31,14 +35,47 @@ describe('Actions TestReports Store', () => {
mock.restore(); mock.restore();
}); });
describe('fetch reports', () => { describe('fetch report summary', () => {
beforeEach(() => { beforeEach(() => {
mock.onGet(`${TEST_HOST}/test_reports.json`).replyOnce(200, testReports, {}); mock.onGet(summaryEndpoint).replyOnce(200, summary, {});
}); });
it('sets testReports and shows tests', done => { it('sets testReports and shows tests', done => {
testAction( testAction(
actions.fetchReports, actions.fetchSummary,
null,
state,
[{ type: types.SET_SUMMARY, payload: summary }],
[],
done,
);
});
it('should create flash on API error', done => {
testAction(
actions.fetchSummary,
null,
{
summaryEndpoint: null,
},
[],
[],
() => {
expect(createFlash).toHaveBeenCalled();
done();
},
);
});
});
describe('fetch full report', () => {
beforeEach(() => {
mock.onGet(fullReportEndpoint).replyOnce(200, testReports, {});
});
it('sets testReports and shows tests', done => {
testAction(
actions.fetchFullReport,
null, null,
state, state,
[{ type: types.SET_REPORTS, payload: testReports }], [{ type: types.SET_REPORTS, payload: testReports }],
...@@ -49,10 +86,10 @@ describe('Actions TestReports Store', () => { ...@@ -49,10 +86,10 @@ describe('Actions TestReports Store', () => {
it('should create flash on API error', done => { it('should create flash on API error', done => {
testAction( testAction(
actions.fetchReports, actions.fetchFullReport,
null, null,
{ {
endpoint: null, fullReportEndpoint: null,
}, },
[], [],
[{ type: 'toggleLoading' }, { type: 'toggleLoading' }], [{ type: 'toggleLoading' }, { type: 'toggleLoading' }],
......
...@@ -18,15 +18,6 @@ describe('Mutations TestReports Store', () => { ...@@ -18,15 +18,6 @@ describe('Mutations TestReports Store', () => {
mockState = defaultState; mockState = defaultState;
}); });
describe('set endpoint', () => {
it('should set endpoint', () => {
const expectedState = { ...mockState, endpoint: 'foo' };
mutations[types.SET_ENDPOINT](mockState, 'foo');
expect(mockState.endpoint).toEqual(expectedState.endpoint);
});
});
describe('set reports', () => { describe('set reports', () => {
it('should set testReports', () => { it('should set testReports', () => {
const expectedState = { ...mockState, testReports }; const expectedState = { ...mockState, testReports };
...@@ -45,6 +36,15 @@ describe('Mutations TestReports Store', () => { ...@@ -45,6 +36,15 @@ describe('Mutations TestReports Store', () => {
}); });
}); });
describe('set summary', () => {
it('should set summary', () => {
const summary = { total_count: 1 };
mutations[types.SET_SUMMARY](mockState, summary);
expect(mockState.summary).toEqual(summary);
});
});
describe('toggle loading', () => { describe('toggle loading', () => {
it('should set to true', () => { it('should set to true', () => {
const expectedState = { ...mockState, isLoading: true }; const expectedState = { ...mockState, isLoading: true };
......
import Vuex from 'vuex'; import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import { getJSONFixture } from 'helpers/fixtures'; import { getJSONFixture } from 'helpers/fixtures';
import TestReports from '~/pipelines/components/test_reports/test_reports.vue'; import TestReports from '~/pipelines/components/test_reports/test_reports.vue';
import * as actions from '~/pipelines/stores/test_reports/actions'; import * as actions from '~/pipelines/stores/test_reports/actions';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Test reports app', () => { describe('Test reports app', () => {
let wrapper; let wrapper;
let store; let store;
...@@ -22,11 +25,15 @@ describe('Test reports app', () => { ...@@ -22,11 +25,15 @@ describe('Test reports app', () => {
testReports, testReports,
...state, ...state,
}, },
actions, actions: {
...actions,
fetchSummary: () => {},
},
}); });
wrapper = shallowMount(TestReports, { wrapper = shallowMount(TestReports, {
store, store,
localVue,
}); });
}; };
......
import Vuex from 'vuex'; import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import { getJSONFixture } from 'helpers/fixtures'; import { getJSONFixture } from 'helpers/fixtures';
import SuiteTable from '~/pipelines/components/test_reports/test_suite_table.vue'; import SuiteTable from '~/pipelines/components/test_reports/test_suite_table.vue';
import * as getters from '~/pipelines/stores/test_reports/getters'; import * as getters from '~/pipelines/stores/test_reports/getters';
import { TestStatus } from '~/pipelines/constants'; import { TestStatus } from '~/pipelines/constants';
import skippedTestCases from './mock_data'; import skippedTestCases from './mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Test reports suite table', () => { describe('Test reports suite table', () => {
let wrapper; let wrapper;
let store; let store;
...@@ -32,6 +35,7 @@ describe('Test reports suite table', () => { ...@@ -32,6 +35,7 @@ describe('Test reports suite table', () => {
wrapper = shallowMount(SuiteTable, { wrapper = shallowMount(SuiteTable, {
store, store,
localVue,
}); });
}; };
......
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