From 366cd756ac5d50f025ef8fae557224e545cffcda Mon Sep 17 00:00:00 2001 From: Angela Cooney Date: Tue, 24 Sep 2024 11:17:41 -0400 Subject: [PATCH] CMDCT-3960: creating new dynamo table for measures with PKs that can be queried instead of scan all (#2431) --- serverless-compose.yml | 2 + services/app-api/handlers/coreSets/create.ts | 16 ++-- services/app-api/handlers/coreSets/delete.ts | 26 +++--- services/app-api/handlers/coreSets/get.ts | 6 +- .../handlers/coreSets/tests/create.test.ts | 5 -- .../handlers/coreSets/tests/delete.test.ts | 17 ++-- .../handlers/coreSets/tests/get.test.ts | 12 --- .../handlers/coreSets/tests/update.test.ts | 12 +-- services/app-api/handlers/coreSets/update.ts | 4 +- .../handlers/dynamoUtils/createCompoundKey.ts | 13 --- .../tests/createCompoundKey.test.ts | 20 ----- services/app-api/handlers/measures/create.ts | 8 +- services/app-api/handlers/measures/delete.ts | 8 +- services/app-api/handlers/measures/get.ts | 23 +++--- .../handlers/measures/tests/create.test.ts | 10 +-- .../handlers/measures/tests/delete.test.ts | 18 ++--- .../handlers/measures/tests/get.test.ts | 20 +++-- .../handlers/measures/tests/update.test.ts | 23 +++--- services/app-api/handlers/measures/update.ts | 10 +-- services/app-api/handlers/rate/get.ts | 2 +- .../handlers/rate/rateCalculations.test.ts | 12 +-- .../app-api/handlers/rate/rateCalculations.ts | 6 +- services/app-api/serverless.yml | 2 + services/app-api/storage/table.test.ts | 10 +-- services/app-api/storage/table.ts | 14 ++-- services/app-api/types.ts | 8 +- services/app-api/utils/parseParameters.ts | 17 +++- services/database/README.md | 5 ++ .../database/scripts/transformMeasureTable.ts | 81 +++++++++++++++++++ services/database/serverless.yml | 24 +++++- services/uploads/serverless.yml | 4 +- services/uploads/src/dynamoSync/handler.js | 2 +- 32 files changed, 232 insertions(+), 208 deletions(-) delete mode 100644 services/app-api/handlers/dynamoUtils/createCompoundKey.ts delete mode 100644 services/app-api/handlers/dynamoUtils/tests/createCompoundKey.test.ts create mode 100644 services/database/scripts/transformMeasureTable.ts diff --git a/serverless-compose.yml b/serverless-compose.yml index 105c7a6704..dbd587575b 100644 --- a/serverless-compose.yml +++ b/serverless-compose.yml @@ -11,6 +11,7 @@ services: params: CoreSetTableName: ${database.CoreSetTableName} MeasureTableName: ${database.MeasureTableName} + MeasureTable: ${database.MeasureTable} RateTableName: ${database.RateTableName} app-api: @@ -19,6 +20,7 @@ services: CoreSetTableName: ${database.CoreSetTableName} CoreSetTableStreamArn: ${database.CoreSetTableStreamArn} MeasureTableName: ${database.MeasureTableName} + MeasureTable: ${database.MeasureTable} MeasureTableStreamArn: ${database.MeasureTableStreamArn} RateTableName: ${database.RateTableName} RateTableStreamArn: ${database.RateTableStreamArn} diff --git a/services/app-api/handlers/coreSets/create.ts b/services/app-api/handlers/coreSets/create.ts index 519f5ff30f..6972a5481a 100644 --- a/services/app-api/handlers/coreSets/create.ts +++ b/services/app-api/handlers/coreSets/create.ts @@ -1,7 +1,6 @@ import handler from "../../libs/handler-lib"; import dynamoDb from "../../libs/dynamodb-lib"; import { getCoreSet } from "./get"; -import { createCoreSetKey } from "../dynamoUtils/createCompoundKey"; import { MeasureMetaData, measures } from "../dynamoUtils/measureList"; import { hasRolePermissions, @@ -42,18 +41,17 @@ export const createCoreSet = handler(async (event, context) => { body: Errors.CORESET_ALREADY_EXISTS, }; } - const dynamoKey = createCoreSetKey({ state, year, coreSet }); await createDependentMeasures( state, - parseInt(year), + year, coreSet as Types.CoreSetAbbr, type ); // filter out qualifier and account for autocomplete measures on creation let autoCompletedMeasures = 0; - const measuresLengthWithoutQualifiers = measures[parseInt(year)].filter( + const measuresLengthWithoutQualifiers = measures[year].filter( (measure: MeasureMetaData) => { if (measure.autocompleteOnCreation && measure.type === type) { autoCompletedMeasures++; @@ -70,9 +68,9 @@ export const createCoreSet = handler(async (event, context) => { const params = { TableName: process.env.coreSetTableName!, Item: { - compoundKey: dynamoKey, + compoundKey: `${state}${year}${coreSet}`, state: state, - year: parseInt(year), + year: year, coreSet: coreSet, createdAt: Date.now(), lastAltered: Date.now(), @@ -104,12 +102,10 @@ const createDependentMeasures = async ( for await (const measure of filteredMeasures) { // The State Year and ID are all part of the path const measureId = measure["measure"]; - // Dynamo only accepts one row as a key, so we are using a combination for the dynamoKey - const dynamoKey = `${state}${year}${coreSet}${measureId}`; const params = { - TableName: process.env.measureTableName!, + TableName: process.env.measureTable!, Item: { - compoundKey: dynamoKey, + compoundKey: `${state}${year}${coreSet}`, state: state, year: year, coreSet: coreSet, diff --git a/services/app-api/handlers/coreSets/delete.ts b/services/app-api/handlers/coreSets/delete.ts index dbb7fde4e9..07c765a26a 100644 --- a/services/app-api/handlers/coreSets/delete.ts +++ b/services/app-api/handlers/coreSets/delete.ts @@ -1,7 +1,5 @@ import handler from "../../libs/handler-lib"; import dynamoDb from "../../libs/dynamodb-lib"; -import { createCoreSetKey } from "../dynamoUtils/createCompoundKey"; -import { convertToDynamoExpression } from "../dynamoUtils/convertToDynamoExpressionVars"; import { hasStatePermissions } from "../../libs/authorization"; import { Measure } from "../../types"; import { Errors, StatusCodes } from "../../utils/constants/constants"; @@ -24,17 +22,16 @@ export const deleteCoreSet = handler(async (event, context) => { }; } - const dynamoKey = createCoreSetKey({ state, year, coreSet }); const params = { TableName: process.env.coreSetTableName!, Key: { - compoundKey: dynamoKey, + compoundKey: `${state}${year}${coreSet}`, coreSet: coreSet, }, }; await dynamoDb.delete(params); - await deleteDependentMeasures(state, parseInt(year), coreSet); + await deleteDependentMeasures(state, year, coreSet); return { status: StatusCodes.SUCCESS, @@ -51,12 +48,11 @@ const deleteDependentMeasures = async ( const Items = measures; for await (const { measure } of Items) { - const dynamoKey = `${state}${year}${coreSet}${measure}`; const params = { - TableName: process.env.measureTableName!, + TableName: process.env.measureTable!, Key: { - compoundKey: dynamoKey, - coreSet: coreSet, + compoundKey: `${state}${year}${coreSet}`, + measure: measure, }, }; @@ -66,12 +62,12 @@ const deleteDependentMeasures = async ( const getMeasures = async (state: string, year: number, coreSet: string) => { const params = { - TableName: process.env.measureTableName!, - ...convertToDynamoExpression( - { state: state, year: year, coreSet: coreSet }, - "list" - ), + TableName: process.env.measureTable!, + KeyConditionExpression: "compoundKey = :compoundKey", + ExpressionAttributeValues: { + ":compoundKey": `${state}${year}${coreSet}`, + }, }; - const queryValue = await dynamoDb.scanAll(params); + const queryValue = await dynamoDb.queryAll(params); return queryValue; }; diff --git a/services/app-api/handlers/coreSets/get.ts b/services/app-api/handlers/coreSets/get.ts index 379555c7aa..203b0e8d59 100644 --- a/services/app-api/handlers/coreSets/get.ts +++ b/services/app-api/handlers/coreSets/get.ts @@ -2,7 +2,6 @@ import handler from "../../libs/handler-lib"; import dynamoDb from "../../libs/dynamodb-lib"; import { updateCoreSetProgress } from "../../libs/updateCoreProgress"; import { convertToDynamoExpression } from "../dynamoUtils/convertToDynamoExpressionVars"; -import { createCoreSetKey } from "../dynamoUtils/createCompoundKey"; import { createCoreSet } from "./create"; import { hasRolePermissions, @@ -41,7 +40,7 @@ export const coreSetList = handler(async (event, context) => { ...convertToDynamoExpression( { state: state, - year: parseInt(year), + year: year, }, "list" ), @@ -121,11 +120,10 @@ export const getCoreSet = handler(async (event, context) => { } } // if not state user, can safely assume admin type user due to baseline handler protections - const dynamoKey = createCoreSetKey({ state, year, coreSet }); const params = { TableName: process.env.coreSetTableName!, Key: { - compoundKey: dynamoKey, + compoundKey: `${state}${year}${coreSet}`, coreSet: coreSet, }, }; diff --git a/services/app-api/handlers/coreSets/tests/create.test.ts b/services/app-api/handlers/coreSets/tests/create.test.ts index e9b62d56a4..62b04f29f7 100644 --- a/services/app-api/handlers/coreSets/tests/create.test.ts +++ b/services/app-api/handlers/coreSets/tests/create.test.ts @@ -23,11 +23,6 @@ jest.mock("../../../libs/authorization", () => ({ hasStatePermissions: () => mockHasStatePermissions(), })); -jest.mock("../../dynamoUtils/createCompoundKey", () => ({ - __esModule: true, - createCoreSetKey: jest.fn().mockReturnValue("FL2021ACSFUA-AD"), -})); - jest.mock("../get", () => ({ __esModule: true, getCoreSet: jest.fn(), diff --git a/services/app-api/handlers/coreSets/tests/delete.test.ts b/services/app-api/handlers/coreSets/tests/delete.test.ts index b34f9b8810..3b0130f9bc 100644 --- a/services/app-api/handlers/coreSets/tests/delete.test.ts +++ b/services/app-api/handlers/coreSets/tests/delete.test.ts @@ -6,7 +6,7 @@ import { CoreSetAbbr } from "../../../types"; jest.mock("../../../libs/dynamodb-lib", () => ({ delete: jest.fn(), - scanAll: jest.fn(), + queryAll: jest.fn(), })); const mockHasStatePermissions = jest.fn(); @@ -15,11 +15,6 @@ jest.mock("../../../libs/authorization", () => ({ hasStatePermissions: () => mockHasStatePermissions(), })); -jest.mock("../../dynamoUtils/createCompoundKey", () => ({ - __esModule: true, - createCoreSetKey: jest.fn().mockReturnValue("FL2020ACSFUA-AD"), -})); - jest.mock("../../../libs/updateCoreProgress", () => ({ __esModule: true, updateCoreSetProgress: jest.fn(), @@ -29,7 +24,7 @@ const event = { ...testEvent }; describe("Testing Delete Core Set Functions", () => { beforeEach(() => { - (db.scanAll as jest.Mock).mockReset(); + (db.queryAll as jest.Mock).mockReset(); (db.delete as jest.Mock).mockReset(); mockHasStatePermissions.mockImplementation(() => true); event.pathParameters = { @@ -67,7 +62,7 @@ describe("Testing Delete Core Set Functions", () => { }); test("Test deleteCoreSet with associated measures", async () => { - (db.scanAll as jest.Mock).mockReturnValue([ + (db.queryAll as jest.Mock).mockReturnValue([ testMeasure, testMeasure, testMeasure, @@ -75,16 +70,16 @@ describe("Testing Delete Core Set Functions", () => { await deleteCoreSet(event, null); - expect(db.scanAll).toHaveBeenCalled(); + expect(db.queryAll).toHaveBeenCalled(); expect(db.delete).toHaveBeenCalledTimes(4); }); test("Test deleteCoreSet with no associated measures", async () => { - (db.scanAll as jest.Mock).mockReturnValue([]); + (db.queryAll as jest.Mock).mockReturnValue([]); await deleteCoreSet(event, null); - expect(db.scanAll).toHaveBeenCalled(); + expect(db.queryAll).toHaveBeenCalled(); expect(db.delete).toHaveBeenCalled(); }); }); diff --git a/services/app-api/handlers/coreSets/tests/get.test.ts b/services/app-api/handlers/coreSets/tests/get.test.ts index 6479f39f92..9f2df1f8c3 100644 --- a/services/app-api/handlers/coreSets/tests/get.test.ts +++ b/services/app-api/handlers/coreSets/tests/get.test.ts @@ -4,7 +4,6 @@ import { testEvent } from "../../../test-util/testEvents"; import dynamodbLib from "../../../libs/dynamodb-lib"; import { updateCoreSetProgress } from "../../../libs/updateCoreProgress"; -import { createCoreSetKey } from "../../dynamoUtils/createCompoundKey"; import { CoreSetAbbr } from "../../../types"; import { createCoreSet } from "../create"; import { Errors, StatusCodes } from "../../../utils/constants/constants"; @@ -35,11 +34,6 @@ jest.mock("../../../libs/updateCoreProgress", () => ({ updateCoreSetProgress: jest.fn(), })); -jest.mock("../../dynamoUtils/createCompoundKey", () => ({ - __esModule: true, - createCoreSetKey: jest.fn().mockReturnValue("FL2020ACSFUA-AD"), -})); - jest.mock("../../dynamoUtils/convertToDynamoExpressionVars", () => ({ __esModule: true, convertToDynamoExpression: jest.fn().mockReturnValue({ testValue: "test" }), @@ -96,12 +90,6 @@ describe("Test Get Core Set Functions", () => { await getCoreSet(event, null); expect(dynamodbLib.get).toHaveBeenCalled(); - expect(createCoreSetKey).toHaveBeenCalled(); - expect(createCoreSetKey).toHaveBeenCalledWith({ - state: "AL", - year: "2021", - coreSet: "ACS", - }); }); test("Test coreSetList unauthorized user attempt (incorrect state)", async () => { diff --git a/services/app-api/handlers/coreSets/tests/update.test.ts b/services/app-api/handlers/coreSets/tests/update.test.ts index d4638cd7bd..f2fe786ad4 100644 --- a/services/app-api/handlers/coreSets/tests/update.test.ts +++ b/services/app-api/handlers/coreSets/tests/update.test.ts @@ -1,7 +1,6 @@ import dynamodbLib from "../../../libs/dynamodb-lib"; import { testEvent } from "../../../test-util/testEvents"; import { convertToDynamoExpression } from "../../dynamoUtils/convertToDynamoExpressionVars"; -import { createCoreSetKey } from "../../dynamoUtils/createCompoundKey"; import { editCoreSet } from "../update"; import { Errors, StatusCodes } from "../../../utils/constants/constants"; import { CoreSetAbbr } from "../../../types"; @@ -17,11 +16,6 @@ jest.mock("../../../libs/authorization", () => ({ hasStatePermissions: () => mockHasStatePermissions(), })); -jest.mock("../../dynamoUtils/createCompoundKey", () => ({ - __esModule: true, - createCoreSetKey: jest.fn().mockReturnValue("FL2020ACSFUA-AD"), -})); - jest.mock("../../dynamoUtils/convertToDynamoExpressionVars", () => ({ __esModule: true, convertToDynamoExpression: jest.fn().mockReturnValue({ testValue: "test" }), @@ -73,7 +67,6 @@ describe("Testing Updating Core Set Functions", () => { Date.now = jest.fn(() => 20); const res = await editCoreSet(event, null); - expect(createCoreSetKey).toHaveBeenCalled(); expect(dynamodbLib.update).toHaveBeenCalled(); expect(convertToDynamoExpression).toHaveBeenCalledWith( { @@ -84,14 +77,13 @@ describe("Testing Updating Core Set Functions", () => { "post" ); expect(res.statusCode).toBe(StatusCodes.SUCCESS); - expect(res.body).toContain("FL2020ACSFUA-AD"); + expect(res.body).toContain("WA2021ACS"); }); test("Test with no user id", async () => { Date.now = jest.fn(() => 20); const res = await editCoreSet(event, null); - expect(createCoreSetKey).toHaveBeenCalled(); expect(dynamodbLib.update).toHaveBeenCalled(); expect(convertToDynamoExpression).toHaveBeenCalledWith( { @@ -102,6 +94,6 @@ describe("Testing Updating Core Set Functions", () => { "post" ); expect(res.statusCode).toBe(StatusCodes.SUCCESS); - expect(res.body).toContain("FL2020ACSFUA-AD"); + expect(res.body).toContain("WA2021ACS"); }); }); diff --git a/services/app-api/handlers/coreSets/update.ts b/services/app-api/handlers/coreSets/update.ts index a5e8c456f0..85b341c900 100644 --- a/services/app-api/handlers/coreSets/update.ts +++ b/services/app-api/handlers/coreSets/update.ts @@ -1,7 +1,6 @@ import handler from "../../libs/handler-lib"; import dynamoDb from "../../libs/dynamodb-lib"; import { convertToDynamoExpression } from "../dynamoUtils/convertToDynamoExpressionVars"; -import { createCoreSetKey } from "../dynamoUtils/createCompoundKey"; import { getUserNameFromJwt, hasStatePermissions, @@ -27,12 +26,11 @@ export const editCoreSet = handler(async (event, context) => { } const { submitted, status } = JSON.parse(event!.body!); - const dynamoKey = createCoreSetKey({ state, year, coreSet }); const lastAlteredBy = getUserNameFromJwt(event); const params = { TableName: process.env.coreSetTableName!, Key: { - compoundKey: dynamoKey, + compoundKey: `${state}${year}${coreSet}`, coreSet: coreSet, }, ...convertToDynamoExpression( diff --git a/services/app-api/handlers/dynamoUtils/createCompoundKey.ts b/services/app-api/handlers/dynamoUtils/createCompoundKey.ts deleted file mode 100644 index 53d5950a4e..0000000000 --- a/services/app-api/handlers/dynamoUtils/createCompoundKey.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { CoreSetParameters, MeasureParameters } from "../../types"; - -export const createMeasureKey = (measureParams: MeasureParameters) => { - const { state, year, coreSet, measure } = measureParams; - - return `${state}${year}${coreSet}${measure}`; -}; - -export const createCoreSetKey = (coreSetParams: CoreSetParameters) => { - const { state, year, coreSet } = coreSetParams; - - return `${state}${year}${coreSet}`; -}; diff --git a/services/app-api/handlers/dynamoUtils/tests/createCompoundKey.test.ts b/services/app-api/handlers/dynamoUtils/tests/createCompoundKey.test.ts deleted file mode 100644 index b120f2a8e4..0000000000 --- a/services/app-api/handlers/dynamoUtils/tests/createCompoundKey.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createCoreSetKey, createMeasureKey } from "../createCompoundKey"; - -describe("Testing createCoreSetKey", () => { - test("Successful key creation", () => { - const key = createCoreSetKey({ year: "2022", state: "FL", coreSet: "ACS" }); - expect(key).toEqual("FL2022ACS"); - }); -}); - -describe("Testing createMeasureKey", () => { - test("Successful key creation", () => { - const key = createMeasureKey({ - year: "2022", - state: "FL", - coreSet: "ACS", - measure: "FUA-AD", - }); - expect(key).toEqual("FL2022ACSFUA-AD"); - }); -}); diff --git a/services/app-api/handlers/measures/create.ts b/services/app-api/handlers/measures/create.ts index 681a14441c..ac63624668 100644 --- a/services/app-api/handlers/measures/create.ts +++ b/services/app-api/handlers/measures/create.ts @@ -1,6 +1,5 @@ import handler from "../../libs/handler-lib"; import dynamoDb from "../../libs/dynamodb-lib"; -import { createMeasureKey } from "../dynamoUtils/createCompoundKey"; import { hasRolePermissions, hasStatePermissions, @@ -31,13 +30,12 @@ export const createMeasure = handler(async (event, context) => { } // if not state user, can safely assume admin type user due to baseline handler protections const body = JSON.parse(event!.body!); - const dynamoKey = createMeasureKey({ state, year, coreSet, measure }); const params = { - TableName: process.env.measureTableName!, + TableName: process.env.measureTable!, Item: { - compoundKey: dynamoKey, + compoundKey: `${state}${year}${coreSet}`, state: state, - year: parseInt(year), + year: year, coreSet: coreSet, measure: measure, createdAt: Date.now(), diff --git a/services/app-api/handlers/measures/delete.ts b/services/app-api/handlers/measures/delete.ts index ecfb2ceea4..d8fbda0219 100644 --- a/services/app-api/handlers/measures/delete.ts +++ b/services/app-api/handlers/measures/delete.ts @@ -1,6 +1,5 @@ import handler from "../../libs/handler-lib"; import dynamoDb from "../../libs/dynamodb-lib"; -import { createMeasureKey } from "../dynamoUtils/createCompoundKey"; import { hasStatePermissions } from "../../libs/authorization"; import { Errors, StatusCodes } from "../../utils/constants/constants"; import { parseMeasureParameters } from "../../utils/parseParameters"; @@ -22,12 +21,11 @@ export const deleteMeasure = handler(async (event, context) => { }; } - const dynamoKey = createMeasureKey({ state, year, coreSet, measure }); const params = { - TableName: process.env.measureTableName!, + TableName: process.env.measureTable!, Key: { - compoundKey: dynamoKey, - coreSet: coreSet, + compoundKey: `${state}${year}${coreSet}`, + measure: measure, }, }; diff --git a/services/app-api/handlers/measures/get.ts b/services/app-api/handlers/measures/get.ts index 812cfdbae3..a8325a0a0b 100644 --- a/services/app-api/handlers/measures/get.ts +++ b/services/app-api/handlers/measures/get.ts @@ -1,7 +1,5 @@ import handler from "../../libs/handler-lib"; import dynamoDb from "../../libs/dynamodb-lib"; -import { convertToDynamoExpression } from "../dynamoUtils/convertToDynamoExpressionVars"; -import { createMeasureKey } from "../dynamoUtils/createCompoundKey"; import { measures } from "../dynamoUtils/measureList"; import { hasRolePermissions, @@ -35,16 +33,16 @@ export const listMeasures = handler(async (event, context) => { } } // if not state user, can safely assume admin type user due to baseline handler protections const params = { - TableName: process.env.measureTableName!, - ...convertToDynamoExpression( - { state: state, year: parseInt(year), coreSet: coreSet }, - "list" - ), + TableName: process.env.measureTable!, + KeyConditionExpression: "compoundKey = :compoundKey", + ExpressionAttributeValues: { + ":compoundKey": `${state}${year}${coreSet}`, + }, }; - let queriedMeasures = await dynamoDb.scanAll(params); + const queriedMeasures = await dynamoDb.queryAll(params); for (let v of queriedMeasures) { - const measure = measures[parseInt(year as string)]?.filter( + const measure = measures[year as number]?.filter( (m) => m.measure === (v as Measure)?.measure )[0]; @@ -81,12 +79,11 @@ export const getMeasure = handler(async (event, context) => { } } // if not state user, can safely assume admin type user due to baseline handler protections - const dynamoKey = createMeasureKey({ state, year, coreSet, measure }); const params = { - TableName: process.env.measureTableName!, + TableName: process.env.measureTable!, Key: { - compoundKey: dynamoKey, - coreSet: coreSet, + compoundKey: `${state}${year}${coreSet}`, + measure: measure, }, }; const queryValue = await dynamoDb.get(params); diff --git a/services/app-api/handlers/measures/tests/create.test.ts b/services/app-api/handlers/measures/tests/create.test.ts index 21dbee992e..f5c6ba5458 100644 --- a/services/app-api/handlers/measures/tests/create.test.ts +++ b/services/app-api/handlers/measures/tests/create.test.ts @@ -14,13 +14,7 @@ jest.mock("../../../libs/authorization", () => ({ hasStatePermissions: () => mockHasStatePermissions(), })); -jest.mock("../../dynamoUtils/createCompoundKey", () => ({ - __esModule: true, - createMeasureKey: jest.fn().mockReturnValue("FL2020ACSFUA-AD"), -})); - const event = { ...testEvent }; -process.env.measureTableName = "SAMPLE TABLE"; describe("Test Create Measure Handler", () => { beforeEach(() => { @@ -52,7 +46,7 @@ describe("Test Create Measure Handler", () => { expect(res.statusCode).toBe(StatusCodes.SUCCESS); expect(res.body).toContain("sample desc"); expect(res.body).toContain("sample detailed desc"); - expect(res.body).toContain("FL2020ACSFUA-AD"); + expect(res.body).toContain("IN2022ACS"); }); test("Test Successful Run of Measure Creation without description", async () => { @@ -62,7 +56,7 @@ describe("Test Create Measure Handler", () => { expect(res.statusCode).toBe(StatusCodes.SUCCESS); expect(res.body).toContain("test"); - expect(res.body).toContain("FL2020ACSFUA-AD"); + expect(res.body).toContain("IN2022ACS"); }); test("Fails with bad request when path params are missing", async () => { diff --git a/services/app-api/handlers/measures/tests/delete.test.ts b/services/app-api/handlers/measures/tests/delete.test.ts index fc7e766080..b0886fdfde 100644 --- a/services/app-api/handlers/measures/tests/delete.test.ts +++ b/services/app-api/handlers/measures/tests/delete.test.ts @@ -1,8 +1,5 @@ import { deleteMeasure } from "../delete"; - import dbLib from "../../../libs/dynamodb-lib"; - -import { APIGatewayProxyEvent } from "../../../types"; import { testEvent } from "../../../test-util/testEvents"; import { StatusCodes, Errors } from "../../../utils/constants/constants"; @@ -16,13 +13,8 @@ jest.mock("../../../libs/authorization", () => ({ hasStatePermissions: () => mockHasStatePermissions(), })); -jest.mock("../../dynamoUtils/createCompoundKey", () => ({ - __esModule: true, - createMeasureKey: jest.fn().mockReturnValue("FL2020ACSFUA-AD"), -})); - const event = { ...testEvent }; -process.env.measureTableName = "SAMPLE TABLE"; +process.env.measureTable = "SAMPLE TABLE"; describe("Test Delete Measure Handler", () => { beforeEach(() => { @@ -69,13 +61,13 @@ describe("Test Delete Measure Handler", () => { const res = await deleteMeasure(event, null); expect(res.statusCode).toBe(StatusCodes.SUCCESS); - expect(res.body).toContain("FL2020ACSFUA-AD"); - expect(res.body).toContain('"coreSet":"ACS"'); + expect(res.body).toContain("IN2022ACS"); + expect(res.body).toContain('"measure":"AAB-AD"'); expect(dbLib.delete).toHaveBeenCalledWith({ TableName: "SAMPLE TABLE", Key: { - compoundKey: "FL2020ACSFUA-AD", - coreSet: "ACS", + compoundKey: "IN2022ACS", + measure: "AAB-AD", }, }); }); diff --git a/services/app-api/handlers/measures/tests/get.test.ts b/services/app-api/handlers/measures/tests/get.test.ts index b2d521f58e..dbc2ddbf51 100644 --- a/services/app-api/handlers/measures/tests/get.test.ts +++ b/services/app-api/handlers/measures/tests/get.test.ts @@ -12,7 +12,7 @@ import { Errors, StatusCodes } from "../../../utils/constants/constants"; jest.mock("../../../libs/dynamodb-lib", () => ({ get: jest.fn().mockResolvedValue("single measure"), - scanAll: jest + queryAll: jest .fn() .mockResolvedValue([{ measure: "CSQ" }, { measure: "LBW-CH" }]), })); @@ -25,18 +25,13 @@ jest.mock("../../../libs/authorization", () => ({ hasStatePermissions: () => mockHasStatePermissions(), })); -jest.mock("../../dynamoUtils/createCompoundKey", () => ({ - __esModule: true, - createMeasureKey: jest.fn().mockReturnValue("FL2020ACSFUA-AD"), -})); - jest.mock("../../dynamoUtils/convertToDynamoExpressionVars", () => ({ __esModule: true, convertToDynamoExpression: jest.fn().mockReturnValue({ testValue: "test" }), })); const event = { ...testEvent }; -process.env.measureTableName = "SAMPLE TABLE"; +process.env.measureTable = "SAMPLE TABLE"; describe("Test Get Measure Handlers", () => { beforeEach(() => { @@ -67,8 +62,8 @@ describe("Test Get Measure Handlers", () => { expect(dbLib.get).toHaveBeenCalledWith({ TableName: "SAMPLE TABLE", Key: { - compoundKey: "FL2020ACSFUA-AD", - coreSet: "ACS", + compoundKey: "IN2022ACS", + measure: "AAB-AD", }, }); }); @@ -118,9 +113,12 @@ describe("Test Get Measure Handlers", () => { expect(res.statusCode).toBe(StatusCodes.SUCCESS); expect(res.body).toContain("CSQ"); expect(res.body).toContain("LBW-CH"); - expect(dbLib.scanAll).toHaveBeenCalledWith({ + expect(dbLib.queryAll).toHaveBeenCalledWith({ TableName: "SAMPLE TABLE", - testValue: "test", + KeyConditionExpression: "compoundKey = :compoundKey", + ExpressionAttributeValues: { + ":compoundKey": "FL2021ACS", + }, }); }); diff --git a/services/app-api/handlers/measures/tests/update.test.ts b/services/app-api/handlers/measures/tests/update.test.ts index 5c3123c4d9..5f2cea2817 100644 --- a/services/app-api/handlers/measures/tests/update.test.ts +++ b/services/app-api/handlers/measures/tests/update.test.ts @@ -18,18 +18,13 @@ jest.mock("../../../libs/authorization", () => ({ hasStatePermissions: () => mockHasStatePermissions(), })); -jest.mock("../../dynamoUtils/createCompoundKey", () => ({ - __esModule: true, - createMeasureKey: jest.fn().mockReturnValue("FL2020ACSFUA-AD"), -})); - jest.mock("../../dynamoUtils/convertToDynamoExpressionVars", () => ({ __esModule: true, convertToDynamoExpression: jest.fn().mockReturnValue({ testValue: "test" }), })); const event = { ...testEvent }; -process.env.measureTableName = "SAMPLE TABLE"; +process.env.measureTable = "SAMPLE TABLE"; describe("Test Update Measure Handler", () => { beforeEach(() => { @@ -78,8 +73,8 @@ describe("Test Update Measure Handler", () => { const res = await editMeasure(event, null); expect(res.statusCode).toBe(StatusCodes.SUCCESS); - expect(res.body).toContain("FL2020ACSFUA-AD"); - expect(res.body).toContain('"coreSet":"ACS"'); + expect(res.body).toContain("IN2022ACS"); + expect(res.body).toContain('"measure":"AAB-AD"'); expect(convertToDynamoExpression).toHaveBeenCalledWith( { status: "status", @@ -93,8 +88,8 @@ describe("Test Update Measure Handler", () => { expect(dbLib.update).toHaveBeenCalledWith({ TableName: "SAMPLE TABLE", Key: { - compoundKey: "FL2020ACSFUA-AD", - coreSet: "ACS", + compoundKey: "IN2022ACS", + measure: "AAB-AD", }, testValue: "test", }); @@ -107,8 +102,8 @@ describe("Test Update Measure Handler", () => { const res = await editMeasure(event, null); expect(res.statusCode).toBe(StatusCodes.SUCCESS); - expect(res.body).toContain("FL2020ACSFUA-AD"); - expect(res.body).toContain('"coreSet":"ACS"'); + expect(res.body).toContain("IN2022ACS"); + expect(res.body).toContain('"measure":"AAB-AD"'); expect(convertToDynamoExpression).toHaveBeenCalledWith( { status: "status", @@ -122,8 +117,8 @@ describe("Test Update Measure Handler", () => { expect(dbLib.update).toHaveBeenCalledWith({ TableName: "SAMPLE TABLE", Key: { - compoundKey: "FL2020ACSFUA-AD", - coreSet: "ACS", + compoundKey: "IN2022ACS", + measure: "AAB-AD", }, testValue: "test", }); diff --git a/services/app-api/handlers/measures/update.ts b/services/app-api/handlers/measures/update.ts index f04fb39327..59ab618c5b 100644 --- a/services/app-api/handlers/measures/update.ts +++ b/services/app-api/handlers/measures/update.ts @@ -1,7 +1,6 @@ import handler from "../../libs/handler-lib"; import dynamoDb from "../../libs/dynamodb-lib"; import { convertToDynamoExpression } from "../dynamoUtils/convertToDynamoExpressionVars"; -import { createMeasureKey } from "../dynamoUtils/createCompoundKey"; import { getUserNameFromJwt, hasStatePermissions, @@ -35,7 +34,6 @@ export const editMeasure = handler(async (event, context) => { detailedDescription, } = JSON.parse(event!.body!); - const dynamoKey = createMeasureKey({ state, year, coreSet, measure }); const lastAlteredBy = getUserNameFromJwt(event); const descriptionParams = @@ -46,10 +44,10 @@ export const editMeasure = handler(async (event, context) => { } : {}; const params = { - TableName: process.env.measureTableName!, + TableName: process.env.measureTable!, Key: { - compoundKey: dynamoKey, - coreSet: coreSet, + compoundKey: `${state}${year}${coreSet}`, + measure: measure, }, ...convertToDynamoExpression( { @@ -66,7 +64,7 @@ export const editMeasure = handler(async (event, context) => { await dynamoDb.update(params); //in 2024 and onward, we added a new feature called combined rates which requires rate calculations to the rates table - if (parseInt(year) >= 2024) { + if (year >= 2024) { //after updating the database with the latest values for the measure, we run the combine rates calculations for said measure await calculateAndPutRate({ state, diff --git a/services/app-api/handlers/rate/get.ts b/services/app-api/handlers/rate/get.ts index cf8a3c908e..9f9e1a9deb 100644 --- a/services/app-api/handlers/rate/get.ts +++ b/services/app-api/handlers/rate/get.ts @@ -4,7 +4,7 @@ import { } from "../../libs/authorization"; import handler from "../../libs/handler-lib"; import { getCombinedRatesFromTable } from "../../storage/table"; -import { MeasureParameters, UserRoles } from "../../types"; +import { UserRoles } from "../../types"; import { Errors, StatusCodes } from "../../utils/constants/constants"; import { parseMeasureParameters } from "../../utils/parseParameters"; diff --git a/services/app-api/handlers/rate/rateCalculations.test.ts b/services/app-api/handlers/rate/rateCalculations.test.ts index ff3124e9bc..3a6b5fe0f0 100644 --- a/services/app-api/handlers/rate/rateCalculations.test.ts +++ b/services/app-api/handlers/rate/rateCalculations.test.ts @@ -2,7 +2,7 @@ import { getMeasureFromTable, putCombinedRatesToTable, } from "../../storage/table"; -import { MeasureParameters } from "../../types"; +import { RateParameters } from "../../types"; import { calculateAndPutRate } from "./rateCalculations"; jest.mock("../../storage/table", () => ({ @@ -21,15 +21,15 @@ describe("Combined Rate Calculations", () => { }); it("Should not calculate a combined rate for core sets that are already combined", async () => { - await calculateAndPutRate({ coreSet: "ACS" } as MeasureParameters); - await calculateAndPutRate({ coreSet: "CCS" } as MeasureParameters); - await calculateAndPutRate({ coreSet: "HHCS" } as MeasureParameters); + await calculateAndPutRate({ coreSet: "ACS" } as RateParameters); + await calculateAndPutRate({ coreSet: "CCS" } as RateParameters); + await calculateAndPutRate({ coreSet: "HHCS" } as RateParameters); expect(getMeasureFromTable).not.toHaveBeenCalled(); }); it("Should calculate combined rates for separated child core sets", async () => { - await calculateAndPutRate({ coreSet: "CCSM" } as MeasureParameters); + await calculateAndPutRate({ coreSet: "CCSM" } as RateParameters); expect(getMeasureFromTable).toHaveBeenCalledWith( expect.objectContaining({ coreSet: "CCSM" }) @@ -44,7 +44,7 @@ describe("Combined Rate Calculations", () => { }); it("Should calculate combined rates for separated asult core sets", async () => { - await calculateAndPutRate({ coreSet: "ACSC" } as MeasureParameters); + await calculateAndPutRate({ coreSet: "ACSC" } as RateParameters); expect(getMeasureFromTable).toHaveBeenCalledWith( expect.objectContaining({ coreSet: "ACSM" }) diff --git a/services/app-api/handlers/rate/rateCalculations.ts b/services/app-api/handlers/rate/rateCalculations.ts index 8af9687438..99f2d4fa75 100644 --- a/services/app-api/handlers/rate/rateCalculations.ts +++ b/services/app-api/handlers/rate/rateCalculations.ts @@ -6,7 +6,7 @@ import { CoreSetAbbr, isCoreSetAbbr, Measure, - MeasureParameters, + RateParameters, } from "../../types"; import { collectDataSources } from "./dataSourceAnalysis"; import { combineRates } from "./rateNDRCalculations"; @@ -19,9 +19,7 @@ import { calculateAdditionalValues } from "./rateValueCalculations"; * 3. Perform all calculations * 4. Store the results to the Rates table */ -export const calculateAndPutRate = async ( - pathParameters: MeasureParameters -) => { +export const calculateAndPutRate = async (pathParameters: RateParameters) => { const { coreSet, measure } = pathParameters; if (!isCoreSetAbbr(coreSet)) { return; diff --git a/services/app-api/serverless.yml b/services/app-api/serverless.yml index ef980255af..7f60f954b9 100644 --- a/services/app-api/serverless.yml +++ b/services/app-api/serverless.yml @@ -46,6 +46,7 @@ custom: coreSetTableName: ${env:coreSetTableName, param:CoreSetTableName} coreSetTableStreamArn: ${env:DYNAMO_TABLE_ARN, param:CoreSetTableStreamArn} measureTableName: ${env:measureTableName, param:MeasureTableName} + measureTable: ${env:measureTable, param:MeasureTable} measureTableStreamArn: ${env:DYNAMO_TABLE_ARN, param:MeasureTableStreamArn} rateTableName: ${env:rateTableName, param:RateTableName} rateTableStreamArn: ${env:DYNAMO_TABLE_ARN, param:RateTableStreamArn} @@ -101,6 +102,7 @@ provider: environment: coreSetTableName: ${self:custom.coreSetTableName} measureTableName: ${self:custom.measureTableName} + measureTable: ${self:custom.measureTable} rateTableName: ${self:custom.rateTableName} bannerTableName: ${self:custom.bannerTableName} stage: ${opt:stage, self:provider.stage} diff --git a/services/app-api/storage/table.test.ts b/services/app-api/storage/table.test.ts index af814b96f6..d18f8a22be 100644 --- a/services/app-api/storage/table.test.ts +++ b/services/app-api/storage/table.test.ts @@ -15,7 +15,7 @@ const mockMeasureParameters = { state: "CO", measure: "ZZZ-AD", coreSet: "ACS", - year: "2024", + year: 2024, }; const mockMeasure = { @@ -61,7 +61,7 @@ const mockCombinedRate = { describe("Test database helper functions", () => { beforeAll(() => { - process.env.measureTableName = "local-measures"; + process.env.measureTable = "local-measure"; process.env.rateTableName = "local-rates"; }); @@ -72,10 +72,10 @@ describe("Test database helper functions", () => { expect(result).toBe(mockMeasure); expect(dynamodbLib.get).toHaveBeenCalledWith({ - TableName: "local-measures", + TableName: "local-measure", Key: { - compoundKey: "CO2024ACSZZZ-AD", - coreSet: "ACS", + compoundKey: "CO2024ACS", + measure: "ZZZ-AD", }, }); }); diff --git a/services/app-api/storage/table.ts b/services/app-api/storage/table.ts index 8d4af36e1a..54726126be 100644 --- a/services/app-api/storage/table.ts +++ b/services/app-api/storage/table.ts @@ -1,20 +1,20 @@ import dynamoDb from "../libs/dynamodb-lib"; import * as Types from "../types"; -import { MeasureParameters, CombinedRatesPayload } from "../types"; +import { RateParameters, CombinedRatesPayload } from "../types"; -export const getMeasureFromTable = async (parameters: MeasureParameters) => { +export const getMeasureFromTable = async (parameters: RateParameters) => { const { state, year, coreSet, measure } = parameters; return await dynamoDb.get({ - TableName: process.env.measureTableName, + TableName: process.env.measureTable, Key: { - compoundKey: `${state}${year}${coreSet}${measure}`, - coreSet: coreSet, + compoundKey: `${state}${year}${coreSet}`, + measure: measure, }, }); }; export const putCombinedRatesToTable = async ( - parameters: MeasureParameters, + parameters: RateParameters, combinedRates: CombinedRatesPayload ) => { const { year, state, coreSet, measure } = parameters; @@ -39,7 +39,7 @@ export const putCombinedRatesToTable = async ( }; export const getCombinedRatesFromTable = async ( - parameters: MeasureParameters + parameters: RateParameters ): Promise => { const { year, state, coreSet, measure } = parameters; const queryValue = await dynamoDb.get({ diff --git a/services/app-api/types.ts b/services/app-api/types.ts index 0f25b4af04..f5913f2160 100644 --- a/services/app-api/types.ts +++ b/services/app-api/types.ts @@ -172,11 +172,17 @@ export enum MeasurementSpecificationType { TheJointCommission = "TheJointCommission", } +export interface RateParameters { + state: string; + year: number; + coreSet: string; + measure: string; +} + export interface MeasureParameters { state: string; year: string; coreSet: string; - measure: string; } export interface CoreSetParameters { diff --git a/services/app-api/utils/parseParameters.ts b/services/app-api/utils/parseParameters.ts index 7ed6b10404..c61df5a3fc 100644 --- a/services/app-api/utils/parseParameters.ts +++ b/services/app-api/utils/parseParameters.ts @@ -29,7 +29,7 @@ export const parseStateAndYearParameters = (event: APIGatewayProxyEvent) => { logger.warn("Invalid or missing year in path"); return { allParamsValid: false as const }; } - return { allParamsValid: true as const, state, year }; + return { allParamsValid: true as const, state, year: parseInt(year) }; }; export const parseCoreSetParameters = (event: APIGatewayProxyEvent) => { @@ -47,7 +47,12 @@ export const parseCoreSetParameters = (event: APIGatewayProxyEvent) => { logger.warn("Invalid or missing coreset in path"); return { allParamsValid: false as const }; } - return { allParamsValid: true as const, state, year, coreSet }; + return { + allParamsValid: true as const, + state, + year: parseInt(year), + coreSet, + }; }; export const parseMeasureParameters = (event: APIGatewayProxyEvent) => { @@ -69,5 +74,11 @@ export const parseMeasureParameters = (event: APIGatewayProxyEvent) => { logger.warn("Invalid or missing measure in path"); return { allParamsValid: false as const }; } - return { allParamsValid: true as const, state, year, coreSet, measure }; + return { + allParamsValid: true as const, + state, + year: parseInt(year), + coreSet, + measure, + }; }; diff --git a/services/database/README.md b/services/database/README.md index 3be9984d05..e0182119fb 100644 --- a/services/database/README.md +++ b/services/database/README.md @@ -1 +1,6 @@ # database + +Run these commands to spin up local dynamo database instance: +`./run local` +`DYNAMO_ENDPOINT=http://localhost:8000 dynamodb-admin` +Navigate to http://localhost:8001 diff --git a/services/database/scripts/transformMeasureTable.ts b/services/database/scripts/transformMeasureTable.ts new file mode 100644 index 0000000000..0dc97f20f4 --- /dev/null +++ b/services/database/scripts/transformMeasureTable.ts @@ -0,0 +1,81 @@ +import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; +import { + DynamoDBDocumentClient, + paginateScan, + PutCommand, +} from "@aws-sdk/lib-dynamodb"; +import prompt from "prompt-sync"; + +/*** + * Run with `npx tsx transformMeasureTable.ts` + */ +const transformMeasureTable = async () => { + let stage = "local"; + const p = prompt(); + const runLocally = p("Do you want to run this script locally? Y/N: "); + const isLocal = runLocally === "Y" ? true : false; + if (!isLocal) { + stage = p("What environment are we running on (e.g. master, val, prod)? "); + } + + const dbClient = buildClient(isLocal); + + const oldTable = `${stage}-measures`; + const newTable = `${stage}-measure`; + console.log(`Processing table ${oldTable}`); + for await (let entry of scan(dbClient, oldTable)) { + add(dbClient, newTable, entry); + } +}; + +async function* scan(client: DynamoDBDocumentClient, table: string) { + const query = { + TableName: table, + }; + for await (const result of paginateScan({ client }, query)) { + yield* result.Items ?? []; + } +} + +async function add(client: DynamoDBDocumentClient, table: string, entry: any) { + const newCompoundKey = `${entry.state}${entry.year}${entry.coreSet}`; + const params = { + TableName: table, + Item: { + ...entry, + compoundKey: newCompoundKey, + }, + }; + await client.send(new PutCommand(params)); +} + +function buildClient(isLocal: boolean) { + if (isLocal) { + return DynamoDBDocumentClient.from( + new DynamoDBClient({ + region: "localhost", + endpoint: "http://localhost:8000", + credentials: { + accessKeyId: "LOCALFAKEKEY", // pragma: allowlist secret + secretAccessKey: "LOCALFAKESECRET", // pragma: allowlist secret + }, + }) + ); + } else { + return DynamoDBDocumentClient.from( + new DynamoDBClient({ + region: "us-east-1", + logger: { + debug: () => { + /* Dynamo's debug logs are extremely noisy */ + }, + info: console.info, + warn: console.warn, + error: console.error, + }, + }) + ); + } +} + +transformMeasureTable(); diff --git a/services/database/serverless.yml b/services/database/serverless.yml index 0d5f5dec8e..d34f51e24c 100644 --- a/services/database/serverless.yml +++ b/services/database/serverless.yml @@ -20,6 +20,7 @@ custom: measureTableName: ${self:custom.stage}-measures bannerTableName: ${self:custom.stage}-banners rateTableName: ${self:custom.stage}-rates + measureTable: ${self:custom.stage}-measure dynamodb: stages: - local @@ -34,7 +35,7 @@ provider: region: us-east-1 stackTags: PROJECT: ${self:custom.project} - SERVICE: ${self:service} + SERVICE: ${self:service} resources: Resources: @@ -108,6 +109,25 @@ resources: - AttributeName: key KeyType: HASH BillingMode: PAY_PER_REQUEST # Set the capacity to auto-scale + QualityMeasureTable: + Type: AWS::DynamoDB::Table + Properties: + TableName: ${self:custom.measureTable} + PointInTimeRecoverySpecification: + PointInTimeRecoveryEnabled: true + StreamSpecification: + StreamViewType: NEW_AND_OLD_IMAGES + AttributeDefinitions: + - AttributeName: compoundKey + AttributeType: S + - AttributeName: measure + AttributeType: S + KeySchema: + - AttributeName: compoundKey + KeyType: HASH + - AttributeName: measure + KeyType: RANGE + BillingMode: PAY_PER_REQUEST # Set the capacity to auto-scale Outputs: CoreSetTableName: Value: !Ref CoreSetTable @@ -117,6 +137,8 @@ resources: Value: !GetAtt CoreSetTable.StreamArn MeasureTableName: Value: !Ref MeasureTable + MeasureTable: + Value: !Ref QualityMeasureTable MeasureTableArn: Value: !GetAtt MeasureTable.Arn MeasureTableStreamArn: diff --git a/services/uploads/serverless.yml b/services/uploads/serverless.yml index 0a9f9aff32..15eec41a8c 100644 --- a/services/uploads/serverless.yml +++ b/services/uploads/serverless.yml @@ -17,7 +17,7 @@ provider: region: us-east-1 stackTags: PROJECT: ${self:custom.project} - SERVICE: ${self:service} + SERVICE: ${self:service} iam: role: path: ${ssm:/configuration/${self:custom.stage}/iam/path, ssm:/configuration/default/iam/path, "/"} @@ -53,6 +53,7 @@ custom: iamPermissionsBoundaryPolicy: ${ssm:/configuration/${self:custom.stage}/iam/permissionsBoundaryPolicy, ssm:/configuration/default/iam/permissionsBoundaryPolicy, ""} coreSetTableName: ${env:coreSetTableName, param:CoreSetTableName} measureTableName: ${env:measureTableName, param:MeasureTableName} + measureTable: ${env:measureTable, param:MeasureTable} rateTableName: ${env:rateTableName, param:RateTableName} serverlessTerminationProtection: stages: @@ -91,6 +92,7 @@ functions: environment: coreSetTableName: ${self:custom.coreSetTableName} measureTableName: ${self:custom.measureTableName} + measureTable: ${self:custom.measureTable} rateTableName: ${self:custom.rateTableName} dynamoSnapshotS3BucketName: !Ref DynamoSnapshotBucket avScan: diff --git a/services/uploads/src/dynamoSync/handler.js b/services/uploads/src/dynamoSync/handler.js index 1be0b1368c..58d64c5e5b 100644 --- a/services/uploads/src/dynamoSync/handler.js +++ b/services/uploads/src/dynamoSync/handler.js @@ -27,7 +27,7 @@ const uploadFileToS3 = async (filePath, scanResult) => { const syncDynamoToS3 = handler(async (_event, _context) => { console.log("Syncing Dynamo to Uploads"); - const measureResults = await scanAll(process.env.measureTableName); + const measureResults = await scanAll(process.env.measureTable); const coreSetResults = await scanAll(process.env.coreSetTableName); const rateResults = await scanAll(process.env.rateTableName);