diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index 2a467640105..b4b84ef792a 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -69,8 +69,9 @@ import { ReviewCorrection } from './views/ReviewCorrection/ReviewCorrection' import { ReviewCertificate } from './views/PrintCertificate/ReviewCertificateAction' import AllUserEmail from './views/SysAdmin/Communications/AllUserEmail/AllUserEmail' import { ReloadModal } from './views/Modals/ReloadModal' -import { Workqueues } from './v2-events/workqueues' -import { V2_ROOT_ROUTE } from './v2-events/routes' +import { V2_EVENT_ROUTE, V2_ROOT_ROUTE } from './v2-events/routes/routes' +import { Workqueues } from './v2-events/features/workqueues' +import { PublishEvent } from './v2-events/features/events/PublishEvent' interface IAppProps { client?: ApolloClient @@ -526,6 +527,11 @@ export function App(props: IAppProps) { path={V2_ROOT_ROUTE} component={Workqueues} /> + ) diff --git a/packages/client/src/v2-events/features/events/PublishEvent.tsx b/packages/client/src/v2-events/features/events/PublishEvent.tsx new file mode 100644 index 00000000000..c84eddccc1e --- /dev/null +++ b/packages/client/src/v2-events/features/events/PublishEvent.tsx @@ -0,0 +1,82 @@ +/* + * 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 { + Frame, + AppBar, + Stack, + Button, + Icon, + Content +} from '@opencrvs/components' +import React from 'react' +import { useEvent } from './useEvent' +import { useParams } from 'react-router-dom' + +export function PublishEvent() { + const { eventType } = useParams<{ eventType: string }>() + const { title, exit, saveAndExit, previous, next, finish } = + useEvent(eventType) + + return ( + + + + + } + /> + } + > + + + {previous && ( + + )} + + + + + Continue + + ]} + > + This is where the form will be rendered + + + + + ) +} diff --git a/packages/client/src/v2-events/features/events/fixtures.ts b/packages/client/src/v2-events/features/events/fixtures.ts new file mode 100644 index 00000000000..2e8782b4cc4 --- /dev/null +++ b/packages/client/src/v2-events/features/events/fixtures.ts @@ -0,0 +1,129 @@ +/* + * 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. + */ +export const tennisClubMembershipEvent = { + id: 'TENNIS_CLUB_MEMBERSHIP', + label: { + defaultMessage: 'Tennis club membership application', + description: 'This is what this event is referred as in the system', + id: 'event.tennis-club-membership.label' + }, + actions: [ + { + type: 'DECLARE', + label: { + defaultMessage: 'Send an application', + description: + 'This is shown as the action name anywhere the user can trigger the action from', + id: 'event.tennis-club-membership.action.declare.label' + }, + forms: [ + { + label: { + id: 'event.tennis-club-membership.action.declare.form.label', + defaultMessage: 'Tennis club membership application', + description: 'This is what this form is referred as in the system' + }, + active: true, + version: { + id: '1.0.0', + label: { + id: 'event.tennis-club-membership.action.declare.form.version.1', + defaultMessage: 'Version 1', + description: 'This is the first version of the form' + } + }, + pages: [ + { + id: 'applicant', + title: { + id: 'event.tennis-club-membership.action.declare.form.section.who.title', + defaultMessage: 'Who is applying for the membership?', + description: 'This is the title of the section' + }, + fields: [ + { + id: 'applicant.firstname', + type: 'TEXT', + required: true, + label: { + defaultMessage: "Applicant's first name", + description: 'This is the label for the field', + id: 'event.tennis-club-membership.action.declare.form.section.who.field.firstname.label' + } + }, + { + id: 'applicant.surname', + type: 'TEXT', + required: true, + label: { + defaultMessage: "Applicant's surname", + description: 'This is the label for the field', + id: 'event.tennis-club-membership.action.declare.form.section.who.field.surname.label' + } + }, + { + id: 'applicant.dob', + type: 'DATE', + required: true, + label: { + defaultMessage: "Applicant's date of birth", + description: 'This is the label for the field', + id: 'event.tennis-club-membership.action.declare.form.section.who.field.dob.label' + } + } + ] + }, + { + id: 'recommender', + title: { + id: 'event.tennis-club-membership.action.declare.form.section.recommender.title', + defaultMessage: 'Who is recommending the applicant?', + description: 'This is the title of the section' + }, + fields: [ + { + id: 'recommender.firstname', + type: 'TEXT', + required: true, + label: { + defaultMessage: "Recommender's first name", + description: 'This is the label for the field', + id: 'event.tennis-club-membership.action.declare.form.section.recommender.field.firstname.label' + } + }, + { + id: 'recommender.surname', + type: 'TEXT', + required: true, + label: { + defaultMessage: "Recommender's surname", + description: 'This is the label for the field', + id: 'event.tennis-club-membership.action.declare.form.section.recommender.field.surname.label' + } + }, + { + id: 'recommender.id', + type: 'TEXT', + required: true, + label: { + defaultMessage: "Recommender's membership ID", + description: 'This is the label for the field', + id: 'event.tennis-club-membership.action.declare.form.section.recommender.field.id.label' + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/client/src/v2-events/features/events/useEvent.ts b/packages/client/src/v2-events/features/events/useEvent.ts new file mode 100644 index 00000000000..7ed530dfb3d --- /dev/null +++ b/packages/client/src/v2-events/features/events/useEvent.ts @@ -0,0 +1,48 @@ +/* + * 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 { tennisClubMembershipEvent } from './fixtures' +import { useIntl } from 'react-intl' +import { usePagination } from './usePagination' +import { FieldConfig } from '@opencrvs/commons' + +const eventTypes = { 'tennis-club-membership': tennisClubMembershipEvent } + +export function useEvent(anyEventType: string) { + const intl = useIntl() + + if (!eventTypes[anyEventType as keyof typeof eventTypes]) { + throw new Error(`Event type ${anyEventType} not found`) + } + + const type = anyEventType as keyof typeof eventTypes + const { pages, label } = eventTypes[type].actions[0].forms[0] + + const { next, previous, page } = usePagination(pages.length) + + const exit = () => alert('exit') + const saveAndExit = () => alert('save and exit') + const finish = () => alert('finish') + + const title = intl.formatMessage(label) + + return { + type, + title, + exit, + saveAndExit, + previous, + next, + finish, + form: pages, + fields: pages[page].fields as FieldConfig[] + } +} diff --git a/packages/client/src/v2-events/features/events/usePagination.ts b/packages/client/src/v2-events/features/events/usePagination.ts new file mode 100644 index 00000000000..00f12a689c9 --- /dev/null +++ b/packages/client/src/v2-events/features/events/usePagination.ts @@ -0,0 +1,29 @@ +/* + * 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 { useState } from 'react' + +// TODO: Paginate with react-router-dom +export const usePagination = ( + /** Amount of pages to iterate through */ + pages: number +) => { + const [page, setPage] = useState(0) + + const next = page < pages - 1 ? () => setPage(page + 1) : undefined + const previous = page > 0 ? () => setPage(page - 1) : undefined + + return { + /** Page number between 0 and pages - 1 */ + page, + next, + previous + } +} diff --git a/packages/client/src/v2-events/workqueues/index.tsx b/packages/client/src/v2-events/features/workqueues/index.tsx similarity index 86% rename from packages/client/src/v2-events/workqueues/index.tsx rename to packages/client/src/v2-events/features/workqueues/index.tsx index 60483d5a7da..8065a50ecc7 100644 --- a/packages/client/src/v2-events/workqueues/index.tsx +++ b/packages/client/src/v2-events/features/workqueues/index.tsx @@ -8,9 +8,8 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ - import React from 'react' -export function Workqueues() { - return
This is where it starts from
+export const Workqueues = () => { + return
Workqueues
} diff --git a/packages/client/src/v2-events/routes.ts b/packages/client/src/v2-events/routes/routes.ts similarity index 88% rename from packages/client/src/v2-events/routes.ts rename to packages/client/src/v2-events/routes/routes.ts index 4fdd9235d68..6d7efe2e514 100644 --- a/packages/client/src/v2-events/routes.ts +++ b/packages/client/src/v2-events/routes/routes.ts @@ -9,3 +9,4 @@ * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ export const V2_ROOT_ROUTE = '/v2' +export const V2_EVENT_ROUTE = `${V2_ROOT_ROUTE}/event/:eventType` diff --git a/packages/client/vite.config.ts b/packages/client/vite.config.ts index dc817bad608..cc2736c8ba1 100644 --- a/packages/client/vite.config.ts +++ b/packages/client/vite.config.ts @@ -78,7 +78,7 @@ export default defineConfig(({ mode }) => { APP_VERSION: JSON.stringify(process.env.npm_package_version) }, optimizeDeps: { - include: ['@opencrvs/commons/client'] + include: ['@opencrvs/commons/client', '@opencrvs/commons/events'] }, // This changes the output dir from dist to build build: { diff --git a/packages/commons/src/events/Action.ts b/packages/commons/src/events/Action.ts deleted file mode 100644 index c9007ad9903..00000000000 --- a/packages/commons/src/events/Action.ts +++ /dev/null @@ -1,96 +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 { z } from 'zod' -import { GetValues, NonEmptyArray } from '../types' -import { Form } from './Form' -import { Label } from './utils' - -/** - * Actions recognized by the system - */ -export const ActionType = { - CREATE: 'CREATE', - ASSIGN: 'ASSIGN', - UNASSIGN: 'UNASSIGN', - REGISTER: 'REGISTER', - VALIDATE: 'VALIDATE', - CORRECT: 'CORRECT', - DETECT_DUPLICATE: 'DETECT_DUPLICATE', - NOTIFY: 'NOTIFY', - DECLARE: 'DECLARE' -} as const - -export const actionTypes = Object.values(ActionType) -export type ActionType = GetValues - -/** - * Configuration of action performed on an event. - * Includes roles that can perform the action, label and forms involved. - */ -export const ActionConfig = z.object({ - type: z.enum(actionTypes as NonEmptyArray), - label: Label, - forms: z.array(Form) -}) - -export const ActionInputBase = z.object({ - fields: z.array( - z.object({ - id: z.string(), - value: z.union([ - z.string(), - z.number(), - z.array( - z.object({ - optionValues: z.array(z.string()), - type: z.string(), - data: z.string(), - fileSize: z.number() - }) - ) - ]) - }) - ) -}) - -export const CreateActionInput = ActionInputBase.extend({}) -export const NotifyActionInput = ActionInputBase.extend({}) -export const DeclareActionInput = ActionInputBase.extend({}) -export const RegisterActionInput = ActionInputBase.extend({ - identifiers: z.object({ - trackingId: z.string(), - registrationNumber: z.string() - }) -}) - -export const ActionInput = z - .union([ - CreateActionInput.extend({ type: z.enum([ActionType.CREATE]) }), - NotifyActionInput.extend({ type: z.enum([ActionType.NOTIFY]) }), - DeclareActionInput.extend({ type: z.enum([ActionType.DECLARE]) }), - RegisterActionInput.extend({ type: z.enum([ActionType.REGISTER]) }) - ]) - .and( - z.object({ - createdBy: z.string() - }) - ) - -export type ActionInput = z.infer - -export const Action = ActionInput.and( - z.object({ - createdAt: z.date(), - createdBy: z.string() - }) -) - -export type Action = z.infer diff --git a/packages/commons/src/events/ActionConfig.ts b/packages/commons/src/events/ActionConfig.ts new file mode 100644 index 00000000000..994e52ba56b --- /dev/null +++ b/packages/commons/src/events/ActionConfig.ts @@ -0,0 +1,57 @@ +/* + * 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 { z } from 'zod' +import { FormConfig } from './FormConfig' +import { TranslationConfig } from './TranslationConfig' + +export const ActionConfigBase = z.object({ + label: TranslationConfig, + forms: z.array(FormConfig) +}) + +/** + * Actions recognized by the system + */ +export const ActionType = { + CREATE: 'CREATE', + ASSIGN: 'ASSIGN', + UNASSIGN: 'UNASSIGN', + REGISTER: 'REGISTER', + VALIDATE: 'VALIDATE', + CORRECT: 'CORRECT', + DETECT_DUPLICATE: 'DETECT_DUPLICATE', + NOTIFY: 'NOTIFY', + DECLARE: 'DECLARE' +} as const + +const CreateConfig = ActionConfigBase.merge( + z.object({ + type: z.literal(ActionType.CREATE) + }) +) + +const DeclareConfig = ActionConfigBase.merge( + z.object({ + type: z.literal(ActionType.DECLARE) + }) +) + +const RegisterConfig = ActionConfigBase.merge( + z.object({ + type: z.literal(ActionType.REGISTER) + }) +) + +export const ActionConfig = z.discriminatedUnion('type', [ + CreateConfig, + DeclareConfig, + RegisterConfig +]) diff --git a/packages/commons/src/events/Event.ts b/packages/commons/src/events/Event.ts deleted file mode 100644 index 61f25f085f1..00000000000 --- a/packages/commons/src/events/Event.ts +++ /dev/null @@ -1,68 +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 { z } from 'zod' -import { Action, ActionConfig, ActionType } from './Action' -import { Label, Summary } from './utils' - -/** - * A subset of an event. Describes fields that can be sent to the system with the intention of either creating or mutating a an event - */ -export const EventInput = z.object({ - type: z.string() -}) -export type EventInput = z.infer - -/** - * Description of event features defined by the country. Includes configuration for process steps and forms involved. - */ -export const EventConfig = z.object({ - id: z.string(), - label: Label, - summary: Summary, - actions: z.array(ActionConfig) -}) - -export type EventConfig = z.infer - -/** - * A subset of an event. Describes how the event is stored in the search index. Contains static fields shared by all event types and custom fields defined by event configuration - */ - -export const EventIndex = z.object({ - id: z.string(), - event: z.string(), - status: z.enum([ActionType.CREATE]), - createdAt: z.date(), - createdBy: z.string(), - createdAtLocation: z.string(), // uuid - modifiedAt: z.date(), - assignedTo: z.string(), - updatedBy: z.string(), - data: z.object({}) -}) - -export type EventIndex = z.infer - -export const Event = EventInput.extend({ - id: z.string(), - type: z.string(), // Should be replaced by a reference to a form version - createdAt: z.date(), - updatedAt: z.date(), - actions: z.array(Action) -}) - -export type Event = z.infer - -/** - * Builds a validated configuration for an event - * @param config - Event specific configuration - */ -export const defineConfig = (config: EventConfig) => EventConfig.parse(config) diff --git a/packages/commons/src/events/EventConfig.ts b/packages/commons/src/events/EventConfig.ts new file mode 100644 index 00000000000..000e3a38a68 --- /dev/null +++ b/packages/commons/src/events/EventConfig.ts @@ -0,0 +1,36 @@ +/* + * 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 { z } from 'zod' +import { ActionConfig } from './ActionConfig' +import { TranslationConfig } from './TranslationConfig' + +/** + * Description of event features defined by the country. Includes configuration for process steps and forms involved. + * + * `Event.parse(config)` will throw an error if the configuration is invalid. + */ +export const EventConfig = z.object({ + id: z + .string() + .describe( + 'A machine-readable identifier for the event, e.g. "birth" or "death"' + ), + label: TranslationConfig, + actions: z.array(ActionConfig) +}) + +export type EventConfig = z.infer + +/** + * Builds a validated configuration for an event + * @param config - Event specific configuration + */ +export const defineConfig = (config: EventConfig) => EventConfig.parse(config) diff --git a/packages/commons/src/events/FieldConfig.ts b/packages/commons/src/events/FieldConfig.ts new file mode 100644 index 00000000000..553ea193e38 --- /dev/null +++ b/packages/commons/src/events/FieldConfig.ts @@ -0,0 +1,54 @@ +/* + * 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 { z } from 'zod' +import { TranslationConfig } from './TranslationConfig' + +const BaseField = z.object({ + id: z.string(), + required: z.boolean(), + label: TranslationConfig +}) + +const TextField = BaseField.describe('Text input').merge( + z.object({ + type: z.literal('TEXT'), + options: z + .object({ + maxLength: z.number().describe('Maximum length of the text') + }) + .optional() + }) +) + +const DateField = BaseField.describe('A single date input (dd-mm-YYYY)').merge( + z.object({ + type: z.literal('DATE'), + options: z + .object({ + notice: TranslationConfig.describe( + 'Text to display above the date input' + ) + }) + .optional() + }) +) + +const Paragraph = BaseField.describe('A read-only HTML

paragraph').merge( + z.object({ type: z.literal('PARAGRAPH') }) +) + +export const FieldConfig = z.discriminatedUnion('type', [ + TextField, + DateField, + Paragraph +]) + +export type FieldConfig = z.infer diff --git a/packages/commons/src/events/FormConfig.ts b/packages/commons/src/events/FormConfig.ts new file mode 100644 index 00000000000..989c94f09ad --- /dev/null +++ b/packages/commons/src/events/FormConfig.ts @@ -0,0 +1,37 @@ +/* + * 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 { z } from 'zod' +import { FieldConfig } from './FieldConfig' +import { TranslationConfig } from './TranslationConfig' + +export const FormConfig = z.object({ + label: TranslationConfig.describe('Human readable description of the form'), + version: z.object({ + id: z + .string() + .describe( + 'Form version. Semantic versioning recommended. Example: 0.0.1' + ), + label: TranslationConfig.describe( + 'Human readable description of the version' + ) + }), + active: z.boolean().default(false).describe('Whether the form is active'), + pages: z.array( + z.object({ + id: z.string().describe('Unique identifier for the page'), + title: TranslationConfig.describe('Header title of the page'), + fields: z.array(FieldConfig).describe('Fields to be rendered on the page') + }) + ) +}) + +export type FormConfig = z.infer diff --git a/packages/commons/src/events/TranslationConfig.ts b/packages/commons/src/events/TranslationConfig.ts new file mode 100644 index 00000000000..0df21e94c0c --- /dev/null +++ b/packages/commons/src/events/TranslationConfig.ts @@ -0,0 +1,25 @@ +/* + * 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 { z } from 'zod' + +export const TranslationConfig = z.object({ + id: z + .string() + .describe( + 'The identifier of the translation referred in translation CSV files' + ), + defaultMessage: z.string().describe('Default translation message'), + description: z + .string() + .describe( + 'Describe the translation for a translator to be able to identify it.' + ) +}) diff --git a/packages/commons/src/events/index.ts b/packages/commons/src/events/index.ts index 729d13e300f..2cf6138b6dd 100644 --- a/packages/commons/src/events/index.ts +++ b/packages/commons/src/events/index.ts @@ -8,7 +8,7 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -export * from './Action' -export * from './Event' -export * from './Form' -export * from './utils' +export * from './ActionConfig' +export * from './EventConfig' +export * from './FieldConfig' +export * from './FormConfig' diff --git a/packages/commons/src/events/utils.ts b/packages/commons/src/events/utils.ts deleted file mode 100644 index 61af9351fa2..00000000000 --- a/packages/commons/src/events/utils.ts +++ /dev/null @@ -1,53 +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 { GetValues } from '../types' -import { z } from 'zod' - -export const Label = z.object({ - defaultMessage: z.string(), - description: z.string(), - id: z.string() -}) - -// Ask whether these are always together -export const Field = z.object({ - label: Label -}) - -export const Summary = z.object({ - title: Label, - fields: z.array(Field) -}) - -// @TODO -export const Query = z.object({ - requester: z.object({ - phone: z.object({ - check: z.string() - }), - name: z.object({ - check: z.string() - }) - }) -}) - -export const SystemRoleType = { - FieldAgent: 'FIELD_AGENT', - LocalRegistrar: 'LOCAL_REGISTRAR', - LocalSystemAdmin: 'LOCAL_SYSTEM_ADMIN', - NationalRegistrar: 'NATIONAL_REGISTRAR', - NationalSystemAdmin: 'NATIONAL_SYSTEM_ADMIN', - PerformanceManagement: 'PERFORMANCE_MANAGEMENT', - RegistrationAgent: 'REGISTRATION_AGENT' -} as const - -export type SystemRoleType = GetValues -export const systemRoleTypes = Object.values(SystemRoleType) diff --git a/packages/commons/src/events/fixtures/tennis-club-membership-event.ts b/packages/commons/src/fixtures/tennis-club-membership-event.ts similarity index 90% rename from packages/commons/src/events/fixtures/tennis-club-membership-event.ts rename to packages/commons/src/fixtures/tennis-club-membership-event.ts index 9d8765cf12f..b100b4c4b97 100644 --- a/packages/commons/src/events/fixtures/tennis-club-membership-event.ts +++ b/packages/commons/src/fixtures/tennis-club-membership-event.ts @@ -8,18 +8,10 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { defineConfig } from '../Event' +import { defineConfig } from '../events' export const tennisClubMembershipEvent = defineConfig({ id: 'TENNIS_CLUB_MEMBERSHIP', - summary: { - title: { - defaultMessage: 'Tennis club membership application', - description: 'This is the title of the form', - id: 'event.tennis-club-membership.summary.title' - }, - fields: [] - }, label: { defaultMessage: 'Tennis club membership application', description: 'This is what this event is referred as in the system', @@ -36,23 +28,29 @@ export const tennisClubMembershipEvent = defineConfig({ }, forms: [ { + label: { + id: 'event.tennis-club-membership.action.declare.form.label', + defaultMessage: 'Tennis club membership application', + description: 'This is what this form is referred as in the system' + }, active: true, version: { - id: '1', + id: '1.0.0', label: { + id: 'event.tennis-club-membership.action.declare.form.version.1', defaultMessage: 'Version 1', - description: 'This is the first version of the form', - id: 'event.tennis-club-membership.action.declare.form.version.1' + description: 'This is the first version of the form' } }, - form: [ + pages: [ { + id: 'applicant', title: { id: 'event.tennis-club-membership.action.declare.form.section.who.title', defaultMessage: 'Who is applying for the membership?', description: 'This is the title of the section' }, - groups: [ + fields: [ { id: 'applicant.firstname', type: 'TEXT', @@ -86,12 +84,13 @@ export const tennisClubMembershipEvent = defineConfig({ ] }, { + id: 'recommender', title: { id: 'event.tennis-club-membership.action.declare.form.section.recommender.title', defaultMessage: 'Who is recommending the applicant?', description: 'This is the title of the section' }, - groups: [ + fields: [ { id: 'recommender.firstname', type: 'TEXT', diff --git a/packages/commons/src/types.ts b/packages/commons/src/types.ts index 5a943f4ce77..530eb9871e5 100644 --- a/packages/commons/src/types.ts +++ b/packages/commons/src/types.ts @@ -16,6 +16,3 @@ export * from './nominal' export * from './search' export type PartialBy = Omit & Partial> - -export type GetValues> = T[keyof T] -export type NonEmptyArray = [T, ...T[]] diff --git a/packages/events/src/index.test.ts b/packages/events/src/index.test.ts index c03357322c9..00f9a1dd227 100644 --- a/packages/events/src/index.test.ts +++ b/packages/events/src/index.test.ts @@ -15,6 +15,7 @@ import { resetServer, setupServer } from './storage/__mocks__/mongodb' +import { getUUID } from '@opencrvs/commons' const { createCallerFactory } = t @@ -40,7 +41,8 @@ const client = createClient() test('event can be created and fetched', async () => { const event = await client.event.create({ transactionId: '1', - event: { type: 'birth' } + type: 'birth', + fields: [] }) const fetchedEvent = await client.event.get(event.id) @@ -53,12 +55,14 @@ test('creating an event is an idempotent operation', async () => { await client.event.create({ transactionId: '1', - event: { type: 'birth' } + type: 'birth', + fields: [] }) await client.event.create({ transactionId: '1', - event: { type: 'birth' } + type: 'birth', + fields: [] }) expect(await db.collection('events').find().toArray()).toHaveLength(1) @@ -67,12 +71,15 @@ test('creating an event is an idempotent operation', async () => { test('stored events can be modified', async () => { const originalEvent = await client.event.create({ transactionId: '1', - event: { type: 'birth' } + type: 'birth', + fields: [] }) const event = await client.event.patch({ id: originalEvent.id, - type: 'death' + type: 'death', + fields: [], + transactionId: getUUID() }) expect(event.updatedAt).not.toBe(originalEvent.updatedAt) @@ -82,15 +89,14 @@ test('stored events can be modified', async () => { test('actions can be added to created events', async () => { const originalEvent = await client.event.create({ transactionId: '1', - event: { type: 'birth' } + type: 'birth', + fields: [] }) const event = await client.event.actions.declare({ eventId: originalEvent.id, transactionId: '2', - action: { - fields: [] - } + fields: [] }) expect(event.actions).toEqual([ diff --git a/packages/events/src/router.ts b/packages/events/src/router.ts index a333af857d5..92ef1a9e7fd 100644 --- a/packages/events/src/router.ts +++ b/packages/events/src/router.ts @@ -9,11 +9,6 @@ * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { - DeclareActionInput, - EventInput, - NotifyActionInput -} from '@opencrvs/commons' import { initTRPC } from '@trpc/server' import superjson from 'superjson' import { z } from 'zod' @@ -25,6 +20,11 @@ import { getEventById, patchEvent } from './service/events' +import { + ActionDeclareInput, + ActionNotifyInput, + EventInput +} from '@events/schema' const ContextSchema = z.object({ user: z.object({ @@ -48,20 +48,13 @@ export type AppRouter = typeof appRouter export const appRouter = router({ event: router({ - create: publicProcedure - .input( - z.object({ - transactionId: z.string(), - event: EventInput - }) + create: publicProcedure.input(EventInput).mutation(async (options) => { + return createEvent( + options.input, + options.ctx.user.id, + options.input.transactionId ) - .mutation(async (options) => { - return createEvent( - options.input.event, - options.ctx.user.id, - options.input.transactionId - ) - }), + }), patch: publicProcedure.input(EventInputWithId).mutation(async (options) => { return patchEvent(options.input) }), @@ -69,36 +62,18 @@ export const appRouter = router({ return getEventById(input) }), actions: router({ - notify: publicProcedure - .input( - z.object({ - eventId: z.string(), - transactionId: z.string(), - action: NotifyActionInput - }) - ) - .mutation(async (options) => { - return addAction(options.input.eventId, { - ...options.input.action, - type: 'NOTIFY', - createdBy: options.ctx.user.id - }) - }), - declare: publicProcedure - .input( - z.object({ - eventId: z.string(), - transactionId: z.string(), - action: DeclareActionInput - }) - ) - .mutation(async (options) => { - return addAction(options.input.eventId, { - ...options.input.action, - type: 'DECLARE', - createdBy: options.ctx.user.id - }) + notify: publicProcedure.input(ActionNotifyInput).mutation((options) => { + return addAction(options.input, { + eventId: options.input.eventId, + createdBy: options.ctx.user.id + }) + }), + declare: publicProcedure.input(ActionDeclareInput).mutation((options) => { + return addAction(options.input, { + eventId: options.input.eventId, + createdBy: options.ctx.user.id }) + }) }) }) }) diff --git a/packages/events/src/schema/ActionDocument.ts b/packages/events/src/schema/ActionDocument.ts new file mode 100644 index 00000000000..a3a447067b7 --- /dev/null +++ b/packages/events/src/schema/ActionDocument.ts @@ -0,0 +1,22 @@ +/* + * 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 { z } from 'zod' +import { ActionInputFields } from './ActionInput' +import { ActionType } from '@opencrvs/commons' + +export const ActionDocument = z.object({ + type: z.nativeEnum(ActionType), + fields: ActionInputFields, + createdBy: z.string().describe('The user who created the action'), + createdAt: z.date() +}) + +export type ActionDocument = z.infer diff --git a/packages/events/src/schema/ActionInput.ts b/packages/events/src/schema/ActionInput.ts new file mode 100644 index 00000000000..ae8bfb08cc5 --- /dev/null +++ b/packages/events/src/schema/ActionInput.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 { ActionType } from '@opencrvs/commons' +import { z } from 'zod' + +export const ActionInputFields = z.array( + z.object({ + id: z.string(), + value: z.union([ + z.string(), + z.number(), + z.array( + z.object({ + optionValues: z.array(z.string()), + type: z.string(), + data: z.string(), + fileSize: z.number() + }) + ) + ]) + }) +) + +const BaseActionInput = z.object({ + eventId: z.string(), + transactionId: z.string(), + fields: ActionInputFields +}) + +const ActionCreateInput = BaseActionInput.merge( + z.object({ + type: z.literal(ActionType.CREATE).default(ActionType.CREATE) + }) +) + +const ActionRegisterInput = BaseActionInput.merge( + z.object({ + type: z.literal(ActionType.REGISTER).default(ActionType.REGISTER), + identifiers: z.object({ + trackingId: z.string(), + registrationNumber: z.string() + }) + }) +) + +export const ActionNotifyInput = BaseActionInput.merge( + z.object({ + type: z.literal(ActionType.NOTIFY).default(ActionType.NOTIFY) + }) +) + +export const ActionDeclareInput = BaseActionInput.merge( + z.object({ + type: z.literal(ActionType.DECLARE).default(ActionType.DECLARE) + }) +) + +export const ActionInput = z.discriminatedUnion('type', [ + ActionCreateInput, + ActionRegisterInput, + ActionNotifyInput, + ActionDeclareInput +]) + +export type ActionInput = z.infer diff --git a/packages/commons/src/events/Form.ts b/packages/events/src/schema/EventDocument.ts similarity index 51% rename from packages/commons/src/events/Form.ts rename to packages/events/src/schema/EventDocument.ts index 1daff128aee..7ef7d7f122b 100644 --- a/packages/commons/src/events/Form.ts +++ b/packages/events/src/schema/EventDocument.ts @@ -8,27 +8,17 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ + import { z } from 'zod' -import { Field, Label } from './utils' +import { ActionDocument } from './ActionDocument' -export const FormGroupField = Field.extend({ +export const EventDocument = z.object({ id: z.string(), - type: z.string(), // @TODO: Get enums from somewhere, field types - required: z.boolean(), - searchable: z.boolean().optional(), - analytics: z.boolean().optional() -}) - -export const FormSection = z.object({ - title: Label, - groups: z.array(FormGroupField) + type: z.string(), + transactionId: z.string(), + createdAt: z.date(), + updatedAt: z.date(), + actions: z.array(ActionDocument) }) -export const Form = z.object({ - active: z.boolean(), - version: z.object({ - id: z.string(), - label: Label - }), - form: z.array(FormSection) -}) +export type EventDocument = z.infer diff --git a/packages/events/src/schema/EventInput.ts b/packages/events/src/schema/EventInput.ts new file mode 100644 index 00000000000..37c36967a9f --- /dev/null +++ b/packages/events/src/schema/EventInput.ts @@ -0,0 +1,36 @@ +/* + * 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 { z } from 'zod' + +export const EventInput = z.object({ + transactionId: z.string(), + type: z.string(), + fields: z.array( + z.object({ + id: z.string(), + value: z.union([ + z.string(), + z.number(), + z.array( + // @TODO: Check if we could make this stricter by leveraging the types in @opencrvs/commons + z.object({ + optionValues: z.array(z.string()), + type: z.string(), + data: z.string(), + fileSize: z.number() + }) + ) + ]) + }) + ) +}) + +export type EventInput = z.infer diff --git a/packages/client/src/v2-events/useEvents.ts b/packages/events/src/schema/index.ts similarity index 79% rename from packages/client/src/v2-events/useEvents.ts rename to packages/events/src/schema/index.ts index 9634905e3f9..b4b63150c23 100644 --- a/packages/client/src/v2-events/useEvents.ts +++ b/packages/events/src/schema/index.ts @@ -8,5 +8,7 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ - -export function noop() {} +export * from './EventInput' +export * from './EventDocument' +export * from './ActionInput' +export * from './ActionDocument' diff --git a/packages/events/src/service/events.ts b/packages/events/src/service/events.ts index 5bed77aeb73..482af32c1cf 100644 --- a/packages/events/src/service/events.ts +++ b/packages/events/src/service/events.ts @@ -10,23 +10,20 @@ */ import { getClient } from '@events/storage/mongodb' -import { - getUUID, - EventInput, - Event, - ActionInput, - ActionType -} from '@opencrvs/commons' +import { getUUID, ActionType } from '@opencrvs/commons' +import { EventInput, ActionInput } from '@events/schema' import { z } from 'zod' +import { EventDocument } from '@events/schema/EventDocument' -const EventWithTransactionId = Event.extend({ - transactionId: z.string() +export const EventInputWithId = EventInput.extend({ + id: z.string() }) +export type EventInputWithId = z.infer + async function getEventByTransactionId(transactionId: string) { const db = await getClient() - const collection = - db.collection>('events') + const collection = db.collection('events') const document = await collection.findOne({ transactionId }) @@ -41,7 +38,7 @@ class EventNotFoundError extends Error { export async function getEventById(id: string) { const db = await getClient() - const collection = db.collection('events') + const collection = db.collection('events') const event = await collection.findOne({ id: id }) if (!event) { throw new EventNotFoundError(id) @@ -53,15 +50,15 @@ export async function createEvent( eventInput: z.infer, createdBy: string, transactionId: string -): Promise { +) { const existingEvent = await getEventByTransactionId(transactionId) + if (existingEvent) { return existingEvent } const db = await getClient() - const collection = - db.collection>('events') + const collection = db.collection('events') const now = new Date() const id = getUUID() @@ -85,18 +82,22 @@ export async function createEvent( return getEventById(id) } -export async function addAction(eventId: string, action: ActionInput) { +export async function addAction( + input: ActionInput, + { eventId, createdBy }: { eventId: string; createdBy: string } +) { const db = await getClient() const now = new Date() - await db.collection('events').updateOne( + await db.collection('events').updateOne( { id: eventId }, { $push: { actions: { - ...action, + ...input, + createdBy, createdAt: now } } @@ -106,13 +107,7 @@ export async function addAction(eventId: string, action: ActionInput) { return getEventById(eventId) } -export const EventInputWithId = EventInput.extend({ - id: z.string() -}) - -export type EventInputWithId = z.infer - -export async function patchEvent(event: EventInputWithId): Promise { +export async function patchEvent(event: EventInputWithId) { const existingEvent = await getEventById(event.id) if (!existingEvent) { @@ -120,7 +115,7 @@ export async function patchEvent(event: EventInputWithId): Promise { } const db = await getClient() - const collection = db.collection('events') + const collection = db.collection('events') const now = new Date() diff --git a/packages/gateway/src/v2-events/events/root-resolvers.ts b/packages/gateway/src/v2-events/events/root-resolvers.ts index b1b14a7b9e5..0fb59fcf989 100644 --- a/packages/gateway/src/v2-events/events/root-resolvers.ts +++ b/packages/gateway/src/v2-events/events/root-resolvers.ts @@ -39,7 +39,8 @@ export const resolvers: GQLResolver = { async createEvent(_, { event }, { headers }) { const createdEvent = await trpc.event.create.mutate( { - event: event, + type: event.type, + fields: [], transactionId: uuid.v4() }, { context: { headers } } @@ -50,8 +51,9 @@ export const resolvers: GQLResolver = { return trpc.event.actions.notify.mutate( { eventId: eventId, - transactionId: uuid.v4(), - action: input + type: 'NOTIFY', + fields: input.fields, + transactionId: uuid.v4() }, { context: { headers } } ) @@ -60,8 +62,9 @@ export const resolvers: GQLResolver = { return trpc.event.actions.declare.mutate( { eventId: eventId, - transactionId: uuid.v4(), - action: input + type: 'DECLARE', + fields: input.fields, + transactionId: uuid.v4() }, { context: { headers } } ) diff --git a/packages/gateway/src/v2-events/events/type-resolvers.ts b/packages/gateway/src/v2-events/events/type-resolvers.ts index 3082f072363..e4eeb7289f5 100644 --- a/packages/gateway/src/v2-events/events/type-resolvers.ts +++ b/packages/gateway/src/v2-events/events/type-resolvers.ts @@ -9,11 +9,12 @@ * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ import { GQLResolver } from '@gateway/graphql/schema' -import { Action, ActionType } from '@opencrvs/commons' +import { ActionType } from '@opencrvs/commons' +import { ActionDocument } from '@opencrvs/events/src/schema' export const eventResolvers: GQLResolver = { Action: { - __resolveType: (obj: Action) => { + __resolveType: (obj: ActionDocument) => { if (obj.type === ActionType.NOTIFY) { return 'NotifyAction' }