Commit 3f078050 authored by Scott Hampton's avatar Scott Hampton

Link to test file in test report widget

Add a link to the test case file in the test report
MR widget modal.
parent c84d9415
export const fieldTypes = {
codeBock: 'codeBlock',
codeBlock: 'codeBlock',
link: 'link',
seconds: 'seconds',
text: 'text',
......
......@@ -25,6 +25,14 @@ export default {
required: true,
},
},
computed: {
filteredModalData() {
// Filter out the properties that don't have a value
return Object.fromEntries(
Object.entries(this.modalData).filter((data) => Boolean(data[1].value)),
);
},
},
fieldTypes,
};
</script>
......@@ -36,23 +44,18 @@ export default {
:hide-footer="true"
@hide="$emit('hide')"
>
<div
v-for="(field, key, index) in modalData"
v-if="field.value"
:key="index"
class="row gl-mt-3 gl-mb-3"
>
<div v-for="(field, key, index) in filteredModalData" :key="index" class="row gl-mt-3 gl-mb-3">
<strong class="col-sm-3 text-right"> {{ field.text }}: </strong>
<div class="col-sm-9 text-secondary">
<code-block v-if="field.type === $options.fieldTypes.codeBock" :code="field.value" />
<code-block v-if="field.type === $options.fieldTypes.codeBlock" :code="field.value" />
<gl-link
v-else-if="field.type === $options.fieldTypes.link"
:href="field.value"
:href="field.value.path"
target="_blank"
>
{{ field.value }}
{{ field.value.text }}
</gl-link>
<gl-sprintf
......
......@@ -39,6 +39,10 @@ export default {
required: false,
default: '',
},
headBlobPath: {
type: String,
required: true,
},
},
componentNames,
computed: {
......@@ -73,12 +77,15 @@ export default {
},
},
created() {
this.setEndpoint(this.endpoint);
this.setPaths({
endpoint: this.endpoint,
headBlobPath: this.headBlobPath,
});
this.fetchReports();
},
methods: {
...mapActions(['setEndpoint', 'fetchReports', 'closeModal']),
...mapActions(['setPaths', 'fetchReports', 'closeModal']),
reportText(report) {
const { name, summary } = report || {};
......
......@@ -4,7 +4,7 @@ import httpStatusCodes from '../../../lib/utils/http_status';
import Poll from '../../../lib/utils/poll';
import * as types from './mutation_types';
export const setEndpoint = ({ commit }, endpoint) => commit(types.SET_ENDPOINT, endpoint);
export const setPaths = ({ commit }, paths) => commit(types.SET_PATHS, paths);
export const requestReports = ({ commit }) => commit(types.REQUEST_REPORTS);
......
export const SET_ENDPOINT = 'SET_ENDPOINT';
export const SET_PATHS = 'SET_PATHS';
export const REQUEST_REPORTS = 'REQUEST_REPORTS';
export const RECEIVE_REPORTS_SUCCESS = 'RECEIVE_REPORTS_SUCCESS';
......
import * as types from './mutation_types';
import { countRecentlyFailedTests } from './utils';
import { countRecentlyFailedTests, formatFilePath } from './utils';
export default {
[types.SET_ENDPOINT](state, endpoint) {
[types.SET_PATHS](state, { endpoint, headBlobPath }) {
state.endpoint = endpoint;
state.headBlobPath = headBlobPath;
},
[types.REQUEST_REPORTS](state) {
state.isLoading = true;
......@@ -42,17 +43,25 @@ export default {
state.status = null;
},
[types.SET_ISSUE_MODAL_DATA](state, payload) {
state.modal.title = payload.issue.name;
const { issue } = payload;
state.modal.title = issue.name;
Object.keys(payload.issue).forEach((key) => {
Object.keys(issue).forEach((key) => {
if (Object.prototype.hasOwnProperty.call(state.modal.data, key)) {
state.modal.data[key] = {
...state.modal.data[key],
value: payload.issue[key],
value: issue[key],
};
}
});
if (issue.file) {
state.modal.data.filename.value = {
text: issue.file,
path: `${state.headBlobPath}/${formatFilePath(issue.file)}`,
};
}
state.modal.open = true;
},
[types.RESET_ISSUE_MODAL_DATA](state) {
......
......@@ -41,16 +41,16 @@ export default () => ({
open: false,
data: {
class: {
value: null,
text: s__('Reports|Class'),
type: fieldTypes.link,
},
classname: {
value: null,
text: s__('Reports|Classname'),
type: fieldTypes.text,
},
filename: {
value: null,
text: s__('Reports|Filename'),
type: fieldTypes.link,
},
execution_time: {
value: null,
text: s__('Reports|Execution time'),
......@@ -59,12 +59,12 @@ export default () => ({
failure: {
value: null,
text: s__('Reports|Failure'),
type: fieldTypes.codeBock,
type: fieldTypes.codeBlock,
},
system_output: {
value: null,
text: s__('Reports|System output'),
type: fieldTypes.codeBock,
type: fieldTypes.codeBlock,
},
},
},
......
......@@ -100,3 +100,12 @@ export const statusIcon = (status) => {
return ICON_NOTFOUND;
};
/**
* Removes `./` from the beginning of a file path so it can be appended onto a blob path
* @param {String} file
* @returns {String} - formatted value
*/
export const formatFilePath = (file) => {
return file.replace(/^\.?\/*/, '');
};
......@@ -480,6 +480,7 @@ export default {
v-if="mr.testResultsPath"
class="js-reports-container"
:endpoint="mr.testResultsPath"
:head-blob-path="mr.headBlobPath"
:pipeline-path="mr.pipeline.path"
/>
......
---
title: Add link to test case file in the test report for merge requests
merge_request: 57911
author:
type: added
......@@ -380,6 +380,7 @@ export default {
v-if="mr.testResultsPath"
class="js-reports-container"
:endpoint="mr.testResultsPath"
:head-blob-path="mr.headBlobPath"
:pipeline-path="mr.pipeline.path"
/>
......
......@@ -25837,9 +25837,6 @@ msgstr ""
msgid "Reports|Base report parsing error:"
msgstr ""
msgid "Reports|Class"
msgstr ""
msgid "Reports|Classname"
msgstr ""
......@@ -25859,6 +25856,9 @@ msgstr[1] ""
msgid "Reports|Failure"
msgstr ""
msgid "Reports|Filename"
msgstr ""
msgid "Reports|Head report parsing error:"
msgstr ""
......
......@@ -15,7 +15,10 @@ describe('Grouped Test Reports Modal', () => {
// populate data
modalDataStructure.execution_time.value = 0.009411;
modalDataStructure.system_output.value = 'Failure/Error: is_expected.to eq(3)\n\n';
modalDataStructure.class.value = 'link';
modalDataStructure.filename.value = {
text: 'link',
path: '/file/path',
};
let wrapper;
......@@ -43,9 +46,9 @@ describe('Grouped Test Reports Modal', () => {
it('renders link', () => {
const link = wrapper.findComponent(GlLink);
expect(link.attributes().href).toEqual(modalDataStructure.class.value);
expect(link.attributes().href).toEqual(modalDataStructure.filename.value.path);
expect(link.text()).toEqual(modalDataStructure.class.value);
expect(link.text()).toEqual(modalDataStructure.filename.value.text);
});
it('renders seconds', () => {
......
......@@ -17,6 +17,7 @@ localVue.use(Vuex);
describe('Grouped test reports app', () => {
const endpoint = 'endpoint.json';
const headBlobPath = '/blob/path';
const pipelinePath = '/path/to/pipeline';
let wrapper;
let mockStore;
......@@ -27,6 +28,7 @@ describe('Grouped test reports app', () => {
localVue,
propsData: {
endpoint,
headBlobPath,
pipelinePath,
...props,
},
......@@ -56,7 +58,7 @@ describe('Grouped test reports app', () => {
...getStoreConfig(),
actions: {
fetchReports: () => {},
setEndpoint: () => {},
setPaths: () => {},
},
});
mountComponent();
......
......@@ -3,7 +3,7 @@ import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper';
import axios from '~/lib/utils/axios_utils';
import {
setEndpoint,
setPaths,
requestReports,
fetchReports,
stopPolling,
......@@ -23,13 +23,18 @@ describe('Reports Store Actions', () => {
mockedState = state();
});
describe('setEndpoint', () => {
it('should commit SET_ENDPOINT mutation', (done) => {
describe('setPaths', () => {
it('should commit SET_PATHS mutation', (done) => {
testAction(
setEndpoint,
'endpoint.json',
setPaths,
{ endpoint: 'endpoint.json', headBlobPath: '/blob/path' },
mockedState,
[{ type: types.SET_ENDPOINT, payload: 'endpoint.json' }],
[
{
type: types.SET_PATHS,
payload: { endpoint: 'endpoint.json', headBlobPath: '/blob/path' },
},
],
[],
done,
);
......
......@@ -10,11 +10,15 @@ describe('Reports Store Mutations', () => {
stateCopy = state();
});
describe('SET_ENDPOINT', () => {
describe('SET_PATHS', () => {
it('should set endpoint', () => {
mutations[types.SET_ENDPOINT](stateCopy, 'endpoint.json');
mutations[types.SET_PATHS](stateCopy, {
endpoint: 'endpoint.json',
headBlobPath: '/blob/path',
});
expect(stateCopy.endpoint).toEqual('endpoint.json');
expect(stateCopy.headBlobPath).toEqual('/blob/path');
});
});
......
......@@ -238,4 +238,18 @@ describe('Reports store utils', () => {
});
});
});
describe('formatFilePath', () => {
it.each`
file | expected
${'./test.js'} | ${'test.js'}
${'/test.js'} | ${'test.js'}
${'.//////////////test.js'} | ${'test.js'}
${'test.js'} | ${'test.js'}
${'mock/path./test.js'} | ${'mock/path./test.js'}
${'./mock/path./test.js'} | ${'mock/path./test.js'}
`('should format $file to be $expected', ({ file, expected }) => {
expect(utils.formatFilePath(file)).toBe(expected);
});
});
});
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