Skip to content
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ This report shows data values for data sets `NHWA Module ...`. There are two kin

The API endpoint `/dataValueSets` does not provide all the features we need, so we use a custom SQL View instead.

### new variants:

nhwa-attachments

## Initial setup

```
Expand Down
25 changes: 17 additions & 8 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: 2023-08-28T11:13:31.544Z\n"
"PO-Revision-Date: 2023-08-28T11:13:31.544Z\n"
"POT-Creation-Date: 2023-12-01T16:20:03.972Z\n"
"PO-Revision-Date: 2023-12-01T16:20:03.972Z\n"

msgid "<No value>"
msgstr ""
Expand Down Expand Up @@ -601,6 +601,21 @@ msgstr ""
msgid "Error when trying to unapprove data set"
msgstr ""

msgid "NHWA Attachment Report"
msgstr ""

msgid "File"
msgstr ""

msgid "Download"
msgstr ""

msgid "Last updated"
msgstr ""

msgid "Stored by"
msgstr ""

msgid "Category Option Combo"
msgstr ""

Expand Down Expand Up @@ -633,12 +648,6 @@ msgstr ""
msgid "Value"
msgstr ""

msgid "Last updated"
msgstr ""

msgid "Stored by"
msgstr ""

msgid "Toggle filters"
msgstr ""

Expand Down
26 changes: 17 additions & 9 deletions i18n/es.po
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: i18next-conv\n"
"POT-Creation-Date: 2023-08-28T11:13:31.544Z\n"
"POT-Creation-Date: 2023-12-01T16:20:03.972Z\n"
"PO-Revision-Date: 2018-10-25T09:02:35.143Z\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
Expand Down Expand Up @@ -601,6 +601,21 @@ msgstr ""
msgid "Error when trying to unapprove data set"
msgstr ""

msgid "NHWA Attachment Report"
msgstr ""

msgid "File"
msgstr ""

msgid "Download"
msgstr ""

msgid "Last updated"
msgstr ""

msgid "Stored by"
msgstr ""

msgid "Category Option Combo"
msgstr ""

Expand Down Expand Up @@ -633,21 +648,14 @@ msgstr ""
msgid "Value"
msgstr ""

msgid "Last updated"
msgstr ""

msgid "Stored by"
msgstr ""

msgid "Toggle filters"
msgstr ""

msgid "No values to update"
msgstr ""

#, fuzzy
msgid "Occupation"
msgstr "Configuración"
msgstr ""

msgid "Practising"
msgstr ""
Expand Down
8 changes: 8 additions & 0 deletions src/compositionRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ import { SaveDataQualityUseCase } from "./domain/reports/data-quality/usecases/S
import { LoadDataQualityValidation } from "./domain/reports/data-quality/usecases/loadDataQualityValidation";
import { ResetDataQualityValidation } from "./domain/reports/data-quality/usecases/ResetDataQualityValidation";
import { GetMonitoringDetailsUseCase } from "./domain/reports/mal-data-subscription/usecases/GetMonitoringDetailsUseCase";
import { NHWAAttachementsDefaultRepository } from "./data/NHWAAttachementsDefaultRepository ";
import { GetAttachementsUseCase } from "./domain/nhwa-attachments/usecases/GetAttachementsUseCase";
import { ExportAttachmentsUseCase } from "./domain/nhwa-attachments/usecases/ExportAttachmentsUseCase";

export function getCompositionRoot(api: D2Api) {
const configRepository = new Dhis2ConfigRepository(api, getReportType());
Expand All @@ -95,6 +98,7 @@ export function getCompositionRoot(api: D2Api) {
const csySummaryRepository = new CSYSummaryDefaultRepository(api);
const csySummaryMortalityRepository = new CSYSummaryMortalityDefaultRepository(api);
const orgUnitsRepository = new Dhis2OrgUnitsRepository(api);
const attachementRepository = new NHWAAttachementsDefaultRepository(api);
const dataSetRepository = new DataSetD2Repository(api);
const dataValuesRepository = new DataValuesD2Repository(api);
const autoCompleteComputeSettingsRepository = new AutoCompleteComputeSettingsD2Repository(api);
Expand Down Expand Up @@ -184,6 +188,10 @@ export function getCompositionRoot(api: D2Api) {
config: getExecute({
get: new GetConfig(configRepository),
}),
attachments: getExecute({
get: new GetAttachementsUseCase(attachementRepository),
export: new ExportAttachmentsUseCase(attachementRepository),
}),
nhwa: {
getAutoCompleteComputeValues: new GetAutoCompleteComputeValuesUseCase(
dataSetRepository,
Expand Down
110 changes: 110 additions & 0 deletions src/data/NHWAAttachementsDefaultRepository .ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import _ from "lodash";
import { DataAttachmentItem } from "../domain/nhwa-attachments/entities/DataAttachmentItem";
import {
NHWADataAttachmentsRepository,
NHWADataAttachmentsRepositoryGetOptions,
} from "../domain/nhwa-attachments/repositories/NHWADataAttachmentsRepository";
import { D2Api, PaginatedObjects, Id } from "../types/d2-api";
import { Dhis2SqlViews } from "./common/Dhis2SqlViews";
import { CsvWriterDataSource } from "./common/CsvWriterCsvDataSource";
import { downloadFile } from "./common/utils/download-file";
import { CsvData } from "./common/CsvDataSource";
import { getSqlViewId } from "../domain/common/entities/Config";
import { SQL_VIEW_ATTACHEMENT_NAME } from "./common/Dhis2ConfigRepository";

interface Variables {
orgUnitIds: string;
dataSetIds: string;
periods: string;
orderByColumn: SqlField;
orderByDirection: "asc" | "desc";
}

type SqlField = "id" | "datasetname" | "link" | "period" | "storedby" | "orgunit" | "lastupdated";

const fieldMapping: Record<keyof DataAttachmentItem, SqlField> = {
id: "id",
period: "period",
orgUnit: "orgunit",
dataSet: "datasetname",
link: "link",
lastUpdated: "lastupdated",
storedBy: "storedby",
};

export class NHWAAttachementsDefaultRepository implements NHWADataAttachmentsRepository {
constructor(private api: D2Api) {}

async get(options: NHWADataAttachmentsRepositoryGetOptions): Promise<PaginatedObjects<DataAttachmentItem>> {
const { config, dataSetIds, orgUnitIds, periods } = options;
const { paging, sorting } = options;

const allDataSetIds = _.values(config.dataSets).map(ds => ds.id);
const dataSetIds2 = _.isEmpty(dataSetIds) ? allDataSetIds : dataSetIds;

const sqlViews = new Dhis2SqlViews(this.api);
const { pager, rows } = await sqlViews
.query<Variables, SqlField>(
getSqlViewId(config, SQL_VIEW_ATTACHEMENT_NAME),
{
orgUnitIds: sqlViewJoinIds(orgUnitIds),
periods: sqlViewJoinIds(_.isEmpty(periods) ? config.years : periods),
dataSetIds: sqlViewJoinIds(dataSetIds2),
orderByColumn: fieldMapping[sorting.field],
orderByDirection: sorting.direction,
},
paging
)
.getData();

// A data value is not associated to a specific data set, but we can still map it
// through the data element (1 data value -> 1 data element -> N data sets).

const dataValues: Array<DataAttachmentItem> = rows.map(
(dv): DataAttachmentItem => ({
id: [dv.datasetname, dv.period, dv.orgunit].join("-"),
period: dv.period.split("-")[0] ?? "",
orgUnit: { name: dv.orgunit },
dataSet: { name: dv.datasetname },
link: this.api.apiPath + "/" + dv.link,
lastUpdated: new Date(dv.lastupdated),
storedBy: dv.storedby,
})
);

return { pager, objects: dataValues };
}

async save(filename: string, dataValues: DataAttachmentItem[]): Promise<void> {
const headers = csvFields.map(field => ({ id: field, text: field }));
const rows = dataValues.map(
(dataValue): DataValueRow => ({
period: dataValue.period,
orgUnit: dataValue.orgUnit.name,
dataSet: dataValue.dataSet.name,
lastUpdated: dataValue.lastUpdated.toISOString(),
storedBy: dataValue.storedBy,
link: dataValue.link,
})
);

const csvDataSource = new CsvWriterDataSource();
const csvData: CsvData<CsvField> = { headers, rows };
const csvContents = csvDataSource.toString(csvData);

await downloadFile(csvContents, filename, "text/csv");
}
}

const csvFields = ["dataSet", "period", "orgUnit", "link", "lastUpdated", "storedBy"] as const;

type CsvField = typeof csvFields[number];

type DataValueRow = Record<CsvField, string>;

/* From the docs: "The variables must contain alphanumeric, dash, underscore and
whitespace characters only.". Use "-" as id separator and also "-" as empty value.
*/
function sqlViewJoinIds(ids: Id[]): string {
return ids.join("-") || "-";
}
8 changes: 7 additions & 1 deletion src/data/common/Dhis2ConfigRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,17 @@ export const SQL_VIEW_OLD_DATA_DUPLICATION_NAME = "MAL Data Approval Status Pre
export const SQL_VIEW_MAL_METADATA_NAME = "MAL Data approval header";
export const SQL_VIEW_MAL_DIFF_NAME = "MAL Data Approval Diff";
export const SQL_VIEW_NHWA_SUBNATIONAL_CORRECT = "NHWA Module 1 Subnational correct org unit name";
export const SQL_VIEW_ATTACHEMENT_NAME = "NHWA attachments";

const base = {
nhwa: {
dataSets: { namePrefix: "NHWA", nameExcluded: /old$/ },
sqlViewNames: [SQL_VIEW_DATA_COMMENTS_NAME, SQL_VIEW_DATA_APPROVAL_NAME, SQL_VIEW_NHWA_SUBNATIONAL_CORRECT],
sqlViewNames: [
SQL_VIEW_DATA_COMMENTS_NAME,
SQL_VIEW_DATA_APPROVAL_NAME,
SQL_VIEW_NHWA_SUBNATIONAL_CORRECT,
SQL_VIEW_ATTACHEMENT_NAME,
],
constantCode: "NHWA_COMMENTS",
approvalWorkflows: { namePrefix: "NHWA" },
},
Expand Down
15 changes: 15 additions & 0 deletions src/domain/nhwa-attachments/entities/DataAttachmentItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Id, Named } from "../../common/entities/Base";

export interface DataAttachmentItem {
id: string;
period: string;
orgUnit: Named;
dataSet: Named;
link: string;
lastUpdated: Date;
storedBy: string;
}

export function getDataAttachmentsItemId(dataValue: DataAttachmentItem): Id {
return [dataValue.dataSet.name, dataValue.period, dataValue.orgUnit.name].join("-");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DataAttachmentItem } from "../entities/DataAttachmentItem";
import { Id } from "../../common/entities/Base";
import { Config } from "../../common/entities/Config";
import { PaginatedObjects, Paging, Sorting } from "../../common/entities/PaginatedObjects";

export interface NHWADataAttachmentsRepository {
get(options: NHWADataAttachmentsRepositoryGetOptions): Promise<PaginatedObjects<DataAttachmentItem>>;
save(filename: string, dataValues: DataAttachmentItem[]): Promise<void>;
}

export interface NHWADataAttachmentsRepositoryGetOptions {
config: Config;
paging: Paging;
sorting: Sorting<DataAttachmentItem>;
periods: string[];
orgUnitIds: Id[];
dataSetIds: Id[];
}
13 changes: 13 additions & 0 deletions src/domain/nhwa-attachments/usecases/ExportAttachmentsUseCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
NHWADataAttachmentsRepository,
NHWADataAttachmentsRepositoryGetOptions,
} from "../repositories/NHWADataAttachmentsRepository";

export class ExportAttachmentsUseCase {
constructor(private attachmentRepository: NHWADataAttachmentsRepository) {}

async execute(options: NHWADataAttachmentsRepositoryGetOptions): Promise<void> {
const attachments = await this.attachmentRepository.get(options);
await this.attachmentRepository.save("data-values.csv", attachments.objects);
}
}
16 changes: 16 additions & 0 deletions src/domain/nhwa-attachments/usecases/GetAttachementsUseCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {
NHWADataAttachmentsRepository,
NHWADataAttachmentsRepositoryGetOptions,
} from "../repositories/NHWADataAttachmentsRepository";
import { DataAttachmentItem } from "../entities/DataAttachmentItem";
import { PaginatedObjects } from "../../common/entities/PaginatedObjects";

type GetAttachementsUseCaseOptions = NHWADataAttachmentsRepositoryGetOptions;

export class GetAttachementsUseCase {
constructor(private attachmentRepository: NHWADataAttachmentsRepository) {}

async execute(options: GetAttachementsUseCaseOptions): Promise<PaginatedObjects<DataAttachmentItem>> {
return this.attachmentRepository.get(options);
}
}
4 changes: 4 additions & 0 deletions src/webapp/reports/Reports.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import CSYAuditTraumaReport from "./csy-audit-trauma/CSYAuditTraumaReport";
import { NHWAAutoCompleteCompute } from "./nhwa-auto-complete-compute/NHWAAutoCompleteCompute";
import { NHWAFixTotals } from "./nhwa-fix-totals-activity-level/NHWAFixTotals";
import { NHWASubnationalCorrectOrgUnit } from "./nhwa-subnational-correct-orgunit/NHWASubnationalCorrectOrgUnit";
import NHWAAttachmentReport from "./nhwa-attachments/NHWAAttachmentReport";

const widget = process.env.REACT_APP_REPORT_VARIANT || "";

Expand All @@ -34,6 +35,9 @@ const Component: React.FC = () => {
case "admin": {
return <AdminReport />;
}
case "nhwa-attachments": {
return <NHWAAttachmentReport />;
}
case "wmr-national-policies": {
return <WMRNationalPolicies />;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Config } from "../../../domain/common/entities/Config";
import {
DataAttachmentItem,
getDataAttachmentsItemId,
} from "../../../domain/nhwa-attachments/entities/DataAttachmentItem";
23 changes: 23 additions & 0 deletions src/webapp/reports/nhwa-attachments/NHWAAttachmentReport.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Typography, makeStyles } from "@material-ui/core";
import i18n from "../../../locales";
import { DataAttachmentsList } from "./data-attachment-list/DataAttachmentsList";

const NHWAAttachmentReport: React.FC = () => {
const classes = useStyles();

return (
<div className={classes.wrapper}>
<Typography variant="h5" gutterBottom>
{i18n.t("NHWA Attachment Report")}
</Typography>

<DataAttachmentsList />
</div>
);
};

const useStyles = makeStyles({
wrapper: { padding: 20 },
});

export default NHWAAttachmentReport;
Loading