Skip to content

Commit

Permalink
chore: merge with development
Browse files Browse the repository at this point in the history
  • Loading branch information
9sneha-n committed Nov 19, 2024
2 parents caef415 + b892c5a commit 0468933
Showing 23 changed files with 613 additions and 501 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@
"@dhis2/d2-i18n": "1.1.0",
"@dhis2/d2-i18n-extract": "1.0.8",
"@dhis2/d2-i18n-generate": "1.2.0",
"@dhis2/expression-parser": "^1.1.0",
"@dhis2/ui": "6.12.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
4 changes: 2 additions & 2 deletions src/CompositionRoot.ts
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ import { GetAllSurveysUseCase } from "./domain/usecases/GetAllSurveysUseCase";
import { PaginatedSurveyRepository } from "./domain/repositories/PaginatedSurveyRepository";
import { PaginatedSurveyTestRepository } from "./data/repositories/testRepositories/PaginatedSurveyTestRepository";
import { PaginatedSurveyD2Repository } from "./data/repositories/PaginatedSurveyD2Repository";
import { GetUserAccessibleOUByLevel } from "./domain/usecases/GetUserAccessibleOUByLevel";
import { GetAccessibleHospitals } from "./domain/usecases/GetAccessibleHospitals";
import { GetChildCountUseCase } from "./domain/usecases/GetChildCountUseCase";
import { ApplyInitialRulesToSurveyUseCase } from "./domain/usecases/ApplyInitialRulesToSurveyUseCase";
import { ASTGuidelinesRepository } from "./domain/repositories/ASTGuidelinesRepository";
@@ -62,7 +62,7 @@ function getCompositionRoot(repositories: Repositories) {
},
users: {
getCurrent: new GetCurrentUserUseCase(repositories.usersRepository),
getAccessibleOUByLevel: new GetUserAccessibleOUByLevel(repositories.usersRepository),
getAccessibleHospitals: new GetAccessibleHospitals(repositories.usersRepository),
savePassword: new SavePasswordUseCase(repositories.usersRepository),
saveKeyUiLocale: new SaveKeyUiLocaleUseCase(repositories.usersRepository),
saveKeyDbLocale: new SaveKeyDbLocaleUseCase(repositories.usersRepository),
108 changes: 108 additions & 0 deletions src/data/entities/D2ExpressionParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import * as xp from "@dhis2/expression-parser";
import _c from "../../domain/entities/generic/Collection";
import { Either } from "../../domain/entities/generic/Either";

export class D2ExpressionParser {
public evaluateRuleEngineCondition(
ruleCondition: string,
variableValues: Map<ProgramRuleVariableName, ProgramRuleVariableValue>
): Either<Error, boolean> {
try {
const expressionParser = new xp.ExpressionJs(
ruleCondition,
xp.ExpressionMode.RULE_ENGINE_CONDITION
);

const ruleVariables = this.mapProgramRuleVariables(expressionParser, variableValues);
const genericVariables = this.mapProgramVariables(expressionParser);
const variables = new Map([...ruleVariables, ...genericVariables]);

const expressionData = new xp.ExpressionDataJs(variables);

const parsedResult: boolean = expressionParser.evaluate(
() => console.debug(""),
expressionData
);

return Either.success(parsedResult);
} catch (error) {
return Either.error(error as Error);
}
}

private getVariableValueByType = (
type: ProgramRuleVariableType,
stringValue: xp.Nullable<string>
): xp.VariableValueJs => {
const valueType = VariableValueTypeMap[type];
return new xp.VariableValueJs(valueType, stringValue, [], null);
};

private mapProgramRuleVariables(
expressionParser: xp.ExpressionJs,
ruleVariables: Map<string, ProgramRuleVariableValue>
) {
const programRuleVariables = expressionParser.collectProgramRuleVariableNames();
const variablesValueMap = programRuleVariables.map(programRuleVariable => {
const currentProgramRuleVariableValue = ruleVariables.get(programRuleVariable);

if (!currentProgramRuleVariableValue)
return {
programRuleVariable: programRuleVariable,
value: new xp.VariableValueJs(xp.ValueType.STRING, null, [], null),
};

const variableValue = this.getVariableValueByType(
currentProgramRuleVariableValue.type,
currentProgramRuleVariableValue.value === ""
? null
: currentProgramRuleVariableValue.value
);

return {
programRuleVariable: programRuleVariable,
value: variableValue,
};
});

return new Map(
variablesValueMap.map(variable => [variable.programRuleVariable, variable.value])
);
}

private mapProgramVariables(expressionParser: xp.ExpressionJs) {
const programVariables = expressionParser.collectProgramVariablesNames();
const programVariableValues = programVariables.map(programVariable => {
switch (programVariable) {
case "current_date": {
const currentISODate = new Date().toISOString().split("T")[0];
const currentDate = this.getVariableValueByType("date", currentISODate);
return { programVariable: programVariable, value: currentDate };
}
default:
throw new Error(
`Unhandled Program variable of type : ${programVariable}. Please contact developer`
);
}
});

const programVariablesMap = new Map(
programVariableValues.map(variable => [variable.programVariable, variable.value])
);

return programVariablesMap;
}
}
export type ProgramRuleVariableType = "text" | "number" | "date" | "boolean";
export type ProgramRuleVariableName = string;
export type ProgramRuleVariableValue = {
type: ProgramRuleVariableType;
value: string;
};

const VariableValueTypeMap: Record<ProgramRuleVariableType, xp.ValueType> = {
text: xp.ValueType.STRING,
boolean: xp.ValueType.BOOLEAN,
date: xp.ValueType.DATE,
number: xp.ValueType.NUMBER,
};
21 changes: 8 additions & 13 deletions src/data/repositories/SurveyFormD2Repository.ts
Original file line number Diff line number Diff line change
@@ -200,28 +200,23 @@ export class SurveyD2Repository implements SurveyRepository {
: mapQuestionnaireToEvent(questionnaire, orgUnitId, programId, this.api, eventId);

return $payload.flatMap(payload => {
return apiToFuture(
this.api.tracker.postAsync({ importStrategy: action }, payload)
).flatMap(response => {
return apiToFuture(
// eslint-disable-next-line testing-library/await-async-utils
this.api.system.waitFor("TRACKER_IMPORT_JOB", response.response.id)
).flatMap(result => {
if (result && result.status !== "ERROR") {
return apiToFuture(this.api.tracker.post({ importStrategy: action }, payload)).flatMap(
response => {
if (response && response.status !== "ERROR") {
//return the saved survey id.

const surveyId = isTrackerProgram(programId)
? result.bundleReport?.typeReportMap?.TRACKED_ENTITY?.objectReports[0]
? response.bundleReport?.typeReportMap?.TRACKED_ENTITY?.objectReports[0]
?.uid
: result.bundleReport?.typeReportMap?.EVENT?.objectReports[0]?.uid;
: response.bundleReport?.typeReportMap?.EVENT?.objectReports[0]?.uid;
return Future.success(surveyId);
} else {
return this.getErrorMessageWithNames(result).flatMap(errorMessage => {
return this.getErrorMessageWithNames(response).flatMap(errorMessage => {
return Future.error(new Error(`Error: ${errorMessage} `));
});
}
});
});
}
);
});
}

146 changes: 77 additions & 69 deletions src/data/repositories/UserD2Repository.ts
Original file line number Diff line number Diff line change
@@ -5,7 +5,8 @@ import { D2Api, MetadataPick } from "../../types/d2-api";
import { apiToFuture, FutureData } from "../api-futures";
import _ from "../../domain/entities/generic/Collection";
import { OrgUnit } from "../../domain/entities/OrgUnit";
import { NamedRef } from "../../domain/entities/Ref";
import { Id, NamedRef } from "../../domain/entities/Ref";
import { PPS_HOSPITAL_FORM_ID, PREVALENCE_FACILITY_LEVEL_FORM_ID } from "../entities/D2Survey";

const NA_OU_ID = "zXAaAXzwt4M";
export const COUNTRY_OU_LEVEL = 3;
@@ -24,26 +25,6 @@ export class UserD2Repository implements UserRepository {
});
}

public getCurrentOUByLevel(
organisationUnits: NamedRef[],
dataViewOrganisationUnits: NamedRef[]
): FutureData<OrgUnitAccess[]> {
const hospital$ = this.getAllOrgUnitsByLevels(
organisationUnits,
dataViewOrganisationUnits,
HOSPITAL_OU_LEVELS
);

return hospital$.flatMap(hospitals => {
const currentAccessibleHospitals = this.mapUserOrgUnitsAccess(
hospitals.userOrgUnits,
hospitals.userDataViewOrgUnits
);

return Future.success(currentAccessibleHospitals);
});
}

public savePassword(password: string): FutureData<string> {
return apiToFuture(
this.api.currentUser.get({
@@ -161,54 +142,6 @@ export class UserD2Repository implements UserRepository {
});
};

//TO DO : TEMP FIX for multi level hospitals.
getAllOrgUnitsByLevels = (
organisationUnits: NamedRef[],
dataViewOrganisationUnits: NamedRef[],
level: number[]
): FutureData<{ userOrgUnits: OrgUnit[]; userDataViewOrgUnits: OrgUnit[] }> => {
//1. Get all OUs
return apiToFuture(
this.api.models.organisationUnits.get({
filter: {
level: { in: [level.toString()] },
},
fields: {
id: true,
shortName: true,
path: true,
level: true,
},
paging: false,
})
).flatMap(res => {
const allLevelOUs: OrgUnit[] = _(
res.objects.map(ou => {
if (!ou.path.includes(NA_OU_ID))
return {
id: ou.id,
shortName: ou.shortName,

path: ou.path,
};
})
)
.compact()
.value();

//2. Filter OUs which the user has access to.
//If the user has access to any parent of the OU, then they have access to the OU.
//So, check the path to see if it contains any OU user has access to.
const userOrgUnits = allLevelOUs.filter(levelOU =>
organisationUnits.some(userOU => levelOU.path.includes(userOU.id))
);
const userDataViewOrgUnits = allLevelOUs.filter(levelOU =>
dataViewOrganisationUnits.some(userOU => levelOU.path.includes(userOU.id))
);

return Future.success({ userOrgUnits, userDataViewOrgUnits });
});
};
mapUserOrgUnitsAccess = (
organisationUnits: OrgUnit[],
dataViewOrganisationUnits: OrgUnit[]
@@ -251,6 +184,81 @@ export class UserD2Repository implements UserRepository {
}
});
}

public getPrevalenceAccessibleHospitals(
organisationUnits: NamedRef[],
dataViewOrganisationUnits: NamedRef[]
): FutureData<OrgUnitAccess[]> {
return this.getAccessibleHospitals(
organisationUnits,
dataViewOrganisationUnits,
PREVALENCE_FACILITY_LEVEL_FORM_ID
);
}

public getPPSAccessibleHospitals(
organisationUnits: NamedRef[],
dataViewOrganisationUnits: NamedRef[]
): FutureData<OrgUnitAccess[]> {
return this.getAccessibleHospitals(
organisationUnits,
dataViewOrganisationUnits,
PPS_HOSPITAL_FORM_ID
);
}

private getAccessibleHospitals(
organisationUnits: NamedRef[],
dataViewOrganisationUnits: NamedRef[],
programId: Id
): FutureData<OrgUnitAccess[]> {
return apiToFuture(
this.api.models.programs.get({
fields: {
organisationUnits: {
id: true,
shortName: true,
path: true,
level: true,
},
},
filter: { id: { eq: programId } },
})
).flatMap(res => {
const programOUs = res.objects[0]?.organisationUnits;
if (!programOUs) return Future.success([]);

const allLevelOUs: OrgUnit[] = _(
programOUs?.map(ou => {
if (!ou.path.includes(NA_OU_ID))
return {
id: ou.id,
shortName: ou.shortName,
path: ou.path,
};
})
)
.compact()
.value();

//Filter OUs which the user has access to.
//If the user has access to any parent of the OU, then they have access to the OU.
//So, check the path to see if it contains any OU user has access to.
const userOrgUnits = allLevelOUs.filter(levelOU =>
organisationUnits.some(userOU => levelOU.path.includes(userOU.id))
);
const userDataViewOrgUnits = allLevelOUs.filter(levelOU =>
dataViewOrganisationUnits.some(userOU => levelOU.path.includes(userOU.id))
);

const prevalenceAccessibleHospitals = this.mapUserOrgUnitsAccess(
userOrgUnits,
userDataViewOrgUnits
);

return Future.success(prevalenceAccessibleHospitals);
});
}
}

const userFields = {
Original file line number Diff line number Diff line change
@@ -6,12 +6,19 @@ import { FutureData } from "../../api-futures";
import { NamedRef } from "../../../domain/entities/Ref";

export class NonAdminUserTestRepository implements UserRepository {
getCurrentOUByLevel(
getPPSAccessibleHospitals(
_organisationUnits: NamedRef[],
_dataViewOrganisationUnits: NamedRef[]
): FutureData<OrgUnitAccess[]> {
return Future.success([]);
}
getPrevalenceAccessibleHospitals(
_organisationUnits: NamedRef[],
_dataViewOrganisationUnits: NamedRef[]
): FutureData<OrgUnitAccess[]> {
return Future.success([]);
}

saveLocale(isUiLocale: boolean, locale: string): FutureData<void> {
if (locale) return Future.success(undefined);
else
9 changes: 8 additions & 1 deletion src/data/repositories/testRepositories/UserTestRepository.ts
Original file line number Diff line number Diff line change
@@ -6,12 +6,19 @@ import { FutureData } from "../../api-futures";
import { NamedRef } from "../../../domain/entities/Ref";

export class UserTestRepository implements UserRepository {
getCurrentOUByLevel(
getPPSAccessibleHospitals(
_organisationUnits: NamedRef[],
_dataViewOrganisationUnits: NamedRef[]
): FutureData<OrgUnitAccess[]> {
return Future.success([]);
}
getPrevalenceAccessibleHospitals(
_organisationUnits: NamedRef[],
_dataViewOrganisationUnits: NamedRef[]
): FutureData<OrgUnitAccess[]> {
return Future.success([]);
}

saveLocale(isUiLocale: boolean, locale: string): FutureData<void> {
if (locale) return Future.success(undefined);
else
Loading

0 comments on commit 0468933

Please sign in to comment.