From 8e845cf063ea026001bf45b9c065b4c48177978d Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Tue, 20 Jan 2026 12:58:06 +0000 Subject: [PATCH 01/14] change to create a PR --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 38df7c65e..7c338e977 100644 --- a/README.md +++ b/README.md @@ -138,3 +138,5 @@ Deployments can be made of any [release](https://github.com/NHSDigital/nhs-notif Unless stated otherwise, the codebase is released under the MIT License. This covers both the codebase and any sample code in the documentation. 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/). + +to open a PR From b51d29032e5af594e00e19e3b8b9bfe89ca7758d Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Wed, 21 Jan 2026 14:30:27 +0000 Subject: [PATCH 02/14] add my first EMF metrics to upsert letter lambda --- lambdas/upsert-letter/package.json | 1 + .../src/handler/upsert-handler.ts | 76 ++++++++++++------- package-lock.json | 26 +++++-- 3 files changed, 67 insertions(+), 36 deletions(-) diff --git a/lambdas/upsert-letter/package.json b/lambdas/upsert-letter/package.json index e4858da0d..4cf3d7f38 100644 --- a/lambdas/upsert-letter/package.json +++ b/lambdas/upsert-letter/package.json @@ -7,6 +7,7 @@ "@nhsdigital/nhs-notify-event-schemas-letter-rendering-v1": "npm:@nhsdigital/nhs-notify-event-schemas-letter-rendering@^1.1.5", "@nhsdigital/nhs-notify-event-schemas-supplier-api": "^1.0.8", "@types/aws-lambda": "^8.10.148", + "aws-embedded-metrics": "^4.2.1", "aws-lambda": "^1.0.7", "esbuild": "^0.27.2", "pino": "^9.7.0", diff --git a/lambdas/upsert-letter/src/handler/upsert-handler.ts b/lambdas/upsert-letter/src/handler/upsert-handler.ts index a1b2ea08b..065044cf5 100644 --- a/lambdas/upsert-letter/src/handler/upsert-handler.ts +++ b/lambdas/upsert-letter/src/handler/upsert-handler.ts @@ -19,6 +19,7 @@ import { LetterRequestPreparedEventV2, } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering"; import z from "zod"; +import { metricScope, Unit } from "aws-embedded-metrics"; import { Deps } from "../config/deps"; type SupplierSpec = { supplierId: string; specId: string }; @@ -153,33 +154,50 @@ async function runUpsert( } export default function createUpsertLetterHandler(deps: Deps): SQSHandler { - return async (event: SQSEvent) => { - const batchItemFailures: SQSBatchItemFailure[] = []; - - const tasks = event.Records.map(async (record) => { - try { - const message: string = parseSNSNotification(record); - - const snsEvent = JSON.parse(message); - - const letterEvent: unknown = removeEventBridgeWrapper(snsEvent); - - const type = getType(letterEvent); - - const operation = getOperationFromType(type); - - await runUpsert(operation, letterEvent, deps); - } catch (error) { - deps.logger.error( - { err: error, message: record.body }, - `Error processing upsert of record ${record.messageId}`, - ); - batchItemFailures.push({ itemIdentifier: record.messageId }); - } - }); - - await Promise.all(tasks); - - return { batchItemFailures }; - }; + return metricScope(async (metrics) => { + return async (event: SQSEvent) => { + const batchItemFailures: SQSBatchItemFailure[] = []; + + const tasks = event.Records.map(async (record) => { + try { + const message: string = parseSNSNotification(record); + + const snsEvent = JSON.parse(message); + + const letterEvent: unknown = removeEventBridgeWrapper(snsEvent); + + const type = getType(letterEvent); + + const operation = getOperationFromType(type); + + await runUpsert(operation, letterEvent, deps); + metrics.putDimensions({ + FunctionName: "upsertLambda", + // eslint-disable-next-line sonarjs/pseudo-random + OddOrEven: `${Math.floor(Math.random() * 10) % 2}`, + OperationType: operation.name, + }); + metrics.setProperty("operation", operation.name); + metrics.putMetric("MessagesProcessed", 1, Unit.Count); + } catch (error) { + deps.logger.error( + { err: error, message: record.body }, + `Error processing upsert of record ${record.messageId}`, + ); + metrics.putDimensions({ + FunctionName: "upsertLambda", + // eslint-disable-next-line sonarjs/pseudo-random + OddOrEven: `${Math.floor(Math.random() * 10) % 2}`, + }); + metrics.setProperty("operation", operation.name); + metrics.putMetric("MessageFailed", 1, Unit.Count); + batchItemFailures.push({ itemIdentifier: record.messageId }); + } + }); + + await Promise.all(tasks); + + return { batchItemFailures }; + }; + }); } diff --git a/package-lock.json b/package-lock.json index 64a58b073..f7940dc43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -798,6 +798,7 @@ "@nhsdigital/nhs-notify-event-schemas-letter-rendering-v1": "npm:@nhsdigital/nhs-notify-event-schemas-letter-rendering@^1.1.5", "@nhsdigital/nhs-notify-event-schemas-supplier-api": "^1.0.8", "@types/aws-lambda": "^8.10.148", + "aws-embedded-metrics": "^4.2.1", "aws-lambda": "^1.0.7", "esbuild": "^0.27.2", "pino": "^9.7.0", @@ -3338,6 +3339,12 @@ "node": ">=18" } }, + "node_modules/@datastructures-js/heap": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/@datastructures-js/heap/-/heap-4.3.7.tgz", + "integrity": "sha512-Dx4un7Uj0dVxkfoq4RkpzsY2OrvNJgQYZ3n3UlGdl88RxxdHd7oTi21/l3zoxUUe0sXFuNUrfmWqlHzqnoN6Ug==", + "license": "MIT" + }, "node_modules/@emnapi/core": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", @@ -9107,6 +9114,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws-embedded-metrics": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/aws-embedded-metrics/-/aws-embedded-metrics-4.2.1.tgz", + "integrity": "sha512-uzydBXlGQVTB2sZ9ACCQZM3y0u4wdvxxRKFL9LP6RdfI2GcOrCcAsz65UKQvX9iagxFhah322VvvatgP8E7MIg==", + "license": "Apache-2.0", + "dependencies": { + "@datastructures-js/heap": "^4.0.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/aws-lambda": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/aws-lambda/-/aws-lambda-1.0.7.tgz", @@ -21309,13 +21328,6 @@ "node": ">=18.17" } }, - "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "extraneous": true, - "license": "MIT" - }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", From 676486692a0c3f0ce70ebdce5cca231472677639 Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Wed, 21 Jan 2026 15:01:11 +0000 Subject: [PATCH 03/14] correct handler function and lint errors --- lambdas/upsert-letter/src/handler/upsert-handler.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lambdas/upsert-letter/src/handler/upsert-handler.ts b/lambdas/upsert-letter/src/handler/upsert-handler.ts index 065044cf5..71b276314 100644 --- a/lambdas/upsert-letter/src/handler/upsert-handler.ts +++ b/lambdas/upsert-letter/src/handler/upsert-handler.ts @@ -19,7 +19,7 @@ import { LetterRequestPreparedEventV2, } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering"; import z from "zod"; -import { metricScope, Unit } from "aws-embedded-metrics"; +import { Unit, metricScope } from "aws-embedded-metrics"; import { Deps } from "../config/deps"; type SupplierSpec = { supplierId: string; specId: string }; @@ -154,15 +154,18 @@ async function runUpsert( } export default function createUpsertLetterHandler(deps: Deps): SQSHandler { - return metricScope(async (metrics) => { + return metricScope((metrics) => { return async (event: SQSEvent) => { + console.log("The SQSEvent:", event); const batchItemFailures: SQSBatchItemFailure[] = []; const tasks = event.Records.map(async (record) => { try { const message: string = parseSNSNotification(record); + console.log("the message:", message); const snsEvent = JSON.parse(message); + console.log("the snsEvent:", snsEvent); const letterEvent: unknown = removeEventBridgeWrapper(snsEvent); @@ -189,7 +192,6 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler { // eslint-disable-next-line sonarjs/pseudo-random OddOrEven: `${Math.floor(Math.random() * 10) % 2}`, }); - metrics.setProperty("operation", operation.name); metrics.putMetric("MessageFailed", 1, Unit.Count); batchItemFailures.push({ itemIdentifier: record.messageId }); } From 9e96b04f2bfdcd8cf065dca2e1467c3a3feb4089 Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Wed, 21 Jan 2026 15:26:58 +0000 Subject: [PATCH 04/14] add cloudwatch:PutMetricData permission --- .../components/api/module_lambda_upsert_letter.tf | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/infrastructure/terraform/components/api/module_lambda_upsert_letter.tf b/infrastructure/terraform/components/api/module_lambda_upsert_letter.tf index 201e10186..69974fce9 100644 --- a/infrastructure/terraform/components/api/module_lambda_upsert_letter.tf +++ b/infrastructure/terraform/components/api/module_lambda_upsert_letter.tf @@ -85,4 +85,16 @@ data "aws_iam_policy_document" "upsert_letter_lambda" { module.sqs_letter_updates.sqs_queue_arn ] } + + statement { + sid = "AllowCloudWatchMetrics" + effect = "Allow" + + actions = [ + "cloudwatch:PutMetricData" + ] + + resources = ["*"] + } + } From 53db977ad0324b9e7a4bc5c6fe2563abf5d1c0f5 Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Wed, 21 Jan 2026 15:53:50 +0000 Subject: [PATCH 05/14] set custom namespace --- lambdas/upsert-letter/package.json | 1 + .../src/handler/upsert-handler.ts | 8 + package-lock.json | 542 +++++++++++++++++- 3 files changed, 548 insertions(+), 3 deletions(-) diff --git a/lambdas/upsert-letter/package.json b/lambdas/upsert-letter/package.json index 4cf3d7f38..d0d6eb6ac 100644 --- a/lambdas/upsert-letter/package.json +++ b/lambdas/upsert-letter/package.json @@ -1,5 +1,6 @@ { "dependencies": { + "@aws-sdk/client-cloudwatch": "^3.972.0", "@aws-sdk/client-dynamodb": "^3.858.0", "@aws-sdk/lib-dynamodb": "^3.858.0", "@internal/datastore": "*", diff --git a/lambdas/upsert-letter/src/handler/upsert-handler.ts b/lambdas/upsert-letter/src/handler/upsert-handler.ts index 71b276314..521ebd722 100644 --- a/lambdas/upsert-letter/src/handler/upsert-handler.ts +++ b/lambdas/upsert-letter/src/handler/upsert-handler.ts @@ -20,8 +20,14 @@ import { } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering"; import z from "zod"; import { Unit, metricScope } from "aws-embedded-metrics"; +import { + CloudWatchClient, + PutMetricDataCommand, +} from "@aws-sdk/client-dynamodb"; import { Deps } from "../config/deps"; +const cwClient = new CloudWatchClient({ region: "eu-west-2" }); + type SupplierSpec = { supplierId: string; specId: string }; type PreparedEvents = LetterRequestPreparedEventV2 | LetterRequestPreparedEvent; type UpsertOperation = { @@ -158,6 +164,7 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler { return async (event: SQSEvent) => { console.log("The SQSEvent:", event); const batchItemFailures: SQSBatchItemFailure[] = []; + metrics.setNamespace("vlasis_upsertLetter"); const tasks = event.Records.map(async (record) => { try { @@ -181,6 +188,7 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler { OperationType: operation.name, }); metrics.setProperty("operation", operation.name); + metrics.putMetric("MessagesProcessed", 1, Unit.Count); } catch (error) { deps.logger.error( diff --git a/package-lock.json b/package-lock.json index f7940dc43..0f8aeb60d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -791,6 +791,7 @@ "name": "nhs-notify-supplier-api-upsert-letter", "version": "0.0.1", "dependencies": { + "@aws-sdk/client-cloudwatch": "^3.972.0", "@aws-sdk/client-dynamodb": "^3.858.0", "@aws-sdk/lib-dynamodb": "^3.858.0", "@internal/datastore": "*", @@ -1192,6 +1193,514 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-cloudwatch": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch/-/client-cloudwatch-3.972.0.tgz", + "integrity": "sha512-6LiuuuIRQ0faBz94dKTL/zrmIMSljQo0JgZ7pNjIIcxXrsQzMTr+AWhvFPlJPorI6h3XcQ5Vkyz20GODyn6TYw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.972.0", + "@aws-sdk/credential-provider-node": "3.972.0", + "@aws-sdk/middleware-host-header": "3.972.0", + "@aws-sdk/middleware-logger": "3.972.0", + "@aws-sdk/middleware-recursion-detection": "3.972.0", + "@aws-sdk/middleware-user-agent": "3.972.0", + "@aws-sdk/region-config-resolver": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@aws-sdk/util-endpoints": "3.972.0", + "@aws-sdk/util-user-agent-browser": "3.972.0", + "@aws-sdk/util-user-agent-node": "3.972.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.6", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-compression": "^4.3.22", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.7", + "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.22", + "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/client-sso": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.972.0.tgz", + "integrity": "sha512-5qw6qLiRE4SUiz0hWy878dSR13tSVhbTWhsvFT8mGHe37NRRiaobm5MA2sWD0deRAuO98djSiV+dhWXa1xIFNw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.972.0", + "@aws-sdk/middleware-host-header": "3.972.0", + "@aws-sdk/middleware-logger": "3.972.0", + "@aws-sdk/middleware-recursion-detection": "3.972.0", + "@aws-sdk/middleware-user-agent": "3.972.0", + "@aws-sdk/region-config-resolver": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@aws-sdk/util-endpoints": "3.972.0", + "@aws-sdk/util-user-agent-browser": "3.972.0", + "@aws-sdk/util-user-agent-node": "3.972.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.6", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.7", + "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.22", + "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/core": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz", + "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.972.0", + "@aws-sdk/xml-builder": "3.972.0", + "@smithy/core": "^3.20.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.0.tgz", + "integrity": "sha512-kKHoNv+maHlPQOAhYamhap0PObd16SAb3jwaY0KYgNTiSbeXlbGUZPLioo9oA3wU10zItJzx83ClU7d7h40luA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.0.tgz", + "integrity": "sha512-xzEi81L7I5jGUbpmqEHCe7zZr54hCABdj4H+3LzktHYuovV/oqnvoDdvZpGFR0e/KAw1+PL38NbGrpG30j6qlA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.0.tgz", + "integrity": "sha512-ruhAMceUIq2aknFd3jhWxmO0P0Efab5efjyIXOkI9i80g+zDY5VekeSxfqRKStEEJSKSCHDLQuOu0BnAn4Rzew==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/credential-provider-env": "3.972.0", + "@aws-sdk/credential-provider-http": "3.972.0", + "@aws-sdk/credential-provider-login": "3.972.0", + "@aws-sdk/credential-provider-process": "3.972.0", + "@aws-sdk/credential-provider-sso": "3.972.0", + "@aws-sdk/credential-provider-web-identity": "3.972.0", + "@aws-sdk/nested-clients": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.0.tgz", + "integrity": "sha512-SsrsFJsEYAJHO4N/r2P0aK6o8si6f1lprR+Ej8J731XJqTckSGs/HFHcbxOyW/iKt+LNUvZa59/VlJmjhF4bEQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/nested-clients": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.0.tgz", + "integrity": "sha512-wwJDpEGl6+sOygic8QKu0OHVB8SiodqF1fr5jvUlSFfS6tJss/E9vBc2aFjl7zI6KpAIYfIzIgM006lRrZtWCQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.972.0", + "@aws-sdk/credential-provider-http": "3.972.0", + "@aws-sdk/credential-provider-ini": "3.972.0", + "@aws-sdk/credential-provider-process": "3.972.0", + "@aws-sdk/credential-provider-sso": "3.972.0", + "@aws-sdk/credential-provider-web-identity": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.0.tgz", + "integrity": "sha512-nmzYhamLDJ8K+v3zWck79IaKMc350xZnWsf/GeaXO6E3MewSzd3lYkTiMi7lEp3/UwDm9NHfPguoPm+mhlSWQQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.0.tgz", + "integrity": "sha512-6mYyfk1SrMZ15cH9T53yAF4YSnvq4yU1Xlgm3nqV1gZVQzmF5kr4t/F3BU3ygbvzi4uSwWxG3I3TYYS5eMlAyg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.972.0", + "@aws-sdk/core": "3.972.0", + "@aws-sdk/token-providers": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.0.tgz", + "integrity": "sha512-vsJXBGL8H54kz4T6do3p5elATj5d1izVGUXMluRJntm9/I0be/zUYtdd4oDTM2kSUmd4Zhyw3fMQ9lw7CVhd4A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/nested-clients": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.0.tgz", + "integrity": "sha512-3eztFI6F9/eHtkIaWKN3nT+PM+eQ6p1MALDuNshFk323ixuCZzOOVT8oUqtZa30Z6dycNXJwhlIq7NhUVFfimw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.972.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.0.tgz", + "integrity": "sha512-ZvdyVRwzK+ra31v1pQrgbqR/KsLD+wwJjHgko6JfoKUBIcEfAwJzQKO6HspHxdHWTVUz6MgvwskheR/TTYZl2g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.972.0", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.0.tgz", + "integrity": "sha512-F2SmUeO+S6l1h6dydNet3BQIk173uAkcfU1HDkw/bUdRLAnh15D3HP9vCZ7oCPBNcdEICbXYDmx0BR9rRUHGlQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.972.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.0.tgz", + "integrity": "sha512-kFHQm2OCBJCzGWRafgdWHGFjitUXY/OxXngymcX4l8CiyiNDZB27HDDBg2yLj3OUJc4z4fexLMmP8r9vgag19g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@aws-sdk/util-endpoints": "3.972.0", + "@smithy/core": "^3.20.6", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/nested-clients": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.972.0.tgz", + "integrity": "sha512-QGlbnuGzSQJVG6bR9Qw6G0Blh6abFR4VxNa61ttMbzy9jt28xmk2iGtrYLrQPlCCPhY6enHqjTWm3n3LOb0wAw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.972.0", + "@aws-sdk/middleware-host-header": "3.972.0", + "@aws-sdk/middleware-logger": "3.972.0", + "@aws-sdk/middleware-recursion-detection": "3.972.0", + "@aws-sdk/middleware-user-agent": "3.972.0", + "@aws-sdk/region-config-resolver": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@aws-sdk/util-endpoints": "3.972.0", + "@aws-sdk/util-user-agent-browser": "3.972.0", + "@aws-sdk/util-user-agent-node": "3.972.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.6", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.7", + "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.22", + "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.0.tgz", + "integrity": "sha512-JyOf+R/6vJW8OEVFCAyzEOn2reri/Q+L0z9zx4JQSKWvTmJ1qeFO25sOm8VIfB8URKhfGRTQF30pfYaH2zxt/A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.972.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/token-providers": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.972.0.tgz", + "integrity": "sha512-kWlXG+y5nZhgXGEtb72Je+EvqepBPs8E3vZse//1PYLWs2speFqbGE/ywCXmzEJgHgVqSB/u/lqBvs5WlYmSqQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/nested-clients": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/types": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", + "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/util-endpoints": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", + "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.972.0", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.0.tgz", + "integrity": "sha512-eOLdkQyoRbDgioTS3Orr7iVsVEutJyMZxvyZ6WAF95IrF0kfWx5Rd/KXnfbnG/VKa2CvjZiitWfouLzfVEyvJA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.972.0", + "@smithy/types": "^4.12.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.0.tgz", + "integrity": "sha512-GOy+AiSrE9kGiojiwlZvVVSXwylu4+fmP0MJfvras/MwP09RB/YtQuOVR1E0fKQc6OMwaTNBjgAbOEhxuWFbAw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/xml-builder": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz", + "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-dynamodb": { "version": "3.971.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.971.0.tgz", @@ -6166,9 +6675,9 @@ } }, "node_modules/@smithy/core": { - "version": "3.20.7", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.7.tgz", - "integrity": "sha512-aO7jmh3CtrmPsIJxUwYIzI5WVlMK8BMCPQ4D4nTzqTqBhbzvxHNzBMGcEg13yg/z9R2Qsz49NUFl0F0lVbTVFw==", + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.21.0.tgz", + "integrity": "sha512-bg2TfzgsERyETAxc/Ims/eJX8eAnIeTi4r4LHpMpfF/2NyO6RsWis0rjKcCPaGksljmOb23BZRiCeT/3NvwkXw==", "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^4.2.9", @@ -6371,6 +6880,27 @@ "node": ">=18.0.0" } }, + "node_modules/@smithy/middleware-compression": { + "version": "4.3.25", + "resolved": "https://registry.npmjs.org/@smithy/middleware-compression/-/middleware-compression-4.3.25.tgz", + "integrity": "sha512-YWL0Mk2WCjHYbSWuD85oTlB0T7nm+YWTHJWMEac+1Uy1NtnHqnFnX4YvRGG9S6Q++aXaVZdbff8uXl2dC3RLfg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.21.0", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "fflate": "0.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/middleware-content-length": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", @@ -13392,6 +13922,12 @@ } } }, + "node_modules/fflate": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.1.tgz", + "integrity": "sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ==", + "license": "MIT" + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", From 2f1513d6aeb1b4e71fe7887a93b3667577d7d855 Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Wed, 21 Jan 2026 15:59:08 +0000 Subject: [PATCH 06/14] remove cloudwatch client declaration --- lambdas/upsert-letter/src/handler/upsert-handler.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lambdas/upsert-letter/src/handler/upsert-handler.ts b/lambdas/upsert-letter/src/handler/upsert-handler.ts index 521ebd722..e4a28ac90 100644 --- a/lambdas/upsert-letter/src/handler/upsert-handler.ts +++ b/lambdas/upsert-letter/src/handler/upsert-handler.ts @@ -20,14 +20,8 @@ import { } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering"; import z from "zod"; import { Unit, metricScope } from "aws-embedded-metrics"; -import { - CloudWatchClient, - PutMetricDataCommand, -} from "@aws-sdk/client-dynamodb"; import { Deps } from "../config/deps"; -const cwClient = new CloudWatchClient({ region: "eu-west-2" }); - type SupplierSpec = { supplierId: string; specId: string }; type PreparedEvents = LetterRequestPreparedEventV2 | LetterRequestPreparedEvent; type UpsertOperation = { From c1a9bc21b5438d448d36fc52f90b1f6173e39490 Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Thu, 22 Jan 2026 13:23:02 +0000 Subject: [PATCH 07/14] remove oddOrEven from dimensions --- lambdas/upsert-letter/src/handler/upsert-handler.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lambdas/upsert-letter/src/handler/upsert-handler.ts b/lambdas/upsert-letter/src/handler/upsert-handler.ts index e4a28ac90..8d3c049dc 100644 --- a/lambdas/upsert-letter/src/handler/upsert-handler.ts +++ b/lambdas/upsert-letter/src/handler/upsert-handler.ts @@ -173,14 +173,12 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler { const type = getType(letterEvent); const operation = getOperationFromType(type); - - await runUpsert(operation, letterEvent, deps); metrics.putDimensions({ FunctionName: "upsertLambda", - // eslint-disable-next-line sonarjs/pseudo-random - OddOrEven: `${Math.floor(Math.random() * 10) % 2}`, OperationType: operation.name, }); + + await runUpsert(operation, letterEvent, deps); metrics.setProperty("operation", operation.name); metrics.putMetric("MessagesProcessed", 1, Unit.Count); @@ -191,8 +189,6 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler { ); metrics.putDimensions({ FunctionName: "upsertLambda", - // eslint-disable-next-line sonarjs/pseudo-random - OddOrEven: `${Math.floor(Math.random() * 10) % 2}`, }); metrics.putMetric("MessageFailed", 1, Unit.Count); batchItemFailures.push({ itemIdentifier: record.messageId }); From 9f9d1cfa0387bc12122b141d0591b3608db06a82 Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Thu, 22 Jan 2026 15:27:59 +0000 Subject: [PATCH 08/14] add oddOrEven from dimensions --- lambdas/upsert-letter/src/handler/upsert-handler.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lambdas/upsert-letter/src/handler/upsert-handler.ts b/lambdas/upsert-letter/src/handler/upsert-handler.ts index 8d3c049dc..38966136f 100644 --- a/lambdas/upsert-letter/src/handler/upsert-handler.ts +++ b/lambdas/upsert-letter/src/handler/upsert-handler.ts @@ -176,6 +176,8 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler { metrics.putDimensions({ FunctionName: "upsertLambda", OperationType: operation.name, + // eslint-disable-next-line sonarjs/pseudo-random + OddOrEven: `${Math.floor(Math.random() * 10) % 2}`, }); await runUpsert(operation, letterEvent, deps); @@ -189,6 +191,8 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler { ); metrics.putDimensions({ FunctionName: "upsertLambda", + // eslint-disable-next-line sonarjs/pseudo-random + OddOrEven: `${Math.floor(Math.random() * 10) % 2}`, }); metrics.putMetric("MessageFailed", 1, Unit.Count); batchItemFailures.push({ itemIdentifier: record.messageId }); From b2f012b106ff658e2f6f6028e2f5fd352d1bdd1a Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Fri, 23 Jan 2026 09:18:28 +0000 Subject: [PATCH 09/14] remove operationType from messageProcessed --- lambdas/upsert-letter/src/handler/upsert-handler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lambdas/upsert-letter/src/handler/upsert-handler.ts b/lambdas/upsert-letter/src/handler/upsert-handler.ts index 38966136f..df8e6f368 100644 --- a/lambdas/upsert-letter/src/handler/upsert-handler.ts +++ b/lambdas/upsert-letter/src/handler/upsert-handler.ts @@ -175,7 +175,6 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler { const operation = getOperationFromType(type); metrics.putDimensions({ FunctionName: "upsertLambda", - OperationType: operation.name, // eslint-disable-next-line sonarjs/pseudo-random OddOrEven: `${Math.floor(Math.random() * 10) % 2}`, }); From 32b30a113bb0fd913944ebfc5c8fb335268e4340 Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Fri, 23 Jan 2026 16:48:39 +0000 Subject: [PATCH 10/14] log environment variables --- lambdas/upsert-letter/src/handler/upsert-handler.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lambdas/upsert-letter/src/handler/upsert-handler.ts b/lambdas/upsert-letter/src/handler/upsert-handler.ts index df8e6f368..2af591a7f 100644 --- a/lambdas/upsert-letter/src/handler/upsert-handler.ts +++ b/lambdas/upsert-letter/src/handler/upsert-handler.ts @@ -156,6 +156,10 @@ async function runUpsert( export default function createUpsertLetterHandler(deps: Deps): SQSHandler { return metricScope((metrics) => { return async (event: SQSEvent) => { + console.log( + "the environment variables:", + JSON.stringify(process.env, null, 2), + ); console.log("The SQSEvent:", event); const batchItemFailures: SQSBatchItemFailure[] = []; metrics.setNamespace("vlasis_upsertLetter"); From ba37764e3e6082b2cff5cdb004a44ec78f30594e Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Mon, 26 Jan 2026 13:45:10 +0000 Subject: [PATCH 11/14] log letterEvent and operation --- README.md | 2 - .../api/module_lambda_upsert_letter.tf | 12 - lambdas/upsert-letter/package.json | 1 - .../src/handler/upsert-handler.ts | 8 +- package-lock.json | 536 ------------------ 5 files changed, 5 insertions(+), 554 deletions(-) diff --git a/README.md b/README.md index 7c338e977..38df7c65e 100644 --- a/README.md +++ b/README.md @@ -138,5 +138,3 @@ Deployments can be made of any [release](https://github.com/NHSDigital/nhs-notif Unless stated otherwise, the codebase is released under the MIT License. This covers both the codebase and any sample code in the documentation. 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/). - -to open a PR diff --git a/infrastructure/terraform/components/api/module_lambda_upsert_letter.tf b/infrastructure/terraform/components/api/module_lambda_upsert_letter.tf index 69974fce9..201e10186 100644 --- a/infrastructure/terraform/components/api/module_lambda_upsert_letter.tf +++ b/infrastructure/terraform/components/api/module_lambda_upsert_letter.tf @@ -85,16 +85,4 @@ data "aws_iam_policy_document" "upsert_letter_lambda" { module.sqs_letter_updates.sqs_queue_arn ] } - - statement { - sid = "AllowCloudWatchMetrics" - effect = "Allow" - - actions = [ - "cloudwatch:PutMetricData" - ] - - resources = ["*"] - } - } diff --git a/lambdas/upsert-letter/package.json b/lambdas/upsert-letter/package.json index d0d6eb6ac..4cf3d7f38 100644 --- a/lambdas/upsert-letter/package.json +++ b/lambdas/upsert-letter/package.json @@ -1,6 +1,5 @@ { "dependencies": { - "@aws-sdk/client-cloudwatch": "^3.972.0", "@aws-sdk/client-dynamodb": "^3.858.0", "@aws-sdk/lib-dynamodb": "^3.858.0", "@internal/datastore": "*", diff --git a/lambdas/upsert-letter/src/handler/upsert-handler.ts b/lambdas/upsert-letter/src/handler/upsert-handler.ts index 2af591a7f..b5b964aa9 100644 --- a/lambdas/upsert-letter/src/handler/upsert-handler.ts +++ b/lambdas/upsert-letter/src/handler/upsert-handler.ts @@ -162,7 +162,9 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler { ); console.log("The SQSEvent:", event); const batchItemFailures: SQSBatchItemFailure[] = []; - metrics.setNamespace("vlasis_upsertLetter"); + metrics.setNamespace( + process.env.AWS_LAMBDA_FUNCTION_NAME || "upsertletter", + ); const tasks = event.Records.map(async (record) => { try { @@ -177,8 +179,9 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler { const type = getType(letterEvent); const operation = getOperationFromType(type); + console.log("letterEvent: ", letterEvent); + console.log("operation: ", operation); metrics.putDimensions({ - FunctionName: "upsertLambda", // eslint-disable-next-line sonarjs/pseudo-random OddOrEven: `${Math.floor(Math.random() * 10) % 2}`, }); @@ -193,7 +196,6 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler { `Error processing upsert of record ${record.messageId}`, ); metrics.putDimensions({ - FunctionName: "upsertLambda", // eslint-disable-next-line sonarjs/pseudo-random OddOrEven: `${Math.floor(Math.random() * 10) % 2}`, }); diff --git a/package-lock.json b/package-lock.json index 0f8aeb60d..221eebc49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -791,7 +791,6 @@ "name": "nhs-notify-supplier-api-upsert-letter", "version": "0.0.1", "dependencies": { - "@aws-sdk/client-cloudwatch": "^3.972.0", "@aws-sdk/client-dynamodb": "^3.858.0", "@aws-sdk/lib-dynamodb": "^3.858.0", "@internal/datastore": "*", @@ -1193,514 +1192,6 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-cloudwatch": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch/-/client-cloudwatch-3.972.0.tgz", - "integrity": "sha512-6LiuuuIRQ0faBz94dKTL/zrmIMSljQo0JgZ7pNjIIcxXrsQzMTr+AWhvFPlJPorI6h3XcQ5Vkyz20GODyn6TYw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.972.0", - "@aws-sdk/credential-provider-node": "3.972.0", - "@aws-sdk/middleware-host-header": "3.972.0", - "@aws-sdk/middleware-logger": "3.972.0", - "@aws-sdk/middleware-recursion-detection": "3.972.0", - "@aws-sdk/middleware-user-agent": "3.972.0", - "@aws-sdk/region-config-resolver": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "3.972.0", - "@aws-sdk/util-user-agent-node": "3.972.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-compression": "^4.3.22", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/client-sso": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.972.0.tgz", - "integrity": "sha512-5qw6qLiRE4SUiz0hWy878dSR13tSVhbTWhsvFT8mGHe37NRRiaobm5MA2sWD0deRAuO98djSiV+dhWXa1xIFNw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.972.0", - "@aws-sdk/middleware-host-header": "3.972.0", - "@aws-sdk/middleware-logger": "3.972.0", - "@aws-sdk/middleware-recursion-detection": "3.972.0", - "@aws-sdk/middleware-user-agent": "3.972.0", - "@aws-sdk/region-config-resolver": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "3.972.0", - "@aws-sdk/util-user-agent-node": "3.972.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/core": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz", - "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@aws-sdk/xml-builder": "3.972.0", - "@smithy/core": "^3.20.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.0.tgz", - "integrity": "sha512-kKHoNv+maHlPQOAhYamhap0PObd16SAb3jwaY0KYgNTiSbeXlbGUZPLioo9oA3wU10zItJzx83ClU7d7h40luA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.0.tgz", - "integrity": "sha512-xzEi81L7I5jGUbpmqEHCe7zZr54hCABdj4H+3LzktHYuovV/oqnvoDdvZpGFR0e/KAw1+PL38NbGrpG30j6qlA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.0.tgz", - "integrity": "sha512-ruhAMceUIq2aknFd3jhWxmO0P0Efab5efjyIXOkI9i80g+zDY5VekeSxfqRKStEEJSKSCHDLQuOu0BnAn4Rzew==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/credential-provider-env": "3.972.0", - "@aws-sdk/credential-provider-http": "3.972.0", - "@aws-sdk/credential-provider-login": "3.972.0", - "@aws-sdk/credential-provider-process": "3.972.0", - "@aws-sdk/credential-provider-sso": "3.972.0", - "@aws-sdk/credential-provider-web-identity": "3.972.0", - "@aws-sdk/nested-clients": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.0.tgz", - "integrity": "sha512-SsrsFJsEYAJHO4N/r2P0aK6o8si6f1lprR+Ej8J731XJqTckSGs/HFHcbxOyW/iKt+LNUvZa59/VlJmjhF4bEQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/nested-clients": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.0.tgz", - "integrity": "sha512-wwJDpEGl6+sOygic8QKu0OHVB8SiodqF1fr5jvUlSFfS6tJss/E9vBc2aFjl7zI6KpAIYfIzIgM006lRrZtWCQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.972.0", - "@aws-sdk/credential-provider-http": "3.972.0", - "@aws-sdk/credential-provider-ini": "3.972.0", - "@aws-sdk/credential-provider-process": "3.972.0", - "@aws-sdk/credential-provider-sso": "3.972.0", - "@aws-sdk/credential-provider-web-identity": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.0.tgz", - "integrity": "sha512-nmzYhamLDJ8K+v3zWck79IaKMc350xZnWsf/GeaXO6E3MewSzd3lYkTiMi7lEp3/UwDm9NHfPguoPm+mhlSWQQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.0.tgz", - "integrity": "sha512-6mYyfk1SrMZ15cH9T53yAF4YSnvq4yU1Xlgm3nqV1gZVQzmF5kr4t/F3BU3ygbvzi4uSwWxG3I3TYYS5eMlAyg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.972.0", - "@aws-sdk/core": "3.972.0", - "@aws-sdk/token-providers": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.0.tgz", - "integrity": "sha512-vsJXBGL8H54kz4T6do3p5elATj5d1izVGUXMluRJntm9/I0be/zUYtdd4oDTM2kSUmd4Zhyw3fMQ9lw7CVhd4A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/nested-clients": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.0.tgz", - "integrity": "sha512-3eztFI6F9/eHtkIaWKN3nT+PM+eQ6p1MALDuNshFk323ixuCZzOOVT8oUqtZa30Z6dycNXJwhlIq7NhUVFfimw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.0.tgz", - "integrity": "sha512-ZvdyVRwzK+ra31v1pQrgbqR/KsLD+wwJjHgko6JfoKUBIcEfAwJzQKO6HspHxdHWTVUz6MgvwskheR/TTYZl2g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.0.tgz", - "integrity": "sha512-F2SmUeO+S6l1h6dydNet3BQIk173uAkcfU1HDkw/bUdRLAnh15D3HP9vCZ7oCPBNcdEICbXYDmx0BR9rRUHGlQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.0.tgz", - "integrity": "sha512-kFHQm2OCBJCzGWRafgdWHGFjitUXY/OxXngymcX4l8CiyiNDZB27HDDBg2yLj3OUJc4z4fexLMmP8r9vgag19g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@smithy/core": "^3.20.6", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/nested-clients": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.972.0.tgz", - "integrity": "sha512-QGlbnuGzSQJVG6bR9Qw6G0Blh6abFR4VxNa61ttMbzy9jt28xmk2iGtrYLrQPlCCPhY6enHqjTWm3n3LOb0wAw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.972.0", - "@aws-sdk/middleware-host-header": "3.972.0", - "@aws-sdk/middleware-logger": "3.972.0", - "@aws-sdk/middleware-recursion-detection": "3.972.0", - "@aws-sdk/middleware-user-agent": "3.972.0", - "@aws-sdk/region-config-resolver": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "3.972.0", - "@aws-sdk/util-user-agent-node": "3.972.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.0.tgz", - "integrity": "sha512-JyOf+R/6vJW8OEVFCAyzEOn2reri/Q+L0z9zx4JQSKWvTmJ1qeFO25sOm8VIfB8URKhfGRTQF30pfYaH2zxt/A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/token-providers": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.972.0.tgz", - "integrity": "sha512-kWlXG+y5nZhgXGEtb72Je+EvqepBPs8E3vZse//1PYLWs2speFqbGE/ywCXmzEJgHgVqSB/u/lqBvs5WlYmSqQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/nested-clients": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/types": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", - "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/util-endpoints": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", - "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.0.tgz", - "integrity": "sha512-eOLdkQyoRbDgioTS3Orr7iVsVEutJyMZxvyZ6WAF95IrF0kfWx5Rd/KXnfbnG/VKa2CvjZiitWfouLzfVEyvJA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.0.tgz", - "integrity": "sha512-GOy+AiSrE9kGiojiwlZvVVSXwylu4+fmP0MJfvras/MwP09RB/YtQuOVR1E0fKQc6OMwaTNBjgAbOEhxuWFbAw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz", - "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@aws-sdk/client-dynamodb": { "version": "3.971.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.971.0.tgz", @@ -6880,27 +6371,6 @@ "node": ">=18.0.0" } }, - "node_modules/@smithy/middleware-compression": { - "version": "4.3.25", - "resolved": "https://registry.npmjs.org/@smithy/middleware-compression/-/middleware-compression-4.3.25.tgz", - "integrity": "sha512-YWL0Mk2WCjHYbSWuD85oTlB0T7nm+YWTHJWMEac+1Uy1NtnHqnFnX4YvRGG9S6Q++aXaVZdbff8uXl2dC3RLfg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.21.0", - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "fflate": "0.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@smithy/middleware-content-length": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", @@ -13922,12 +13392,6 @@ } } }, - "node_modules/fflate": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.1.tgz", - "integrity": "sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ==", - "license": "MIT" - }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", From 0233eb8162670de64bb2af59d4cfbc2ffe75600c Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Mon, 26 Jan 2026 13:52:43 +0000 Subject: [PATCH 12/14] fix linting errors --- lambdas/upsert-letter/src/handler/upsert-handler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambdas/upsert-letter/src/handler/upsert-handler.ts b/lambdas/upsert-letter/src/handler/upsert-handler.ts index b5b964aa9..d69d636b4 100644 --- a/lambdas/upsert-letter/src/handler/upsert-handler.ts +++ b/lambdas/upsert-letter/src/handler/upsert-handler.ts @@ -179,8 +179,8 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler { const type = getType(letterEvent); const operation = getOperationFromType(type); - console.log("letterEvent: ", letterEvent); - console.log("operation: ", operation); + console.log("letterEvent:", letterEvent); + console.log("operation:", operation); metrics.putDimensions({ // eslint-disable-next-line sonarjs/pseudo-random OddOrEven: `${Math.floor(Math.random() * 10) % 2}`, From 6198984823a40f3feee9d50946c4d8230c198e6e Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Mon, 26 Jan 2026 15:20:58 +0000 Subject: [PATCH 13/14] emit metrics per supplier and with correct count --- .../src/handler/upsert-handler.ts | 62 +++++++++++-------- tests/resources/prepared-letter.json | 3 +- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/lambdas/upsert-letter/src/handler/upsert-handler.ts b/lambdas/upsert-letter/src/handler/upsert-handler.ts index d69d636b4..e79ee6bbc 100644 --- a/lambdas/upsert-letter/src/handler/upsert-handler.ts +++ b/lambdas/upsert-letter/src/handler/upsert-handler.ts @@ -19,7 +19,7 @@ import { LetterRequestPreparedEventV2, } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering"; import z from "zod"; -import { Unit, metricScope } from "aws-embedded-metrics"; +import { MetricsLogger, Unit, metricScope } from "aws-embedded-metrics"; import { Deps } from "../config/deps"; type SupplierSpec = { supplierId: string; specId: string }; @@ -153,58 +153,70 @@ async function runUpsert( throw new Error("No matching schema for received message"); } +async function emitMetrics( + metrics: MetricsLogger, + successMetrics: Map, + failMetrics: Map, +) { + metrics.setNamespace(process.env.AWS_LAMBDA_FUNCTION_NAME || `upsertLetter`); + // emit success metrics + for (const [supplier, count] of Object.entries(successMetrics)) { + metrics.putDimensions({ + Supplier: supplier, + }); + metrics.putMetric("MessagesProcessed", count, Unit.Count); + } + // emit failure metrics + for (const [supplier, count] of Object.entries(failMetrics)) { + metrics.putDimensions({ + Supplier: supplier, + }); + metrics.putMetric("MessageFailed", count, Unit.Count); + } +} + export default function createUpsertLetterHandler(deps: Deps): SQSHandler { return metricScope((metrics) => { return async (event: SQSEvent) => { - console.log( - "the environment variables:", - JSON.stringify(process.env, null, 2), - ); - console.log("The SQSEvent:", event); const batchItemFailures: SQSBatchItemFailure[] = []; - metrics.setNamespace( - process.env.AWS_LAMBDA_FUNCTION_NAME || "upsertletter", - ); + const perSupplierSuccess: Map = new Map(); + const perSupplierFailure: Map = new Map(); const tasks = event.Records.map(async (record) => { + let supplier = "unknown"; try { const message: string = parseSNSNotification(record); - console.log("the message:", message); - const snsEvent = JSON.parse(message); - console.log("the snsEvent:", snsEvent); - + supplier = snsEvent.data.supplierId || "unknown"; const letterEvent: unknown = removeEventBridgeWrapper(snsEvent); - const type = getType(letterEvent); const operation = getOperationFromType(type); - console.log("letterEvent:", letterEvent); - console.log("operation:", operation); metrics.putDimensions({ - // eslint-disable-next-line sonarjs/pseudo-random - OddOrEven: `${Math.floor(Math.random() * 10) % 2}`, + supplier: snsEvent.data.supplierId || "unknown", }); await runUpsert(operation, letterEvent, deps); - metrics.setProperty("operation", operation.name); - metrics.putMetric("MessagesProcessed", 1, Unit.Count); + perSupplierSuccess.set( + supplier, + (perSupplierSuccess.get(supplier) || 0) + 1, + ); } catch (error) { deps.logger.error( { err: error, message: record.body }, `Error processing upsert of record ${record.messageId}`, ); - metrics.putDimensions({ - // eslint-disable-next-line sonarjs/pseudo-random - OddOrEven: `${Math.floor(Math.random() * 10) % 2}`, - }); - metrics.putMetric("MessageFailed", 1, Unit.Count); + perSupplierFailure.set( + supplier, + (perSupplierFailure.get(supplier) || 0) + 1, + ); batchItemFailures.push({ itemIdentifier: record.messageId }); } }); await Promise.all(tasks); + await emitMetrics(metrics, perSupplierSuccess, perSupplierFailure); return { batchItemFailures }; }; diff --git a/tests/resources/prepared-letter.json b/tests/resources/prepared-letter.json index 50f6eba6c..f9c1fea55 100644 --- a/tests/resources/prepared-letter.json +++ b/tests/resources/prepared-letter.json @@ -4,13 +4,14 @@ "clientId": "testClientId", "createdAt": "2025-08-28T08:45:00.000Z", "domainId": "letter1", - "letterVariantId": "lv1", + "letterVariantId": "notify-standard", "pageCount": 2, "requestId": "0o5Fs0EELR0fUjHjbCnEtdUwQe3", "requestItemId": "0o5Fs0EELR0fUjHjbCnEtdUwQe4", "requestItemPlanId": "0o5Fs0EELR0fUjHjbCnEtdUwQe5", "sha256Hash": "3a7bd3e2360a3d29eea436fcfb7e44c735d117c8f2f1d2d1e4f6e8f7e6e8f7e6", "status": "PREPARED", + "supplierId": "testSupplierId", "templateId": "template_123", "url": "s3://nhs-820178564574-eu-west-2-pr280-supapi-test-letters/letter1.png" }, From e965c60277419eb5a336be9c0df31b95805e8c6c Mon Sep 17 00:00:00 2001 From: vlasis-perdikidis Date: Mon, 26 Jan 2026 15:58:04 +0000 Subject: [PATCH 14/14] correct emitMetrics function --- .../src/handler/upsert-handler.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lambdas/upsert-letter/src/handler/upsert-handler.ts b/lambdas/upsert-letter/src/handler/upsert-handler.ts index e79ee6bbc..32d66ee6b 100644 --- a/lambdas/upsert-letter/src/handler/upsert-handler.ts +++ b/lambdas/upsert-letter/src/handler/upsert-handler.ts @@ -160,14 +160,14 @@ async function emitMetrics( ) { metrics.setNamespace(process.env.AWS_LAMBDA_FUNCTION_NAME || `upsertLetter`); // emit success metrics - for (const [supplier, count] of Object.entries(successMetrics)) { + for (const [supplier, count] of successMetrics) { metrics.putDimensions({ Supplier: supplier, }); metrics.putMetric("MessagesProcessed", count, Unit.Count); } // emit failure metrics - for (const [supplier, count] of Object.entries(failMetrics)) { + for (const [supplier, count] of failMetrics) { metrics.putDimensions({ Supplier: supplier, }); @@ -175,6 +175,13 @@ async function emitMetrics( } } +function getSupplierId(snsEvent: any): string { + if (snsEvent && snsEvent.data && snsEvent.data.supplierId) { + return snsEvent.data.supplierId; + } + return "unknown"; +} + export default function createUpsertLetterHandler(deps: Deps): SQSHandler { return metricScope((metrics) => { return async (event: SQSEvent) => { @@ -187,14 +194,11 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler { try { const message: string = parseSNSNotification(record); const snsEvent = JSON.parse(message); - supplier = snsEvent.data.supplierId || "unknown"; + supplier = getSupplierId(snsEvent); const letterEvent: unknown = removeEventBridgeWrapper(snsEvent); const type = getType(letterEvent); const operation = getOperationFromType(type); - metrics.putDimensions({ - supplier: snsEvent.data.supplierId || "unknown", - }); await runUpsert(operation, letterEvent, deps); @@ -216,8 +220,8 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler { }); await Promise.all(tasks); - await emitMetrics(metrics, perSupplierSuccess, perSupplierFailure); + await emitMetrics(metrics, perSupplierSuccess, perSupplierFailure); return { batchItemFailures }; }; });