From b2915f4de1230dbece571a84047d257d095fc8d8 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 2 Mar 2026 21:30:49 -0800 Subject: [PATCH] refactor: Simplify click URL resolution logic --- .../server/src/services/event-hook-service.ts | 41 +++++++++---------- .../unit/services/event-hook-service.test.ts | 11 +++-- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/apps/server/src/services/event-hook-service.ts b/apps/server/src/services/event-hook-service.ts index 64973565..005e7e54 100644 --- a/apps/server/src/services/event-hook-service.ts +++ b/apps/server/src/services/event-hook-service.ts @@ -588,29 +588,26 @@ export class EventHookService { eventType: context.eventType, }; - // Build click URL with deep-link if project context is available - let clickUrl = action.clickUrl; - if (!clickUrl && endpoint.defaultClickUrl) { - clickUrl = endpoint.defaultClickUrl; - // If we have a project path and the click URL looks like the server URL, - // append deep-link path - if (context.projectPath && clickUrl) { - try { - const url = new URL(clickUrl); - // Add featureId as query param for deep linking to board with feature output modal - if (context.featureId) { - url.pathname = '/board'; - url.searchParams.set('featureId', context.featureId); - } else if (context.projectPath) { - url.pathname = '/board'; - } - clickUrl = url.toString(); - } catch (error) { - // If URL parsing fails, log warning and use as-is - logger.warn( - `Failed to parse defaultClickUrl "${clickUrl}" for deep linking: ${error instanceof Error ? error.message : String(error)}` - ); + // Resolve click URL: action-level overrides endpoint default + let clickUrl = action.clickUrl || endpoint.defaultClickUrl; + + // Apply deep-link parameters to the resolved click URL + if (clickUrl && context.projectPath) { + try { + const url = new URL(clickUrl); + // Add featureId as query param for deep linking to board with feature output modal + if (context.featureId) { + url.pathname = '/board'; + url.searchParams.set('featureId', context.featureId); + } else { + url.pathname = '/board'; } + clickUrl = url.toString(); + } catch (error) { + // If URL parsing fails, log warning and use as-is + logger.warn( + `Failed to parse click URL "${clickUrl}" for deep linking: ${error instanceof Error ? error.message : String(error)}` + ); } } diff --git a/apps/server/tests/unit/services/event-hook-service.test.ts b/apps/server/tests/unit/services/event-hook-service.test.ts index 900bb3b3..fba67664 100644 --- a/apps/server/tests/unit/services/event-hook-service.test.ts +++ b/apps/server/tests/unit/services/event-hook-service.test.ts @@ -1246,7 +1246,9 @@ describe('EventHookService', () => { const options = mockFetch.mock.calls[0][1]; // Hook values should override endpoint defaults expect(options.headers['Tags']).toBe('override-emoji,override-tag'); - expect(options.headers['Click']).toBe('https://override.example.com'); + // Click URL uses hook-specific base URL with deep link params applied + expect(options.headers['Click']).toContain('https://override.example.com/board'); + expect(options.headers['Click']).toContain('featureId=feat-1'); expect(options.headers['Priority']).toBe('5'); }); @@ -1359,7 +1361,7 @@ describe('EventHookService', () => { expect(clickUrl).not.toContain('featureId='); }); - it('should use hook-specific click URL overriding default with featureId', async () => { + it('should apply deep link params to hook-specific click URL', async () => { mockFetch.mockResolvedValueOnce({ ok: true, status: 200, @@ -1409,8 +1411,9 @@ describe('EventHookService', () => { const options = mockFetch.mock.calls[0][1]; const clickUrl = options.headers['Click']; - // Should use the hook-specific click URL (not modified with featureId since it's a custom URL) - expect(clickUrl).toBe('https://custom.example.com/custom-page'); + // Should use the hook-specific click URL with deep link params applied + expect(clickUrl).toContain('https://custom.example.com/board'); + expect(clickUrl).toContain('featureId=feat-789'); }); it('should preserve existing query params when adding featureId', async () => {