Skip to content

Commit

Permalink
Merge branch 'development' into feat/add-children-column & small fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbgomes committed May 2, 2024
2 parents 9b14285 + 562a264 commit de78e22
Show file tree
Hide file tree
Showing 20 changed files with 357 additions and 135 deletions.
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,48 @@
# AMR Surveys
AMR Surveys is an app to manage data input and configuration of surveys for AMR.

## User guide

### Hide elements or sections by survey
1. Based on the rules defined in datastore for each parent survey instance, apply HIDEFIELD/HIDESECTION rule for each child form.
2. refactor code in src/domain/usecases/GetSurveyUseCase.ts to break up into functions.

NOTE : datastore structure for survey rules
```
"rulesBySurvey": [
{
"surveyId": "p91asa2vebZ",
"surveyRules": [
{
"formId": "mesnCzaLc7u",
"rules": [
{
"id": "1",
"toHide": [
"SCYImStXDHM"
],
"type": "HIDEFIELD"
},
{
"id": "2",
"toHide": [
"mIHOCwjHtyu"
],
"type": "HIDESECTION"
}
]
}
]
}
],
```
### :video_camera: Screenshots/Screen capture

[video](https://github.com/EyeSeeTea/amr-surveys/assets/83749675/1c200566-dabf-4e43-aa9a-b40910448c93)

# Developer guide

## Setup

```
Expand Down Expand Up @@ -75,3 +120,5 @@ Check the example script, entry `"script-example"`in `package.json`->scripts and
- Requests to DHIS2 will be transparently proxied (see `vite.config.ts` -> `server.proxy`) from `http://localhost:8081/dhis2/xyz` to `${VITE_DHIS2_BASE_URL}/xyz`. This prevents CORS and cross-domain problems.

- You can use `.env` variables within the React app: `const value = import.meta.env.NAME;`


6 changes: 6 additions & 0 deletions src/data/entities/D2Program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ export interface TrackedEntityAttibute {
optionSet?: { id: string };
value?: string;
}

export interface ProgramTrackedEntityAttibute {
trackedEntityAttribute: { id: string };
sortOrder: number;
}
export interface ProgramMetadata {
programs: Program[];
programStageDataElements: ProgramStageDataElement[];
Expand All @@ -81,6 +86,7 @@ export interface ProgramMetadata {
optionSets: OptionSet[];
options: Option[];
trackedEntityAttributes?: TrackedEntityAttibute[];
programTrackedEntityAttributes: ProgramTrackedEntityAttibute[];
programStages: ProgramStage[];
programRules: D2ProgramRule[];
programRuleVariables: D2ProgramRuleVariable[];
Expand Down
4 changes: 4 additions & 0 deletions src/data/entities/D2Survey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export const AMR_SURVEYS_PREVALENCE_TEA_SURVEY_ID_CRL = "b9dqKVYm4Xn";
export const AMR_SURVEYS_PREVALENCE_TEA_SURVEY_ID_PIS = "w74wn7Wz2hV";
export const AMR_SURVEYS_PREVALENCE_TEA_SURVEY_ID_SRL = "mcY57Zn7FFl";
export const AMR_SURVEYS_PREVALENCE_TEA_SURVEY_ID_CRF = "tlRPoWumrSa";
export const AMR_SURVEYS_PREVALENCE_TEA_UNIQUE_PATIENT_ID = "yEkJlUFeJdP";

export const PREVALENCE_START_DATE_DATAELEMENT_ID = "xlvLBmg9Mkg";
export const PREVALENCE_SURVEY_COMPLETED_DATAELEMENT_ID = "xiFcLr23IbW";
export const PREVALENCE_SURVEY_NAME_DATAELEMENT_ID = "HXnhZ8rsDts";
Expand All @@ -63,6 +65,7 @@ type SURVEY_DATA_ELEMENT_KEYS =
| "wardCode"
| "patientCode"
| "parentWardRegisterId"
| "uniqueSurveyPatientId"
| "astGuideline"
| "customAstGuideline";
interface SurveyKeyDataElementMapType {
Expand Down Expand Up @@ -95,6 +98,7 @@ export const keyToDataElementMap: SurveyKeyDataElementMapType[] = [
{ key: "wardCode", dataElements: [SURVEY_WARD_CODE_DATAELEMENT_ID] },
{ key: "patientCode", dataElements: [SURVEY_PATIENT_CODE_DATAELEMENT_ID] },
{ key: "parentWardRegisterId", dataElements: [WARD_ID_DATAELEMENT_ID] },
{ key: "uniqueSurveyPatientId", dataElements: [AMR_SURVEYS_PREVALENCE_TEA_UNIQUE_PATIENT_ID] },
{ key: "astGuideline", dataElements: [AMR_SURVEYS_PREVALENCE_DEA_AST_GUIDELINES] },
{ key: "customAstGuideline", dataElements: [AMR_SURVEYS_PREVALENCE_DEA_CUSTOM_AST_GUIDE] },
];
86 changes: 66 additions & 20 deletions src/data/repositories/SurveyFormD2Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
SURVEY_NAME_DATAELEMENT_ID,
PREVALENCE_SURVEY_NAME_DATAELEMENT_ID,
} from "../entities/D2Survey";
import { ProgramDataElement, ProgramMetadata } from "../entities/D2Program";
import { ProgramMetadata } from "../entities/D2Program";
import {
mapProgramToQuestionnaire,
mapQuestionnaireToEvent,
Expand All @@ -35,6 +35,7 @@ import { mapEventToSurvey, mapTrackedEntityToSurvey } from "../utils/surveyListM
import { Questionnaire } from "../../domain/entities/Questionnaire/Questionnaire";
import { getSurveyChildCount } from "../utils/surveyCountHelper";

const OU_CHUNK_SIZE = 500;
export class SurveyD2Repository implements SurveyRepository {
constructor(private api: D2Api) {}

Expand All @@ -46,24 +47,28 @@ export class SurveyD2Repository implements SurveyRepository {
return apiToFuture(
this.api.request<ProgramMetadata>({
method: "get",
url: `/programs/${programId}/metadata.json?fields=programs,dataElements,programStageDataElements,programStageSections,trackedEntityAttributes,programStages,programRules,programRuleVariables,programRuleActions`,
url: `/programs/${programId}/metadata.json?fields=programs,dataElements,programStageDataElements.dataElement,programStageSections,programTrackedEntityAttributes,trackedEntityAttributes,programStages,programRules,programRuleVariables,programRuleActions`,
})
).flatMap(resp => {
if (resp.programs[0]) {
const programDataElements = resp.programStageDataElements.map(
psde => psde.dataElement
);

const dataElementsWithSortOrder: ProgramDataElement[] = resp.dataElements.map(
de => {
return {
...de,
sortOrder: resp.programStageDataElements.find(
psde => psde.dataElement.id === de.id
)?.sortOrder,
};
}
);
const sortedTrackedentityAttr = resp.programTrackedEntityAttributes
? _(
_(resp.programTrackedEntityAttributes)
.sortBy(te => te.sortOrder)
.value()
.map(pste =>
resp.trackedEntityAttributes?.find(
te => te.id === pste.trackedEntityAttribute.id
)
)
)
.compact()
.value()
: resp.trackedEntityAttributes;

//If event specified,populate the form
if (eventId) {
Expand All @@ -78,11 +83,11 @@ export class SurveyD2Repository implements SurveyRepository {
undefined,
trackedEntity,
programDataElements,
dataElementsWithSortOrder,
resp.dataElements,
resp.options,
resp.programStages,
resp.programStageSections,
resp.trackedEntityAttributes,
sortedTrackedentityAttr,
resp.programRules,
resp.programRuleVariables,
resp.programRuleActions
Expand All @@ -104,11 +109,11 @@ export class SurveyD2Repository implements SurveyRepository {
event,
undefined,
programDataElements,
dataElementsWithSortOrder,
resp.dataElements,
resp.options,
resp.programStages,
resp.programStageSections,
resp.trackedEntityAttributes,
sortedTrackedentityAttr,
resp.programRules,
resp.programRuleVariables,
resp.programRuleActions
Expand All @@ -128,11 +133,11 @@ export class SurveyD2Repository implements SurveyRepository {
undefined,
undefined,
programDataElements,
dataElementsWithSortOrder,
resp.dataElements,
resp.options,
resp.programStages,
resp.programStageSections,
resp.trackedEntityAttributes,
sortedTrackedentityAttr,
resp.programRules,
resp.programRuleVariables,
resp.programRuleActions
Expand Down Expand Up @@ -187,15 +192,27 @@ export class SurveyD2Repository implements SurveyRepository {
getSurveys(
surveyFormType: SURVEY_FORM_TYPES,
programId: Id,
orgUnitId: Id
orgUnitId: Id,
chunked = false
): FutureData<Survey[]> {
return isTrackerProgram(programId)
? this.getTrackerProgramSurveys(surveyFormType, programId, orgUnitId)
? this.getTrackerProgramSurveys(surveyFormType, programId, orgUnitId, chunked)
: this.getEventProgramSurveys(surveyFormType, programId, orgUnitId);
}

//Currently tracker programs are only in Prevalence module
private getTrackerProgramSurveys(
surveyFormType: SURVEY_FORM_TYPES,
programId: Id,
orgUnitId: Id,
chunked = false
): FutureData<Survey[]> {
return chunked
? this.getTrackerProgramSurveysChunked(surveyFormType, programId, orgUnitId)
: this.getTrackerProgramSurveysUnchunked(surveyFormType, programId, orgUnitId);
}

private getTrackerProgramSurveysUnchunked(
surveyFormType: SURVEY_FORM_TYPES,
programId: Id,
orgUnitId: Id
Expand All @@ -218,6 +235,35 @@ export class SurveyD2Repository implements SurveyRepository {
});
}

private getTrackerProgramSurveysChunked(
surveyFormType: SURVEY_FORM_TYPES,
programId: Id,
orgUnits: string
): FutureData<Survey[]> {
const orgUnitIds = orgUnits.split(";");
const chunkedOUs = _(orgUnitIds).chunk(OU_CHUNK_SIZE).value();

return Future.sequential(
chunkedOUs.flatMap(ouChunk => {
return apiToFuture(
this.api.tracker.trackedEntities.get({
fields: {
attributes: true,
enrollments: true,
trackedEntity: true,
orgUnit: true,
},
program: programId,
orgUnit: ouChunk.join(";"),
})
).flatMap((trackedEntities: TrackedEntitiesGetResponse) => {
const surveys = mapTrackedEntityToSurvey(trackedEntities, surveyFormType);
return Future.success(surveys);
});
})
).flatMap(listOfSurveys => Future.success(_(listOfSurveys).flatten().value()));
}

private getEventProgramSurveys(
surveyFormType: SURVEY_FORM_TYPES,
programId: Id,
Expand Down
3 changes: 0 additions & 3 deletions src/data/utils/questionHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,6 @@ export const mapProgramDataElementToQuestions = (
})
)
.compact()
.sortBy(q => q.sortOrder)
.value();

return questions;
Expand Down Expand Up @@ -320,7 +319,6 @@ export const mapRepeatedStageEventToQuestions = (
})
)
.compact()
.sortBy(q => q.sortOrder)
.value();

return questions;
Expand Down Expand Up @@ -363,7 +361,6 @@ export const mapTrackedAttributesToQuestions = (
})
)
.compact()
.sortBy(q => q.sortOrder)
.value();

return questions;
Expand Down
7 changes: 7 additions & 0 deletions src/data/utils/surveyListMappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
PREVALENCE_SURVEY_FORM_ID,
SURVEY_ID_FACILITY_LEVEL_DATAELEMENT_ID,
keyToDataElementMap,
AMR_SURVEYS_PREVALENCE_TEA_UNIQUE_PATIENT_ID,
} from "../entities/D2Survey";
import { D2TrackerEvent } from "@eyeseetea/d2-api/api/trackerEvents";
import { getSurveyNameBySurveyFormType } from "./surveyProgramHelper";
Expand All @@ -31,6 +32,11 @@ export const mapTrackedEntityToSurvey = (
attribute.attribute === AMR_SURVEYS_PREVALENCE_TEA_SURVEY_ID_CRF
)?.value ?? "";

const uniqueSurveyPatientId =
trackedEntity.attributes?.find(
attribute => attribute.attribute === AMR_SURVEYS_PREVALENCE_TEA_UNIQUE_PATIENT_ID
)?.value ?? "";

const survey: Survey = {
id: trackedEntity.trackedEntity ?? "",
name: trackedEntity.trackedEntity ?? "",
Expand All @@ -49,6 +55,7 @@ export const mapTrackedEntityToSurvey = (
parentWardRegisterId: undefined,
surveyFormType: surveyFormType,
childCount: undefined,
uniqueSurveyPatientId: uniqueSurveyPatientId,
};
return survey;
});
Expand Down
22 changes: 22 additions & 0 deletions src/domain/entities/Questionnaire/Questionnaire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ export class Questionnaire {
allQsInQuestionnaire
);

const isEntityQuestionUpdated = questionnaire.entity?.questions.find(
question => question.id === updatedQuestion.id
);

const updatedRulesQuestionnaire = Questionnaire.create({
...questionnaire.data,
stages: questionnaire.stages.map(stage => {
Expand All @@ -251,6 +255,10 @@ export class Questionnaire {
),
};
}),
entity:
isEntityQuestionUpdated && questionnaire.entity
? this.updateEntityQuestion(questionnaire.entity, updatedQuestion)
: questionnaire.entity,
});

return this.handleSpeciesAntibioticQuestionUpdate(
Expand Down Expand Up @@ -444,4 +452,18 @@ export class Questionnaire {
const updatedStages = questionnaire.stages.filter(stage => stage.id !== stageId);
return Questionnaire.updateQuestionnaireStages(questionnaire, updatedStages);
}

static updateEntityQuestion(
questionnaireEntity: QuestionnaireEntity,
updatedQuestion: Question
): QuestionnaireEntity | undefined {
const updatedEntityQuestions = questionnaireEntity.questions.map(question => {
return question.id === updatedQuestion.id ? updatedQuestion : question;
});

return {
...questionnaireEntity,
questions: updatedEntityQuestions,
};
}
}
1 change: 1 addition & 0 deletions src/domain/entities/Survey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ export interface Survey extends SurveyBase {
surveyFormType: SURVEY_FORM_TYPES;
parentWardRegisterId?: Id;
childCount?: number | ProgramOptionCountMap;
uniqueSurveyPatientId?: string;
}
3 changes: 2 additions & 1 deletion src/domain/repositories/SurveyRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export interface SurveyRepository {
getSurveys(
surveyFormType: SURVEY_FORM_TYPES,
programId: Id,
orgUnitId: Id
orgUnitId: Id,
chunked: boolean
): FutureData<Survey[]>;
getPopulatedSurveyById(
eventId: Id,
Expand Down
Loading

0 comments on commit de78e22

Please sign in to comment.