Skip to content

Commit

Permalink
code review
Browse files Browse the repository at this point in the history
  • Loading branch information
CynthiaKamau committed Jan 15, 2025
1 parent 777dea6 commit 6776aac
Show file tree
Hide file tree
Showing 13 changed files with 141 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -169,16 +169,6 @@ const UiSelectExtended: React.FC<FormFieldInputProps> = ({ field, errors, warnin
itemToString={(item) => item?.display}
selectedItem={selectedItem}
placeholder={isSearchable ? t('search', 'Search') + '...' : null}
<<<<<<< HEAD
=======
shouldFilterItem={({ item, inputValue }) => {
if (!inputValue || items.find((item) => item.uuid == field.value)) {
// Carbon's initial call at component mount
return true;
}
return item.display?.toLowerCase().includes(inputValue.toLowerCase());
}}
>>>>>>> 5510d0b ((feat) O3-3367 Add support for person attributes)
onChange={({ selectedItem }) => {
isProcessingSelection.current = true;
setFieldValue(selectedItem?.uuid);
Expand Down
23 changes: 15 additions & 8 deletions src/hooks/useFormJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,21 @@ async function refineFormJson(
schemaTransformers: FormSchemaTransformer[] = [],
formSessionIntent?: string,
): Promise<FormSchema> {
removeInlineSubForms(formJson, formSessionIntent);
// apply form schema transformers
for (let transformer of schemaTransformers) {
const draftForm = await transformer.transform(formJson);
formJson = draftForm;
}
setEncounterType(formJson);
return applyFormIntent(formSessionIntent, formJson);
await removeInlineSubForms(formJson, formSessionIntent);
const transformedFormJson = await schemaTransformers.reduce(async (form, transformer) => {
const currentForm = await form;
if (isPromise(transformer.transform(currentForm))) {
return transformer.transform(currentForm);
} else {
return transformer.transform(currentForm);
}
}, Promise.resolve(formJson));
setEncounterType(transformedFormJson);
return applyFormIntent(formSessionIntent, transformedFormJson);
}

function isPromise(value: any): value is Promise<any> {
return value && typeof value.then === 'function';
}

/**
Expand Down
7 changes: 4 additions & 3 deletions src/hooks/usePersonAttributes.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { openmrsFetch, type PersonAttribute, restBaseUrl } from '@openmrs/esm-framework';
import { useEffect, useState } from 'react';
import { type FormSchema } from '../types';

export const usePersonAttributes = (patientUuid: string) => {
export const usePersonAttributes = (patientUuid: string, formJson: FormSchema) => {
const [personAttributes, setPersonAttributes] = useState<Array<PersonAttribute>>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
if (patientUuid) {
if (formJson.meta?.personAttributes?.hasPersonAttributeFields && patientUuid) {
openmrsFetch(`${restBaseUrl}/patient/${patientUuid}?v=custom:(attributes)`)
.then((response) => {
setPersonAttributes(response?.data?.attributes);
setPersonAttributes(response.data?.attributes);
setIsLoading(false);
})
.catch((error) => {
Expand Down
14 changes: 7 additions & 7 deletions src/processors/encounter/encounter-form-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ function useCustomHooks(context: Partial<FormProcessorContextProps>) {
const [isLoading, setIsLoading] = useState(true);
const { encounter, isLoading: isLoadingEncounter } = useEncounter(context.formJson);
const { encounterRole, isLoading: isLoadingEncounterRole } = useEncounterRole();
const { isLoading: isLoadingPatientPrograms, patientPrograms } = usePatientPrograms(
const { isLoadingPatientPrograms, patientPrograms } = usePatientPrograms(context.patient?.id, context.formJson);
const { isLoading: isLoadingPersonAttributes, personAttributes } = usePersonAttributes(
context.patient?.id,
context.formJson,
);
const { isLoading: isLoadingPersonAttributes, personAttributes } = usePersonAttributes(context.patient?.id);

useEffect(() => {
setIsLoading(isLoadingPatientPrograms || isLoadingEncounter || isLoadingEncounterRole || isLoadingPersonAttributes);
Expand Down Expand Up @@ -170,9 +170,9 @@ export class EncounterFormProcessor extends FormProcessor {

// save person attributes
try {
const personattributes = preparePersonAttributes(context.formFields, context.location?.uuid);
const savedPrograms = await savePersonAttributes(context.patient, personattributes);
if (savedPrograms?.length) {
const personAttributes = preparePersonAttributes(context.formFields, context.location?.uuid);
const savedAttributes = await savePersonAttributes(context.patient, personAttributes);
if (savedAttributes?.length) {
showSnackbar({
title: translateFn('personAttributesSaved', 'Person attribute(s) saved successfully'),
kind: 'success',
Expand All @@ -181,12 +181,12 @@ export class EncounterFormProcessor extends FormProcessor {
}
} catch (error) {
const errorMessages = extractErrorMessagesFromResponse(error);
return Promise.reject({
throw {
title: translateFn('errorSavingPersonAttributes', 'Error saving person attributes'),
description: errorMessages.join(', '),
kind: 'error',
critical: true,
});
};
}

// save encounter
Expand Down
4 changes: 1 addition & 3 deletions src/processors/encounter/encounter-processor-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,7 @@ export function saveAttachments(fields: FormField[], encounter: OpenmrsEncounter
}

export function savePersonAttributes(patient: fhir.Patient, attributes: PersonAttribute[]) {
return attributes.map((personAttribute) => {
return savePersonAttribute(personAttribute, patient.id);
});
return attributes.map((personAttribute) => savePersonAttribute(personAttribute, patient.id));
}

export function getMutableSessionProps(context: FormContextProps) {
Expand Down
6 changes: 0 additions & 6 deletions src/registry/inbuilt-components/control-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,6 @@ export const controlTemplates: Array<ControlTemplate> = [
},
},
},
{
name: 'person-attribute-location',
datasource: {
name: 'person_attribute_location_datasource',
},
},
];

export const getControlTemplate = (name: string) => {
Expand Down
5 changes: 2 additions & 3 deletions src/registry/inbuilt-components/inbuiltDataSources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { LocationDataSource } from '../../datasources/location-data-source';
import { ProviderDataSource } from '../../datasources/provider-datasource';
import { SelectConceptAnswersDatasource } from '../../datasources/select-concept-answers-datasource';
import { EncounterRoleDataSource } from '../../datasources/encounter-role-datasource';
import { PersonAttributeLocationDataSource } from '../../datasources/person-attribute-datasource';

/**
* @internal
Expand Down Expand Up @@ -36,8 +35,8 @@ export const inbuiltDataSources: Array<RegistryItem<DataSource<any>>> = [
component: new EncounterRoleDataSource(),
},
{
name: 'person_attribute_location_datasource',
component: new PersonAttributeLocationDataSource(),
name: 'person-attribute-location',
component: new LocationDataSource(),
},
];

Expand Down
5 changes: 5 additions & 0 deletions src/registry/inbuilt-components/inbuiltTransformers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PersonAttributesTransformer } from '../../transformers/person-attributes-transformer';
import { DefaultFormSchemaTransformer } from '../../transformers/default-schema-transformer';
import { type FormSchemaTransformer } from '../../types';
import { type RegistryItem } from '../registry';
Expand All @@ -7,4 +8,8 @@ export const inbuiltFormTransformers: Array<RegistryItem<FormSchemaTransformer>>
name: 'DefaultFormSchemaTransformer',
component: DefaultFormSchemaTransformer,
},
{
name: 'PersonAttributesTransformer',
component: PersonAttributesTransformer,
},
];
4 changes: 0 additions & 4 deletions src/registry/inbuilt-components/template-component-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,4 @@ export const templateToComponentMap = [
name: 'encounter-role',
baseControlComponent: UiSelectExtended,
},
{
name: 'person_attribute_location_datasource',
baseControlComponent: UiSelectExtended,
},
];
3 changes: 2 additions & 1 deletion src/registry/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,10 @@ export async function getRegisteredFieldValueAdapter(type: string): Promise<Form
}

export async function getRegisteredFormSchemaTransformers(): Promise<FormSchemaTransformer[]> {
const transformers: FormSchemaTransformer[] = [];
const transformers: Array<FormSchemaTransformer> = [];

const cachedTransformers = registryCache.formSchemaTransformers;

if (Object.keys(cachedTransformers).length) {
return Object.values(cachedTransformers);
}
Expand Down
78 changes: 20 additions & 58 deletions src/transformers/default-schema-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,34 @@ import { type OpenmrsResource } from '@openmrs/esm-framework';
import { type FormField, type FormSchema, type FormSchemaTransformer, type RenderType, type FormPage } from '../types';
import { isTrue } from '../utils/boolean-utils';
import { hasRendering } from '../utils/common-utils';
import { getPersonAttributeTypeFormat } from '../api/';

export type RenderTypeExtended = 'multiCheckbox' | 'numeric' | RenderType;

export const DefaultFormSchemaTransformer: FormSchemaTransformer = {
transform: async (form: FormSchema): Promise<FormSchema> => {
try {
parseBooleanTokenIfPresent(form, 'readonly');
for (const [index, page] of form.pages.entries()) {
const label = page.label ?? '';
page.id = `page-${label.replace(/\s/g, '')}-${index}`;
parseBooleanTokenIfPresent(page, 'readonly');
if (page.sections) {
for (const section of page.sections) {
section.questions = handleQuestionsWithDateOptions(section.questions);
section.questions = handleQuestionsWithObsComments(section.questions);
parseBooleanTokenIfPresent(section, 'readonly');
parseBooleanTokenIfPresent(section, 'isExpanded');
if (section.questions) {
section.questions = await Promise.all(
section.questions.map((question) => handleQuestion(question, page, form)),
);
}
}
}
transform: (form: FormSchema) => {
parseBooleanTokenIfPresent(form, 'readonly');
form.pages.forEach((page, index) => {
const label = page.label ?? '';
page.id = `page-${label.replace(/\s/g, '')}-${index}`;
parseBooleanTokenIfPresent(page, 'readonly');
if (page.sections) {
page.sections.forEach((section) => {
section.questions = handleQuestionsWithDateOptions(section.questions);
section.questions = handleQuestionsWithObsComments(section.questions);
parseBooleanTokenIfPresent(section, 'readonly');
parseBooleanTokenIfPresent(section, 'isExpanded');
section?.questions?.forEach((question, index) => handleQuestion(question, page, form));
});
}
} catch (error) {
console.error('Error in form transformation:', error);
throw error;
}
});
if (form.meta?.programs) {
handleProgramMetaTags(form);
}
return form;
},
};

async function handleQuestion(question: FormField, page: FormPage, form: FormSchema): Promise<FormField> {
function handleQuestion(question: FormField, page: FormPage, form: FormSchema) {
if (question.type === 'programState') {
const formMeta = form.meta ?? {};
formMeta.programs = formMeta.programs
Expand All @@ -50,20 +40,17 @@ async function handleQuestion(question: FormField, page: FormPage, form: FormSch
try {
sanitizeQuestion(question);
setFieldValidators(question);
await transformByType(question);
transformByType(question);
transformByRendering(question);

if (question.questions?.length) {
if (question.type === 'obsGroup' && question.questions.length) {
question.questions.forEach((nestedQuestion) => handleQuestion(nestedQuestion, page, form));
} else {
question.questions = await Promise.all(
question.questions.map((nestedQuestion) => handleQuestion(nestedQuestion, page, form)),
);
question.questions.forEach((nestedQuestion) => handleQuestion(nestedQuestion, page, form));
}
}
question.meta.pageId = page.id;
return question;
} catch (error) {
console.error(error);
}
Expand Down Expand Up @@ -121,7 +108,7 @@ function sanitizeQuestion(question: FormField) {
}
}

function parseBooleanTokenIfPresent(node: any, token: any) {
export function parseBooleanTokenIfPresent(node: any, token: any) {
if (node && typeof node[token] === 'string') {
const trimmed = node[token].trim().toLowerCase();
if (trimmed === 'true' || trimmed === 'false') {
Expand All @@ -145,7 +132,7 @@ function setFieldValidators(question: FormField) {
}
}

async function transformByType(question: FormField) {
function transformByType(question: FormField) {
switch (question.type) {
case 'encounterProvider':
question.questionOptions.rendering = 'encounter-provider';
Expand All @@ -161,9 +148,6 @@ async function transformByType(question: FormField) {
? 'date'
: question.questionOptions.rendering;
break;
case 'personAttribute':
await handlePersonAttributeType(question);
break;
}
}

Expand Down Expand Up @@ -292,25 +276,3 @@ function handleQuestionsWithObsComments(sectionQuestions: Array<FormField>): Arr

return augmentedQuestions;
}

async function handlePersonAttributeType(question: FormField) {
if (question.questionOptions.rendering !== 'text') {
question.questionOptions.rendering === 'ui-select-extended';
}

const attributeTypeFormat = await getPersonAttributeTypeFormat(question.questionOptions.attributeType);
if (attributeTypeFormat === 'org.openmrs.Location') {
question.questionOptions.datasource = {
name: 'person_attribute_location_datasource',
};
} else if (attributeTypeFormat === 'org.openmrs.Concept') {
question.questionOptions.datasource = {
name: 'select_concept_answers_datasource',
config: {
concept: question.questionOptions?.concept,
},
};
} else if (attributeTypeFormat === 'java.lang.String') {
question.questionOptions.rendering = 'text';
}
}
Loading

0 comments on commit 6776aac

Please sign in to comment.