Skip to content

Commit

Permalink
feat(core): dismiss notifications (#388)
Browse files Browse the repository at this point in the history
* feat: dismiss notifications

* feat: dismiss notifications

* feat: dismiss notifications

* feat: add some unit tests
  • Loading branch information
Sotatek-HocNguyena committed Mar 26, 2024
1 parent 366dfbb commit ae440b0
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 7 deletions.
17 changes: 17 additions & 0 deletions src/core/agent/services/credentialService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
ConnectionType,
} from "../agent.types";
import { SignifyApi } from "../modules/signify/signifyApi";
import { RecordType } from "../../storage/storage.types";
import { NotificationRoute } from "../modules/signify/signifyApi.types";

const eventEmitter = new EventEmitter();

Expand Down Expand Up @@ -1061,4 +1063,19 @@ describe("Credential service of agent - CredentialExchangeRecord helpers", () =>
CredentialService.CREDENTIAL_MISSING_METADATA_ERROR_MSG
);
});

test("Should pass the filter throught findAllByQuery when call getUnhandledCredentials", async () => {
agent.credentials.findAllByQuery = jest.fn().mockResolvedValueOnce([]);
basicStorage.findAllByQuery = jest.fn().mockResolvedValue([]);
await credentialService.getUnhandledCredentials({
isDismissed: false,
});
expect(basicStorage.findAllByQuery).toBeCalledWith(
RecordType.NOTIFICATION_KERI,
{
route: NotificationRoute.Credential,
isDismissed: false,
}
);
});
});
17 changes: 12 additions & 5 deletions src/core/agent/services/credentialService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,14 +402,16 @@ class CredentialService extends AgentService {
});
}

async getUnhandledCredentials(): Promise<
(CredentialExchangeRecord | KeriNotification)[]
> {
async getUnhandledCredentials(
filters: {
isDismissed?: boolean;
} = {}
): Promise<(CredentialExchangeRecord | KeriNotification)[]> {
const results = await Promise.all([
this.agent.credentials.findAllByQuery({
state: CredentialState.OfferReceived,
}),
this.getKeriCredentialNotifications(),
this.getKeriCredentialNotifications(filters),
]);
return results.flat();
}
Expand All @@ -431,11 +433,16 @@ class CredentialService extends AgentService {
return metadata;
}

private async getKeriCredentialNotifications(): Promise<KeriNotification[]> {
private async getKeriCredentialNotifications(
filters: {
isDismissed?: boolean;
} = {}
): Promise<KeriNotification[]> {
const results = await this.basicStorage.findAllByQuery(
RecordType.NOTIFICATION_KERI,
{
route: NotificationRoute.Credential,
...filters,
}
);
return results.map((result) => {
Expand Down
49 changes: 49 additions & 0 deletions src/core/agent/services/identifierService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { IdentifierType } from "./identifierService.types";
import { ConnectionStatus, ConnectionType } from "../agent.types";
import { AriesAgent } from "../agent";
import { SignifyApi } from "../modules/signify/signifyApi";
import { RecordType } from "../../storage/storage.types";
import { NotificationRoute } from "../modules/signify/signifyApi.types";

// We are losing typing here but the Agent class is overly complex to setup for tests.
const agent = jest.mocked({
Expand Down Expand Up @@ -1923,4 +1925,51 @@ describe("Identifier service of agent", () => {
`${IdentifierService.EXN_MESSAGE_NOT_FOUND} EHe8OnqWhR--r7zPJy97PS2B5rY7Zp4vnYQICs4gXodW`
);
});

test("Can get unhandled Multisig Identifier notifications", async () => {
const basicRecord = {
_tags: {
isDismiss: true,
type: RecordType.NOTIFICATION_KERI,
route: NotificationRoute.MultiSigIcp,
},
id: "AIeGgKkS23FDK4mxpfodpbWhTydFz2tdM64DER6EdgG-",
createdAt: "2024-03-25T09:33:05.325Z",
content: {
r: NotificationRoute.MultiSigIcp,
d: "EF6Nmxz8hs0oVc4loyh2J5Sq9H3Z7apQVqjO6e4chtsp",
},
type: RecordType.NOTIFICATION_KERI,
};
basicStorage.findAllByQuery = jest.fn().mockResolvedValue([basicRecord]);
expect(
await identifierService.getUnhandledMultisigIdentifiers()
).toStrictEqual([
{
id: basicRecord.id,
createdAt: basicRecord.createdAt,
a: basicRecord.content,
},
]);
});

test("Should pass the filter throught findAllByQuery when call getUnhandledMultisigIdentifiers", async () => {
basicStorage.findAllByQuery = jest.fn().mockResolvedValue([]);
await identifierService.getUnhandledMultisigIdentifiers({
isDismissed: false,
});
expect(basicStorage.findAllByQuery).toBeCalledWith(
RecordType.NOTIFICATION_KERI,
{
route: NotificationRoute.MultiSigIcp,
isDismissed: false,
$or: [
{ route: NotificationRoute.MultiSigIcp },
{
route: NotificationRoute.MultiSigRot,
},
],
}
);
});
});
7 changes: 6 additions & 1 deletion src/core/agent/services/identifierService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -663,11 +663,16 @@ class IdentifierService extends AgentService {
return { done: false };
}

async getUnhandledMultisigIdentifiers(): Promise<KeriNotification[]> {
async getUnhandledMultisigIdentifiers(
filters: {
isDismissed?: boolean;
} = {}
): Promise<KeriNotification[]> {
const results = await this.basicStorage.findAllByQuery(
RecordType.NOTIFICATION_KERI,
{
route: NotificationRoute.MultiSigIcp,
...filters,
$or: [
{ route: NotificationRoute.MultiSigIcp },
{
Expand Down
24 changes: 24 additions & 0 deletions src/core/agent/services/signifyNotificationService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,28 @@ describe("Signify notification service of agent", () => {
expect(basicStorage.save).toBeCalledTimes(2);
expect(callback).toBeCalledTimes(2);
});

test("Should call update when dismiss a notification", async () => {
const notification = {
id: "id",
_tags: {
isDismissed: false,
} as any,
setTag: function (name: string, value: any) {
this._tags[name] = value;
},
};
basicStorage.findById = jest.fn().mockResolvedValue(notification);
await signifyNotificationService.dismissNotification(notification.id);
expect(basicStorage.update).toBeCalledTimes(1);
});

test("Should throw error when dismiss an invalid notification", async () => {
basicStorage.findById = jest.fn().mockResolvedValue(null);
await expect(
signifyNotificationService.dismissNotification("not-exist-noti-id")
).rejects.toThrowError(
SignifyNotificationService.KERI_NOTIFICATION_NOT_FOUND
);
});
});
23 changes: 23 additions & 0 deletions src/core/agent/services/signifyNotificationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { NotificationRoute } from "../modules/signify/signifyApi.types";
import { PreferencesKeys, PreferencesStorage } from "../../storage";
import { RecordType } from "../../storage/storage.types";
class SignifyNotificationService extends AgentService {
static readonly KERI_NOTIFICATION_NOT_FOUND =
"Keri notification record not found";
async onNotificationKeriStateChanged(
callback: (event: KeriNotification) => void
) {
Expand Down Expand Up @@ -114,6 +116,7 @@ class SignifyNotificationService extends AgentService {
content: event.a,
type: RecordType.NOTIFICATION_KERI,
tags: {
isDismissed: false,
type: RecordType.NOTIFICATION_KERI,
route: event.a.r,
},
Expand All @@ -124,6 +127,26 @@ class SignifyNotificationService extends AgentService {
a: result.content,
};
}

async dismissNotification(notificationId: string) {
const notificationRecord = await this.basicStorage.findById(notificationId);
if (!notificationRecord) {
throw new Error(SignifyNotificationService.KERI_NOTIFICATION_NOT_FOUND);
}
notificationRecord.setTag("isDismissed", true);
await this.basicStorage.update(notificationRecord);
}

/**This allow us to get all dismissed notifications */
async getDismissedNotifications() {
const notifications = await this.basicStorage.findAllByQuery(
RecordType.NOTIFICATION_KERI,
{
isDismissed: true,
}
);
return notifications;
}
}

export { SignifyNotificationService };
1 change: 1 addition & 0 deletions src/ui/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jest.mock("../core/agent/agent", () => ({
identifiers: {
getIdentifiers: jest.fn().mockResolvedValue([]),
syncKeriaIdentifiers: jest.fn(),
getUnhandledMultisigIdentifiers: jest.fn(),
},
connections: {
getConnections: jest.fn().mockResolvedValue([]),
Expand Down
1 change: 1 addition & 0 deletions src/ui/components/AppWrapper/AppWrapper.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jest.mock("../../../core/agent/agent", () => ({
identifiers: {
getIdentifiers: jest.fn().mockResolvedValue([]),
syncKeriaIdentifiers: jest.fn(),
getUnhandledMultisigIdentifiers: jest.fn(),
},
connections: {
getConnections: jest.fn().mockResolvedValue([]),
Expand Down
7 changes: 6 additions & 1 deletion src/ui/components/AppWrapper/AppWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,12 @@ const AppWrapper = (props: { children: ReactNode }) => {
const oldMessages = (
await Promise.all([
AriesAgent.agent.connections.getUnhandledConnections(),
AriesAgent.agent.credentials.getUnhandledCredentials(),
AriesAgent.agent.credentials.getUnhandledCredentials({
isDismissed: false,
}),
AriesAgent.agent.identifiers.getUnhandledMultisigIdentifiers({
isDismissed: false,
}),
])
)
.flat()
Expand Down
3 changes: 3 additions & 0 deletions src/ui/pages/IncomingRequest/IncomingRequest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ const IncomingRequest = () => {
incomingRequest.type === IncomingRequestType.MULTI_SIG_REQUEST_INCOMING
) {
// @TODO - sdisalvo: placeholder for ignoring the request
await AriesAgent.agent.signifyNotifications.dismissNotification(
incomingRequest.id
);
}
handleReset();
};
Expand Down

0 comments on commit ae440b0

Please sign in to comment.