From afabb37104c358ad8799a83c3f2d960b58a1012e Mon Sep 17 00:00:00 2001 From: thomvaill Date: Thu, 26 Feb 2026 16:50:57 +0100 Subject: [PATCH] fix(devtools): urlencode task IDs when calling trigger route Signed-off-by: thomvaill --- .changeset/cold-dodos-think.md | 5 ++ .../devtools/src/api/DevToolsClient.test.ts | 49 +++++++++++++++++++ plugins/devtools/src/api/DevToolsClient.ts | 2 +- 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 .changeset/cold-dodos-think.md create mode 100644 plugins/devtools/src/api/DevToolsClient.test.ts diff --git a/.changeset/cold-dodos-think.md b/.changeset/cold-dodos-think.md new file mode 100644 index 0000000000..021e5f679e --- /dev/null +++ b/.changeset/cold-dodos-think.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-devtools': patch +--- + +Fixed URL encoding of task IDs for the trigger feature (tasks that contained a "/" in their ID were not triggered) diff --git a/plugins/devtools/src/api/DevToolsClient.test.ts b/plugins/devtools/src/api/DevToolsClient.test.ts new file mode 100644 index 0000000000..d1972cd528 --- /dev/null +++ b/plugins/devtools/src/api/DevToolsClient.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DevToolsClient } from './DevToolsClient'; + +describe('DevToolsClient', () => { + const mockBaseUrl = 'http://backstage/api/catalog'; + const discoveryApi = { + getBaseUrl: async (pluginId: string) => `${mockBaseUrl}/${pluginId}`, + }; + const mockFetch = jest.fn(); + const fetchApi = { fetch: mockFetch }; + + let client: DevToolsClient; + beforeEach(() => { + mockFetch.mockResolvedValue({ + ok: true, + json: async () => ({ status: 'triggered' }), + }); + client = new DevToolsClient({ discoveryApi, fetchApi }); + }); + + afterEach(() => jest.resetAllMocks()); + + it('should URL-encode the taskId when triggering a scheduled task', async () => { + await client.triggerScheduledTask( + 'my-plugin', + 'task/with/slashes:and-special-chars', + ); + + expect(mockFetch).toHaveBeenCalledWith( + `${mockBaseUrl}/my-plugin/.backstage/scheduler/v1/tasks/task%2Fwith%2Fslashes%3Aand-special-chars/trigger`, + { method: 'POST' }, + ); + }); +}); diff --git a/plugins/devtools/src/api/DevToolsClient.ts b/plugins/devtools/src/api/DevToolsClient.ts index cd4f53fa81..0577757838 100644 --- a/plugins/devtools/src/api/DevToolsClient.ts +++ b/plugins/devtools/src/api/DevToolsClient.ts @@ -70,7 +70,7 @@ export class DevToolsClient implements DevToolsApi { ): Promise { const baseUrl = `${await this.discoveryApi.getBaseUrl(plugin)}/`; const url = new URL( - `.backstage/scheduler/v1/tasks/${taskId}/trigger`, + `.backstage/scheduler/v1/tasks/${encodeURIComponent(taskId)}/trigger`, baseUrl, );