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

Moved schema-validator logic to form engine #107

Closed
wants to merge 15 commits into from

Conversation

arodidev
Copy link
Contributor

@arodidev arodidev commented Aug 17, 2023

Moves the form-schema validator logic to live inside the form-engine

@arodidev arodidev changed the title Added form-validator logic to form engine Moved form-validator logic to form engine Aug 17, 2023
Copy link
Member

@samuelmale samuelmale left a comment

Choose a reason for hiding this comment

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

src/form-validator.ts Outdated Show resolved Hide resolved
src/components/inputs/date/ohri-date.component.tsx Outdated Show resolved Hide resolved
src/components/inputs/date/ohri-date.component.tsx Outdated Show resolved Hide resolved
if (data.results.length) {
const [resObject] = data.results;

resObject.datatype.name === 'Boolean' &&
Copy link
Member

Choose a reason for hiding this comment

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

Should be driven by a config

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@samuelmale kindly expound on this

Copy link
Member

Choose a reason for hiding this comment

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

My bad @arodidev my comment regarding the config, see.

As a good practice, I would create an enum holding all the know datatypes to prevent hard-coding these.

const ConceptDataTypes {
  Boolean: 'Boolean',
  Coded: 'Coded',
  Numeric: 'Numeric',
  Text: 'Text'
  // more...
};


// validator.js


if (concept.datatype.name === ConceptDataTypes.Boolean) {}


if (searchRef) {
try {
const { data } = await openmrsFetch(`/ws/rest/v1/concept?references=${searchRef}&v=${conceptRepresentation}`);
Copy link
Member

Choose a reason for hiding this comment

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

I'm concerned about the fact that we have fused the validation logic with API-specific logic. I advise reusing the existing useConcepts() hook. This means the validator becomes a React hook:

// the validator runs if doValidate is set to true
// isValidating shows the validation status
const { errors, warnings, isValidating } = useSchemaValidationResults(doValidate, schema);

Alternatively, you can define a FormSchemaValidator interface but ensure you fetch the concepts in one API hit with proper separation of concerns.

Copy link
Contributor Author

@arodidev arodidev Aug 27, 2023

Choose a reason for hiding this comment

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

@samuelmale I had a previous implementation of how I understood your statement here, kindly advise if this logic suffices.

Comment on lines 59 to 61
if (
answer.concept !== 'cf82933b-3f3f-45e7-a5ab-5d31aaee3da3' &&
answer.concept !== '488b58ff-64f5-4f8a-8979-fa79940b1594'
Copy link
Member

Choose a reason for hiding this comment

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

I would do something like:

if ( ![ConceptTrue, ConceptFalse].includes(answer.concept) ) {
   // handle error
}

The ConceptTrue and ConceptFalse are driven by a System configuration: concept.true and concept.false. We need to be picking these values from the backend. On the contrary, these are currently defined as constants within the library which isn't the conventional practice. I've always wanted to address this technical debt but never got the latitude.

Ideally, we should introduce a new hook that will read this system setting. It would be great if you offered to address this otherwise you can use the already defined constants and we address this later on.

Copy link
Contributor Author

@arodidev arodidev Aug 27, 2023

Choose a reason for hiding this comment

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

@samuelmale this is well noted. I would also like to take on the issue of the hook reading the values from the backend if possible.

@arodidev arodidev changed the title Moved form-validator logic to form engine Moved schema-validator logic to form engine Aug 27, 2023
import { ConceptTrue, ConceptFalse } from '../constants';
import { OHRIFormField, OHRIFormSchema } from '../api/types';

export function useSchemaValidator(schema: OHRIFormSchema) {
Copy link
Member

Choose a reason for hiding this comment

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

Two things here:

  1. We need a trigger state (to trigger the schema validation)
  2. On top of the errors and warnings, it's important to return a progress state i.e.. isValidating

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted.

import { OHRIFormField, OHRIFormSchema } from '../api/types';

export function useSchemaValidator(schema: OHRIFormSchema) {
//initializing state
Copy link
Member

Choose a reason for hiding this comment

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

Get rid of the comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted.

const [answerConceptSet, setAnswerConceptSet] = useState<Set<string>>(new Set());
const { dataTypeToRenderingMap, conceptDataTypes } = useConfig();

const unresolvedConceptsFunc = (fullArray, filteredSetArray) => {
Copy link
Member

Choose a reason for hiding this comment

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

The signature of this function can be improved, starting with the function name. Assuming it finds unresolved concepts, findUnresolvedConcepts is more descriptive.


fullArray: It's not straightforward to tell the kind of objects this variable holds same applies to the other param

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted.

//initializing state
const [errors, setErrors] = useState([]);
const [warnings, setWarnings] = useState([]);
const [fullArray, setFullArray] = useState<Array<OHRIFormField>>([]);
Copy link
Member

Choose a reason for hiding this comment

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

Can you rename this variable to clearly reflect that it's holding fields?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted.

const conceptRepresentation =
'custom:(uuid,display,datatype,conceptMappings:(conceptReferenceTerm:(conceptSource:(name),code)))';

export function useDatatype(references: Set<string>) {
Copy link
Member

Choose a reason for hiding this comment

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

Assuming this returns concepts, what stops you from reusing the useConcepts() hook?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we had this conversation a while back to create a new custom hook that also returns a filtered set with unresolved concepts, but if need be I can revert back to using the useConcept hook and modifying it.

src/api/types.ts Outdated
Comment on lines 264 to 272
export interface ConfigObject {
Numeric: Array<string>;
Coded: Array<string>;
Text: Array<string>;
Date: Array<string>;
Datetime: Array<string>;
Boolean: Array<string>;
Rule: Array<string>;
}
Copy link
Member

Choose a reason for hiding this comment

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

This is possessing a generic name; one may assume it's the general config interface for the form-engine configuration yet I think it's a form schema validator configuration?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted.

const [answersArray, setAnswersArray] = useState([]);
const [conceptSet, setConceptSet] = useState<Set<string>>(new Set());
const [answerConceptSet, setAnswerConceptSet] = useState<Set<string>>(new Set());
const { dataTypeToRenderingMap, conceptDataTypes } = useConfig();
Copy link
Member

Choose a reason for hiding this comment

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

Have you actually tested this out? I don't see where you defined the config schema plus you would have to load this config by module name:

const { dataTypeToRenderingMap, conceptDataTypes } = useConfig({
    externalModuleName: "@openmrs/openmrs-form-engine-lib",
  });

@samuelmale samuelmale closed this Apr 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants