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

feat: [DHIS-11419] display assigned users on events in enrollment overview page #3453

Merged
merged 11 commits into from
Dec 18, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const query = {
id: ({ id }) => id,
params: {
fields:
['programStages[id,repeatable,hideDueDate,programStageDataElements[displayInReports,dataElement[id,valueType,displayName,displayFormName,optionSet[options[code,name]]]'],
['programStages[id,repeatable,hideDueDate,enableUserAssignment,programStageDataElements[displayInReports,dataElement[id,valueType,displayName,displayFormName,optionSet[options[code,name]]]'],
Copy link
Member

Choose a reason for hiding this comment

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

Do you know the reason why useProgramMetadata is retrieving data from the api instead of using the IndexedDB cache? If not, can you change it to use useIndexedDBQuery?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure, but it could perhaps be related to translations, i.e. displayName needs to come from the server?

Copy link
Member

Choose a reason for hiding this comment

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

We have a ticket to fix the displayName cache problem. Great if you change this one to use useIndexedDBQuery

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@JoakimSM programStageDataElements in IndexedDB doesn't contain the dataelement subvalues like valueType and optionSet. Do you think we can solve that with local data as well?

Copy link
Member

Choose a reason for hiding this comment

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

If I understand you correctly, there is a separate store/table for data elements. Should be retrieved from there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed the metadata source in this case from the server to IndexedDB. The code got rather complicated the way I did it though. Got any suggestions for making it less complicated?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think it looks too bad. But, what is the advantage of using your custom useQueryStyleEvaluation instead of useIndexedDBQuery like we do in useProgramFromIndexedDB?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's just how the caching is done. I think useIndexedDBQuery generally uses time to determine when to clear out a cache entry, which in this case doesn't feel very natural to me. useQueryStyleEvaluation does the caching like useMemo would.

Copy link
Member

Choose a reason for hiding this comment

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

It would be beneficial to have one approach for this and currently that is useIndexedDBQuery. If we don't like the approach there, we can look into what we don't like and change it for all queries to IndexedDB. But let's start by using useIndexedDBQuery.

},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const useProgramStages = (program: Program, programStages?: Array<apiProg
if (program && programStages) {
program.stages.forEach((item) => {
const { id, name, icon, stageForm } = item;
const { hideDueDate, programStageDataElements, repeatable } = programStages.find(p => p.id === id) || {};
const { hideDueDate, programStageDataElements, repeatable, enableUserAssignment } = programStages.find(p => p.id === id) || {};
if (!programStageDataElements) {
log.error(errorCreator(i18n.t('Program stage not found'))(id));
} else {
Expand All @@ -20,6 +20,7 @@ export const useProgramStages = (program: Program, programStages?: Array<apiProg
icon,
hideDueDate,
repeatable,
enableUserAssignment,
description: stageForm.description,
dataElements: programStageDataElements?.reduce((acc, currentStageData) => {
const { displayInReports, dataElement } = currentStageData;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const hideProgramStage = (ruleEffects, stageId) => (

export const StagePlain = ({ stage, events, classes, className, onCreateNew, ruleEffects, ...passOnProps }: Props) => {
const [open, setOpenStatus] = useState(true);
const { id, name, icon, description, dataElements, hideDueDate, repeatable } = stage;
const { id, name, icon, description, dataElements, hideDueDate, repeatable, enableUserAssignment } = stage;
const hiddenProgramStage = hideProgramStage(ruleEffects, id);

return (
Expand All @@ -57,6 +57,7 @@ export const StagePlain = ({ stage, events, classes, className, onCreateNew, rul
dataElements={dataElements}
hideDueDate={hideDueDate}
repeatable={repeatable}
enableUserAssignment={enableUserAssignment}
onCreateNew={onCreateNew}
hiddenProgramStage={hiddenProgramStage}
{...passOnProps}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const StageDetailPlain = (props: Props) => {
dataElements,
hideDueDate = false,
repeatable = false,
enableUserAssignment = false,
onEventClick,
onViewAll,
onCreateNew,
Expand All @@ -76,7 +77,7 @@ const StageDetailPlain = (props: Props) => {
sortDirection: SORT_DIRECTION.DESC,
};
const { stage } = getProgramAndStageForProgram(programId, stageId);
const headerColumns = useComputeHeaderColumn(dataElements, hideDueDate, stage?.stageForm);
const headerColumns = useComputeHeaderColumn(dataElements, hideDueDate, enableUserAssignment, stage?.stageForm);
const { loading, value: dataSource, error } = useComputeDataFromEvent(dataElements, events);


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@ import {
import { SORT_DIRECTION, MULIT_TEXT_WITH_NO_OPTIONS_SET } from './constants';
import { isNotValidOptionSet } from '../../../../../../utils/isNotValidOptionSet';

const baseKeys = [{ id: 'status' }, { id: 'occurredAt' }, { id: 'orgUnitName' }, { id: 'scheduledAt' }, { id: 'comments' }];
const baseKeys = [{ id: 'status' }, { id: 'occurredAt' }, { id: 'assignedUser' }, { id: 'orgUnitName' }, { id: 'scheduledAt' }, { id: 'comments' }];
const basedFieldTypes = [
{ type: dataElementTypes.STATUS, resolveValue: convertStatusForView },
{ type: dataElementTypes.DATE },
{ type: 'ASSIGNEE' },
{ type: dataElementTypes.TEXT },
{ type: dataElementTypes.DATE },
{ type: dataElementTypes.UNKNOWN, resolveValue: convertCommentForView },
];
const getBaseColumnHeaders = props => [
{ header: i18n.t('Status'), sortDirection: SORT_DIRECTION.DEFAULT, isPredefined: true },
{ header: props.formFoundation.getLabel('occurredAt'), sortDirection: SORT_DIRECTION.DEFAULT, isPredefined: true },
{ header: i18n.t('Assigned to'), sortDirection: SORT_DIRECTION.DEFAULT, isPredefined: true },
{ header: i18n.t('Registering unit'), sortDirection: SORT_DIRECTION.DEFAULT, isPredefined: true },
{ header: props.formFoundation.getLabel('scheduledAt'), sortDirection: SORT_DIRECTION.DEFAULT, isPredefined: true },
{ header: '', sortDirection: null, isPredefined: true },
Expand Down Expand Up @@ -110,7 +112,7 @@ const useComputeDataFromEvent = (dataElements: Array<StageDataElement>, events:
};


const useComputeHeaderColumn = (dataElements: Array<StageDataElement>, hideDueDate: boolean, formFoundation: Object) => {
const useComputeHeaderColumn = (dataElements: Array<StageDataElement>, hideDueDate: boolean, enableUserAssignment: boolean, formFoundation: Object) => {
const headerColumns = useMemo(() => {
const dataElementHeaders = dataElements.reduce((acc, currDataElement) => {
const { id, name, formName, type, optionSet } = currDataElement;
Expand All @@ -124,9 +126,10 @@ const useComputeHeaderColumn = (dataElements: Array<StageDataElement>, hideDueDa
return acc;
}, []);
return [
...getBaseColumns({ formFoundation }).filter(col => (hideDueDate ? col.id !== 'scheduledAt' : true)),
...getBaseColumns({ formFoundation })
.filter(col => (enableUserAssignment || col.id !== 'assignedUser') && (!hideDueDate || col.id !== 'scheduledAt')),
...dataElementHeaders];
}, [dataElements, hideDueDate, formFoundation]);
}, [dataElements, hideDueDate, enableUserAssignment, formFoundation]);

return headerColumns;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { StageDataElement, StageCommonProps } from '../../../types/common.t
eventName: string,
hideDueDate?: boolean,
repeatable?: boolean,
enableUserAssignment?: boolean,
stageId: string,
hiddenProgramStage?: boolean,
...CssClasses,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type Stage = {
description?: ?string,
icon?: Icon,
dataElements: Array<StageDataElement>,
enableUserAssignment: boolean,
hideDueDate?: boolean,
repeatable?: boolean
}
Expand Down
2 changes: 1 addition & 1 deletion src/core_modules/capture-core/converters/clientToList.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const valueConvertersForType = {
[dataElementTypes.FILE_RESOURCE]: convertResourceForDisplay,
[dataElementTypes.IMAGE]: convertResourceForDisplay,
[dataElementTypes.ORGANISATION_UNIT]: (rawValue: Object) => rawValue.name,
[dataElementTypes.ASSIGNEE]: (rawValue: Object) => `${rawValue.name} (${rawValue.username})`,
[dataElementTypes.ASSIGNEE]: (rawValue: Object) => `${rawValue.name || rawValue.displayName} (${rawValue.username})`,
Copy link
Contributor

Choose a reason for hiding this comment

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

NIT: There is an edge case where displayName is undefined. To reproduce, create a new enrollment event, you will be redirected to the enrollment overview and the undefined (undefined) text will appear in the program stage table. You must refresh the page to see the correct value of displayName.

Screenshot 2023-11-01 at 15 58 47

This happens because only the uid property is stored in Redux (within enrollmentDomain) when the event is created. The solution is quite straightforward and I already needed to fix it in one of the other related tickets in the epic DHIS2-15480.

Thanks!

Copy link
Contributor Author

@superskip superskip Nov 2, 2023

Choose a reason for hiding this comment

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

Thanks @simonadomnisoru! Implemented the fix so that it won't come up as an issue during testing. I tried undoing the change I did on this line, but that didn't work. Seems like removing rawValue.name works, though. But it also seems like a risky thing to do. Do you think it would break something?

Copy link
Contributor

@simonadomnisoru simonadomnisoru Nov 3, 2023

Choose a reason for hiding this comment

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

To undo the change, you can add a converter for dataElementTypes.ASSIGNEE inside serverToClient. Something like this should work:

    [dataElementTypes.ASSIGNEE]: (serverValue: ApiAssignedUser) => ({
        id: serverValue.uid,
        name: serverValue.displayName,
        username: serverValue.username,
        firstName: serverValue.firstName,
        surname: serverValue.surname,
    }),

This ensures that when the assignedUser value reaches the clientToList converter, it is in the correct format. Thanks!

[dataElementTypes.NUMBER_RANGE]: convertNumberRangeForDisplay,
[dataElementTypes.STATUS]: convertStatusForDisplay,
};
Expand Down
Loading