Skip to content

Commit

Permalink
feat(awell): start cf and session with one action (#531)
Browse files Browse the repository at this point in the history
  • Loading branch information
nckhell authored Dec 5, 2024
1 parent b89b04e commit e225389
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 0 deletions.
1 change: 1 addition & 0 deletions extensions/awell/v1/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export { updateBaselineInfo } from './updateBaselineInfo'
export { getPatientByIdentifier } from './getPatientByIdentifier'
export { addIdentifierToPatient } from './addIdentifierToPatient'
export { startHostedPagesSession } from './startHostedPagesSession'
export { startCareFlowAndSession } from './startCareFlowAndSession'
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { type DataPointDefinition } from '@awell-health/extensions-core'

export const dataPoints = {
careFlowId: {
key: 'careFlowId',
valueType: 'string',
},
sessionUrl: {
key: 'sessionUrl',
valueType: 'string',
},
} satisfies Record<string, DataPointDefinition>
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { type BaselineInfoInput } from '@awell-health/awell-sdk'
import { type Field, FieldType } from '@awell-health/extensions-core'
import { isEmpty, isNil } from 'lodash'
import { z, type ZodTypeAny } from 'zod'

export const fields = {
careFlowDefinitionId: {
id: 'careFlowDefinitionId',
label: 'Care flow definition ID',
description: 'The definition ID of the care flow to start',
type: FieldType.STRING,
required: true,
},
stakeholderDefinitionId: {
id: 'stakeholderDefinitionId',
label: 'Stakeholder definition ID',
description:
'The definition ID of the stakeholder to start the session for',
type: FieldType.STRING,
required: true,
},
baselineInfo: {
id: 'baselineInfo',
label: 'Baseline info',
description:
'Use baseline info to pass values for data points when starting a care flow. This needs to be an array of objects, please read the documentation for more info.',
type: FieldType.JSON,
required: false,
},
} satisfies Record<string, Field>

export const FieldsValidationSchema = z.object({
careFlowDefinitionId: z.string().min(1),
stakeholderDefinitionId: z.string().min(1),
baselineInfo: z
.optional(z.string())
.transform((str, ctx): BaselineInfoInput[] | undefined => {
if (isNil(str) || isEmpty(str)) return undefined

try {
const parsedJson = JSON.parse(str)

if (isEmpty(parsedJson)) {
return undefined
}

if (!Array.isArray(parsedJson)) {
ctx.addIssue({
code: 'custom',
message: `Baseline info should be an array, it's currently a ${typeof parsedJson}.`,
})
return z.NEVER
}

const allObjectsHaveKeys = parsedJson.every((obj) => {
if (typeof obj !== 'object') {
ctx.addIssue({
code: 'custom',
message: `Item "${String(
obj
)}" in baseline info array is not an object.`,
})
return z.NEVER
}

return 'data_point_definition_id' in obj && 'value' in obj
})

const allObjectValuesAreStrings = parsedJson.every((obj) => {
return Object.values(obj).every((v) => typeof v === 'string')
})

if (!allObjectsHaveKeys) {
ctx.addIssue({
code: 'custom',
message:
'Every object in the baseline info array should (only) have a `data_point_definition_id` and `value` field.',
})
return z.NEVER
}

if (!allObjectValuesAreStrings) {
ctx.addIssue({
code: 'custom',
message:
'Not all baseline info values are strings. Given data point values are polymorphic, the value for a data point should always be sent as a string.',
})
return z.NEVER
}

return parsedJson
} catch (e) {
ctx.addIssue({ code: 'custom', message: 'Invalid JSON.' })
return z.NEVER
}
}),
} satisfies Record<keyof typeof fields, ZodTypeAny>)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { fields, FieldsValidationSchema } from './fields'
export { dataPoints } from './dataPoints'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { startCareFlowAndSession } from './startCareFlowAndSession'
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { TestHelpers } from '@awell-health/extensions-core'
import { generateTestPayload } from '../../../../../tests'
import { startCareFlowAndSession } from './startCareFlowAndSession'

jest.mock('../../sdk/awellSdk')

describe('Start care flow', () => {
const { onComplete, onError, helpers, extensionAction, clearMocks } =
TestHelpers.fromAction(startCareFlowAndSession)
const sdkMock = {
orchestration: {
mutation: jest.fn().mockResolvedValue({
startHostedPathwaySession: {
pathway_id: 'a-care-flow-id',
session_url: 'a-session-url',
},
}),
},
}

helpers.awellSdk = jest.fn().mockResolvedValue(sdkMock)

beforeEach(() => {
clearMocks()
})

test('Should call the onComplete callback', async () => {
await extensionAction.onEvent({
payload: generateTestPayload({
fields: {
careFlowDefinitionId: 'definition-id',
stakeholderDefinitionId: 'stakeholder-id',
},
patient: {
id: '123',
},
settings: {},
}),
onComplete,
onError,
helpers,
})

expect(onComplete).toHaveBeenCalled()
expect(onComplete).toHaveBeenCalledWith({
data_points: {
careFlowId: 'a-care-flow-id',
sessionUrl: 'a-session-url',
},
events: expect.any(Array),
})
expect(helpers.awellSdk).toHaveBeenCalledTimes(1)
expect(sdkMock.orchestration.mutation).toHaveBeenCalledTimes(1)
expect(onError).not.toHaveBeenCalled()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { type Action } from '@awell-health/extensions-core'
import { type settings } from '../../../settings'
import { Category, validate } from '@awell-health/extensions-core'
import { fields, dataPoints, FieldsValidationSchema } from './config'
import { z } from 'zod'
import { addActivityEventLog } from '../../../../../src/lib/awell/addEventLog'

export const startCareFlowAndSession: Action<typeof fields, typeof settings> = {
key: 'startCareFlowAndSession',
category: Category.WORKFLOW,
title: 'Start Care flow and session',
description: 'Start a new care flow and immediately create a session',
fields,
dataPoints,
previewable: false,
onEvent: async ({ payload, onComplete, onError, helpers }): Promise<void> => {
const {
fields: { careFlowDefinitionId, stakeholderDefinitionId, baselineInfo },
} = validate({
schema: z.object({
fields: FieldsValidationSchema,
}),
payload,
})

const sdk = await helpers.awellSdk()

const resp = await sdk.orchestration.mutation({
startHostedPathwaySession: {
__args: {
input: {
patient_id: payload.patient.id,
pathway_definition_id: careFlowDefinitionId,
stakeholder_definition_id: stakeholderDefinitionId,
data_points: baselineInfo,
},
},
success: true,
pathway_id: true,
session_url: true,
},
})

const {
startHostedPathwaySession: { pathway_id, session_url },
} = resp

await onComplete({
data_points: {
careFlowId: pathway_id,
sessionUrl: session_url,
},
events: [
addActivityEventLog({
message: `Care flow started with instance id ${pathway_id}. Session URL is ${session_url}.`,
}),
],
})
},
}

0 comments on commit e225389

Please sign in to comment.