From 4e0c231bc3eb4c0c2b964dc657262a5a00bd8e6a Mon Sep 17 00:00:00 2001 From: Hyper3x Date: Tue, 7 Jan 2025 13:00:40 +0530 Subject: [PATCH 01/15] SELF_SERVICE_PORTAL system client type, self-service-portal Scope and option to create this client is created. --- .../client/src/i18n/messages/views/integrations.ts | 6 ++++++ packages/client/src/tests/schema.graphql | 1 + packages/client/src/utils/gateway.ts | 3 ++- .../src/views/SysAdmin/Config/Systems/Systems.tsx | 11 ++++++++++- packages/commons/src/authentication.ts | 6 ++++-- packages/config/src/config/routes.ts | 4 +++- packages/gateway/src/features/systems/schema.graphql | 1 + packages/gateway/src/graphql/schema.d.ts | 3 ++- packages/gateway/src/graphql/schema.graphql | 1 + packages/user-mgnt/src/model/system.ts | 8 +++++++- packages/user-mgnt/src/utils/system.ts | 3 ++- 11 files changed, 39 insertions(+), 8 deletions(-) diff --git a/packages/client/src/i18n/messages/views/integrations.ts b/packages/client/src/i18n/messages/views/integrations.ts index 1e580100510..06905dd810c 100644 --- a/packages/client/src/i18n/messages/views/integrations.ts +++ b/packages/client/src/i18n/messages/views/integrations.ts @@ -127,6 +127,12 @@ const messagesToDefine = { description: 'Label for event notification' }, + selfServicePortal: { + id: 'integrations.type.selfServicePortal', + defaultMessage: 'Self Service Portal', + description: 'Label for Self Service Portal' + }, + childDetails: { id: 'integrations.childDetails', defaultMessage: `Child's details`, diff --git a/packages/client/src/tests/schema.graphql b/packages/client/src/tests/schema.graphql index aa106ca8ee7..64324b91dcc 100644 --- a/packages/client/src/tests/schema.graphql +++ b/packages/client/src/tests/schema.graphql @@ -1733,6 +1733,7 @@ enum SystemType { HEALTH RECORD_SEARCH WEBHOOK + SELF_SERVICE_PORTAL } enum TelecomSystem { diff --git a/packages/client/src/utils/gateway.ts b/packages/client/src/utils/gateway.ts index 4b284927e27..f12f69905da 100644 --- a/packages/client/src/utils/gateway.ts +++ b/packages/client/src/utils/gateway.ts @@ -2228,7 +2228,8 @@ export enum SystemStatus { export enum SystemType { Health = 'HEALTH', RecordSearch = 'RECORD_SEARCH', - Webhook = 'WEBHOOK' + Webhook = 'WEBHOOK', + SelfServicePortal = 'SELF_SERVICE_PORTAL' } export enum TelecomSystem { diff --git a/packages/client/src/views/SysAdmin/Config/Systems/Systems.tsx b/packages/client/src/views/SysAdmin/Config/Systems/Systems.tsx index f2cfe773945..c280dd3e19a 100644 --- a/packages/client/src/views/SysAdmin/Config/Systems/Systems.tsx +++ b/packages/client/src/views/SysAdmin/Config/Systems/Systems.tsx @@ -214,7 +214,10 @@ export function SystemList() { const systemTypeLabels = { HEALTH: intl.formatMessage(integrationMessages.eventNotification), RECORD_SEARCH: intl.formatMessage(integrationMessages.recordSearch), - WEBHOOK: intl.formatMessage(integrationMessages.webhook) + WEBHOOK: intl.formatMessage(integrationMessages.webhook), + SELF_SERVICE_PORTAL: intl.formatMessage( + integrationMessages.selfServicePortal + ) } const systemToLabel = (system: System) => { @@ -531,6 +534,12 @@ export function SystemList() { { label: intl.formatMessage(integrationMessages.webhook), value: SystemType.Webhook + }, + { + label: intl.formatMessage( + integrationMessages.selfServicePortal + ), + value: SystemType.SelfServicePortal } ]} id={'permissions-selectors'} diff --git a/packages/commons/src/authentication.ts b/packages/commons/src/authentication.ts index 2941a8d8691..289cda3d0a8 100644 --- a/packages/commons/src/authentication.ts +++ b/packages/commons/src/authentication.ts @@ -63,14 +63,16 @@ const systemScopes = { declare: 'declare', notificationApi: 'notification-api', webhook: 'webhook', - nationalId: 'nationalId' + nationalId: 'nationalId', + selfServicePortal: 'self-service-portal' } as const export const systemRoleScopes = { HEALTH: [systemScopes.declare, systemScopes.notificationApi], NATIONAL_ID: [systemScopes.nationalId], RECORD_SEARCH: [systemScopes.recordsearch], - WEBHOOK: [systemScopes.webhook] + WEBHOOK: [systemScopes.webhook], + SELF_SERVICE_PORTAL: [systemScopes.selfServicePortal] } export type UserRole = keyof typeof userRoleScopes diff --git a/packages/config/src/config/routes.ts b/packages/config/src/config/routes.ts index 50adc608592..3b9a3201a2c 100644 --- a/packages/config/src/config/routes.ts +++ b/packages/config/src/config/routes.ts @@ -45,7 +45,8 @@ export const enum RouteScope { PERFORMANCE = 'performance', SYSADMIN = 'sysadmin', VALIDATE = 'validate', - NATLSYSADMIN = 'natlsysadmin' + NATLSYSADMIN = 'natlsysadmin', + SELF_SERVICE_PORTAL = 'self-service-portal' } export default function getRoutes(): ServerRoute[] { @@ -80,6 +81,7 @@ export default function getRoutes(): ServerRoute[] { RouteScope.PERFORMANCE, RouteScope.SYSADMIN, RouteScope.VALIDATE, + RouteScope.SELF_SERVICE_PORTAL, // @TODO: Refer to an enum / constant 'record.confirm-registration' ] diff --git a/packages/gateway/src/features/systems/schema.graphql b/packages/gateway/src/features/systems/schema.graphql index e1893cb2393..b05f2e08ca9 100644 --- a/packages/gateway/src/features/systems/schema.graphql +++ b/packages/gateway/src/features/systems/schema.graphql @@ -25,6 +25,7 @@ enum SystemType { HEALTH RECORD_SEARCH WEBHOOK + SELF_SERVICE_PORTAL } enum IntegratingSystemType { diff --git a/packages/gateway/src/graphql/schema.d.ts b/packages/gateway/src/graphql/schema.d.ts index a0e94933792..accb021e1ff 100644 --- a/packages/gateway/src/graphql/schema.d.ts +++ b/packages/gateway/src/graphql/schema.d.ts @@ -1009,7 +1009,8 @@ export const enum GQLSystemType { NATIONAL_ID = 'NATIONAL_ID', HEALTH = 'HEALTH', RECORD_SEARCH = 'RECORD_SEARCH', - WEBHOOK = 'WEBHOOK' + WEBHOOK = 'WEBHOOK', + SELF_SERVICE_PORTAL = 'SELF_SERVICE_PORTAL' } export const enum GQLIntegratingSystemType { diff --git a/packages/gateway/src/graphql/schema.graphql b/packages/gateway/src/graphql/schema.graphql index e54010f41eb..b6589119072 100644 --- a/packages/gateway/src/graphql/schema.graphql +++ b/packages/gateway/src/graphql/schema.graphql @@ -1111,6 +1111,7 @@ enum SystemType { HEALTH RECORD_SEARCH WEBHOOK + SELF_SERVICE_PORTAL } enum IntegratingSystemType { diff --git a/packages/user-mgnt/src/model/system.ts b/packages/user-mgnt/src/model/system.ts index a2d1bafdce3..b439aa60c68 100644 --- a/packages/user-mgnt/src/model/system.ts +++ b/packages/user-mgnt/src/model/system.ts @@ -78,7 +78,13 @@ const systemSchema = new Schema({ creationDate: { type: Number, default: Date.now }, type: { type: String, - enum: [types.HEALTH, types.NATIONAL_ID, types.RECORD_SEARCH, types.WEBHOOK] + enum: [ + types.HEALTH, + types.NATIONAL_ID, + types.RECORD_SEARCH, + types.WEBHOOK, + types.SELF_SERVICE_PORTAL + ] }, integratingSystemType: { type: String, diff --git a/packages/user-mgnt/src/utils/system.ts b/packages/user-mgnt/src/utils/system.ts index ef3a21084c4..94c1c81774a 100644 --- a/packages/user-mgnt/src/utils/system.ts +++ b/packages/user-mgnt/src/utils/system.ts @@ -16,7 +16,8 @@ export const types = { NATIONAL_ID: 'NATIONAL_ID', HEALTH: 'HEALTH', RECORD_SEARCH: 'RECORD_SEARCH', - WEBHOOK: 'WEBHOOK' + WEBHOOK: 'WEBHOOK', + SELF_SERVICE_PORTAL: 'SELF_SERVICE_PORTAL' } export const integratingSystemTypes = { From e484f5849083d1b5e5c714431762b80eda756338 Mon Sep 17 00:00:00 2001 From: anjana6 Date: Wed, 15 Jan 2025 10:58:06 +0530 Subject: [PATCH 02/15] Enable system clients access to the markBirthAsCertified GraphQL request --- packages/commons/src/authentication.ts | 9 +++++++-- packages/gateway/src/features/registration/utils.ts | 3 +++ packages/gateway/src/graphql/config.ts | 4 +++- packages/workflow/src/records/user.ts | 9 ++++++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/commons/src/authentication.ts b/packages/commons/src/authentication.ts index 289cda3d0a8..99b0b462145 100644 --- a/packages/commons/src/authentication.ts +++ b/packages/commons/src/authentication.ts @@ -64,7 +64,8 @@ const systemScopes = { notificationApi: 'notification-api', webhook: 'webhook', nationalId: 'nationalId', - selfServicePortal: 'self-service-portal' + selfServicePortal: 'self-service-portal', + certify: 'certify' } as const export const systemRoleScopes = { @@ -72,7 +73,11 @@ export const systemRoleScopes = { NATIONAL_ID: [systemScopes.nationalId], RECORD_SEARCH: [systemScopes.recordsearch], WEBHOOK: [systemScopes.webhook], - SELF_SERVICE_PORTAL: [systemScopes.selfServicePortal] + SELF_SERVICE_PORTAL: [ + systemScopes.declare, + systemScopes.certify, + systemScopes.selfServicePortal + ] } export type UserRole = keyof typeof userRoleScopes diff --git a/packages/gateway/src/features/registration/utils.ts b/packages/gateway/src/features/registration/utils.ts index 305ed1328c4..8ea0bca720b 100644 --- a/packages/gateway/src/features/registration/utils.ts +++ b/packages/gateway/src/features/registration/utils.ts @@ -40,6 +40,9 @@ export async function setCollectorForPrintInAdvance( authHeader: IAuthHeader ) { const tokenPayload = getTokenPayload(authHeader.Authorization.split(' ')[1]) + if (tokenPayload.scope.indexOf('self-service-portal') > -1) { + return details + } const userId = tokenPayload.sub const userDetails = await getUser({ userId }, authHeader) const name = userDetails.name.map((nameItem) => ({ diff --git a/packages/gateway/src/graphql/config.ts b/packages/gateway/src/graphql/config.ts index f3f51b95a79..f9b9e1f018e 100644 --- a/packages/gateway/src/graphql/config.ts +++ b/packages/gateway/src/graphql/config.ts @@ -199,7 +199,9 @@ export function authSchemaTransformer(schema: GraphQLSchema) { try { const userId = credentials.sub let user: IUserModelData | ISystemModelData - const isSystemUser = credentials.scope.indexOf('recordsearch') > -1 + const isSystemUser = + credentials.scope.indexOf('recordsearch') > -1 || + credentials.scope.indexOf('self-service-portal') > -1 if (isSystemUser) { user = await getSystem( { systemId: userId }, diff --git a/packages/workflow/src/records/user.ts b/packages/workflow/src/records/user.ts index f069901ceb6..c52c47e057c 100644 --- a/packages/workflow/src/records/user.ts +++ b/packages/workflow/src/records/user.ts @@ -78,8 +78,15 @@ export async function getUserOrSystem( const tokenPayload = getTokenPayload(token) const isNotificationAPIUser = tokenPayload.scope.includes('notification-api') const isRecordSearchAPIUser = tokenPayload.scope.includes('recordsearch') + const isSelfServicePortalAPIUser = tokenPayload.scope.includes( + 'self-service-portal' + ) - if (isNotificationAPIUser || isRecordSearchAPIUser) { + if ( + isNotificationAPIUser || + isRecordSearchAPIUser || + isSelfServicePortalAPIUser + ) { return await getSystem(tokenPayload.sub, { Authorization: `Bearer ${token}` }) From 727033affe626738b51d9db3a217e72a3244d9c4 Mon Sep 17 00:00:00 2001 From: anjana6 Date: Fri, 17 Jan 2025 16:50:39 +0530 Subject: [PATCH 03/15] Resolved the officeId issue when the system client accessed the createBirthRegistration GraphQL mutation. --- packages/workflow/src/features/user/utils.ts | 9 +++++++-- packages/workflow/src/records/fhir.ts | 16 ++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/workflow/src/features/user/utils.ts b/packages/workflow/src/features/user/utils.ts index 60f59e538e5..735f026c110 100644 --- a/packages/workflow/src/features/user/utils.ts +++ b/packages/workflow/src/features/user/utils.ts @@ -137,9 +137,14 @@ export async function getLoggedInPractitionerResource( const isNotificationAPIUser = tokenPayload.scope.indexOf('notification-api') > -1 const isRecordSearchAPIUser = tokenPayload.scope.indexOf('recordsearch') > -1 - + const isSelfServicePortalAPIUser = + tokenPayload.scope.indexOf('self-service-portal') > -1 let userResponse - if (isNotificationAPIUser || isRecordSearchAPIUser) { + if ( + isNotificationAPIUser || + isRecordSearchAPIUser || + isSelfServicePortalAPIUser + ) { userResponse = await getSystem(tokenPayload.sub, { Authorization: `Bearer ${token}` }) diff --git a/packages/workflow/src/records/fhir.ts b/packages/workflow/src/records/fhir.ts index 8f0118d7abc..646286fecfc 100644 --- a/packages/workflow/src/records/fhir.ts +++ b/packages/workflow/src/records/fhir.ts @@ -132,14 +132,14 @@ export async function withPractitionerDetails( type }) }) - return [ - newTask, - { - type: 'document', - resourceType: 'Bundle', - entry: [] - } - ] + // return [ + // newTask, + // { + // type: 'document', + // resourceType: 'Bundle', + // entry: [] + // } + // ] } const user = userOrSystem const practitioner = await getLoggedInPractitionerResource(token) From 6635245b44ad10a77b718e1cfe96972bba0ff8ca Mon Sep 17 00:00:00 2001 From: Markus Date: Tue, 28 Jan 2025 14:48:27 +0200 Subject: [PATCH 04/15] fix: relax elastic condition for partial dates --- packages/events/src/service/indexing/indexing.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/events/src/service/indexing/indexing.ts b/packages/events/src/service/indexing/indexing.ts index ca9fd995763..43c9d21ba36 100644 --- a/packages/events/src/service/indexing/indexing.ts +++ b/packages/events/src/service/indexing/indexing.ts @@ -86,7 +86,9 @@ export async function createIndex( function getElasticsearchMappingForType(field: FieldConfig) { switch (field.type) { case 'DATE': - return { type: 'date' } + // @TODO: This should be changed back to 'date' + // When we have proper validation of custom fields. + return { type: 'text' } case 'TEXT': case 'PARAGRAPH': case 'BULLET_LIST': From c0081067e1867210f96beb7f28d922d7f6f7ff25 Mon Sep 17 00:00:00 2001 From: Riku Rouvila Date: Tue, 28 Jan 2025 15:50:01 +0200 Subject: [PATCH 05/15] Events v2: update toolkit --- packages/toolkit/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 67306aedfcd..d6176ab3c23 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@opencrvs/toolkit", - "version": "0.0.30-rr", + "version": "0.0.31-rr", "description": "OpenCRVS toolkit for building country configurations", "license": "MPL-2.0", "exports": { @@ -15,12 +15,14 @@ "build:all": "lerna run build --include-dependencies --scope @opencrvs/toolkit && ./build.sh" }, "dependencies": { + "uuid": "^9.0.0", "ajv": "^8.17.1", "superjson": "1.9.0-0", "@trpc/client": "^11.0.0-rc.648", "@trpc/server": "^11.0.0-rc.532" }, "devDependencies": { + "@types/uuid": "^9.0.3", "esbuild": "^0.24.0", "typescript": "^5.6.3", "@opencrvs/events": "^1.7.0" From e539616bd444c1f6d8ba222ee516d9ee3495b528 Mon Sep 17 00:00:00 2001 From: Jamil Date: Tue, 28 Jan 2025 21:05:04 +0600 Subject: [PATCH 06/15] fix: unify declare form flow (#8491) * fix: titles in v2 event pages * feat: add postfix and prefix option for text input field * fix: labels for v2 inputs * feat: add dividers * upgrade toolkit --- .../components/forms/FormFieldGenerator.tsx | 96 ++++++++++++------- .../src/v2-events/components/forms/utils.ts | 3 +- .../events/registered-fields/Select.tsx | 14 +-- .../src/v2-events/layouts/form/FormHeader.tsx | 8 +- packages/commons/src/events/FieldConfig.ts | 18 +++- .../components/src/FormWizard/FormWizard.tsx | 2 +- .../events/src/service/indexing/indexing.ts | 4 +- packages/toolkit/package.json | 2 +- 8 files changed, 91 insertions(+), 56 deletions(-) diff --git a/packages/client/src/v2-events/components/forms/FormFieldGenerator.tsx b/packages/client/src/v2-events/components/forms/FormFieldGenerator.tsx index 23b50c344e4..4e68fa223e3 100644 --- a/packages/client/src/v2-events/components/forms/FormFieldGenerator.tsx +++ b/packages/client/src/v2-events/components/forms/FormFieldGenerator.tsx @@ -63,6 +63,7 @@ import { Select } from '@client/v2-events/features/events/registered-fields/Sele import { SelectCountry } from '@client/v2-events/features/events/registered-fields/SelectCountry' import { SubHeader } from '@opencrvs/components' import { formatISO } from 'date-fns' +import { Divider } from '@opencrvs/components' const fadeIn = keyframes` from { opacity: 0; } @@ -117,7 +118,9 @@ const GeneratedInputField = React.memo( const inputFieldProps = { id: fieldDefinition.id, - label: intl.formatMessage(fieldDefinition.label), + label: fieldDefinition.hideLabel + ? undefined + : intl.formatMessage(fieldDefinition.label), // helperText: fieldDefinition.helperText, // tooltip: fieldDefinition.tooltip, // description: fieldDefinition.description, @@ -189,7 +192,17 @@ const GeneratedInputField = React.memo( if (fieldDefinition.type === TEXT) { return ( - + + return ( + + + + ) } if (fieldDefinition.type === 'SELECT') { return ( - setFieldValue(fieldDefinition.id, val)} + /> + ) } if (fieldDefinition.type === 'COUNTRY') { return ( - + + + ) } if (fieldDefinition.type === 'CHECKBOX') { @@ -244,37 +265,46 @@ const GeneratedInputField = React.memo( } if (fieldDefinition.type === 'RADIO_GROUP') { return ( - + + + ) } if (fieldDefinition.type === 'LOCATION') { if (fieldDefinition.options.type === 'HEALTH_FACILITY') return ( - + + + ) + return ( + + - ) - return ( - + ) } + if (fieldDefinition.type === 'DIVIDER') { + return + } throw new Error(`Unsupported field ${fieldDefinition}`) } ) diff --git a/packages/client/src/v2-events/components/forms/utils.ts b/packages/client/src/v2-events/components/forms/utils.ts index 604c8f3354e..97df52815ed 100644 --- a/packages/client/src/v2-events/components/forms/utils.ts +++ b/packages/client/src/v2-events/components/forms/utils.ts @@ -124,7 +124,8 @@ const initialValueMapping: Record = { [FieldType.COUNTRY]: null, [FieldType.LOCATION]: null, [FieldType.SELECT]: null, - [FieldType.PAGE_HEADER]: null + [FieldType.PAGE_HEADER]: null, + [FieldType.DIVIDER]: null } export function getInitialValues(fields: FieldConfig[]) { diff --git a/packages/client/src/v2-events/features/events/registered-fields/Select.tsx b/packages/client/src/v2-events/features/events/registered-fields/Select.tsx index cb85fd0d684..bb350df4d7e 100644 --- a/packages/client/src/v2-events/features/events/registered-fields/Select.tsx +++ b/packages/client/src/v2-events/features/events/registered-fields/Select.tsx @@ -16,7 +16,6 @@ import { SelectOption } from '@opencrvs/commons/client' import { Select as SelectComponent } from '@opencrvs/components' -import { InputField } from '@client/components/form/InputField' export function Select({ onChange, @@ -36,14 +35,11 @@ export function Select({ })) return ( - - - + ) } diff --git a/packages/client/src/v2-events/layouts/form/FormHeader.tsx b/packages/client/src/v2-events/layouts/form/FormHeader.tsx index 8c180429e8b..6a75baec310 100644 --- a/packages/client/src/v2-events/layouts/form/FormHeader.tsx +++ b/packages/client/src/v2-events/layouts/form/FormHeader.tsx @@ -112,9 +112,7 @@ export function FormHeader({ {modal} } - desktopTitle={intl.formatMessage(messages.newVitalEventRegistration, { - event: intl.formatMessage(label) - })} + desktopTitle={intl.formatMessage(label)} mobileLeft={} mobileRight={ <> @@ -147,9 +145,7 @@ export function FormHeader({ {modal} } - mobileTitle={intl.formatMessage(messages.newVitalEventRegistration, { - event: intl.formatMessage(label) - })} + mobileTitle={intl.formatMessage(label)} /> ) } diff --git a/packages/commons/src/events/FieldConfig.ts b/packages/commons/src/events/FieldConfig.ts index 0059221615b..5e7c010bd83 100644 --- a/packages/commons/src/events/FieldConfig.ts +++ b/packages/commons/src/events/FieldConfig.ts @@ -82,7 +82,8 @@ const BaseField = z.object({ .default([]) .optional(), dependsOn: z.array(FieldId).default([]).optional(), - label: TranslationConfig + label: TranslationConfig, + hideLabel: z.boolean().default(false).optional() }) export type BaseField = z.infer @@ -99,7 +100,8 @@ export const FieldType = { CHECKBOX: 'CHECKBOX', SELECT: 'SELECT', COUNTRY: 'COUNTRY', - LOCATION: 'LOCATION' + LOCATION: 'LOCATION', + DIVIDER: 'DIVIDER' } as const export const fieldTypes = Object.values(FieldType) @@ -119,12 +121,18 @@ export interface FieldValueByType { [FieldType.SELECT]: SelectFieldValue } +const Divider = BaseField.extend({ + type: z.literal(FieldType.DIVIDER) +}) + const TextField = BaseField.extend({ type: z.literal(FieldType.TEXT), options: z .object({ maxLength: z.number().optional().describe('Maximum length of the text'), - type: z.enum(['text', 'email', 'password', 'number']).optional() + type: z.enum(['text', 'email', 'password', 'number']).optional(), + prefix: TranslationConfig.optional(), + postfix: TranslationConfig.optional() }) .default({ type: 'text' }) .optional() @@ -239,6 +247,7 @@ export type AllFields = | typeof File | typeof Country | typeof Location + | typeof Divider export const FieldConfig = z.discriminatedUnion('type', [ TextField, @@ -251,7 +260,8 @@ export const FieldConfig = z.discriminatedUnion('type', [ Checkbox, File, Country, - Location + Location, + Divider ]) as unknown as z.ZodDiscriminatedUnion<'type', AllFields[]> export type SelectField = z.infer diff --git a/packages/components/src/FormWizard/FormWizard.tsx b/packages/components/src/FormWizard/FormWizard.tsx index 9df4606a3c5..c59160bc659 100644 --- a/packages/components/src/FormWizard/FormWizard.tsx +++ b/packages/components/src/FormWizard/FormWizard.tsx @@ -52,7 +52,7 @@ export const FormWizard = ({ )} - + {children} diff --git a/packages/events/src/service/indexing/indexing.ts b/packages/events/src/service/indexing/indexing.ts index f08c66d91aa..ea12e8ce3ce 100644 --- a/packages/events/src/service/indexing/indexing.ts +++ b/packages/events/src/service/indexing/indexing.ts @@ -91,8 +91,8 @@ function getElasticsearchMappingForType(field: FieldConfig) { return { type: 'text' } case 'TEXT': case 'PARAGRAPH': - case 'PAGE_HEADER': case 'BULLET_LIST': + case 'PAGE_HEADER': return { type: 'text' } case 'RADIO_GROUP': case 'SELECT': @@ -109,6 +109,8 @@ function getElasticsearchMappingForType(field: FieldConfig) { type: { type: 'keyword' } } } + case 'DIVIDER': + return {} default: assertNever() diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index d6176ab3c23..ed53a5be02f 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@opencrvs/toolkit", - "version": "0.0.31-rr", + "version": "0.0.32-jr", "description": "OpenCRVS toolkit for building country configurations", "license": "MPL-2.0", "exports": { From 8b0c1612e44449ef66846a9246a3961c147e5319 Mon Sep 17 00:00:00 2001 From: euanmillar Date: Tue, 28 Jan 2025 13:57:13 -0500 Subject: [PATCH 07/15] removed console log --- packages/gateway/src/workflow/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/gateway/src/workflow/index.ts b/packages/gateway/src/workflow/index.ts index e0b2ca9e8ed..9d7e0345261 100644 --- a/packages/gateway/src/workflow/index.ts +++ b/packages/gateway/src/workflow/index.ts @@ -47,7 +47,6 @@ const createRequest = async ( authHeader: IAuthHeader, body?: Record ): Promise => { - console.log('HEY: ', new URL(path, WORKFLOW_URL)) const response = await fetch(new URL(path, WORKFLOW_URL).href, { method, headers: { From af2032ba1d9781d44ee173364ed8366fbf70f12d Mon Sep 17 00:00:00 2001 From: Riku Rouvila Date: Wed, 29 Jan 2025 15:48:33 +0200 Subject: [PATCH 08/15] Events v2 demo branch (#8510) Co-authored-by: jamil314 Co-authored-by: Markus --- .../components/forms/FormFieldGenerator.tsx | 10 ++-- .../src/v2-events/components/forms/utils.ts | 3 +- .../v2-events/components/forms/validation.ts | 29 +++++++++- .../EventOverview/EventOverview.tsx | 2 +- .../EventOverview/EventOverviewContext.tsx | 2 +- .../components/EventHistory/EventHistory.tsx | 30 ++++++++-- .../EventHistory/EventHistoryModal.tsx | 13 ++++- .../components/EventHistory/messages.ts | 20 ------- .../features/workqueues/Workqueue.tsx | 25 ++++++--- .../src/v2-events/hooks/useTransformer.ts | 10 +++- .../client/src/v2-events/messages/index.ts | 1 - .../src/v2-events/messages/registrarHome.ts | 26 --------- packages/commons/src/events/ActionDocument.ts | 2 +- .../events/src/router/user/user.list.test.ts | 2 +- .../deduplication/deduplication.test.ts | 7 ++- .../service/deduplication/deduplication.ts | 56 ++++++++++++------- .../events/src/service/indexing/indexing.ts | 55 ++++++++++++++++-- packages/events/src/service/users/users.ts | 4 +- packages/events/src/tests/generators.ts | 8 +-- packages/events/tsconfig.json | 2 +- packages/gateway/tsconfig.json | 2 +- 21 files changed, 196 insertions(+), 113 deletions(-) delete mode 100644 packages/client/src/v2-events/features/workqueues/EventOverview/components/EventHistory/messages.ts delete mode 100644 packages/client/src/v2-events/messages/registrarHome.ts diff --git a/packages/client/src/v2-events/components/forms/FormFieldGenerator.tsx b/packages/client/src/v2-events/components/forms/FormFieldGenerator.tsx index 4e68fa223e3..68497822389 100644 --- a/packages/client/src/v2-events/components/forms/FormFieldGenerator.tsx +++ b/packages/client/src/v2-events/components/forms/FormFieldGenerator.tsx @@ -454,7 +454,9 @@ class FormSectionComponent extends React.Component { const language = this.props.intl.locale - const errors = this.props.errors as unknown as Errors + const errors = makeFormFieldIdsFormikCompatible( + this.props.errors as unknown as Errors + ) const fields = fieldsWithDotIds.map((field) => ({ ...field, @@ -506,11 +508,7 @@ class FormSectionComponent extends React.Component { error={isFieldDisabled ? '' : error} fields={fields} formData={formData} - touched={ - makeFormikFieldIdsOpenCRVSCompatible(touched)[ - field.id - ] || false - } + touched={touched[field.id] || false} values={values} onUploadingStateChanged={ this.props.onUploadingStateChanged diff --git a/packages/client/src/v2-events/components/forms/utils.ts b/packages/client/src/v2-events/components/forms/utils.ts index 97df52815ed..25c54ebc260 100644 --- a/packages/client/src/v2-events/components/forms/utils.ts +++ b/packages/client/src/v2-events/components/forms/utils.ts @@ -21,7 +21,8 @@ import { validate, DateFieldValue, TextFieldValue, - RadioGroupFieldValue + RadioGroupFieldValue, + FileFieldValue } from '@opencrvs/commons/client' import { CheckboxFieldValue, diff --git a/packages/client/src/v2-events/components/forms/validation.ts b/packages/client/src/v2-events/components/forms/validation.ts index 5dedd5f22fa..9a481fd04d5 100644 --- a/packages/client/src/v2-events/components/forms/validation.ts +++ b/packages/client/src/v2-events/components/forms/validation.ts @@ -67,9 +67,32 @@ function getValidationErrors( const validators = field.validation ? field.validation : [] - // if (field.required && !checkValidationErrorsOnly) { - // validators.push(required(requiredErrorMessage)) - // } else if (isFieldButton(field)) { + if (field.required && !checkValidationErrorsOnly) { + validators.push({ + message: { + defaultMessage: 'Required for registration', + description: 'This is the error message for required fields', + id: 'error.required' + }, + validator: { + type: 'object', + properties: { + $form: { + type: 'object', + properties: { + [field.id]: { + type: 'string', + minLength: 1 + } + }, + required: [field.id] + } + }, + required: ['$form'] + } + }) + } + // else if (isFieldButton(field)) { // const { trigger } = field.options // validators.push(httpErrorResponseValidator(trigger)) // } else if (field.validateEmpty) { diff --git a/packages/client/src/v2-events/features/workqueues/EventOverview/EventOverview.tsx b/packages/client/src/v2-events/features/workqueues/EventOverview/EventOverview.tsx index f0e00dccde6..83c60199d1f 100644 --- a/packages/client/src/v2-events/features/workqueues/EventOverview/EventOverview.tsx +++ b/packages/client/src/v2-events/features/workqueues/EventOverview/EventOverview.tsx @@ -104,7 +104,7 @@ function EventOverview({ : '' return ( } + icon={() => } size={ContentSize.LARGE} title={title || fallbackTitle} titleColor={event.id ? 'copy' : 'grey600'} diff --git a/packages/client/src/v2-events/features/workqueues/EventOverview/EventOverviewContext.tsx b/packages/client/src/v2-events/features/workqueues/EventOverview/EventOverviewContext.tsx index 186ce4cc253..4facbd6fef9 100644 --- a/packages/client/src/v2-events/features/workqueues/EventOverview/EventOverviewContext.tsx +++ b/packages/client/src/v2-events/features/workqueues/EventOverview/EventOverviewContext.tsx @@ -28,7 +28,7 @@ const MISSING_USER = { family: 'user' } ], - systemRole: '-' + role: '-' } /** diff --git a/packages/client/src/v2-events/features/workqueues/EventOverview/components/EventHistory/EventHistory.tsx b/packages/client/src/v2-events/features/workqueues/EventOverview/components/EventHistory/EventHistory.tsx index 67a9114e702..e023db8884d 100644 --- a/packages/client/src/v2-events/features/workqueues/EventOverview/components/EventHistory/EventHistory.tsx +++ b/packages/client/src/v2-events/features/workqueues/EventOverview/components/EventHistory/EventHistory.tsx @@ -11,7 +11,7 @@ import React from 'react' import { format } from 'date-fns' import styled from 'styled-components' -import { useIntl } from 'react-intl' +import { defineMessages, useIntl } from 'react-intl' import { useNavigate } from 'react-router-dom' import { stringify } from 'query-string' import { Link } from '@opencrvs/components' @@ -28,7 +28,6 @@ import { formatUrl } from '@client/navigation' import { useEventOverviewContext } from '@client/v2-events/features/workqueues/EventOverview/EventOverviewContext' import { EventHistoryModal } from './EventHistoryModal' import { UserAvatar } from './UserAvatar' -import { messages } from './messages' /** * Based on packages/client/src/views/RecordAudit/History.tsx @@ -40,6 +39,25 @@ const TableDiv = styled.div` const DEFAULT_HISTORY_RECORD_PAGE_SIZE = 10 +const messages = defineMessages({ + 'event.history.timeFormat': { + defaultMessage: 'MMMM dd, yyyy · hh.mm a', + id: 'event.history.timeFormat', + description: 'Time format for timestamps in event history' + }, + 'events.history.status': { + id: `events.history.status`, + defaultMessage: + '{status, select, CREATE {Draft} VALIDATE {Validated} DRAFT {Draft} DECLARE {Declared} REGISTER {Registered} other {Unknown}}' + }, + 'event.history.role': { + id: 'event.history.role', + defaultMessage: + '{role, select, LOCAL_REGISTRAR{Local Registrar} other{Unknown}}', + description: 'Role of the user in the event history' + } +}) + /** * Renders the event history table. Used for audit trail. */ @@ -72,7 +90,9 @@ export function EventHistory({ history }: { history: ActionDocument[] }) { onHistoryRowClick(item, user) }} > - {item.type} + {intl.formatMessage(messages['events.history.status'], { + status: item.type + })} ), user: ( @@ -95,7 +115,9 @@ export function EventHistory({ history }: { history: ActionDocument[] }) { /> ), - role: user.systemRole, + role: intl.formatMessage(messages['event.history.role'], { + role: user.role + }), location: ( (x: T, message: string) { if (x === undefined || x === null) { throw new Error(message) @@ -207,8 +216,8 @@ function Workqueue({ ...fieldsWithPopulatedValues, ...event, event: intl.formatMessage(eventConfig.label), - createdAt: intl.formatDate(new Date(event.createdAt)), - modifiedAt: intl.formatDate(new Date(event.modifiedAt)), + createdAt: formattedDuration(new Date(event.createdAt)), + modifiedAt: formattedDuration(new Date(event.modifiedAt)), status: intl.formatMessage( { diff --git a/packages/client/src/v2-events/hooks/useTransformer.ts b/packages/client/src/v2-events/hooks/useTransformer.ts index ce558c1b06c..def68af9b71 100644 --- a/packages/client/src/v2-events/hooks/useTransformer.ts +++ b/packages/client/src/v2-events/hooks/useTransformer.ts @@ -11,7 +11,11 @@ import { useIntl } from 'react-intl' import { useSelector } from 'react-redux' -import { ActionFormData, findPageFields } from '@opencrvs/commons/client' +import { + ActionFormData, + findPageFields, + FieldType +} from '@opencrvs/commons/client' import { fieldValueToString } from '@client/v2-events/components/forms/utils' import { useEventConfiguration } from '@client/v2-events/features/events/useEventConfiguration' // eslint-disable-next-line no-restricted-imports @@ -36,6 +40,10 @@ export const useTransformer = (eventType: string) => { throw new Error(`Field not found for ${key}`) } + if (fieldConfig.type === FieldType.FILE) { + continue + } + stringifiedValues[key] = fieldValueToString({ fieldConfig, value, diff --git a/packages/client/src/v2-events/messages/index.ts b/packages/client/src/v2-events/messages/index.ts index 0c9249addb7..d1f4a8d95de 100644 --- a/packages/client/src/v2-events/messages/index.ts +++ b/packages/client/src/v2-events/messages/index.ts @@ -10,7 +10,6 @@ */ export * from './constants' -export * from './registrarHome' export * from './workqueue' export * from './navigation' export * from './errors' diff --git a/packages/client/src/v2-events/messages/registrarHome.ts b/packages/client/src/v2-events/messages/registrarHome.ts deleted file mode 100644 index 6126ee1b3a9..00000000000 --- a/packages/client/src/v2-events/messages/registrarHome.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 { defineMessages, MessageDescriptor } from 'react-intl' - -interface IOfficeHomeMessages - extends Record { - empty: MessageDescriptor -} - -const messagesToDefine: IOfficeHomeMessages = { - empty: { - defaultMessage: 'Empty message', - description: 'Label for workqueue tooltip', - id: 'regHome.issued' - } -} - -export const messages: IOfficeHomeMessages = defineMessages(messagesToDefine) diff --git a/packages/commons/src/events/ActionDocument.ts b/packages/commons/src/events/ActionDocument.ts index 67f811d4736..361e13271ed 100644 --- a/packages/commons/src/events/ActionDocument.ts +++ b/packages/commons/src/events/ActionDocument.ts @@ -112,7 +112,7 @@ export type ActionDocument = z.infer export const ResolvedUser = z.object({ id: z.string(), - systemRole: z.string(), + role: z.string(), name: z.array( z.object({ use: z.string(), diff --git a/packages/events/src/router/user/user.list.test.ts b/packages/events/src/router/user/user.list.test.ts index cc22ff808d6..301647af071 100644 --- a/packages/events/src/router/user/user.list.test.ts +++ b/packages/events/src/router/user/user.list.test.ts @@ -39,7 +39,7 @@ test('Returns user in correct format', async () => { { id: user.id, name: user.name, - systemRole: user.systemRole + role: user.role } ]) }) diff --git a/packages/events/src/service/deduplication/deduplication.test.ts b/packages/events/src/service/deduplication/deduplication.test.ts index 410ef96263a..c6741022816 100644 --- a/packages/events/src/service/deduplication/deduplication.test.ts +++ b/packages/events/src/service/deduplication/deduplication.test.ts @@ -10,9 +10,10 @@ */ import { getOrCreateClient } from '@events/storage/elasticsearch' -import { DeduplicationConfig, getUUID } from '@opencrvs/commons' +import { DeduplicationConfig, EventIndex, getUUID } from '@opencrvs/commons' import { searchForDuplicates } from './deduplication' import { getEventIndexName } from '@events/storage/__mocks__/elasticsearch' +import { encodeEventIndex } from '@events/service/indexing/indexing' const LEGACY_BIRTH_DEDUPLICATION_RULES = { id: 'Legacy birth deduplication check', @@ -155,11 +156,11 @@ export async function findDuplicates( index: getEventIndexName(), id: getUUID(), body: { - doc: { + doc: encodeEventIndex({ id: getUUID(), transactionId: getUUID(), data: existingComposition - }, + } as unknown as EventIndex), doc_as_upsert: true }, refresh: 'wait_for' diff --git a/packages/events/src/service/deduplication/deduplication.ts b/packages/events/src/service/deduplication/deduplication.ts index 1e7d5b9af4c..f8347e4902f 100644 --- a/packages/events/src/service/deduplication/deduplication.ts +++ b/packages/events/src/service/deduplication/deduplication.ts @@ -22,19 +22,25 @@ import { ClauseOutput } from '@opencrvs/commons/events' import { subDays, addDays } from 'date-fns' +import { + decodeEventIndex, + EncodedEventIndex, + encodeEventIndex, + encodeFieldId +} from '@events/service/indexing/indexing' function dataReference(fieldName: string) { return `data.${fieldName}` } function generateElasticsearchQuery( - eventIndex: EventIndex, + eventIndex: EncodedEventIndex, configuration: ClauseOutput ): elasticsearch.estypes.QueryDslQueryContainer | null { const matcherFieldWithoutData = configuration.type !== 'and' && configuration.type !== 'or' && - !eventIndex.data[configuration.fieldId] + !eventIndex.data[encodeFieldId(configuration.fieldId)] if (matcherFieldWithoutData) { return null @@ -66,47 +72,56 @@ function generateElasticsearchQuery( ) } } - case 'fuzzy': + case 'fuzzy': { + const encodedFieldId = encodeFieldId(configuration.fieldId) return { match: { - ['data.' + configuration.fieldId]: { - query: eventIndex.data[configuration.fieldId], + ['data.' + encodedFieldId]: { + query: eventIndex.data[encodedFieldId], fuzziness: configuration.options.fuzziness, boost: configuration.options.boost } } } - case 'strict': + } + case 'strict': { + const encodedFieldId = encodeFieldId(configuration.fieldId) return { match_phrase: { - [dataReference(configuration.fieldId)]: - eventIndex.data[configuration.fieldId] || '' + [dataReference(encodedFieldId)]: eventIndex.data[encodedFieldId] || '' } } - case 'dateRange': + } + case 'dateRange': { + const encodedFieldId = encodeFieldId(configuration.fieldId) + const origin = encodeFieldId(configuration.options.origin) return { range: { - [dataReference(configuration.fieldId)]: { + [dataReference(encodedFieldId)]: { // @TODO: Improve types for origin field to be sure it returns a string when accessing data gte: subDays( - new Date(eventIndex.data[configuration.options.origin] as string), + new Date(eventIndex.data[origin] as string), configuration.options.days ).toISOString(), lte: addDays( - new Date(eventIndex.data[configuration.options.origin] as string), + new Date(eventIndex.data[origin] as string), configuration.options.days ).toISOString() } } } - case 'dateDistance': + } + case 'dateDistance': { + const encodedFieldId = encodeFieldId(configuration.fieldId) + const origin = encodeFieldId(configuration.options.origin) return { distance_feature: { - field: dataReference(configuration.fieldId), + field: dataReference(encodedFieldId), pivot: `${configuration.options.days}d`, - origin: eventIndex.data[configuration.options.origin] + origin: eventIndex.data[origin] } } + } } } @@ -117,14 +132,17 @@ export async function searchForDuplicates( const esClient = getOrCreateClient() const query = Clause.parse(configuration.query) - const esQuery = generateElasticsearchQuery(eventIndex, query) + const esQuery = generateElasticsearchQuery( + encodeEventIndex(eventIndex), + query + ) if (!esQuery) { return [] } - const result = await esClient.search({ - index: getEventIndexName('TENNIS_CLUB_MEMBERSHIP'), + const result = await esClient.search({ + index: getEventIndexName(eventIndex.type), query: { bool: { should: [esQuery], @@ -137,6 +155,6 @@ export async function searchForDuplicates( .filter((hit) => hit._source) .map((hit) => ({ score: hit._score || 0, - event: hit._source + event: hit._source && decodeEventIndex(hit._source) })) } diff --git a/packages/events/src/service/indexing/indexing.ts b/packages/events/src/service/indexing/indexing.ts index ea12e8ce3ce..ffbf3f5a464 100644 --- a/packages/events/src/service/indexing/indexing.ts +++ b/packages/events/src/service/indexing/indexing.ts @@ -28,7 +28,34 @@ import { Transform } from 'stream' import { z } from 'zod' function eventToEventIndex(event: EventDocument): EventIndex { - return getCurrentEventState(event) + return encodeEventIndex(getCurrentEventState(event)) +} + +export type EncodedEventIndex = EventIndex +export function encodeEventIndex(event: EventIndex): EncodedEventIndex { + return { + ...event, + data: Object.entries(event.data).reduce( + (acc, [key, value]) => ({ + ...acc, + [encodeFieldId(key)]: value + }), + {} + ) + } +} + +export function decodeEventIndex(event: EncodedEventIndex): EventIndex { + return { + ...event, + data: Object.entries(event.data).reduce( + (acc, [key, value]) => ({ + ...acc, + [decodeFieldId(key)]: value + }), + {} + ) + } } /* @@ -94,6 +121,7 @@ function getElasticsearchMappingForType(field: FieldConfig) { case 'BULLET_LIST': case 'PAGE_HEADER': return { type: 'text' } + case 'DIVIDER': case 'RADIO_GROUP': case 'SELECT': case 'COUNTRY': @@ -109,8 +137,6 @@ function getElasticsearchMappingForType(field: FieldConfig) { type: { type: 'keyword' } } } - case 'DIVIDER': - return {} default: assertNever() @@ -121,11 +147,21 @@ function assertNever(): never { throw new Error('Should never happen') } +const SEPARATOR = '____' + +export function encodeFieldId(fieldId: string) { + return fieldId.replaceAll('.', SEPARATOR) +} + +function decodeFieldId(fieldId: string) { + return fieldId.replaceAll(SEPARATOR, '.') +} + function formFieldsToDataMapping(fields: FieldConfig[]) { return fields.reduce((acc, field) => { return { ...acc, - [field.id]: getElasticsearchMappingForType(field) + [encodeFieldId(field.id)]: getElasticsearchMappingForType(field) } }, {}) } @@ -207,11 +243,18 @@ export async function getIndexedEvents() { return [] } - const response = await esClient.search({ + const response = await esClient.search({ index: getEventAliasName(), size: 10000, request_cache: false }) - return z.array(EventIndex).parse(response.hits.hits.map((hit) => hit._source)) + const events = z.array(EventIndex).parse( + response.hits.hits + .map((hit) => hit._source) + .filter((event): event is EncodedEventIndex => event !== undefined) + .map((event) => decodeEventIndex(event)) + ) + + return events } diff --git a/packages/events/src/service/users/users.ts b/packages/events/src/service/users/users.ts index 8659466a71e..dba3574aece 100644 --- a/packages/events/src/service/users/users.ts +++ b/packages/events/src/service/users/users.ts @@ -24,7 +24,7 @@ export const getUsersById = async (ids: string[]) => { .collection<{ _id: ObjectId name: ResolvedUser['name'] - systemRole: string + role: string }>('users') .find({ _id: { @@ -38,6 +38,6 @@ export const getUsersById = async (ids: string[]) => { return results.map((user) => ({ id: user._id.toString(), name: user.name, - systemRole: user.systemRole + role: user.role })) } diff --git a/packages/events/src/tests/generators.ts b/packages/events/src/tests/generators.ts index 9612f5c3aa7..bf05d740137 100644 --- a/packages/events/src/tests/generators.ts +++ b/packages/events/src/tests/generators.ts @@ -30,13 +30,13 @@ interface Name { export interface CreatedUser { id: string primaryOfficeId: string - systemRole: string + role: string name: Array } interface CreateUser { primaryOfficeId: string - systemRole?: string + role?: string name?: Array } /** @@ -127,7 +127,7 @@ export function payloadGenerator() { const user = { create: (input: CreateUser) => ({ - systemRole: input.systemRole ?? 'REGISTRATION_AGENT', + role: input.role ?? 'REGISTRATION_AGENT', name: input.name ?? [{ use: 'en', family: 'Doe', given: ['John'] }], primaryOfficeId: input.primaryOfficeId }) @@ -168,7 +168,7 @@ export function seeder() { return { primaryOfficeId: user.primaryOfficeId, name: user.name, - systemRole: user.systemRole, + role: user.role, id: createdUser.insertedId.toString() } } diff --git a/packages/events/tsconfig.json b/packages/events/tsconfig.json index fdabbd8f49b..f5e4c7bb98b 100644 --- a/packages/events/tsconfig.json +++ b/packages/events/tsconfig.json @@ -11,7 +11,7 @@ "skipLibCheck": true, "moduleResolution": "node16", "rootDir": ".", - "lib": ["esnext.asynciterable", "es6", "es2019"], + "lib": ["esnext.asynciterable", "es6", "es2019", "es2021"], "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noImplicitThis": true, diff --git a/packages/gateway/tsconfig.json b/packages/gateway/tsconfig.json index 7c94cd211e4..03881691ae4 100644 --- a/packages/gateway/tsconfig.json +++ b/packages/gateway/tsconfig.json @@ -12,7 +12,7 @@ "sourceMap": true, "moduleResolution": "node16", "rootDir": ".", - "lib": ["esnext.asynciterable", "es6", "es2019", "DOM.Iterable"], + "lib": ["esnext.asynciterable", "es6", "es2019", "DOM.Iterable", "es2021"], "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noImplicitThis": true, From a36ffbc6997048a6f6b34e7d23577bcf38b37d99 Mon Sep 17 00:00:00 2001 From: PathumN99 Date: Thu, 30 Jan 2025 10:16:17 +0530 Subject: [PATCH 09/15] Unit test case fix in webhook package --- .../src/features/manage/handler.test.ts | 6 +++--- .../webhooks/src/features/manage/handler.ts | 21 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/webhooks/src/features/manage/handler.test.ts b/packages/webhooks/src/features/manage/handler.test.ts index 573f9454058..edc6ed58722 100644 --- a/packages/webhooks/src/features/manage/handler.test.ts +++ b/packages/webhooks/src/features/manage/handler.test.ts @@ -150,7 +150,7 @@ describe('subscribeWebhooksHandler handler', () => { expect(res.statusCode).toBe(400) }) - it('return an error if a topic is unsupported', async () => { + it('return an error if a topic is undefined', async () => { fetch.mockResponses( [JSON.stringify(mockActiveSystem), { status: 200 }], [JSON.stringify({ challenge: '123' }), { status: 200 }] @@ -167,14 +167,14 @@ describe('subscribeWebhooksHandler handler', () => { callback: 'https://www.your-great-domain.com/webhooks', mode: 'subscribe', secret: '123', - topic: 'XXX' + topic: undefined } }, headers: { Authorization: `Bearer ${token}` } }) - expect(res.result.hub.reason).toEqual('Unsupported topic: XXX') + expect(res.result.hub.reason).toEqual('hub.topic is required') expect(res.statusCode).toBe(400) }) diff --git a/packages/webhooks/src/features/manage/handler.ts b/packages/webhooks/src/features/manage/handler.ts index 4ca526f0f8d..d0166f2e8ef 100644 --- a/packages/webhooks/src/features/manage/handler.ts +++ b/packages/webhooks/src/features/manage/handler.ts @@ -40,17 +40,16 @@ export async function subscribeWebhooksHandler( h: Hapi.ResponseToolkit ) { const { hub } = request.payload as ISubscribePayload - // if (!(hub.topic in TRIGGERS)) { - // return h - // .response({ - // hub: { - // mode: 'denied', - // topic: hub.topic, - // reason: `Unsupported topic: ${hub.topic}` - // } - // }) - // .code(400) - // } + if (hub.topic === undefined) { + return h + .response({ + hub: { + mode: 'denied', + reason: 'hub.topic is required' + } + }) + .code(400) + } const token: ITokenPayload = getTokenPayload( request.headers.authorization.split(' ')[1] ) From c4deca19d29c1d1c594355e63034e8d4456d63b0 Mon Sep 17 00:00:00 2001 From: Tameem Bin Haider Date: Thu, 30 Jan 2025 15:47:24 +0600 Subject: [PATCH 10/15] perf: only forward the questionnaire resource (#8521) The other resources need not be forwarded as we are only updating the questionnaire resource. --- packages/workflow/src/records/handler/update-field.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/workflow/src/records/handler/update-field.ts b/packages/workflow/src/records/handler/update-field.ts index 7ccd6a5c01b..c4d74b1bcb5 100644 --- a/packages/workflow/src/records/handler/update-field.ts +++ b/packages/workflow/src/records/handler/update-field.ts @@ -61,12 +61,7 @@ export async function updateField( const updatedRecord = { ...savedRecord, - entry: [ - ...savedRecord.entry.filter( - ({ resource }) => !isQuestionnaireResponse(resource) - ), - updatedQuestionnaireResponseResource - ] + entry: [updatedQuestionnaireResponseResource] } await sendBundleToHearth(updatedRecord) From 5e9dd041a60532a6b4c177e11b591cac1a1aa199 Mon Sep 17 00:00:00 2001 From: anjana6 Date: Thu, 30 Jan 2025 15:40:35 +0530 Subject: [PATCH 11/15] Scope removed from the /config API --- packages/config/src/config/routes.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/config/src/config/routes.ts b/packages/config/src/config/routes.ts index 80d854e8931..b1b2cc286dc 100644 --- a/packages/config/src/config/routes.ts +++ b/packages/config/src/config/routes.ts @@ -53,13 +53,6 @@ export default function getRoutes(): ServerRoute[] { path: '/config', handler: configHandler, options: { - auth: { - scope: [ - SCOPES.CONFIG_UPDATE_ALL, - SCOPES.USER_DATA_SEEDING, - SCOPES.SELF_SERVICE_PORTAL - ] - }, tags: ['api'], description: 'Retrieve all configuration' } From 198a165bc876d8585468e01642a2de8826adc4d5 Mon Sep 17 00:00:00 2001 From: PathumN99 Date: Thu, 30 Jan 2025 16:36:15 +0530 Subject: [PATCH 12/15] Unit test case fixes in workflow package --- .../workflow/src/records/handler/archive.test.ts | 12 ++++++++++++ .../workflow/src/records/handler/certify.test.ts | 12 ++++++++++++ packages/workflow/src/records/handler/issue.test.ts | 12 ++++++++++++ .../workflow/src/records/handler/reject.test.ts | 12 ++++++++++++ .../workflow/src/records/handler/validate.test.ts | 13 +++++++++++++ 5 files changed, 61 insertions(+) diff --git a/packages/workflow/src/records/handler/archive.test.ts b/packages/workflow/src/records/handler/archive.test.ts index 676a2261d11..5e79bc3d587 100644 --- a/packages/workflow/src/records/handler/archive.test.ts +++ b/packages/workflow/src/records/handler/archive.test.ts @@ -22,6 +22,10 @@ import { } from '@opencrvs/commons/types' import { READY_FOR_REVIEW_BIRTH_RECORD } from '@test/mocks/records/readyForReview' import { SCOPES } from '@opencrvs/commons/authentication' +import { invokeWebhooks } from '../webhooks' +import { getEventType } from '@workflow/features/registration/utils' + +jest.mock('../webhooks') describe('archive record endpoint', () => { let server: Awaited> @@ -85,6 +89,14 @@ describe('archive record endpoint', () => { } }) + expect(invokeWebhooks).toHaveBeenCalledWith({ + bundle: READY_FOR_REVIEW_BIRTH_RECORD, + token, + event: getEventType(READY_FOR_REVIEW_BIRTH_RECORD), + isNotRegistered: true, + statusType: 'archived' + }) + const task = getTaskFromSavedBundle(JSON.parse(res.payload) as ValidRecord) const businessStatus = getStatusFromTask(task) diff --git a/packages/workflow/src/records/handler/certify.test.ts b/packages/workflow/src/records/handler/certify.test.ts index b2030b912f3..55781837b5b 100644 --- a/packages/workflow/src/records/handler/certify.test.ts +++ b/packages/workflow/src/records/handler/certify.test.ts @@ -22,6 +22,10 @@ import { } from '@opencrvs/commons/types' import { REGISTERED_BIRTH_RECORD } from '@test/mocks/records/register' import { SCOPES } from '@opencrvs/commons/authentication' +import { invokeWebhooks } from '../webhooks' +import { getEventType } from '@workflow/features/registration/utils' + +jest.mock('../webhooks') describe('Certify record endpoint', () => { let server: Awaited> @@ -241,6 +245,14 @@ describe('Certify record endpoint', () => { } }) + expect(invokeWebhooks).toHaveBeenCalledWith({ + bundle: REGISTERED_BIRTH_RECORD, + token, + event: getEventType(REGISTERED_BIRTH_RECORD), + isNotRegistered: true, + statusType: 'certified' + }) + const certifiedRecord = JSON.parse(response.payload) as CertifiedRecord const task = getTaskFromSavedBundle(certifiedRecord) const businessStatus = getStatusFromTask(task) diff --git a/packages/workflow/src/records/handler/issue.test.ts b/packages/workflow/src/records/handler/issue.test.ts index 887988ed362..ea04e3e29a5 100644 --- a/packages/workflow/src/records/handler/issue.test.ts +++ b/packages/workflow/src/records/handler/issue.test.ts @@ -22,6 +22,10 @@ import { } from '@opencrvs/commons/types' import { CERTIFIED_BIRTH_RECORD } from '@test/mocks/records/certify' import { SCOPES } from '@opencrvs/commons/authentication' +import { invokeWebhooks } from '../webhooks' +import { getEventType } from '@workflow/features/registration/utils' + +jest.mock('../webhooks') describe('Issue record endpoint', () => { let server: Awaited> @@ -127,6 +131,14 @@ describe('Issue record endpoint', () => { } }) + expect(invokeWebhooks).toHaveBeenCalledWith({ + bundle: CERTIFIED_BIRTH_RECORD, + token, + event: getEventType(CERTIFIED_BIRTH_RECORD), + isNotRegistered: true, + statusType: 'issued' + }) + const issuedRecord = JSON.parse(response.payload) as IssuedRecord const task = getTaskFromSavedBundle(issuedRecord) const businessStatus = getStatusFromTask(task) diff --git a/packages/workflow/src/records/handler/reject.test.ts b/packages/workflow/src/records/handler/reject.test.ts index 750041a90c8..ce1002c78a6 100644 --- a/packages/workflow/src/records/handler/reject.test.ts +++ b/packages/workflow/src/records/handler/reject.test.ts @@ -23,6 +23,10 @@ import { } from '@opencrvs/commons/types' import { READY_FOR_REVIEW_BIRTH_RECORD } from '@test/mocks/records/readyForReview' import { SCOPES } from '@opencrvs/commons/authentication' +import { invokeWebhooks } from '../webhooks' +import { getEventType } from '@workflow/features/registration/utils' + +jest.mock('../webhooks') function getReasonFromTask(task: SavedTask) { return task.statusReason?.text @@ -95,6 +99,14 @@ describe('Reject record endpoint', () => { } }) + expect(invokeWebhooks).toHaveBeenCalledWith({ + bundle: READY_FOR_REVIEW_BIRTH_RECORD, + token, + event: getEventType(READY_FOR_REVIEW_BIRTH_RECORD), + isNotRegistered: true, + statusType: 'rejected' + }) + const task = getTaskFromSavedBundle( JSON.parse(response.payload) as ValidRecord ) diff --git a/packages/workflow/src/records/handler/validate.test.ts b/packages/workflow/src/records/handler/validate.test.ts index 5563fc3e3bf..39de0e599c7 100644 --- a/packages/workflow/src/records/handler/validate.test.ts +++ b/packages/workflow/src/records/handler/validate.test.ts @@ -22,6 +22,10 @@ import { ValidRecord } from '@opencrvs/commons/types' import { SCOPES } from '@opencrvs/commons/authentication' +import { invokeWebhooks } from '../webhooks' +import { getEventType } from '@workflow/features/registration/utils' + +jest.mock('../webhooks') describe('Validate record endpoint', () => { let server: Awaited> @@ -88,6 +92,15 @@ describe('Validate record endpoint', () => { const task = getTaskFromSavedBundle( JSON.parse(response.payload) as ValidRecord ) + + expect(invokeWebhooks).toHaveBeenCalledWith({ + bundle: READY_FOR_REVIEW_BIRTH_RECORD, + token, + event: getEventType(READY_FOR_REVIEW_BIRTH_RECORD), + isNotRegistered: true, + statusType: 'validated' + }) + const businessStatus = getStatusFromTask(task) expect(response.statusCode).toBe(200) From 3c2653cbfa4a13dc915d95f5f5c1acc62c009a3b Mon Sep 17 00:00:00 2001 From: PathumN99 Date: Thu, 30 Jan 2025 17:06:12 +0530 Subject: [PATCH 13/15] import changes in test case files --- packages/workflow/src/records/handler/archive.test.ts | 4 ++-- packages/workflow/src/records/handler/certify.test.ts | 4 ++-- packages/workflow/src/records/handler/issue.test.ts | 4 ++-- packages/workflow/src/records/handler/reject.test.ts | 4 ++-- packages/workflow/src/records/handler/validate.test.ts | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/workflow/src/records/handler/archive.test.ts b/packages/workflow/src/records/handler/archive.test.ts index 5e79bc3d587..080504699e5 100644 --- a/packages/workflow/src/records/handler/archive.test.ts +++ b/packages/workflow/src/records/handler/archive.test.ts @@ -22,10 +22,10 @@ import { } from '@opencrvs/commons/types' import { READY_FOR_REVIEW_BIRTH_RECORD } from '@test/mocks/records/readyForReview' import { SCOPES } from '@opencrvs/commons/authentication' -import { invokeWebhooks } from '../webhooks' +import { invokeWebhooks } from '@workflow/records/webhooks' import { getEventType } from '@workflow/features/registration/utils' -jest.mock('../webhooks') +jest.mock('@workflow/records/webhooks') describe('archive record endpoint', () => { let server: Awaited> diff --git a/packages/workflow/src/records/handler/certify.test.ts b/packages/workflow/src/records/handler/certify.test.ts index 55781837b5b..1e10b9560b8 100644 --- a/packages/workflow/src/records/handler/certify.test.ts +++ b/packages/workflow/src/records/handler/certify.test.ts @@ -22,10 +22,10 @@ import { } from '@opencrvs/commons/types' import { REGISTERED_BIRTH_RECORD } from '@test/mocks/records/register' import { SCOPES } from '@opencrvs/commons/authentication' -import { invokeWebhooks } from '../webhooks' +import { invokeWebhooks } from '@workflow/records/webhooks' import { getEventType } from '@workflow/features/registration/utils' -jest.mock('../webhooks') +jest.mock('@workflow/records/webhooks') describe('Certify record endpoint', () => { let server: Awaited> diff --git a/packages/workflow/src/records/handler/issue.test.ts b/packages/workflow/src/records/handler/issue.test.ts index ea04e3e29a5..915f7077655 100644 --- a/packages/workflow/src/records/handler/issue.test.ts +++ b/packages/workflow/src/records/handler/issue.test.ts @@ -22,10 +22,10 @@ import { } from '@opencrvs/commons/types' import { CERTIFIED_BIRTH_RECORD } from '@test/mocks/records/certify' import { SCOPES } from '@opencrvs/commons/authentication' -import { invokeWebhooks } from '../webhooks' +import { invokeWebhooks } from '@workflow/records/webhooks' import { getEventType } from '@workflow/features/registration/utils' -jest.mock('../webhooks') +jest.mock('@workflow/records/webhooks') describe('Issue record endpoint', () => { let server: Awaited> diff --git a/packages/workflow/src/records/handler/reject.test.ts b/packages/workflow/src/records/handler/reject.test.ts index ce1002c78a6..2bafbc137eb 100644 --- a/packages/workflow/src/records/handler/reject.test.ts +++ b/packages/workflow/src/records/handler/reject.test.ts @@ -23,10 +23,10 @@ import { } from '@opencrvs/commons/types' import { READY_FOR_REVIEW_BIRTH_RECORD } from '@test/mocks/records/readyForReview' import { SCOPES } from '@opencrvs/commons/authentication' -import { invokeWebhooks } from '../webhooks' +import { invokeWebhooks } from '@workflow/records/webhooks' import { getEventType } from '@workflow/features/registration/utils' -jest.mock('../webhooks') +jest.mock('@workflow/records/webhooks') function getReasonFromTask(task: SavedTask) { return task.statusReason?.text diff --git a/packages/workflow/src/records/handler/validate.test.ts b/packages/workflow/src/records/handler/validate.test.ts index 39de0e599c7..069156cf8b1 100644 --- a/packages/workflow/src/records/handler/validate.test.ts +++ b/packages/workflow/src/records/handler/validate.test.ts @@ -22,10 +22,10 @@ import { ValidRecord } from '@opencrvs/commons/types' import { SCOPES } from '@opencrvs/commons/authentication' -import { invokeWebhooks } from '../webhooks' import { getEventType } from '@workflow/features/registration/utils' +import { invokeWebhooks } from '@workflow/records/webhooks' -jest.mock('../webhooks') +jest.mock('@workflow/records/webhooks') describe('Validate record endpoint', () => { let server: Awaited> From b2e6aeeede38853ee36f1b2fca35ca9050ed4ec2 Mon Sep 17 00:00:00 2001 From: anjana6 Date: Mon, 3 Feb 2025 11:54:45 +0530 Subject: [PATCH 14/15] Enable system clients access to the markBirthAsCertified and markBirthAsIssued GraphQL request to system clients --- .../src/features/registration/root-resolvers.ts | 10 ++++++++-- packages/workflow/src/records/handler/certify.ts | 5 ++++- packages/workflow/src/records/handler/issue.ts | 5 ++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/gateway/src/features/registration/root-resolvers.ts b/packages/gateway/src/features/registration/root-resolvers.ts index 18d51cd915f..818a902e416 100644 --- a/packages/gateway/src/features/registration/root-resolvers.ts +++ b/packages/gateway/src/features/registration/root-resolvers.ts @@ -513,7 +513,10 @@ export const resolvers: GQLResolver = { }, markBirthAsCertified: requireAssignment( async (_, { id, details }, { headers: authHeader }) => { - if (!hasScope(authHeader, SCOPES.RECORD_PRINT_ISSUE_CERTIFIED_COPIES)) { + if ( + !hasScope(authHeader, SCOPES.RECORD_PRINT_ISSUE_CERTIFIED_COPIES) && + !hasScope(authHeader, SCOPES.SELF_SERVICE_PORTAL) + ) { throw new Error('User does not have enough scope') } return markEventAsCertified(id, details, authHeader, EVENT_TYPE.BIRTH) @@ -521,7 +524,10 @@ export const resolvers: GQLResolver = { ), // @todo: add new query for certify and issue later where require assignment wrapper will be used markBirthAsIssued: (_, { id, details }, { headers: authHeader }) => { - if (!hasScope(authHeader, SCOPES.RECORD_PRINT_ISSUE_CERTIFIED_COPIES)) { + if ( + !hasScope(authHeader, SCOPES.RECORD_PRINT_ISSUE_CERTIFIED_COPIES) && + !hasScope(authHeader, SCOPES.SELF_SERVICE_PORTAL) + ) { throw new Error('User does not have enough scope') } return markEventAsIssued(id, details, authHeader, EVENT_TYPE.BIRTH) diff --git a/packages/workflow/src/records/handler/certify.ts b/packages/workflow/src/records/handler/certify.ts index 7ecd6325936..0e1110bb47c 100644 --- a/packages/workflow/src/records/handler/certify.ts +++ b/packages/workflow/src/records/handler/certify.ts @@ -28,7 +28,10 @@ export const certifyRoute = createRoute({ allowedStartStates: ['REGISTERED'], action: 'CERTIFY', includeHistoryResources: true, - allowedScopes: [SCOPES.RECORD_PRINT_ISSUE_CERTIFIED_COPIES], + allowedScopes: [ + SCOPES.RECORD_PRINT_ISSUE_CERTIFIED_COPIES, + SCOPES.SELF_SERVICE_PORTAL + ], handler: async (request, record): Promise => { const token = getToken(request) const { certificate: certificateDetailsWithRawAttachments, event } = diff --git a/packages/workflow/src/records/handler/issue.ts b/packages/workflow/src/records/handler/issue.ts index 8b4ac71ab3b..d74c0f2886a 100644 --- a/packages/workflow/src/records/handler/issue.ts +++ b/packages/workflow/src/records/handler/issue.ts @@ -28,7 +28,10 @@ export const issueRoute = createRoute({ allowedStartStates: ['CERTIFIED'], action: 'ISSUE', includeHistoryResources: true, - allowedScopes: [SCOPES.RECORD_PRINT_ISSUE_CERTIFIED_COPIES], + allowedScopes: [ + SCOPES.RECORD_PRINT_ISSUE_CERTIFIED_COPIES, + SCOPES.SELF_SERVICE_PORTAL + ], handler: async (request, record): Promise => { const token = getToken(request) const { certificate: certificateDetailsWithRawAttachments, event } = From 94c21ccc122a2c83fbb1f98776524f2d35af96c8 Mon Sep 17 00:00:00 2001 From: PathumN99 Date: Mon, 3 Feb 2025 15:15:45 +0530 Subject: [PATCH 15/15] Added self service portal scope check in markDeathAsIssued & markDeathAsCertified --- .../src/features/registration/root-resolvers.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/gateway/src/features/registration/root-resolvers.ts b/packages/gateway/src/features/registration/root-resolvers.ts index 818a902e416..7e202cea767 100644 --- a/packages/gateway/src/features/registration/root-resolvers.ts +++ b/packages/gateway/src/features/registration/root-resolvers.ts @@ -534,7 +534,10 @@ export const resolvers: GQLResolver = { }, markDeathAsCertified: requireAssignment( (_, { id, details }, { headers: authHeader }) => { - if (!hasScope(authHeader, SCOPES.RECORD_PRINT_ISSUE_CERTIFIED_COPIES)) { + if ( + !hasScope(authHeader, SCOPES.RECORD_PRINT_ISSUE_CERTIFIED_COPIES) && + !hasScope(authHeader, SCOPES.SELF_SERVICE_PORTAL) + ) { throw new Error('User does not have enough scope') } return markEventAsCertified(id, details, authHeader, EVENT_TYPE.DEATH) @@ -542,7 +545,10 @@ export const resolvers: GQLResolver = { ), // @todo: add new query for certify and issue later where require assignment wrapper will be used markDeathAsIssued: (_, { id, details }, { headers: authHeader }) => { - if (!hasScope(authHeader, SCOPES.RECORD_PRINT_ISSUE_CERTIFIED_COPIES)) { + if ( + !hasScope(authHeader, SCOPES.RECORD_PRINT_ISSUE_CERTIFIED_COPIES) && + !hasScope(authHeader, SCOPES.SELF_SERVICE_PORTAL) + ) { throw new Error('User does not have enough scope') } return markEventAsIssued(id, details, authHeader, EVENT_TYPE.DEATH)