diff --git a/extensions/awell/v1/actions/index.ts b/extensions/awell/v1/actions/index.ts index 20f04e887..52fa60cca 100644 --- a/extensions/awell/v1/actions/index.ts +++ b/extensions/awell/v1/actions/index.ts @@ -8,3 +8,4 @@ export { updateBaselineInfo } from './updateBaselineInfo' export { getPatientByIdentifier } from './getPatientByIdentifier' export { addIdentifierToPatient } from './addIdentifierToPatient' export { startHostedPagesSession } from './startHostedPagesSession' +export { startCareFlowAndSession } from './startCareFlowAndSession' diff --git a/extensions/awell/v1/actions/startCareFlowAndSession/config/dataPoints.ts b/extensions/awell/v1/actions/startCareFlowAndSession/config/dataPoints.ts new file mode 100644 index 000000000..4804baa67 --- /dev/null +++ b/extensions/awell/v1/actions/startCareFlowAndSession/config/dataPoints.ts @@ -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 diff --git a/extensions/awell/v1/actions/startCareFlowAndSession/config/fields.ts b/extensions/awell/v1/actions/startCareFlowAndSession/config/fields.ts new file mode 100644 index 000000000..dcfec96bc --- /dev/null +++ b/extensions/awell/v1/actions/startCareFlowAndSession/config/fields.ts @@ -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 + +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) diff --git a/extensions/awell/v1/actions/startCareFlowAndSession/config/index.ts b/extensions/awell/v1/actions/startCareFlowAndSession/config/index.ts new file mode 100644 index 000000000..cd36e4bd6 --- /dev/null +++ b/extensions/awell/v1/actions/startCareFlowAndSession/config/index.ts @@ -0,0 +1,2 @@ +export { fields, FieldsValidationSchema } from './fields' +export { dataPoints } from './dataPoints' diff --git a/extensions/awell/v1/actions/startCareFlowAndSession/index.ts b/extensions/awell/v1/actions/startCareFlowAndSession/index.ts new file mode 100644 index 000000000..54a36ddb1 --- /dev/null +++ b/extensions/awell/v1/actions/startCareFlowAndSession/index.ts @@ -0,0 +1 @@ +export { startCareFlowAndSession } from './startCareFlowAndSession' diff --git a/extensions/awell/v1/actions/startCareFlowAndSession/startCareFlowAndSession.test.ts b/extensions/awell/v1/actions/startCareFlowAndSession/startCareFlowAndSession.test.ts new file mode 100644 index 000000000..2d11bd20c --- /dev/null +++ b/extensions/awell/v1/actions/startCareFlowAndSession/startCareFlowAndSession.test.ts @@ -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() + }) +}) diff --git a/extensions/awell/v1/actions/startCareFlowAndSession/startCareFlowAndSession.ts b/extensions/awell/v1/actions/startCareFlowAndSession/startCareFlowAndSession.ts new file mode 100644 index 000000000..fab1cff8a --- /dev/null +++ b/extensions/awell/v1/actions/startCareFlowAndSession/startCareFlowAndSession.ts @@ -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 = { + 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 => { + 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}.`, + }), + ], + }) + }, +}