Commit fca02890 authored by Mark Florian's avatar Mark Florian

Merge branch '254711-fuzzing-download-mr-widget' into 'master'

Implement fuzzing artifcact download

See merge request gitlab-org/gitlab!44263
parents 64b58410 493c8945
......@@ -38,13 +38,12 @@ export default {
<template>
<div>
<strong>{{ s__('SecurityReports|Download Report') }}</strong>
<slot name="label"></slot>
<gl-dropdown
v-if="hasDropdown"
class="d-block mt-1"
:text="$options.i18n.FUZZING_ARTIFACTS"
category="secondary"
variant="info"
size="small"
>
<gl-deprecated-dropdown-item
......@@ -58,7 +57,6 @@ export default {
v-else
class="d-block mt-1"
category="secondary"
variant="info"
size="small"
:href="artifactDownloadUrl(jobs[0])"
>
......
......@@ -167,7 +167,11 @@ export default {
<vulnerability-count-list v-if="shouldShowCountList" />
<filters>
<template v-if="hasFuzzingArtifacts" #buttons>
<fuzzing-artifacts-download :jobs="fuzzingJobsWithArtifact" :project-id="projectId" />
<fuzzing-artifacts-download :jobs="fuzzingJobsWithArtifact" :project-id="projectId">
<template #label>
<strong>{{ s__('SecurityReports|Download Report') }}</strong>
</template>
</fuzzing-artifacts-download>
</template>
</filters>
</template>
......
import * as Sentry from '~/sentry/wrapper';
import axios from '~/lib/utils/axios_utils';
import Api from '~/api';
import * as types from './mutation_types';
export const setPipelineJobsPath = ({ commit }, path) => commit(types.SET_PIPELINE_JOBS_PATH, path);
export const setProjectId = ({ commit }, id) => commit(types.SET_PROJECT_ID, id);
export const setPipelineId = ({ commit }, id) => commit(types.SET_PIPELINE_ID, id);
export const fetchPipelineJobs = ({ commit, state }) => {
if (!state.pipelineJobsPath) {
if (!state.pipelineJobsPath && !(state.projectId && state.pipelineId)) {
return commit(types.RECEIVE_PIPELINE_JOBS_ERROR);
}
commit(types.REQUEST_PIPELINE_JOBS);
return axios({
let requestPromise;
// Support existing usages that rely on server provided path,
// otherwise generate client side
if (state.pipelineJobsPath) {
requestPromise = axios({
method: 'GET',
url: state.pipelineJobsPath,
})
});
} else {
requestPromise = Api.pipelineJobs(state.projectId, state.pipelineId);
}
return requestPromise
.then(response => {
const { data } = response;
commit(types.RECEIVE_PIPELINE_JOBS_SUCCESS, data);
......
export const SET_PIPELINE_JOBS_PATH = 'SET_PIPELINE_JOBS_PATH';
export const SET_PROJECT_ID = 'SET_PROJECT_ID ';
export const SET_PIPELINE_ID = 'SET_PIPELINE_ID ';
export const REQUEST_PIPELINE_JOBS = 'REQUEST_PIPELINE_JOBS';
export const RECEIVE_PIPELINE_JOBS_SUCCESS = 'RECEIVE_PIPELINE_JOBS_SUCESS';
......
......@@ -7,6 +7,9 @@ export default {
[types.SET_PROJECT_ID](state, payload) {
state.projectId = payload;
},
[types.SET_PIPELINE_ID](state, payload) {
state.pipelineId = payload;
},
[types.REQUEST_PIPELINE_JOBS](state) {
state.isLoading = true;
},
......
export default () => ({
projectId: undefined,
pipelineId: undefined,
pipelineJobsPath: '',
isLoading: false,
pipelineJobs: [],
......
......@@ -326,6 +326,7 @@ export default {
:pipeline-path="mr.pipeline.path"
:pipeline-id="mr.securityReportsPipelineId"
:pipeline-iid="mr.securityReportsPipelineIid"
:project-id="mr.targetProjectId"
:project-full-path="mr.sourceProjectFullPath"
:diverged-commits-count="mr.divergedCommitsCount"
:mr-state="mr.state"
......
......@@ -4,6 +4,7 @@ import { once } from 'lodash';
import { componentNames } from 'ee/reports/components/issue_body';
import { GlButton, GlSprintf, GlLink, GlModalDirective } from '@gitlab/ui';
import { trackMrSecurityReportDetails } from 'ee/vue_shared/security_reports/store/constants';
import FuzzingArtifactsDownload from 'ee/security_dashboard/components/fuzzing_artifacts_download.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ReportSection from '~/reports/components/report_section.vue';
import SummaryRow from '~/reports/components/summary_row.vue';
......@@ -30,6 +31,7 @@ export default {
GlLink,
DastModal,
GlButton,
FuzzingArtifactsDownload,
},
directives: {
'gl-modal': GlModalDirective,
......@@ -171,6 +173,11 @@ export default {
required: false,
default: '',
},
projectId: {
type: Number,
required: false,
default: null,
},
projectFullPath: {
type: String,
required: true,
......@@ -210,6 +217,7 @@ export default {
'canDismissVulnerability',
]),
...mapGetters('sast', ['groupedSastText', 'sastStatusIcon']),
...mapGetters('pipelineJobs', ['hasFuzzingArtifacts', 'fuzzingJobsWithArtifact']),
securityTab() {
return `${this.pipelinePath}/security`;
},
......@@ -274,7 +282,9 @@ export default {
this.createVulnerabilityFeedbackMergeRequestPath,
);
this.setCreateVulnerabilityFeedbackDismissalPath(this.createVulnerabilityFeedbackDismissalPath);
this.setProjectId(this.projectId);
this.setPipelineId(this.pipelineId);
this.setPipelineJobsId(this.pipelineId);
const sastDiffEndpoint = gl?.mrWidgetData?.sast_comparison_path;
......@@ -315,6 +325,7 @@ export default {
if (coverageFuzzingDiffEndpoint && this.hasCoverageFuzzingReports) {
this.setCoverageFuzzingDiffEndpoint(coverageFuzzingDiffEndpoint);
this.fetchCoverageFuzzingDiff();
this.fetchPipelineJobs();
}
},
methods: {
......@@ -356,6 +367,10 @@ export default {
setSastDiffEndpoint: 'setDiffEndpoint',
fetchSastDiff: 'fetchDiff',
}),
...mapActions('pipelineJobs', ['fetchPipelineJobs', 'setPipelineJobsPath', 'setProjectId']),
...mapActions('pipelineJobs', {
setPipelineJobsId: 'setPipelineId',
}),
},
summarySlots: ['success', 'error', 'loading'],
};
......@@ -555,6 +570,11 @@ export default {
<template #summary>
<security-summary :message="groupedCoverageFuzzingText" />
</template>
<fuzzing-artifacts-download
v-if="hasFuzzingArtifacts"
:jobs="fuzzingJobsWithArtifact"
:project-id="projectId"
/>
</summary-row>
<grouped-issues-list
......
import Vue from 'vue';
import Vuex from 'vuex';
import pipelineJobs from 'ee/security_dashboard/store/modules/pipeline_jobs';
import configureMediator from './mediator';
import * as actions from './actions';
import * as getters from './getters';
......@@ -14,6 +15,7 @@ export default () =>
new Vuex.Store({
modules: {
sast,
pipelineJobs,
},
actions,
getters,
......
---
title: Update fuzzing download button styles
merge_request: 44263
author:
type: changed
......@@ -54,6 +54,26 @@ describe('pipeling jobs actions', () => {
});
});
describe('setPipelineId', () => {
const pipelineId = 123;
it('should commit the SET_PIPELINE_ID mutation', done => {
testAction(
actions.setPipelineId,
pipelineId,
state,
[
{
type: types.SET_PIPELINE_ID,
payload: pipelineId,
},
],
[],
done,
);
});
});
describe('fetchPipelineJobs', () => {
let mock;
const jobs = [{}, {}];
......@@ -67,7 +87,7 @@ describe('pipeling jobs actions', () => {
mock.restore();
});
describe('on success', () => {
describe('on success with pipeline path', () => {
beforeEach(() => {
mock.onGet(state.pipelineJobsPath).replyOnce(200, jobs);
});
......@@ -90,6 +110,33 @@ describe('pipeling jobs actions', () => {
});
});
describe('on success with pipeline id and project id', () => {
beforeEach(() => {
mock.onGet('/api/undefined/projects/123/pipelines/321/jobs').replyOnce(200, jobs);
});
it('should commit the request and success mutations', done => {
state.pipelineJobsPath = '';
state.projectId = 123;
state.pipelineId = 321;
testAction(
actions.fetchPipelineJobs,
{},
state,
[
{ type: types.REQUEST_PIPELINE_JOBS },
{
type: types.RECEIVE_PIPELINE_JOBS_SUCCESS,
payload: jobs,
},
],
[],
done,
);
});
});
describe('without pipelineJobsPath set', () => {
beforeEach(() => {
mock.onGet(state.pipelineJobsPath).replyOnce(200, jobs);
......@@ -113,6 +160,31 @@ describe('pipeling jobs actions', () => {
});
});
describe('without projectId or pipelineId set', () => {
beforeEach(() => {
mock.onGet(state.pipelineJobsPath).replyOnce(200, jobs);
});
it('should commit RECEIVE_PIPELINE_JOBS_ERROR mutation', done => {
state.pipelineJobsPath = '';
state.projectId = undefined;
state.pipelineId = undefined;
testAction(
actions.fetchPipelineJobs,
{},
state,
[
{
type: types.RECEIVE_PIPELINE_JOBS_ERROR,
},
],
[],
done,
);
});
});
describe('with server error', () => {
beforeEach(() => {
mock.onGet(state.pipelineJobsPath).replyOnce(404);
......
......@@ -26,6 +26,15 @@ describe('pipeline jobs module mutations', () => {
});
});
describe('SET_PIPELINE_ID', () => {
const pipelineId = 123;
it(`should set the pipelineId to ${pipelineId}`, () => {
mutations[types.SET_PIPELINE_ID](state, pipelineId);
expect(state.pipelineId).toBe(pipelineId);
});
});
describe('REQUEST_PIPELINE_JOBS', () => {
it('should set the isLoading to true', () => {
mutations[types.REQUEST_PIPELINE_JOBS](state);
......
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import GroupedSecurityReportsApp from 'ee/vue_shared/security_reports/grouped_security_reports_app.vue';
import state from 'ee/vue_shared/security_reports/store/state';
import appStore from 'ee/vue_shared/security_reports/store';
import * as types from 'ee/vue_shared/security_reports/store/mutation_types';
import sastState from 'ee/vue_shared/security_reports/store/modules/sast/state';
import * as sastTypes from 'ee/vue_shared/security_reports/store/modules/sast/mutation_types';
import { mount } from '@vue/test-utils';
import { waitForMutation } from 'helpers/vue_test_utils_helper';
......@@ -29,6 +28,7 @@ const CONTAINER_SCANNING_DIFF_ENDPOINT = 'container_scanning.json';
const DEPENDENCY_SCANNING_DIFF_ENDPOINT = 'dependency_scanning.json';
const DAST_DIFF_ENDPOINT = 'dast.json';
const SAST_DIFF_ENDPOINT = 'sast.json';
const PIPELINE_JOBS_ENDPOINT = 'jobs.json';
const SECRET_SCANNING_DIFF_ENDPOINT = 'secret_detection.json';
const COVERAGE_FUZZING_DIFF_ENDPOINT = 'coverage_fuzzing.json';
......@@ -51,6 +51,7 @@ describe('Grouped security reports app', () => {
vulnerabilityFeedbackHelpPath: 'path',
coverageFuzzingHelpPath: 'path',
pipelineId: 123,
projectId: 321,
projectFullPath: 'path',
};
......@@ -78,6 +79,7 @@ describe('Grouped security reports app', () => {
},
},
},
store: appStore(),
});
};
......@@ -87,10 +89,6 @@ describe('Grouped security reports app', () => {
});
afterEach(() => {
wrapper.vm.$store.replaceState({
...state(),
sast: sastState(),
});
wrapper.vm.$destroy();
mock.restore();
});
......@@ -169,6 +167,7 @@ describe('Grouped security reports app', () => {
describe('while loading', () => {
beforeEach(() => {
mock.onGet(PIPELINE_JOBS_ENDPOINT).reply(200, {});
mock.onGet(CONTAINER_SCANNING_DIFF_ENDPOINT).reply(200, {});
mock.onGet(DEPENDENCY_SCANNING_DIFF_ENDPOINT).reply(200, {});
mock.onGet(DAST_DIFF_ENDPOINT).reply(200, {});
......@@ -606,7 +605,7 @@ describe('Grouped security reports app', () => {
});
});
it(`${shouldShowFuzzing ? 'renders' : 'does not render'}`, () => {
it(`${shouldShowFuzzing ? 'renders' : 'does not render'} security row`, () => {
expect(wrapper.find('[data-qa-selector="coverage_fuzzing_report"]').exists()).toBe(
shouldShowFuzzing,
);
......
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