Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0699b82
emit metrics for supplier-allocator
Vlasis-Perdikidis Apr 17, 2026
3e690b4
ensure that clientId and campaignId is never undefined when emiting m…
Vlasis-Perdikidis Apr 17, 2026
e26fa97
use distinct the name for the new metric
Vlasis-Perdikidis Apr 21, 2026
3b75bb4
get unit tests to pass
Vlasis-Perdikidis Apr 21, 2026
6e722e4
add a dummy supplier
Vlasis-Perdikidis Apr 21, 2026
e5377f4
remove comments
Vlasis-Perdikidis Apr 22, 2026
c2aa97e
rename metric key variable in function
Vlasis-Perdikidis May 6, 2026
ff924d8
expose groupId in allocate-handler lambda. Expose groupId and correct…
Vlasis-Perdikidis May 7, 2026
4792824
correct check file format
Vlasis-Perdikidis May 7, 2026
2614e3f
correct markdown errors
Vlasis-Perdikidis May 7, 2026
881969f
correct markdown errors
Vlasis-Perdikidis May 7, 2026
5fbc192
update lock file
Vlasis-Perdikidis May 7, 2026
eff46fc
merge with main
Vlasis-Perdikidis May 7, 2026
0373f49
fix lint and unit test errors
Vlasis-Perdikidis May 7, 2026
2433a4a
Merge branch 'main' into feature/CCM-17047-addClientCampaignToDashboards
Vlasis-Perdikidis May 8, 2026
2155558
fix audit errors
Vlasis-Perdikidis May 8, 2026
a3cccc2
Revert "fix audit errors"
Vlasis-Perdikidis May 8, 2026
d89211f
address PR review comments
Vlasis-Perdikidis May 12, 2026
5cf4f9d
add new line in group-id.ts
Vlasis-Perdikidis May 12, 2026
4165a12
update package-lock file
Vlasis-Perdikidis May 12, 2026
58107bd
fix linting and pact test error
Vlasis-Perdikidis May 12, 2026
a6812f4
fix failing pact test
Vlasis-Perdikidis May 12, 2026
f3ca016
fix build?
Vlasis-Perdikidis May 13, 2026
4b4ee83
merge with main
Vlasis-Perdikidis May 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
ENVIRONMENT=$ENV_NAME
API_KEY=
HEADERAUTH=
PR_NUMBER=prxx # remove if needs to run against main
NHSD_APIM_TOKEN=
PROXY_NAME=
GITHUB_TOKEN= # Your github Personal Access Token (PAT)


# The variables below are used for End to End tests
PROXY_NAME= # information about the proxy name can be found in the tests/e2e-tests/README.md



# * nhs-notify-supplier--internal-dev--nhs-notify-supplier
# * nhs-notify-supplier--internal-dev--nhs-notify-supplier-PR-XX
# * nhs-notify-supplier--ref--nhs-notify-supplier -- ref env
Expand Down
49 changes: 46 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<!-- vale off -->
# NHS Notify Supplier API

[![1. CI/CD pull request](https://github.com/NHSDigital/nhs-notify-supplier-api/actions/workflows/cicd-1-pull-request.yaml/badge.svg)](https://github.com/NHSDigital/nhs-notify-supplier-api/actions/workflows/cicd-1-pull-request.yaml)
Expand Down Expand Up @@ -75,9 +76,49 @@ New developers of the NHS Notify Supplier API should understand the below.

#### Prerequisites and Configuration

- Utilised the devcontainer, for pre reqs and configuration.
- You should open in a devcontainer or a Github workspaces.
- By default it will run `make config` when the container is first setup
- Create the file `~/.aws/config` with the following contents:

```dsconfig
[profile ]
region = eu-west-2
output = json

[profile supplier-dev]
sso_start_url = https://d-9c67018f89.awsapps.com/start#/
sso_region = eu-west-2
sso_account_id = 820178564574
sso_role_name = nhs-notify-bc-developer
region = eu-west-2
output = json

[profile supplier-nonprod]
sso_start_url = https://d-9c67018f89.awsapps.com/start#/
sso_region = eu-west-2
sso_account_id = 885964308133
sso_role_name = nhs-notify-bc-developer
region = eu-west-2
output = json
```

- In your `~/.bashrc` or `~/.zshrc` add the export `export AWS_PROFILE=supplier-dev`, or whichever profile you need
- In the project's root directory create an `.env` file based on the `.env.template` file and fill variables as needed.
- Create the file `~/.npmrc` with the contents:

```dsconfig
# Authenticate to GitHub Packages for github.com
//npm.pkg.github.com/:_authToken=<Insert your Github PAT (Personal Access Token)>


# Package is scoped under @org, set registry for that scope
@nhsdigital:registry=https://npm.pkg.github.com
```

- Install `node` (to run `npm install` and build the project)
- Install `aws cli` to be able to connect to AWS (needed for some tests)
- If AWS CLI calls are blocked by a firewall (e.g. Zscaler), you need to add the custom certificates in the location `/scripts/devcontainer/custom-ca-certs`
- Install `docker` or `Rancher` for containerisation
- You should open in a devcontainer or a Github workspaces. (In VSCode -> Open Command Palet -> "Dev containers: rebuild without cache and reopen in container")
- By default it will run `make config` when the container is first setup

##### SDKs

Expand Down Expand Up @@ -151,3 +192,5 @@ Import the files into postman
Select a target environment in postman
Run the collection
The collections must be kept in sync manually

<!-- vale on -->
7 changes: 7 additions & 0 deletions internal/helpers/src/group-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function formatGroupId(
clientId: string,
campaignId = "unknown",
safeTemplateId = "unknown",
): string {
return `${clientId}_${campaignId}_${safeTemplateId}`;
}
1 change: 1 addition & 0 deletions internal/helpers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { default as $Environment } from "./environment";
export * from "./id-ref";
export * from "./logger";
export * from "./metrics";
export { default as formatGroupId } from "./group-id";
2 changes: 1 addition & 1 deletion lambdas/supplier-allocator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@
"typecheck": "tsc --noEmit"
},
"version": "0.0.1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ function createPreparedV1Event(
requestItemId: "requestItem1",
requestItemPlanId: "requestItemPlan1",
clientId: "client1",
campaignId: "campaign1",
templateId: "template1",
campaignId: overrides.campaignId ?? "campaign1",
templateId: overrides.templateId ?? "template1",
url: overrides.url ?? "s3://letterDataBucket/letter1.pdf",
sha256Hash:
"3a7bd3e2360a3d29eea436fcfb7e44c735d117c8f2f1d2d1e4f6e8f7e6e8f7e6",
Expand Down Expand Up @@ -230,6 +230,40 @@ describe("createSupplierAllocatorHandler", () => {
});
});

test("parses SNS notification and sends message to SQS queue for v2 event without a campaignId and templateId", async () => {
const preparedEvent = createPreparedV2Event({
campaignId: "",
templateId: "",
});
const evt: SQSEvent = createSQSEvent([
createSqsRecord("msg1", JSON.stringify(preparedEvent)),
]);

setupDefaultMocks();
process.env.UPSERT_LETTERS_QUEUE_URL = "https://sqs.test.queue";

const handler = createSupplierAllocatorHandler(mockedDeps);
const result = await handler(evt, {} as any, {} as any);

expect(result).toBeDefined();
if (!result) throw new Error("expected BatchResponse, got void");

expect(result.batchItemFailures).toHaveLength(0);

expect(mockSqsClient.send).toHaveBeenCalledTimes(1);
const sendCall = (mockSqsClient.send as jest.Mock).mock.calls[0][0];
expect(sendCall).toBeInstanceOf(SendMessageCommand);

const messageBody = JSON.parse(sendCall.input.MessageBody);
expect(messageBody.letterEvent).toEqual(preparedEvent);
expect(messageBody.supplierSpec).toEqual({
supplierId: "supplier1",
specId: "spec1",
priority: 1,
billingId: "billing1",
});
});

test("parses SNS notification and sends message to SQS queue for v1 event", async () => {
const preparedEvent = createPreparedV1Event();

Expand Down
39 changes: 35 additions & 4 deletions lambdas/supplier-allocator/src/handler/allocate-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import {
import { LetterRequestPreparedEventV2 } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering";
import z from "zod";
import { Unit } from "aws-embedded-metrics";
import { MetricEntry, MetricStatus, buildEMFObject } from "@internal/helpers";
import {
MetricEntry,
MetricStatus,
buildEMFObject,
formatGroupId,
} from "@internal/helpers";
import {
getSupplierAllocationsForVolumeGroup,
getSupplierDetails,
Expand Down Expand Up @@ -144,6 +149,29 @@ function emitMetrics(
}
}

function emitDataMetrics(
letterEvent: PreparedEvents,
supplier: string,
metricKey: string,
deps: Deps,
) {
const namespace = "supplier-allocator";
const { campaignId, clientId, templateId } = letterEvent.data;
const dimensions: Record<string, string> = {
Supplier: supplier,
ClientId: clientId,
CampaignId: campaignId || "unknown",
TemplateId: templateId || "unknown",
GroupId: formatGroupId(clientId, campaignId, templateId),
};
const metric: MetricEntry = {
key: metricKey,
value: 1,
unit: Unit.Count,
};
deps.logger.info(buildEMFObject(namespace, dimensions, metric));
}

export default function createSupplierAllocatorHandler(deps: Deps): SQSHandler {
return async (event: SQSEvent) => {
const batchItemFailures: SQSBatchItemFailure[] = [];
Expand All @@ -154,7 +182,9 @@ export default function createSupplierAllocatorHandler(deps: Deps): SQSHandler {
let supplier = "unknown";
let priority = "unknown";
try {
const letterEvent: unknown = JSON.parse(record.body);
const letterEvent: PreparedEvents = JSON.parse(
record.body,
) as PreparedEvents;

deps.logger.info({
description: "Extracted letter event",
Expand All @@ -163,8 +193,8 @@ export default function createSupplierAllocatorHandler(deps: Deps): SQSHandler {

validateType(letterEvent);

const supplierSpec = getSupplier(letterEvent as PreparedEvents, deps);
await getSupplierFromConfig(letterEvent as PreparedEvents, deps);
const supplierSpec = getSupplier(letterEvent, deps);
await getSupplierFromConfig(letterEvent, deps);

supplier = supplierSpec.supplierId;
priority = String(supplierSpec.priority);
Expand Down Expand Up @@ -199,6 +229,7 @@ export default function createSupplierAllocatorHandler(deps: Deps): SQSHandler {
);

incrementMetric(perAllocationSuccess, supplier, priority);
emitDataMetrics(letterEvent, supplier, "extra_data_dimensions", deps);
} catch (error) {
deps.logger.error({
description: "Error processing allocation of record",
Expand Down
Loading
Loading