Skip to content

Commit adf6435

Browse files
authored
Merge pull request #3742 from harshithb3304/feature/enable-triage-button
enabled ignore-settings for compliance page and triage buttons
2 parents 8bb17e0 + a692716 commit adf6435

File tree

4 files changed

+235
-41
lines changed

4 files changed

+235
-41
lines changed

apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CompliancePage.jsx

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import func from "@/util/func";
77
import { MarkFulfilledMinor, ReportMinor, ExternalMinor } from '@shopify/polaris-icons';
88
import PersistStore from "../../../../main/PersistStore";
99
import { Button, Popover, Box, Avatar, Text, HorizontalGrid, HorizontalStack, IndexFiltersMode, VerticalStack, ActionList } from "@shopify/polaris";
10+
import CompulsoryDescriptionModal from "../components/CompulsoryDescriptionModal.jsx";
1011
import EmptyScreensLayout from "../../../components/banners/EmptyScreensLayout";
1112
import { ISSUES_PAGE_DOCS_URL } from "../../../../main/onboardingData";
1213
import {SelectCollectionComponent} from "../../testing/TestRunsPage/TestrunsBannerComponent"
@@ -27,9 +28,9 @@ import TableStore from "../../../components/tables/TableStore.js";
2728
import CriticalFindingsGraph from "./CriticalFindingsGraph.jsx";
2829
import settingFunctions from "../../settings/module.js";
2930
import JiraTicketCreationModal from "../../../components/shared/JiraTicketCreationModal.jsx";
30-
import testingApi from "../../testing/api.js"
3131
import issuesFunctions from '@/apps/dashboard/pages/issues/module';
3232
import { isMCPSecurityCategory, isGenAISecurityCategory, isAgenticSecurityCategory, mapLabel, getDashboardCategory } from "../../../../main/labelHelper";
33+
import testingApi from "../../testing/api.js"
3334

3435
const sortOptions = [
3536
{ label: 'Severity', value: 'severity asc', directionLabel: 'Highest', sortKey: 'severity', columnIndex: 2 },
@@ -194,6 +195,17 @@ function CompliancePage() {
194195
const [serviceNowTables, setServiceNowTables] = useState([])
195196
const [serviceNowTable, setServiceNowTable] = useState('')
196197

198+
// Compulsory description modal states
199+
const [compulsoryDescriptionModal, setCompulsoryDescriptionModal] = useState(false)
200+
const [pendingIgnoreAction, setPendingIgnoreAction] = useState(null)
201+
const [mandatoryDescription, setMandatoryDescription] = useState("")
202+
const [modalLoading, setModalLoading] = useState(false)
203+
const [compulsorySettings, setCompulsorySettings] = useState({
204+
falsePositive: false,
205+
noTimeToFix: false,
206+
acceptableFix: false
207+
})
208+
197209

198210
const [currDateRange, dispatchCurrDateRange] = useReducer(produce((draft, action) => func.dateRangeReducer(draft, action)), values.ranges[5])
199211

@@ -260,6 +272,65 @@ function CompliancePage() {
260272
setKey(!key)
261273
}, [startTimestamp, endTimestamp])
262274

275+
// Fetch compulsory description settings
276+
useEffect(() => {
277+
const fetchCompulsorySettings = async () => {
278+
try {
279+
const {resp} = await settingFunctions.fetchAdminInfo();
280+
281+
if (resp?.compulsoryDescription) {
282+
setCompulsorySettings(resp.compulsoryDescription);
283+
}
284+
} catch (error) {
285+
}
286+
};
287+
fetchCompulsorySettings();
288+
}, []);
289+
290+
// Map ignore reason text to key
291+
const getIgnoreReasonKey = (ignoreReason) => {
292+
const reasonMap = {
293+
"False positive": "falsePositive",
294+
"Acceptable risk": "acceptableFix",
295+
"No time to fix": "noTimeToFix"
296+
};
297+
return reasonMap[ignoreReason] || ignoreReason;
298+
};
299+
300+
// Use keys directly for reasons and compulsorySettings
301+
const requiresDescription = (reasonKey) => {
302+
return compulsorySettings[reasonKey] || false;
303+
};
304+
305+
const handleIgnoreWithDescription = () => {
306+
if (pendingIgnoreAction && mandatoryDescription.trim()) {
307+
// Update description first for all items
308+
const updatePromises = pendingIgnoreAction.items.map(item =>
309+
testingApi.updateIssueDescription(item, mandatoryDescription)
310+
);
311+
Promise.allSettled(updatePromises).then(() => {
312+
performBulkIgnoreAction(pendingIgnoreAction.items, pendingIgnoreAction.reason, mandatoryDescription);
313+
setCompulsoryDescriptionModal(false);
314+
setPendingIgnoreAction(null);
315+
setMandatoryDescription("");
316+
});
317+
}
318+
};
319+
320+
useEffect(() => {
321+
if (compulsoryDescriptionModal && pendingIgnoreAction && pendingIgnoreAction.items?.length > 0) {
322+
setMandatoryDescription("");
323+
setModalLoading(false);
324+
}
325+
}, [compulsoryDescriptionModal, pendingIgnoreAction]);
326+
327+
const performBulkIgnoreAction = (items, ignoreReason, description = "") => {
328+
api.bulkUpdateIssueStatus(items, "IGNORED", ignoreReason, { description }).then((res) => {
329+
setToast(true, false, `Issue${items.length==1 ? "" : "s"} ignored${description ? " with description" : ""}`)
330+
resetResourcesSelected()
331+
})
332+
}
333+
263334
const [searchParams, setSearchParams] = useSearchParams();
264335
const resultId = searchParams.get("result")
265336

@@ -363,10 +434,13 @@ function CompliancePage() {
363434
}
364435

365436
function ignoreAction(ignoreReason){
366-
api.bulkUpdateIssueStatus(items, "IGNORED", ignoreReason, {} ).then((res) => {
367-
setToast(true, false, `Issue${items.length==1 ? "" : "s"} ignored`)
368-
resetResourcesSelected()
369-
})
437+
const reasonKey = getIgnoreReasonKey(ignoreReason);
438+
if (requiresDescription(reasonKey)) {
439+
setPendingIgnoreAction({ items, reason: ignoreReason });
440+
setCompulsoryDescriptionModal(true);
441+
return;
442+
}
443+
performBulkIgnoreAction(items, ignoreReason);
370444
}
371445

372446
function reopenAction(){
@@ -794,6 +868,16 @@ function CompliancePage() {
794868
issueType=""
795869
isServiceNowModal={true}
796870
/>
871+
872+
<CompulsoryDescriptionModal
873+
open={compulsoryDescriptionModal}
874+
onClose={() => setCompulsoryDescriptionModal(false)}
875+
onConfirm={handleIgnoreWithDescription}
876+
reasonLabel={pendingIgnoreAction?.reason}
877+
description={mandatoryDescription}
878+
onChangeDescription={setMandatoryDescription}
879+
loading={modalLoading}
880+
/>
797881
</>
798882
)
799883
}

apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import Store from "../../../store";
66
import func from "@/util/func";
77
import { MarkFulfilledMinor, ReportMinor, ExternalMinor } from '@shopify/polaris-icons';
88
import PersistStore from "../../../../main/PersistStore";
9-
import { ActionList, Button, HorizontalGrid, HorizontalStack, IndexFiltersMode, Popover, Modal, TextField, Text, VerticalStack } from "@shopify/polaris";
9+
import { ActionList, Button, HorizontalGrid, HorizontalStack, IndexFiltersMode, Popover, TextField, Text, VerticalStack } from "@shopify/polaris";
10+
import CompulsoryDescriptionModal from "../components/CompulsoryDescriptionModal.jsx";
1011
import EmptyScreensLayout from "../../../components/banners/EmptyScreensLayout";
1112
import { ISSUES_PAGE_DOCS_URL } from "../../../../main/onboardingData";
1213
import {SelectCollectionComponent} from "../../testing/TestRunsPage/TestrunsBannerComponent"
@@ -977,39 +978,15 @@ function IssuesPage() {
977978
isServiceNowModal={true}
978979
/>
979980

980-
<Modal
981+
<CompulsoryDescriptionModal
981982
open={compulsoryDescriptionModal}
982983
onClose={() => setCompulsoryDescriptionModal(false)}
983-
title="Description Required"
984-
primaryAction={{
985-
content: modalLoading ? 'Loading...' : 'Confirm',
986-
onAction: handleIgnoreWithDescription,
987-
disabled: mandatoryDescription.trim().length === 0 || modalLoading
988-
}}
989-
secondaryActions={[
990-
{
991-
content: 'Cancel',
992-
onAction: () => setCompulsoryDescriptionModal(false)
993-
}
994-
]}
995-
>
996-
<Modal.Section>
997-
<VerticalStack gap="4">
998-
<Text variant="bodyMd">
999-
A description is required for this action based on your account settings. Please provide a reason for marking these issues as "{pendingIgnoreAction?.reason}".
1000-
</Text>
1001-
<TextField
1002-
label="Description"
1003-
value={mandatoryDescription}
1004-
onChange={setMandatoryDescription}
1005-
multiline={4}
1006-
autoComplete="off"
1007-
placeholder="Please provide a description for this action..."
1008-
disabled={modalLoading}
1009-
/>
1010-
</VerticalStack>
1011-
</Modal.Section>
1012-
</Modal>
984+
onConfirm={handleIgnoreWithDescription}
985+
reasonLabel={pendingIgnoreAction?.reason}
986+
description={mandatoryDescription}
987+
onChangeDescription={setMandatoryDescription}
988+
loading={modalLoading}
989+
/>
1013990
</>
1014991
)
1015992
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Modal, Text, TextField, VerticalStack } from "@shopify/polaris";
2+
3+
const CompulsoryDescriptionModal = ({
4+
open,
5+
reasonLabel,
6+
description,
7+
onChangeDescription,
8+
onConfirm,
9+
onClose,
10+
loading = false
11+
}) => {
12+
return (
13+
<Modal
14+
open={open}
15+
onClose={onClose}
16+
title="Description Required"
17+
primaryAction={{
18+
content: loading ? 'Loading...' : 'Confirm',
19+
onAction: onConfirm,
20+
disabled: description.trim().length === 0 || loading
21+
}}
22+
secondaryActions={[
23+
{
24+
content: 'Cancel',
25+
onAction: onClose
26+
}
27+
]}
28+
>
29+
<Modal.Section>
30+
<VerticalStack gap="4">
31+
<Text variant="bodyMd">
32+
A description is required for this action based on your account settings. Please provide a reason for marking these issues as "{reasonLabel}".
33+
</Text>
34+
<TextField
35+
label="Description"
36+
value={description}
37+
onChange={onChangeDescription}
38+
multiline={4}
39+
autoComplete="off"
40+
placeholder="Please provide a description for this action..."
41+
disabled={loading}
42+
/>
43+
</VerticalStack>
44+
</Modal.Section>
45+
</Modal>
46+
)
47+
}
48+
49+
export default CompulsoryDescriptionModal

apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/TestRunResultPage/TestRunResultFlyout.jsx

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import SampleDataList from '../../../components/shared/SampleDataList'
66
import SampleData from '../../../components/shared/SampleData'
77
import LayoutWithTabs from '../../../components/layouts/LayoutWithTabs'
88
import { Badge, Box, Button, Divider, HorizontalStack, Icon, Popover, Text, VerticalStack, Link} from '@shopify/polaris'
9+
import CompulsoryDescriptionModal from "../../issues/components/CompulsoryDescriptionModal.jsx"
910
import api from '../../observe/api'
1011
import issuesApi from "../../issues/api"
1112
import testingApi from "../api"
@@ -57,6 +58,17 @@ function TestRunResultFlyout(props) {
5758
const [refreshFlag, setRefreshFlag] = useState(Date.now().toString())
5859
const [labelsText, setLabelsText] = useState("")
5960

61+
// Compulsory description modal states
62+
const [compulsoryDescriptionModal, setCompulsoryDescriptionModal] = useState(false)
63+
const [pendingIgnoreAction, setPendingIgnoreAction] = useState(null)
64+
const [mandatoryDescription, setMandatoryDescription] = useState("")
65+
const [modalLoading, setModalLoading] = useState(false)
66+
const [compulsorySettings, setCompulsorySettings] = useState({
67+
falsePositive: false,
68+
noTimeToFix: false,
69+
acceptableFix: false
70+
})
71+
6072
const fetchRemediationInfo = useCallback (async (testId) => {
6173
if (testId && testId.length > 0) {
6274
await testingApi.fetchRemediationInfo(testId).then((resp) => {
@@ -121,7 +133,6 @@ function TestRunResultFlyout(props) {
121133
props.setIssueDetails({ ...issueDetails, description: editDescription });
122134
}
123135
} catch (err) {
124-
console.error("Failed to save description:", err);
125136
func.setToast(true, true, "Failed to save description");
126137
}
127138
}
@@ -139,18 +150,80 @@ function TestRunResultFlyout(props) {
139150
}
140151
}, [selectedTestRunResult.testCategoryId, remediationSrc, conversationRemediationText, fetchRemediationInfo])
141152

153+
// Fetch compulsory description settings
154+
useEffect(() => {
155+
const fetchCompulsorySettings = async () => {
156+
try {
157+
const {resp} = await settingFunctions.fetchAdminInfo();
158+
159+
if (resp?.compulsoryDescription) {
160+
setCompulsorySettings(resp.compulsoryDescription);
161+
}
162+
} catch (error) {
163+
164+
}
165+
};
166+
fetchCompulsorySettings();
167+
}, []);
168+
169+
// Map ignore reason text to key
170+
const getIgnoreReasonKey = (ignoreReason) => {
171+
const reasonMap = {
172+
"False positive": "falsePositive",
173+
"Acceptable risk": "acceptableFix",
174+
"No time to fix": "noTimeToFix"
175+
};
176+
return reasonMap[ignoreReason] || ignoreReason;
177+
};
178+
179+
// Use keys directly for reasons and compulsorySettings
180+
const requiresDescription = (reasonKey) => {
181+
return compulsorySettings[reasonKey] || false;
182+
};
183+
184+
const handleIgnoreWithDescription = () => {
185+
if (pendingIgnoreAction && mandatoryDescription.trim()) {
186+
// Update description first
187+
testingApi.updateIssueDescription(issueDetails.id, mandatoryDescription).then(() => {
188+
performIgnoreAction(pendingIgnoreAction.ignoreReason, mandatoryDescription);
189+
setCompulsoryDescriptionModal(false);
190+
setPendingIgnoreAction(null);
191+
setMandatoryDescription("");
192+
}).catch((err) => {
193+
func.setToast(true, true, "Failed to update description");
194+
});
195+
}
196+
};
197+
198+
useEffect(() => {
199+
if (compulsoryDescriptionModal && pendingIgnoreAction) {
200+
setMandatoryDescription("");
201+
setModalLoading(false);
202+
}
203+
}, [compulsoryDescriptionModal, pendingIgnoreAction]);
142204

143-
function ignoreAction(ignoreReason){
205+
const performIgnoreAction = (ignoreReason, description = "") => {
144206
const severity = (selectedTestRunResult && selectedTestRunResult.vulnerable) ? issueDetails.severity : "";
145207
let obj = {}
146208
if(issueDetails?.testRunIssueStatus !== "IGNORED"){
147209
obj = {[selectedTestRunResult.id]: severity.toUpperCase()}
148210
}
149-
issuesApi.bulkUpdateIssueStatus([issueDetails.id], "IGNORED", ignoreReason, obj ).then((res) => {
150-
func.setToast(true, false, `Issue ignored`)
211+
issuesApi.bulkUpdateIssueStatus([issueDetails.id], "IGNORED", ignoreReason, { description }).then((res) => {
212+
func.setToast(true, false, `Issue ignored${description ? " with description" : ""}`)
151213
})
152214
}
153215

216+
217+
function ignoreAction(ignoreReason){
218+
const reasonKey = getIgnoreReasonKey(ignoreReason);
219+
if (requiresDescription(reasonKey)) {
220+
setPendingIgnoreAction({ ignoreReason });
221+
setCompulsoryDescriptionModal(true);
222+
return;
223+
}
224+
performIgnoreAction(ignoreReason);
225+
}
226+
154227
function reopenAction(){
155228
issuesApi.bulkUpdateIssueStatus([issueDetails.id], "OPEN", "" ).then((res) => {
156229
func.setToast(true, false, "Issue re-opened")
@@ -761,6 +834,7 @@ function TestRunResultFlyout(props) {
761834
const title = isIssuePage ? "Issue details" : mapLabel('Test result', getDashboardCategory())
762835

763836
return (
837+
<>
764838
<FlyLayout
765839
title={title}
766840
show={showDetails}
@@ -772,6 +846,16 @@ function TestRunResultFlyout(props) {
772846
handleClose={handleClose}
773847
isHandleClose={true}
774848
/>
849+
<CompulsoryDescriptionModal
850+
open={compulsoryDescriptionModal}
851+
onClose={() => setCompulsoryDescriptionModal(false)}
852+
onConfirm={handleIgnoreWithDescription}
853+
reasonLabel={pendingIgnoreAction?.ignoreReason}
854+
description={mandatoryDescription}
855+
onChangeDescription={setMandatoryDescription}
856+
loading={modalLoading}
857+
/>
858+
</>
775859
)
776860
}
777861

0 commit comments

Comments
 (0)