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

feat: updateField GraphQL mutation #8291

Merged
merged 24 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ac31d24
feat: notify CC about actions
naftis Jan 6, 2025
57b76be
refactor: use 'getValidRecordById' as it has the same statuses
naftis Jan 7, 2025
5bbcebc
feat: workflow endpoint allowing editing field ids
naftis Jan 7, 2025
91f09e4
fix(tests): mock country config event notification hook
naftis Jan 7, 2025
bbd398b
Merge branch 'develop' of https://github.com/opencrvs/opencrvs-core i…
naftis Jan 22, 2025
a41d2e7
feat: initial graphql endpoint for update field
naftis Jan 22, 2025
57e0cfa
chore: test and fix errors, return boolean
naftis Jan 22, 2025
fd33fef
feat: update the action notification according to events v2
naftis Jan 22, 2025
0109607
fix: update test to conform events v2 notification format
naftis Jan 22, 2025
3f35252
Merge branch 'develop' into notify-cc-about-actions
naftis Jan 23, 2025
c360d6f
Merge branch 'develop' into notify-cc-about-actions
naftis Jan 23, 2025
7bd2ce0
feat: token exchange on notify
naftis Jan 23, 2025
1ee22a7
Merge branch 'notify-cc-about-actions' of https://github.com/opencrvs…
naftis Jan 23, 2025
6656b72
update: test comment
naftis Jan 24, 2025
59f1b1d
chore(changelog): add update field
naftis Jan 24, 2025
a66600a
Merge branch 'notify-cc-about-actions' of https://github.com/opencrvs…
naftis Jan 24, 2025
10477f8
Merge branch 'develop' of https://github.com/opencrvs/opencrvs-core i…
naftis Jan 24, 2025
5166559
fix: rename config to options
naftis Jan 24, 2025
13cba43
refactor: rename token-exchange away from 'handler' as it's not a htt…
naftis Jan 24, 2025
504acf5
fix: test not mocking the token exchange
naftis Jan 24, 2025
93622a0
chore: remove unused function
naftis Jan 24, 2025
cfa93ed
Merge branch 'develop' into notify-cc-about-actions
naftis Jan 24, 2025
a1a5764
fix: align the error handling with how events service notifies
naftis Jan 24, 2025
3be2948
Merge branch 'notify-cc-about-actions' of https://github.com/opencrvs…
naftis Jan 24, 2025
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
- Introduced a new customisable UI component: Banner [#8276](https://github.com/opencrvs/opencrvs-core/issues/8276)
- Auth now allows exchanging user's token for a new record-specific token [#7728](https://github.com/opencrvs/opencrvs-core/issues/7728)
- A new GraphQL mutation `upsertRegistrationIdentifier` is added to allow updating the patient identifiers of a registration record such as NID [#8034](https://github.com/opencrvs/opencrvs-core/pull/8034)
- A new GraphQL mutation `updateField` is added to allow updating any field in a record [#8291](https://github.com/opencrvs/opencrvs-core/pull/8291)
- Updated GraphQL mutation `confirmRegistration` to allow adding a `comment` for record audit [#8197](https://github.com/opencrvs/opencrvs-core/pull/8197)

### Improvements
Expand Down
12 changes: 11 additions & 1 deletion packages/gateway/src/features/registration/root-resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ import {
markNotADuplicate,
rejectRegistration,
confirmRegistration,
upsertRegistrationIdentifier
upsertRegistrationIdentifier,
updateField
} from '@gateway/workflow/index'
import { getRecordById } from '@gateway/records'
import { SCOPES } from '@opencrvs/commons/authentication'
Expand Down Expand Up @@ -674,6 +675,15 @@ export const resolvers: GQLResolver = {
} catch (error) {
throw new Error(`Failed to confirm registration: ${error.message}`)
}
},
async updateField(_, { id, details }, { headers: authHeader }) {
if (!hasRecordAccess(authHeader, id)) {
throw new Error('User does not have access to the record')
}

await updateField(id, authHeader, details)

return true
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions packages/gateway/src/features/registration/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,12 @@ input RejectRegistrationInput {
comment: String
}

input UpdateFieldInput {
fieldId: String!
valueString: String
valueBoolean: Boolean
}

type Mutation {
# Generic correction handlers for all event types
# Applying a correction request is made on a event level as payload is dependant on event type
Expand Down Expand Up @@ -662,4 +668,5 @@ type Mutation {
identifierType: String!
identifierValue: String!
): ID!
updateField(id: ID!, details: UpdateFieldInput!): Boolean!
}
21 changes: 21 additions & 0 deletions packages/gateway/src/graphql/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export interface GQLMutation {
confirmRegistration: string
rejectRegistration: string
upsertRegistrationIdentifier: string
updateField: boolean
createOrUpdateUser: GQLUser
activateUser?: string
changePassword?: string
Expand Down Expand Up @@ -574,6 +575,12 @@ export interface GQLRejectRegistrationInput {
comment?: string
}

export interface GQLUpdateFieldInput {
fieldId: string
valueString?: string
valueBoolean?: boolean
}

export interface GQLUserInput {
id?: string
name: Array<GQLHumanNameInput>
Expand Down Expand Up @@ -2376,6 +2383,7 @@ export interface GQLMutationTypeResolver<TParent = any> {
confirmRegistration?: MutationToConfirmRegistrationResolver<TParent>
rejectRegistration?: MutationToRejectRegistrationResolver<TParent>
upsertRegistrationIdentifier?: MutationToUpsertRegistrationIdentifierResolver<TParent>
updateField?: MutationToUpdateFieldResolver<TParent>
createOrUpdateUser?: MutationToCreateOrUpdateUserResolver<TParent>
activateUser?: MutationToActivateUserResolver<TParent>
changePassword?: MutationToChangePasswordResolver<TParent>
Expand Down Expand Up @@ -2956,6 +2964,19 @@ export interface MutationToUpsertRegistrationIdentifierResolver<
): TResult
}

export interface MutationToUpdateFieldArgs {
id: string
details: GQLUpdateFieldInput
}
export interface MutationToUpdateFieldResolver<TParent = any, TResult = any> {
(
parent: TParent,
args: MutationToUpdateFieldArgs,
context: Context,
info: GraphQLResolveInfo
): TResult
}

export interface MutationToCreateOrUpdateUserArgs {
user: GQLUserInput
}
Expand Down
7 changes: 7 additions & 0 deletions packages/gateway/src/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ type Mutation {
identifierType: String!
identifierValue: String!
): ID!
updateField(id: ID!, details: UpdateFieldInput!): Boolean!
createOrUpdateUser(user: UserInput!): User!
activateUser(
userId: String!
Expand Down Expand Up @@ -691,6 +692,12 @@ input RejectRegistrationInput {
comment: String
}

input UpdateFieldInput {
fieldId: String!
valueString: String
valueBoolean: Boolean
}

input UserInput {
id: ID
name: [HumanNameInput!]!
Expand Down
21 changes: 21 additions & 0 deletions packages/gateway/src/workflow/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,27 @@ export async function upsertRegistrationIdentifier(
return taskEntry
}

type UpdateFieldInput = {
fieldId: string
valueString?: string
valueBoolean?: boolean
}

export async function updateField(
id: string,
authHeader: IAuthHeader,
details: UpdateFieldInput
) {
const res = await createRequest<ValidRecord>(
'POST',
`/records/${id}/update-field`,
authHeader,
details
)

return res
}

export async function archiveRegistration(
id: string,
authHeader: IAuthHeader,
Expand Down
1 change: 1 addition & 0 deletions packages/workflow/src/__mocks__/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const USER_MANAGEMENT_URL = 'http://localhost:3030/'
export const SEARCH_URL = 'http://localhost:9090/'
export const METRICS_URL = 'http://localhost:1050/'
export const DOCUMENTS_URL = 'http://localhost:9050'
export const AUTH_URL = 'http://localhost:4040/'
export const NOTIFICATION_SERVICE_URL = 'http://localhost:2020/'
export const APPLICATION_CONFIG_URL = 'http://localhost:2021/'
export const COUNTRY_CONFIG_URL = 'http://localhost:3040'
Expand Down
12 changes: 11 additions & 1 deletion packages/workflow/src/config/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { eventNotificationHandler } from '@workflow/records/handler/eventNotific
import * as Hapi from '@hapi/hapi'
import { SCOPES } from '@opencrvs/commons/authentication'
import { upsertRegistrationHandler } from '@workflow/records/handler/upsert-identifiers'
import { updateField } from '@workflow/records/handler/update-field'

export const getRoutes = () => {
const routes: Hapi.ServerRoute[] = [
Expand Down Expand Up @@ -175,7 +176,16 @@ export const getRoutes = () => {
approveCorrectionRoute,
rejectCorrectionRoute,
requestCorrectionRoute,
makeCorrectionRoute
makeCorrectionRoute,
{
method: 'POST',
path: '/records/{id}/update-field',
handler: updateField,
options: {
tags: ['api'],
description: 'Update a single field in a registration'
}
}
]

return routes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

import { UUID } from '@opencrvs/commons'
import {
Bundle,
Composition,
Patient,
Practitioner,
Expand All @@ -25,36 +24,10 @@ import {
SupportedPatientIdentifierCode,
ValidRecord
} from '@opencrvs/commons/types'
import { COUNTRY_CONFIG_URL } from '@workflow/constants'
import { OPENCRVS_SPECIFICATION_URL } from '@workflow/features/registration/fhir/constants'
import { getSectionEntryBySectionCode } from '@workflow/features/registration/fhir/fhir-template'
import { getPractitionerRef } from '@workflow/features/user/utils'
import { isEqual } from 'lodash'
import fetch from 'node-fetch'

export async function invokeRegistrationValidation(
bundle: Saved<Bundle>,
headers: Record<string, string>
): Promise<Bundle> {
const res = await fetch(
new URL('event-registration', COUNTRY_CONFIG_URL).toString(),
{
method: 'POST',
body: JSON.stringify(bundle),
headers: {
'Content-Type': 'application/json',
...headers
}
}
)
if (!res.ok) {
const errorText = await res.text()
throw new Error(
`Error calling country configuration event-registration [${res.statusText} ${res.status}]: ${errorText}`
)
}
return bundle
}

export function setupLastRegOffice<T extends Task>(
taskResource: T,
Expand Down
13 changes: 0 additions & 13 deletions packages/workflow/src/features/registration/fhir/fhir-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,19 +312,6 @@ export function generateEmptyBundle(): Bundle {
}
}

export async function fetchExistingRegStatusCode(taskId: string | undefined) {
const existingTaskResource: Task = await getFromFhir(`/Task/${taskId}`)
const existingRegStatusCode =
existingTaskResource &&
existingTaskResource.businessStatus &&
existingTaskResource.businessStatus.coding &&
existingTaskResource.businessStatus.coding.find((code) => {
return code.system === `${OPENCRVS_SPECIFICATION_URL}reg-status`
})

return existingRegStatusCode
}

function mergeFhirIdentifiers(
currentIdentifiers: fhir3.Identifier[],
newIdentifiers: fhir3.Identifier[]
Expand Down
31 changes: 31 additions & 0 deletions packages/workflow/src/records/handler/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,29 @@ describe('Create record endpoint', () => {
}
)

// Notification endpoint mockcall
mswServer.use(
rest.get('http://localhost:3040/record-notification', (_, res, ctx) => {
return res(ctx.json({}))
})
)

// Token exchange mock call
mswServer.use(
// The actual more verbose query below, but for simplicity we can keep simpler one unless this causes issues:

// ?grant_type=urn:opencrvs:oauth:grant-type:token-exchange&subject_token=${token}&subject_token_type=urn:ietf:params:oauth:token-type:access_token
// &requested_token_type=urn:opencrvs:oauth:token-type:single_record_token&record_id=${recordId}

rest.post(`http://localhost:4040/token`, (_, res, ctx) => {
return res(
ctx.json({
access_token: 'some-token'
})
)
})
)

// used for checking already created composition with
// the same draftId
mswServer.use(
Expand Down Expand Up @@ -157,6 +180,14 @@ describe('Create record endpoint', () => {
})
)

// mock country config event action hook returning a basic 200
mswServer.use(
rest.post(
'http://localhost:3040/events/BIRTH/actions/sent-notification-for-review',
(_, res, ctx) => res(ctx.status(200))
)
)

const res = await server.server.inject({
method: 'POST',
url: '/create-record',
Expand Down
22 changes: 22 additions & 0 deletions packages/workflow/src/records/handler/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ import {
toWaitingForExternalValidationState
} from '@workflow/records/state-transitions'
import { logger, UUID } from '@opencrvs/commons'
import { notifyForAction } from '@workflow/utils/country-config-api'
import { getRecordSpecificToken } from '@workflow/records/token-exchange'

const requestSchema = z.object({
event: z.custom<EVENT_TYPE>(),
Expand Down Expand Up @@ -348,6 +350,8 @@ export default async function createRecordHandler(
/*
* We need to initiate registration for a
* record in waiting validation state
*
* `initiateRegistration` notifies country configuration about the event which then either confirms or rejects the record.
*/
if (isWaitingExternalValidation(record)) {
const rejectedOrWaitingValidationRecord = await initiateRegistration(
Expand All @@ -360,6 +364,24 @@ export default async function createRecordHandler(
await indexBundle(rejectedOrWaitingValidationRecord, token)
await auditEvent('sent-for-updates', record, token)
}
} else {
/*
* Notify country configuration about the event so that countries can hook into actions like "sent-for-approval"
*/
const recordSpecificToken = await getRecordSpecificToken(
token,
request.headers,
getComposition(record).id
)
await notifyForAction({
event,
action: eventAction,
record,
headers: {
...request.headers,
authorization: `Bearer ${recordSpecificToken.access_token}`
}
})
naftis marked this conversation as resolved.
Show resolved Hide resolved
}

return {
Expand Down
Loading
Loading