Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/attachment report #22

Open
wants to merge 9 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
32 changes: 19 additions & 13 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-01-10T08:43:46.954Z\n"
"PO-Revision-Date: 2022-01-10T08:43:46.954Z\n"
"POT-Creation-Date: 2022-08-03T07:20:50.359Z\n"
"PO-Revision-Date: 2022-08-03T07:20:50.359Z\n"

msgid "<No value>"
msgstr ""
Expand Down Expand Up @@ -77,10 +77,10 @@ msgstr ""
msgid "NHWA Data Approval Status Report"
msgstr ""

msgid "Data set"
msgid "Period"
msgstr ""

msgid "Period"
msgid "Data set"
msgstr ""

msgid "Attribute"
Expand Down Expand Up @@ -125,6 +125,21 @@ msgstr ""
msgid "Periods"
msgstr ""

msgid "NHWA Atachment Report"
msgstr ""

msgid "link"
msgstr ""

msgid "Last updated"
msgstr ""

msgid "Stored by"
msgstr ""

msgid "Toggle filters"
msgstr ""

msgid "NHWA Comments Report"
msgstr ""

Expand All @@ -143,14 +158,5 @@ msgstr ""
msgid "Comment"
msgstr ""

msgid "Last updated"
msgstr ""

msgid "Stored by"
msgstr ""

msgid "Sections"
msgstr ""

msgid "Toggle filters"
msgstr ""
30 changes: 18 additions & 12 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: 2022-01-10T08:43:46.954Z\n"
"POT-Creation-Date: 2022-08-03T07:20:50.359Z\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 @@ -77,10 +77,10 @@ msgstr ""
msgid "NHWA Data Approval Status Report"
msgstr ""

msgid "Data set"
msgid "Period"
msgstr ""

msgid "Period"
msgid "Data set"
msgstr ""

msgid "Attribute"
Expand Down Expand Up @@ -125,6 +125,21 @@ msgstr ""
msgid "Periods"
msgstr ""

msgid "NHWA Atachment Report"
msgstr ""

msgid "link"
msgstr ""

msgid "Last updated"
msgstr ""

msgid "Stored by"
msgstr ""

msgid "Toggle filters"
msgstr ""

msgid "NHWA Comments Report"
msgstr ""

Expand All @@ -143,18 +158,9 @@ msgstr ""
msgid "Comment"
msgstr ""

msgid "Last updated"
msgstr ""

msgid "Stored by"
msgstr ""

msgid "Sections"
msgstr ""

msgid "Toggle filters"
msgstr ""

#~ msgid "Add"
#~ msgstr "Añadir"

Expand Down
8 changes: 8 additions & 0 deletions src/compositionRoot.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Dhis2ConfigRepository } from "./data/Dhis2ConfigRepository";
import { Dhis2OrgUnitsRepository } from "./data/Dhis2OrgUnitsRepository";
import { NHWAAttachementsDefaultRepository } from "./data/NHWAAttachementsDefaultRepository ";
import { NHWADataApprovalDefaultRepository } from "./data/NHWADataApprovalDefaultRepository";
import { NHWADataCommentsDefaultRepository } from "./data/NHWADataCommentsDefaultRepository";
import { WIDPAdminDefaultRepository } from "./data/WIDPAdminDefaultRepository";
Expand All @@ -12,6 +13,8 @@ import { GetApprovalColumnsUseCase } from "./domain/nhwa-approval-status/usecase
import { GetDataSetsUseCase } from "./domain/nhwa-approval-status/usecases/GetDataSetsUseCase";
import { SaveApprovalColumnsUseCase } from "./domain/nhwa-approval-status/usecases/SaveApprovalColumnsUseCase";
import { SaveDataSetsUseCase } from "./domain/nhwa-approval-status/usecases/SaveDataSetsCsvUseCase";
import { GetAttachementsUseCase } from "./domain/nhwa-attachments/usecases/GetAttachementsUseCase";
import { SaveAttachementsUseCase } from "./domain/nhwa-attachments/usecases/SaveAttachementsUseCase";
import { GetDataValuesUseCase } from "./domain/nhwa-comments/usecases/GetDataValuesUseCase";
import { SaveDataValuesUseCase } from "./domain/nhwa-comments/usecases/SaveDataValuesCsvUseCase";
import { D2Api } from "./types/d2-api";
Expand All @@ -22,6 +25,7 @@ export function getCompositionRoot(api: D2Api) {
const dataApprovalRepository = new NHWADataApprovalDefaultRepository(api);
const widpAdminDefaultRepository = new WIDPAdminDefaultRepository(api);
const orgUnitsRepository = new Dhis2OrgUnitsRepository(api);
const attachementRepository = new NHWAAttachementsDefaultRepository(api);

return {
admin: getExecute({
Expand All @@ -45,6 +49,10 @@ export function getCompositionRoot(api: D2Api) {
config: getExecute({
get: new GetConfig(configRepository),
}),
attachements: getExecute({
Copy link
Contributor

@tokland tokland Aug 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: attachments, in variables/functions and file names.

get: new GetAttachementsUseCase(attachementRepository),
save: new SaveAttachementsUseCase(attachementRepository),
}),
};
}

Expand Down
9 changes: 8 additions & 1 deletion src/data/Dhis2ConfigRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { D2Api, Id } from "../types/d2-api";

const SQL_VIEW_DATA_COMMENTS_NAME = "NHWA Data Comments";
const SQL_VIEW_DATA_APPROVAL_NAME = "NHWA Data Approval Status";
const SQL_VIEW_ATTACHEMENT_NAME = "NHWA attachments";

const base = {
dataSets: { namePrefix: "NHWA", nameExcluded: /old$/ },
sqlViewNames: [SQL_VIEW_DATA_COMMENTS_NAME, SQL_VIEW_DATA_APPROVAL_NAME],
sqlViewNames: [SQL_VIEW_DATA_COMMENTS_NAME, SQL_VIEW_DATA_APPROVAL_NAME, SQL_VIEW_ATTACHEMENT_NAME],
constantCode: "NHWA_COMMENTS",
approvalWorkflows: { namePrefix: "NHWA" },
};
Expand All @@ -23,6 +24,7 @@ export class Dhis2ConfigRepository implements ConfigRepository {
const filteredDataSets = getFilteredDataSets(dataSets);
const dataCommentsSqlView = sqlViews.find(({ name }) => name === SQL_VIEW_DATA_COMMENTS_NAME);
const dataApprovalSqlView = sqlViews.find(({ name }) => name === SQL_VIEW_DATA_APPROVAL_NAME);
const dataAttachmentSqlView = sqlViews.find(({ name }) => name === SQL_VIEW_ATTACHEMENT_NAME);
if (!dataCommentsSqlView) {
throw new Error(`Missing SQL views: ${SQL_VIEW_DATA_COMMENTS_NAME}`);
}
Expand All @@ -31,6 +33,10 @@ export class Dhis2ConfigRepository implements ConfigRepository {
throw new Error(`Missing SQL views: ${SQL_VIEW_DATA_APPROVAL_NAME}`);
}

if (!dataAttachmentSqlView) {
throw new Error(`Missing SQL views: ${SQL_VIEW_ATTACHEMENT_NAME}`);
}

const constant = getNth(constants, 0, `Missing constant: ${base.constantCode}`);
const currentUser = await this.getCurrentUser();
const pairedDataElements = getPairedMapping(filteredDataSets);
Expand All @@ -43,6 +49,7 @@ export class Dhis2ConfigRepository implements ConfigRepository {
currentUser,
dataCommentsSqlView,
dataApprovalSqlView,
dataAttachmentSqlView,
pairedDataElementsByDataSet: pairedDataElements,
sections: keyById(sections),
sectionsByDataSet,
Expand Down
106 changes: 106 additions & 0 deletions src/data/NHWAAttachementsDefaultRepository .ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
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 "./Dhis2SqlViews";
import { CsvWriterDataSource } from "./CsvWriterCsvDataSource";
import { downloadFile } from "./utils/download-file";
import { CsvData } from "./CsvDataSource";

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

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

const fieldMapping: Record<keyof DataAttachmentItem, SqlField> = {
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>(
config.dataAttachmentSqlView.id,
{
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 => ({
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("-") || "-";
}
1 change: 1 addition & 0 deletions src/domain/common/entities/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface Config {
currentUser: User;
dataCommentsSqlView: NamedRef;
dataApprovalSqlView: NamedRef;
dataAttachmentSqlView: NamedRef;
pairedDataElementsByDataSet: {
[dataSetId: string]: Array<{ dataValueVal: Id; dataValueComment: Id }>;
};
Expand Down
14 changes: 14 additions & 0 deletions src/domain/nhwa-attachments/entities/DataAttachmentItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Id, Named } from "../../common/entities/Base";

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

export function getDataAttachmentsItemId(dataValue: DataAttachmentItem): Id {
return [dataValue.dataSet, 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[];
}
17 changes: 17 additions & 0 deletions src/domain/nhwa-attachments/usecases/GetAttachementsUseCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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 dataValueRepository: NHWADataAttachmentsRepository) {}

execute(options: GetAttachementsUseCaseOptions): Promise<PaginatedObjects<DataAttachmentItem>> {
// FUTURE: Return a Future-like instead, to allow better error handling and cancellation.
return this.dataValueRepository.get(options);
}
}
10 changes: 10 additions & 0 deletions src/domain/nhwa-attachments/usecases/SaveAttachementsUseCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { DataAttachmentItem } from "../entities/DataAttachmentItem";
import { NHWADataAttachmentsRepository } from "../repositories/NHWADataAttachmentsRepository";

export class SaveAttachementsUseCase {
constructor(private dataValueRepository: NHWADataAttachmentsRepository) {}

async execute(filename: string, dataValues: DataAttachmentItem[]): Promise<void> {
this.dataValueRepository.save(filename, dataValues);
}
}
Loading