Skip to content
Snippets Groups Projects
Commit b8050d35 authored by Phil Hughes's avatar Phil Hughes
Browse files

Merge branch '390216-add-dismiss-with-comment-functionality' into 'master'

Add dismiss-with-comment to GraphQL finding modal

See merge request https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111903



Merged-by: Phil Hughes's avatarPhil Hughes <me@iamphill.com>
Approved-by: default avatarSavas Vedova <svedova@gitlab.com>
Approved-by: Phil Hughes's avatarPhil Hughes <me@iamphill.com>
Co-authored-by: default avatarDave Pisek <dpisek@gitlab.com>
parents 3847d798 8828e6fa
No related branches found
No related tags found
No related merge requests found
<script>
import { GlAlert, GlButton, GlModal, GlSkeletonLoader } from '@gitlab/ui';
import {
GlAlert,
GlCard,
GlButton,
GlButtonGroup,
GlFormTextarea,
GlModal,
GlTooltipDirective,
GlSkeletonLoader,
} from '@gitlab/ui';
import { __, s__ } from '~/locale';
import SolutionCard from 'ee/vue_shared/security_reports/components/solution_card_graphql.vue';
import IssueNote from 'ee/vue_shared/security_reports/components/issue_note_graphql.vue';
import VulnerabilityDetailsGraphql from 'ee/security_dashboard/components/shared/vulnerability_details_graphql/index.vue';
import EventItem from 'ee/vue_shared/security_reports/components/event_item.vue';
import securityReportFindingQuery from 'ee/security_dashboard/graphql/queries/security_report_finding.query.graphql';
import dismissFindingMutation from 'ee/security_dashboard/graphql/mutations/dismiss_finding.mutation.graphql';
import revertFindingToDetectedMutation from 'ee/security_dashboard/graphql/mutations/revert_finding_to_detected.mutation.graphql';
Loading
Loading
@@ -14,14 +24,21 @@ export const STATE_DETECTED = 'DETECTED';
 
export default {
components: {
EventItem,
GlAlert,
GlCard,
GlButton,
GlButtonGroup,
GlFormTextarea,
GlModal,
GlSkeletonLoader,
IssueNote,
SolutionCard,
VulnerabilityDetailsGraphql,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
findingUuid: {
type: String,
Loading
Loading
@@ -41,6 +58,8 @@ export default {
hasFindingFetchError: false,
errorMessage: '',
isUpdatingFindingState: false,
isShowingDismissalCommentSection: false,
dismissalComment: '',
};
},
apollo: {
Loading
Loading
@@ -104,8 +123,48 @@ export default {
 
return { ...configs[toggledState], toggledState };
},
currentUser() {
const {
current_user_id: id,
current_username: username,
current_user_fullname: name,
} = window.gon;
return {
author: {
id,
name,
username,
state: 'active',
},
};
},
dismissButtonText() {
const {
isFindingDismissed,
isShowingDismissalCommentSection,
$options: { i18n },
} = this;
if (isFindingDismissed) {
return i18n.revertDismissFinding;
}
if (isShowingDismissalCommentSection) {
return i18n.addCommentAndDismiss;
}
return i18n.dismissFinding;
},
},
methods: {
handleCancel() {
if (this.isShowingDismissalCommentSection) {
this.isShowingDismissalCommentSection = false;
} else {
this.closeModal();
}
},
closeModal() {
this.$refs.modal.hide();
},
Loading
Loading
@@ -124,6 +183,7 @@ export default {
mutation,
variables: {
uuid: this.findingUuid,
...(this.dismissalComment && { comment: this.dismissalComment }),
},
update: (store, response) => {
const { errors } = response.data[mutationName];
Loading
Loading
@@ -162,6 +222,7 @@ export default {
cancel: __('Cancel'),
dismissFinding: s__('SecurityReports|Dismiss vulnerability'),
revertDismissFinding: s__('SecurityReports|Undo dismiss'),
addCommentAndDismiss: s__('SecurityReports|Add comment and dismiss'),
},
};
</script>
Loading
Loading
@@ -199,6 +260,20 @@ export default {
<issue-note :issue-links="issueLinks" :project="finding.project" />
 
<!-- adding the comment-history is captured in: https://gitlab.com/gitlab-org/gitlab/-/issues/337488 -->
<gl-card v-if="isShowingDismissalCommentSection" data-testid="dismissal-comment-section">
<event-item
:author="currentUser.author"
icon-name="cancel"
icon-class="ci-status-icon-pending"
/>
<div class="gl-mt-5 gl-pt-5 gl-border-t">
<gl-form-textarea
v-model="dismissalComment"
autofocus
@keydown.meta.enter="toggleFindingState"
/>
</div>
</gl-card>
</template>
 
<gl-alert v-if="showErrorMessage" variant="danger" :dismissible="false">
Loading
Loading
@@ -207,18 +282,26 @@ export default {
 
<template #modal-footer>
<div data-testid="footer">
<gl-button data-testid="cancel-button" @click="closeModal">
<gl-button data-testid="cancel-button" @click="handleCancel">
{{ $options.i18n.cancel }}
</gl-button>
<gl-button
data-testid="dismiss-button"
:loading="isUpdatingFindingState"
@click="toggleFindingState"
>
{{
isFindingDismissed ? $options.i18n.revertDismissFinding : $options.i18n.dismissFinding
}}
</gl-button>
<gl-button-group>
<gl-button
data-testid="dismiss-button"
:loading="isUpdatingFindingState"
@click="toggleFindingState"
>
{{ dismissButtonText }}
</gl-button>
<gl-button
v-if="!isFindingDismissed && !isShowingDismissalCommentSection"
v-gl-tooltip
data-testid="dismiss-with-comment-button"
icon="comment"
:title="$options.i18n.addCommentAndDismiss"
@click="isShowingDismissalCommentSection = true"
/>
</gl-button-group>
</div>
</template>
</gl-modal>
Loading
Loading
import { GlAlert, GlModal } from '@gitlab/ui';
import Vue from 'vue';
import { GlAlert, GlModal, GlFormTextarea } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
Loading
Loading
@@ -10,6 +10,7 @@ import VulnerabilityFindingModal, {
STATE_DISMISSED,
} from 'ee/security_dashboard/components/pipeline/vulnerability_finding_modal.vue';
import SolutionCard from 'ee/vue_shared/security_reports/components/solution_card_graphql.vue';
import EventItem from 'ee/vue_shared/security_reports/components/event_item.vue';
import VulnerabilityDetailsGraphql from 'ee/security_dashboard/components/shared/vulnerability_details_graphql/index.vue';
import securityReportFindingQuery from 'ee/security_dashboard/graphql/queries/security_report_finding.query.graphql';
import dismissFindingMutation from 'ee/security_dashboard/graphql/mutations/dismiss_finding.mutation.graphql';
Loading
Loading
@@ -90,6 +91,7 @@ describe('ee/security_dashboard/components/pipeline/vulnerability_finding_modal.
wrapper.findByTestId('content-loading-indicator'),
];
const findDismissButton = () => withinFooter().findByTestId('dismiss-button');
const findCancelButton = () => withinFooter().findByTestId('cancel-button');
 
const toggleFindingState = () => findDismissButton().vm.$emit('click');
 
Loading
Loading
@@ -136,7 +138,7 @@ describe('ee/security_dashboard/components/pipeline/vulnerability_finding_modal.
it('contains a "cancel" button that will hide the modal', () => {
expectModalToBeHiddenAfter({
action: () => {
withinFooter().findByTestId('cancel-button').vm.$emit('click');
findCancelButton().vm.$emit('click');
},
});
});
Loading
Loading
@@ -339,5 +341,110 @@ describe('ee/security_dashboard/components/pipeline/vulnerability_finding_modal.
},
);
});
describe('with comment', () => {
const mockDismissFindingMutation = jest.fn().mockResolvedValue({
data: {
securityFindingDismiss: {
errors: [],
},
},
});
beforeEach(() => {
wrapper = createWrapper({
responseHandlers: { dismissMutation: mockDismissFindingMutation },
});
});
const findDismissalCommentSection = () => wrapper.findByTestId('dismissal-comment-section');
const findCommentInput = () => findDismissalCommentSection().findComponent(GlFormTextarea);
const findCommentAndDismissButton = () => wrapper.findByTestId('dismiss-with-comment-button');
it('should not show the dismissal comment section by default', () => {
expect(findDismissalCommentSection().exists()).toBe(false);
});
it('contains a button to add a comment and dismiss', () => {
expect(findCommentAndDismissButton().attributes()).toMatchObject({
title: 'Add comment and dismiss',
});
});
describe('when the "Add comment and dismiss" split-button is clicked', () => {
beforeEach(() => findCommentAndDismissButton().vm.$emit('click'));
it('should show the dismissal comment section', async () => {
expect(findDismissalCommentSection().exists()).toBe(true);
});
it('should hide split-button', () => {
expect(findCommentAndDismissButton().exists()).toBe(false);
});
it('should change the text of the dismissal button', () => {
expect(findDismissButton().text()).toBe('Add comment and dismiss');
});
});
describe('comment section', () => {
let originalGon;
const TEST_CURRENT_USER_ID = '1';
const TEST_CURRENT_USER_NAME = 'root';
const TEST_CURRENT_USER_FULL_NAME = 'root user';
beforeEach(async () => {
originalGon = window.gon;
window.gon = {
current_user_id: TEST_CURRENT_USER_ID,
current_username: TEST_CURRENT_USER_NAME,
current_user_fullname: TEST_CURRENT_USER_FULL_NAME,
};
await findCommentAndDismissButton().vm.$emit('click');
});
afterEach(() => {
window.gon = originalGon;
});
it('shows information about the current user', () => {
expect(findDismissalCommentSection().findComponent(EventItem).props()).toMatchObject({
author: {
id: TEST_CURRENT_USER_ID,
username: TEST_CURRENT_USER_NAME,
name: TEST_CURRENT_USER_FULL_NAME,
state: 'active',
},
});
});
it('contains a text area that receives auto focus', () => {
expect(findCommentInput().attributes('autofocus')).not.toBe(undefined);
});
it('shows an input to enter a comment and dismiss with it', async () => {
expect(mockDismissFindingMutation).not.toHaveBeenCalled();
const comment = 'dismissed because the finding is a false-positive';
findCommentInput().vm.$emit('input', comment);
findDismissButton().vm.$emit('click');
await waitForPromises();
expect(mockDismissFindingMutation).toHaveBeenCalledTimes(1);
const [firstCall] = mockDismissFindingMutation.mock.calls;
expect(firstCall[0].comment).toBe(comment);
});
it('cancels commenting', async () => {
expect(findDismissalCommentSection().exists()).toBe(true);
findCancelButton().vm.$emit('click');
await nextTick();
expect(findDismissalCommentSection().exists()).toBe(false);
});
});
});
});
});
Loading
Loading
@@ -38611,6 +38611,9 @@ msgstr ""
msgid "SecurityReports|Activity"
msgstr ""
 
msgid "SecurityReports|Add comment and dismiss"
msgstr ""
msgid "SecurityReports|Add or remove projects to monitor in the security area. Projects included in this list will have their results displayed in the security dashboard and vulnerability report."
msgstr ""
 
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment