diff --git a/data-serving/data-service/api/openapi.yaml b/data-serving/data-service/api/openapi.yaml index 9354fe81..27af56ac 100644 --- a/data-serving/data-service/api/openapi.yaml +++ b/data-serving/data-service/api/openapi.yaml @@ -684,6 +684,8 @@ components: - suspected - discarded - omit_error + comment: + type: string caseReference: type: object properties: diff --git a/data-serving/data-service/src/controllers/case.ts b/data-serving/data-service/src/controllers/case.ts index aff67c59..43165884 100644 --- a/data-serving/data-service/src/controllers/case.ts +++ b/data-serving/data-service/src/controllers/case.ts @@ -316,6 +316,7 @@ export class CasesController { while (doc != null) { delete doc.caseReference.sourceEntryId; const caseDTO = await dtoFromCase(doc); + delete caseDTO.comment; const stringifiedCase = stringify([caseDTO], { header: false, columns: this.csvHeaders, diff --git a/data-serving/data-service/src/model/day0-case.ts b/data-serving/data-service/src/model/day0-case.ts index 8f204cfd..0b995120 100644 --- a/data-serving/data-service/src/model/day0-case.ts +++ b/data-serving/data-service/src/model/day0-case.ts @@ -80,6 +80,7 @@ export const caseSchema = new mongoose.Schema( enum: CaseStatus, required: true, }, + comment: String, pathogen: { type: String, required: true, @@ -149,6 +150,7 @@ caseSchema.methods.equalsJSON = function (jsonCase: any): boolean { return ( _.isEqual(thisJson.caseStatus, other.caseStatus) && + _.isEqual(thisJson.comment, other.comment) && _.isEqual(thisJson.demographics, other.demographics) && _.isEqual(thisJson.events, other.events) && _.isEqual(thisJson.genomeSequences, other.genomeSequences) && @@ -179,6 +181,7 @@ export interface ISource { export type ICase = { caseStatus: CaseStatus; + comment?: string; pathogen: string; symptoms: string; dateLastModified: string; diff --git a/data-serving/data-service/src/model/fields.json b/data-serving/data-service/src/model/fields.json index b15f4507..04a1d690 100644 --- a/data-serving/data-service/src/model/fields.json +++ b/data-serving/data-service/src/model/fields.json @@ -1,6 +1,7 @@ [ "_id", "caseStatus", + "comment", "pathogen", "caseReference.additionalSources", "caseReference.sourceEntryId", diff --git a/verification/curator-service/api/openapi/openapi.yaml b/verification/curator-service/api/openapi/openapi.yaml index d43a5eaf..145e9ae0 100644 --- a/verification/curator-service/api/openapi/openapi.yaml +++ b/verification/curator-service/api/openapi/openapi.yaml @@ -1423,6 +1423,8 @@ components: - suspected - discarded - omit_error + comment: + type: string caseReference: type: object properties: diff --git a/verification/curator-service/ui/cypress/e2e/components/Curator.spec.ts b/verification/curator-service/ui/cypress/e2e/components/Curator.spec.ts index 860d0720..2adf9adc 100644 --- a/verification/curator-service/ui/cypress/e2e/components/Curator.spec.ts +++ b/verification/curator-service/ui/cypress/e2e/components/Curator.spec.ts @@ -56,6 +56,7 @@ describe('Curator', function () { // GENERAL cy.get('div[data-testid="caseStatus"]').click(); cy.get('li[data-value="confirmed"').click(); + cy.get('div[data-testid="comment"]').type('This case should be consulted with Supervisor.'); // DATA SOURCE @@ -426,6 +427,8 @@ describe('Curator', function () { // TODO UI for demographics.age needs redesigning // View full details about the case cy.contains('td', 'www.example.com').click({ force: true }); + // Curator's comment. + cy.contains('This case should be consulted with Supervisor.'); // Case data. cy.contains('www.example.com'); // Demographics. diff --git a/verification/curator-service/ui/src/api/models/Day0Case.ts b/verification/curator-service/ui/src/api/models/Day0Case.ts index db8d6a72..bf4271e3 100644 --- a/verification/curator-service/ui/src/api/models/Day0Case.ts +++ b/verification/curator-service/ui/src/api/models/Day0Case.ts @@ -174,6 +174,7 @@ export interface Curators { export interface Day0Case { _id?: string; caseStatus: CaseStatus | ''; + comment?: string; caseReference: CaseReference; demographics: Demographics; location: Location; @@ -224,6 +225,7 @@ interface VaccinationFormValues { // contains all the fields present in manual case entry form export interface Day0CaseFormValues { caseStatus: CaseStatus | ''; + comment?: string; caseReference: { sourceId: string; sourceUrl: string; diff --git a/verification/curator-service/ui/src/components/BulkCaseForm.tsx b/verification/curator-service/ui/src/components/BulkCaseForm.tsx index dce95a46..d9ec0e51 100644 --- a/verification/curator-service/ui/src/components/BulkCaseForm.tsx +++ b/verification/curator-service/ui/src/components/BulkCaseForm.tsx @@ -101,6 +101,7 @@ interface RawParsedCase { [key: string]: string | number | boolean | undefined; caseStatus: CaseStatus; + comment?: string; pathogen: string; // CaseReference @@ -255,6 +256,7 @@ const BulkCaseForm = (props: BulkCaseFormProps) => { ): CompleteParsedCase => { return { caseStatus: c.caseStatus, + comment: c.comment, pathogen: c.pathogen, caseReference: { sourceId: caseReference.sourceId, diff --git a/verification/curator-service/ui/src/components/CaseForm.tsx b/verification/curator-service/ui/src/components/CaseForm.tsx index 5fab61c2..1db8fa33 100644 --- a/verification/curator-service/ui/src/components/CaseForm.tsx +++ b/verification/curator-service/ui/src/components/CaseForm.tsx @@ -65,6 +65,7 @@ const initialValuesFromCase = ( // return minimal viable case return { caseStatus: '', + comment: '', caseReference: { sourceId: '', sourceUrl: '', @@ -255,6 +256,7 @@ const NewCaseValidation = Yup.object().shape( caseStatus: Yup.string() .oneOf(['confirmed', 'suspected', 'discarded', 'omit_error']) .required('Required'), + comment: Yup.string(), caseReference: Yup.object().shape({ sourceUrl: Yup.string() .required('Required') @@ -670,65 +672,64 @@ export default function CaseForm(props: Props): JSX.Element { - scrollTo('demographics') + scrollTo('location') } > {tableOfContentsIcon({ isChecked: isChecked({ - optionalValues: [ - values.demographics.gender, - values.demographics.age, - values.demographics - .occupation, - values.demographics - .healthcareWorker, + requiredValues: [ + values.location.countryISO3, ], }), hasError: hasErrors( - ['demographics'], + ['location'], errors, touched, ), })} - {'Demographics'.toLocaleUpperCase()} + {'Location'.toLocaleUpperCase()} - - scrollTo('location') - } + onClick={(): void => scrollTo('events')} > {tableOfContentsIcon({ isChecked: isChecked({ requiredValues: [ - values.location.countryISO3, + values.events.dateEntry, + values.events.dateReported, ], }), hasError: hasErrors( - ['location'], + ['events'], errors, touched, ), })} - {'Location'.toLocaleUpperCase()} + {'Events'.toLocaleUpperCase()} scrollTo('events')} + onClick={(): void => + scrollTo('demographics') + } > {tableOfContentsIcon({ isChecked: isChecked({ - requiredValues: [ - values.events.dateEntry, - values.events.dateReported, + optionalValues: [ + values.demographics.gender, + values.demographics.age, + values.demographics + .occupation, + values.demographics + .healthcareWorker, ], }), hasError: hasErrors( - ['events'], + ['demographics'], errors, touched, ), })} - {'Events'.toLocaleUpperCase()} + {'Demographics'.toLocaleUpperCase()} @@ -949,15 +950,15 @@ export default function CaseForm(props: Props): JSX.Element { hasSourceEntryId /> - - - + + + diff --git a/verification/curator-service/ui/src/components/LinelistTable/helperFunctions.ts b/verification/curator-service/ui/src/components/LinelistTable/helperFunctions.ts index cb437c79..171e039b 100644 --- a/verification/curator-service/ui/src/components/LinelistTable/helperFunctions.ts +++ b/verification/curator-service/ui/src/components/LinelistTable/helperFunctions.ts @@ -32,12 +32,14 @@ export const createData = ( dateOnset?: string, source?: string, caseStatus?: string, + comment?: string, ) => { return { caseId: caseId || '', dateEntry: dateEntry || '', dateReported: dateReported || '', caseStatus: caseStatus || '', + comment: comment || '', country: country || '', region: region || '', district: district || '', diff --git a/verification/curator-service/ui/src/components/LinelistTable/index.tsx b/verification/curator-service/ui/src/components/LinelistTable/index.tsx index 5c95959b..d93d24dd 100644 --- a/verification/curator-service/ui/src/components/LinelistTable/index.tsx +++ b/verification/curator-service/ui/src/components/LinelistTable/index.tsx @@ -128,6 +128,7 @@ const LinelistTable = () => { renderDate(data.events.dateOnset) || '-', data.caseReference.sourceUrl || '-', data.caseStatus || '-', + data.comment || '', ); }); diff --git a/verification/curator-service/ui/src/components/ViewCase.tsx b/verification/curator-service/ui/src/components/ViewCase.tsx index eee68460..d59e7370 100644 --- a/verification/curator-service/ui/src/components/ViewCase.tsx +++ b/verification/curator-service/ui/src/components/ViewCase.tsx @@ -225,23 +225,23 @@ function CaseDetails(props: CaseDetailsProps): JSX.Element {