Skip to content

Commit fe38d8a

Browse files
sarthak688claude
andcommitted
feat(notifications): add delete flows (deleteNotifications, deleteAll) [internal]
Adds the two delete methods on top of the notifications foundation and mark-read flows already on main. Both POST to the NotificationEntry.DeleteBulk OData action; deleteAll uses the server-side deleteAll flag. Both methods are tagged @internal (consistent with the rest of the notification service) so no oauth-scopes entry is added. Tests: 4 unit tests (deleteNotifications + deleteAll, success + error). No integration tests — these destructively mutate the inbox with no SDK-level undo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b6bada2 commit fe38d8a

5 files changed

Lines changed: 149 additions & 0 deletions

File tree

src/models/notification/notifications.models.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import type {
1010
} from '../../utils/pagination/types';
1111

1212
import type {
13+
NotificationDeleteAllResponse,
14+
NotificationDeleteResponse,
1315
NotificationGetAllOptions,
1416
NotificationGetResponse,
1517
NotificationMarkAllReadResponse,
@@ -116,4 +118,35 @@ export interface NotificationServiceModel {
116118
* @internal
117119
*/
118120
markAllAsRead(tenantId: string): Promise<NotificationMarkAllReadResponse>;
121+
122+
/**
123+
* Deletes the given notifications.
124+
*
125+
* @param tenantId - Tenant GUID
126+
* @param notificationIds - GUIDs of notifications to delete. Must be non-empty.
127+
* @returns Operation result echoing the deleted IDs
128+
* {@link NotificationDeleteResponse}
129+
*
130+
* @example
131+
* ```typescript
132+
* await notifications.deleteNotifications('<tenantId>', ['<notificationId-1>', '<notificationId-2>']);
133+
* ```
134+
* @internal
135+
*/
136+
deleteNotifications(tenantId: string, notificationIds: string[]): Promise<NotificationDeleteResponse>;
137+
138+
/**
139+
* Deletes all notifications from the current user's inbox.
140+
*
141+
* @param tenantId - Tenant GUID
142+
* @returns Operation result confirming the bulk delete
143+
* {@link NotificationDeleteAllResponse}
144+
*
145+
* @example
146+
* ```typescript
147+
* await notifications.deleteAll('<tenantId>');
148+
* ```
149+
* @internal
150+
*/
151+
deleteAll(tenantId: string): Promise<NotificationDeleteAllResponse>;
119152
}

src/models/notification/notifications.types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,19 @@ export type NotificationMarkAllReadResponse = OperationResponse<{
105105
all: true;
106106
read: true;
107107
}>;
108+
109+
/**
110+
* Response from `deleteNotifications()`.
111+
*
112+
* `notificationIds` echoes the IDs that were deleted.
113+
*/
114+
export type NotificationDeleteResponse = OperationResponse<{
115+
notificationIds: string[];
116+
}>;
117+
118+
/**
119+
* Response from `deleteAll()`.
120+
*/
121+
export type NotificationDeleteAllResponse = OperationResponse<{
122+
all: true;
123+
}>;

src/services/notification/notifications.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { track } from '../../core/telemetry';
66
import { BaseService } from '../base';
77

88
import type {
9+
NotificationDeleteAllResponse,
10+
NotificationDeleteResponse,
911
NotificationGetAllOptions,
1012
NotificationGetResponse,
1113
NotificationMarkAllReadResponse,
@@ -187,6 +189,53 @@ export class NotificationService extends BaseService implements NotificationServ
187189
return { success: true, data: { all: true, read: true } };
188190
}
189191

192+
/**
193+
* Deletes the given notifications.
194+
*
195+
* @param tenantId - Tenant GUID
196+
* @param notificationIds - GUIDs of notifications to delete. Must be non-empty.
197+
* @returns Operation result echoing the deleted IDs
198+
* {@link NotificationDeleteResponse}
199+
*
200+
* @example
201+
* ```typescript
202+
* await notifications.deleteNotifications('<tenantId>', ['<notificationId-1>', '<notificationId-2>']);
203+
* ```
204+
* @internal
205+
*/
206+
@track('Notifications.DeleteNotifications')
207+
async deleteNotifications(tenantId: string, notificationIds: string[]): Promise<NotificationDeleteResponse> {
208+
await this.post(NOTIFICATION_ENDPOINTS.DELETE_BULK, {
209+
// API spec misspells the key as `notifcationIds` — preserve it.
210+
notifcationIds: notificationIds,
211+
deleteAll: false,
212+
}, { headers: createHeaders({ [TENANT_ID]: tenantId }) });
213+
return { success: true, data: { notificationIds } };
214+
}
215+
216+
/**
217+
* Deletes all notifications from the current user's inbox.
218+
*
219+
* @param tenantId - Tenant GUID
220+
* @returns Operation result confirming the bulk delete
221+
* {@link NotificationDeleteAllResponse}
222+
*
223+
* @example
224+
* ```typescript
225+
* await notifications.deleteAll('<tenantId>');
226+
* ```
227+
* @internal
228+
*/
229+
@track('Notifications.DeleteAll')
230+
async deleteAll(tenantId: string): Promise<NotificationDeleteAllResponse> {
231+
await this.post(NOTIFICATION_ENDPOINTS.DELETE_BULK, {
232+
// API spec misspells the key as `notifcationIds` — preserve it.
233+
notifcationIds: [],
234+
deleteAll: true,
235+
}, { headers: createHeaders({ [TENANT_ID]: tenantId }) });
236+
return { success: true, data: { all: true } };
237+
}
238+
190239
private async updateRead(
191240
tenantId: string,
192241
notificationIds: string[],

src/utils/constants/endpoints/notification.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ const NOTIFICATION_API_BASE = `${NOTIFICATION_BASE}/notificationserviceapi`;
1616
export const NOTIFICATION_ENDPOINTS = {
1717
GET_ALL: `${NOTIFICATION_API_BASE}/odata/v1/NotificationEntry`,
1818
UPDATE_READ: `${NOTIFICATION_API_BASE}/odata/v1/NotificationEntry/UiPath.NotificationService.Api.UpdateRead`,
19+
DELETE_BULK: `${NOTIFICATION_API_BASE}/odata/v1/NotificationEntry/UiPath.NotificationService.Api.DeleteBulk`,
1920
} as const;

tests/unit/services/notification/notifications.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,56 @@ describe('NotificationService Unit Tests', () => {
186186
});
187187
});
188188

189+
describe('deleteNotifications', () => {
190+
it('should POST notifcationIds (preserving the API typo), deleteAll=false, and tenant header', async () => {
191+
mockApiClient.post.mockResolvedValue(undefined);
192+
const ids = [
193+
NOTIFICATION_TEST_CONSTANTS.NOTIFICATION_ID,
194+
NOTIFICATION_TEST_CONSTANTS.NOTIFICATION_ID_2,
195+
];
196+
197+
const result = await notificationService.deleteNotifications(NOTIFICATION_TEST_CONSTANTS.TENANT_ID, ids);
198+
199+
expect(mockApiClient.post).toHaveBeenCalledWith(
200+
NOTIFICATION_ENDPOINTS.DELETE_BULK,
201+
{ notifcationIds: ids, deleteAll: false },
202+
{ headers: TENANT_HEADER }
203+
);
204+
expect(result).toEqual({ success: true, data: { notificationIds: ids } });
205+
});
206+
207+
it('should propagate errors', async () => {
208+
mockApiClient.post.mockRejectedValue(createMockError(NOTIFICATION_TEST_CONSTANTS.ERROR_NOTIFICATION_NOT_FOUND));
209+
210+
await expect(
211+
notificationService.deleteNotifications(NOTIFICATION_TEST_CONSTANTS.TENANT_ID, [NOTIFICATION_TEST_CONSTANTS.NOTIFICATION_ID])
212+
).rejects.toThrow(NOTIFICATION_TEST_CONSTANTS.ERROR_NOTIFICATION_NOT_FOUND);
213+
});
214+
});
215+
216+
describe('deleteAll', () => {
217+
it('should POST deleteAll=true with empty notifcationIds array and tenant header', async () => {
218+
mockApiClient.post.mockResolvedValue(undefined);
219+
220+
const result = await notificationService.deleteAll(NOTIFICATION_TEST_CONSTANTS.TENANT_ID);
221+
222+
expect(mockApiClient.post).toHaveBeenCalledWith(
223+
NOTIFICATION_ENDPOINTS.DELETE_BULK,
224+
{ notifcationIds: [], deleteAll: true },
225+
{ headers: TENANT_HEADER }
226+
);
227+
expect(result).toEqual({ success: true, data: { all: true } });
228+
});
229+
230+
it('should propagate errors', async () => {
231+
mockApiClient.post.mockRejectedValue(createMockError(TEST_CONSTANTS.ERROR_MESSAGE));
232+
233+
await expect(
234+
notificationService.deleteAll(NOTIFICATION_TEST_CONSTANTS.TENANT_ID)
235+
).rejects.toThrow(TEST_CONSTANTS.ERROR_MESSAGE);
236+
});
237+
});
238+
189239
describe('response transformation', () => {
190240
it('strips internal fields and renames isRead → hasRead in the returned notifications', async () => {
191241
const raw: RawNotificationEntry = createBasicNotificationEntry();

0 commit comments

Comments
 (0)