-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(notifications): add notification api and update banner (#986)
* setup api * setup hooks * hook up banner * small fix * resource allowance * Update api.ts * arn change * quick fix * Update getSystemNotifs.ts * no toString * feat: merge hooks into one file * Create useGetSystemNotifs.test.ts * fix: infinite re-render * fix tests
- Loading branch information
1 parent
a70d67d
commit 0db7d48
Showing
14 changed files
with
273 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { describe, it, expect, vi, beforeEach } from "vitest"; | ||
import { getSystemNotifs } from "./getSystemNotifs"; | ||
import * as util from "shared-utils"; | ||
|
||
vi.mock("shared-utils", () => ({ | ||
getExport: vi.fn(), | ||
getSecret: vi.fn(), | ||
})); | ||
|
||
describe("notif handler", () => { | ||
beforeEach(() => { | ||
vi.resetAllMocks(); | ||
}); | ||
|
||
it("returns 200 and notifs if secret exists", async () => { | ||
vi.stubEnv("notificationSecretArn", "test_secret"); | ||
vi.spyOn(util, "getSecret").mockImplementation(async () => "[]"); | ||
const result = await getSystemNotifs(); | ||
expect(result.statusCode).toBe(200); | ||
expect(result.body).toBe("[]"); | ||
}); | ||
|
||
it("returns 200 and empty array if no notifs", async () => { | ||
vi.stubEnv("notificationSecretArn", "test_secret"); | ||
vi.spyOn(util, "getSecret").mockImplementation(async () => null as unknown as string); | ||
const result = await getSystemNotifs(); | ||
expect(result.statusCode).toBe(200); | ||
expect(result.body).toBe("[]"); | ||
}); | ||
|
||
it("returns 502 with specific error", async () => { | ||
vi.stubEnv("notificationSecretArn", "error"); | ||
vi.spyOn(util, "getSecret").mockImplementation(async () => { | ||
throw new Error("test error"); | ||
}); | ||
const result = await getSystemNotifs(); | ||
expect(result.statusCode).toBe(502); | ||
expect(JSON.parse(result.body).error).toBe("test error"); | ||
}); | ||
|
||
it("returns 502 with generic error", async () => { | ||
vi.stubEnv("notificationSecretArn", undefined); | ||
vi.spyOn(util, "getSecret").mockImplementation(async () => { | ||
throw new Error(); | ||
}); | ||
const result = await getSystemNotifs(); | ||
expect(result.statusCode).toBe(502); | ||
expect(JSON.parse(result.body).error).toBe("Internal server error"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { getSecret } from "shared-utils"; | ||
import { response } from "libs/handler-lib"; | ||
|
||
export const getSystemNotifs = async () => { | ||
try { | ||
const notifs = await getSecret(process.env.notificationSecretArn!); | ||
|
||
return response({ | ||
statusCode: 200, | ||
body: JSON.parse(notifs) || [], | ||
}); | ||
} catch (error: any) { | ||
console.error("Error:", error); | ||
return response({ | ||
statusCode: 502, | ||
body: { | ||
error: error.message ? error.message : "Internal server error", | ||
}, | ||
}); | ||
} | ||
}; | ||
|
||
export const handler = getSystemNotifs; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export interface BannerNotification { | ||
notifId: string; | ||
header: string; | ||
body: string; | ||
buttonText?: string; | ||
buttonLink?: string; | ||
pubDate: string; | ||
expDate?: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { http, HttpResponse } from "msw"; | ||
import { BannerNotification } from "shared-types"; | ||
|
||
export type NotifRequestBody = BannerNotification[]; | ||
|
||
const defaultNotificationHandler = http.get<any, NotifRequestBody>(/\/systemNotifs/, async () => { | ||
return HttpResponse.json( | ||
[ | ||
{ | ||
notifId: "testId", | ||
body: "testBody", | ||
header: "testHeader", | ||
pubDate: new Date().toISOString(), | ||
}, | ||
], | ||
{ status: 200 }, | ||
); | ||
}); | ||
|
||
export const notificationHandlers = [defaultNotificationHandler]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { beforeAll, afterAll, test, expect, vi, describe } from "vitest"; | ||
import { renderHook } from "@testing-library/react"; | ||
import { mockUseGetUser } from "mocks"; | ||
|
||
import * as api from "@/api"; | ||
import * as query from "@tanstack/react-query"; | ||
|
||
import { OneMacUser } from "."; | ||
|
||
vi.mock("@/api/useGetUser", () => ({ | ||
useGetUser: vi.fn(), | ||
})); | ||
|
||
vi.mock("@tanstack/react-query", async (imp) => ({ | ||
...(await imp()), | ||
useQuery: vi.fn(), | ||
})); | ||
|
||
const testNotifs = [ | ||
{ | ||
notifId: "testId", | ||
body: "testBody", | ||
header: "testHeader", | ||
pubDate: new Date().toISOString(), | ||
}, | ||
]; | ||
|
||
describe("useGetSystemNotif", () => { | ||
beforeAll(() => { | ||
vi.spyOn(api, "useGetUser").mockImplementation(() => { | ||
const response = mockUseGetUser(); | ||
return response as query.UseQueryResult<OneMacUser, unknown>; | ||
}); | ||
vi.spyOn(Storage.prototype, "getItem").mockImplementation(() => "[]"); | ||
vi.spyOn(Storage.prototype, "setItem").mockImplementation(() => undefined); | ||
vi.spyOn(query, "useQuery").mockImplementation( | ||
() => | ||
({ | ||
data: testNotifs, | ||
}) as query.UseQueryResult<unknown, unknown>, | ||
); | ||
}); | ||
|
||
afterAll(() => { | ||
vi.resetAllMocks(); | ||
}); | ||
|
||
test("API call", async () => { | ||
const notifs = await api.getSystemNotifs(); | ||
expect(notifs).toBeTruthy(); | ||
expect(notifs.length).toEqual(1); | ||
}); | ||
|
||
test("returns test notification array", () => { | ||
// const testHook = api.useGetSystemNotifs(); | ||
const { | ||
result: { current: testHook }, | ||
} = renderHook(() => api.useGetSystemNotifs()); | ||
|
||
// expect(testHook.notifications[0].notifId).toBe(testNotifs[0].notifId); | ||
expect(testHook.allNotifications[0].notifId).toBe(testNotifs[0].notifId); | ||
expect(testHook.dismissed).toStrictEqual([]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { API } from "aws-amplify"; | ||
import { useState, useEffect } from "react"; | ||
import { useGetUser } from "@/api"; | ||
import { useQuery } from "@tanstack/react-query"; | ||
import { BannerNotification, ReactQueryApiError } from "shared-types"; | ||
|
||
export const getSystemNotifs = async (): Promise<BannerNotification[]> => { | ||
return await API.get("os", "/systemNotifs", {}); | ||
}; | ||
|
||
export const useGetSystemNotifs = () => { | ||
const userQuery = useGetUser(); | ||
const [dismissed, setDismissed] = useState<string[]>([]); | ||
|
||
useEffect(() => { | ||
const dismissedNotifs = localStorage.getItem(`notifs.${userQuery?.data?.user?.username}`); | ||
const parsed: string[] = JSON.parse(dismissedNotifs) ?? []; | ||
setDismissed(parsed); | ||
}, [userQuery?.data?.user?.username]); | ||
|
||
const result = useQuery<BannerNotification[], ReactQueryApiError>(["systemBannerNotifs"], () => | ||
getSystemNotifs(), | ||
); | ||
|
||
const notDismissed = result.data?.filter((i) => !dismissed.includes(i.notifId)) ?? []; //check dismissed | ||
const currentNotifs = notDismissed.filter( | ||
(i) => i.expDate && new Date(i.expDate).getTime() > new Date().getTime(), | ||
); //check expired | ||
|
||
const clearNotif = (id?: string) => { | ||
const toBeRemoved = id ?? currentNotifs?.[0]?.notifId ?? ""; | ||
const cleared = [...dismissed, toBeRemoved].filter((v, i, a) => a.indexOf(v) === i); | ||
|
||
setDismissed(cleared); | ||
localStorage.setItem(`notifs.${userQuery?.data?.user?.username}`, JSON.stringify(cleared)); | ||
}; | ||
|
||
const resetNotifs = () => { | ||
setDismissed([]); | ||
localStorage.setItem(`notifs.${userQuery?.data?.user?.username}`, JSON.stringify([])); | ||
}; | ||
return { | ||
notifications: currentNotifs, | ||
dismissed: dismissed, | ||
allNotifications: result.data ?? [], | ||
clearNotif: clearNotif, | ||
resetNotifs: resetNotifs, | ||
}; | ||
}; |
Oops, something went wrong.