Skip to content

Commit

Permalink
Merge pull request #3 from pughlab/form-generator-schema-refactor
Browse files Browse the repository at this point in the history
Form generator schema refactor
  • Loading branch information
suluxan authored Jan 31, 2024
2 parents ce1f7a3 + c19da6c commit 307b12b
Show file tree
Hide file tree
Showing 20 changed files with 1,586 additions and 1,658 deletions.
32 changes: 31 additions & 1 deletion api/src/graphql/resolvers/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,36 @@ export const resolvers = {
throw new ApolloError('value');
}
}
})
}),
Mutation: {
findOrCreatePatient: async (_obj, args, { driver }) => {
const { patient_id, program_id, study } = args
const session = driver.session()

try {
let command = study
? "MATCH (p:Patient { patient_id: $patient_id, program_id: $program_id, study: $study }) RETURN p"
: "MATCH (p:Patient { patient_id: $patient_id, program_id: $program_id }) RETURN p"
const result = await session.run(command, study ? { patient_id, program_id, study } : { patient_id, program_id })

if (result.records.length > 0 ) {
return result.records[0].get('p').properties
}

command = study
? "CREATE (p:Patient { patient_id: $patient_id, program_id: $program_id, study: $study }) return p"
: "CREATE (p:Patient { patient_id: $patient_id, program_id: $program_id }) return p"
const createPatient = await session.run(
command,
study ? { patient_id, program_id, study } : { patient_id, program_id }
)

return createPatient.records[0].get(0).properties
} catch (error) {
throw new Error(`Could not find or create Patient. Caused by ${error}`)
} finally {
session.close()
}
}
}
};
26 changes: 13 additions & 13 deletions api/src/graphql/resolvers/keycloak.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,31 @@ export const resolvers = {
throw new ApolloError('mutation.me error')
}
},
assignKeycloakUserToSubmitter: async (obj, { submitterID }, { driver, kauth }, resolveInfo) => {
assignKeycloakUserToSubmission: async (_obj, { submissionID }, { driver, kauth }) => {
const session = driver.session()
try {
const { sub: keycloakUserID, email, name, ...kcAuth } = kauth.accessToken.content
const { sub: keycloakUserID, email, name } = kauth.accessToken.content
const keycloakUser = { keycloakUserID, email, name }

const session = driver.session()
const existingUser = await session.run(
'MATCH (a:KeycloakUser {keycloakUserID: $keycloakUserID}) RETURN a',
{ keycloakUserID }
)
// console.log('match result', existingUser)
if (existingUser.records.length) {
const createConnectionToSubmitter = await session.run(
'MATCH (s:Submitter {uuid: $submitterID}), (k:KeycloakUser {keycloakUserID: $keycloakUserID}) MERGE (s)-[:SUBMITTED_BY]->(k) RETURN s, k',
{ submitterID, keycloakUserID }

if (existingUser.records.length > 0) {
await session.run(
"MATCH (s:Submission {submission_id: $submissionID}), (k:KeycloakUser {keycloakUserID: $keycloakUserID}) MERGE (s)-[:SUBMITTED_BY]->(k) RETURN s, k",
{ submissionID, keycloakUserID }
)

return keycloakUser
} else {
// console.log('existing user props', existingUser.records[0].get(0).properties)
throw new ApolloError('mutation.missing user error')
throw new ApolloError('Error: The user does not exist')
}

} catch (error) {
throw new ApolloError('mutation.me error')
throw new Error(`Could not assign the user data to the submission. Caused by: ${error}`)
} finally {
session.close()
}
}
},
Expand Down
92 changes: 54 additions & 38 deletions api/src/graphql/typeDefinitions/form.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@
value : FormValue!
}

type Submitter{
"""
"""
uuid : String!
form : String!
formPrimaryIdentifierKeys : Parser
fields : [FieldKeyValuePair!]! @relationship(type : "HAS_FIELD_VALUE", direction : OUT)
formReferenceKeys : [Submitter!]! @relationship(type : "REFERENCE_PRIMARY_KEY", direction : OUT)
connectedFormsReferencingSubmitter : [Submitter!]! @relationship(type : "REFERENCE_PRIMARY_KEY", direction : IN)

submittedByKeycloakUser : KeycloakUser! @relationship(type : "SUBMITTED_BY", direction : OUT)
type Patient {
patient_id: String!
program_id: String!
study: String
submissions: [Submission] @relationship(type: "DATA_FOR", direction: IN)
}

type Submission {
submission_id: ID! @id
form_id: String!
patient: Patient! @relationship(type: "DATA_FOR", direction: OUT)
fields: [FieldKeyValuePair!]! @relationship(type: "HAS_VALUE", direction: OUT)
submittedBy: KeycloakUser! @relationship(type: "SUBMITTED_BY", direction: OUT)
}

type FormDraft {
Expand All @@ -41,6 +42,7 @@
patient_id: String!
secondary_ids: String # Secondary IDs are fields that are unique to a form, but are not part of the patient's data
data: String
createdBy: KeycloakUser @relationship(type: "CREATED_BY", direction: OUT)
}

type Form {
Expand All @@ -50,14 +52,12 @@
form_id : String
form_name : String
form_relationship_cardinality : FormValue
has_next_question : Field @relationship(type : "HAS_NEXT_QUESTION", direction : OUT)
next_form : [Form!]! @relationship(type : "NEXT_FORM", direction : OUT)
primary_key : [Field!]! @relationship(type : "HAS_PRIMARY_KEY", direction : OUT)
foreign_key : [Field!]! @relationship(type : "HAS_FOREIGN_KEY", properties : "reference", direction : OUT)
identifier : [Field!]! @cypher(statement: """ MATCH (f:Form)-[:HAS_PRIMARY_KEY]->(pk)
WHERE NOT ()-[:NEXT_FORM]->(f)
RETURN pk
""" )
next_form: [Form!]! @relationship(type : "NEXT_FORM", direction : OUT, properties: "Studies")
display_name: Parser
fields: [Field!] @relationship(type: "BELONGS_TO", direction: IN, properties: "IsID")
has_next_question: [Field] @relationship(type : "HAS_NEXT_QUESTION", direction : OUT)
studies: [String]
branch_fields: [String]
}

type Field {
Expand All @@ -72,36 +72,52 @@
regex : String
value : FormValue
set : SampleSet
required : Boolean
required : FormValue
component : String
conditionals : Parser
placeholder : String
filter : Parser
has_next_question : Field @relationship(type : "HAS_NEXT_QUESTION", direction : OUT)
foreign_key_to : [Form!]! @relationship(type : "HAS_FOREIGN_KEY", direction : IN)
primaryFormIdentifiers : Form @relationship(type : "HAS_PRIMARY_KEY", direction : IN)
info: String
display_name: Parser
studies: [String]
has_next_question: Field @relationship(type : "HAS_NEXT_QUESTION", direction : OUT)
belongs_to(isID: Boolean): Form! @relationship(type: "BELONGS_TO", direction: OUT)
}

interface reference @relationshipProperties {
"""
reference relation holds extra information to inter connection
Override: switches any metadata value within the field its connected to
[EX: a reference key might not be required witin the current form but is set to required]
relationship_cardinality : holds the how many time the referencekey can be allocated to another submitter
EX : Radiation has a relationship cardinality of 1 towards Treatment meaning that per each Treatment generated
we can only refernce the primary identifiers once
"""
relationship_cardinality : FormValue
override : Parser
interface IsID @relationshipProperties {
isID: Boolean!
override: Parser
}

interface Studies @relationshipProperties {
studies: [String]
}


type Query {

PopulateForm(id : String!) : [Field]
GetFormFields(id: String!, study: String) : [Field]
@cypher( statement: """
MATCH (:Form {form_id : $id})-[:HAS_NEXT_QUESTION*]->(f:Field) RETURN f
MATCH (:Form {form_id : $id})-[:HAS_NEXT_QUESTION*]->(f:Field)
WHERE single(x in f.studies WHERE x = $study)
RETURN f
""")
GetRootForm(study: String!): Form @cypher(statement: """
MATCH (f:Form)
WHERE NOT ()-[:NEXT_FORM]->(f)
AND single(x IN f.studies WHERE x = $study)
RETURN f
""")
GetPatientID: [Field!]! @cypher(statement:"""
MATCH (f:Form)
WHERE NOT ()-[:NEXT_FORM]->(f)
WITH f
MATCH (n:Field)-[r:BELONGS_TO]->(f)
WHERE r.isID = true
RETURN n
""")
}

type Mutation {
findOrCreatePatient(patient_id: String!, program_id: String!, study: String): Patient
}
3 changes: 2 additions & 1 deletion api/src/graphql/typeDefinitions/keycloak.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ type KeycloakUser @exclude {

type Mutation {
me: KeycloakUser @auth
assignKeycloakUserToSubmitter(submitterID: ID!): KeycloakUser @auth
assignKeycloakUserToSubmission(submissionID: ID!): KeycloakUser @auth
assignKeycloakUserToDraft(draftID: ID!): KeycloakUser @auth
}
27 changes: 19 additions & 8 deletions ui/src/components/Portal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, createContext } from "react";
import { Sticky, Menu, Divider, Label, Popup, Header, Icon } from 'semantic-ui-react'
import useKeycloakMeMutation from '../hooks/useKeycloakMeMutation'
import dayjs from 'dayjs'
Expand All @@ -13,6 +13,10 @@ import PortalNavBarIntro, {HOME_MENU_ELEMENT_ID} from './intros/PortalNavBarIntr
import FormFactory from './layout/FormFactory';
import PatientSearchForm from './layout/PatientSearchForm'

export const PatientIdentifierContext = createContext({})
export const ActiveSubmissionContext = createContext({})
export const PatientFoundContext = createContext({})

const DocsLink = () => {
return (
<>
Expand All @@ -36,7 +40,9 @@ export default function Portal () {
const {navigate, location, isActivePathElement} = useRouter()
const [meMutationState] = useKeycloakMeMutation()

const [patientIdentifier, setPatientIdentifier] = useState({submitter_donor_id: '', program_id: ''})
const [patientIdentifier, setPatientIdentifier] = useState({submitter_donor_id: '', program_id: '', study: ''})
const [activeSubmission, setActiveSubmission] = useState({})
const [patientFound, setPatientFound] = useState(false)

const routes = [
{path: '/', icon: 'info circle', introID: HOME_MENU_ELEMENT_ID},
Expand Down Expand Up @@ -71,12 +77,17 @@ export default function Portal () {
</Sticky>

<Divider horizontal />
<div style={{padding: '1em'}}>
<PatientSearchForm patientIdentifier={patientIdentifier} setPatientIdentifier={setPatientIdentifier}/>
<Divider horizontal />

<FormFactory patientIdentifier={patientIdentifier} setPatientIdentifier={setPatientIdentifier}/>
</div>
<PatientIdentifierContext.Provider value={{patientIdentifier, setPatientIdentifier }}>
<ActiveSubmissionContext.Provider value={{activeSubmission, setActiveSubmission}}>
<PatientFoundContext.Provider value={{patientFound, setPatientFound}}>
<div style={{padding: '1em'}}>
<PatientSearchForm />
<Divider horizontal />
<FormFactory />
</div>
</PatientFoundContext.Provider>
</ActiveSubmissionContext.Provider>
</PatientIdentifierContext.Provider>
</>
)
}
Loading

0 comments on commit 307b12b

Please sign in to comment.