-
Notifications
You must be signed in to change notification settings - Fork 280
feat(#10036): add PersonQualifier
and related functions
#10043
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(#10036): add PersonQualifier
and related functions
#10043
Conversation
Signed-off-by: apoorvapendse <[email protected]>
|
||
if (data.type === 'contact' && !hasField(data, { name: 'contact_type', type: 'string' })) { | ||
return false; | ||
} else if (!(data.type === 'person')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} else if (!(data.type === 'person')) { | |
} else if (data.type !== 'person') { |
if (!isRecord(data)) { | ||
throw new InvalidArgumentError('Invalid "data": expected an object.'); | ||
} | ||
const qualifier = { ...data }; | ||
if ('reported_date' in qualifier && !isValidReportedDate(qualifier.reported_date)) { | ||
throw new InvalidArgumentError( | ||
// eslint-disable-next-line max-len | ||
`Invalid reported_date. Expected format to be 'YYYY-MM-DDTHH:mm:ssZ', 'YYYY-MM-DDTHH:mm:ss.SSSZ', or a Unix epoch.` | ||
); | ||
} | ||
if (!isContactQualifier(qualifier) || !hasField(data, { name: 'parent', type: 'object' })) { | ||
throw new InvalidArgumentError(`Missing or empty required fields [${JSON.stringify(data)}].`); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: can the byContactQualifier
function be reused here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same problem mentioned here.
if (!isContactQualifier(data)) { | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: this should be at the top of the file because the first two checks are included in that function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That causes TypeScript to think that data
is a ContactQualifer, which results in a lot of assertions for further checks.
Is there some way to deal with that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, there a few ways to deal with this.
- reset type
export const isPersonQualifier = (data: unknown): data is PersonQualifier => {
if (!isContactQualifier(data)) {
return false;
}
// Reset data back to unknown for further type checking
const unknownData = data as unknown;
// Continue with your additional checks on unknownData
// ...
};
- Double refactor the code. Put the common code from
isContactQualifier
andisPersonQualifier
into another function.
const checkContactQualifierFields = (data: unknown): data is Record<string, unknown> => {
return isRecord(data) &&
hasFields(data, [
{name: 'type', type: 'string', ensureTruthyValue: true},
{name: 'name', type: 'string', ensureTruthyValue: true}
]) &&
(!('reported_date' in data) || isValidReportedDate(data.reported_date));
};
export const isContactQualifier = (qualifier: unknown): qualifier is ContactQualifier => {
return checkContactQualifierFields(qualifier);
};
export const isPersonQualifier = (data: unknown): data is PersonQualifier => {
if (!checkContactQualifierFields(data)) {
return false;
}
// data is still treated as Record<string, unknown> here, not ContactQualifier
// Add your PersonQualifier-specific checks
// ...
return true;
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try out the 2nd option as I think it will get the ball rolling for the other comments.
if (data.type === 'contact' && !hasField(data, { name: 'contact_type', type: 'string' })) { | ||
throw new InvalidArgumentError(`Missing or empty required fields [${JSON.stringify(data)}].`); | ||
} else if (!(data.type === 'person')) { | ||
throw new InvalidArgumentError('Expected `type` to be `person`.'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: duplicate code with lines 358-362, refactor into a function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same argument as the one I made here, we throw different errors for different stuff and booleans in case of the is
functions.
I think we did something similar for the ContactQualifier, where we redundantly copied the checks to throw errors/false returns based on whether it was a is
or by
function.
// Ensure parent lineage doesn't have any additional properties other than `_id` and `parent`. | ||
let parent = data.parent; | ||
while (parent) { | ||
if (Object.keys(parent).length > 2) { | ||
// This means that the parent certainly has extra fields and is not minfied/de-hydrated as per | ||
// our liking as `isNormalized` check ensures that it does have two keys `_id` and `parent`. | ||
throw new InvalidArgumentError(`Additional fields found in the parent lineage [${JSON.stringify(data)}].`); | ||
} | ||
parent = data.parent; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: duplicate code with lines 344-352, refactor into a function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I didn't refactor into a function is because this check depends on first checking isNormalizedParent
which is done in the previous steps of both the functions, so it cannot be used independently.
also including isNormalizedParent
call in the new extracted function wouldn't be convenient because of the fact that it throws a different error and the same throwing error or returning false dilemma we have been facing for the qualifiers.
let parent = data.parent; | ||
while (parent) { | ||
if (Object.keys(parent).length > 2) { | ||
// This means that the parent certainly has extra fields and is not minfied/de-hydrated as per | ||
// our liking as `isNormalized` check ensures that it does have two keys `_id` and `parent`. | ||
return false; | ||
} | ||
parent = data.parent; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: this is more of a design decision. Personally, I think it's cleaner to have just a immediate parent as an field and not allow more nests. We could add the grandparents and so on from the server side. There's no benefit of the current way other than not having to query the database for the grandparents.
@jkuester What are you thoughts on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Cross posting from the call with hopefully better clarity)
I think going from the current version to the version @sugat009 mentioned would be easier for the user.
Transitioning from a stricter to more lenient data requirement would introduce complexity and we can make it broader after doing the "MVP" version.
The ideal version would probably be the one where someone can just pass the immediate parent_id or the entire lineage(normalized or hydrated) and we just ignore everything apart from the immediate parent to do our own lineage checks up the hierarchy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current assumption with createPerson
and PersonQualifier
methods is that the user passed in the correct lineage. We do throw errors if it is bloated or misses the required _id
, parent
fields.
I'd like to keep it this way and maybe come back to building the more forgiving and robust version later down the line.
Would love to hear your thoughts @jkuester, @sugat009.
945afa9
to
da56aa3
Compare
Signed-off-by: apoorvapendse <[email protected]>
da56aa3
to
7c8a9b8
Compare
Signed-off-by: apoorvapendse <[email protected]>
7c8a9b8
to
07a3e92
Compare
Signed-off-by: apoorvapendse <[email protected]>
Signed-off-by: apoorvapendse <[email protected]>
byContactQualifierNonAssertive(data); | ||
return data as ContactQualifier; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: shouldn't this be returning the function instead like this?
byContactQualifierNonAssertive(data); | |
return data as ContactQualifier; | |
return byContactQualifierNonAssertive(data) as ContactQualifier; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah makes sense.
if (!hasValidContactType(data) && !hasValidLegacyContactType(data, 'person')) { | ||
return false; | ||
} | ||
|
||
return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!hasValidContactType(data) && !hasValidLegacyContactType(data, 'person')) { | |
return false; | |
} | |
return true; | |
return hasValidContactType(data) || hasValidLegacyContactType(data, 'person'); |
equivalent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup
if (data.type === 'contact' && hasField(data, { name: 'contact_type', type: 'string' })) { | ||
return true; | ||
} | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (data.type === 'contact' && hasField(data, { name: 'contact_type', type: 'string' })) { | |
return true; | |
} | |
return false; | |
return data.type === 'contact' && hasField(data, { name: 'contact_type', type: 'string' }) |
Signed-off-by: apoorvapendse <[email protected]>
Signed-off-by: apoorvapendse <[email protected]>
{name: 'type', type: 'string', ensureTruthyValue: true}, | ||
{name: 'name', type: 'string', ensureTruthyValue: true} | ||
]) && | ||
(!('reported_date' in data) || isValidReportedDate(data.reported_date)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: if the reported_date
does not exist, shouldn't it be added to it? Or are you planning on adding it elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, added in the latest commit.
Signed-off-by: apoorvapendse <[email protected]>
e9e775f
to
3427c99
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there. Some final touches.
insertReportedDateIfMissing(data); | ||
|
||
const qualifier = {...data}; | ||
if ('reported_date' in qualifier && !isValidReportedDate(qualifier.reported_date)) { | ||
if (!isValidReportedDate(qualifier.reported_date)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: the addition of date should be done after line 242, because the whole point of deep copying the data
object into qualifier
is to not mutate the passed in data
object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right.
.throw(`Additional fields found in the parent lineage [${JSON.stringify(data)}].`); | ||
}); | ||
|
||
it('builds qualifier for valid objects', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: can you add a test for when a reported_date
already exists within the passed body
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, added in the last commit.
7549817
to
566a085
Compare
c734c65
into
medic:9835-add-cht-datasource-apis-for-creation-and-update-of-contacts-and-reports
Description
WIP to integrate
PersonQualifier
to eventually add support for creatingperson
documents.Related to #10036.
Code review checklist
can_view_old_navigation
permission to see the old design. Test it has appropriate design for RTL languages.License
The software is provided under AGPL-3.0. Contributions to this project are accepted under the same license.