Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Events v2: Skeleton GraphQL resolvers for basic operations #8033

Merged
merged 23 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1b7d031
add some basic operations and unit tests for interfacing with events
rikukissa Nov 18, 2024
9f83721
Merge branch 'develop' of github.com:opencrvs/opencrvs-core into ocrv…
rikukissa Nov 19, 2024
f259bc3
feat(events): create first draft of types
makelicious Nov 18, 2024
d2a03f8
feat(events): unify and clean up types
makelicious Nov 19, 2024
5d1351f
add basic structure for graphql endpoints
rikukissa Nov 20, 2024
a05906d
add tennic club membership example data
rikukissa Nov 20, 2024
68f8adb
Merge branch 'feat/event-types' of github.com:opencrvs/opencrvs-core …
rikukissa Nov 20, 2024
a9f98a9
implement initial graphql resolvers for basic event operations
rikukissa Nov 20, 2024
7914c63
implement authorisation and first GraphQL -> MongoDB request chain
rikukissa Nov 20, 2024
81f470f
add license headers to all files
rikukissa Nov 21, 2024
d8addb9
Merge branch 'feat/event-types' of github.com:opencrvs/opencrvs-core …
rikukissa Nov 21, 2024
55a2d63
Merge branch 'feat/event-types' of github.com:opencrvs/opencrvs-core …
rikukissa Nov 21, 2024
1f90403
fix tests
rikukissa Nov 21, 2024
269970d
add events package to gateway build
rikukissa Nov 21, 2024
e8add12
fix the test pipeline to not remove events package if its used in pac…
rikukissa Nov 21, 2024
307a04f
cleanup
rikukissa Nov 21, 2024
d31792f
Merge branch 'feat/event-types' of github.com:opencrvs/opencrvs-core …
rikukissa Nov 21, 2024
a5e2c8d
fix docker build dependencies
rikukissa Nov 21, 2024
e04fef8
trick knip not to complain about duplicate exports
rikukissa Nov 21, 2024
d031ed5
remove unused export
rikukissa Nov 22, 2024
6d7b57b
update docker compose config
rikukissa Nov 22, 2024
c3c97ac
Merge branch 'feat/event-types' into ocrvs-7824-b
rikukissa Nov 25, 2024
c604312
Merge branch 'develop' into ocrvs-7824-b
rikukissa Nov 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/commons/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@
"jwt-decode": "^3.0.0",
"lodash": "^4.17.10",
"node-fetch": "^2.6.7",
"pino": "^7.0.0",
"pkg-up": "^3.1.0",
"typescript": "5.6.3",
"uuid": "^9.0.0",
"pino": "^7.0.0"
"zod": "^3.23.8"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.5.0",
Expand Down
79 changes: 79 additions & 0 deletions packages/commons/src/events/Action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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<typeof ActionType>

/**
* 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<ActionType>),
label: Label,
forms: z.array(Form)
})

export const ActionInputBase = z.object({
type: z.enum(actionTypes as NonEmptyArray<ActionType>),
createdAt: z.date(),
createdBy: z.string(),
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 ActionInput = z.union([
ActionInputBase.extend({
type: z.enum([ActionType.CREATE])
}),
ActionInputBase.extend({
type: z.enum([ActionType.REGISTER]),
identifiers: z.object({
trackingId: z.string(),
registrationNumber: z.string()
})
})
])

export type ActionInput = z.infer<typeof ActionInput>

export const Action = ActionInput.and(
z.object({
createdAt: z.date(),
createdBy: z.string()
})
)

export type Action = z.infer<typeof Action>
52 changes: 52 additions & 0 deletions packages/commons/src/events/Event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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()
Copy link
Member Author

@rikukissa rikukissa Nov 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only field the user can really send at the point of Event creation is type if we take on the approach of only submitting actions like registerEvent(eventId, registrationData) from the client

})
export type EventInput = z.infer<typeof EventInput>

/**
* 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<typeof EventConfig>

/**
* 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<typeof EventIndex>

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<typeof Event>
24 changes: 24 additions & 0 deletions packages/commons/src/events/Form.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { z } from 'zod'
import { Field, Label } from './utils'

export const FormGroupField = Field.extend({
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)
})

export const Form = z.object({
active: z.boolean(),
version: z.object({
id: z.string(),
label: Label
}),
form: z.array(FormSection)
})
122 changes: 122 additions & 0 deletions packages/commons/src/events/fixtures/tennis-club-membership-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { type EventConfig } from '../Event'

export const tennisClubMembershipEvent: EventConfig = {
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',
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: [
{
active: true,
version: {
id: '1',
label: {
defaultMessage: 'Version 1',
description: 'This is the first version of the form',
id: 'event.tennis-club-membership.action.declare.form.version.1'
}
},
form: [
{
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: [
{
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'
}
}
]
},
{
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: [
{
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'
}
}
]
}
]
}
]
}
]
}
3 changes: 3 additions & 0 deletions packages/commons/src/events/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './Action'
export * from './Event'
export * from './utils'
43 changes: 43 additions & 0 deletions packages/commons/src/events/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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<typeof SystemRoleType>
export const systemRoleTypes = Object.values(SystemRoleType)
1 change: 1 addition & 0 deletions packages/commons/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export * from './documents'
export * from './http'
export * from './logger'
export * from './search'
export * from './events'
3 changes: 3 additions & 0 deletions packages/commons/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ export * from './nominal'
export * from './search'

export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>

export type GetValues<T extends Record<string, string>> = T[keyof T]
export type NonEmptyArray<T> = [T, ...T[]]
3 changes: 1 addition & 2 deletions packages/events/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ import { initTRPC } from '@trpc/server'
import superjson from 'superjson'
import { z } from 'zod'
import {
ActionInput,
addAction,
createEvent,
EventInput,
EventInputWithId,
getEventById,
patchEvent
} from './service/events'
import { ActionInput, EventInput } from '@opencrvs/commons'

export const t = initTRPC.create({
transformer: superjson
Expand Down
Loading
Loading