From ac31d248154243b449f855dded0bf7bd93fb670f Mon Sep 17 00:00:00 2001 From: Pyry Date: Mon, 6 Jan 2025 20:16:56 +0900 Subject: [PATCH 01/16] feat: notify CC about actions --- .../registration/fhir/fhir-bundle-modifier.ts | 27 ------ .../workflow/src/records/handler/create.ts | 13 +++ .../workflow/src/records/state-transitions.ts | 2 +- .../workflow/src/utils/country-config-api.ts | 86 +++++++++++++++++++ 4 files changed, 100 insertions(+), 28 deletions(-) create mode 100644 packages/workflow/src/utils/country-config-api.ts diff --git a/packages/workflow/src/features/registration/fhir/fhir-bundle-modifier.ts b/packages/workflow/src/features/registration/fhir/fhir-bundle-modifier.ts index 08d6f5d5af3..18f740b1075 100644 --- a/packages/workflow/src/features/registration/fhir/fhir-bundle-modifier.ts +++ b/packages/workflow/src/features/registration/fhir/fhir-bundle-modifier.ts @@ -11,7 +11,6 @@ import { UUID, logger } from '@opencrvs/commons' import { - Bundle, Composition, Patient, Practitioner, @@ -26,7 +25,6 @@ import { SupportedPatientIdentifierCode, ValidRecord } from '@opencrvs/commons/types' -import { COUNTRY_CONFIG_URL } from '@workflow/constants' import { OPENCRVS_SPECIFICATION_URL, RegStatus @@ -39,31 +37,6 @@ import { import { getPractitionerRef } from '@workflow/features/user/utils' import { ITokenPayload } from '@workflow/utils/auth-utils' import { isEqual } from 'lodash' -import fetch from 'node-fetch' - -export async function invokeRegistrationValidation( - bundle: Saved, - headers: Record -): Promise { - const res = await fetch( - new URL('event-registration', COUNTRY_CONFIG_URL).toString(), - { - method: 'POST', - body: JSON.stringify(bundle), - headers: { - 'Content-Type': 'application/json', - ...headers - } - } - ) - if (!res.ok) { - const errorText = await res.text() - throw new Error( - `Error calling country configuration event-registration [${res.statusText} ${res.status}]: ${errorText}` - ) - } - return bundle -} export async function setupRegistrationWorkflow( taskResource: Task, diff --git a/packages/workflow/src/records/handler/create.ts b/packages/workflow/src/records/handler/create.ts index 73a69ca43eb..882e6eb7536 100644 --- a/packages/workflow/src/records/handler/create.ts +++ b/packages/workflow/src/records/handler/create.ts @@ -75,6 +75,7 @@ import { toWaitingForExternalValidationState } from '@workflow/records/state-transitions' import { logger, UUID } from '@opencrvs/commons' +import { notifyForAction } from '@workflow/utils/country-config-api' const requestSchema = z.object({ event: z.custom(), @@ -348,6 +349,8 @@ export default async function createRecordHandler( /* * We need to initiate registration for a * record in waiting validation state + * + * `initiateRegistration` notifies country configuration about the event which then either confirms or rejects the record. */ if (isWaitingExternalValidation(record)) { const rejectedOrWaitingValidationRecord = await initiateRegistration( @@ -360,6 +363,16 @@ export default async function createRecordHandler( await indexBundle(rejectedOrWaitingValidationRecord, token) await auditEvent('sent-for-updates', record, token) } + } else { + /* + * Notify country configuration about the event so that countries can hook into actions like "sent-for-approval" + */ + await notifyForAction({ + event, + action: eventAction, + record, + headers: request.headers + }) } return { diff --git a/packages/workflow/src/records/state-transitions.ts b/packages/workflow/src/records/state-transitions.ts index b6ac688a16b..7a42c6f8537 100644 --- a/packages/workflow/src/records/state-transitions.ts +++ b/packages/workflow/src/records/state-transitions.ts @@ -58,7 +58,6 @@ import { SECTION_CODE } from '@workflow/features/events/utils' import { - invokeRegistrationValidation, setupLastRegOffice, setupLastRegUser, updatePatientIdentifierWithRN, @@ -109,6 +108,7 @@ import { } from '@workflow/records/fhir' import { REG_NUMBER_GENERATION_FAILED } from '@workflow/features/registration/fhir/constants' import { tokenExchangeHandler } from './token-exchange-handler' +import { invokeRegistrationValidation } from '@workflow/utils/country-config-api' export async function toCorrected( record: RegisteredRecord | CertifiedRecord | IssuedRecord, diff --git a/packages/workflow/src/utils/country-config-api.ts b/packages/workflow/src/utils/country-config-api.ts new file mode 100644 index 00000000000..2b4be0e13b3 --- /dev/null +++ b/packages/workflow/src/utils/country-config-api.ts @@ -0,0 +1,86 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * OpenCRVS is also distributed under the terms of the Civil Registration + * & Healthcare Disclaimer located at http://opencrvs.org/license. + * + * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. + */ +import { logger } from '@opencrvs/commons' +import { Bundle, EVENT_TYPE, Saved, ValidRecord } from '@opencrvs/commons/types' +import { COUNTRY_CONFIG_URL } from '@workflow/constants' +import fetch from 'node-fetch' + +const ACTION_NOTIFY_URL = (event: EVENT_TYPE, action: string) => + new URL( + `event/${event.toLowerCase() as Lowercase}/action/${action}`, + COUNTRY_CONFIG_URL + ) + +export async function notifyForAction({ + event, + action, + record, + headers +}: { + event: EVENT_TYPE + action: string + record: Saved + headers: Record +}) { + const response = await fetch(ACTION_NOTIFY_URL(event, action), { + method: 'POST', + body: JSON.stringify(record), + headers: { + 'Content-Type': 'application/json', + ...headers + } + }) + + if (response.status === 404) { + logger.debug( + `Non-issue: No country configuration endpoint for POST ${ACTION_NOTIFY_URL( + event, + action + )}. To optionally hook into this action, you must implement the corresponding action route in your country configuration.` + ) + return + } + + if (!response.ok) { + throw new Error( + `Error notifying country-config as POST ${ACTION_NOTIFY_URL( + event, + action + )} [${response.statusText} ${response.status}]: ${response.text()}` + ) + } + + return response +} + +export async function invokeRegistrationValidation( + bundle: Saved, + headers: Record +): Promise { + const res = await fetch( + new URL('event-registration', COUNTRY_CONFIG_URL).toString(), + { + method: 'POST', + body: JSON.stringify(bundle), + headers: { + 'Content-Type': 'application/json', + ...headers + } + } + ) + if (!res.ok) { + const errorText = await res.text() + throw new Error( + `Error calling country configuration event-registration [${res.statusText} ${res.status}]: ${errorText}` + ) + } + return bundle +} From 57b76be52052b3f232eb3fe024c0eefea75120c3 Mon Sep 17 00:00:00 2001 From: Pyry Date: Tue, 7 Jan 2025 19:09:08 +0900 Subject: [PATCH 02/16] refactor: use 'getValidRecordById' as it has the same statuses --- .../src/records/handler/upsert-identifiers.ts | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/workflow/src/records/handler/upsert-identifiers.ts b/packages/workflow/src/records/handler/upsert-identifiers.ts index 2d2ce4e3345..2e83caadda4 100644 --- a/packages/workflow/src/records/handler/upsert-identifiers.ts +++ b/packages/workflow/src/records/handler/upsert-identifiers.ts @@ -10,7 +10,7 @@ */ import { getToken } from '@workflow/utils/auth-utils' import * as Hapi from '@hapi/hapi' -import { getRecordById } from '@workflow/records/index' +import { getValidRecordById } from '@workflow/records/index' import { indexBundle } from '@workflow/records/search' import { toIdentifierUpserted } from '@workflow/records/state-transitions' import { SupportedPatientIdentifierCode } from '@opencrvs/commons/types' @@ -33,21 +33,9 @@ export async function upsertRegistrationHandler( const compositionId = request.params.id const { identifiers } = request.payload as EventRegistrationPayload - const savedRecord = await getRecordById( + const savedRecord = await getValidRecordById( compositionId, request.headers.authorization, - [ - 'ARCHIVED', - 'CERTIFIED', - 'CORRECTION_REQUESTED', - 'IN_PROGRESS', - 'READY_FOR_REVIEW', - 'ISSUED', - 'REGISTERED', - 'REJECTED', - 'VALIDATED', - 'WAITING_VALIDATION' - ], true ) From 5bbcebcf3f558c992f9b0a86f9a4afb37c8d9203 Mon Sep 17 00:00:00 2001 From: Pyry Date: Tue, 7 Jan 2025 19:09:37 +0900 Subject: [PATCH 03/16] feat: workflow endpoint allowing editing field ids --- packages/workflow/src/config/routes.ts | 12 ++- .../src/records/handler/update-field.ts | 74 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 packages/workflow/src/records/handler/update-field.ts diff --git a/packages/workflow/src/config/routes.ts b/packages/workflow/src/config/routes.ts index 124a85828d1..6a920db057c 100644 --- a/packages/workflow/src/config/routes.ts +++ b/packages/workflow/src/config/routes.ts @@ -30,6 +30,7 @@ import { requestCorrectionRoute } from '@workflow/records/handler/correction/req import { makeCorrectionRoute } from '@workflow/records/handler/correction/make-correction' import { eventNotificationHandler } from '@workflow/records/handler/eventNotificationHandler' import { upsertRegistrationHandler } from '@workflow/records/handler/upsert-identifiers' +import { updateField } from '@workflow/records/handler/update-field' export const getRoutes = () => { const routes = [ @@ -163,7 +164,16 @@ export const getRoutes = () => { approveCorrectionRoute, rejectCorrectionRoute, requestCorrectionRoute, - makeCorrectionRoute + makeCorrectionRoute, + { + method: 'POST', + path: '/records/{id}/update-field', + handler: updateField, + config: { + tags: ['api'], + description: 'Update a single field in a registration' + } + } ] return routes diff --git a/packages/workflow/src/records/handler/update-field.ts b/packages/workflow/src/records/handler/update-field.ts new file mode 100644 index 00000000000..7ccd6a5c01b --- /dev/null +++ b/packages/workflow/src/records/handler/update-field.ts @@ -0,0 +1,74 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * OpenCRVS is also distributed under the terms of the Civil Registration + * & Healthcare Disclaimer located at http://opencrvs.org/license. + * + * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. + */ +import * as Hapi from '@hapi/hapi' +import { getValidRecordById } from '@workflow/records/index' +import { sendBundleToHearth } from '@workflow/records/fhir' +import { isQuestionnaireResponse } from '@opencrvs/commons/types' + +interface FieldInput { + fieldId: string + valueString?: string + valueBoolean?: boolean +} + +export async function updateField( + request: Hapi.Request, + h: Hapi.ResponseToolkit +) { + const compositionId = request.params.id + const { fieldId, valueString, valueBoolean } = request.payload as FieldInput + + const savedRecord = await getValidRecordById( + compositionId, + request.headers.authorization, + false + ) + const recordResources = savedRecord.entry.map((x) => x.resource) + + // There is only one QuestionnaireResponse in the record + const questionnaireResponseResource = recordResources.filter((resource) => + isQuestionnaireResponse(resource) + )[0] + + const questionnaireResponseItems = questionnaireResponseResource.item?.map( + (item) => { + if (item.text === fieldId && valueString) { + item.answer![0].valueString = valueString + } + + if (item.text === fieldId && valueBoolean) { + item.answer![0].valueBoolean = valueBoolean + } + + return item + } + ) + + const updatedQuestionnaireResponseResource = { + resource: { + ...questionnaireResponseResource, + item: questionnaireResponseItems + } + } + + const updatedRecord = { + ...savedRecord, + entry: [ + ...savedRecord.entry.filter( + ({ resource }) => !isQuestionnaireResponse(resource) + ), + updatedQuestionnaireResponseResource + ] + } + + await sendBundleToHearth(updatedRecord) + return h.response(updatedRecord).code(200) +} From 91f09e4bc9e66aa4ecacd00f3c813ac5baf0d5b4 Mon Sep 17 00:00:00 2001 From: Pyry Date: Tue, 7 Jan 2025 19:45:05 +0900 Subject: [PATCH 04/16] fix(tests): mock country config event notification hook --- packages/workflow/src/records/handler/create.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/workflow/src/records/handler/create.test.ts b/packages/workflow/src/records/handler/create.test.ts index 6842c6cf7c5..900814838ca 100644 --- a/packages/workflow/src/records/handler/create.test.ts +++ b/packages/workflow/src/records/handler/create.test.ts @@ -150,6 +150,14 @@ describe('Create record endpoint', () => { }) ) + // mock country config event hook not being implemented (404) + mswServer.use( + rest.post( + 'http://localhost:3040/event/birth/action/sent-notification-for-review', + (_, res, ctx) => res(ctx.status(404)) + ) + ) + const res = await server.server.inject({ method: 'POST', url: '/create-record', From a41d2e745bfd03824d38e6ac9249a3401ae760b8 Mon Sep 17 00:00:00 2001 From: naftis Date: Wed, 22 Jan 2025 14:39:25 +0200 Subject: [PATCH 05/16] feat: initial graphql endpoint for update field --- .../features/registration/root-resolvers.ts | 12 ++++++++++- .../src/features/registration/schema.graphql | 7 +++++++ packages/gateway/src/graphql/schema.d.ts | 21 +++++++++++++++++++ packages/gateway/src/graphql/schema.graphql | 7 +++++++ packages/gateway/src/workflow/index.ts | 21 +++++++++++++++++++ 5 files changed, 67 insertions(+), 1 deletion(-) diff --git a/packages/gateway/src/features/registration/root-resolvers.ts b/packages/gateway/src/features/registration/root-resolvers.ts index 385f40d7c5b..9382ffc44db 100644 --- a/packages/gateway/src/features/registration/root-resolvers.ts +++ b/packages/gateway/src/features/registration/root-resolvers.ts @@ -61,7 +61,8 @@ import { markNotADuplicate, rejectRegistration, confirmRegistration, - upsertRegistrationIdentifier + upsertRegistrationIdentifier, + updateField } from '@gateway/workflow/index' import { getRecordById } from '@gateway/records' import { UnassignError, UserInputError } from '@gateway/utils/graphql-errors' @@ -657,6 +658,15 @@ export const resolvers: GQLResolver = { } catch (error) { throw new Error(`Failed to confirm registration: ${error.message}`) } + }, + async updateField(_, { id, details }, { headers: authHeader }) { + if (!hasRecordAccess(authHeader, id)) { + throw new Error('User does not have access to the record') + } + + const record = await updateField(id, authHeader, details) + + return record.id } } } diff --git a/packages/gateway/src/features/registration/schema.graphql b/packages/gateway/src/features/registration/schema.graphql index 676a05f64ac..87584b8cb8d 100644 --- a/packages/gateway/src/features/registration/schema.graphql +++ b/packages/gateway/src/features/registration/schema.graphql @@ -580,6 +580,12 @@ input RejectRegistrationInput { comment: String } +input UpdateFieldInput { + fieldId: String! + valueString: String + valueBoolean: Boolean +} + type Mutation { # Generic correction handlers for all event types # Applying a correction request is made on a event level as payload is dependant on event type @@ -663,4 +669,5 @@ type Mutation { identifierType: String! identifierValue: String! ): ID! + updateField(id: ID!, details: UpdateFieldInput!): ID! } diff --git a/packages/gateway/src/graphql/schema.d.ts b/packages/gateway/src/graphql/schema.d.ts index a0e94933792..952bb1a78c1 100644 --- a/packages/gateway/src/graphql/schema.d.ts +++ b/packages/gateway/src/graphql/schema.d.ts @@ -88,6 +88,7 @@ export interface GQLMutation { confirmRegistration: string rejectRegistration: string upsertRegistrationIdentifier: string + updateField: string createOrUpdateUser: GQLUser activateUser?: string changePassword?: string @@ -590,6 +591,12 @@ export interface GQLRejectRegistrationInput { comment?: string } +export interface GQLUpdateFieldInput { + fieldId: string + valueString?: string + valueBoolean?: boolean +} + export interface GQLUserInput { id?: string name: Array @@ -2472,6 +2479,7 @@ export interface GQLMutationTypeResolver { confirmRegistration?: MutationToConfirmRegistrationResolver rejectRegistration?: MutationToRejectRegistrationResolver upsertRegistrationIdentifier?: MutationToUpsertRegistrationIdentifierResolver + updateField?: MutationToUpdateFieldResolver createOrUpdateUser?: MutationToCreateOrUpdateUserResolver activateUser?: MutationToActivateUserResolver changePassword?: MutationToChangePasswordResolver @@ -3053,6 +3061,19 @@ export interface MutationToUpsertRegistrationIdentifierResolver< ): TResult } +export interface MutationToUpdateFieldArgs { + id: string + details: GQLUpdateFieldInput +} +export interface MutationToUpdateFieldResolver { + ( + parent: TParent, + args: MutationToUpdateFieldArgs, + context: Context, + info: GraphQLResolveInfo + ): TResult +} + export interface MutationToCreateOrUpdateUserArgs { user: GQLUserInput } diff --git a/packages/gateway/src/graphql/schema.graphql b/packages/gateway/src/graphql/schema.graphql index e54010f41eb..9d6b9345fc1 100644 --- a/packages/gateway/src/graphql/schema.graphql +++ b/packages/gateway/src/graphql/schema.graphql @@ -223,6 +223,7 @@ type Mutation { identifierType: String! identifierValue: String! ): ID! + updateField(id: ID!, details: UpdateFieldInput!): ID! createOrUpdateUser(user: UserInput!): User! activateUser( userId: String! @@ -715,6 +716,12 @@ input RejectRegistrationInput { comment: String } +input UpdateFieldInput { + fieldId: String! + valueString: String + valueBoolean: Boolean +} + input UserInput { id: ID name: [HumanNameInput!]! diff --git a/packages/gateway/src/workflow/index.ts b/packages/gateway/src/workflow/index.ts index 841b6a3a4a4..9d7e0345261 100644 --- a/packages/gateway/src/workflow/index.ts +++ b/packages/gateway/src/workflow/index.ts @@ -304,6 +304,27 @@ export async function upsertRegistrationIdentifier( return taskEntry } +type UpdateFieldInput = { + fieldId: string + valueString?: string + valueBoolean?: boolean +} + +export async function updateField( + id: string, + authHeader: IAuthHeader, + details: UpdateFieldInput +) { + const res = await createRequest( + 'POST', + `/records/${id}/update-field`, + authHeader, + details + ) + + return res +} + export async function archiveRegistration( id: string, authHeader: IAuthHeader, From 57e0cfa764ba97b0b62529761008f4195974daf0 Mon Sep 17 00:00:00 2001 From: naftis Date: Wed, 22 Jan 2025 14:58:30 +0200 Subject: [PATCH 06/16] chore: test and fix errors, return boolean --- packages/gateway/src/features/registration/root-resolvers.ts | 4 ++-- packages/gateway/src/features/registration/schema.graphql | 2 +- packages/gateway/src/graphql/schema.d.ts | 2 +- packages/gateway/src/graphql/schema.graphql | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/gateway/src/features/registration/root-resolvers.ts b/packages/gateway/src/features/registration/root-resolvers.ts index 9382ffc44db..1df6edcf403 100644 --- a/packages/gateway/src/features/registration/root-resolvers.ts +++ b/packages/gateway/src/features/registration/root-resolvers.ts @@ -664,9 +664,9 @@ export const resolvers: GQLResolver = { throw new Error('User does not have access to the record') } - const record = await updateField(id, authHeader, details) + await updateField(id, authHeader, details) - return record.id + return true } } } diff --git a/packages/gateway/src/features/registration/schema.graphql b/packages/gateway/src/features/registration/schema.graphql index 87584b8cb8d..0193b18caa9 100644 --- a/packages/gateway/src/features/registration/schema.graphql +++ b/packages/gateway/src/features/registration/schema.graphql @@ -669,5 +669,5 @@ type Mutation { identifierType: String! identifierValue: String! ): ID! - updateField(id: ID!, details: UpdateFieldInput!): ID! + updateField(id: ID!, details: UpdateFieldInput!): Boolean! } diff --git a/packages/gateway/src/graphql/schema.d.ts b/packages/gateway/src/graphql/schema.d.ts index 952bb1a78c1..d3eeb5f2a3f 100644 --- a/packages/gateway/src/graphql/schema.d.ts +++ b/packages/gateway/src/graphql/schema.d.ts @@ -88,7 +88,7 @@ export interface GQLMutation { confirmRegistration: string rejectRegistration: string upsertRegistrationIdentifier: string - updateField: string + updateField: boolean createOrUpdateUser: GQLUser activateUser?: string changePassword?: string diff --git a/packages/gateway/src/graphql/schema.graphql b/packages/gateway/src/graphql/schema.graphql index 9d6b9345fc1..7175f8c2d3b 100644 --- a/packages/gateway/src/graphql/schema.graphql +++ b/packages/gateway/src/graphql/schema.graphql @@ -223,7 +223,7 @@ type Mutation { identifierType: String! identifierValue: String! ): ID! - updateField(id: ID!, details: UpdateFieldInput!): ID! + updateField(id: ID!, details: UpdateFieldInput!): Boolean! createOrUpdateUser(user: UserInput!): User! activateUser( userId: String! From fd33fefa65fd547227ddcaf613f4ff75f0d8ad76 Mon Sep 17 00:00:00 2001 From: naftis Date: Wed, 22 Jan 2025 16:18:51 +0200 Subject: [PATCH 07/16] feat: update the action notification according to events v2 --- .../workflow/src/utils/country-config-api.ts | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/packages/workflow/src/utils/country-config-api.ts b/packages/workflow/src/utils/country-config-api.ts index 2b4be0e13b3..5bce92732af 100644 --- a/packages/workflow/src/utils/country-config-api.ts +++ b/packages/workflow/src/utils/country-config-api.ts @@ -8,17 +8,17 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { logger } from '@opencrvs/commons' import { Bundle, EVENT_TYPE, Saved, ValidRecord } from '@opencrvs/commons/types' import { COUNTRY_CONFIG_URL } from '@workflow/constants' import fetch from 'node-fetch' const ACTION_NOTIFY_URL = (event: EVENT_TYPE, action: string) => - new URL( - `event/${event.toLowerCase() as Lowercase}/action/${action}`, - COUNTRY_CONFIG_URL - ) + new URL(`events/${event}/actions/${action}`, COUNTRY_CONFIG_URL) +/** + * Notifies legacy events to country configuration + * @deprecated + */ export async function notifyForAction({ event, action, @@ -34,21 +34,11 @@ export async function notifyForAction({ method: 'POST', body: JSON.stringify(record), headers: { - 'Content-Type': 'application/json', + 'Content-Type': 'application/fhir+json', ...headers } }) - if (response.status === 404) { - logger.debug( - `Non-issue: No country configuration endpoint for POST ${ACTION_NOTIFY_URL( - event, - action - )}. To optionally hook into this action, you must implement the corresponding action route in your country configuration.` - ) - return - } - if (!response.ok) { throw new Error( `Error notifying country-config as POST ${ACTION_NOTIFY_URL( From 010960700520f18f29054493fbbd70a91755483e Mon Sep 17 00:00:00 2001 From: naftis Date: Wed, 22 Jan 2025 16:26:22 +0200 Subject: [PATCH 08/16] fix: update test to conform events v2 notification format --- packages/workflow/src/records/handler/create.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/workflow/src/records/handler/create.test.ts b/packages/workflow/src/records/handler/create.test.ts index 900814838ca..e3eb1e48977 100644 --- a/packages/workflow/src/records/handler/create.test.ts +++ b/packages/workflow/src/records/handler/create.test.ts @@ -153,8 +153,8 @@ describe('Create record endpoint', () => { // mock country config event hook not being implemented (404) mswServer.use( rest.post( - 'http://localhost:3040/event/birth/action/sent-notification-for-review', - (_, res, ctx) => res(ctx.status(404)) + 'http://localhost:3040/events/BIRTH/actions/sent-notification-for-review', + (_, res, ctx) => res(ctx.status(200)) ) ) From 7bd2ce0accccce34a85824168a20a818c17088e4 Mon Sep 17 00:00:00 2001 From: naftis Date: Thu, 23 Jan 2025 16:19:18 +0200 Subject: [PATCH 09/16] feat: token exchange on notify --- packages/workflow/src/records/handler/create.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/workflow/src/records/handler/create.ts b/packages/workflow/src/records/handler/create.ts index 882e6eb7536..7c5fde9c148 100644 --- a/packages/workflow/src/records/handler/create.ts +++ b/packages/workflow/src/records/handler/create.ts @@ -76,6 +76,7 @@ import { } from '@workflow/records/state-transitions' import { logger, UUID } from '@opencrvs/commons' import { notifyForAction } from '@workflow/utils/country-config-api' +import { tokenExchangeHandler } from '@workflow/records/token-exchange-handler' const requestSchema = z.object({ event: z.custom(), @@ -367,11 +368,19 @@ export default async function createRecordHandler( /* * Notify country configuration about the event so that countries can hook into actions like "sent-for-approval" */ + const recordSpecificToken = await tokenExchangeHandler( + token, + request.headers, + getComposition(record).id + ) await notifyForAction({ event, action: eventAction, record, - headers: request.headers + headers: { + ...request.headers, + authorization: `Bearer ${recordSpecificToken.access_token}` + } }) } From 6656b725084edf54322a5d2d6507c081b3368bd9 Mon Sep 17 00:00:00 2001 From: Pyry Rouvila Date: Fri, 24 Jan 2025 10:13:33 +0200 Subject: [PATCH 10/16] update: test comment --- packages/workflow/src/records/handler/create.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/workflow/src/records/handler/create.test.ts b/packages/workflow/src/records/handler/create.test.ts index e3eb1e48977..d04ad947ce3 100644 --- a/packages/workflow/src/records/handler/create.test.ts +++ b/packages/workflow/src/records/handler/create.test.ts @@ -150,7 +150,7 @@ describe('Create record endpoint', () => { }) ) - // mock country config event hook not being implemented (404) + // mock country config event action hook returning a basic 200 mswServer.use( rest.post( 'http://localhost:3040/events/BIRTH/actions/sent-notification-for-review', From 59f1b1d8d720a67389cf5e15f2d49bad4932b961 Mon Sep 17 00:00:00 2001 From: naftis Date: Fri, 24 Jan 2025 10:18:53 +0200 Subject: [PATCH 11/16] chore(changelog): add update field --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc17731e6dd..d4dfcfa677b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Introduced a new customisable UI component: Banner [#8276](https://github.com/opencrvs/opencrvs-core/issues/8276) - Auth now allows exchanging user's token for a new record-specific token [#7728](https://github.com/opencrvs/opencrvs-core/issues/7728) - A new GraphQL mutation `upsertRegistrationIdentifier` is added to allow updating the patient identifiers of a registration record such as NID [#8034](https://github.com/opencrvs/opencrvs-core/pull/8034) +- A new GraphQL mutation `updateField` is added to allow updating any field in a record [#8291](https://github.com/opencrvs/opencrvs-core/pull/8291) - Updated GraphQL mutation `confirmRegistration` to allow adding a `comment` for record audit [#8197](https://github.com/opencrvs/opencrvs-core/pull/8197) ### Improvements From 516655986ec19223b129dc2e6c1e541e84c0b87c Mon Sep 17 00:00:00 2001 From: naftis Date: Fri, 24 Jan 2025 10:35:54 +0200 Subject: [PATCH 12/16] fix: rename config to options --- packages/workflow/src/config/routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/workflow/src/config/routes.ts b/packages/workflow/src/config/routes.ts index a76f98dccc7..85bdb67ac91 100644 --- a/packages/workflow/src/config/routes.ts +++ b/packages/workflow/src/config/routes.ts @@ -181,7 +181,7 @@ export const getRoutes = () => { method: 'POST', path: '/records/{id}/update-field', handler: updateField, - config: { + options: { tags: ['api'], description: 'Update a single field in a registration' } From 13cba43ddb070293dad68832d0bf774fefe81db3 Mon Sep 17 00:00:00 2001 From: naftis Date: Fri, 24 Jan 2025 10:58:56 +0200 Subject: [PATCH 13/16] refactor: rename token-exchange away from 'handler' as it's not a http handler --- packages/workflow/src/records/handler/create.ts | 4 ++-- packages/workflow/src/records/state-transitions.ts | 4 ++-- .../records/{token-exchange-handler.ts => token-exchange.ts} | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename packages/workflow/src/records/{token-exchange-handler.ts => token-exchange.ts} (96%) diff --git a/packages/workflow/src/records/handler/create.ts b/packages/workflow/src/records/handler/create.ts index 7c5fde9c148..7d7bf71832d 100644 --- a/packages/workflow/src/records/handler/create.ts +++ b/packages/workflow/src/records/handler/create.ts @@ -76,7 +76,7 @@ import { } from '@workflow/records/state-transitions' import { logger, UUID } from '@opencrvs/commons' import { notifyForAction } from '@workflow/utils/country-config-api' -import { tokenExchangeHandler } from '@workflow/records/token-exchange-handler' +import { getRecordSpecificToken } from '@workflow/records/token-exchange' const requestSchema = z.object({ event: z.custom(), @@ -368,7 +368,7 @@ export default async function createRecordHandler( /* * Notify country configuration about the event so that countries can hook into actions like "sent-for-approval" */ - const recordSpecificToken = await tokenExchangeHandler( + const recordSpecificToken = await getRecordSpecificToken( token, request.headers, getComposition(record).id diff --git a/packages/workflow/src/records/state-transitions.ts b/packages/workflow/src/records/state-transitions.ts index 90d1041f5f9..9d1ce5c3c6a 100644 --- a/packages/workflow/src/records/state-transitions.ts +++ b/packages/workflow/src/records/state-transitions.ts @@ -109,7 +109,7 @@ import { getPractitionerRoleFromToken } from '@workflow/records/fhir' import { REG_NUMBER_GENERATION_FAILED } from '@workflow/features/registration/fhir/constants' -import { tokenExchangeHandler } from './token-exchange-handler' +import { getRecordSpecificToken } from './token-exchange' import { invokeRegistrationValidation } from '@workflow/utils/country-config-api' export async function toCorrected( @@ -514,7 +514,7 @@ export async function initiateRegistration( ): Promise { try { const composition = getComposition(record) - const recordSpecificToken = await tokenExchangeHandler( + const recordSpecificToken = await getRecordSpecificToken( token, headers, composition.id diff --git a/packages/workflow/src/records/token-exchange-handler.ts b/packages/workflow/src/records/token-exchange.ts similarity index 96% rename from packages/workflow/src/records/token-exchange-handler.ts rename to packages/workflow/src/records/token-exchange.ts index 7467f88d625..d3897ff1ca1 100644 --- a/packages/workflow/src/records/token-exchange-handler.ts +++ b/packages/workflow/src/records/token-exchange.ts @@ -11,7 +11,7 @@ import { AUTH_URL } from '@workflow/constants' import fetch from 'node-fetch' -export async function tokenExchangeHandler( +export async function getRecordSpecificToken( token: string, headers: Record, recordId: string From 504acf5c41426d0fa8e7a88c076e88150741e56e Mon Sep 17 00:00:00 2001 From: naftis Date: Fri, 24 Jan 2025 11:14:15 +0200 Subject: [PATCH 14/16] fix: test not mocking the token exchange --- packages/workflow/src/__mocks__/constants.ts | 1 + .../src/records/handler/create.test.ts | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/packages/workflow/src/__mocks__/constants.ts b/packages/workflow/src/__mocks__/constants.ts index 76446ad0368..90c7807f4aa 100644 --- a/packages/workflow/src/__mocks__/constants.ts +++ b/packages/workflow/src/__mocks__/constants.ts @@ -14,6 +14,7 @@ export const USER_MANAGEMENT_URL = 'http://localhost:3030/' export const SEARCH_URL = 'http://localhost:9090/' export const METRICS_URL = 'http://localhost:1050/' export const DOCUMENTS_URL = 'http://localhost:9050' +export const AUTH_URL = 'http://localhost:4040/' export const NOTIFICATION_SERVICE_URL = 'http://localhost:2020/' export const APPLICATION_CONFIG_URL = 'http://localhost:2021/' export const COUNTRY_CONFIG_URL = 'http://localhost:3040' diff --git a/packages/workflow/src/records/handler/create.test.ts b/packages/workflow/src/records/handler/create.test.ts index d85078e985d..03274828a63 100644 --- a/packages/workflow/src/records/handler/create.test.ts +++ b/packages/workflow/src/records/handler/create.test.ts @@ -74,6 +74,29 @@ describe('Create record endpoint', () => { } ) + // Notification endpoint mockcall + mswServer.use( + rest.get('http://localhost:3040/record-notification', (_, res, ctx) => { + return res(ctx.json({})) + }) + ) + + // Token exchange mock call + mswServer.use( + // The actual more verbose query below, but for simplicity we can keep simpler one unless this causes issues: + + // ?grant_type=urn:opencrvs:oauth:grant-type:token-exchange&subject_token=${token}&subject_token_type=urn:ietf:params:oauth:token-type:access_token + // &requested_token_type=urn:opencrvs:oauth:token-type:single_record_token&record_id=${recordId} + + rest.post(`http://localhost:4040/token`, (_, res, ctx) => { + return res( + ctx.json({ + access_token: 'some-token' + }) + ) + }) + ) + // used for checking already created composition with // the same draftId mswServer.use( From 93622a09bb96d5737575829b1ec1d3412faacfd7 Mon Sep 17 00:00:00 2001 From: naftis Date: Fri, 24 Jan 2025 11:23:20 +0200 Subject: [PATCH 15/16] chore: remove unused function maybe Knip is satisfied after this --- .../src/features/registration/fhir/fhir-utils.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/workflow/src/features/registration/fhir/fhir-utils.ts b/packages/workflow/src/features/registration/fhir/fhir-utils.ts index 0c635edad76..c3634f5aa6d 100644 --- a/packages/workflow/src/features/registration/fhir/fhir-utils.ts +++ b/packages/workflow/src/features/registration/fhir/fhir-utils.ts @@ -312,19 +312,6 @@ export function generateEmptyBundle(): Bundle { } } -export async function fetchExistingRegStatusCode(taskId: string | undefined) { - const existingTaskResource: Task = await getFromFhir(`/Task/${taskId}`) - const existingRegStatusCode = - existingTaskResource && - existingTaskResource.businessStatus && - existingTaskResource.businessStatus.coding && - existingTaskResource.businessStatus.coding.find((code) => { - return code.system === `${OPENCRVS_SPECIFICATION_URL}reg-status` - }) - - return existingRegStatusCode -} - function mergeFhirIdentifiers( currentIdentifiers: fhir3.Identifier[], newIdentifiers: fhir3.Identifier[] From a1a57642ade70b7edbe2838a2a054cec932b9434 Mon Sep 17 00:00:00 2001 From: naftis Date: Fri, 24 Jan 2025 13:25:19 +0200 Subject: [PATCH 16/16] fix: align the error handling with how events service notifies --- .../workflow/src/utils/country-config-api.ts | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/packages/workflow/src/utils/country-config-api.ts b/packages/workflow/src/utils/country-config-api.ts index 5bce92732af..e72cce3b943 100644 --- a/packages/workflow/src/utils/country-config-api.ts +++ b/packages/workflow/src/utils/country-config-api.ts @@ -8,6 +8,7 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ +import { logger } from '@opencrvs/commons' import { Bundle, EVENT_TYPE, Saved, ValidRecord } from '@opencrvs/commons/types' import { COUNTRY_CONFIG_URL } from '@workflow/constants' import fetch from 'node-fetch' @@ -30,25 +31,18 @@ export async function notifyForAction({ record: Saved headers: Record }) { - const response = await fetch(ACTION_NOTIFY_URL(event, action), { - method: 'POST', - body: JSON.stringify(record), - headers: { - 'Content-Type': 'application/fhir+json', - ...headers - } - }) - - if (!response.ok) { - throw new Error( - `Error notifying country-config as POST ${ACTION_NOTIFY_URL( - event, - action - )} [${response.statusText} ${response.status}]: ${response.text()}` - ) + try { + await fetch(ACTION_NOTIFY_URL(event, action), { + method: 'POST', + body: JSON.stringify(record), + headers: { + 'Content-Type': 'application/fhir+json', + ...headers + } + }) + } catch (e) { + logger.error(e) } - - return response } export async function invokeRegistrationValidation(