Skip to content

Commit 5a2a2d4

Browse files
committed
Merge remote-tracking branch 'origin/main' into feature/CCM-12934-Create-Letter-Queue
2 parents ecbcc60 + c7a8a06 commit 5a2a2d4

16 files changed

Lines changed: 672 additions & 1689 deletions

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ This repository documents the Supplier API specification and provides an SDK wit
3434
- [CI (Automatic)](#ci-automatic)
3535
- [CD (Manual)](#cd-manual)
3636
- [Licence](#licence)
37+
- [Postman](#postman)
3738

3839
## API Consumers - Getting Started
3940

@@ -138,3 +139,15 @@ Deployments can be made of any [release](https://github.com/NHSDigital/nhs-notif
138139
Unless stated otherwise, the codebase is released under the MIT License. This covers both the codebase and any sample code in the documentation.
139140

140141
Any HTML or Markdown documentation is [© Crown Copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/) and available under the terms of the [Open Government Licence v3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).
142+
143+
## Postman
144+
145+
Included in this repo are postman collections that allows the user to interact with the sandbox APIs.
146+
147+
To use the collections:
148+
149+
Download the json files located in the postman directory
150+
Import the files into postman
151+
Select a target environment in postman
152+
Run the collection
153+
The collections must be kept in sync manually

internal/helpers/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"dependencies": {
3+
"aws-embedded-metrics": "^4.2.1",
34
"pino": "^10.3.0",
45
"zod": "^4.1.11"
56
},

internal/helpers/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export * from "./version";
88
export { default as $Environment } from "./environment";
99
export * from "./id-ref";
1010
export * from "./logger";
11+
export * from "./metrics";

internal/helpers/src/metrics.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { MetricsLogger, Unit } from "aws-embedded-metrics";
2+
3+
export function emitForSingleSupplier(
4+
metrics: MetricsLogger,
5+
functionName: string,
6+
supplierId: string,
7+
count: number,
8+
message: string,
9+
dimensions?: Record<string, string>,
10+
) {
11+
metrics.setNamespace(process.env.AWS_LAMBDA_FUNCTION_NAME || functionName);
12+
metrics.putDimensions({
13+
...dimensions,
14+
Supplier: supplierId,
15+
});
16+
metrics.putMetric(message, count, Unit.Count);
17+
}
18+
19+
export enum MetricStatus {
20+
Success = "success",
21+
Failure = "failure",
22+
}
23+
24+
export interface MetricEntry {
25+
key: string;
26+
value: number;
27+
unit: Unit;
28+
}
29+
30+
// build EMF object
31+
export function buildEMFObject(
32+
functionName: string,
33+
dimensions: Record<string, string>,
34+
metric: MetricEntry,
35+
) {
36+
const namespace = process.env.AWS_LAMBDA_FUNCTION_NAME || functionName;
37+
return {
38+
LogGroup: namespace,
39+
ServiceName: namespace,
40+
...dimensions,
41+
_aws: {
42+
Timestamp: Date.now(),
43+
CloudWatchMetrics: [
44+
{
45+
Namespace: namespace,
46+
Dimensions: [[...Object.keys(dimensions), "ServiceName", "LogGroup"]],
47+
Metrics: [
48+
{ Name: metric.key, Value: metric.value, Unit: metric.unit },
49+
],
50+
},
51+
],
52+
},
53+
[metric.key]: metric.value,
54+
};
55+
}

lambdas/api-handler/src/handlers/amendment-event-transformer.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { SQSBatchItemFailure, SQSEvent, SQSHandler } from "aws-lambda";
22
import { PublishCommand } from "@aws-sdk/client-sns";
33
import { LetterEvent } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/events/letter-events";
44
import { mapLetterToCloudEvent } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/events/letter-mapper";
5+
import { Unit } from "aws-embedded-metrics";
6+
import pino from "pino";
7+
import { MetricEntry, MetricStatus, buildEMFObject } from "@internal/helpers";
58
import {
69
UpdateLetterCommand,
710
UpdateLetterCommandSchema,
@@ -39,6 +42,11 @@ export default function createTransformAmendmentEventHandler(
3942
messageId: message.messageId,
4043
correlationId: message.messageAttributes.CorrelationId.stringValue,
4144
});
45+
emitSuccessMetrics(
46+
updateLetterCommand.supplierId,
47+
updateLetterCommand.status,
48+
deps.logger,
49+
);
4250
} catch (error) {
4351
deps.logger.error({
4452
description: "Error processing letter status update",
@@ -52,7 +60,7 @@ export default function createTransformAmendmentEventHandler(
5260
});
5361

5462
await Promise.all(tasks);
55-
63+
emitFailedItems(batchItemFailures, deps.logger);
5664
return { batchItemFailures };
5765
};
5866
}
@@ -66,3 +74,43 @@ function buildSnsCommand(
6674
Message: JSON.stringify(letterEvent),
6775
});
6876
}
77+
78+
function emitSuccessMetrics(
79+
supplierId: string,
80+
status: string,
81+
logger: pino.Logger,
82+
) {
83+
const dimensions: Record<string, string> = {
84+
supplier: supplierId,
85+
status,
86+
};
87+
const metric: MetricEntry = {
88+
key: MetricStatus.Success,
89+
value: 1,
90+
unit: Unit.Count,
91+
};
92+
const emf = buildEMFObject("amendment-event-transformer", dimensions, metric);
93+
logger.info(emf);
94+
}
95+
96+
function emitFailedItems(
97+
batchFailures: SQSBatchItemFailure[],
98+
logger: pino.Logger,
99+
) {
100+
for (const item of batchFailures) {
101+
const dimensions: Record<string, string> = {
102+
identifier: item.itemIdentifier,
103+
};
104+
const metric: MetricEntry = {
105+
key: MetricStatus.Failure,
106+
value: 1,
107+
unit: Unit.Count,
108+
};
109+
const emf = buildEMFObject(
110+
"amendment-event-transformer",
111+
dimensions,
112+
metric,
113+
);
114+
logger.info(emf);
115+
}
116+
}

lambdas/api-handler/src/handlers/get-letter-data.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { APIGatewayProxyHandler } from "aws-lambda";
22
import { MetricsLogger, metricScope } from "aws-embedded-metrics";
3+
import { MetricStatus, emitForSingleSupplier } from "@internal/helpers";
34
import { assertNotEmpty } from "../utils/validation";
45
import { extractCommonIds } from "../utils/common-ids";
56
import { ApiErrorDetail } from "../contracts/errors";
67
import { processError } from "../mappers/error-mapper";
78
import ValidationError from "../errors/validation-error";
89
import { getLetterDataUrl } from "../services/letter-operations";
910
import type { Deps } from "../config/deps";
10-
import { MetricStatus, emitForSingleSupplier } from "../utils/metrics";
1111

1212
export default function createGetLetterDataHandler(
1313
deps: Deps,

lambdas/api-handler/src/handlers/get-letter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { APIGatewayProxyHandler } from "aws-lambda";
22
import { MetricsLogger, metricScope } from "aws-embedded-metrics";
3+
import { MetricStatus, emitForSingleSupplier } from "@internal/helpers";
34
import { assertNotEmpty } from "../utils/validation";
45
import { extractCommonIds } from "../utils/common-ids";
56
import ValidationError from "../errors/validation-error";
@@ -8,7 +9,6 @@ import { getLetterById } from "../services/letter-operations";
89
import { processError } from "../mappers/error-mapper";
910
import { mapToGetLetterResponse } from "../mappers/letter-mapper";
1011
import { Deps } from "../config/deps";
11-
import { MetricStatus, emitForSingleSupplier } from "../utils/metrics";
1212

1313
// Get letter data
1414
export default function createGetLetterHandler(

lambdas/api-handler/src/handlers/get-letters.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
} from "aws-lambda";
55
import { Logger } from "pino";
66
import { MetricsLogger, metricScope } from "aws-embedded-metrics";
7+
import { MetricStatus, emitForSingleSupplier } from "@internal/helpers";
78
import { getLettersForSupplier } from "../services/letter-operations";
89
import { extractCommonIds } from "../utils/common-ids";
910
import { requireEnvVar } from "../utils/validation";
@@ -12,9 +13,7 @@ import { processError } from "../mappers/error-mapper";
1213
import ValidationError from "../errors/validation-error";
1314
import { mapToGetLettersResponse } from "../mappers/letter-mapper";
1415
import type { Deps } from "../config/deps";
15-
import { MetricStatus, emitForSingleSupplier } from "../utils/metrics";
1616

17-
// List letters Handlers
1817
// The endpoint should only return pending letters for now
1918
const status = "PENDING";
2019

lambdas/api-handler/src/handlers/patch-letter.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { APIGatewayProxyHandler } from "aws-lambda";
22
import { MetricsLogger, Unit, metricScope } from "aws-embedded-metrics";
3+
import { MetricStatus } from "@internal/helpers";
34
import { enqueueLetterUpdateRequests } from "../services/letter-operations";
45
import {
56
PatchLetterRequest,
@@ -13,7 +14,6 @@ import { assertNotEmpty } from "../utils/validation";
1314
import { extractCommonIds } from "../utils/common-ids";
1415
import { mapToUpdateCommand } from "../mappers/letter-mapper";
1516
import type { Deps } from "../config/deps";
16-
import { MetricStatus } from "../utils/metrics";
1717

1818
export default function createPatchLetterHandler(
1919
deps: Deps,
@@ -56,6 +56,7 @@ export default function createPatchLetterHandler(
5656
try {
5757
patchLetterRequest = PatchLetterRequestSchema.parse(JSON.parse(body));
5858
} catch (error) {
59+
emitErrorMetric(metrics, supplierId);
5960
const typedError =
6061
error instanceof Error
6162
? new ValidationError(ApiErrorDetail.InvalidRequestBody, {
@@ -79,6 +80,7 @@ export default function createPatchLetterHandler(
7980
);
8081

8182
if (updateLetterCommand.id !== letterId) {
83+
emitErrorMetric(metrics, supplierId);
8284
throw new ValidationError(
8385
ApiErrorDetail.InvalidRequestLetterIdsMismatch,
8486
);
@@ -100,12 +102,16 @@ export default function createPatchLetterHandler(
100102
body: "",
101103
};
102104
} catch (error) {
103-
metrics.putDimensions({
104-
supplier: supplierId,
105-
});
106-
metrics.putMetric(MetricStatus.Success, 1, Unit.Count);
105+
emitErrorMetric(metrics, supplierId);
107106
return processError(error, commonIds.value.correlationId, deps.logger);
108107
}
109108
};
110109
});
111110
}
111+
112+
function emitErrorMetric(metrics: MetricsLogger, supplierId: string) {
113+
metrics.putDimensions({
114+
supplier: supplierId,
115+
});
116+
metrics.putMetric(MetricStatus.Failure, 1, Unit.Count);
117+
}

0 commit comments

Comments
 (0)