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

Event tracker repository implementation #6

Merged
merged 15 commits into from
Aug 14, 2024

Conversation

9sneha-n
Copy link
Contributor

@9sneha-n 9sneha-n commented Jul 10, 2024

📌 References

📝 Implementation

  1. Implement DiseaseOutbreakEvent Repo
  2. Implement partially OrgUnit Repo
  3. Implement partially Options Repo
  4. Implement partially Team Member Repo

📹 Screenshots/Screen capture

🔥 Notes to the tester

@9sneha-n 9sneha-n changed the title Feature/event tracker repo Event tracker repository implementation Jul 31, 2024
@9sneha-n 9sneha-n marked this pull request as ready for review July 31, 2024 05:42
@9sneha-n 9sneha-n requested review from delemaf and anagperal July 31, 2024 05:42
this.api.metadata.get({
users: {
fields: { id: true, name: true, email: true, phoneNumber: true },
filter: { username: { eq: id } },
Copy link
Contributor

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 ?

Copy link
Contributor Author

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?

Copy link
Contributor

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)

Copy link
Contributor

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'

Copy link
Contributor Author

@9sneha-n 9sneha-n Aug 6, 2024

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

Copy link
Contributor

@anagperal anagperal left a 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

Copy link
Contributor

@delemaf delemaf left a 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 👏

@9sneha-n 9sneha-n requested review from anagperal and tokland August 6, 2024 10:58
Copy link
Contributor

@anagperal anagperal left a 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

Copy link

@tokland tokland left a 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");
Copy link

@tokland tokland Aug 8, 2024

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> {
Copy link

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: "" });
Copy link

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.

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 for future

const option: NamedRef = {
id: response.options[0].code,
name: response.options[0].name,
};
Copy link

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
Copy link

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) {
Copy link

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";
Copy link

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,
Copy link

@tokland tokland Aug 8, 2024

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 {
Copy link

@tokland tokland Aug 8, 2024

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.

@9sneha-n
Copy link
Contributor Author

9sneha-n commented Aug 9, 2024

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

Thanks @anagperal Good point, its fixed now

@9sneha-n
Copy link
Contributor Author

9sneha-n commented Aug 9, 2024

[code-only review] I've added some comments, but 99% very minor things, code looks good.

Thanks @tokland , addressed all the comments, except the ones requiring Jorge's input. Will ask him over slack or in technical decision meeting

@9sneha-n 9sneha-n requested review from tokland and anagperal August 9, 2024 10:05
Copy link

@tokland tokland left a 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! :)

anagperal

This comment was marked as outdated.

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)),
Copy link
Contributor

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
Copy link
Contributor

@anagperal anagperal Aug 12, 2024

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"

Copy link
Contributor Author

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.

Copy link
Contributor

@anagperal anagperal left a 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 🙏

@9sneha-n 9sneha-n requested review from anagperal and tokland August 13, 2024 08:30
Copy link

@tokland tokland left a 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,
Copy link

@tokland tokland Aug 14, 2024

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
Copy link

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>.

Copy link
Contributor

@anagperal anagperal left a comment

Choose a reason for hiding this comment

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

Thanks @9sneha-n !!

@bhavananarayanan bhavananarayanan merged commit 1e727e4 into development Aug 14, 2024
1 check passed
@bhavananarayanan bhavananarayanan deleted the feature/event-tracker-repo branch August 14, 2024 07:41
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.

6 participants