Skip to content

Commit

Permalink
Merge pull request #261 from EyeSeeTea/development
Browse files Browse the repository at this point in the history
Glass Release 1.6.3
  • Loading branch information
bhavananarayanan authored May 30, 2024
2 parents c4722fa + 8efab72 commit a993dfd
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 59 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "glass",
"description": "DHIS2 Glass App",
"version": "1.6.2",
"version": "1.6.3",
"license": "GPL-3.0",
"author": "EyeSeeTea team",
"homepage": ".",
Expand Down
59 changes: 59 additions & 0 deletions src/data/repositories/MetadataDefaultRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@ import { Id } from "../../domain/entities/Base";
const AMR_EGASP_Clinics = "lohCVAxPxMM";
const AMR_EGASP_Labs = "KhLlLrKWPKu";

type CodeRef = {
code: string;
id: Id;
shortName: string;
};

type MetadataIdReponse = {
system: {
id: Id;
rev: string;
version: string;
date: string;
};
trackedEntityAttributes?: CodeRef[];
programs?: CodeRef[];
dataElements?: CodeRef[];
};
export class MetadataDefaultRepository implements MetadataRepository {
private api: D2Api;

Expand All @@ -23,6 +40,48 @@ export class MetadataDefaultRepository implements MetadataRepository {
this.api = getD2APiFromInstance(instance);
}

getD2Ids(ids: string[]): FutureData<NamedRef[]> {
return apiToFuture(
this.api
.get<MetadataIdReponse>(`/metadata?filter=id:in:[${ids.join(",")}]&fields=id,code,shortName`)
.map(response => {
if (response?.data) {
console.debug("response", response);

const deIds =
response.data.dataElements?.map((idRef: any) => {
return {
id: idRef.id,
name: `${idRef.shortName}(${idRef.code})`,
};
}) || [];

const programIds =
response.data.programs?.map((idRef: any) => {
return {
id: idRef.id,
name: `${idRef.shortName}(${idRef.code})`,
};
}) || [];

const teaIds =
response.data.trackedEntityAttributes?.map((idRef: any) => {
return {
id: idRef.id,
name: `${idRef.shortName}(${idRef.code})`,
};
}) || [];

const parsedIds = deIds.concat(programIds).concat(teaIds);

return parsedIds;
} else {
return [];
}
})
);
}

getDataElementNames(dataElementIds: string[]): FutureData<NamedRef[]> {
return apiToFuture(
this.api.metadata
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,11 @@ export class AMCProductDataDefaultRepository implements AMCProductDataRepository

getProductRegisterAndRawProductConsumptionByProductIds(
orgUnitId: Id,
productIds: string[]
productIds: string[],
period: string
): FutureData<ProductDataTrackedEntity[]> {
return Future.fromPromise(
this.getProductRegisterAndRawProductConsumptionByProductIdsAsync(orgUnitId, productIds)
this.getProductRegisterAndRawProductConsumptionByProductIdsAsync(orgUnitId, productIds, period)
).map(trackedEntities => {
return this.mapFromTrackedEntitiesToProductData(trackedEntities);
});
Expand Down Expand Up @@ -134,7 +135,8 @@ export class AMCProductDataDefaultRepository implements AMCProductDataRepository

private async getProductRegisterAndRawProductConsumptionByProductIdsAsync(
orgUnit: Id,
productIds: string[]
productIds: string[],
period: string
): Promise<D2TrackerTrackedEntity[]> {
const trackedEntities: D2TrackerTrackedEntity[] = [];
const pageSize = 250;
Expand All @@ -143,9 +145,18 @@ export class AMCProductDataDefaultRepository implements AMCProductDataRepository
let result;
const productIdsString = productIds.join(";");
const filter = `${AMR_GLASS_AMC_TEA_PRODUCT_ID}:IN:${productIdsString}`;
const enrollmentEnrolledAfter = `${period}-1-1`;
const enrollmentEnrolledBefore = `${period}-12-31`;

do {
result = await this.getTrackedEntitiesOfPage({ orgUnit, filter, page, pageSize });
result = await this.getTrackedEntitiesOfPage({
orgUnit,
filter,
page,
pageSize,
enrollmentEnrolledBefore,
enrollmentEnrolledAfter,
});
trackedEntities.push(...result.instances);
page++;
} while (result.page < totalPages);
Expand Down Expand Up @@ -267,7 +278,8 @@ export class AMCProductDataDefaultRepository implements AMCProductDataRepository
trackedEntityId: trackedEntity.trackedEntity,
enrollmentId: trackedEntity.enrollments[0].enrollment,
enrollmentStatus: trackedEntity.enrollments[0].status,
events,
enrolledAt: trackedEntity.enrollments[0].enrolledAt,
events: events,
attributes: trackedEntity.attributes.map(({ attribute, valueType, value }) => ({
id: attribute,
valueType: valueType as string,
Expand All @@ -289,9 +301,14 @@ export class AMCProductDataDefaultRepository implements AMCProductDataRepository
return rawSubstanceConsumptionCalculatedData
.map(data => {
const productId = data.AMR_GLASS_AMC_TEA_PRODUCT_ID;
const productDataTrackedEntity = productDataTrackedEntities.find(productDataTrackedEntity =>
productDataTrackedEntity.attributes.some(attribute => attribute.value === productId)
);
const productDataTrackedEntity = productDataTrackedEntities.find(productDataTrackedEntity => {
const enrolledYear = new Date(productDataTrackedEntity.enrolledAt).getFullYear();
return (
productDataTrackedEntity.attributes.some(attribute => attribute.value === productId) &&
enrolledYear === Number(period)
);
});

if (productDataTrackedEntity) {
const dataValues: DataValue[] = rawSubstanceConsumptionCalculatedStageMetadata.dataElements.map(
({ id, code, valueType, optionSetValue, optionSet }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ export class DownloadTemplateDefaultRepository implements DownloadTemplateReposi
attributeOptionCombo: true,
latitude: true,
longitude: true,
trackedEntityInstance: true,
trackedEntity: true,
programStage: true,
},
program: program,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type EventDataValue = {

export type Event = {
eventId: Id;
occurredAt: Id;
occurredAt: string;
dataValues: EventDataValue[];
};

Expand All @@ -22,6 +22,7 @@ export type ProductDataTrackedEntity = {
trackedEntityId: Id;
enrollmentId: Id;
enrollmentStatus: Id;
enrolledAt: string;
events: Event[];
attributes: Attributes[];
};
1 change: 1 addition & 0 deletions src/domain/repositories/MetadataRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CodedRef, NamedRef } from "../entities/Ref";

export interface MetadataRepository {
getDataElementNames(dataElementIds: string[]): FutureData<NamedRef[]>;
getD2Ids(ids: string[]): FutureData<NamedRef[]>;
getOrgUnitsByCode(orgUnitCodes: string[]): FutureData<CodedRef[]>;
getClinicOrLabNames(clinicLabIds: string[]): FutureData<{ id: string; name: string }[]>;
getClinicsAndLabsInOrgUnitId(id: string): FutureData<Id[]>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export interface AMCProductDataRepository {
getProductRegisterProgramMetadata(): FutureData<ProductRegisterProgramMetadata | undefined>;
getProductRegisterAndRawProductConsumptionByProductIds(
orgUnitId: Id,
productIds: string[]
productIds: string[],
period: string
): FutureData<ProductDataTrackedEntity[]>;
getAllProductRegisterAndRawProductConsumptionByPeriod(
orgUnitId: Id,
Expand Down
3 changes: 1 addition & 2 deletions src/domain/usecases/DownloadEmptyTemplateUseCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export class DownloadEmptyTemplateUseCase implements UseCase {
);

const downloadRelationships = moduleName === "AMC" && fileType === "PRODUCT" ? true : false;
const useCodesForMetadata = moduleName === "EGASP" ? true : false;
return this.metadataRepository.getClinicsAndLabsInOrgUnitId(orgUnit).flatMap(clinicsAndLabsOrgUnits => {
const orgUnits = moduleName === "EGASP" ? clinicsAndLabsOrgUnits : [orgUnit];
return Future.fromPromise(
Expand All @@ -32,7 +31,7 @@ export class DownloadEmptyTemplateUseCase implements UseCase {
orgUnits,
populate: false, //Do not download data for empty template
downloadRelationships,
useCodesForMetadata,
useCodesForMetadata: moduleName === "EGASP" || moduleName === "AMC",
})
);
});
Expand Down
3 changes: 1 addition & 2 deletions src/domain/usecases/DownloadPopulatedTemplateUseCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export class DownloadPopulatedTemplateUseCase implements UseCase {
): FutureData<File> {
const startDateOfPeriod = moment(period).startOf("year");
const endDateOfPeriod = moment(period).endOf("year");
const useCodesForMetadata = moduleName === "EGASP" ? true : false;
const downloadRelationships = moduleName === "AMC" && fileType === "PRODUCT" ? true : false;
const filterTEIEnrollmentDate = downloadRelationships;

Expand All @@ -40,7 +39,7 @@ export class DownloadPopulatedTemplateUseCase implements UseCase {
orgUnits: [orgUnit],
populate: true,
downloadRelationships,
useCodesForMetadata,
useCodesForMetadata: moduleName === "EGASP" || moduleName === "AMC",
downloadType,
populateStartDate: startDateOfPeriod,
populateEndDate: endDateOfPeriod,
Expand Down
66 changes: 27 additions & 39 deletions src/domain/usecases/data-entry/ImportBLTemplateEventProgram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,31 +356,25 @@ export const mapToImportSummary = (

const blockingErrorsByGroup = _(blockingErrorList).groupBy("error").value();

//Get list of DataElement Ids in error messages.
const dataElementIds = _.compact(
//Get list of any d2-Ids in error messages.
const d2Ids = _(
Object.entries(blockingErrorsByGroup).map(err => {
const errMsg = err[0];

//Error message type 1 contains regex in format : DataElement `{dataElementId}`
const pattern1 = /(?<=DataElement )`([A-Za-z0-9]{11})`/g;
const dataelementIds1 = pattern1.exec(errMsg);
//Error message type 2 contains regex in format : {id}
const pattern = /([A-Za-z0-9]{11})/g;
const matches = errMsg.match(pattern);

//Error message type 2 contains regex in format : {dataElementId} DataElement
const pattern2 = /([A-Za-z0-9]{11}) DataElement/g;
const dataelementsIds2 = pattern2.exec(errMsg);

//Error message type 3 contains regex in format : `DataElement``{dataElementId}`
const pattern3 = /`(DataElement)` `([A-Za-z0-9]{11})`/g;
const dataelementsIds3 = pattern3.exec(errMsg);

if (dataelementIds1 && dataelementIds1[1]) return dataelementIds1[1];
else if (dataelementsIds2 && dataelementsIds2[1]) return dataelementsIds2[1];
else if (dataelementsIds3 && dataelementsIds3[1]) return dataelementsIds3[2];
if (matches) return matches;
})
);
)
.compact()
.uniq()
.flatten()
.value();

//Get list of DataElement Names in error messages.
return metadataRepository.getDataElementNames(dataElementIds).flatMap(dataElementMap => {
return metadataRepository.getD2Ids(d2Ids).flatMap(d2IdsMap => {
const importSummary: ImportSummary = {
status: result.status === "OK" ? "SUCCESS" : result.status,
importCount: {
Expand All @@ -390,29 +384,23 @@ export const mapToImportSummary = (
deleted: result.stats.deleted,
},
blockingErrors: Object.entries(blockingErrorsByGroup).map(err => {
const dataElementInErrMsg = dataElementIds.filter(de => err[0].includes(de));
const errMsg = err[0];

if (dataElementInErrMsg && dataElementInErrMsg[0] && dataElementInErrMsg.length === 1) {
//There should be only one dataelement id in each errMsg
const parsedErrMsg = d2Ids.reduce((currentMessage, id) => {
return currentMessage.includes(id)
? currentMessage.replace(
new RegExp(id, "g"),
d2IdsMap.find(ref => ref.id === id)?.name ?? id
)
: currentMessage;
}, errMsg);

const dataElementName = dataElementMap.find(de => de.id === dataElementInErrMsg[0]);
//Replace DataElement Ids with DataElement Names in error messages.
const parsedErrMsg = err[0].replace(
dataElementInErrMsg[0],
dataElementName?.name ?? dataElementInErrMsg[0]
);

const lines = err[1].flatMap(a => eventIdLineNoMap?.find(e => e.id === a.eventId)?.lineNo);
console.debug(lines);
return {
error: parsedErrMsg,
count: err[1].length,
lines: _.compact(lines),
};
} else {
const lines = err[1].flatMap(a => eventIdLineNoMap?.find(e => e.id === a.eventId)?.lineNo);
return { error: err[0], count: err[1].length, lines: _.compact(lines) };
}
const lines = err[1].flatMap(a => eventIdLineNoMap?.find(e => e.id === a.eventId)?.lineNo);
return {
error: parsedErrMsg,
count: err[1].length,
lines: _.compact(lines),
};
}),
nonBlockingErrors: nonBlockingErrors ? nonBlockingErrors : [],
importTime: new Date(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ export class CalculateConsumptionDataProductLevelUseCase {
productDataTrackedEntities:
this.amcProductDataRepository.getProductRegisterAndRawProductConsumptionByProductIds(
orgUnitId,
productIds
productIds,
period
),
atcVersionHistory: this.atcRepository.getAtcHistory(),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export class ImportAMCProductLevelData {
}) => {
const currentAttribute = tei.attributeValues.find(at => at.attribute.id === attr.id);
let currentAttrVal = attr.optionSetValue
? attr.optionSet.options.find(option => option.name === currentAttribute?.value)?.code
? attr.optionSet.options.find(option => option.code === currentAttribute?.value)?.code
: currentAttribute?.value;

if (attr.valueType === "BOOLEAN") {
Expand All @@ -180,7 +180,6 @@ export class ImportAMCProductLevelData {
}
return {
attribute: attr.id,
// @ts-ignore
value: currentAttrVal ? currentAttrVal : "",
};
}
Expand All @@ -201,7 +200,6 @@ export class ImportAMCProductLevelData {
)?.value;
return {
dataElement: de.id,
// @ts-ignore
value: currentDataElement ? currentDataElement : "",
};
}
Expand Down

0 comments on commit a993dfd

Please sign in to comment.