From 1da263df828e9ea66af301838dd1704a435ebb4e Mon Sep 17 00:00:00 2001 From: idelcano Date: Tue, 2 Aug 2022 21:21:16 +0200 Subject: [PATCH 1/8] added new attachement report --- i18n/en.pot | 34 +++-- i18n/es.po | 32 +++-- src/compositionRoot.ts | 8 ++ src/data/Dhis2ConfigRepository.ts | 9 +- .../NHWAAttachementsDefaultRepository .ts | 117 +++++++++++++++ src/domain/common/entities/Config.ts | 1 + .../entities/DataAttachmentItem.ts | 15 ++ .../NHWADataAttachmentsRepository.ts | 18 +++ .../usecases/GetAttachementsUseCase.ts | 17 +++ .../usecases/SaveAttachementsUseCase.ts | 10 ++ src/webapp/reports/Reports.tsx | 4 + .../DataAttachmentsViewModel.ts | 31 ++++ .../nhwa-attachments/NHWAAttachmentReport.tsx | 23 +++ .../DataAttachmentsList.tsx | 133 ++++++++++++++++++ .../data-attachment-list/Filters.tsx | 69 +++++++++ .../data-attachment-list/FiltersBox.tsx | 46 ++++++ 16 files changed, 539 insertions(+), 28 deletions(-) create mode 100644 src/data/NHWAAttachementsDefaultRepository .ts create mode 100644 src/domain/nhwa-attachments/entities/DataAttachmentItem.ts create mode 100644 src/domain/nhwa-attachments/repositories/NHWADataAttachmentsRepository.ts create mode 100644 src/domain/nhwa-attachments/usecases/GetAttachementsUseCase.ts create mode 100644 src/domain/nhwa-attachments/usecases/SaveAttachementsUseCase.ts create mode 100644 src/webapp/reports/nhwa-attachments/DataAttachmentsViewModel.ts create mode 100644 src/webapp/reports/nhwa-attachments/NHWAAttachmentReport.tsx create mode 100644 src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx create mode 100644 src/webapp/reports/nhwa-attachments/data-attachment-list/Filters.tsx create mode 100644 src/webapp/reports/nhwa-attachments/data-attachment-list/FiltersBox.tsx diff --git a/i18n/en.pot b/i18n/en.pot index 73d40913..1d1ed09d 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -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-02T18:51:46.114Z\n" +"PO-Revision-Date: 2022-08-02T18:51:46.114Z\n" msgid "" msgstr "" @@ -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" @@ -125,32 +125,38 @@ msgstr "" msgid "Periods" msgstr "" -msgid "NHWA Comments Report" +msgid "NHWA Atachment Report" msgstr "" -msgid "Section" +msgid "Data Element" msgstr "" -msgid "Data Element" +msgid "link" msgstr "" -msgid "Category option combo" +msgid "Last updated" msgstr "" -msgid "Value" +msgid "Stored by" msgstr "" -msgid "Comment" +msgid "Toggle filters" msgstr "" -msgid "Last updated" +msgid "NHWA Comments Report" msgstr "" -msgid "Stored by" +msgid "Section" msgstr "" -msgid "Sections" +msgid "Category option combo" msgstr "" -msgid "Toggle filters" +msgid "Value" +msgstr "" + +msgid "Comment" +msgstr "" + +msgid "Sections" msgstr "" diff --git a/i18n/es.po b/i18n/es.po index a81ba594..b380765d 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -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-02T18:51:46.114Z\n" "PO-Revision-Date: 2018-10-25T09:02:35.143Z\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -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" @@ -125,34 +125,40 @@ msgstr "" msgid "Periods" msgstr "" -msgid "NHWA Comments Report" +msgid "NHWA Atachment Report" msgstr "" -msgid "Section" +msgid "Data Element" msgstr "" -msgid "Data Element" +msgid "link" msgstr "" -msgid "Category option combo" +msgid "Last updated" msgstr "" -msgid "Value" +msgid "Stored by" msgstr "" -msgid "Comment" +msgid "Toggle filters" msgstr "" -msgid "Last updated" +msgid "NHWA Comments Report" msgstr "" -msgid "Stored by" +msgid "Section" msgstr "" -msgid "Sections" +msgid "Category option combo" msgstr "" -msgid "Toggle filters" +msgid "Value" +msgstr "" + +msgid "Comment" +msgstr "" + +msgid "Sections" msgstr "" #~ msgid "Add" diff --git a/src/compositionRoot.ts b/src/compositionRoot.ts index ea31c433..693a4089 100644 --- a/src/compositionRoot.ts +++ b/src/compositionRoot.ts @@ -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"; @@ -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"; @@ -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({ @@ -45,6 +49,10 @@ export function getCompositionRoot(api: D2Api) { config: getExecute({ get: new GetConfig(configRepository), }), + attachements: getExecute({ + get: new GetAttachementsUseCase(attachementRepository), + save: new SaveAttachementsUseCase(attachementRepository), + }), }; } diff --git a/src/data/Dhis2ConfigRepository.ts b/src/data/Dhis2ConfigRepository.ts index 33a3dab0..91f759bf 100644 --- a/src/data/Dhis2ConfigRepository.ts +++ b/src/data/Dhis2ConfigRepository.ts @@ -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 (wip)"; 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" }, }; @@ -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}`); } @@ -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); @@ -43,6 +49,7 @@ export class Dhis2ConfigRepository implements ConfigRepository { currentUser, dataCommentsSqlView, dataApprovalSqlView, + dataAttachmentSqlView, pairedDataElementsByDataSet: pairedDataElements, sections: keyById(sections), sectionsByDataSet, diff --git a/src/data/NHWAAttachementsDefaultRepository .ts b/src/data/NHWAAttachementsDefaultRepository .ts new file mode 100644 index 00000000..f89beb4d --- /dev/null +++ b/src/data/NHWAAttachementsDefaultRepository .ts @@ -0,0 +1,117 @@ +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" + | "dataelementid" + | "dataelementname" + | "link" + | "period" + | "storedby" + | "orgunit" + | "lastupdated"; + +const fieldMapping: Record = { + period: "period", + orgUnit: "orgunit", + dataSet: "datasetname", + dataElement: "dataelementname", + link: "link", + lastUpdated: "lastupdated", + storedBy: "storedby", +}; + +export class NHWAAttachementsDefaultRepository implements NHWADataAttachmentsRepository { + constructor(private api: D2Api) {} + + async get(options: NHWADataAttachmentsRepositoryGetOptions): Promise> { + 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( + 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 = rows.map( + (dv): DataAttachmentItem => ({ + period: dv.period.split("-")[0] ?? "", + orgUnit: { name: dv.orgunit }, + dataSet: { name: dv.datasetname }, + dataElement: { id: dv.dataelementid, name: dv.dataelementname }, + link: dv.link, + lastUpdated: new Date(dv.lastupdated), + storedBy: dv.storedby, + }) + ); + + return { pager, objects: dataValues }; + } + + async save(filename: string, dataValues: DataAttachmentItem[]): Promise { + 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, + dataElement: dataValue.dataElement.name, + lastUpdated: dataValue.lastUpdated.toISOString(), + storedBy: dataValue.storedBy, + link: dataValue.link, + }) + ); + + const csvDataSource = new CsvWriterDataSource(); + const csvData: CsvData = { headers, rows }; + const csvContents = csvDataSource.toString(csvData); + + await downloadFile(csvContents, filename, "text/csv"); + } +} + +const csvFields = ["dataSet", "period", "orgUnit", "dataElement", "link", "lastUpdated", "storedBy"] as const; + +type CsvField = typeof csvFields[number]; + +type DataValueRow = Record; + +/* 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("-") || "-"; +} diff --git a/src/domain/common/entities/Config.ts b/src/domain/common/entities/Config.ts index 42439e61..2fa2ff0a 100644 --- a/src/domain/common/entities/Config.ts +++ b/src/domain/common/entities/Config.ts @@ -9,6 +9,7 @@ export interface Config { currentUser: User; dataCommentsSqlView: NamedRef; dataApprovalSqlView: NamedRef; + dataAttachmentSqlView: NamedRef; pairedDataElementsByDataSet: { [dataSetId: string]: Array<{ dataValueVal: Id; dataValueComment: Id }>; }; diff --git a/src/domain/nhwa-attachments/entities/DataAttachmentItem.ts b/src/domain/nhwa-attachments/entities/DataAttachmentItem.ts new file mode 100644 index 00000000..fda7592e --- /dev/null +++ b/src/domain/nhwa-attachments/entities/DataAttachmentItem.ts @@ -0,0 +1,15 @@ +import { Id, NamedRef, Named } from "../../common/entities/Base"; + +export interface DataAttachmentItem { + period: string; + orgUnit: Named; + dataSet: Named; + dataElement: NamedRef; + link: string; + lastUpdated: Date; + storedBy: string; +} + +export function getDataAttachmentsItemId(dataValue: DataAttachmentItem): Id { + return [dataValue.dataElement, dataValue.period, dataValue.orgUnit.name].join("-"); +} diff --git a/src/domain/nhwa-attachments/repositories/NHWADataAttachmentsRepository.ts b/src/domain/nhwa-attachments/repositories/NHWADataAttachmentsRepository.ts new file mode 100644 index 00000000..b1dbdad4 --- /dev/null +++ b/src/domain/nhwa-attachments/repositories/NHWADataAttachmentsRepository.ts @@ -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>; + save(filename: string, dataValues: DataAttachmentItem[]): Promise; +} + +export interface NHWADataAttachmentsRepositoryGetOptions { + config: Config; + paging: Paging; + sorting: Sorting; + periods: string[]; + orgUnitIds: Id[]; + dataSetIds: Id[]; +} diff --git a/src/domain/nhwa-attachments/usecases/GetAttachementsUseCase.ts b/src/domain/nhwa-attachments/usecases/GetAttachementsUseCase.ts new file mode 100644 index 00000000..8ccdf9ea --- /dev/null +++ b/src/domain/nhwa-attachments/usecases/GetAttachementsUseCase.ts @@ -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> { + // FUTURE: Return a Future-like instead, to allow better error handling and cancellation. + return this.dataValueRepository.get(options); + } +} diff --git a/src/domain/nhwa-attachments/usecases/SaveAttachementsUseCase.ts b/src/domain/nhwa-attachments/usecases/SaveAttachementsUseCase.ts new file mode 100644 index 00000000..4d64ca19 --- /dev/null +++ b/src/domain/nhwa-attachments/usecases/SaveAttachementsUseCase.ts @@ -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 { + this.dataValueRepository.save(filename, dataValues); + } +} diff --git a/src/webapp/reports/Reports.tsx b/src/webapp/reports/Reports.tsx index 15f4cf3c..6a07eb24 100644 --- a/src/webapp/reports/Reports.tsx +++ b/src/webapp/reports/Reports.tsx @@ -2,6 +2,7 @@ import React from "react"; import AdminReport from "./admin/AdminReport"; import NHWACommentsReport from "./nhwa-comments/NHWACommentsReport"; import NHWADataApprovalStatusReport from "./nhwa-approval-status/NHWADataApprovalStatusReport"; +import NHWAAttachmentReport from "./nhwa-attachments/NHWAAttachmentReport"; const widget = process.env.REACT_APP_REPORT_VARIANT || ""; @@ -16,6 +17,9 @@ const Component: React.FC = () => { case "admin": { return ; } + case "nhwa-attachments": { + return ; + } default: { return

{`Please provide a valid REACT_APP_REPORT_VARIANT`}

; } diff --git a/src/webapp/reports/nhwa-attachments/DataAttachmentsViewModel.ts b/src/webapp/reports/nhwa-attachments/DataAttachmentsViewModel.ts new file mode 100644 index 00000000..7f9bb2ad --- /dev/null +++ b/src/webapp/reports/nhwa-attachments/DataAttachmentsViewModel.ts @@ -0,0 +1,31 @@ +import { Config } from "../../../domain/common/entities/Config"; +import { + DataAttachmentItem, + getDataAttachmentsItemId, +} from "../../../domain/nhwa-attachments/entities/DataAttachmentItem"; + +export interface DataAttachmentsViewModel { + id: string; + period: string; + orgUnit: string; + dataSet: string; + dataElement: string; + link: string; + lastUpdated: string; + storedBy: string; +} + +export function getDataAttachmentsViews(config: Config, dataValues: DataAttachmentItem[]): DataAttachmentsViewModel[] { + return dataValues.map(dataValue => { + return { + id: getDataAttachmentsItemId(dataValue), + period: dataValue.period, + orgUnit: dataValue.orgUnit.name, + dataSet: dataValue.dataSet.name, + dataElement: dataValue.dataElement.name, + link: dataValue.link || "", + lastUpdated: dataValue.lastUpdated.toISOString(), + storedBy: dataValue.storedBy, + }; + }); +} diff --git a/src/webapp/reports/nhwa-attachments/NHWAAttachmentReport.tsx b/src/webapp/reports/nhwa-attachments/NHWAAttachmentReport.tsx new file mode 100644 index 00000000..b9dc4d75 --- /dev/null +++ b/src/webapp/reports/nhwa-attachments/NHWAAttachmentReport.tsx @@ -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 ( +
+ + {i18n.t("NHWA Atachment Report")} + + + +
+ ); +}; + +const useStyles = makeStyles({ + wrapper: { padding: 20 }, +}); + +export default NHWAAttachmentReport; diff --git a/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx b/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx new file mode 100644 index 00000000..53e03866 --- /dev/null +++ b/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx @@ -0,0 +1,133 @@ +import { + PaginationOptions, + TableColumn, + TableGlobalAction, + TablePagination, + TableSorting, +} from "@eyeseetea/d2-ui-components"; +import StorageIcon from "@material-ui/icons/Storage"; +import _ from "lodash"; +import React from "react"; +import { sortByName } from "../../../../domain/common/entities/Base"; +import { Config, getMainUserPaths } from "../../../../domain/common/entities/Config"; +import { getOrgUnitIdsFromPaths } from "../../../../domain/common/entities/OrgUnit"; +import { Sorting } from "../../../../domain/common/entities/PaginatedObjects"; +import { DataAttachmentItem } from "../../../../domain/nhwa-attachments/entities/DataAttachmentItem"; +import i18n from "../../../../locales"; +import { TableConfig, useObjectsTable } from "../../../components/objects-list/objects-list-hooks"; +import { ObjectsList } from "../../../components/objects-list/ObjectsList"; +import { useAppContext } from "../../../contexts/app-context"; +import { useSnackbarOnError } from "../../../utils/snackbar"; +import { DataAttachmentsViewModel, getDataAttachmentsViews } from "../DataAttachmentsViewModel"; +import { DataValuesFilter } from "./Filters"; +import { FiltersBox } from "./FiltersBox"; + +export const DataAttachmentsList: React.FC = React.memo(() => { + const { compositionRoot, config } = useAppContext(); + const [filters, setFilters] = React.useState(() => getEmptyDataValuesFilter(config)); + const baseConfig = React.useMemo(getBaseListConfig, []); + const [sorting, setSorting] = React.useState>(); + + const getRows = React.useMemo( + () => async (paging: TablePagination, sorting: TableSorting) => { + const { pager, objects } = await compositionRoot.attachements.get({ + config, + paging: { page: paging.page, pageSize: paging.pageSize }, + sorting: getSortingFromTableSorting(sorting), + ...getUseCaseOptions(filters), + }); + setSorting(sorting); + return { pager, objects: getDataAttachmentsViews(config, objects) }; + }, + [config, compositionRoot, filters] + ); + + const getRowsWithSnackbarOrError = useSnackbarOnError(getRows); + const tableProps = useObjectsTable(baseConfig, getRowsWithSnackbarOrError); + const filterOptions = React.useMemo(() => getFilterOptions(config, filters), [config, filters]); + + const downloadCsv: TableGlobalAction = { + name: "downloadCsv", + text: "Download CSV", + icon: , + onClick: async () => { + if (!sorting) return; + // FUTURE: create a single use case that performs the get+saveCSV + const { objects: dataValues } = await compositionRoot.attachements.get({ + config, + paging: { page: 1, pageSize: 100000 }, + sorting: getSortingFromTableSorting(sorting), + ...getUseCaseOptions(filters), + }); + compositionRoot.attachements.save("data-values.csv", dataValues); + }, + }; + + return ( + {...tableProps} globalActions={[downloadCsv]}> + + + ); +}); + +function getUseCaseOptions(filter: DataValuesFilter) { + return { + ...filter, + orgUnitIds: getOrgUnitIdsFromPaths(filter.orgUnitPaths), + }; +} + +function getSortingFromTableSorting(sorting: TableSorting): Sorting { + return { + field: sorting.field === "id" ? "period" : sorting.field, + direction: sorting.order, + }; +} + +function getBaseListConfig(): TableConfig { + const paginationOptions: PaginationOptions = { + pageSizeOptions: [10, 20, 50], + pageSizeInitialValue: 10, + }; + + const initialSorting: TableSorting = { + field: "dataSet" as const, + order: "asc" as const, + }; + + const columns: TableColumn[] = [ + { name: "dataSet", text: i18n.t("Data set"), sortable: true }, + { name: "period", text: i18n.t("Period"), sortable: true }, + { name: "orgUnit", text: i18n.t("Organisation unit"), sortable: true }, + { name: "dataElement", text: i18n.t("Data Element"), sortable: true }, + { name: "link", text: i18n.t("link"), sortable: true }, + { name: "lastUpdated", text: i18n.t("Last updated"), sortable: true, hidden: true }, + { name: "storedBy", text: i18n.t("Stored by"), sortable: true, hidden: true }, + ]; + + return { columns, initialSorting, paginationOptions }; +} + +function getFilterOptions(config: Config, filters: DataValuesFilter) { + const { dataSetIds } = filters; + const sections = _(config.sectionsByDataSet) + .at(_.isEmpty(dataSetIds) ? _.keys(config.sectionsByDataSet) : dataSetIds) + .flatten() + .compact() + .uniqBy(section => section.id) + .value(); + + return { + periods: config.years, + dataSets: sortByName(_.values(config.dataSets)), + sections: sortByName(sections), + }; +} + +function getEmptyDataValuesFilter(config: Config): DataValuesFilter { + return { + orgUnitPaths: getMainUserPaths(config), + periods: [], + dataSetIds: [], + }; +} diff --git a/src/webapp/reports/nhwa-attachments/data-attachment-list/Filters.tsx b/src/webapp/reports/nhwa-attachments/data-attachment-list/Filters.tsx new file mode 100644 index 00000000..f5a336fa --- /dev/null +++ b/src/webapp/reports/nhwa-attachments/data-attachment-list/Filters.tsx @@ -0,0 +1,69 @@ +import React from "react"; +import i18n from "../../../../locales"; +import MultipleDropdown from "../../../components/dropdown/MultipleDropdown"; +import { Id, NamedRef } from "../../../../domain/common/entities/Base"; +import { useAppContext } from "../../../contexts/app-context"; +import { getRootIds } from "../../../../domain/common/entities/OrgUnit"; +import { OrgUnitsFilterButton } from "../../../components/org-units-filter/OrgUnitsFilterButton"; + +export interface DataValuesFiltersProps { + values: DataValuesFilter; + options: FilterOptions; + onChange(newFilters: DataValuesFilter): void; +} + +export interface DataValuesFilter { + orgUnitPaths: Id[]; + periods: string[]; + dataSetIds: Id[]; +} + +interface FilterOptions { + periods: string[]; + dataSets: NamedRef[]; +} + +export const Filters: React.FC = React.memo(props => { + const { config, api } = useAppContext(); + const { values: filter, options: filterOptions, onChange } = props; + const periodItems = useMemoOptionsFromStrings(filterOptions.periods); + const dataSetItems = useMemoOptionsFromNamedRef(filterOptions.dataSets); + const rootIds = React.useMemo(() => getRootIds(config.currentUser.orgUnits), [config]); + + return ( +
+ onChange({ ...filter, orgUnitPaths: paths })} + /> + + onChange({ ...filter, periods })} + label={i18n.t("Periods")} + /> + + onChange({ ...filter, dataSetIds })} + label={i18n.t("Data sets")} + /> +
+ ); +}); + +function useMemoOptionsFromStrings(options: string[]) { + return React.useMemo(() => { + return options.map(option => ({ value: option, text: option })); + }, [options]); +} + +function useMemoOptionsFromNamedRef(options: NamedRef[]) { + return React.useMemo(() => { + return options.map(option => ({ value: option.id, text: option.name })); + }, [options]); +} diff --git a/src/webapp/reports/nhwa-attachments/data-attachment-list/FiltersBox.tsx b/src/webapp/reports/nhwa-attachments/data-attachment-list/FiltersBox.tsx new file mode 100644 index 00000000..f050ca47 --- /dev/null +++ b/src/webapp/reports/nhwa-attachments/data-attachment-list/FiltersBox.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import _ from "lodash"; +import { IconButton } from "material-ui"; +import { FilterList } from "@material-ui/icons"; +import { Filters, DataValuesFiltersProps } from "./Filters"; +import { useBooleanState } from "../../../utils/use-boolean"; +import i18n from "../../../../locales"; + +export interface FiltersBoxProps extends DataValuesFiltersProps { + showToggleButton: boolean; +} + +export const FiltersBox: React.FC = React.memo(props => { + const { showToggleButton = true, ...otherProps } = props; + const areFiltersApplied = !_(props.values).values().every(_.isEmpty); + const [isFilterBoxVisible, { toggle: toggleFilterBoxVisibility }] = useBooleanState(false); + const filterIconColor = areFiltersApplied ? "#ff9800" : undefined; + const filterButtonColor = isFilterBoxVisible ? { backgroundColor: "#cdcdcd" } : undefined; + const areFiltersVisible = !showToggleButton || isFilterBoxVisible; + const filtersStyle = areFiltersVisible ? styles.filters.visible : styles.filters.hidden; + + return ( + + {showToggleButton && ( + + + + )} + +
+ +
+
+ ); +}); + +const styles = { + filters: { + visible: {}, + hidden: { display: "none" }, + }, +}; From f63c0daa87b91f15b24d990b504c219fc3a5f884 Mon Sep 17 00:00:00 2001 From: idelcano Date: Wed, 3 Aug 2022 09:48:08 +0200 Subject: [PATCH 2/8] remove dataelement and add working link --- i18n/en.pot | 10 +++++----- i18n/es.po | 8 ++++---- src/data/Dhis2ConfigRepository.ts | 2 +- src/data/NHWAAttachementsDefaultRepository .ts | 17 +++-------------- .../entities/DataAttachmentItem.ts | 5 ++--- .../DataAttachmentsViewModel.ts | 2 -- .../DataAttachmentsList.tsx | 8 ++++++-- 7 files changed, 21 insertions(+), 31 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 1d1ed09d..fb8db151 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -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-08-02T18:51:46.114Z\n" -"PO-Revision-Date: 2022-08-02T18:51:46.114Z\n" +"POT-Creation-Date: 2022-08-03T07:20:50.359Z\n" +"PO-Revision-Date: 2022-08-03T07:20:50.359Z\n" msgid "" msgstr "" @@ -128,9 +128,6 @@ msgstr "" msgid "NHWA Atachment Report" msgstr "" -msgid "Data Element" -msgstr "" - msgid "link" msgstr "" @@ -149,6 +146,9 @@ msgstr "" msgid "Section" msgstr "" +msgid "Data Element" +msgstr "" + msgid "Category option combo" msgstr "" diff --git a/i18n/es.po b/i18n/es.po index b380765d..df1b7cbf 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-02T18:51:46.114Z\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" @@ -128,9 +128,6 @@ msgstr "" msgid "NHWA Atachment Report" msgstr "" -msgid "Data Element" -msgstr "" - msgid "link" msgstr "" @@ -149,6 +146,9 @@ msgstr "" msgid "Section" msgstr "" +msgid "Data Element" +msgstr "" + msgid "Category option combo" msgstr "" diff --git a/src/data/Dhis2ConfigRepository.ts b/src/data/Dhis2ConfigRepository.ts index 91f759bf..ce266d4b 100644 --- a/src/data/Dhis2ConfigRepository.ts +++ b/src/data/Dhis2ConfigRepository.ts @@ -7,7 +7,7 @@ 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 (wip)"; +const SQL_VIEW_ATTACHEMENT_NAME = "NHWA attachments"; const base = { dataSets: { namePrefix: "NHWA", nameExcluded: /old$/ }, diff --git a/src/data/NHWAAttachementsDefaultRepository .ts b/src/data/NHWAAttachementsDefaultRepository .ts index f89beb4d..f566f0fb 100644 --- a/src/data/NHWAAttachementsDefaultRepository .ts +++ b/src/data/NHWAAttachementsDefaultRepository .ts @@ -18,21 +18,12 @@ interface Variables { orderByDirection: "asc" | "desc"; } -type SqlField = - | "datasetname" - | "dataelementid" - | "dataelementname" - | "link" - | "period" - | "storedby" - | "orgunit" - | "lastupdated"; +type SqlField = "datasetname" | "link" | "period" | "storedby" | "orgunit" | "lastupdated"; const fieldMapping: Record = { period: "period", orgUnit: "orgunit", dataSet: "datasetname", - dataElement: "dataelementname", link: "link", lastUpdated: "lastupdated", storedBy: "storedby", @@ -71,8 +62,7 @@ export class NHWAAttachementsDefaultRepository implements NHWADataAttachmentsRep period: dv.period.split("-")[0] ?? "", orgUnit: { name: dv.orgunit }, dataSet: { name: dv.datasetname }, - dataElement: { id: dv.dataelementid, name: dv.dataelementname }, - link: dv.link, + link: this.api.apiPath + dv.link, lastUpdated: new Date(dv.lastupdated), storedBy: dv.storedby, }) @@ -88,7 +78,6 @@ export class NHWAAttachementsDefaultRepository implements NHWADataAttachmentsRep period: dataValue.period, orgUnit: dataValue.orgUnit.name, dataSet: dataValue.dataSet.name, - dataElement: dataValue.dataElement.name, lastUpdated: dataValue.lastUpdated.toISOString(), storedBy: dataValue.storedBy, link: dataValue.link, @@ -103,7 +92,7 @@ export class NHWAAttachementsDefaultRepository implements NHWADataAttachmentsRep } } -const csvFields = ["dataSet", "period", "orgUnit", "dataElement", "link", "lastUpdated", "storedBy"] as const; +const csvFields = ["dataSet", "period", "orgUnit", "link", "lastUpdated", "storedBy"] as const; type CsvField = typeof csvFields[number]; diff --git a/src/domain/nhwa-attachments/entities/DataAttachmentItem.ts b/src/domain/nhwa-attachments/entities/DataAttachmentItem.ts index fda7592e..0cac0545 100644 --- a/src/domain/nhwa-attachments/entities/DataAttachmentItem.ts +++ b/src/domain/nhwa-attachments/entities/DataAttachmentItem.ts @@ -1,15 +1,14 @@ -import { Id, NamedRef, Named } from "../../common/entities/Base"; +import { Id, Named } from "../../common/entities/Base"; export interface DataAttachmentItem { period: string; orgUnit: Named; dataSet: Named; - dataElement: NamedRef; link: string; lastUpdated: Date; storedBy: string; } export function getDataAttachmentsItemId(dataValue: DataAttachmentItem): Id { - return [dataValue.dataElement, dataValue.period, dataValue.orgUnit.name].join("-"); + return [dataValue.dataSet, dataValue.period, dataValue.orgUnit.name].join("-"); } diff --git a/src/webapp/reports/nhwa-attachments/DataAttachmentsViewModel.ts b/src/webapp/reports/nhwa-attachments/DataAttachmentsViewModel.ts index 7f9bb2ad..c6b4bb32 100644 --- a/src/webapp/reports/nhwa-attachments/DataAttachmentsViewModel.ts +++ b/src/webapp/reports/nhwa-attachments/DataAttachmentsViewModel.ts @@ -9,7 +9,6 @@ export interface DataAttachmentsViewModel { period: string; orgUnit: string; dataSet: string; - dataElement: string; link: string; lastUpdated: string; storedBy: string; @@ -22,7 +21,6 @@ export function getDataAttachmentsViews(config: Config, dataValues: DataAttachme period: dataValue.period, orgUnit: dataValue.orgUnit.name, dataSet: dataValue.dataSet.name, - dataElement: dataValue.dataElement.name, link: dataValue.link || "", lastUpdated: dataValue.lastUpdated.toISOString(), storedBy: dataValue.storedBy, diff --git a/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx b/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx index 53e03866..76742895 100644 --- a/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx +++ b/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx @@ -99,8 +99,12 @@ function getBaseListConfig(): TableConfig { { name: "dataSet", text: i18n.t("Data set"), sortable: true }, { name: "period", text: i18n.t("Period"), sortable: true }, { name: "orgUnit", text: i18n.t("Organisation unit"), sortable: true }, - { name: "dataElement", text: i18n.t("Data Element"), sortable: true }, - { name: "link", text: i18n.t("link"), sortable: true }, + { + name: "link", + text: i18n.t("link"), + sortable: true, + getValue: model => link, + }, { name: "lastUpdated", text: i18n.t("Last updated"), sortable: true, hidden: true }, { name: "storedBy", text: i18n.t("Stored by"), sortable: true, hidden: true }, ]; From 08657e5dc88bbfef202cb89de0f81d0fdf31d212 Mon Sep 17 00:00:00 2001 From: idelcano Date: Wed, 3 Aug 2022 16:42:25 +0200 Subject: [PATCH 3/8] added variant name into readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f64abc21..f73fe22c 100644 --- a/README.md +++ b/README.md @@ -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 ``` From 37b90065a7731cce5f0a04207b2871f690e42338 Mon Sep 17 00:00:00 2001 From: idelcano Date: Wed, 3 Aug 2022 16:59:50 +0200 Subject: [PATCH 4/8] fix typo --- src/data/NHWAAttachementsDefaultRepository .ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/NHWAAttachementsDefaultRepository .ts b/src/data/NHWAAttachementsDefaultRepository .ts index f566f0fb..88d3a6c5 100644 --- a/src/data/NHWAAttachementsDefaultRepository .ts +++ b/src/data/NHWAAttachementsDefaultRepository .ts @@ -62,7 +62,7 @@ export class NHWAAttachementsDefaultRepository implements NHWADataAttachmentsRep period: dv.period.split("-")[0] ?? "", orgUnit: { name: dv.orgunit }, dataSet: { name: dv.datasetname }, - link: this.api.apiPath + dv.link, + link: this.api.apiPath + "/" + dv.link, lastUpdated: new Date(dv.lastupdated), storedBy: dv.storedby, }) From d9e0821e7255ad659ee5207e2ef3e94f615ae958 Mon Sep 17 00:00:00 2001 From: Eduardo Peredo Rivero Date: Thu, 23 Nov 2023 14:28:53 -0500 Subject: [PATCH 5/8] Reuse filter component --- .../nhwa-attachments/NHWAAttachmentReport.tsx | 2 +- .../DataAttachmentsList.tsx | 6 +- .../data-attachment-list/Filters.tsx | 69 ------------------- .../data-attachment-list/FiltersBox.tsx | 46 ------------- .../data-comments-list/DataCommentsList.tsx | 1 + .../data-comments-list/Filters.tsx | 15 ++-- 6 files changed, 15 insertions(+), 124 deletions(-) delete mode 100644 src/webapp/reports/nhwa-attachments/data-attachment-list/Filters.tsx delete mode 100644 src/webapp/reports/nhwa-attachments/data-attachment-list/FiltersBox.tsx diff --git a/src/webapp/reports/nhwa-attachments/NHWAAttachmentReport.tsx b/src/webapp/reports/nhwa-attachments/NHWAAttachmentReport.tsx index b9dc4d75..7582fac1 100644 --- a/src/webapp/reports/nhwa-attachments/NHWAAttachmentReport.tsx +++ b/src/webapp/reports/nhwa-attachments/NHWAAttachmentReport.tsx @@ -8,7 +8,7 @@ const NHWAAttachmentReport: React.FC = () => { return (
- {i18n.t("NHWA Atachment Report")} + {i18n.t("NHWA Attachment Report")} diff --git a/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx b/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx index 1a5eb004..a8d4a0fc 100644 --- a/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx +++ b/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx @@ -18,9 +18,9 @@ import { TableConfig, useObjectsTable } from "../../../components/objects-list/o import { ObjectsList } from "../../../components/objects-list/ObjectsList"; import { useAppContext } from "../../../contexts/app-context"; import { useSnackbarOnError } from "../../../utils/snackbar"; +import { FiltersBox } from "../../nhwa-comments/data-comments-list/FiltersBox"; import { DataAttachmentsViewModel, getDataAttachmentsViews } from "../DataAttachmentsViewModel"; -import { DataValuesFilter } from "./Filters"; -import { FiltersBox } from "./FiltersBox"; +import { DataValuesFilter } from "./../../nhwa-comments/data-comments-list/Filters"; export const DataAttachmentsList: React.FC = React.memo(() => { const { compositionRoot, config } = useAppContext(); @@ -133,5 +133,7 @@ function getEmptyDataValuesFilter(config: Config): DataValuesFilter { orgUnitPaths: getMainUserPaths(config), periods: [], dataSetIds: [], + sectionIds: [], + showSections: false, }; } diff --git a/src/webapp/reports/nhwa-attachments/data-attachment-list/Filters.tsx b/src/webapp/reports/nhwa-attachments/data-attachment-list/Filters.tsx deleted file mode 100644 index f5a336fa..00000000 --- a/src/webapp/reports/nhwa-attachments/data-attachment-list/Filters.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from "react"; -import i18n from "../../../../locales"; -import MultipleDropdown from "../../../components/dropdown/MultipleDropdown"; -import { Id, NamedRef } from "../../../../domain/common/entities/Base"; -import { useAppContext } from "../../../contexts/app-context"; -import { getRootIds } from "../../../../domain/common/entities/OrgUnit"; -import { OrgUnitsFilterButton } from "../../../components/org-units-filter/OrgUnitsFilterButton"; - -export interface DataValuesFiltersProps { - values: DataValuesFilter; - options: FilterOptions; - onChange(newFilters: DataValuesFilter): void; -} - -export interface DataValuesFilter { - orgUnitPaths: Id[]; - periods: string[]; - dataSetIds: Id[]; -} - -interface FilterOptions { - periods: string[]; - dataSets: NamedRef[]; -} - -export const Filters: React.FC = React.memo(props => { - const { config, api } = useAppContext(); - const { values: filter, options: filterOptions, onChange } = props; - const periodItems = useMemoOptionsFromStrings(filterOptions.periods); - const dataSetItems = useMemoOptionsFromNamedRef(filterOptions.dataSets); - const rootIds = React.useMemo(() => getRootIds(config.currentUser.orgUnits), [config]); - - return ( -
- onChange({ ...filter, orgUnitPaths: paths })} - /> - - onChange({ ...filter, periods })} - label={i18n.t("Periods")} - /> - - onChange({ ...filter, dataSetIds })} - label={i18n.t("Data sets")} - /> -
- ); -}); - -function useMemoOptionsFromStrings(options: string[]) { - return React.useMemo(() => { - return options.map(option => ({ value: option, text: option })); - }, [options]); -} - -function useMemoOptionsFromNamedRef(options: NamedRef[]) { - return React.useMemo(() => { - return options.map(option => ({ value: option.id, text: option.name })); - }, [options]); -} diff --git a/src/webapp/reports/nhwa-attachments/data-attachment-list/FiltersBox.tsx b/src/webapp/reports/nhwa-attachments/data-attachment-list/FiltersBox.tsx deleted file mode 100644 index f050ca47..00000000 --- a/src/webapp/reports/nhwa-attachments/data-attachment-list/FiltersBox.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from "react"; -import _ from "lodash"; -import { IconButton } from "material-ui"; -import { FilterList } from "@material-ui/icons"; -import { Filters, DataValuesFiltersProps } from "./Filters"; -import { useBooleanState } from "../../../utils/use-boolean"; -import i18n from "../../../../locales"; - -export interface FiltersBoxProps extends DataValuesFiltersProps { - showToggleButton: boolean; -} - -export const FiltersBox: React.FC = React.memo(props => { - const { showToggleButton = true, ...otherProps } = props; - const areFiltersApplied = !_(props.values).values().every(_.isEmpty); - const [isFilterBoxVisible, { toggle: toggleFilterBoxVisibility }] = useBooleanState(false); - const filterIconColor = areFiltersApplied ? "#ff9800" : undefined; - const filterButtonColor = isFilterBoxVisible ? { backgroundColor: "#cdcdcd" } : undefined; - const areFiltersVisible = !showToggleButton || isFilterBoxVisible; - const filtersStyle = areFiltersVisible ? styles.filters.visible : styles.filters.hidden; - - return ( - - {showToggleButton && ( - - - - )} - -
- -
-
- ); -}); - -const styles = { - filters: { - visible: {}, - hidden: { display: "none" }, - }, -}; diff --git a/src/webapp/reports/nhwa-comments/data-comments-list/DataCommentsList.tsx b/src/webapp/reports/nhwa-comments/data-comments-list/DataCommentsList.tsx index d20d1e9d..be25dd21 100644 --- a/src/webapp/reports/nhwa-comments/data-comments-list/DataCommentsList.tsx +++ b/src/webapp/reports/nhwa-comments/data-comments-list/DataCommentsList.tsx @@ -133,5 +133,6 @@ function getEmptyDataValuesFilter(config: Config): DataValuesFilter { periods: [], dataSetIds: [], sectionIds: [], + showSections: true, }; } diff --git a/src/webapp/reports/nhwa-comments/data-comments-list/Filters.tsx b/src/webapp/reports/nhwa-comments/data-comments-list/Filters.tsx index 1c98c780..32011c91 100644 --- a/src/webapp/reports/nhwa-comments/data-comments-list/Filters.tsx +++ b/src/webapp/reports/nhwa-comments/data-comments-list/Filters.tsx @@ -17,6 +17,7 @@ export interface DataValuesFilter { periods: string[]; dataSetIds: Id[]; sectionIds: Id[]; + showSections: boolean; } interface FilterOptions { @@ -56,12 +57,14 @@ export const Filters: React.FC = React.memo(props => { label={i18n.t("Data sets")} /> - onChange({ ...filter, sectionIds })} - label={i18n.t("Sections")} - /> + {filter.showSections && ( + onChange({ ...filter, sectionIds })} + label={i18n.t("Sections")} + /> + )}
); }); From 7a5ad29bebbb47626a7b8f4029e2d561a111e637 Mon Sep 17 00:00:00 2001 From: Eduardo Peredo Rivero Date: Thu, 30 Nov 2023 17:30:54 -0500 Subject: [PATCH 6/8] remove duplicate components, methods, entities and refactor code --- src/compositionRoot.ts | 4 +- .../NHWAAttachementsDefaultRepository .ts | 4 +- .../entities/DataAttachmentItem.ts | 3 +- .../usecases/ExportAttachmentsUseCase.ts | 13 ++++++ .../usecases/GetAttachementsUseCase.ts | 7 ++- .../usecases/SaveAttachementsUseCase.ts | 10 ----- .../DataAttachmentsViewModel.ts | 24 ----------- .../DataAttachmentsList.tsx | 43 +++++++------------ 8 files changed, 38 insertions(+), 70 deletions(-) create mode 100644 src/domain/nhwa-attachments/usecases/ExportAttachmentsUseCase.ts delete mode 100644 src/domain/nhwa-attachments/usecases/SaveAttachementsUseCase.ts diff --git a/src/compositionRoot.ts b/src/compositionRoot.ts index 778664bb..10915b54 100644 --- a/src/compositionRoot.ts +++ b/src/compositionRoot.ts @@ -82,7 +82,7 @@ import { ResetDataQualityValidation } from "./domain/reports/data-quality/usecas import { GetMonitoringDetailsUseCase } from "./domain/reports/mal-data-subscription/usecases/GetMonitoringDetailsUseCase"; import { NHWAAttachementsDefaultRepository } from "./data/NHWAAttachementsDefaultRepository "; import { GetAttachementsUseCase } from "./domain/nhwa-attachments/usecases/GetAttachementsUseCase"; -import { SaveAttachementsUseCase } from "./domain/nhwa-attachments/usecases/SaveAttachementsUseCase"; +import { ExportAttachmentsUseCase } from "./domain/nhwa-attachments/usecases/ExportAttachmentsUseCase"; export function getCompositionRoot(api: D2Api) { const configRepository = new Dhis2ConfigRepository(api, getReportType()); @@ -190,7 +190,7 @@ export function getCompositionRoot(api: D2Api) { }), attachments: getExecute({ get: new GetAttachementsUseCase(attachementRepository), - save: new SaveAttachementsUseCase(attachementRepository), + export: new ExportAttachmentsUseCase(attachementRepository), }), nhwa: { getAutoCompleteComputeValues: new GetAutoCompleteComputeValuesUseCase( diff --git a/src/data/NHWAAttachementsDefaultRepository .ts b/src/data/NHWAAttachementsDefaultRepository .ts index 59b9d8c5..45a5415a 100644 --- a/src/data/NHWAAttachementsDefaultRepository .ts +++ b/src/data/NHWAAttachementsDefaultRepository .ts @@ -20,9 +20,10 @@ interface Variables { orderByDirection: "asc" | "desc"; } -type SqlField = "datasetname" | "link" | "period" | "storedby" | "orgunit" | "lastupdated"; +type SqlField = "id" | "datasetname" | "link" | "period" | "storedby" | "orgunit" | "lastupdated"; const fieldMapping: Record = { + id: "id", period: "period", orgUnit: "orgunit", dataSet: "datasetname", @@ -61,6 +62,7 @@ export class NHWAAttachementsDefaultRepository implements NHWADataAttachmentsRep const dataValues: Array = 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 }, diff --git a/src/domain/nhwa-attachments/entities/DataAttachmentItem.ts b/src/domain/nhwa-attachments/entities/DataAttachmentItem.ts index 0cac0545..3f1d600a 100644 --- a/src/domain/nhwa-attachments/entities/DataAttachmentItem.ts +++ b/src/domain/nhwa-attachments/entities/DataAttachmentItem.ts @@ -1,6 +1,7 @@ import { Id, Named } from "../../common/entities/Base"; export interface DataAttachmentItem { + id: string; period: string; orgUnit: Named; dataSet: Named; @@ -10,5 +11,5 @@ export interface DataAttachmentItem { } export function getDataAttachmentsItemId(dataValue: DataAttachmentItem): Id { - return [dataValue.dataSet, dataValue.period, dataValue.orgUnit.name].join("-"); + return [dataValue.dataSet.name, dataValue.period, dataValue.orgUnit.name].join("-"); } diff --git a/src/domain/nhwa-attachments/usecases/ExportAttachmentsUseCase.ts b/src/domain/nhwa-attachments/usecases/ExportAttachmentsUseCase.ts new file mode 100644 index 00000000..a109ed9d --- /dev/null +++ b/src/domain/nhwa-attachments/usecases/ExportAttachmentsUseCase.ts @@ -0,0 +1,13 @@ +import { + NHWADataAttachmentsRepository, + NHWADataAttachmentsRepositoryGetOptions, +} from "../repositories/NHWADataAttachmentsRepository"; + +export class ExportAttachmentsUseCase { + constructor(private attachmentRepository: NHWADataAttachmentsRepository) {} + + async execute(options: NHWADataAttachmentsRepositoryGetOptions): Promise { + const attachments = await this.attachmentRepository.get(options); + await this.attachmentRepository.save("data-values.csv", attachments.objects); + } +} diff --git a/src/domain/nhwa-attachments/usecases/GetAttachementsUseCase.ts b/src/domain/nhwa-attachments/usecases/GetAttachementsUseCase.ts index 8ccdf9ea..23c65c16 100644 --- a/src/domain/nhwa-attachments/usecases/GetAttachementsUseCase.ts +++ b/src/domain/nhwa-attachments/usecases/GetAttachementsUseCase.ts @@ -8,10 +8,9 @@ import { PaginatedObjects } from "../../common/entities/PaginatedObjects"; type GetAttachementsUseCaseOptions = NHWADataAttachmentsRepositoryGetOptions; export class GetAttachementsUseCase { - constructor(private dataValueRepository: NHWADataAttachmentsRepository) {} + constructor(private attachmentRepository: NHWADataAttachmentsRepository) {} - execute(options: GetAttachementsUseCaseOptions): Promise> { - // FUTURE: Return a Future-like instead, to allow better error handling and cancellation. - return this.dataValueRepository.get(options); + async execute(options: GetAttachementsUseCaseOptions): Promise> { + return this.attachmentRepository.get(options); } } diff --git a/src/domain/nhwa-attachments/usecases/SaveAttachementsUseCase.ts b/src/domain/nhwa-attachments/usecases/SaveAttachementsUseCase.ts deleted file mode 100644 index 4d64ca19..00000000 --- a/src/domain/nhwa-attachments/usecases/SaveAttachementsUseCase.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DataAttachmentItem } from "../entities/DataAttachmentItem"; -import { NHWADataAttachmentsRepository } from "../repositories/NHWADataAttachmentsRepository"; - -export class SaveAttachementsUseCase { - constructor(private dataValueRepository: NHWADataAttachmentsRepository) {} - - async execute(filename: string, dataValues: DataAttachmentItem[]): Promise { - this.dataValueRepository.save(filename, dataValues); - } -} diff --git a/src/webapp/reports/nhwa-attachments/DataAttachmentsViewModel.ts b/src/webapp/reports/nhwa-attachments/DataAttachmentsViewModel.ts index c6b4bb32..b6a64bd5 100644 --- a/src/webapp/reports/nhwa-attachments/DataAttachmentsViewModel.ts +++ b/src/webapp/reports/nhwa-attachments/DataAttachmentsViewModel.ts @@ -3,27 +3,3 @@ import { DataAttachmentItem, getDataAttachmentsItemId, } from "../../../domain/nhwa-attachments/entities/DataAttachmentItem"; - -export interface DataAttachmentsViewModel { - id: string; - period: string; - orgUnit: string; - dataSet: string; - link: string; - lastUpdated: string; - storedBy: string; -} - -export function getDataAttachmentsViews(config: Config, dataValues: DataAttachmentItem[]): DataAttachmentsViewModel[] { - return dataValues.map(dataValue => { - return { - id: getDataAttachmentsItemId(dataValue), - period: dataValue.period, - orgUnit: dataValue.orgUnit.name, - dataSet: dataValue.dataSet.name, - link: dataValue.link || "", - lastUpdated: dataValue.lastUpdated.toISOString(), - storedBy: dataValue.storedBy, - }; - }); -} diff --git a/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx b/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx index a8d4a0fc..1e82dfec 100644 --- a/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx +++ b/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx @@ -19,17 +19,19 @@ import { ObjectsList } from "../../../components/objects-list/ObjectsList"; import { useAppContext } from "../../../contexts/app-context"; import { useSnackbarOnError } from "../../../utils/snackbar"; import { FiltersBox } from "../../nhwa-comments/data-comments-list/FiltersBox"; -import { DataAttachmentsViewModel, getDataAttachmentsViews } from "../DataAttachmentsViewModel"; import { DataValuesFilter } from "./../../nhwa-comments/data-comments-list/Filters"; export const DataAttachmentsList: React.FC = React.memo(() => { const { compositionRoot, config } = useAppContext(); const [filters, setFilters] = React.useState(() => getEmptyDataValuesFilter(config)); const baseConfig = React.useMemo(getBaseListConfig, []); - const [sorting, setSorting] = React.useState>(); + const [sorting, setSorting] = React.useState>({ + field: "dataSet", + order: "asc", + }); const getRows = React.useMemo( - () => async (paging: TablePagination, sorting: TableSorting) => { + () => async (paging: TablePagination, sorting: TableSorting) => { const { pager, objects } = await compositionRoot.attachments.get({ config, paging: { page: paging.page, pageSize: paging.pageSize }, @@ -37,7 +39,7 @@ export const DataAttachmentsList: React.FC = React.memo(() => { ...getUseCaseOptions(filters), }); setSorting(sorting); - return { pager, objects: getDataAttachmentsViews(config, objects) }; + return { pager, objects }; }, [config, compositionRoot, filters] ); @@ -51,51 +53,36 @@ export const DataAttachmentsList: React.FC = React.memo(() => { text: "Download CSV", icon: , onClick: async () => { - if (!sorting) return; - // FUTURE: create a single use case that performs the get+saveCSV - const { objects: dataValues } = await compositionRoot.attachments.get({ + await compositionRoot.attachments.export({ config, paging: { page: 1, pageSize: 100000 }, sorting: getSortingFromTableSorting(sorting), ...getUseCaseOptions(filters), }); - compositionRoot.attachments.save("data-values.csv", dataValues); }, }; return ( - {...tableProps} globalActions={[downloadCsv]}> + {...tableProps} globalActions={[downloadCsv]}> ); }); function getUseCaseOptions(filter: DataValuesFilter) { - return { - ...filter, - orgUnitIds: getOrgUnitIdsFromPaths(filter.orgUnitPaths), - }; + return { ...filter, orgUnitIds: getOrgUnitIdsFromPaths(filter.orgUnitPaths) }; } -function getSortingFromTableSorting(sorting: TableSorting): Sorting { - return { - field: sorting.field === "id" ? "period" : sorting.field, - direction: sorting.order, - }; +function getSortingFromTableSorting(sorting: TableSorting): Sorting { + return { field: sorting.field === "id" ? "period" : sorting.field, direction: sorting.order }; } -function getBaseListConfig(): TableConfig { - const paginationOptions: PaginationOptions = { - pageSizeOptions: [10, 20, 50], - pageSizeInitialValue: 10, - }; +function getBaseListConfig(): TableConfig { + const paginationOptions: PaginationOptions = { pageSizeOptions: [10, 20, 50], pageSizeInitialValue: 10 }; - const initialSorting: TableSorting = { - field: "dataSet" as const, - order: "asc" as const, - }; + const initialSorting: TableSorting = { field: "dataSet" as const, order: "asc" as const }; - const columns: TableColumn[] = [ + const columns: TableColumn[] = [ { name: "dataSet", text: i18n.t("Data set"), sortable: true }, { name: "period", text: i18n.t("Period"), sortable: true }, { name: "orgUnit", text: i18n.t("Organisation unit"), sortable: true }, From c7d7601ba6e40895cc5e98224d9153beb934abac Mon Sep 17 00:00:00 2001 From: Eduardo Peredo Rivero Date: Fri, 1 Dec 2023 11:13:12 -0500 Subject: [PATCH 7/8] add download file option --- .../data-attachment-list/DataAttachmentsList.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx b/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx index 1e82dfec..75b07b9d 100644 --- a/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx +++ b/src/webapp/reports/nhwa-attachments/data-attachment-list/DataAttachmentsList.tsx @@ -6,6 +6,7 @@ import { TableSorting, } from "@eyeseetea/d2-ui-components"; import StorageIcon from "@material-ui/icons/Storage"; +import CloudDownload from "@material-ui/icons/CloudDownload"; import _ from "lodash"; import React from "react"; import { sortByName } from "../../../../domain/common/entities/Base"; @@ -88,9 +89,13 @@ function getBaseListConfig(): TableConfig { { name: "orgUnit", text: i18n.t("Organisation unit"), sortable: true }, { name: "link", - text: i18n.t("link"), + text: i18n.t("File"), sortable: true, - getValue: model => link, + getValue: model => ( + + + + ), }, { name: "lastUpdated", text: i18n.t("Last updated"), sortable: true, hidden: true }, { name: "storedBy", text: i18n.t("Stored by"), sortable: true, hidden: true }, From d36dc903462040b78797f121d04c8460c8736e16 Mon Sep 17 00:00:00 2001 From: Eduardo Peredo Rivero Date: Fri, 1 Dec 2023 11:22:55 -0500 Subject: [PATCH 8/8] update i18n --- i18n/en.pot | 46 +++++++++++++++++----------------------------- i18n/es.po | 48 +++++++++++++++++------------------------------- 2 files changed, 34 insertions(+), 60 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 75a346ce..21450941 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -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 "" msgstr "" @@ -100,9 +100,6 @@ msgstr "" msgid "Period" msgstr "" -msgid "Period" -msgstr "" - msgid "CSY Audit Filters - Emergency Care" msgstr "" @@ -532,22 +529,6 @@ 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" msgid "Malaria Data Subscription Report" msgstr "" @@ -620,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 "" @@ -652,14 +648,6 @@ msgstr "" msgid "Value" msgstr "" -msgid "Sections" -msgstr "" -msgid "Last updated" -msgstr "" - -msgid "Stored by" -msgstr "" - msgid "Toggle filters" msgstr "" diff --git a/i18n/es.po b/i18n/es.po index 2c9407d6..d6ba8233 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -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" @@ -100,9 +100,6 @@ msgstr "" msgid "Period" msgstr "" -msgid "Period" -msgstr "" - msgid "CSY Audit Filters - Emergency Care" msgstr "" @@ -532,22 +529,6 @@ 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" msgid "Malaria Data Subscription Report" msgstr "" @@ -620,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 "" @@ -652,24 +648,14 @@ msgstr "" msgid "Value" msgstr "" -msgid "Sections" -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 ""