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
22 changes: 18 additions & 4 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ 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-05-06T05:50:16.003Z\n"
"PO-Revision-Date: 2022-05-06T05:50:16.003Z\n"

msgid ""
"ERROR Dataelement with UID '{{dataElementId}}' does not exist in dataset "
"with UID '{{dataSetId}}'"
msgstr ""

msgid "<No value>"
msgstr ""
Expand Down Expand Up @@ -77,10 +82,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 @@ -154,3 +159,12 @@ msgstr ""

msgid "Toggle filters"
msgstr ""

msgid "No errors detected"
msgstr ""

msgid "Custom Form Validation"
msgstr ""

msgid "Select custom form to validate..."
msgstr ""
20 changes: 17 additions & 3 deletions i18n/es.po
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: i18next-conv\n"
"POT-Creation-Date: 2022-01-10T08:43:46.954Z\n"
"POT-Creation-Date: 2022-05-06T05:50:16.003Z\n"
"PO-Revision-Date: 2018-10-25T09:02:35.143Z\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"

msgid ""
"ERROR Dataelement with UID '{{dataElementId}}' does not exist in dataset "
"with UID '{{dataSetId}}'"
msgstr ""

msgid "<No value>"
msgstr ""

Expand Down Expand Up @@ -77,10 +82,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 @@ -155,6 +160,15 @@ msgstr ""
msgid "Toggle filters"
msgstr ""

msgid "No errors detected"
msgstr ""

msgid "Custom Form Validation"
msgstr ""

msgid "Select custom form to validate..."
msgstr ""

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

Expand Down
6 changes: 6 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 { DataSetsDefaultRepository } from "./data/DataSetsDefaultRepository";
import { NHWADataApprovalDefaultRepository } from "./data/NHWADataApprovalDefaultRepository";
import { NHWADataCommentsDefaultRepository } from "./data/NHWADataCommentsDefaultRepository";
import { WIDPAdminDefaultRepository } from "./data/WIDPAdminDefaultRepository";
Expand All @@ -15,13 +16,15 @@ import { SaveDataSetsUseCase } from "./domain/nhwa-approval-status/usecases/Save
import { GetDataValuesUseCase } from "./domain/nhwa-comments/usecases/GetDataValuesUseCase";
import { SaveDataValuesUseCase } from "./domain/nhwa-comments/usecases/SaveDataValuesCsvUseCase";
import { D2Api } from "./types/d2-api";
import { GetValidatedDataSetsUseCase } from "./domain/validatecustomforms/usecases/GetValidatedDataSetsUseCase";

export function getCompositionRoot(api: D2Api) {
const configRepository = new Dhis2ConfigRepository(api);
const dataCommentsRepository = new NHWADataCommentsDefaultRepository(api);
const dataApprovalRepository = new NHWADataApprovalDefaultRepository(api);
const widpAdminDefaultRepository = new WIDPAdminDefaultRepository(api);
const orgUnitsRepository = new Dhis2OrgUnitsRepository(api);
const dataSetsRepositoryDefaultRepository = new DataSetsDefaultRepository(api);

return {
admin: getExecute({
Expand All @@ -42,6 +45,9 @@ export function getCompositionRoot(api: D2Api) {
orgUnits: getExecute({
get: new GetOrgUnitsUseCase(orgUnitsRepository),
}),
validateCustomForm: getExecute({
get: new GetValidatedDataSetsUseCase(dataSetsRepositoryDefaultRepository),
}),
config: getExecute({
get: new GetConfig(configRepository),
}),
Expand Down
75 changes: 75 additions & 0 deletions src/data/DataSetsDefaultRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import _ from "lodash";
import { DataSetsRepository } from "../domain/validatecustomforms/repositories/DataSetsRepository";
import i18n from "../locales";
import { D2Api } from "../types/d2-api";

export class DataSetsDefaultRepository implements DataSetsRepository {
constructor(private api: D2Api) {}

async validate(id: string): Promise<string[]> {
const metadata$ = this.api.metadata.get({
dataSets: {
fields: {
id: true,
displayName: true,
dataEntryForm: { htmlCode: true },
dataSetElements: {
dataElement: { id: true, categoryCombo: { id: true } },
},
},
},
dataElements: {
fields: { id: true, categoryCombo: { id: true } },
},
categoryCombos: {
fields: { id: true, categoryOptionCombos: { id: true } },
},
});

const { dataSets, categoryCombos, dataElements } = await metadata$.getData();

const dataSet = dataSets.find(dataset => dataset.id === id);
const htmlCode = dataSet?.dataEntryForm.htmlCode;
const newRegExp = new RegExp(/(([a-zA-Z0-9]){11})-(([A-Za-zA-Z0-9]){11})-(val)/g);

const matches = htmlCode?.match(newRegExp);
const customFormIds = _(matches).map(item => {
return { dataElementId: item.split("-")[0] ?? "-", categoryOptionComboId: item.split("-")[1] ?? "-" };
}).commit().value();

const categoryCombosById = _.keyBy(categoryCombos, cc => cc.id);
const dataElementsDataSetById = _.keyBy(dataSet?.dataSetElements, cc => cc.dataElement.id);
const dataElementsById = _.keyBy(dataElements, cc => cc.id);

const errors = _.map(customFormIds, dataElementFromCustomForm => {
const dataElement = dataElementsDataSetById[dataElementFromCustomForm.dataElementId]?.dataElement;
if (dataElement) {
const categoryCombo =
dataElement.categoryCombo.id ??
dataElementsById[dataElementFromCustomForm.dataElementId]?.categoryCombo.id;
const isValid = categoryCombosById[categoryCombo ?? ""]?.categoryOptionCombos.find(
coc => coc.id === dataElementFromCustomForm.categoryOptionComboId
);
if (!isValid) {
return (
i18n.t("ERROR Dataelement with UID:") +
" " +
dataElementFromCustomForm["dataElementId"] +
" " +
i18n.t("is not associated with CategoryOptionComboID:") +
" " +
dataElementFromCustomForm["categoryOptionComboId"]
);
Copy link
Contributor

Choose a reason for hiding this comment

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

i18n.t interpolation with {{ var }}

}
} else {
return i18n.t(
"ERROR Dataelement with UID '{{dataElementId}}' does not exist in dataset with UID '{{dataSetId}}'",
{ dataElementId: dataElementFromCustomForm.dataElementId, dataSetId: id, nsSeparator: false }
);
}
});
const newerror = _.compact(errors);

return _.uniq(newerror);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface DataSetsRepository {
validate(id: string): Promise<string[]>;
Copy link
Contributor

Choose a reason for hiding this comment

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

validateCustomForm

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { DataSetsRepository } from "../repositories/DataSetsRepository";

export class GetValidatedDataSetsUseCase {
constructor(private dataSetsRepository: DataSetsRepository) {}

execute(id: string): Promise<string[]> {
// FUTURE: Return a Future-like instead, to allow better error handling and cancellation.
return this.dataSetsRepository.validate(id);
}
}
79 changes: 79 additions & 0 deletions src/webapp/components/select/Select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
FormControl,
InputLabel,
MenuItem,
Select as MuiSelect,
SelectProps as MuiSelectProps,
} from "@material-ui/core";
import { createStyles, makeStyles } from "@material-ui/core/styles";
import _ from "lodash";
import React, { useMemo, useState } from "react";

export type SelectOption = { value: string; label: string };

export interface SelectProps extends Omit<MuiSelectProps, "onChange"> {
placeholder?: string;
options: Array<SelectOption>;
onChange: (option: SelectOption) => void;
defaultValue?: SelectOption;
value?: string;
allowEmpty?: boolean;
emptyLabel?: string;
}

export const Select: React.FC<SelectProps> = ({
placeholder,
options,
onChange,
defaultValue,
value,
allowEmpty = false,
emptyLabel = "",
...rest
}) => {
const classes = useStyles();
const [stateValue, setValue] = useState(defaultValue ? defaultValue.value : "");
const optionsByValue = useMemo(() => _.keyBy(options, option => option.value), [options]);
const defaultOption = allowEmpty ? { label: "", value: "" } : undefined;

const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
const newValue = event.target.value as string;
const option = _(optionsByValue).get(newValue, defaultOption);
setValue(newValue);
if (option) onChange(option);
};

const defaultLabel = allowEmpty ? emptyLabel : placeholder;

return (
<div>
<FormControl className={classes.formControl}>
{!!placeholder && <InputLabel id="demo-simple-select-label">{placeholder}</InputLabel>}
<MuiSelect onChange={handleChange} value={value ?? stateValue} autoWidth={true} {...rest}>
{!!defaultLabel && (
<MenuItem value="" disabled={!allowEmpty} className={classes.menuItem}>
{defaultLabel}
</MenuItem>
)}
{options.map(option => (
<MenuItem key={option.value} value={option.value} className={classes.menuItem}>
{option.label}
</MenuItem>
))}
</MuiSelect>
</FormControl>
</div>
);
};

const useStyles = makeStyles(() =>
createStyles({
formControl: {
margin: 0,
display: "flex",
},
menuItem: {
minHeight: 35,
},
})
);
4 changes: 4 additions & 0 deletions src/webapp/reports/Reports.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ValidateCustomFormsReport from "./validate-custom-forms/ValidateCustomFormsReport";

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

Expand All @@ -16,6 +17,9 @@ const Component: React.FC = () => {
case "admin": {
return <AdminReport />;
}
case "validate-custom-forms": {
return <ValidateCustomFormsReport />;
}
default: {
return <p>{`Please provide a valid REACT_APP_REPORT_VARIANT`}</p>;
}
Expand Down
Loading