Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/models/notification/notifications.models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import type {
} from '../../utils/pagination/types';

import type {
NotificationDeleteAllResponse,
NotificationDeleteResponse,
NotificationGetAllOptions,
NotificationGetResponse,
NotificationMarkAllReadResponse,
Expand Down Expand Up @@ -116,4 +118,35 @@ export interface NotificationServiceModel {
* @internal
*/
markAllAsRead(tenantId: string): Promise<NotificationMarkAllReadResponse>;

/**
* Deletes the given notifications.
*
* @param tenantId - Tenant GUID
* @param notificationIds - GUIDs of notifications to delete. Must be non-empty.
* @returns Operation result echoing the deleted IDs
* {@link NotificationDeleteResponse}
*
* @example
* ```typescript
* await notifications.deleteByIds('<tenantId>', ['<notificationId-1>', '<notificationId-2>']);
* ```
* @internal
*/
deleteByIds(tenantId: string, notificationIds: string[]): Promise<NotificationDeleteResponse>;

/**
* Deletes all notifications from the current user's inbox.
*
* @param tenantId - Tenant GUID
* @returns Operation result confirming the bulk delete
* {@link NotificationDeleteAllResponse}
*
* @example
* ```typescript
* await notifications.deleteAll('<tenantId>');
* ```
* @internal
*/
deleteAll(tenantId: string): Promise<NotificationDeleteAllResponse>;
}
16 changes: 16 additions & 0 deletions src/models/notification/notifications.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,19 @@ export type NotificationMarkAllReadResponse = OperationResponse<{
all: true;
read: true;
}>;

/**
* Response from `deleteByIds()`.
*
* `notificationIds` echoes the IDs that were deleted.
*/
export type NotificationDeleteResponse = OperationResponse<{
notificationIds: string[];
}>;

/**
* Response from `deleteAll()`.
*/
export type NotificationDeleteAllResponse = OperationResponse<{
all: true;
}>;
49 changes: 49 additions & 0 deletions src/services/notification/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { track } from '../../core/telemetry';
import { BaseService } from '../base';

import type {
NotificationDeleteAllResponse,
NotificationDeleteResponse,
NotificationGetAllOptions,
NotificationGetResponse,
NotificationMarkAllReadResponse,
Expand Down Expand Up @@ -187,6 +189,53 @@ export class NotificationService extends BaseService implements NotificationServ
return { success: true, data: { all: true, read: true } };
}

/**
* Deletes the given notifications.
*
* @param tenantId - Tenant GUID
* @param notificationIds - GUIDs of notifications to delete. Must be non-empty.
* @returns Operation result echoing the deleted IDs
* {@link NotificationDeleteResponse}
*
* @example
* ```typescript
* await notifications.deleteByIds('<tenantId>', ['<notificationId-1>', '<notificationId-2>']);
* ```
* @internal
*/
@track('Notifications.DeleteByIds')
async deleteByIds(tenantId: string, notificationIds: string[]): Promise<NotificationDeleteResponse> {
await this.post(NOTIFICATION_ENDPOINTS.DELETE_BULK, {
// API spec misspells the key as `notifcationIds` — preserve it.
notifcationIds: notificationIds,
deleteAll: false,
}, { headers: createHeaders({ [TENANT_ID]: tenantId }) });
return { success: true, data: { notificationIds } };
}

/**
* Deletes all notifications from the current user's inbox.
*
* @param tenantId - Tenant GUID
* @returns Operation result confirming the bulk delete
* {@link NotificationDeleteAllResponse}
*
* @example
* ```typescript
* await notifications.deleteAll('<tenantId>');
* ```
* @internal
*/
@track('Notifications.DeleteAll')
async deleteAll(tenantId: string): Promise<NotificationDeleteAllResponse> {
await this.post(NOTIFICATION_ENDPOINTS.DELETE_BULK, {
// API spec misspells the key as `notifcationIds` — preserve it.
notifcationIds: [],
deleteAll: true,
}, { headers: createHeaders({ [TENANT_ID]: tenantId }) });
return { success: true, data: { all: true } };
}

private async updateRead(
tenantId: string,
notificationIds: string[],
Expand Down
1 change: 1 addition & 0 deletions src/utils/constants/endpoints/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ const NOTIFICATION_API_BASE = `${NOTIFICATION_BASE}/notificationserviceapi`;
export const NOTIFICATION_ENDPOINTS = {
GET_ALL: `${NOTIFICATION_API_BASE}/odata/v1/NotificationEntry`,
UPDATE_READ: `${NOTIFICATION_API_BASE}/odata/v1/NotificationEntry/UiPath.NotificationService.Api.UpdateRead`,
DELETE_BULK: `${NOTIFICATION_API_BASE}/odata/v1/NotificationEntry/UiPath.NotificationService.Api.DeleteBulk`,
} as const;
50 changes: 50 additions & 0 deletions tests/unit/services/notification/notifications.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

const result = await notificationService.getAll(NOTIFICATION_TEST_CONSTANTS.TENANT_ID);

expect(result.items.length).toBe(1);

Check warning on line 54 in tests/unit/services/notification/notifications.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer a more specific assertion instead of this generic one, e.g. "expect(result.items).toHaveLength(1)".

See more on https://sonarcloud.io/project/issues?id=UiPath_uipath-typescript&issues=AZ8h2bT44X8EE6L6x40a&open=AZ8h2bT44X8EE6L6x40a&pullRequest=514
expect(result.totalCount).toBe(1);

expect(PaginationHelpers.getAll).toHaveBeenCalledWith(
Expand Down Expand Up @@ -186,6 +186,56 @@
});
});

describe('deleteByIds', () => {
it('should POST notifcationIds (preserving the API typo), deleteAll=false, and tenant header', async () => {
mockApiClient.post.mockResolvedValue(undefined);
const ids = [
NOTIFICATION_TEST_CONSTANTS.NOTIFICATION_ID,
NOTIFICATION_TEST_CONSTANTS.NOTIFICATION_ID_2,
];

const result = await notificationService.deleteByIds(NOTIFICATION_TEST_CONSTANTS.TENANT_ID, ids);

expect(mockApiClient.post).toHaveBeenCalledWith(
NOTIFICATION_ENDPOINTS.DELETE_BULK,
{ notifcationIds: ids, deleteAll: false },
{ headers: TENANT_HEADER }
);
expect(result).toEqual({ success: true, data: { notificationIds: ids } });
});

it('should propagate errors', async () => {
mockApiClient.post.mockRejectedValue(createMockError(NOTIFICATION_TEST_CONSTANTS.ERROR_NOTIFICATION_NOT_FOUND));

await expect(
notificationService.deleteByIds(NOTIFICATION_TEST_CONSTANTS.TENANT_ID, [NOTIFICATION_TEST_CONSTANTS.NOTIFICATION_ID])
).rejects.toThrow(NOTIFICATION_TEST_CONSTANTS.ERROR_NOTIFICATION_NOT_FOUND);
});
Comment thread
sarthak688 marked this conversation as resolved.
});

describe('deleteAll', () => {
it('should POST deleteAll=true with empty notifcationIds array and tenant header', async () => {
mockApiClient.post.mockResolvedValue(undefined);

const result = await notificationService.deleteAll(NOTIFICATION_TEST_CONSTANTS.TENANT_ID);

expect(mockApiClient.post).toHaveBeenCalledWith(
NOTIFICATION_ENDPOINTS.DELETE_BULK,
{ notifcationIds: [], deleteAll: true },
{ headers: TENANT_HEADER }
);
expect(result).toEqual({ success: true, data: { all: true } });
});

it('should propagate errors', async () => {
mockApiClient.post.mockRejectedValue(createMockError(TEST_CONSTANTS.ERROR_MESSAGE));

await expect(
notificationService.deleteAll(NOTIFICATION_TEST_CONSTANTS.TENANT_ID)
).rejects.toThrow(TEST_CONSTANTS.ERROR_MESSAGE);
});
});

describe('response transformation', () => {
it('strips internal fields and renames isRead → hasRead in the returned notifications', async () => {
const raw: RawNotificationEntry = createBasicNotificationEntry();
Expand Down
Loading