Skip to content

Commit

Permalink
feat(package-actions): Refactor logic for getPackageActions (#275)
Browse files Browse the repository at this point in the history
* Refactor action rules

* Tidy up latestRai stuff

* Tidy up latestRai stuff

* Fix 'err is any' ts issue

* Fix logic for disable rai response withdraw

* Type jiu jitsu

* Fix med spa gate logic

* Refactoring how we check planType

* Oops, wrong allowed plan type

* Add status check helper object

* Type update

* Remove action type from spa details

* Reduce load on check process

* Add jsdocs for convenience

* small change

* Remove unused utility

* PlanChek -> PlanTypeCheck

* Only instantiate ActionAvailabilityCheck once
  • Loading branch information
Kevin Haube authored Dec 21, 2023
1 parent 9045759 commit aed336c
Show file tree
Hide file tree
Showing 28 changed files with 245 additions and 186 deletions.
59 changes: 56 additions & 3 deletions src/packages/shared-types/actions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,61 @@
import { OsMainSourceItem } from "./opensearch";
import { CognitoUserAttributes } from "./user";
import { getLatestRai } from "shared-utils";
import { SEATOOL_STATUS } from "./statusHelper";

export enum Action {
ISSUE_RAI = "issue-rai",
RESPOND_TO_RAI = "respond-to-rai",
ENABLE_RAI_WITHDRAW = "enable-rai-withdraw",
WITHDRAW_PACKAGE = "withdraw-package",
DISABLE_RAI_WITHDRAW = "disable-rai-withdraw",
ISSUE_RAI = "issue-rai",
WITHDRAW_RAI = "withdraw-rai",
RESPOND_TO_RAI = "respond-to-rai",
WITHDRAW_PACKAGE = "withdraw-package",
}

const checkStatus = (seatoolStatus: string, authorized: string | string[]) =>
typeof authorized === "string"
? seatoolStatus === authorized
: authorized.includes(seatoolStatus);

export const ActionAvailabilityCheck = ({
seatoolStatus,
rais,
raiWithdrawEnabled,
}: OsMainSourceItem) => {
const latestRai = getLatestRai(rais);
return {
/** Is in any of our pending statuses, sans Pending-RAI **/
isInActivePendingStatus: checkStatus(seatoolStatus, [
SEATOOL_STATUS.PENDING,
SEATOOL_STATUS.PENDING_OFF_THE_CLOCK,
SEATOOL_STATUS.PENDING_APPROVAL,
SEATOOL_STATUS.PENDING_CONCURRENCE,
]),
/** Latest RAI is requested and status is Pending-RAI **/
hasRequestedRai:
latestRai?.status === "requested" &&
checkStatus(seatoolStatus, SEATOOL_STATUS.PENDING_RAI),
/** Latest RAI is not null **/
hasLatestRai: latestRai !== null,
/** Latest RAI has been responded to **/
hasRaiResponse: latestRai?.status === "received",
/** RAI Withdraw has been enabled **/
hasEnabledRaiWithdraw: raiWithdrawEnabled,
/** Is in any status except Package Withdrawn **/
isNotWithdrawn: !checkStatus(seatoolStatus, SEATOOL_STATUS.WITHDRAWN),
/** Added for elasticity, but common checks should always bubble up as
* object attributes! **/
hasStatus: (authorizedStatuses: string | string[]) =>
checkStatus(seatoolStatus, authorizedStatuses),
};
};

export type ActionRule = {
action: Action;
check: (
checker: ReturnType<typeof ActionAvailabilityCheck>,
user: CognitoUserAttributes,
/** Keep excess parameters to a minimum **/
...any: any[]
) => boolean;
};
11 changes: 0 additions & 11 deletions src/packages/shared-types/authority.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/packages/shared-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export * from "./opensearch";
export * from "./uploads";
export * from "./actions";
export * from "./attachments";
export * from "./authority";
export * from "./planType";
export * from "./action-types";
export * from "./forms";
export * from "./inputs";
Expand Down
16 changes: 16 additions & 0 deletions src/packages/shared-types/planType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export enum PlanType {
MED_SPA = "medicaid spa",
CHIP_SPA = "chip spa",
}

const checkPlan = (planType: PlanType | null, validPlanTypes: PlanType[]) =>
!planType
? false
: validPlanTypes.includes(planType.toLowerCase() as PlanType);

export const PlanTypeCheck = (planType: PlanType | null) => ({
isSpa: checkPlan(planType, [PlanType.MED_SPA, PlanType.CHIP_SPA]),
isWaiver: checkPlan(planType, []),
/** Keep excess methods to a minimum with `is` **/
is: (validPlanTypes: PlanType[]) => checkPlan(planType, validPlanTypes),
});
3 changes: 2 additions & 1 deletion src/packages/shared-types/seatool.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z } from "zod";
import { SEATOOL_STATUS, getStatus } from "./statusHelper";
import { PlanType } from "./planType";

type AuthorityType = "SPA" | "WAIVER" | "MEDICAID" | "CHIP";

Expand Down Expand Up @@ -199,7 +200,7 @@ export const transformSeatoolData = (id: string) => {
finalDispositionDate: getFinalDispositionDate(seatoolStatus, data),
leadAnalystOfficerId,
leadAnalystName,
planType: data.PLAN_TYPES?.[0].PLAN_TYPE_NAME,
planType: data.PLAN_TYPES?.[0].PLAN_TYPE_NAME as PlanType | null,
planTypeId: data.STATE_PLAN.PLAN_TYPE,
proposedDate: getDateStringOrNullFromEpoc(data.STATE_PLAN.PROPOSED_DATE),
raiReceivedDate,
Expand Down
2 changes: 1 addition & 1 deletion src/packages/shared-utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ export * from "./s3-url-parser";
export { isStateUser } from "./is-state-user";
export * from "./rai-helper";
export * from "./regex";
export * from './package-actions'
export * from "./package-actions/getAvailableActions";
57 changes: 0 additions & 57 deletions src/packages/shared-utils/package-actions.ts

This file was deleted.

18 changes: 18 additions & 0 deletions src/packages/shared-utils/package-actions/getAvailableActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
ActionAvailabilityCheck,
CognitoUserAttributes,
OsMainSourceItem,
PlanTypeCheck,
PlanType,
} from "../../shared-types";
import rules from "./rules";

export const getAvailableActions = (
user: CognitoUserAttributes,
result: OsMainSourceItem
) => {
const actionChecker = ActionAvailabilityCheck(result);
return PlanTypeCheck(result.planType).is([PlanType.MED_SPA])
? rules.filter((r) => r.check(actionChecker, user)).map((r) => r.action)
: [];
};
65 changes: 65 additions & 0 deletions src/packages/shared-utils/package-actions/rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
Action,
ActionAvailabilityCheck,
ActionRule,
SEATOOL_STATUS,
} from "../../shared-types";
import { isCmsUser, isStateUser } from "../user-helper";

const arIssueRai: ActionRule = {
action: Action.ISSUE_RAI,
check: (checker, user) =>
checker.isInActivePendingStatus &&
(!checker.hasLatestRai || checker.hasRequestedRai) &&
isCmsUser(user),
};

const arRespondToRai: ActionRule = {
action: Action.RESPOND_TO_RAI,
check: (checker, user) =>
checker.hasStatus(SEATOOL_STATUS.PENDING_RAI) &&
checker.hasRequestedRai &&
isStateUser(user),
};

const arEnableWithdrawRaiResponse: ActionRule = {
action: Action.ENABLE_RAI_WITHDRAW,
check: (checker, user) =>
checker.isNotWithdrawn &&
checker.hasRaiResponse &&
!checker.hasEnabledRaiWithdraw &&
isCmsUser(user),
};

const arDisableWithdrawRaiResponse: ActionRule = {
action: Action.DISABLE_RAI_WITHDRAW,
check: (checker, user) =>
checker.isNotWithdrawn &&
checker.hasRaiResponse &&
checker.hasEnabledRaiWithdraw &&
isCmsUser(user),
};

const arWithdrawRaiResponse: ActionRule = {
action: Action.WITHDRAW_RAI,
check: (checker, user) =>
checker.isInActivePendingStatus &&
checker.hasRaiResponse &&
checker.hasEnabledRaiWithdraw &&
isStateUser(user),
};

const arWithdrawPackage: ActionRule = {
action: Action.WITHDRAW_PACKAGE,
check: (checker, user) =>
checker.isInActivePendingStatus && isStateUser(user),
};

export default [
arIssueRai,
arRespondToRai,
arEnableWithdrawRaiResponse,
arDisableWithdrawRaiResponse,
arWithdrawRaiResponse,
arWithdrawPackage,
];
54 changes: 31 additions & 23 deletions src/packages/shared-utils/rai-helper.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@

export const getLatestRai = (rais: any) => {
const keys = Object.keys(rais);
if (keys.length === 0) {
return null;
} else {
const maxKey = keys.reduce((max, key) => Math.max(max, Number(key)), -Infinity);
return {
key: maxKey,
status: getRaiStatus(rais[maxKey]),
value: rais[maxKey],
};
}
export type LatestRai = {
key: number;
status: RaiStatus;
value: any;
};
export const getLatestRai = (rais: any | undefined): LatestRai | null => {
if (!rais || Object.keys(rais).length === 0) {
// No keys = no rai entries
return null;
} else {
const maxKey = Object.keys(rais).reduce(
(max, key) => Math.max(max, Number(key)),
-Infinity
);
return {
key: maxKey,
status: getRaiStatus(rais[maxKey]),
value: rais[maxKey],
};
}
};

export const getRaiStatus = (rai: any) => {
if(rai.withdrawnDate) {
return "withdrawn"
} else if(rai.receivedDate) {
return "received"
} else if (rai.requestedDate) {
return "requested"
} else {
return "unknown"
}
}
if (rai.withdrawnDate) {
return "withdrawn";
} else if (rai.receivedDate) {
return "received";
} else if (rai.requestedDate) {
return "requested";
} else {
return "unknown";
}
};
export type RaiStatus = ReturnType<typeof getRaiStatus>;
4 changes: 2 additions & 2 deletions src/services/api/handlers/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
isAuthorized,
lookupUserAttributes,
} from "../libs/auth/user";
import { packageActionsForResult } from "shared-utils";
import { getAvailableActions } from "shared-utils";
import { Action } from "shared-types";
import {
issueRai,
Expand Down Expand Up @@ -56,7 +56,7 @@ export const handler = async (event: APIGatewayEvent) => {
);

// Check that the package action is available
const actions: Action[] = packageActionsForResult(userAttr, result._source);
const actions: Action[] = getAvailableActions(userAttr, result._source);
if (!actions.includes(actionType)) {
return response({
statusCode: 401,
Expand Down
4 changes: 2 additions & 2 deletions src/services/api/handlers/getPackageActions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { APIGatewayEvent } from "aws-lambda";
import { packageActionsForResult } from "shared-utils";
import { getAvailableActions } from "shared-utils";
import { getPackage } from "../libs/package/getPackage";
import {
getAuthDetails,
Expand Down Expand Up @@ -41,7 +41,7 @@ export const getPackageActions = async (event: APIGatewayEvent) => {
return response({
statusCode: 200,
body: {
actions: packageActionsForResult(userAttr, result._source),
actions: getAvailableActions(userAttr, result._source),
},
});
} catch (err) {
Expand Down
2 changes: 1 addition & 1 deletion src/services/api/handlers/packageActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ export async function withdrawPackage(body: WithdrawPackage) {
console.error("Error executing query:", err);
return response({
statusCode: 500,
body: { message: err.message },
body: err instanceof Error ? { message: err.message } : err,
});
} finally {
// Close pool
Expand Down
4 changes: 2 additions & 2 deletions src/services/api/handlers/submit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const config = {
} as sql.config;

import { Kafka, Message } from "kafkajs";
import { Authority, onemacSchema, transformOnemac } from "shared-types";
import { PlanType, onemacSchema, transformOnemac } from "shared-types";

const kafka = new Kafka({
clientId: "submit",
Expand Down Expand Up @@ -50,7 +50,7 @@ export const submit = async (event: APIGatewayEvent) => {
});
}

const activeSubmissionTypes = [Authority.CHIP_SPA, Authority.MED_SPA];
const activeSubmissionTypes = [PlanType.CHIP_SPA, PlanType.MED_SPA];
if (!activeSubmissionTypes.includes(body.authority)) {
return response({
statusCode: 400,
Expand Down
Loading

0 comments on commit aed336c

Please sign in to comment.