From 85f38c29d19c1f0673853fce41ba0158f947dcc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 11 Mar 2026 16:47:24 +0100 Subject: [PATCH 1/5] devtools: Add cancel task operation to scheduled tasks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renames useTriggerScheduledTask to useScheduledTasksOperations and adds cancelTask alongside triggerTask, with shared isLoading/error state. Adds CancelScheduledTask type to devtools-common and cancelScheduledTask to the DevToolsApi interface and client, hitting the existing scheduler cancel endpoint. The ScheduledTasksContent UI now shows both trigger and cancel buttons per task row. Signed-off-by: Fredrik Adelöw Made-with: Cursor --- plugins/devtools-common/src/alpha.ts | 1 + plugins/devtools-common/src/types.ts | 5 ++ plugins/devtools/src/api/DevToolsApi.ts | 5 ++ plugins/devtools/src/api/DevToolsClient.ts | 22 ++++++ .../ScheduledTasksContent.tsx | 73 +++++++++++++------ plugins/devtools/src/hooks/index.ts | 2 +- ...Task.ts => useScheduledTasksOperations.ts} | 31 ++++++-- 7 files changed, 108 insertions(+), 31 deletions(-) rename plugins/devtools/src/hooks/{useTriggerScheduledTask.ts => useScheduledTasksOperations.ts} (66%) diff --git a/plugins/devtools-common/src/alpha.ts b/plugins/devtools-common/src/alpha.ts index 984967c4f3..d7f9f94d76 100644 --- a/plugins/devtools-common/src/alpha.ts +++ b/plugins/devtools-common/src/alpha.ts @@ -18,6 +18,7 @@ export { devToolsTaskSchedulerCreatePermission, } from './permissions'; export type { + CancelScheduledTask, ScheduledTasks, TaskApiTasksResponse, TriggerScheduledTask, diff --git a/plugins/devtools-common/src/types.ts b/plugins/devtools-common/src/types.ts index dc35188fde..3879fb1ae3 100644 --- a/plugins/devtools-common/src/types.ts +++ b/plugins/devtools-common/src/types.ts @@ -133,3 +133,8 @@ export type ScheduledTasks = { export type TriggerScheduledTask = { error?: string; }; + +/** @alpha */ +export type CancelScheduledTask = { + error?: string; +}; diff --git a/plugins/devtools/src/api/DevToolsApi.ts b/plugins/devtools/src/api/DevToolsApi.ts index 4fc579ee06..6894a0a8b2 100644 --- a/plugins/devtools/src/api/DevToolsApi.ts +++ b/plugins/devtools/src/api/DevToolsApi.ts @@ -21,6 +21,7 @@ import { ExternalDependency, } from '@backstage/plugin-devtools-common'; import { + CancelScheduledTask, ScheduledTasks, TriggerScheduledTask, } from '@backstage/plugin-devtools-common/alpha'; @@ -38,4 +39,8 @@ export interface DevToolsApi { plugin: string, taskId: string, ): Promise; + cancelScheduledTask( + plugin: string, + taskId: string, + ): Promise; } diff --git a/plugins/devtools/src/api/DevToolsClient.ts b/plugins/devtools/src/api/DevToolsClient.ts index 0577757838..c5b50193ea 100644 --- a/plugins/devtools/src/api/DevToolsClient.ts +++ b/plugins/devtools/src/api/DevToolsClient.ts @@ -21,6 +21,7 @@ import { ExternalDependency, } from '@backstage/plugin-devtools-common'; import { + CancelScheduledTask, ScheduledTasks, TriggerScheduledTask, } from '@backstage/plugin-devtools-common/alpha'; @@ -85,6 +86,27 @@ export class DevToolsClient implements DevToolsApi { return response.json() as Promise; } + public async cancelScheduledTask( + plugin: string, + taskId: string, + ): Promise { + const baseUrl = `${await this.discoveryApi.getBaseUrl(plugin)}/`; + const url = new URL( + `.backstage/scheduler/v1/tasks/${encodeURIComponent(taskId)}/cancel`, + baseUrl, + ); + + const response = await this.fetchApi.fetch(url.toString(), { + method: 'POST', + }); + + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + return response.json() as Promise; + } + public async getExternalDependencies(): Promise< ExternalDependency[] | undefined > { diff --git a/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx b/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx index 7517cf8ea5..d115968a77 100644 --- a/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx +++ b/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx @@ -29,10 +29,11 @@ import { TableColumn, } from '@backstage/core-components'; import Alert from '@material-ui/lab/Alert'; -import { useScheduledTasks, useTriggerScheduledTask } from '../../../hooks'; +import { useScheduledTasks, useScheduledTasksOperations } from '../../../hooks'; import { TaskApiTasksResponse } from '@backstage/plugin-devtools-common/alpha'; import { alertApiRef, configApiRef, useApi } from '@backstage/core-plugin-api'; import RefreshIcon from '@material-ui/icons/Refresh'; +import StopIcon from '@material-ui/icons/Stop'; import NightsStay from '@material-ui/icons/NightsStay'; import ErrorIcon from '@material-ui/icons/Error'; import BlockIcon from '@material-ui/icons/Block'; @@ -105,7 +106,7 @@ export const ScheduledTasksContent = () => { configApi.getOptionalStringArray('devTools.scheduledTasks.plugins') || []; const [selectedPlugin, setSelectedPlugin] = useState(plugins[0] || ''); const { scheduledTasks, loading, error } = useScheduledTasks(selectedPlugin); - const { triggerTask, isTriggering, triggerError } = useTriggerScheduledTask(); + const { triggerTask, cancelTask, isLoading } = useScheduledTasksOperations(); const [inputValue, setInputValue] = useState(''); @@ -209,28 +210,52 @@ export const ScheduledTasksContent = () => { permission={devToolsTaskSchedulerCreatePermission} errorPage={} > - - { - triggerTask(selectedPlugin, rowData.taskId); - if (triggerError) { - alertApi.post({ - message: `Error triggering task ${rowData.taskId}: ${error}`, - severity: 'error', - }); - } else { - alertApi.post({ - message: `Successfully triggered task ${rowData.taskId}`, - severity: 'success', - }); - } - }} - > - - - + + + { + try { + await triggerTask(selectedPlugin, rowData.taskId); + alertApi.post({ + message: `Successfully triggered task ${rowData.taskId}`, + severity: 'success', + }); + } catch (e) { + alertApi.post({ + message: `Error triggering task ${rowData.taskId}: ${e.message}`, + severity: 'error', + }); + } + }} + > + + + + + { + try { + await cancelTask(selectedPlugin, rowData.taskId); + alertApi.post({ + message: `Successfully cancelled task ${rowData.taskId}`, + severity: 'success', + }); + } catch (e) { + alertApi.post({ + message: `Error cancelling task ${rowData.taskId}: ${e.message}`, + severity: 'error', + }); + } + }} + > + + + + ), sorting: false, diff --git a/plugins/devtools/src/hooks/index.ts b/plugins/devtools/src/hooks/index.ts index 105d03fb68..9d212bc3af 100644 --- a/plugins/devtools/src/hooks/index.ts +++ b/plugins/devtools/src/hooks/index.ts @@ -18,4 +18,4 @@ export { useConfig } from './useConfig'; export { useExternalDependencies } from './useExternalDependencies'; export { useInfo } from './useInfo'; export { useScheduledTasks } from './useScheduledTasks'; -export { useTriggerScheduledTask } from './useTriggerScheduledTask'; +export { useScheduledTasksOperations } from './useScheduledTasksOperations'; diff --git a/plugins/devtools/src/hooks/useTriggerScheduledTask.ts b/plugins/devtools/src/hooks/useScheduledTasksOperations.ts similarity index 66% rename from plugins/devtools/src/hooks/useTriggerScheduledTask.ts rename to plugins/devtools/src/hooks/useScheduledTasksOperations.ts index d26b7a8025..ff9d02b675 100644 --- a/plugins/devtools/src/hooks/useTriggerScheduledTask.ts +++ b/plugins/devtools/src/hooks/useScheduledTasksOperations.ts @@ -17,22 +17,40 @@ import { useState, useCallback } from 'react'; import { devToolsApiRef } from '../api'; import { useApi } from '@backstage/core-plugin-api'; -export const useTriggerScheduledTask = () => { +export const useScheduledTasksOperations = () => { const api = useApi(devToolsApiRef); - const [isTriggering, setIsTriggering] = useState(false); + const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(); const triggerTask = useCallback( async (plugin: string, taskId: string) => { - setIsTriggering(true); + setIsLoading(true); setError(undefined); try { await api.triggerScheduledTask(plugin, taskId); } catch (e) { setError(e); + throw e; } finally { - setIsTriggering(false); + setIsLoading(false); + } + }, + [api], + ); + + const cancelTask = useCallback( + async (plugin: string, taskId: string) => { + setIsLoading(true); + setError(undefined); + + try { + await api.cancelScheduledTask(plugin, taskId); + } catch (e) { + setError(e); + throw e; + } finally { + setIsLoading(false); } }, [api], @@ -40,7 +58,8 @@ export const useTriggerScheduledTask = () => { return { triggerTask, - isTriggering, - triggerError: error?.message, + cancelTask, + isLoading, + error: error?.message, }; }; From f80195ec3f18b137f2cf1e9a3dd0b531eab3f68f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 11 Mar 2026 16:51:25 +0100 Subject: [PATCH 2/5] Add changeset for devtools cancel task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw Made-with: Cursor Signed-off-by: Fredrik Adelöw Made-with: Cursor --- .changeset/devtools-cancel-scheduled-task.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/devtools-cancel-scheduled-task.md diff --git a/.changeset/devtools-cancel-scheduled-task.md b/.changeset/devtools-cancel-scheduled-task.md new file mode 100644 index 0000000000..ac057d61b6 --- /dev/null +++ b/.changeset/devtools-cancel-scheduled-task.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-devtools-common': patch +'@backstage/plugin-devtools': patch +--- + +Added `cancelScheduledTask` to the DevTools API and a cancel button to the scheduled tasks UI. The `useTriggerScheduledTask` hook has been renamed to `useScheduledTasksOperations`, now exposing both `triggerTask` and `cancelTask` with shared loading and error state. From 9110c16c8a681351dd42201dc17cd49da8f3becc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 11 Mar 2026 19:41:10 +0100 Subject: [PATCH 3/5] api report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- plugins/devtools-common/report-alpha.api.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/devtools-common/report-alpha.api.md b/plugins/devtools-common/report-alpha.api.md index 3c3b7ff89e..e11b2d62ca 100644 --- a/plugins/devtools-common/report-alpha.api.md +++ b/plugins/devtools-common/report-alpha.api.md @@ -6,6 +6,11 @@ import { BasicPermission } from '@backstage/plugin-permission-common'; import { JsonObject } from '@backstage/types'; +// @alpha (undocumented) +export type CancelScheduledTask = { + error?: string; +}; + // @alpha (undocumented) export const devToolsTaskSchedulerCreatePermission: BasicPermission; From 239c8dcfd2af169f6e3caecf1608a018618e2132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 11 Mar 2026 20:29:45 +0100 Subject: [PATCH 4/5] cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- plugins/devtools-common/report-alpha.api.md | 5 ----- plugins/devtools-common/src/alpha.ts | 2 +- plugins/devtools-common/src/types.ts | 5 ----- plugins/devtools/src/api/DevToolsApi.ts | 6 +----- plugins/devtools/src/api/DevToolsClient.ts | 14 ++++++++------ .../ScheduledTasksContent.tsx | 5 +++-- 6 files changed, 13 insertions(+), 24 deletions(-) diff --git a/plugins/devtools-common/report-alpha.api.md b/plugins/devtools-common/report-alpha.api.md index e11b2d62ca..3c3b7ff89e 100644 --- a/plugins/devtools-common/report-alpha.api.md +++ b/plugins/devtools-common/report-alpha.api.md @@ -6,11 +6,6 @@ import { BasicPermission } from '@backstage/plugin-permission-common'; import { JsonObject } from '@backstage/types'; -// @alpha (undocumented) -export type CancelScheduledTask = { - error?: string; -}; - // @alpha (undocumented) export const devToolsTaskSchedulerCreatePermission: BasicPermission; diff --git a/plugins/devtools-common/src/alpha.ts b/plugins/devtools-common/src/alpha.ts index d7f9f94d76..7abe4b1f5e 100644 --- a/plugins/devtools-common/src/alpha.ts +++ b/plugins/devtools-common/src/alpha.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { devToolsTaskSchedulerReadPermission, devToolsTaskSchedulerCreatePermission, } from './permissions'; export type { - CancelScheduledTask, ScheduledTasks, TaskApiTasksResponse, TriggerScheduledTask, diff --git a/plugins/devtools-common/src/types.ts b/plugins/devtools-common/src/types.ts index 3879fb1ae3..dc35188fde 100644 --- a/plugins/devtools-common/src/types.ts +++ b/plugins/devtools-common/src/types.ts @@ -133,8 +133,3 @@ export type ScheduledTasks = { export type TriggerScheduledTask = { error?: string; }; - -/** @alpha */ -export type CancelScheduledTask = { - error?: string; -}; diff --git a/plugins/devtools/src/api/DevToolsApi.ts b/plugins/devtools/src/api/DevToolsApi.ts index 6894a0a8b2..2aaad49aa8 100644 --- a/plugins/devtools/src/api/DevToolsApi.ts +++ b/plugins/devtools/src/api/DevToolsApi.ts @@ -21,7 +21,6 @@ import { ExternalDependency, } from '@backstage/plugin-devtools-common'; import { - CancelScheduledTask, ScheduledTasks, TriggerScheduledTask, } from '@backstage/plugin-devtools-common/alpha'; @@ -39,8 +38,5 @@ export interface DevToolsApi { plugin: string, taskId: string, ): Promise; - cancelScheduledTask( - plugin: string, - taskId: string, - ): Promise; + cancelScheduledTask(plugin: string, taskId: string): Promise; } diff --git a/plugins/devtools/src/api/DevToolsClient.ts b/plugins/devtools/src/api/DevToolsClient.ts index c5b50193ea..b6d9d4b119 100644 --- a/plugins/devtools/src/api/DevToolsClient.ts +++ b/plugins/devtools/src/api/DevToolsClient.ts @@ -21,11 +21,10 @@ import { ExternalDependency, } from '@backstage/plugin-devtools-common'; import { - CancelScheduledTask, ScheduledTasks, TriggerScheduledTask, } from '@backstage/plugin-devtools-common/alpha'; -import { ResponseError } from '@backstage/errors'; +import { ResponseError, NotFoundError, ConflictError } from '@backstage/errors'; import { DevToolsApi } from './DevToolsApi'; export class DevToolsClient implements DevToolsApi { @@ -83,13 +82,13 @@ export class DevToolsClient implements DevToolsApi { throw await ResponseError.fromResponse(response); } - return response.json() as Promise; + return {}; } public async cancelScheduledTask( plugin: string, taskId: string, - ): Promise { + ): Promise { const baseUrl = `${await this.discoveryApi.getBaseUrl(plugin)}/`; const url = new URL( `.backstage/scheduler/v1/tasks/${encodeURIComponent(taskId)}/cancel`, @@ -101,10 +100,13 @@ export class DevToolsClient implements DevToolsApi { }); if (!response.ok) { + if (response.status === 404) { + throw new NotFoundError(`Task ${taskId} not found`); + } else if (response.status === 409) { + throw new ConflictError(`Task ${taskId} is not running`); + } throw await ResponseError.fromResponse(response); } - - return response.json() as Promise; } public async getExternalDependencies(): Promise< diff --git a/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx b/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx index d115968a77..af86b9b237 100644 --- a/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx +++ b/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx @@ -288,7 +288,7 @@ export const ScheduledTasksContent = () => { )} /> - {loading && } + {loading && !scheduledTasks && } {error && ( { )} - {!loading && !error && ( + {scheduledTasks && ( { search: true, sorting: true, searchFieldAlignment: 'right', + padding: 'dense', }} columns={columns} data={scheduledTasks || []} From 325feafebf6e1fa30c7d16fd20f8c8c19f845f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 12 Mar 2026 09:24:38 +0100 Subject: [PATCH 5/5] Update .changeset/devtools-cancel-scheduled-task.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/devtools-cancel-scheduled-task.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/devtools-cancel-scheduled-task.md b/.changeset/devtools-cancel-scheduled-task.md index ac057d61b6..fadd51f2aa 100644 --- a/.changeset/devtools-cancel-scheduled-task.md +++ b/.changeset/devtools-cancel-scheduled-task.md @@ -3,4 +3,4 @@ '@backstage/plugin-devtools': patch --- -Added `cancelScheduledTask` to the DevTools API and a cancel button to the scheduled tasks UI. The `useTriggerScheduledTask` hook has been renamed to `useScheduledTasksOperations`, now exposing both `triggerTask` and `cancelTask` with shared loading and error state. +Added `cancelScheduledTask` to the DevTools API and a cancel button to the scheduled tasks UI.