Skip to content

Commit debe4fb

Browse files
authored
Merge pull request #63 from eresearchqut/dev
QA Release 2025.3
2 parents ddd0d5c + c235da6 commit debe4fb

27 files changed

+1448
-1253
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ git config blame.ignoreRevsFile .git-blame-ignore-revs
5555

5656
```
5757
cd api
58-
npm install
58+
pnpm install
5959
```
6060

6161
```

api/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "transcription-api",
2+
"name": "api",
33
"version": "1.0.0",
44
"description": "API for the Transcription Service",
55
"engines": {
@@ -34,7 +34,7 @@
3434
"@types/body-parser": "^1.19.5",
3535
"@types/cors": "^2.8.13",
3636
"@types/express": "^5.0.0",
37-
"@types/jest": "^29.5.13",
37+
"@types/jest": "^29.5.14",
3838
"@typescript-eslint/parser": "7.2.0",
3939
"aws-sdk-client-mock": "^4.1.0",
4040
"aws-sdk-client-mock-jest": "^4.1.0",
@@ -45,7 +45,7 @@
4545
"jest-dynalite": "^3.6.1",
4646
"prettier": "^3.0.3",
4747
"rimraf": "^6.0.1",
48-
"ts-jest": "^29.1.1",
48+
"ts-jest": "^29.3.2",
4949
"typescript": "5.5.4"
5050
},
5151
"prettier": {

api/src/event/summariseTranscriptionHandler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Transcription } from "model";
1212
import { bedrockClientConfig, invokeModel } from "../client/bedrockClient";
1313
import {
1414
getTranscription,
15+
normaliseJobId,
1516
summaryKey as updateSummaryKey,
1617
} from "../service/transcriptionService";
1718

@@ -43,7 +44,7 @@ export const handler = async (event: S3Event) => {
4344
...key.matchAll(outputPattern),
4445
][0];
4546
if (matchedKey) {
46-
const jobId = fileName.split(".")[0];
47+
const jobId = normaliseJobId(fileName.split(".")[0]);
4748
const summaryKey = `${identityId}/summary/${jobId}`;
4849
const privateSummaryKey = `private/${cognitoId}/${summaryKey}`;
4950
promises.push(

api/src/service/transcriptionService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
updateResource,
88
} from "../repository/repository";
99

10-
const normaliseJobId = (jobId: string): string =>
10+
export const normaliseJobId = (jobId: string): string =>
1111
jobId.split("redacted-").at(-1) ?? jobId;
1212

1313
export const jobStarted = (

api/test/event/summariseTranscriptionHandler.test.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,25 @@ describe("summariseTranscriptionHandler", () => {
3232
bedrockClientMock.reset();
3333
});
3434

35-
test("handler", async () => {
35+
test.each([
36+
{
37+
name: "generate summary",
38+
metadata: { generatesummary: "true" },
39+
transcriptionKey: "2e9b38b5-1df0-4841-8308-f174fb88aac7.json",
40+
},
41+
{
42+
name: "generate summary with pii redaction",
43+
metadata: { generatesummary: "true", enablepiiredaction: "true" },
44+
transcriptionKey: "redacted-2e9b38b5-1df0-4841-8308-f174fb88aac7.json",
45+
},
46+
])("test $name", async ({ name, metadata, transcriptionKey }) => {
3647
await dynamoDBClient.send(
3748
new PutItemCommand({
3849
TableName: tableName,
3950
Item: marshall({
4051
pk: "76c65a59-1c57-489b-be96-020ceaa9675a",
4152
sk: "2e9b38b5-1df0-4841-8308-f174fb88aac7",
42-
metadata: JSON.parse(JSON.stringify({ generatesummary: "true" })),
53+
metadata: JSON.parse(JSON.stringify(metadata)),
4354
}),
4455
}),
4556
);
@@ -72,7 +83,7 @@ describe("summariseTranscriptionHandler", () => {
7283
s3: {
7384
bucket: { name: "local-transcriptions" },
7485
object: {
75-
key: "private/ap-southeast-2%3Abcb38797-8e6a-43ea-9844-d8505927785a/76c65a59-1c57-489b-be96-020ceaa9675a/2e9b38b5-1df0-4841-8308-f174fb88aac7.json",
86+
key: `private/ap-southeast-2%3Abcb38797-8e6a-43ea-9844-d8505927785a/76c65a59-1c57-489b-be96-020ceaa9675a/${transcriptionKey}`,
7687
},
7788
},
7889
},
@@ -82,7 +93,7 @@ describe("summariseTranscriptionHandler", () => {
8293

8394
expect(s3ClientMock).toHaveReceivedCommandWith(GetObjectCommand, {
8495
Bucket: "local-transcriptions",
85-
Key: "private/ap-southeast-2:bcb38797-8e6a-43ea-9844-d8505927785a/76c65a59-1c57-489b-be96-020ceaa9675a/2e9b38b5-1df0-4841-8308-f174fb88aac7.json",
96+
Key: `private/ap-southeast-2:bcb38797-8e6a-43ea-9844-d8505927785a/76c65a59-1c57-489b-be96-020ceaa9675a/${transcriptionKey}`,
8697
});
8798

8899
expect(bedrockClientMock).toHaveReceivedCommandWith(InvokeModelCommand, {

deployment/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
},
1616
"devDependencies": {
1717
"@types/node": "20.16.11",
18-
"aws-cdk": "^2.178.1",
19-
"esbuild": "^0.24.0",
18+
"aws-cdk": "^2.1014.0",
19+
"esbuild": "^0.25.4",
2020
"ts-node": "^10.9.2",
2121
"typescript": "~5.6.3",
2222
"rimraf": "^6.0.1"
2323
},
2424
"dependencies": {
2525
"@aws-sdk/client-ssm": "^3.667.0",
26-
"aws-cdk-lib": "^2.178.1",
26+
"aws-cdk-lib": "^2.195.0",
2727
"cdk-pipelines-github": "^0.4.125",
2828
"constructs": "^10.3.0",
2929
"source-map-support": "^0.5.21"

frontend/client/fetchers.ts

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { fetchAuthSession } from "aws-amplify/auth";
1+
import { fetchAuthSession, signOut } from "aws-amplify/auth";
22

33
export interface FetcherProps {
44
apiUrl: string;
@@ -58,25 +58,26 @@ export const buildApiEndpoint = ({
5858

5959
export const getHeaders = async () =>
6060
fetchAuthToken()
61-
.then(
62-
(idToken) =>
63-
({
64-
Authorization: `Bearer ${idToken}`,
65-
Accept: "application/json",
66-
"Content-Type": "application/json",
67-
}) as HeadersInit,
68-
)
69-
.catch((error) => {
70-
console.error(error);
71-
throw error;
61+
.then((idToken) => {
62+
if (idToken === undefined) throw Error("No idToken");
63+
return {
64+
Authorization: `Bearer ${idToken}`,
65+
Accept: "application/json",
66+
"Content-Type": "application/json",
67+
} as HeadersInit;
68+
})
69+
.catch(async () => {
70+
await signOut();
7271
});
7372

7473
export const getter = (props: FetcherProps) =>
75-
getHeaders().then((headers) =>
76-
fetch(buildApiEndpoint(props), {
77-
...props.init,
78-
headers,
79-
}).then((response) =>
80-
response.ok ? response.json() : rejectApiError(response),
81-
),
74+
getHeaders().then(
75+
(headers) =>
76+
headers &&
77+
fetch(buildApiEndpoint(props), {
78+
...props.init,
79+
headers,
80+
}).then((response) =>
81+
response.ok ? response.json() : rejectApiError(response),
82+
),
8283
);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { AsyncErrorBoundary } from "./";
3+
import { Text } from "@chakra-ui/react";
4+
import { ErrorBoundary } from "react-error-boundary";
5+
import { ErrorMessage } from "@/components/errorMessage";
6+
7+
export default {
8+
title: "Components/AsyncErrorBoundary",
9+
component: AsyncErrorBoundary,
10+
parameters: {
11+
layout: "centered",
12+
},
13+
} as Meta<typeof AsyncErrorBoundary>;
14+
type Story = StoryObj<typeof AsyncErrorBoundary>;
15+
16+
const ErrorComponent = () => {
17+
throw new Error("Test error");
18+
};
19+
20+
export const Default: Story = {
21+
args: {
22+
children: <ErrorComponent />,
23+
},
24+
render: (args) => (
25+
<ErrorBoundary FallbackComponent={ErrorMessage}>
26+
<AsyncErrorBoundary {...args} />
27+
</ErrorBoundary>
28+
),
29+
};
30+
31+
export const WithNoErrors: Story = {
32+
...Default,
33+
args: {
34+
children: <Text>Success, no errors!</Text>,
35+
},
36+
};
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { FunctionComponent, PropsWithChildren, useEffect } from "react";
2+
import { useErrorBoundary } from "react-error-boundary";
3+
4+
const AsyncErrorBoundary: FunctionComponent<PropsWithChildren> = ({
5+
children,
6+
}) => {
7+
const { showBoundary } = useErrorBoundary();
8+
9+
useEffect(() => {
10+
const handleError = (event: ErrorEvent | PromiseRejectionEvent) => {
11+
showBoundary(event);
12+
};
13+
14+
window.addEventListener("unhandledrejection", handleError);
15+
16+
return () => {
17+
window.removeEventListener("unhandledrejection", handleError);
18+
};
19+
}, [showBoundary]);
20+
21+
return children;
22+
};
23+
24+
export default AsyncErrorBoundary;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as AsyncErrorBoundary } from "./asyncErrorBoundary";
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { ErrorMessage } from "./";
3+
import { ErrorBoundary } from "react-error-boundary";
4+
5+
export default {
6+
title: "Components/ErrorMessage",
7+
component: ErrorMessage,
8+
} as Meta<typeof ErrorMessage>;
9+
10+
type Story = StoryObj<typeof ErrorMessage>;
11+
12+
const ErrorComponent = () => {
13+
throw new Error("Test error");
14+
};
15+
16+
export const Default: Story = {
17+
args: {
18+
message: "An error occurred while processing your request.",
19+
},
20+
render: (args) => (
21+
<ErrorBoundary fallback={<ErrorMessage {...args} />}>
22+
<ErrorComponent />
23+
</ErrorBoundary>
24+
),
25+
};
26+
27+
export const WithTitle: Story = {
28+
...Default,
29+
args: {
30+
title: "Oh no!",
31+
message: "Something went wrong. Please try again later.",
32+
},
33+
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { FunctionComponent } from "react";
2+
import {
3+
DialogBackdrop,
4+
DialogBody,
5+
DialogCloseTrigger,
6+
DialogContent,
7+
DialogFooter,
8+
DialogHeader,
9+
DialogRoot,
10+
DialogTitle,
11+
DialogTrigger,
12+
} from "@/components/ui/dialog";
13+
import { Text } from "@chakra-ui/react";
14+
import { ExternalLink } from "@/components/externalLink";
15+
import { FallbackProps, useErrorBoundary } from "react-error-boundary";
16+
import { MappedIcon } from "@/components/mappedIcon";
17+
18+
export interface ErrorMessageProps extends FallbackProps {
19+
icon?: string;
20+
title?: string;
21+
message?: string;
22+
}
23+
24+
const ErrorMessage: FunctionComponent<ErrorMessageProps & FallbackProps> = ({
25+
icon = "exclamation-circle",
26+
title = "An unexpected error has occurred",
27+
message,
28+
}) => {
29+
const { resetBoundary } = useErrorBoundary();
30+
31+
return (
32+
<DialogRoot
33+
defaultOpen={true}
34+
onOpenChange={() => {
35+
resetBoundary();
36+
}}
37+
preventScroll={false}
38+
>
39+
<DialogTrigger />
40+
<DialogBackdrop />
41+
<DialogContent>
42+
<DialogCloseTrigger />
43+
<DialogHeader>
44+
<DialogTitle>
45+
{icon && <MappedIcon mb={1} icon={icon} />} {title}
46+
</DialogTitle>
47+
</DialogHeader>
48+
<DialogBody>
49+
{message && <Text>{message}</Text>}
50+
<Text>
51+
If you continue to receive this error, please{" "}
52+
<ExternalLink
53+
href={
54+
"https://qutvirtual4.qut.edu.au/group/research-students/conducting-research/specialty-research-facilities/advanced-research-computing-storage"
55+
}
56+
>
57+
contact eResearch
58+
</ExternalLink>{" "}
59+
for assistance.
60+
</Text>
61+
</DialogBody>
62+
<DialogFooter />
63+
</DialogContent>
64+
</DialogRoot>
65+
);
66+
};
67+
68+
export default ErrorMessage;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export {
2+
default as ErrorMessage,
3+
type ErrorMessageProps,
4+
} from "./errorMessage";

frontend/components/header/header.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ import {
1010
} from "@chakra-ui/react";
1111
import { MappedIcon } from "@/components/mappedIcon";
1212
import { ColorModeButton } from "@/components/ui/color-mode";
13-
import { useAuth, useLogin, useLogout } from "../../context/auth-context";
13+
import { useAuth, useLogin } from "../../context/auth-context";
1414

1515
import logo from "@/public/logo.png";
1616
import { ButtonProps } from "@/components/ui/button";
17+
import { signOut } from "aws-amplify/auth";
1718

1819
export const Header: FunctionComponent = () => {
1920
const {
2021
state: { isAuthenticated },
2122
} = useAuth();
2223
const { handleLogin } = useLogin();
23-
const { handleLogout } = useLogout();
2424
const buttonProps: ButtonProps = {
2525
colorPalette: "blue",
2626
variant: "outline",
@@ -47,7 +47,12 @@ export const Header: FunctionComponent = () => {
4747
</Button>
4848
)}
4949
{isAuthenticated && (
50-
<Button onClick={handleLogout} {...buttonProps}>
50+
<Button
51+
onClick={() => {
52+
signOut().then();
53+
}}
54+
{...buttonProps}
55+
>
5156
<MappedIcon icon={"exit-outline"} />
5257
Log out
5358
</Button>

0 commit comments

Comments
 (0)