Skip to content

Commit

Permalink
Merge pull request #484 from EyeSeeTea/development
Browse files Browse the repository at this point in the history
Release 1.4
  • Loading branch information
adrianq authored Feb 20, 2023
2 parents 455fb27 + ea579af commit a9e80af
Show file tree
Hide file tree
Showing 17 changed files with 329 additions and 49 deletions.
10 changes: 8 additions & 2 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2022-09-15T15:24:44.935Z\n"
"PO-Revision-Date: 2022-09-15T15:24:44.935Z\n"
"POT-Creation-Date: 2022-10-24T06:53:19.502Z\n"
"PO-Revision-Date: 2022-10-24T06:53:19.502Z\n"

msgid "Name"
msgstr ""
Expand Down Expand Up @@ -908,6 +908,12 @@ msgstr ""
msgid "Create Project"
msgstr ""

msgid "Last Updated (metadata)"
msgstr ""

msgid "Last Updated (data)"
msgstr ""

msgid "Created By"
msgstr ""

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "data-management-app",
"description": "DHIS2 Data Management App",
"version": "1.3.1",
"version": "1.4.0",
"license": "GPL-3.0",
"author": "EyeSeeTea team",
"homepage": ".",
Expand Down
12 changes: 9 additions & 3 deletions src/components/data-entry/DataSetStateButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface DataSetStateButtonProps {

const DataSetStateButton: React.FunctionComponent<DataSetStateButtonProps> = props => {
const [isActive, setActive] = React.useState(false);
const { api, currentUser, isTest } = useAppContext();
const { api, currentUser, isTest, appConfig } = useAppContext();
const { period, dataSetType, dataSet, project, onChange, validation } = props;
const projectDataSet = project.getProjectDataSet(dataSet);
const showErrorAndSetInactive = useSnackbarOnError(() => setActive(false));
Expand Down Expand Up @@ -54,7 +54,13 @@ const DataSetStateButton: React.FunctionComponent<DataSetStateButtonProps> = pro

const notifyUsers = React.useCallback(async () => {
try {
const notificator = new ProjectNotification(api, project, currentUser, isTest);
const notificator = new ProjectNotification(
api,
appConfig,
project,
currentUser,
isTest
);
const emailSent = await notificator.notifyForDataReview(
period,
dataSet.id,
Expand All @@ -68,7 +74,7 @@ const DataSetStateButton: React.FunctionComponent<DataSetStateButtonProps> = pro
} catch (err: any) {
snackbar.error(err.message);
}
}, [api, project, currentUser, isTest, period, dataSet.id, dataSetType, snackbar]);
}, [api, project, currentUser, isTest, period, dataSet.id, dataSetType, snackbar, appConfig]);

const reopenConfirmation = useConfirmation({
title: i18n.t("Reopen data set"),
Expand Down
6 changes: 4 additions & 2 deletions src/components/data-entry/validation-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ValidationResult } from "../../models/validators/validator-common";
import { useSnackbar } from "@eyeseetea/d2-ui-components";
import i18n from "../../locales";
import { usePageExitConfirmation } from "../../utils/hooks";
import ProjectDb from "../../models/ProjectDb";

export interface UseValidationResponse {
result: ValidationResult;
Expand Down Expand Up @@ -67,11 +68,12 @@ export function useValidation(hookOptions: {
});
}
case "dataValueSaved": {
// Executed after data value successfully saved
const projectDb = new ProjectDb(project);
projectDb.updateLastUpdatedData(api, config, project);
}
}
},
[validator, snackbar]
[validator, snackbar, project, config, api]
);

const [validationResult, setValidationResult] = React.useState<ValidationResult>({});
Expand Down
21 changes: 16 additions & 5 deletions src/components/steps/save/SaveStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,15 @@ const SaveStep: React.FC<StepProps> = ({ project, onCancel, action }) => {
await save();
if (!existingData) return;

const notificator = new ProjectNotification(api, project, currentUser, isTest);
const notificator = new ProjectNotification(
api,
appConfig,
project,
currentUser,
isTest
);
await notificator.sendMessageForIndicatorsRemoval({
message,
recipients: appConfig.app.notifyEmailOnProjectSave,
currentUser,
existingData,
});
Expand All @@ -62,6 +67,7 @@ const SaveStep: React.FC<StepProps> = ({ project, onCancel, action }) => {
const validation = await project.validate();
const error = _(validation).values().flatten().join("\n");
if (error) {
loading.hide();
snackbar.error(error);
return;
}
Expand Down Expand Up @@ -222,12 +228,17 @@ function useSave(project: Project, action: StepProps["action"], projectInfo: Rea
loading.show(true, i18n.t("Saving Project"));
setSaving(true);
const { payload, response, project: projectSaved } = await project.save();
const recipients = appConfig.app.notifyEmailOnProjectSave;
setSaving(false);

if (response && response.status === "OK") {
const notificator = new ProjectNotification(api, projectSaved, currentUser, isTest);
notificator.notifyOnProjectSave(projectInfo, recipients, action);
const notificator = new ProjectNotification(
api,
appConfig,
projectSaved,
currentUser,
isTest
);
notificator.notifyOnProjectSave(projectInfo, action);
const baseMsg =
action === "create" ? i18n.t("Project created") : i18n.t("Project updated");
const msg = `${baseMsg}: ${projectSaved.name}`;
Expand Down
45 changes: 45 additions & 0 deletions src/migrations/tasks/09.add-last-updated-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { D2Api, Id } from "../../types/d2-api";
import { Debug, Migration } from "../types";
import { post } from "./common";
import { getUid } from "../../utils/dhis2";

class AddLastUpdatedDataMigration {
constructor(private api: D2Api, private debug: Debug) {}

private async post<Payload>(payload: Payload) {
await post(this.api, this.debug, payload);
}

async execute() {
await this.createAttributeForOrgUnitLastUpdated();
}

async createAttributeForOrgUnitLastUpdated() {
this.debug(
"Create the attribute metadata to store last update data for organisation units"
);
return this.post({
attributes: [
{
id: getId("attributes", "last-updated-org-unit"),
name: "Last Updated Data",
code: "DM_LAST_UPDATED_DATA",
valueType: "DATETIME",
dataSetAttribute: true,
},
],
});
}
}

function getId(model: string, key: string): Id {
return getUid(model + "-", key);
}

const migration: Migration = {
name: "Create the attribute metadata to store last update data for organisation units",
migrate: async (api: D2Api, debug: Debug) =>
new AddLastUpdatedDataMigration(api, debug).execute(),
};

export default migration;
1 change: 1 addition & 0 deletions src/migrations/tasks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export async function getMigrationTasks(): Promise<MigrationTasks> {
migration(6, (await import("./06.add-benefits-disaggregation")).default),
migration(7, (await import("./07.update-to-v2.36")).default),
migration(8, (await import("./08.set-integer-people-dataelements")).default),
migration(9, (await import("./09.add-last-updated-data")).default),
];
}
1 change: 1 addition & 0 deletions src/models/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const baseConfig = {
pairedDataElement: "DM_PAIRED_DE",
extraDataElement: "DM_EXTRA_INFO_DE",
createdByApp: "DM_CREATED_BY_DATA_MANAGEMENT",
lastUpdatedData: "DM_LAST_UPDATED_DATA",
orgUnitProject: "DM_ORGUNIT_PROJECT_ID",
projectDashboard: "DM_PROJECT_DASHBOARD_ID",
awardNumberDashboard: "DM_AWARD_NUMBER_DASHBOARD_ID",
Expand Down
106 changes: 98 additions & 8 deletions src/models/ProjectDb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,40 @@ export default class ProjectDb {
const cleanOrgUnits = (orgUnits: Array<{ id?: Id }> | undefined) =>
(orgUnits || []).filter(ou => ou.id && orgUnitIds.has(ou.id));

const existingMetadata = await api.metadata
.get({
organisationUnitGroupSets: {
fields: { $owner: true },
filter: {
id: { in: _.compact(metadata.organisationUnitGroupSets.map(o => o.id)) },
},
},
})
.getData();

/* Remove non-existing org units from references */

const metadata2 = {
...metadata,
organisationUnitGroups: metadata.organisationUnitGroups.map(oug => {
return { ...oug, organisationUnits: cleanOrgUnits(oug.organisationUnits) };
}),
organisationUnitGroupSets: metadata.organisationUnitGroupSets.map(ougSet => {
return {
...ougSet,
organisationUnitGroups: _.intersectionBy(
_.unionBy(
existingMetadata.organisationUnitGroupSets.find(
ougs_ => ougs_.id === ougSet.id
)?.organisationUnitGroups || [],
metadata.organisationUnitGroups.map(oug => ({ id: oug.id })),
o => o.id
),
(ougSet.organisationUnitGroups || []).map(oug => ({ id: oug.id })),
o => o.id
),
};
}),
visualizations: metadata.visualizations.map(visualization => {
return {
...visualization,
Expand Down Expand Up @@ -211,6 +239,17 @@ export default class ProjectDb {
{ value: "true", attribute: { id: config.attributes.createdByApp.id } },
];

const metadata = await this.api.metadata
.get({
organisationUnits: {
fields: { attributeValues: { attribute: { id: true }, value: true } },
filter: { id: { eq: project.id } },
},
})
.getData();

const existingOrgUnit = metadata.organisationUnits[0];

const orgUnit = {
id: project.id,
created: project.created ? toISOString(project.created) : undefined,
Expand All @@ -224,14 +263,26 @@ export default class ProjectDb {
...getOrgUnitDatesFromProject(startDate, endDate),
openingDate: toISOString(startDate.clone().subtract(1, "month")),
closedDate: toISOString(endDate.clone().add(1, "month").endOf("month")),
attributeValues: baseAttributeValues,
attributeValues: existingOrgUnit?.attributeValues || [],
// No sharing, permissions through user.organisationUnits
};

const projectWithOrgUnit = project.set("orgUnit", orgUnit);

const dataSetActualId = getUid("dataSet", project.uid + "ACTUAL");
const dataSetsMetadata = await this.api.metadata
.get({
dataSets: {
fields: { $owner: true },
filter: { id: { eq: dataSetActualId } },
},
})
.getData();

const existingDataSetActual = _.get(dataSetsMetadata.dataSets, 0, null);

const dataSetAttributeValues = addAttributeValue(
baseAttributeValues,
_.concat(existingDataSetActual?.attributeValues || [], baseAttributeValues),
config.attributes.orgUnitProject,
orgUnit.id
);
Expand Down Expand Up @@ -262,10 +313,16 @@ export default class ProjectDb {
);
if (!dashboards.project) throw new Error("Dashboards error");

const projectOrgUnit = addAttributeValueToObj(orgUnit, {
attribute: config.attributes.projectDashboard,
value: dashboards.project.id,
});
const projectOrgUnit = addAttributeValueToObj(
addAttributeValueToObj(orgUnit, {
attribute: config.attributes.createdByApp,
value: "true",
}),
{
attribute: config.attributes.projectDashboard,
value: dashboards.project.id,
}
);

const orgUnitGroupsMetadata = await getOrgUnitGroupsMetadata(api, project, dashboards);

Expand Down Expand Up @@ -405,7 +462,7 @@ export default class ProjectDb {
case "actual": {
const actualPeriods = getMonthsRange(startDate, endDate).map(date => ({
period: { id: date.format("YYYYMM") },
openingDate: toISOString(projectOpeningDate),
openingDate: toISOString(projectOpeningDate.clone().startOf("month")),
closingDate: toISOString(
date.clone().startOf("month").add(1, "month").date(expiryDaysInMonthActual)
),
Expand Down Expand Up @@ -456,6 +513,34 @@ export default class ProjectDb {
}
}

async updateLastUpdatedData(api: D2Api, config: Config, project: Project) {
const dataSetRef = project.dataSets?.actual;
if (!dataSetRef) return;

const { dataSets } = await api.metadata
.get({
dataSets: {
fields: { $owner: true },
filter: { id: { eq: dataSetRef.id } },
},
})
.getData();

const dataSet = _(dataSets).get(0, null);
if (!dataSet) return;

if (dataSet) {
const dataSetUpdated = addAttributeValueToObj(dataSet, {
attribute: config.attributes.lastUpdatedData,
value: moment().toISOString(),
});

const res = await api.models.dataSets.put(dataSetUpdated).getData();

if (res.status !== "OK") console.error("Error saving data set");
}
}

getDataElementsBySector() {
const { project } = this;
const sectorIdForDataElementId = this.getDataElementsBySectorMapping();
Expand Down Expand Up @@ -606,7 +691,12 @@ export default class ProjectDb {

const getDataSet = (type: DataSetType) => {
const dataSet = _(dataSets).find(dataSet => dataSet.code.endsWith(type.toUpperCase()));
if (!dataSet) throw new Error(`Cannot find dataset: ${type}`);
if (!dataSet)
throw new Error(
`Cannot find dataset: ${type} (dataSets: ${
dataSets.map(ds => ds.id).join(", ") || "-"
})`
);
return dataSet;
};

Expand Down
Loading

0 comments on commit a9e80af

Please sign in to comment.