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

[feature] Integrate d2-logger (coding dojo ----DO NOT MERGE----- ) #23

Open
wants to merge 13 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@emotion/react": "11.11.4",
"@emotion/styled": "11.11.5",
"@eyeseetea/d2-api": "1.16.0-beta.12",
"@eyeseetea/d2-logger": "^1.1.0",
"@eyeseetea/d2-ui-components": "v2.9.0-beta.2",
"@eyeseetea/feedback-component": "0.0.3",
"@material-ui/core": "4.12.4",
Expand Down
23 changes: 23 additions & 0 deletions src/data/entities/Instance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export interface InstanceData {
url: string;
username?: string;
password?: string;
}

export class Instance {
public readonly url: string;
private username: string | undefined;
private password: string | undefined;

constructor(data: InstanceData) {
this.url = data.url;
this.username = data.username;
this.password = data.password;
}

public get auth(): { username: string; password: string } | undefined {
return this.username && this.password
? { username: this.username, password: this.password }
: undefined;
}
}
108 changes: 30 additions & 78 deletions src/data/repositories/AlertD2Repository.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { D2Api } from "@eyeseetea/d2-api/2.36";
import { apiToFuture, FutureData } from "../api-futures";
import {
RTSL_ZEBRA_ALERTS_DISEASE_TEA_ID,
RTSL_ZEBRA_ALERTS_EVENT_TYPE_TEA_ID,
RTSL_ZEBRA_ALERTS_NATIONAL_DISEASE_OUTBREAK_EVENT_ID_TEA_ID,
RTSL_ZEBRA_ALERTS_NATIONAL_INCIDENT_STATUS_TEA_ID,
RTSL_ZEBRA_ALERTS_PROGRAM_ID,
Expand All @@ -12,32 +10,26 @@ import { AlertOptions, AlertRepository } from "../../domain/repositories/AlertRe
import { Id } from "../../domain/entities/Ref";
import _ from "../../domain/entities/generic/Collection";
import { Future } from "../../domain/entities/generic/Future";
import {
D2TrackerTrackedEntity,
TrackedEntitiesGetResponse,
} from "@eyeseetea/d2-api/api/trackerTrackedEntities";
import { D2TrackerTrackedEntity } from "@eyeseetea/d2-api/api/trackerTrackedEntities";
import { Maybe } from "../../utils/ts-utils";
import { DataSource } from "../../domain/entities/disease-outbreak-event/DiseaseOutbreakEvent";
import { Alert } from "../../domain/entities/alert/Alert";

export type Filter = {
id: Id;
value: Maybe<string>;
};
import { OutbreakData } from "../../domain/entities/alert/OutbreakAlert";
import { getAllTrackedEntitiesAsync } from "./utils/getAllTrackedEntities";
import { outbreakDataSourceMapping, outbreakTEAMapping } from "./utils/AlertOutbreakMapper";

export class AlertD2Repository implements AlertRepository {
constructor(private api: D2Api) {}

updateAlerts(alertOptions: AlertOptions): FutureData<Alert[]> {
const { dataSource, eventId, hazardTypeCode, incidentStatus, suspectedDiseaseCode } =
alertOptions;
const filter = this.getAlertFilter(dataSource, suspectedDiseaseCode, hazardTypeCode);
const { dataSource, eventId, incidentStatus, outbreakValue } = alertOptions;
const outbreakData = this.getAlertOutbreakData(dataSource, outbreakValue);

return this.getTrackedEntitiesByTEACode({
program: RTSL_ZEBRA_ALERTS_PROGRAM_ID,
orgUnit: RTSL_ZEBRA_ORG_UNIT_ID,
ouMode: "DESCENDANTS",
filter: filter,
filter: outbreakData,
}).flatMap(alertTrackedEntities => {
const alertsToMap: Alert[] = alertTrackedEntities.map(trackedEntity => ({
id: trackedEntity.trackedEntity || "",
Expand Down Expand Up @@ -79,78 +71,38 @@ export class AlertD2Repository implements AlertRepository {
});
}

private async getTrackedEntitiesByTEACodeAsync(options: {
private getTrackedEntitiesByTEACode(options: {
program: Id;
orgUnit: Id;
ouMode: "SELECTED" | "DESCENDANTS";
filter?: Filter;
}): Promise<D2TrackerTrackedEntity[]> {
filter: OutbreakData;
}): FutureData<D2TrackerTrackedEntity[]> {
const { program, orgUnit, ouMode, filter } = options;
const d2TrackerTrackedEntities: D2TrackerTrackedEntity[] = [];

const pageSize = 250;
let page = 1;
let result: TrackedEntitiesGetResponse;

try {
do {
result = await this.api.tracker.trackedEntities
.get({
program: program,
orgUnit: orgUnit,
ouMode: ouMode,
totalPages: true,
page: page,
pageSize: pageSize,
fields: {
attributes: true,
orgUnit: true,
trackedEntity: true,
trackedEntityType: true,
enrollments: {
events: {
createdAt: true,
dataValues: {
dataElement: true,
value: true,
},
event: true,
},
},
},
filter: filter ? `${filter.id}:eq:${filter.value}` : undefined,
})
.getData();

d2TrackerTrackedEntities.push(...result.instances);

page++;
} while (result.page < Math.ceil((result.total as number) / pageSize));
return d2TrackerTrackedEntities;
} catch {
return [];
}
return Future.fromPromise(
getAllTrackedEntitiesAsync(this.api, {
programId: program,
orgUnitId: orgUnit,
ouMode: ouMode,
filter: {
id: this.getOutbreakFilterId(filter),
value: filter.value,
},
})
);
}

getTrackedEntitiesByTEACode(options: {
program: Id;
orgUnit: Id;
ouMode: "SELECTED" | "DESCENDANTS";
filter?: Filter;
}): FutureData<D2TrackerTrackedEntity[]> {
return Future.fromPromise(this.getTrackedEntitiesByTEACodeAsync(options));
private getOutbreakFilterId(filter: OutbreakData): string {
return outbreakTEAMapping[filter.type];
}

private getAlertFilter(
private getAlertOutbreakData(
dataSource: DataSource,
suspectedDiseaseCode: Maybe<string>,
hazardTypeCode: Maybe<string>
): Filter {
switch (dataSource) {
case DataSource.RTSL_ZEB_OS_DATA_SOURCE_IBS:
return { id: RTSL_ZEBRA_ALERTS_DISEASE_TEA_ID, value: suspectedDiseaseCode };
case DataSource.RTSL_ZEB_OS_DATA_SOURCE_EBS:
return { id: RTSL_ZEBRA_ALERTS_EVENT_TYPE_TEA_ID, value: hazardTypeCode };
}
outbreakValue: Maybe<string>
): OutbreakData {
return {
type: outbreakDataSourceMapping[dataSource],
value: outbreakValue,
};
tokland marked this conversation as resolved.
Show resolved Hide resolved
}
}
22 changes: 9 additions & 13 deletions src/data/repositories/AlertSyncDataStoreRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import {
AlertSyncRepository,
} from "../../domain/repositories/AlertSyncRepository";
import { apiToFuture, FutureData } from "../api-futures";
import { getOutbreakKey, getAlertValueFromMap } from "./utils/AlertOutbreakMapper";
import { getAlertValueFromMap } from "./utils/AlertOutbreakMapper";
import { Maybe } from "../../utils/ts-utils";
import { DataValue } from "@eyeseetea/d2-api/api/trackerEvents";
import { AlertSynchronizationData } from "../../domain/entities/alert/AlertData";
import {
AlertSynchronizationData,
getOutbreakKey,
} from "../../domain/entities/alert/AlertSynchronizationData";
import { DataSource } from "../../domain/entities/disease-outbreak-event/DiseaseOutbreakEvent";
import { RTSL_ZEBRA_ALERTS_PROGRAM_ID } from "./consts/DiseaseOutbreakConstants";
import { assertOrError } from "./utils/AssertOrError";
Expand All @@ -24,14 +27,7 @@ export class AlertSyncDataStoreRepository implements AlertSyncRepository {
}

saveAlertSyncData(options: AlertSyncOptions): FutureData<void> {
const {
alert,
dataSource,
hazardTypeCode,
suspectedDiseaseCode,
hazardTypes,
suspectedDiseases,
} = options;
const { alert, outbreakValue, dataSource, hazardTypes, suspectedDiseases } = options;

return this.getAlertTrackedEntity(alert).flatMap(alertTrackedEntity => {
const verificationStatus = getAlertValueFromMap(
Expand All @@ -42,7 +38,7 @@ export class AlertSyncDataStoreRepository implements AlertSyncRepository {
if (verificationStatus === VerificationStatus.RTSL_ZEB_AL_OS_VERIFICATION_VERIFIED) {
const outbreakKey = getOutbreakKey({
dataSource: dataSource,
outbreakValue: suspectedDiseaseCode || hazardTypeCode,
outbreakValue: outbreakValue,
hazardTypes: hazardTypes,
suspectedDiseases: suspectedDiseases,
});
Expand Down Expand Up @@ -72,7 +68,7 @@ export class AlertSyncDataStoreRepository implements AlertSyncRepository {
});
}

public getAlertTrackedEntity(alert: Alert): FutureData<D2TrackerTrackedEntity> {
private getAlertTrackedEntity(alert: Alert): FutureData<D2TrackerTrackedEntity> {
return apiToFuture(
this.api.tracker.trackedEntities.get({
program: RTSL_ZEBRA_ALERTS_PROGRAM_ID,
Expand Down Expand Up @@ -104,7 +100,7 @@ export class AlertSyncDataStoreRepository implements AlertSyncRepository {
trackedEntity: D2TrackerTrackedEntity,
outbreakKey: string
): AlertSynchronizationData {
const { alert, nationalDiseaseOutbreakEventId, dataSource } = options;
const { alert, dataSource, nationalDiseaseOutbreakEventId } = options;
const outbreakType =
dataSource === DataSource.RTSL_ZEB_OS_DATA_SOURCE_IBS ? "disease" : "hazard";

Expand Down
39 changes: 38 additions & 1 deletion src/data/repositories/DiseaseOutbreakEventD2Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { getProgramTEAsMetadata } from "./utils/MetadataHelper";
import { assertOrError } from "./utils/AssertOrError";
import { Future } from "../../domain/entities/generic/Future";
import { getAllTrackedEntitiesAsync } from "./utils/getAllTrackedEntities";
import { OutbreakData, OutbreakDataType } from "../../domain/entities/alert/OutbreakAlert";

export class DiseaseOutbreakEventD2Repository implements DiseaseOutbreakEventRepository {
constructor(private api: D2Api) {}
Expand All @@ -34,7 +35,10 @@ export class DiseaseOutbreakEventD2Repository implements DiseaseOutbreakEventRep

getAll(): FutureData<DiseaseOutbreakEventBaseAttrs[]> {
return Future.fromPromise(
getAllTrackedEntitiesAsync(this.api, RTSL_ZEBRA_PROGRAM_ID, RTSL_ZEBRA_ORG_UNIT_ID)
getAllTrackedEntitiesAsync(this.api, {
programId: RTSL_ZEBRA_PROGRAM_ID,
orgUnitId: RTSL_ZEBRA_ORG_UNIT_ID,
})
).map(trackedEntities => {
return trackedEntities
.map(trackedEntity => {
Expand All @@ -44,6 +48,34 @@ export class DiseaseOutbreakEventD2Repository implements DiseaseOutbreakEventRep
});
}

getEventByDiseaseOrHazardType(
filter: OutbreakData
): FutureData<DiseaseOutbreakEventBaseAttrs[]> {
return Future.fromPromise(
getAllTrackedEntitiesAsync(this.api, {
programId: RTSL_ZEBRA_PROGRAM_ID,
orgUnitId: RTSL_ZEBRA_ORG_UNIT_ID,
filter: {
id: this.getDiseaseOutbreakFilterId(filter),
value: filter.value,
},
})
).map(trackedEntities => {
return trackedEntities.map(trackedEntity => {
return mapTrackedEntityAttributesToDiseaseOutbreak(trackedEntity);
});
});
}

private getDiseaseOutbreakFilterId(filter: OutbreakData): string {
const mapping: Record<OutbreakDataType, TrackedEntityAttributeId> = {
disease: RTSL_ZEBRA_DISEASE_TEA_ID,
hazard: RTSL_ZEBRA_HAZARD_TEA_ID,
};

return mapping[filter.type];
}

save(diseaseOutbreak: DiseaseOutbreakEventBaseAttrs): FutureData<Id> {
return getProgramTEAsMetadata(this.api, RTSL_ZEBRA_PROGRAM_ID).flatMap(
teasMetadataResponse => {
Expand Down Expand Up @@ -90,3 +122,8 @@ export class DiseaseOutbreakEventD2Repository implements DiseaseOutbreakEventRep

//TO DO : Implement delete/archive after requirement confirmation
}

const RTSL_ZEBRA_DISEASE_TEA_ID = "jLvbkuvPdZ6";
const RTSL_ZEBRA_HAZARD_TEA_ID = "Dzrw3Tf0ukB";

type TrackedEntityAttributeId = Id;
38 changes: 33 additions & 5 deletions src/data/repositories/NotificationD2Repository.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
import { D2Api } from "@eyeseetea/d2-api/2.36";
import {
Notification,
NotificationOptions,
NotificationRepository,
} from "../../domain/repositories/NotificationRepository";
import { apiToFuture, FutureData } from "../api-futures";
import { Future } from "../../domain/entities/generic/Future";
import { UserGroup } from "../../domain/entities/UserGroup";
import { OutbreakAlert } from "../../domain/entities/alert/OutbreakAlert";
import i18n from "../../utils/i18n";

export class NotificationD2Repository implements NotificationRepository {
constructor(private api: D2Api) {}

save(notification: Notification): FutureData<void> {
return apiToFuture(this.api.messageConversations.post(notification)).flatMap(() =>
Future.success(undefined)
);
notifyNationalWatchStaff(
alertData: OutbreakAlert,
outbreakName: string,
userGroups: UserGroup[]
): FutureData<void> {
const { notificationOptions } = alertData;

return apiToFuture(
this.api.messageConversations.post({
subject: `New Outbreak Alert: ${outbreakName} in zm Zambia Ministry of Health`,
text: buildNotificationText(outbreakName, notificationOptions),
userGroups: userGroups,
})
).flatMap(() => Future.success(undefined));
}
}

function buildNotificationText(outbreakKey: string, notificationData: NotificationOptions): string {
const { detectionDate, emergenceDate, incidentManager, notificationDate, verificationStatus } =
notificationData;

return i18n.t(`There has been a new Outbreak detected for ${outbreakKey} in zm Zambia Ministry of Health.

Please see the details of the outbreak below:

Emergence date: ${emergenceDate}
Detection Date : ${detectionDate}
Notification Date : ${notificationDate}
Incident Manager : ${incidentManager}
Verification Status : ${verificationStatus}`);
}
Loading
Loading