Commit cfaf663b authored by Scott Hampton's avatar Scott Hampton

Use the test report summary

The test report summary endpoint is behind a
feature flag `build_report_summary`. We can
use this endpoint to load the summary of the
test report first, and then load the rest of the
report when the user clicks on a test suite.

This `test_report_summary` in the pipeline
also allows us to render the total count in the tab
without us having to update the badge in JS.
parent 6c97d4ed
...@@ -122,13 +122,17 @@ const createTestDetails = () => { ...@@ -122,13 +122,17 @@ const createTestDetails = () => {
} }
const el = document.querySelector('#js-pipeline-tests-detail'); const el = document.querySelector('#js-pipeline-tests-detail');
const { fullReportEndpoint, countEndpoint } = el?.dataset || {}; const { fullReportEndpoint, summaryEndpoint, countEndpoint } = el?.dataset || {};
const testReportsStore = createTestReportsStore({ const testReportsStore = createTestReportsStore({
fullReportEndpoint, fullReportEndpoint,
summaryEndpoint: countEndpoint, summaryEndpoint: summaryEndpoint || countEndpoint,
useBuildSummaryReport: window.gon?.features?.buildReportSummary,
}); });
createPipelinesTabs(testReportsStore);
if (!window.gon?.features?.buildReportSummary) {
createPipelinesTabs(testReportsStore);
}
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
......
...@@ -3,21 +3,35 @@ import * as types from './mutation_types'; ...@@ -3,21 +3,35 @@ import * as types from './mutation_types';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
export const fetchSummary = ({ state, commit }) => { export const fetchSummary = ({ state, commit, dispatch }) => {
// If we do this without the build_report_summary feature flag enabled
// it causes a race condition for toggleLoading and ruins the loading
// state in the application
if (state.useBuildSummaryReport) {
dispatch('toggleLoading');
}
return axios return axios
.get(state.summaryEndpoint) .get(state.summaryEndpoint)
.then(({ data }) => { .then(({ data }) => {
commit(types.SET_SUMMARY, data); commit(types.SET_SUMMARY, data);
// Set the tab counter badge to total_count if (!state.useBuildSummaryReport) {
// This is temporary until we can server-side render that count number // Set the tab counter badge to total_count
// (see https://gitlab.com/gitlab-org/gitlab/-/issues/223134) // This is temporary until we can server-side render that count number
if (data.total_count !== undefined) { // (see https://gitlab.com/gitlab-org/gitlab/-/issues/223134)
document.querySelector('.js-test-report-badge-counter').innerHTML = data.total_count; if (data.total_count !== undefined) {
document.querySelector('.js-test-report-badge-counter').innerHTML = data.total_count;
}
} }
}) })
.catch(() => { .catch(() => {
createFlash(s__('TestReports|There was an error fetching the summary.')); createFlash(s__('TestReports|There was an error fetching the summary.'));
})
.finally(() => {
if (state.useBuildSummaryReport) {
dispatch('toggleLoading');
}
}); });
}; };
...@@ -35,8 +49,15 @@ export const fetchFullReport = ({ state, commit, dispatch }) => { ...@@ -35,8 +49,15 @@ export const fetchFullReport = ({ state, commit, dispatch }) => {
}); });
}; };
export const setSelectedSuiteIndex = ({ commit }, data) => export const setSelectedSuiteIndex = ({ state, commit, dispatch }, data) => {
commit(types.SET_SELECTED_SUITE_INDEX, data); commit(types.SET_SELECTED_SUITE_INDEX, data);
// Fetch the full report when the user clicks to see more details
if (!state.hasFullReport) {
dispatch('fetchFullReport');
}
};
export const removeSelectedSuiteIndex = ({ commit }) => export const removeSelectedSuiteIndex = ({ commit }) =>
commit(types.SET_SELECTED_SUITE_INDEX, null); commit(types.SET_SELECTED_SUITE_INDEX, null);
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_LOADING); export const toggleLoading = ({ commit }) => commit(types.TOGGLE_LOADING);
......
...@@ -2,7 +2,7 @@ import * as types from './mutation_types'; ...@@ -2,7 +2,7 @@ import * as types from './mutation_types';
export default { export default {
[types.SET_REPORTS](state, testReports) { [types.SET_REPORTS](state, testReports) {
Object.assign(state, { testReports }); Object.assign(state, { testReports, hasFullReport: true });
}, },
[types.SET_SELECTED_SUITE_INDEX](state, selectedSuiteIndex) { [types.SET_SELECTED_SUITE_INDEX](state, selectedSuiteIndex) {
...@@ -10,7 +10,7 @@ export default { ...@@ -10,7 +10,7 @@ export default {
}, },
[types.SET_SUMMARY](state, summary) { [types.SET_SUMMARY](state, summary) {
Object.assign(state, { summary }); Object.assign(state, { testReports: { ...state.testReports, ...summary } });
}, },
[types.TOGGLE_LOADING](state) { [types.TOGGLE_LOADING](state) {
......
export default ({ fullReportEndpoint = '', summaryEndpoint = '' }) => ({ export default ({
fullReportEndpoint = '',
summaryEndpoint = '',
useBuildSummaryReport = false,
}) => ({
summaryEndpoint, summaryEndpoint,
fullReportEndpoint, fullReportEndpoint,
testReports: {}, testReports: {},
selectedSuiteIndex: null, selectedSuiteIndex: null,
summary: {}, hasFullReport: false,
isLoading: false, isLoading: false,
useBuildSummaryReport,
}); });
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
%li.js-tests-tab-link %li.js-tests-tab-link
= link_to test_report_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-tests', action: 'test_report', toggle: 'tab' }, class: 'test-tab' do = link_to test_report_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-tests', action: 'test_report', toggle: 'tab' }, class: 'test-tab' do
= s_('TestReports|Tests') = s_('TestReports|Tests')
%span.badge.badge-pill.js-test-report-badge-counter %span.badge.badge-pill.js-test-report-badge-counter= Feature.enabled?(:build_report_summary, @project) ? @pipeline.test_report_summary.total_count : ''
= render_if_exists "projects/pipelines/tabs_holder", pipeline: @pipeline, project: @project = render_if_exists "projects/pipelines/tabs_holder", pipeline: @pipeline, project: @project
.tab-content .tab-content
...@@ -86,5 +86,7 @@ ...@@ -86,5 +86,7 @@
#js-pipeline-dag-vue{ data: { pipeline_data_path: dag_project_pipeline_path(@project, @pipeline), empty_svg_path: image_path('illustrations/empty-state/empty-dag-md.svg'), dag_doc_path: help_page_path('ci/yaml/README.md', anchor: 'needs')} } #js-pipeline-dag-vue{ data: { pipeline_data_path: dag_project_pipeline_path(@project, @pipeline), empty_svg_path: image_path('illustrations/empty-state/empty-dag-md.svg'), dag_doc_path: help_page_path('ci/yaml/README.md', anchor: 'needs')} }
#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), count_endpoint: test_reports_count_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_tests_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
...@@ -23,12 +23,12 @@ describe('Actions TestReports Store', () => { ...@@ -23,12 +23,12 @@ describe('Actions TestReports Store', () => {
summaryEndpoint, summaryEndpoint,
testReports: {}, testReports: {},
selectedSuite: null, selectedSuite: null,
summary: {}, useBuildSummaryReport: false,
}; };
beforeEach(() => { beforeEach(() => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
state = defaultState; state = { ...defaultState };
}); });
afterEach(() => { afterEach(() => {
...@@ -40,31 +40,63 @@ describe('Actions TestReports Store', () => { ...@@ -40,31 +40,63 @@ describe('Actions TestReports Store', () => {
mock.onGet(summaryEndpoint).replyOnce(200, summary, {}); mock.onGet(summaryEndpoint).replyOnce(200, summary, {});
}); });
it('sets testReports and shows tests', done => { describe('when useBuildSummaryReport in state is true', () => {
testAction( it('sets testReports and shows tests', done => {
actions.fetchSummary, testAction(
null, actions.fetchSummary,
state, null,
[{ type: types.SET_SUMMARY, payload: summary }], { ...state, useBuildSummaryReport: true },
[], [{ type: types.SET_SUMMARY, payload: summary }],
done, [{ type: 'toggleLoading' }, { type: 'toggleLoading' }],
); done,
);
});
it('should create flash on API error', done => {
testAction(
actions.fetchSummary,
null,
{
summaryEndpoint: null,
useBuildSummaryReport: true,
},
[],
[{ type: 'toggleLoading' }, { type: 'toggleLoading' }],
() => {
expect(createFlash).toHaveBeenCalled();
done();
},
);
});
}); });
it('should create flash on API error', done => { describe('when useBuildSummaryReport in state is false', () => {
testAction( it('sets testReports and shows tests', done => {
actions.fetchSummary, testAction(
null, actions.fetchSummary,
{ null,
summaryEndpoint: null, state,
}, [{ type: types.SET_SUMMARY, payload: summary }],
[], [],
[], done,
() => { );
expect(createFlash).toHaveBeenCalled(); });
done();
}, it('should create flash on API error', done => {
); testAction(
actions.fetchSummary,
null,
{
summaryEndpoint: null,
},
[],
[],
() => {
expect(createFlash).toHaveBeenCalled();
done();
},
);
});
}); });
}); });
...@@ -104,15 +136,30 @@ describe('Actions TestReports Store', () => { ...@@ -104,15 +136,30 @@ describe('Actions TestReports Store', () => {
describe('set selected suite index', () => { describe('set selected suite index', () => {
const selectedSuiteIndex = 0; const selectedSuiteIndex = 0;
it('sets selectedSuiteIndex', done => { describe('when state does not have full report', () => {
testAction( it('sets selectedSuiteIndex', done => {
actions.setSelectedSuiteIndex, testAction(
selectedSuiteIndex, actions.setSelectedSuiteIndex,
state, selectedSuiteIndex,
[{ type: types.SET_SELECTED_SUITE_INDEX, payload: selectedSuiteIndex }], state,
[], [{ type: types.SET_SELECTED_SUITE_INDEX, payload: selectedSuiteIndex }],
done, [{ type: 'fetchFullReport' }],
); done,
);
});
});
describe('when state has full report', () => {
it('sets selectedSuiteIndex', done => {
testAction(
actions.setSelectedSuiteIndex,
selectedSuiteIndex,
{ ...state, hasFullReport: true },
[{ type: types.SET_SELECTED_SUITE_INDEX, payload: selectedSuiteIndex }],
[],
done,
);
});
}); });
}); });
......
...@@ -12,10 +12,11 @@ describe('Mutations TestReports Store', () => { ...@@ -12,10 +12,11 @@ describe('Mutations TestReports Store', () => {
testReports: {}, testReports: {},
selectedSuite: null, selectedSuite: null,
isLoading: false, isLoading: false,
hasFullReport: false,
}; };
beforeEach(() => { beforeEach(() => {
mockState = defaultState; mockState = { ...defaultState };
}); });
describe('set reports', () => { describe('set reports', () => {
...@@ -24,6 +25,7 @@ describe('Mutations TestReports Store', () => { ...@@ -24,6 +25,7 @@ describe('Mutations TestReports Store', () => {
mutations[types.SET_REPORTS](mockState, testReports); mutations[types.SET_REPORTS](mockState, testReports);
expect(mockState.testReports).toEqual(expectedState.testReports); expect(mockState.testReports).toEqual(expectedState.testReports);
expect(mockState.hasFullReport).toBe(true);
}); });
}); });
...@@ -41,7 +43,7 @@ describe('Mutations TestReports Store', () => { ...@@ -41,7 +43,7 @@ describe('Mutations TestReports Store', () => {
const summary = { total_count: 1 }; const summary = { total_count: 1 };
mutations[types.SET_SUMMARY](mockState, summary); mutations[types.SET_SUMMARY](mockState, summary);
expect(mockState.summary).toEqual(summary); expect(mockState.testReports).toEqual(summary);
}); });
}); });
......
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