Commit 9689e8f0 authored by Enrique Alcántara's avatar Enrique Alcántara

Merge branch '211839-test-report-suite' into 'master'

Get test suite details on click

See merge request gitlab-org/gitlab!37232
parents 1285e22d 400f918c
...@@ -29,7 +29,7 @@ export default { ...@@ -29,7 +29,7 @@ export default {
}, },
methods: { methods: {
...mapActions([ ...mapActions([
'fetchFullReport', 'fetchTestSuite',
'fetchSummary', 'fetchSummary',
'setSelectedSuiteIndex', 'setSelectedSuiteIndex',
'removeSelectedSuiteIndex', 'removeSelectedSuiteIndex',
...@@ -40,10 +40,8 @@ export default { ...@@ -40,10 +40,8 @@ export default {
summaryTableRowClick(index) { summaryTableRowClick(index) {
this.setSelectedSuiteIndex(index); this.setSelectedSuiteIndex(index);
// Fetch the full report when the user clicks to see more details // Fetch the test suite when the user clicks to see more details
if (!this.hasFullReport) { this.fetchTestSuite(index);
this.fetchFullReport();
}
}, },
beforeEnterTransition() { beforeEnterTransition() {
document.documentElement.style.overflowX = 'hidden'; document.documentElement.style.overflowX = 'hidden';
......
...@@ -122,11 +122,12 @@ const createTestDetails = () => { ...@@ -122,11 +122,12 @@ const createTestDetails = () => {
} }
const el = document.querySelector('#js-pipeline-tests-detail'); const el = document.querySelector('#js-pipeline-tests-detail');
const { fullReportEndpoint, summaryEndpoint, countEndpoint } = el?.dataset || {}; const { fullReportEndpoint, summaryEndpoint, suiteEndpoint, countEndpoint } = el?.dataset || {};
const testReportsStore = createTestReportsStore({ const testReportsStore = createTestReportsStore({
fullReportEndpoint, fullReportEndpoint,
summaryEndpoint: summaryEndpoint || countEndpoint, summaryEndpoint: summaryEndpoint || countEndpoint,
suiteEndpoint,
useBuildSummaryReport: window.gon?.features?.buildReportSummary, useBuildSummaryReport: window.gon?.features?.buildReportSummary,
}); });
......
...@@ -33,6 +33,31 @@ export const fetchSummary = ({ state, commit, dispatch }) => { ...@@ -33,6 +33,31 @@ export const fetchSummary = ({ state, commit, dispatch }) => {
}); });
}; };
export const fetchTestSuite = ({ state, commit, dispatch }, index) => {
const { hasFullSuite } = state.testReports?.test_suites?.[index] || {};
// We don't need to fetch the suite if we have the information already
if (state.hasFullReport || hasFullSuite) {
return Promise.resolve();
}
dispatch('toggleLoading');
const { name = '', build_ids = [] } = state.testReports?.test_suites?.[index] || {};
// Replacing `/:suite_name.json` with the name of the suite. Including the extra characters
// to ensure that we replace exactly the template part of the URL string
const endpoint = state.suiteEndpoint?.replace('/:suite_name.json', `/${name}.json`);
return axios
.get(endpoint, { params: { build_ids } })
.then(({ data }) => commit(types.SET_SUITE, { suite: data, index }))
.catch(() => {
createFlash(s__('TestReports|There was an error fetching the test suite.'));
})
.finally(() => {
dispatch('toggleLoading');
});
};
export const fetchFullReport = ({ state, commit, dispatch }) => { export const fetchFullReport = ({ state, commit, dispatch }) => {
dispatch('toggleLoading'); dispatch('toggleLoading');
......
export const SET_REPORTS = 'SET_REPORTS'; export const SET_REPORTS = 'SET_REPORTS';
export const SET_SELECTED_SUITE_INDEX = 'SET_SELECTED_SUITE_INDEX'; export const SET_SELECTED_SUITE_INDEX = 'SET_SELECTED_SUITE_INDEX';
export const SET_SUMMARY = 'SET_SUMMARY'; export const SET_SUMMARY = 'SET_SUMMARY';
export const SET_SUITE = 'SET_SUITE';
export const TOGGLE_LOADING = 'TOGGLE_LOADING'; export const TOGGLE_LOADING = 'TOGGLE_LOADING';
...@@ -5,6 +5,10 @@ export default { ...@@ -5,6 +5,10 @@ export default {
Object.assign(state, { testReports, hasFullReport: true }); Object.assign(state, { testReports, hasFullReport: true });
}, },
[types.SET_SUITE](state, { suite = {}, index = null }) {
state.testReports.test_suites[index] = { ...suite, hasFullSuite: true };
},
[types.SET_SELECTED_SUITE_INDEX](state, selectedSuiteIndex) { [types.SET_SELECTED_SUITE_INDEX](state, selectedSuiteIndex) {
Object.assign(state, { selectedSuiteIndex }); Object.assign(state, { selectedSuiteIndex });
}, },
......
export default ({ export default ({
fullReportEndpoint = '', fullReportEndpoint = '',
summaryEndpoint = '', summaryEndpoint = '',
suiteEndpoint = '',
useBuildSummaryReport = false, useBuildSummaryReport = false,
}) => ({ }) => ({
summaryEndpoint, summaryEndpoint,
suiteEndpoint,
fullReportEndpoint, fullReportEndpoint,
testReports: {}, testReports: {},
selectedSuiteIndex: null, selectedSuiteIndex: null,
......
...@@ -88,5 +88,6 @@ ...@@ -88,5 +88,6 @@
#js-tab-tests.tab-pane #js-tab-tests.tab-pane
#js-pipeline-tests-detail{ data: { full_report_endpoint: test_report_project_pipeline_path(@project, @pipeline, format: :json), #js-pipeline-tests-detail{ data: { full_report_endpoint: test_report_project_pipeline_path(@project, @pipeline, format: :json),
summary_endpoint: Feature.enabled?(:build_report_summary, @project) ? summary_project_pipeline_tests_path(@project, @pipeline, format: :json) : '', summary_endpoint: Feature.enabled?(:build_report_summary, @project) ? summary_project_pipeline_tests_path(@project, @pipeline, format: :json) : '',
suite_endpoint: Feature.enabled?(:build_report_summary, @project) ? project_pipeline_test_path(@project, @pipeline, suite_name: ':suite_name', format: :json) : '',
count_endpoint: test_reports_count_project_pipeline_path(@project, @pipeline, format: :json) } } count_endpoint: test_reports_count_project_pipeline_path(@project, @pipeline, format: :json) } }
= render_if_exists "projects/pipelines/tabs_content", pipeline: @pipeline, project: @project = render_if_exists "projects/pipelines/tabs_content", pipeline: @pipeline, project: @project
...@@ -23383,6 +23383,9 @@ msgstr "" ...@@ -23383,6 +23383,9 @@ msgstr ""
msgid "TestReports|There was an error fetching the test reports." msgid "TestReports|There was an error fetching the test reports."
msgstr "" msgstr ""
msgid "TestReports|There was an error fetching the test suite."
msgstr ""
msgid "Tests" msgid "Tests"
msgstr "" msgstr ""
......
...@@ -17,9 +17,11 @@ describe('Actions TestReports Store', () => { ...@@ -17,9 +17,11 @@ describe('Actions TestReports Store', () => {
const summary = { total_count: 1 }; const summary = { total_count: 1 };
const fullReportEndpoint = `${TEST_HOST}/test_reports.json`; const fullReportEndpoint = `${TEST_HOST}/test_reports.json`;
const suiteEndpoint = `${TEST_HOST}/tests/:suite_name.json`;
const summaryEndpoint = `${TEST_HOST}/test_reports/summary.json`; const summaryEndpoint = `${TEST_HOST}/test_reports/summary.json`;
const defaultState = { const defaultState = {
fullReportEndpoint, fullReportEndpoint,
suiteEndpoint,
summaryEndpoint, summaryEndpoint,
testReports: {}, testReports: {},
selectedSuite: null, selectedSuite: null,
...@@ -100,6 +102,65 @@ describe('Actions TestReports Store', () => { ...@@ -100,6 +102,65 @@ describe('Actions TestReports Store', () => {
}); });
}); });
describe('fetch test suite', () => {
beforeEach(() => {
const buildIds = [1];
testReports.test_suites[0].build_ids = buildIds;
const endpoint = suiteEndpoint.replace(':suite_name', testReports.test_suites[0].name);
mock
.onGet(endpoint, { params: { build_ids: buildIds } })
.replyOnce(200, testReports.test_suites[0], {});
});
it('sets test suite and shows tests', done => {
const suite = testReports.test_suites[0];
const index = 0;
testAction(
actions.fetchTestSuite,
index,
{ ...state, testReports },
[{ type: types.SET_SUITE, payload: { suite, index } }],
[{ type: 'toggleLoading' }, { type: 'toggleLoading' }],
done,
);
});
it('should create flash on API error', done => {
const index = 0;
testAction(
actions.fetchTestSuite,
index,
{ ...state, testReports, suiteEndpoint: null },
[],
[{ type: 'toggleLoading' }, { type: 'toggleLoading' }],
() => {
expect(createFlash).toHaveBeenCalled();
done();
},
);
});
describe('when we already have the suite data', () => {
it('should not fetch suite', done => {
const index = 0;
testReports.test_suites[0].hasFullSuite = true;
testAction(actions.fetchTestSuite, index, { ...state, testReports }, [], [], done);
});
});
describe('when we already have the full report data', () => {
it('should not fetch suite', done => {
const index = 0;
testReports.hasFullReport = true;
testAction(actions.fetchTestSuite, index, { ...state, testReports }, [], [], done);
});
});
});
describe('fetch full report', () => { describe('fetch full report', () => {
beforeEach(() => { beforeEach(() => {
mock.onGet(fullReportEndpoint).replyOnce(200, testReports, {}); mock.onGet(fullReportEndpoint).replyOnce(200, testReports, {});
......
...@@ -29,6 +29,21 @@ describe('Mutations TestReports Store', () => { ...@@ -29,6 +29,21 @@ describe('Mutations TestReports Store', () => {
}); });
}); });
describe('set suite', () => {
it('should set the suite at the given index', () => {
mockState.testReports = testReports;
const suite = { name: 'test_suite' };
const index = 0;
const expectedState = { ...mockState };
expectedState.testReports.test_suites[index] = { suite, hasFullSuite: true };
mutations[types.SET_SUITE](mockState, { suite, index });
expect(mockState.testReports.test_suites[index]).toEqual(
expectedState.testReports.test_suites[index],
);
});
});
describe('set selected suite index', () => { describe('set selected suite index', () => {
it('should set selectedSuiteIndex', () => { it('should set selectedSuiteIndex', () => {
const selectedSuiteIndex = 0; const selectedSuiteIndex = 0;
......
...@@ -22,7 +22,7 @@ describe('Test reports app', () => { ...@@ -22,7 +22,7 @@ describe('Test reports app', () => {
const testSummaryTable = () => wrapper.find(TestSummaryTable); const testSummaryTable = () => wrapper.find(TestSummaryTable);
const actionSpies = { const actionSpies = {
fetchFullReport: jest.fn(), fetchTestSuite: jest.fn(),
fetchSummary: jest.fn(), fetchSummary: jest.fn(),
setSelectedSuiteIndex: jest.fn(), setSelectedSuiteIndex: jest.fn(),
removeSelectedSuiteIndex: jest.fn(), removeSelectedSuiteIndex: jest.fn(),
...@@ -91,28 +91,14 @@ describe('Test reports app', () => { ...@@ -91,28 +91,14 @@ describe('Test reports app', () => {
}); });
describe('when a suite is clicked', () => { describe('when a suite is clicked', () => {
describe('when the full test report has already been received', () => { beforeEach(() => {
beforeEach(() => { createComponent({ hasFullReport: true });
createComponent({ hasFullReport: true }); testSummaryTable().vm.$emit('row-click', 0);
testSummaryTable().vm.$emit('row-click', 0);
});
it('should only call setSelectedSuiteIndex', () => {
expect(actionSpies.setSelectedSuiteIndex).toHaveBeenCalled();
expect(actionSpies.fetchFullReport).not.toHaveBeenCalled();
});
}); });
describe('when the full test report has not been received', () => { it('should call setSelectedSuiteIndex and fetchTestSuite', () => {
beforeEach(() => { expect(actionSpies.setSelectedSuiteIndex).toHaveBeenCalled();
createComponent({ hasFullReport: false }); expect(actionSpies.fetchTestSuite).toHaveBeenCalled();
testSummaryTable().vm.$emit('row-click', 0);
});
it('should call setSelectedSuiteIndex and fetchFullReport', () => {
expect(actionSpies.setSelectedSuiteIndex).toHaveBeenCalled();
expect(actionSpies.fetchFullReport).toHaveBeenCalled();
});
}); });
}); });
......
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