Commit 52a2b0d6 authored by Kev's avatar Kev

Add basic vulnerability component

The events footer <-> header don't work yet, including all related tests
but all three components are now in a root component.
parent 652e84b1
import Vue from 'vue'; import Vue from 'vue';
import MainApp from 'ee/vulnerabilities/components/main.vue';
import HeaderApp from 'ee/vulnerabilities/components/header.vue'; import HeaderApp from 'ee/vulnerabilities/components/header.vue';
import DetailsApp from 'ee/vulnerabilities/components/details.vue'; import DetailsApp from 'ee/vulnerabilities/components/details.vue';
import FooterApp from 'ee/vulnerabilities/components/footer.vue'; import FooterApp from 'ee/vulnerabilities/components/footer.vue';
...@@ -101,8 +102,32 @@ function createFooterApp() { ...@@ -101,8 +102,32 @@ function createFooterApp() {
}); });
} }
function createMainApp() {
const el = document.getElementById('js-vulnerability-main');
const vulnerability = JSON.parse(el.dataset.vulnerability);
return new Vue({
el,
provide: {
reportType: vulnerability.report_type,
createIssueUrl: vulnerability.create_issue_url,
projectFingerprint: vulnerability.project_fingerprint,
vulnerabilityId: vulnerability.id,
},
render: h =>
h(MainApp, {
props: {
vulnerability,
},
}),
});
}
window.addEventListener('DOMContentLoaded', () => { window.addEventListener('DOMContentLoaded', () => {
createHeaderApp(); // createHeaderApp();
createDetailsApp(); // createDetailsApp();
createFooterApp(); // createFooterApp();
createMainApp();
}); });
...@@ -10,7 +10,6 @@ import { deprecatedCreateFlash as createFlash } from '~/flash'; ...@@ -10,7 +10,6 @@ import { deprecatedCreateFlash as createFlash } from '~/flash';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import RelatedIssues from './related_issues.vue'; import RelatedIssues from './related_issues.vue';
import HistoryEntry from './history_entry.vue'; import HistoryEntry from './history_entry.vue';
import VulnerabilitiesEventBus from './vulnerabilities_event_bus';
import initUserPopovers from '~/user_popovers'; import initUserPopovers from '~/user_popovers';
export default { export default {
...@@ -84,8 +83,6 @@ export default { ...@@ -84,8 +83,6 @@ export default {
created() { created() {
this.fetchDiscussions(); this.fetchDiscussions();
VulnerabilitiesEventBus.$on('VULNERABILITY_STATE_CHANGE', this.fetchDiscussions);
}, },
updated() { updated() {
...@@ -187,7 +184,7 @@ export default { ...@@ -187,7 +184,7 @@ export default {
// Emit an event that tells the header to refresh the vulnerability. // Emit an event that tells the header to refresh the vulnerability.
if (isVulnerabilityStateChanged) { if (isVulnerabilityStateChanged) {
VulnerabilitiesEventBus.$emit('VULNERABILITY_STATE_CHANGED'); this.$emit('vulnerability-state-change');
} }
}, },
}, },
......
...@@ -13,7 +13,6 @@ import ResolutionAlert from './resolution_alert.vue'; ...@@ -13,7 +13,6 @@ import ResolutionAlert from './resolution_alert.vue';
import VulnerabilityStateDropdown from './vulnerability_state_dropdown.vue'; import VulnerabilityStateDropdown from './vulnerability_state_dropdown.vue';
import StatusDescription from './status_description.vue'; import StatusDescription from './status_description.vue';
import { VULNERABILITY_STATE_OBJECTS, FEEDBACK_TYPES, HEADER_ACTION_BUTTONS } from '../constants'; import { VULNERABILITY_STATE_OBJECTS, FEEDBACK_TYPES, HEADER_ACTION_BUTTONS } from '../constants';
import VulnerabilitiesEventBus from './vulnerabilities_event_bus';
export default { export default {
name: 'VulnerabilityHeader', name: 'VulnerabilityHeader',
...@@ -116,14 +115,6 @@ export default { ...@@ -116,14 +115,6 @@ export default {
}, },
}, },
created() {
VulnerabilitiesEventBus.$on('VULNERABILITY_STATE_CHANGED', this.refreshVulnerability);
},
destroyed() {
VulnerabilitiesEventBus.$off('VULNERABILITY_STATE_CHANGED', this.refreshVulnerability);
},
methods: { methods: {
triggerClick(action) { triggerClick(action) {
const fn = this[action]; const fn = this[action];
...@@ -145,7 +136,7 @@ export default { ...@@ -145,7 +136,7 @@ export default {
}) })
.finally(() => { .finally(() => {
this.isLoadingVulnerability = false; this.isLoadingVulnerability = false;
VulnerabilitiesEventBus.$emit('VULNERABILITY_STATE_CHANGE'); this.$emit('vulnerability-state-change');
}); });
}, },
createMergeRequest() { createMergeRequest() {
...@@ -237,9 +228,7 @@ export default { ...@@ -237,9 +228,7 @@ export default {
:class=" :class="
`text-capitalize align-self-center issuable-status-box status-box status-box-${statusBoxStyle}` `text-capitalize align-self-center issuable-status-box status-box status-box-${statusBoxStyle}`
" "
> >{{ vulnerability.state }}</span>
{{ vulnerability.state }}
</span>
<status-description <status-description
class="issuable-meta" class="issuable-meta"
...@@ -273,9 +262,7 @@ export default { ...@@ -273,9 +262,7 @@ export default {
category="secondary" category="secondary"
:loading="isProcessingAction" :loading="isProcessingAction"
@click="triggerClick(actionButtons[0].action)" @click="triggerClick(actionButtons[0].action)"
> >{{ actionButtons[0].name }}</gl-button>
{{ actionButtons[0].name }}
</gl-button>
</div> </div>
</div> </div>
</div> </div>
......
<script>
import Vue from 'vue';
import VulnerabilityHeader from './header.vue';
import VulnerabilityDetails from './details.vue';
import VulnerabilityFooter from './footer.vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { VULNERABILITY_STATE_OBJECTS } from 'ee/vulnerabilities/constants';
export default {
name: 'Vulnerability',
components: { VulnerabilityHeader, VulnerabilityDetails, VulnerabilityFooter },
props: {
vulnerability: {
type: Object,
required: true,
},
},
computed: {
footerInfo: function() {
const {
vulnerabilityFeedbackHelpPath,
hasMr,
discussionsUrl,
createIssueUrl,
state,
issueFeedback,
mergeRequestFeedback,
notesUrl,
project,
projectFingerprint,
remediations,
reportType,
solution,
id,
canModifyRelatedIssues,
relatedIssuesHelpPath,
} = convertObjectPropsToCamelCase(this.vulnerability);
const remediation = remediations?.length ? remediations[0] : null;
const hasDownload = Boolean(
state !== VULNERABILITY_STATE_OBJECTS.resolved.state && remediation?.diff?.length && !hasMr,
);
const hasRemediation = Boolean(remediation);
const props = {
vulnerabilityId: id,
discussionsUrl,
notesUrl,
projectFingerprint,
solutionInfo: {
solution,
remediation,
hasDownload,
hasMr,
hasRemediation,
vulnerabilityFeedbackHelpPath,
isStandaloneVulnerability: true,
},
createIssueUrl,
reportType,
issueFeedback,
mergeRequestFeedback,
canModifyRelatedIssues,
project: {
url: this.vulnerability.project.full_path,
value: this.vulnerability.project.full_name,
},
relatedIssuesHelpPath,
};
return props;
},
},
methods: {
handleVulnerabilityStateChange(a) {
console.error('state-change', this.footerInfo);
this.$refs.footer.fetchDiscussions();
},
},
};
</script>
<template>
<div>
<vulnerability-header
:initialVulnerability="vulnerability"
v-on:vulnerability-state-change="handleVulnerabilityStateChange"
></vulnerability-header>
<vulnerability-details :vulnerability="vulnerability"></vulnerability-details>
<vulnerability-footer
v-bind="footerInfo"
v-on:vulnerability-state-change="handleVulnerabilityStateChange"
ref="footer"
></vulnerability-footer>
</div>
</template>
\ No newline at end of file
...@@ -8,3 +8,5 @@ ...@@ -8,3 +8,5 @@
#js-vulnerability-header{ data: vulnerability_init_details } #js-vulnerability-header{ data: vulnerability_init_details }
#js-vulnerability-details{ data: vulnerability_init_details } #js-vulnerability-details{ data: vulnerability_init_details }
#js-vulnerability-footer{ data: vulnerability_init_details } #js-vulnerability-footer{ data: vulnerability_init_details }
#js-vulnerability-main{ data: vulnerability_init_details }
...@@ -123,7 +123,6 @@ describe('Vulnerability Header', () => { ...@@ -123,7 +123,6 @@ describe('Vulnerability Header', () => {
it('when the vulnerability state dropdown emits a change event, the vulnerabilities event bus event is emitted with the proper event', () => { it('when the vulnerability state dropdown emits a change event, the vulnerabilities event bus event is emitted with the proper event', () => {
const newState = 'dismiss'; const newState = 'dismiss';
const spy = jest.spyOn(VulnerabilitiesEventBus, '$emit');
mockAxios.onPost().reply(201, { state: newState }); mockAxios.onPost().reply(201, { state: newState });
expect(findBadge().text()).not.toBe(newState); expect(findBadge().text()).not.toBe(newState);
...@@ -132,8 +131,7 @@ describe('Vulnerability Header', () => { ...@@ -132,8 +131,7 @@ describe('Vulnerability Header', () => {
dropdown.vm.$emit('change'); dropdown.vm.$emit('change');
return waitForPromises().then(() => { return waitForPromises().then(() => {
expect(spy).toHaveBeenCalledTimes(1); expect(wrapper.emitted()['vulnerability-state-change']).toBeTruthy();
expect(spy).toHaveBeenCalledWith('VULNERABILITY_STATE_CHANGE');
}); });
}); });
...@@ -374,7 +372,8 @@ describe('Vulnerability Header', () => { ...@@ -374,7 +372,8 @@ describe('Vulnerability Header', () => {
const vulnerability = { state: 'dismissed' }; const vulnerability = { state: 'dismissed' };
mockAxios.onGet(url).replyOnce(200, vulnerability); mockAxios.onGet(url).replyOnce(200, vulnerability);
createWrapper(); createWrapper();
VulnerabilitiesEventBus.$emit('VULNERABILITY_STATE_CHANGED');
wrapper.vm.$emit('vulnerability-state-change');
await waitForPromises(); await waitForPromises();
expect(findBadge().text()).toBe(vulnerability.state); expect(findBadge().text()).toBe(vulnerability.state);
...@@ -384,7 +383,7 @@ describe('Vulnerability Header', () => { ...@@ -384,7 +383,7 @@ describe('Vulnerability Header', () => {
it('shows an error message when the vulnerability cannot be loaded', async () => { it('shows an error message when the vulnerability cannot be loaded', async () => {
mockAxios.onGet().replyOnce(500); mockAxios.onGet().replyOnce(500);
createWrapper(); createWrapper();
VulnerabilitiesEventBus.$emit('VULNERABILITY_STATE_CHANGED'); wrapper.vm.$emit('vulnerability-state-change');
await waitForPromises(); await waitForPromises();
expect(createFlash).toHaveBeenCalledTimes(1); expect(createFlash).toHaveBeenCalledTimes(1);
...@@ -394,12 +393,12 @@ describe('Vulnerability Header', () => { ...@@ -394,12 +393,12 @@ describe('Vulnerability Header', () => {
it('cancels a pending refresh request if the vulnerability state has changed', async () => { it('cancels a pending refresh request if the vulnerability state has changed', async () => {
mockAxios.onGet().reply(200); mockAxios.onGet().reply(200);
createWrapper(); createWrapper();
VulnerabilitiesEventBus.$emit('VULNERABILITY_STATE_CHANGED'); wrapper.vm.$emit('vulnerability-state-change');
const source = wrapper.vm.refreshVulnerabilitySource; const source = wrapper.vm.refreshVulnerabilitySource;
const spy = jest.spyOn(source, 'cancel'); const spy = jest.spyOn(source, 'cancel');
VulnerabilitiesEventBus.$emit('VULNERABILITY_STATE_CHANGED'); wrapper.vm.$emit('vulnerability-state-change');
await waitForPromises(); await waitForPromises();
expect(createFlash).toHaveBeenCalledTimes(0); expect(createFlash).toHaveBeenCalledTimes(0);
......
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