From b5b3cf09bddad1eeaa8f4c3bfbb9f8da1c3ef883 Mon Sep 17 00:00:00 2001 From: gauravwarale Date: Tue, 6 Jan 2026 15:07:41 +0530 Subject: [PATCH 1/4] Implementation job cancelation feature --- web/src/app/api/jobs/jobs.ts | 7 ++ web/src/common/ClientLayout/ClientLayout.tsx | 7 ++ .../Jobs/JobDetails/JobDetailsHeader.tsx | 67 ++++++++++++++++--- 3 files changed, 73 insertions(+), 8 deletions(-) diff --git a/web/src/app/api/jobs/jobs.ts b/web/src/app/api/jobs/jobs.ts index ecf7714..4e2ba8a 100644 --- a/web/src/app/api/jobs/jobs.ts +++ b/web/src/app/api/jobs/jobs.ts @@ -23,3 +23,10 @@ export const getJobStatus = async () => { const response = await fetch(`${API_URL}/job/statuses`) return await response.json() } + +export const cancelJob = async (id: string) => { + const response = await fetch(`${API_URL}/job/${id}/cancel`, { + method: 'POST', + }) + return await response.json() +} diff --git a/web/src/common/ClientLayout/ClientLayout.tsx b/web/src/common/ClientLayout/ClientLayout.tsx index 1eb5d65..58f8afe 100644 --- a/web/src/common/ClientLayout/ClientLayout.tsx +++ b/web/src/common/ClientLayout/ClientLayout.tsx @@ -33,9 +33,16 @@ const LeftNavContainer = dynamic( { ssr: false }, ) +const PatternToastContainer = dynamic( + () => import('@patterninc/react-ui').then((mod) => mod.PatternToastContainer), + { + ssr: false, + }, +) const ClientLayout = ({ children }: { children: ReactNode }) => { return ( +
diff --git a/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx b/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx index d7ceb70..33fe41a 100644 --- a/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx +++ b/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx @@ -1,14 +1,51 @@ 'use client' -import { Alert, PageHeader, SectionHeader } from '@patterninc/react-ui' +import { + Alert, + PageFooter, + PageHeader, + SectionHeader, + toast, +} from '@patterninc/react-ui' import { JobDataTypesProps } from '../Helper' import SyntaxHighlighter from 'react-syntax-highlighter' import { github } from 'react-syntax-highlighter/dist/esm/styles/hljs' import ApiResponseButton from '@/components/ApiResponseButton/ApiResponseButton' +import { cancelJob } from '@/app/api/jobs/jobs' +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { useMemo } from 'react' const JobDetailsHeader = ({ jobData, }: JobDataTypesProps): React.JSX.Element => { + const queryClient = useQueryClient() + const cancelMutation = useMutation({ + mutationFn: (id: string) => cancelJob(id), + onSuccess: (response) => { + if (response.status === 'CANCELLING') { + toast({ + type: 'info', + message: `Job is being cancelled...`, + }) + queryClient.invalidateQueries({ queryKey: ['job', jobData?.id] }) + } else { + toast({ + type: 'error', + message: response.error || 'Failed to cancel job', + }) + } + }, + onError: () => { + toast({ + type: 'error', + message: 'Failed to cancel job', + }) + }, + }) + const isCancelable = useMemo( + () => jobData?.status === 'RUNNING', + [jobData?.status], + ) return (
} bottomSectionChildren={ -
+
{jobData?.status === 'FAILED' ? ( @@ -42,7 +77,9 @@ const JobDetailsHeader = ({
    - {jobData?.tags.map((value) =>
  • {value}
  • )} + {jobData?.tags.map((value) => ( +
  • {value}
  • + ))}
) : null} @@ -50,9 +87,9 @@ const JobDetailsHeader = ({ {jobData?.context?.query ? ( <> - - {jobData.context.query} - + + {jobData.context.query} + ) : null}
@@ -88,6 +125,20 @@ const JobDetailsHeader = ({ value: <>, }} /> + { + if (jobData?.id) cancelMutation.mutate(jobData.id) + }, + disabled: !isCancelable, + children: cancelMutation.isPending ? 'Cancelling...' : 'Cancel Job', + styleType: 'primary-red', + type: 'button', + }, + ]} + />
) } From d142bb802d28844bff2891b9409d8759c6144ac2 Mon Sep 17 00:00:00 2001 From: gauravwarale Date: Wed, 7 Jan 2026 12:43:57 +0530 Subject: [PATCH 2/4] update logic as per suggestions --- web/src/modules/Jobs/Helper.tsx | 1 + .../Jobs/JobDetails/JobDetailsHeader.tsx | 36 +++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/web/src/modules/Jobs/Helper.tsx b/web/src/modules/Jobs/Helper.tsx index c47f205..cf6901f 100644 --- a/web/src/modules/Jobs/Helper.tsx +++ b/web/src/modules/Jobs/Helper.tsx @@ -30,6 +30,7 @@ export type JobType = { created_at: number updated_at: number status: string + is_sync: boolean command_criteria: string[] cluster_criteria: string[] command_id: string diff --git a/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx b/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx index 33fe41a..68d7c35 100644 --- a/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx +++ b/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx @@ -2,6 +2,7 @@ import { Alert, + Ellipsis, PageFooter, PageHeader, SectionHeader, @@ -15,6 +16,8 @@ import { cancelJob } from '@/app/api/jobs/jobs' import { useMutation, useQueryClient } from '@tanstack/react-query' import { useMemo } from 'react' +const CANCELABLE_STATUSES = ['NEW', 'ACCEPTED', 'RUNNING'] + const JobDetailsHeader = ({ jobData, }: JobDataTypesProps): React.JSX.Element => { @@ -42,10 +45,17 @@ const JobDetailsHeader = ({ }) }, }) + + // Only async jobs in active states can be cancelled const isCancelable = useMemo( - () => jobData?.status === 'RUNNING', - [jobData?.status], + () => + CANCELABLE_STATUSES.includes(jobData?.status ?? '') && + jobData?.is_sync !== true, + [jobData?.status, jobData?.is_sync], ) + + const isCancelling = jobData?.status === 'CANCELLING' + return (
{ - if (jobData?.id) cancelMutation.mutate(jobData.id) + as: 'confirmation', + confirmation: { + header: 'Cancel Job', + body: 'Are you sure you want to cancel this job?', + confirmCallout: () => { + if (jobData?.id) cancelMutation.mutate(jobData.id) + }, + type: 'red', }, - disabled: !isCancelable, - children: cancelMutation.isPending ? 'Cancelling...' : 'Cancel Job', + children: isCancelling ? ( + + Cancelling job + + + ) : ( + 'Cancel Job' + ), styleType: 'primary-red', type: 'button', + disabled: !isCancelable || isCancelling, }, ]} /> From 879f6d71d3959cb349b83226838f4fffa01b0d1d Mon Sep 17 00:00:00 2001 From: gauravwarale <131575530+gauravwarale@users.noreply.github.com> Date: Thu, 8 Jan 2026 08:36:30 +0530 Subject: [PATCH 3/4] Update web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx Co-authored-by: Josh Diaz --- web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx b/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx index 68d7c35..3c9af1c 100644 --- a/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx +++ b/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx @@ -28,7 +28,7 @@ const JobDetailsHeader = ({ if (response.status === 'CANCELLING') { toast({ type: 'info', - message: `Job is being cancelled...`, + message: `Job is being canceled...`, }) queryClient.invalidateQueries({ queryKey: ['job', jobData?.id] }) } else { From 577660343f903c2fa92a0e449b721bb757731292 Mon Sep 17 00:00:00 2001 From: gauravwarale <131575530+gauravwarale@users.noreply.github.com> Date: Thu, 8 Jan 2026 08:36:42 +0530 Subject: [PATCH 4/4] Update web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx Co-authored-by: Josh Diaz --- web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx b/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx index 3c9af1c..3128e18 100644 --- a/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx +++ b/web/src/modules/Jobs/JobDetails/JobDetailsHeader.tsx @@ -149,7 +149,7 @@ const JobDetailsHeader = ({ }, children: isCancelling ? ( - Cancelling job + Canceling job ) : (