From 8f63a1befc46ab2ef1a9353b16d7d5689a33cddf Mon Sep 17 00:00:00 2001 From: Prios Shrestha <30313649+priosshrsth@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:33:34 +0545 Subject: [PATCH 1/2] OUT-4027 | Fix 500 when deleting a task whose label row is missing (#1397) deleteLabel passed `id: currentLabel?.id` straight into label.delete, so when findFirst matched nothing Prisma got `{ id: undefined }` and threw PrismaClientValidationError, failing the whole delete transaction. Return early instead. --- .../label-mapping.service.test.ts | 44 +++++++++++++++++++ .../label-mapping/label-mapping.service.ts | 3 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/app/api/label-mapping/label-mapping.service.test.ts diff --git a/src/app/api/label-mapping/label-mapping.service.test.ts b/src/app/api/label-mapping/label-mapping.service.test.ts new file mode 100644 index 000000000..24a195ede --- /dev/null +++ b/src/app/api/label-mapping/label-mapping.service.test.ts @@ -0,0 +1,44 @@ +const mockLabelFindFirst = jest.fn() +const mockLabelDelete = jest.fn() + +jest.mock('@/lib/db', () => ({ + __esModule: true, + default: { + getInstance: () => ({ + label: { findFirst: mockLabelFindFirst, delete: mockLabelDelete }, + }), + }, +})) + +jest.mock('@/utils/CopilotAPI', () => ({ CopilotAPI: jest.fn() })) + +import { LabelMappingService } from '@api/label-mapping/label-mapping.service' +import User from '@api/core/models/User.model' +import { UserRole } from '@api/core/types/user' + +const user = { + workspaceId: 'ws-1', + role: UserRole.IU, + internalUserId: 'iu-1', + token: 'token', +} as unknown as User + +describe('LabelMappingService#deleteLabel', () => { + beforeEach(() => jest.clearAllMocks()) + + it('deletes the matching label row', async () => { + mockLabelFindFirst.mockResolvedValue({ id: 'label-1' }) + + await new LabelMappingService(user).deleteLabel('ASS10-009') + + expect(mockLabelDelete).toHaveBeenCalledWith({ where: { id: 'label-1' } }) + }) + + it('no-ops when the label row is already gone', async () => { + mockLabelFindFirst.mockResolvedValue(null) + + await new LabelMappingService(user).deleteLabel('ASS10-009') + + expect(mockLabelDelete).not.toHaveBeenCalled() + }) +}) diff --git a/src/app/api/label-mapping/label-mapping.service.ts b/src/app/api/label-mapping/label-mapping.service.ts index 42007a03c..b0d9c2abd 100644 --- a/src/app/api/label-mapping/label-mapping.service.ts +++ b/src/app/api/label-mapping/label-mapping.service.ts @@ -175,9 +175,10 @@ export class LabelMappingService extends BaseService { label, }, }) + if (!currentLabel) return await this.db.label.delete({ where: { - id: currentLabel?.id, + id: currentLabel.id, }, }) } From d2d0d0d9f97fe75c2be8b1594d81c3e8950bd8a7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 22 Aug 2026 04:44:43 +0000 Subject: [PATCH 2/2] fix(POR-22668): handle platform files/folders errors in notification-center Wrap notification fetch in try/catch so platform API errors like 'no files or folders provided' show a user-friendly SilentError instead of an unhandled request error in Sentry. Co-authored-by: Neil Raina --- src/app/notification-center/page.tsx | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/app/notification-center/page.tsx b/src/app/notification-center/page.tsx index e39b77851..57cdada72 100644 --- a/src/app/notification-center/page.tsx +++ b/src/app/notification-center/page.tsx @@ -9,9 +9,9 @@ async function getNotificationDetail(token: string) { const copilot = new CopilotAPI(token) const tokenPayload = await copilot.getTokenPayload() - if (!tokenPayload) throw new Error('Failed to get token payload') + if (!tokenPayload?.notificationId) return null - return await copilot.getIUNotification(z.string().parse(tokenPayload.notificationId), tokenPayload.workspaceId) // notification "id" is expected in tokenPayload + return await copilot.getIUNotification(tokenPayload.notificationId, tokenPayload.workspaceId) } export default async function NotificationCenter(props: { searchParams: Promise<{ token: string }> }) { @@ -21,13 +21,24 @@ export default async function NotificationCenter(props: { searchParams: Promise< return } - const notificationDetail = await getNotificationDetail(token) + let notificationDetail + try { + notificationDetail = await getNotificationDetail(token) + } catch (error) { + console.warn('notification-center: failed to load notification', error) + return + } + if (!notificationDetail) return - const params = NotificationInProductCtaParamsSchema.parse(notificationDetail.deliveryTargets?.inProduct?.ctaParams) + const params = NotificationInProductCtaParamsSchema.safeParse( + notificationDetail.deliveryTargets?.inProduct?.ctaParams, + ) + if (!params.success) { + return + } - redirectIfTaskCta({ ...params, ...searchParams }, UserType.INTERNAL_USER, true) + redirectIfTaskCta({ ...params.data, ...searchParams }, UserType.INTERNAL_USER, true) - // Silent Error is shown if redirect fails. Only possible reason for redirect to not work can be of the taskId not found return }