Skip to content

Commit

Permalink
Merge branch 'main' into mt-db-vm
Browse files Browse the repository at this point in the history
  • Loading branch information
mojotalantikite committed Jul 19, 2023
2 parents 9edca8a + 3dcec7a commit 0b3e4cf
Show file tree
Hide file tree
Showing 16 changed files with 453 additions and 96 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/deploy-app-to-env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,12 @@ jobs:
env:
STAGE_NAME: ${{ inputs.stage_name }}
run: |
./scripts/invoke-migrate-lambda.sh app-api-$STAGE_NAME-migrate
./scripts/invoke-migrate-lambda.sh app-api-$STAGE_NAME-migrate \$LATEST "Migration of the database failed."
- name: invoke proto_to_db lambda
id: invoke-proto_to_db
working-directory: services/app-api
env:
STAGE_NAME: ${{ inputs.stage_name }}
run: |
./scripts/invoke-migrate-lambda.sh app-api-$STAGE_NAME-proto_to_db \$LATEST "Migration of the protos to the database failed."
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ jobs:
needs.api-unit-tests.result == 'success' &&
needs.build-prisma-client-lambda-layer.result == 'success' &&
needs.begin-deployment.result == 'success'
uses: Enterprise-CMCS/managed-care-review/.github/workflows/deploy-app-to-env.yml@main
uses: Enterprise-CMCS/managed-care-review/.github/workflows/deploy-app-to-env.yml@ma_3212_migration_lambda
with:
environment: dev
stage_name: ${{ needs.begin-deployment.outputs.stage-name }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sechub-jira-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
stage-name: prod

- name: Sync Security Hub and Jira
uses: Enterprise-CMCS/security-hub-visibility@v1.0.1
uses: Enterprise-CMCS/mac-fc-security-hub-visibility@v1.0.1
with:
jira-token: ${{ secrets.JIRA_TOKEN }}
jira-username: ${{ secrets.JIRA_USERNAME }}
Expand Down
5 changes: 3 additions & 2 deletions services/app-api/scripts/invoke-migrate-lambda.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ set -u

function_name="$1"
lambda_version="${2:-\$LATEST}"
error_message="${3:-}"

cli_read_timeout=240

if (set -x ; aws lambda invoke --qualifier "$lambda_version" --cli-read-timeout "$cli_read_timeout" --function "$function_name" lambda_response.json) ; then
exitCode="$(jq '.statusCode' < lambda_response.json)"
if [[ "$exitCode" != 200 ]] ; then
cat lambda_response.json
echo "Migration of the database failed." 1>&2
echo "$error_message" 1>&2
exit 1
fi
else
exit
fi
fi
11 changes: 11 additions & 0 deletions services/app-api/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,17 @@ functions:
- ${self:custom.sgId}
subnetIds: ${self:custom.privateSubnets}

proto_to_db:
handler: src/handlers/proto_to_db.main
layers:
- !Ref PrismaClientEngineLambdaLayer
- arn:aws:lambda:us-east-1:901920570463:layer:aws-otel-nodejs-amd64-ver-1-9-1:1
timeout: 300
vpc:
securityGroupIds:
- ${self:custom.sgId}
subnetIds: ${self:custom.privateSubnets}

migrate_rate_documents:
handler: src/handlers/migrate_rate_documents.main
layers:
Expand Down
146 changes: 146 additions & 0 deletions services/app-api/src/domain-models/healthPlanPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import {
HealthPlanPackageType,
} from './HealthPlanPackageType'
import { pruneDuplicateEmails } from '../emailer/formatters'
import { ContractType } from './contractAndRates/contractAndRatesZodSchema'
import {
SubmissionDocument,
UnlockedHealthPlanFormDataType,
} from '../../../app-web/src/common-code/healthPlanFormDataType'
import {
toProtoBuffer,
toDomain,
} from '../../../app-web/src/common-code/proto/healthPlanFormDataProto'

// submissionStatus computes the current status of the submission based on
// the submit/unlock info on its revisions.
Expand Down Expand Up @@ -60,9 +69,146 @@ function packageSubmitters(pkg: HealthPlanPackageType): string[] {
return pruneDuplicateEmails(submitters)
}

function convertContractToUnlockedHealthPlanPackage(
contract: ContractType
): HealthPlanPackageType | Error {
console.info('Attempting to convert contract to health plan package')

const healthPlanRevisions =
convertContractRevisionToHealthPlanRevision(contract)

if (healthPlanRevisions instanceof Error) {
return healthPlanRevisions
}

return {
id: contract.id,
stateCode: contract.stateCode,
revisions: healthPlanRevisions,
}
}

function convertContractRevisionToHealthPlanRevision(
contract: ContractType
): HealthPlanRevisionType[] | Error {
if (contract.status !== 'DRAFT') {
return new Error(
`Contract with ID: ${contract.id} status is not "DRAFT". Cannot convert to unlocked health plan package`
)
}

let healthPlanRevisions: HealthPlanRevisionType[] | Error = []
for (const contractRev of contract.revisions) {
const unlockedHealthPlanFormData: UnlockedHealthPlanFormDataType = {
id: contractRev.id,
createdAt: contractRev.createdAt,
updatedAt: contractRev.updatedAt,
status: contract.status,
stateCode: contract.stateCode,
stateNumber: contract.stateNumber,
programIDs: contractRev.formData.programIDs,
populationCovered: contractRev.formData.populationCovered,
submissionType: contractRev.formData.submissionType,
riskBasedContract: contractRev.formData.riskBasedContract,
submissionDescription: contractRev.formData.submissionDescription,
stateContacts: contractRev.formData.stateContacts,
addtlActuaryCommunicationPreference: undefined,
addtlActuaryContacts: [],
documents: contractRev.formData.supportingDocuments.map((doc) => ({
...doc,
documentCategories: doc.documentCategories.filter(
(category) => category !== undefined
),
})) as SubmissionDocument[],
contractType: contractRev.formData.contractType,
contractExecutionStatus:
contractRev.formData.contractExecutionStatus,
contractDocuments: contractRev.formData.contractDocuments.map(
(doc) => ({
...doc,
documentCategories: doc.documentCategories.filter(
(category) => category !== undefined
),
})
) as SubmissionDocument[],
contractDateStart: contractRev.formData.contractDateStart,
contractDateEnd: contractRev.formData.contractDateEnd,
managedCareEntities: contractRev.formData.managedCareEntities,
federalAuthorities: contractRev.formData.federalAuthorities,
contractAmendmentInfo: {
modifiedProvisions: {
inLieuServicesAndSettings:
contractRev.formData.inLieuServicesAndSettings,
modifiedBenefitsProvided:
contractRev.formData.modifiedBenefitsProvided,
modifiedGeoAreaServed:
contractRev.formData.modifiedGeoAreaServed,
modifiedMedicaidBeneficiaries:
contractRev.formData.modifiedMedicaidBeneficiaries,
modifiedRiskSharingStrategy:
contractRev.formData.modifiedRiskSharingStrategy,
modifiedIncentiveArrangements:
contractRev.formData.modifiedIncentiveArrangements,
modifiedWitholdAgreements:
contractRev.formData.modifiedWitholdAgreements,
modifiedStateDirectedPayments:
contractRev.formData.modifiedStateDirectedPayments,
modifiedPassThroughPayments:
contractRev.formData.modifiedPassThroughPayments,
modifiedPaymentsForMentalDiseaseInstitutions:
contractRev.formData
.modifiedPaymentsForMentalDiseaseInstitutions,
modifiedMedicalLossRatioStandards:
contractRev.formData.modifiedMedicalLossRatioStandards,
modifiedOtherFinancialPaymentIncentive:
contractRev.formData
.modifiedOtherFinancialPaymentIncentive,
modifiedEnrollmentProcess:
contractRev.formData.modifiedEnrollmentProcess,
modifiedGrevienceAndAppeal:
contractRev.formData.modifiedGrevienceAndAppeal,
modifiedNetworkAdequacyStandards:
contractRev.formData.modifiedNetworkAdequacyStandards,
modifiedLengthOfContract:
contractRev.formData.modifiedLengthOfContract,
modifiedNonRiskPaymentArrangements:
contractRev.formData.modifiedNonRiskPaymentArrangements,
},
},
rateInfos: [],
}

const formDataProto = toProtoBuffer(unlockedHealthPlanFormData)

// check that we can encode then decode with no issues
const domainData = toDomain(formDataProto)

// If any revision has en error in decoding we break the loop and return an error
if (domainData instanceof Error) {
healthPlanRevisions = new Error(
`Could not convert contract revision with ID: ${contractRev.id} to health plan package revision: ${domainData}`
)
break
}

const healthPlanRevision: HealthPlanRevisionType = {
id: contractRev.id,
unlockInfo: contractRev.unlockInfo,
submitInfo: contractRev.submitInfo,
createdAt: contractRev.createdAt,
formDataProto,
}

healthPlanRevisions.push(healthPlanRevision)
}

return healthPlanRevisions
}

export {
packageCurrentRevision,
packageStatus,
packageSubmittedAt,
packageSubmitters,
convertContractToUnlockedHealthPlanPackage,
}
1 change: 1 addition & 0 deletions services/app-api/src/domain-models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export {
packageStatus,
packageSubmittedAt,
packageSubmitters,
convertContractToUnlockedHealthPlanPackage,
} from './healthPlanPackage'

export type {
Expand Down
82 changes: 82 additions & 0 deletions services/app-api/src/handlers/proto_to_db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Handler } from 'aws-lambda'
import { initTracer, initMeter } from '../../../uploads/src/lib/otel'
import { configurePostgres } from './configuration'
import { NewPostgresStore } from '../postgres/postgresStore'
import { Store } from '../postgres'
import { HealthPlanRevisionTable } from '@prisma/client'
import { isStoreError, StoreError } from '../postgres/storeError'

export const getDatabaseConnection = async (): Promise<Store> => {
const dbURL = process.env.DATABASE_URL
const secretsManagerSecret = process.env.SECRETS_MANAGER_SECRET

if (!dbURL) {
console.error('DATABASE_URL not set')
throw new Error('Init Error: DATABASE_URL is required to run app-api')
}
if (!secretsManagerSecret) {
console.error('SECRETS_MANAGER_SECRET not set')
}

const pgResult = await configurePostgres(dbURL, secretsManagerSecret)
if (pgResult instanceof Error) {
console.error(
"Init Error: Postgres couldn't be configured in data exporter"
)
throw pgResult
} else {
console.info('Postgres configured in data exporter')
}
const store = NewPostgresStore(pgResult)

return store
}

export const getRevisions = async (
store: Store
): Promise<HealthPlanRevisionTable[]> => {
const result: HealthPlanRevisionTable[] | StoreError =
await store.findAllRevisions()
if (isStoreError(result)) {
console.error(
`Error getting revisions from db ${JSON.stringify(result)}`
)
throw new Error('Error getting records; cannot generate report')
}

return result
}

export const main: Handler = async (event, context) => {
// Check on the values for our required config
const stageName = process.env.stage ?? 'stageNotSet'
const serviceName = `proto_to_db_lambda-${stageName}`
const otelCollectorURL = process.env.REACT_APP_OTEL_COLLECTOR_URL
if (otelCollectorURL) {
initTracer(serviceName, otelCollectorURL)
} else {
console.error(
'Configuration Error: REACT_APP_OTEL_COLLECTOR_URL must be set'
)
}

initMeter(serviceName)
const store = await getDatabaseConnection()

const revisions = await getRevisions(store)
// Get the pkgID from the first revision in the list
const pkgID = revisions[0].pkgID
if (!pkgID) {
console.error('Package ID is missing in the revisions')
throw new Error('Package ID is required')
}
console.info(`Package ID: ${pkgID}`)

return {
statusCode: 200,
body: JSON.stringify({
message: 'Lambda function executed successfully',
packageId: pkgID,
}),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { draftContractRevisionsWithDraftRates } from '../prismaHelpers'

type InsertContractArgsType = {
stateCode: string
populationCovered: PopulationCoverageType
populationCovered?: PopulationCoverageType
programIDs: string[]
riskBasedContract: boolean
riskBasedContract?: boolean
submissionType: SubmissionType
submissionDescription: string
contractType: PrismaContractType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('insertHealthPlanPackage', () => {
submissionType: 'CONTRACT_ONLY' as const,
submissionDescription: 'concurrency state code test',
contractType: 'BASE' as const,
populationCovered: 'MEDICAID' as const,
}

const resultPromises = []
Expand Down
13 changes: 13 additions & 0 deletions services/app-api/src/postgres/postgresStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ import {
insertQuestionResponse,
} from './questionResponse'
import { findAllSupportedStates } from './state'
import { ContractType } from '../domain-models/contractAndRates/contractAndRatesZodSchema'
import {
InsertContractArgsType,
insertDraftContract,
} from './contractAndRates/insertContract'

type Store = {
findPrograms: (
Expand Down Expand Up @@ -116,6 +121,10 @@ type Store = {
questionInput: InsertQuestionResponseArgs,
user: StateUserType
) => Promise<QuestionResponseType | StoreError>

insertDraftContract: (
args: InsertContractArgsType
) => Promise<ContractType | Error>
}

function NewPostgresStore(client: PrismaClient): Store {
Expand Down Expand Up @@ -170,6 +179,10 @@ function NewPostgresStore(client: PrismaClient): Store {
findAllQuestionsByHealthPlanPackage(client, pkgID),
insertQuestionResponse: (questionInput, user) =>
insertQuestionResponse(client, questionInput, user),
/**
* Rates database refactor prisma functions
*/
insertDraftContract: (args) => insertDraftContract(client, args),
}
}

Expand Down
5 changes: 4 additions & 1 deletion services/app-api/src/resolvers/configureResolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ export function configureResolvers(
),
},
Mutation: {
createHealthPlanPackage: createHealthPlanPackageResolver(store),
createHealthPlanPackage: createHealthPlanPackageResolver(
store,
launchDarkly
),
updateHealthPlanFormData: updateHealthPlanFormDataResolver(store),
submitHealthPlanPackage: submitHealthPlanPackageResolver(
store,
Expand Down
Loading

0 comments on commit 0b3e4cf

Please sign in to comment.