-
Notifications
You must be signed in to change notification settings - Fork 0
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
Event tracker repository implementation #6
Conversation
this.api.metadata.get({ | ||
users: { | ||
fields: { id: true, name: true, email: true, phoneNumber: true }, | ||
filter: { username: { eq: id } }, |
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.
Maybe for consistency add username: true
to fields and to TeamMember because incidentManagerName
is the username instead of the id. Also it's better to change to incidentManagerUsername
?
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.
Added username: true , however do not understand the second part of the comment. Incident Manager is a type of role in the Team Member. Other Team Members could have other roles, so having the name incidentManagerUsername would not make sense, right?
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.
But I was thinking that incidentManagerName
in DiseaseOutbreakEventBaseAttrs
was the incident manager username selected in the form so I was populating this property with the username (tha's also why I need username in TeamMember
entity, and also needed to populate the options in te incident manager selector)
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.
By the way, I tried to do it in the other PR but I had an error saying: Property 'username' does not exist on type 'D2User'
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.
ahh ok, i get it now. You are right, i get the same error. Lets discuss in the next tech decisions meeting
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.
thanks @9sneha-n!! The idea of the mappers is very good! I have added some comments in the code
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.
So for it looks good tome, mapping is a very nice idead 👏
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.
thanks @9sneha-n!! All good!! we'll wait to comment the username
error in the tech meeting and just one more thing related to how is modeled the na
fields in DHIS2 and how are in the app: https://github.com/EyeSeeTea/zebra-dev/pull/6/files#r1705539592
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.
[code-only review] I've added some comments, but 99% very minor things, code looks good.
fields: { attributes: true, trackedEntity: true }, | ||
}) | ||
).map(trackedEntity => { | ||
if (!trackedEntity.instances[0]) throw new Error("Tracked entity not found"); |
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.
As the method is returning a Future, the natural way to signal an error is returning a Future.error
(as you know, it's a typical pattern using flatMap)
(I'd apply the same idea for all the code base).
You can think of some abstraction to make it as DRY as possible. Proposal (just an idea):
.flatMap(res => assertOrError(res.instances[0]))
.map(mapTrackedEntityAttributesToDiseaseOutbreak);
}); | ||
}); | ||
} | ||
save(diseaseOutbreak: DiseaseOutbreakEventBaseAttrs): FutureData<void> { |
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.
[nit-picking] blank lines between methods. A pity there is no way for prettier to do that automatically!
constructor(private api: D2Api) {} | ||
|
||
get(code: Code): FutureData<NamedRef> { | ||
if (!code) return Future.success({ id: "", name: "" }); |
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.
I am very weary of data repositories returning empty objects like this. It's going to be used as en empty option for dropdowns? that should be handled at the presentation level.
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.
noted for future
const option: NamedRef = { | ||
id: response.options[0].code, | ||
name: response.options[0].name, | ||
}; |
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.
options[0]
referenced 3 times -> local variable
case "RTSL_ZEB_TEA_AREAS_AFFECTED_PROVINCES": | ||
return diseaseOutbreak.areasAffectedProvinceIds[0] ?? ""; //TO DO : Handle multiple provinces once metadata change is done | ||
case "RTSL_ZEB_TEA_AREAS_AFFECTED_DISTRICTS": | ||
return diseaseOutbreak.areasAffectedDistrictIds[0] ?? ""; //TO DO : Handle multiple provinces once metadata change is done |
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.
abstract processing patterns
@@ -0,0 +1,21 @@ | |||
import { D2Api } from "@eyeseetea/d2-api/2.36"; | |||
|
|||
export function getProgramTEAsMetadata(api: D2Api, programId: string) { |
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.
Minor, but typically you'd transform to future here, so the caller has less work to do.
@@ -0,0 +1,21 @@ | |||
import { D2Api } from "@eyeseetea/d2-api/2.36"; |
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.
We should avoid these imports. Instead, import from a wrapper module (src/types/d2-api.ts)
created: trackedEntity.createdAt ? new Date(trackedEntity.createdAt) : new Date(), | ||
lastUpdated: trackedEntity.updatedAt ? new Date(trackedEntity.updatedAt) : new Date(), | ||
createdByName: undefined, | ||
hazardType: getValueFromMap("hazardType", trackedEntity) as HazardType, |
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.
isValueInUnionType
also here.
Note that this kind of valud/type-validation are ok for simple structures like this. When we have more complex structures, as you know, we can always use purify-ts->Codec.
function getValueFromDiseaseOutbreak( | ||
key: (typeof DiseaseOutbreakCodes)[keyof typeof DiseaseOutbreakCodes], | ||
diseaseOutbreak: DiseaseOutbreakEventBaseAttrs | ||
): string { |
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.
I can see only a minor drawback of the mapping approach (it creates an indirection), but it definitely left me thinking what would be the non-mapping approach :) I think it would be something like this:
type Code = GetValue<typeof DiseaseOutbreakCodes>
const attributeValues: Record<Code, string> = {
RTSL_ZEB_TEA_EVENT_id: diseaseOutbreak.eventId.toString(),
RTSL_ZEB_TEA_EVENT_NAME: diseaseOutbreak.name,
// ...
};
const attributes = getAttributesFromAttributeValues(attributesMetadata, attributeValues)
(btw, this Code should not be here, instead move the definition to DiseaseOutbreakCodes. Modules must help their callers)
With both styles at hand, pick the one that looks more declarative to you.
Thanks @anagperal Good point, its fixed now |
Thanks @tokland , addressed all the comments, except the ones requiring Jorge's input. Will ask him over slack or in technical decision meeting |
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.
Nice! Very close. Sorry for the nit-picking! :)
created: trackedEntity.createdAt ? new Date(trackedEntity.createdAt) : new Date(), | ||
lastUpdated: trackedEntity.updatedAt ? new Date(trackedEntity.updatedAt) : new Date(), | ||
createdByName: undefined, | ||
hazardType: getHazardTypeValue(getValueFromMap("hazardType", trackedEntity)), |
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.
This does not seem to work because the hazard types in the trackedEntity are like BIOLOGICAL_ANIMAL
, BIOLOGICAL_HUMAN
... (check https://metadata.eyeseetea.com/rsl-zebra/dhis-web-maintenance/index.html#/edit/otherSection/optionSet/p2N1GkG713S/options) so first we need to map from these codes to "Biological:Animal", "Biological:Human", "Chemical", "Environmental" or "Unknown". Also there is one new: Biological: Human and Animal
diseaseOutbreak.earlyResponseActions.initiateInvestigation.toISOString(), | ||
RTSL_ZEB_TEA_CONDUCT_EPIDEMIOLOGICAL_ANALYSIS: | ||
diseaseOutbreak.earlyResponseActions.conductEpidemiologicalAnalysis.toISOString(), | ||
RTSL_ZEB_TEA_LABORATORY_CONFIRMATION: diseaseOutbreak.earlyResponseActions |
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.
For NA values is the other way around:
RTSL_ZEB_TEA_LABORATORY_CONFIRMATION: diseaseOutbreak.earlyResponseActions
.laboratoryConfirmation.na
? ""
: "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.
Hi @anagperal In DHIS all of these are mandatory tracked entity attribute of Yes only type. They cannot be null or empty. The app implementation needs to take care of the true value, we can discuss further over call.
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.
thansk @9sneha-n ! I had approved the PR but in using it I noticed a couple of small bugs 🙏
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.
All good as far as I can see, code-wise!
@@ -174,8 +165,8 @@ export function mapDiseaseOutbreakEventToTrackedEntityAttributes( | |||
} | |||
|
|||
function getValueFromMap( | |||
key: keyof typeof DiseaseOutbreakCodes, | |||
key: keyof typeof diseaseOutbreakCodes, |
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.
[no need change] we may create type aliases to improve readability/re-usability
].includes(hazardType); | ||
export function getHazardTypeValue(hazardType: string): HazardType { | ||
const hazardTypeString = Object.keys(hazardTypeCodeMap).find( | ||
key => hazardTypeCodeMap[key as HazardType] === hazardType |
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.
[no need to change] that's me being a TS zealot :) you are completely certain that hazardTypeCodeMap
has the keys you want, it's a literal, but TS has structural typing, so that object could potentially have more props and key as HazardType
(which is a "narrow down") would be type unsafe (and even then the function will not fail). Another way of implementing this is by inverting hazardTypeCodeMap
so we have a Record<string, HazardType>
.
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.
Thanks @9sneha-n !!
📌 References
📝 Implementation
📹 Screenshots/Screen capture
🔥 Notes to the tester