From 2bc6eb17cfe5b0cd07a9e3ba620fcf06f0e9ea88 Mon Sep 17 00:00:00 2001 From: Miri Safra <122939076+MiriSafra@users.noreply.github.com> Date: Mon, 4 Nov 2024 18:40:08 +0200 Subject: [PATCH] :sparkles: Add cancel option for multiple analyses (#2099) Added support for cancelling multiple analyses simultaneously. Integrated functionality into the actions menu for bulk applications. Resolves: https://issues.redhat.com/browse/MTA-3639 --------- Signed-off-by: MiriSafra Signed-off-by: shevijacobson Co-authored-by: shevijacobson --- client/public/locales/en/translation.json | 6 ++ .../applications-table/applications-table.tsx | 63 +++++++++++++++++-- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/client/public/locales/en/translation.json b/client/public/locales/en/translation.json index 6942e06e7d..c47a362674 100644 --- a/client/public/locales/en/translation.json +++ b/client/public/locales/en/translation.json @@ -14,6 +14,7 @@ "accept": "Accept", "back": "Back", "cancel": "Cancel", + "cancelTasks": "Cancel tasks", "checkDocumentation": "Check documentation", "clearAllFilters": "Clear all filters", "clearRepositoryNotSupported": "This action is disabled when RWX volumes are not available", @@ -107,7 +108,9 @@ "dialog": { "message": { "applicationsBulkDelete": "The selected application(s) will be deleted.", + "TasksBulkCancel": "The selected task(s) will be canceled.", "delete": "This action cannot be undone.", + "cancel": "This action cannot be undone.", "discardAssessment": "The assessment(s) for <1>{{applicationName}} will be discarded. Do you wish to continue?", "discardReview": "The review for <1>{{applicationName}} will be discarded. Do you wish to continue?", "leavePage": "Are you sure you want to leave this page? Be sure to save your changes, or they will be lost.", @@ -124,7 +127,9 @@ "copyApplicationAssessmentAndReviewFrom": "Copy {{what}} assessment and review", "copyApplicationAssessmentFrom": "Copy {{what}} assessment", "delete": "Delete {{what}}?", + "cancel": "Cancel {{what}}?", "deleteWithName": "Delete {{what}} \"{{name}}\"?", + "cancelWithName": "Cancel {{what}} \"{{name}}\"?", "discard": "Discard {{what}}?", "download": "Download {{what}}", "edit": "Edit {{what}}", @@ -481,6 +486,7 @@ "tagCategoryDeleted": "Tag category deleted", "tagCategories": "Tag categories", "tasks": "Tasks", + "task": "Task", "teamMember": "team member", "terminated": "Terminated", "ticket": "Ticket", diff --git a/client/src/app/pages/applications/applications-table/applications-table.tsx b/client/src/app/pages/applications/applications-table/applications-table.tsx index d102d69a2a..a7542103c7 100644 --- a/client/src/app/pages/applications/applications-table/applications-table.tsx +++ b/client/src/app/pages/applications/applications-table/applications-table.tsx @@ -116,7 +116,6 @@ export const ApplicationsTable: React.FC = () => { const history = useHistory(); const token = keycloak.tokenParsed; - // ----- State for the modals const [saveApplicationModalState, setSaveApplicationModalState] = useState< "create" | DecoratedApplication | null @@ -156,7 +155,9 @@ export const ApplicationsTable: React.FC = () => { const [applicationsToDelete, setApplicationsToDelete] = useState< DecoratedApplication[] >([]); - + const [tasksToCancel, setTasksToCancel] = useState( + [] + ); const [assessmentToDiscard, setAssessmentToDiscard] = useState(null); @@ -214,7 +215,7 @@ export const ApplicationsTable: React.FC = () => { const isTaskCancellable = (application: DecoratedApplication) => { const task = application.tasks.currentAnalyzer; - return !TaskStates.Terminal.includes(task?.state ?? ""); + return !!task && !TaskStates.Terminal.includes(task?.state ?? ""); }; // TODO: Review the refetchInterval calculation for the application list @@ -272,7 +273,6 @@ export const ApplicationsTable: React.FC = () => { }); } ); - const discardReview = async (application: DecoratedApplication) => { if (application.review) { deleteReview({ @@ -297,7 +297,6 @@ export const ApplicationsTable: React.FC = () => { }); } ); - const discardAssessment = async (application: DecoratedApplication) => { if (application.assessments) { application.assessments.forEach((assessment) => { @@ -575,6 +574,23 @@ export const ApplicationsTable: React.FC = () => { > {t("actions.delete")} , + ...(tasksReadAccess && tasksWriteAccess + ? [ + + isTaskCancellable(application) + ) + } + onClick={() => { + handleCancelBulkAnalysis(); + }} + > + {t("actions.cancelAnalysis")} + , + ] + : []), ...(credentialsReadAccess ? [ { }) ); }; + const handleCancelBulkAnalysis = () => { + const runningTasksToCancel = selectedRows.filter((application) => + isTaskCancellable(application) + ); + setTasksToCancel(runningTasksToCancel); + }; const assessSelectedApp = async (application: DecoratedApplication) => { setApplicationToAssess(application); @@ -1149,6 +1171,37 @@ export const ApplicationsTable: React.FC = () => { if (ids) bulkDeleteApplication({ ids: ids }); }} /> + 1 + ? "dialog.title.cancel" + : "dialog.title.cancelWithName", + { + what: + tasksToCancel.length > 1 + ? t("terms.tasks").toLowerCase() + : t("terms.task").toLowerCase(), + name: tasksToCancel.length === 1 && tasksToCancel[0].name, + } + )} + titleIconVariant={"warning"} + isOpen={tasksToCancel.length > 0} + message={`${ + tasksToCancel.length > 1 ? t("dialog.message.TasksBulkCancel") : "" + } ${t("dialog.message.cancel")}`} + aria-label="Tasks bulk cancel" + confirmBtnVariant={ButtonVariant.danger} + confirmBtnLabel={t("actions.cancelTasks")} + cancelBtnLabel={t("actions.cancel")} + onCancel={() => setTasksToCancel([])} + onClose={() => setTasksToCancel([])} + onConfirm={() => { + tasksToCancel.forEach((application) => { + cancelAnalysis(application); + }); + setTasksToCancel([]); + }} + />