Skip to content

Commit 7f2bfa5

Browse files
committed
call out to new octopus digital imaging ordering API during createItem mutation, if the item type is imaging-request
1 parent 64b6309 commit 7f2bfa5

File tree

9 files changed

+169
-22
lines changed

9 files changed

+169
-22
lines changed

cdk/lib/__snapshots__/stack.test.ts.snap

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4462,6 +4462,26 @@ $util.toJson($ctx.result)",
44624462
"Effect": "Allow",
44634463
"Resource": "arn:aws:s3:::permissions-cache/TEST/*",
44644464
},
4465+
Object {
4466+
"Action": "ssm:GetParameter",
4467+
"Effect": "Allow",
4468+
"Resource": Object {
4469+
"Fn::Join": Array [
4470+
"",
4471+
Array [
4472+
"arn:aws:ssm:",
4473+
Object {
4474+
"Ref": "AWS::Region",
4475+
},
4476+
":",
4477+
Object {
4478+
"Ref": "AWS::AccountId",
4479+
},
4480+
":parameter/pinboard/*",
4481+
],
4482+
],
4483+
},
4484+
},
44654485
Object {
44664486
"Action": "rds-db:connect",
44674487
"Effect": "Allow",

cdk/lib/stack.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,10 @@ export class PinBoardStack extends GuStack {
301301
deployBucket,
302302
`${this.stack}/${this.stage}/${DATABASE_BRIDGE_LAMBDA_BASENAME}/${DATABASE_BRIDGE_LAMBDA_BASENAME}.zip`
303303
),
304-
initialPolicy: [permissionsFilePolicyStatement],
304+
initialPolicy: [
305+
permissionsFilePolicyStatement,
306+
readPinboardParamStorePolicyStatement,
307+
],
305308
vpc: accountVpc,
306309
securityGroups: [databaseSecurityGroup],
307310
}

client/src/grid/octopusImagingOrderDisplay.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ export const OctopusImagingOrderDisplay = ({
6868
draggable={false}
6969
// TODO: hover for larger thumbnail
7070
/>
71+
{payload.octopusOrderId && (
72+
<div
73+
css={css`
74+
${agateSans.xxsmall()}
75+
font-size: 8px;
76+
`}
77+
>
78+
<strong>Order Ref:</strong> {payload.octopusOrderId}
79+
</div>
80+
)}
7181
</div>
7282
);
7383
};

client/src/itemInputBox.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,8 @@ export const ItemInputBox = ({
265265
`}
266266
>
267267
<em>
268-
You must provide some notes to help Imaging team to understand your
269-
request
268+
You must provide some notes to help the Imaging team to understand
269+
your request
270270
</em>
271271
</div>
272272
)}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Item } from "../../../shared/graphql/graphql";
1+
import { Item } from "shared/graphql/graphql";
22

3-
export type ItemWithParsedPayload = Item & {
3+
export type ItemWithParsedPayload = Omit<Item, "payload"> & {
44
payload: Record<string, unknown> | null | undefined;
55
};

client/src/types/PayloadAndType.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export interface PayloadWithApiUrl extends PayloadCommon {
4747

4848
export interface PayloadWithRequestType extends PayloadWithThumbnail {
4949
requestType: ImagingRequestType;
50+
octopusOrderId?: string;
5051
}
5152

5253
export type Payload =

database-bridge-lambda/run.ts

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import { handler } from "./src";
2-
import { AppSyncResolverEvent } from "aws-lambda/trigger/appsync-resolver";
3-
import { createDatabaseTunnel } from "../shared/database/local/databaseTunnel";
2+
import { createDatabaseTunnel } from "shared/database/local/databaseTunnel";
43
import prompts from "prompts";
5-
import { DatabaseOperation } from "../shared/graphql/operations";
6-
import { getYourEmail } from "../shared/local/yourEmail";
7-
import { CreateItemInput, EditItemInput } from "../shared/graphql/graphql";
4+
import { DatabaseOperation } from "shared/graphql/operations";
5+
import { getYourEmail } from "shared/local/yourEmail";
6+
import { CreateItemInput, EditItemInput } from "shared/graphql/graphql";
7+
import { IMAGING_REQUEST_ITEM_TYPE } from "shared/octopusImaging";
88

99
(async () => {
1010
const baseInput = {
1111
identity: { resolverContext: { userEmail: await getYourEmail() } },
12+
request: { headers: { referrer: "LOCAL-TESTING" }, domainName: null },
1213
};
1314

14-
const sampleInputs: Partial<Record<DatabaseOperation, unknown>> = {
15+
const sampleInputs = {
1516
listItems: { pinboardId: "63206" },
1617
searchMentionableUsers: { prefix: "a" },
1718
claimItem: { itemId: "1667" },
@@ -35,7 +36,7 @@ import { CreateItemInput, EditItemInput } from "../shared/graphql/graphql";
3536
deleteItem: { itemId: "2352" },
3637
getMyUser: {},
3738
visitTourStep: { tourStepId: "testing" },
38-
};
39+
} satisfies Partial<Record<DatabaseOperation, unknown>>;
3940

4041
await createDatabaseTunnel();
4142

@@ -48,14 +49,34 @@ import { CreateItemInput, EditItemInput } from "../shared/graphql/graphql";
4849
type: "select",
4950
name: "inputPayload",
5051
message: "Operation?",
51-
choices: Object.entries(sampleInputs).map(([operation, sampleInput]) => ({
52-
title: operation,
53-
value: {
54-
...baseInput,
55-
arguments: sampleInput,
56-
info: { fieldName: operation },
57-
} as AppSyncResolverEvent<unknown, unknown>,
58-
})),
52+
choices: [
53+
...Object.entries(sampleInputs).map(([operation, sampleInput]) => ({
54+
title: operation,
55+
value: {
56+
...baseInput,
57+
arguments: sampleInput,
58+
info: { fieldName: operation },
59+
},
60+
})),
61+
{
62+
title: "Digital Imaging Order (via octopus)",
63+
value: {
64+
...baseInput,
65+
info: { fieldName: "createItem" },
66+
arguments: {
67+
input: {
68+
...sampleInputs.createItem.input,
69+
type: IMAGING_REQUEST_ITEM_TYPE,
70+
payload: {
71+
requestType: "Cut out",
72+
embeddableUrl:
73+
"https://media.test.dev-gutools.co.uk/images/24733ea386c7fcb37496c55cc86e8f1468b9dfcf",
74+
},
75+
},
76+
},
77+
},
78+
},
79+
],
5980
});
6081

6182
console.log(JSON.stringify(await handler(inputPayload), null, 2));
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {
2+
pinboardConfigPromiseGetter,
3+
pinboardSecretPromiseGetter,
4+
STAGE,
5+
} from "shared/awsIntegration";
6+
import fetch from "node-fetch";
7+
import { ItemWithParsedPayload } from "client/src/types/ItemWithParsedPayload";
8+
9+
const paramStorePrefix = `octopus-imaging/${STAGE}`;
10+
const octopusImagingBaseUrl = pinboardConfigPromiseGetter(
11+
`${paramStorePrefix}/base-url`
12+
);
13+
const octopusImagingApiKey = pinboardSecretPromiseGetter(
14+
`${paramStorePrefix}/api-key`
15+
);
16+
const octopusImagingApiSecret = pinboardSecretPromiseGetter(
17+
`${paramStorePrefix}/api-secret`
18+
);
19+
20+
export const performImagingRequest = async (
21+
item: ItemWithParsedPayload,
22+
{ embeddableUrl, requestType }: Record<string, unknown>
23+
): Promise<string> => {
24+
const gridId = (embeddableUrl as string).split("/").pop();
25+
if (!gridId) {
26+
throw new Error(`Couldn't extract grid ID from payload: ${item.payload}`);
27+
}
28+
const imagingRequestBody = {
29+
workflowId: item.pinboardId,
30+
pinboardItemId: item.id,
31+
gridId,
32+
lastUser: item.userEmail,
33+
notes: item.message, //TODO check for 256 max (probably limit in UI too)
34+
requestType,
35+
sectionId: 0, // FIXME this shouldn't be a requirement of the API surely
36+
};
37+
console.log("Performing imaging request", imagingRequestBody);
38+
39+
const response = await fetch(await octopusImagingBaseUrl, {
40+
method: "PUT",
41+
headers: {
42+
"Content-Type": "application/json",
43+
"x-api-key": await octopusImagingApiKey,
44+
"x-api-secret": await octopusImagingApiSecret,
45+
},
46+
body: JSON.stringify(imagingRequestBody),
47+
});
48+
49+
if (!response.ok) {
50+
const errorMessage = `Octopus Imaging request failed: ${response.status} ${response.statusText}`;
51+
console.error(errorMessage, await response.text());
52+
throw Error(errorMessage);
53+
}
54+
55+
const body = await response.json();
56+
57+
console.log("Imaging request successful", body);
58+
59+
return body.id;
60+
};

database-bridge-lambda/src/sql/Item.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import {
88
} from "shared/graphql/graphql";
99
import { Sql } from "shared/database/types";
1010
import { Range } from "shared/types/grafanaType";
11+
import { performImagingRequest } from "../imagingRequestCallout";
12+
import { IMAGING_REQUEST_ITEM_TYPE } from "shared/octopusImaging";
13+
import { ItemWithParsedPayload } from "client/src/types/ItemWithParsedPayload";
1114

1215
const fragmentIndividualMentionsToMentionHandles = (
1316
sql: Sql,
@@ -61,10 +64,39 @@ export const createItem = async (
6164
args: { input: CreateItemInput },
6265
userEmail: string
6366
) =>
64-
sql`
67+
sql.begin(async (sql) => {
68+
const insertResult = (await sql`
6569
INSERT INTO "Item" ${sql({ userEmail, ...args.input })}
6670
RETURNING ${fragmentItemFields(sql, userEmail)}
67-
`.then((rows) => rows[0]);
71+
`.then((rows) => rows[0])) as ItemWithParsedPayload;
72+
if (
73+
insertResult.type === IMAGING_REQUEST_ITEM_TYPE &&
74+
insertResult.payload
75+
) {
76+
// if this throws, the SQL transaction should be rolled back
77+
const octopusOrderId = await performImagingRequest(
78+
insertResult,
79+
insertResult.payload
80+
);
81+
82+
const augmentedPayload = {
83+
...insertResult.payload,
84+
octopusOrderId,
85+
};
86+
87+
await sql`
88+
UPDATE "Item"
89+
SET "payload" = ${augmentedPayload}
90+
WHERE "id" = ${insertResult.id}
91+
`;
92+
93+
return {
94+
...insertResult,
95+
payload: augmentedPayload,
96+
};
97+
}
98+
return insertResult;
99+
});
68100

69101
export const editItem = async (
70102
sql: Sql,

0 commit comments

Comments
 (0)