Skip to content

Commit

Permalink
feat: [DHIS2-17591][DHIS2-17607] Plugins in event forms (#3684)
Browse files Browse the repository at this point in the history
  • Loading branch information
eirikhaugstulen authored Aug 7, 2024
1 parent 24be388 commit 205b9f5
Show file tree
Hide file tree
Showing 38 changed files with 492 additions and 165 deletions.
7 changes: 5 additions & 2 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: 2024-08-02T09:44:11.640Z\n"
"PO-Revision-Date: 2024-08-02T09:44:11.640Z\n"
"POT-Creation-Date: 2024-06-18T22:47:46.585Z\n"
"PO-Revision-Date: 2024-06-18T22:47:46.585Z\n"

msgid "Choose one or more dates..."
msgstr "Choose one or more dates..."
Expand Down Expand Up @@ -982,6 +982,9 @@ msgstr "Could not retrieve metadata. Please try again later."
msgid "The enrollment event data could not be found"
msgstr "The enrollment event data could not be found"

msgid "Loading"
msgstr "Loading"

msgid "Possible duplicates found"
msgstr "Possible duplicates found"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ class D2Form extends React.PureComponent<PropsForPureComponent> {
renderHorizontal = (section: Section, passOnProps: any) => (
<D2SectionContainer
key={section.id}
innerRef={(sectionInstance) => { this.setSectionInstance(sectionInstance, section.id); }}
innerRef={(sectionInstance) => {
this.setSectionInstance(sectionInstance, section.id);
}}
sectionMetaData={section}
validationStrategy={this.props.formFoundation.validationStrategy}
formId={this.getFormId()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export class D2SectionFieldsComponent extends Component<Props> {
fieldsMetadata: metaDataElement.fields,
customAttributes: metaDataElement.customAttributes,
formId: props.formId,
viewMode: props.viewMode,
},
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,7 @@ export class FormBuilder extends React.Component<Props> {
customAttributes,
name,
formId,
viewMode,
} = field.props;

return (
Expand All @@ -578,6 +579,7 @@ export class FormBuilder extends React.Component<Props> {
pluginSource={pluginSource}
fieldsMetadata={fieldsMetadata}
formId={formId}
viewMode={viewMode}
onUpdateField={this.commitFieldUpdateFromPlugin.bind(this)}
pluginContext={pluginContext}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
export const PluginErrorMessages = Object.freeze({
SET_FIELD_VALUE_MISSING_ID: 'setFieldValue: missing required fieldId',
SET_FIELD_VALUE_ID_NOT_ALLOWED: 'setFieldValue: fieldId must be one of the configured plugin ids',
SET_CONTEXT_FIELD_VALUE_MISSING_ID: 'setContextFieldValue: tried to set value for a field that does not exist in the plugin context',
});

export const FormFieldTypes = Object.freeze({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const FormFieldPlugin = (props: ContainerProps) => {
onUpdateField,
customAttributes,
pluginContext,
viewMode = false,
} = props;
const metadataByPluginId = useMemo(() => Object.fromEntries(fieldsMetadata), [fieldsMetadata]);
const configuredPluginIds = useMemo(() => Object.keys(metadataByPluginId), [metadataByPluginId]);
Expand Down Expand Up @@ -55,6 +56,7 @@ export const FormFieldPlugin = (props: ContainerProps) => {
setContextFieldValue={setContextFieldValue}
errors={errors}
warnings={warnings}
viewMode={viewMode}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,5 @@ export type ComponentProps = {|
errors: { [id: string]: Array<string> },
warnings: { [id: string]: Array<string> },
setContextFieldValue: (SetFieldValueProps) => void,
viewMode: boolean,
|}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,16 @@ export const usePluginCallbacks = ({
}, [configuredPluginIds, metadataByPluginId, onUpdateField]);

const setContextFieldValue = useCallback(({ fieldId, value }: SetFieldValueProps) => {
pluginContext[fieldId]?.setDataEntryFieldValue(value);
const contextField = pluginContext[fieldId];

if (!contextField) {
log.error(errorCreator(
PluginErrorMessages.SET_CONTEXT_FIELD_VALUE_MISSING_ID,
)({ fieldId, value }));
return;
}

contextField?.setDataEntryFieldValue(value);
}, [pluginContext]);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ import typeof { newEventSaveTypes } from './newEventSaveTypes';
const makeMapStateToProps = () => {
const programNameSelector = makeProgramNameSelector();

const mapStateToProps = (state: ReduxState, props: Object) =>
({ recentlyAddedRelationshipId: state.newEventPage.recentlyAddedRelationshipId,
ready: !state.activePage.isDataEntryLoading,
error: !props.formFoundation ?
i18n.t('This is not an event program or the metadata is corrupt. See log for details.') : null,
programName: programNameSelector(state),
orgUnitName: state.organisationUnits[state.currentSelections.orgUnitId] &&
const mapStateToProps = (state: ReduxState, props: Object) => ({
recentlyAddedRelationshipId: state.newEventPage.recentlyAddedRelationshipId,
ready: !state.activePage.isDataEntryLoading,
error: !props.formFoundation ?
i18n.t('This is not an event program or the metadata is corrupt. See log for details.') : null,
programName: programNameSelector(state),
orgUnitName: state.organisationUnits[state.currentSelections.orgUnitId] &&
state.organisationUnits[state.currentSelections.orgUnitId].name,
});
});


// $FlowFixMe[not-an-object] automated comment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useCoreOrgUnit } from '../../../../metadataRetrieval/coreOrgUnit';
import { useLocationQuery } from '../../../../utils/routing';
import { useRulesEngine } from './useRulesEngine';
import type { PlainProps } from './NewEventDataEntryWrapper.types';
import { useMetadataForProgramStage } from '../../common/ProgramStage/useMetadataForProgramStage';

const getStyles = () => ({
flexContainer: {
Expand Down Expand Up @@ -41,13 +42,12 @@ const getStyles = () => ({

const NewEventDataEntryWrapperPlain = ({
classes,
formFoundation,
formHorizontal,
stage,
onFormLayoutDirectionChange,
}: PlainProps) => {
const { id: programId } = useCurrentProgramInfo();
const orgUnitId = useLocationQuery().orgUnitId;
const { formFoundation, stage } = useMetadataForProgramStage({ programId });
const { orgUnit, error } = useCoreOrgUnit(orgUnitId);
const rulesReady = useRulesEngine({ programId, orgUnit, formFoundation });
const titleText = useScopeTitleText(programId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,13 @@ import { NewEventDataEntryWrapperComponent } from './NewEventDataEntryWrapper.co
import {
setNewEventFormLayoutDirection,
} from './newEventDataEntryWrapper.actions';
import {
makeStageSelector,
} from './newEventDataEntryWrapper.selectors';
import { getDataEntryHasChanges } from '../getNewEventDataEntryHasChanges';
import type { Props, ContainerProps, StateProps, MapStateToProps } from './NewEventDataEntryWrapper.types';

const makeMapStateToProps = (): MapStateToProps => {
const stageSelector = makeStageSelector();

return (state: ReduxState): StateProps => {
const stage = stageSelector(state);
const formFoundation = stage && stage.stageForm ? stage.stageForm : null;
return ({
stage,
formFoundation,
dataEntryHasChanges: getDataEntryHasChanges(state),
formHorizontal: (formFoundation && formFoundation.customForm ? false : !!state.newEventPage.formHorizontal),
});
};
};
const makeMapStateToProps = (): MapStateToProps => (state: ReduxState): StateProps => ({
dataEntryHasChanges: getDataEntryHasChanges(state),
formHorizontal: !!state.newEventPage.formHorizontal,
});

const mapDispatchToProps = (dispatch: ReduxDispatch) => ({
onFormLayoutDirectionChange: (formHorizontal: boolean) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @flow
import type { ProgramStage, RenderFoundation } from '../../../../metaData';

export type PlainProps = {|
...CssClasses,
Expand All @@ -10,18 +9,15 @@ export type Props = {|
dataEntryHasChanges: boolean,
formHorizontal: ?boolean,
onFormLayoutDirectionChange: (formHorizontal: boolean) => void,
formFoundation: ?RenderFoundation,
stage: ?ProgramStage,
|}

export type StateProps = {|
dataEntryHasChanges: boolean,
formHorizontal: ?boolean,
formFoundation: ?RenderFoundation,
stage: ?ProgramStage,
|}

export type ContainerProps = {|

|};

export type MapStateToProps = (ReduxState, ContainerProps) => StateProps;

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const useRulesEngine = ({
}: {
programId: string,
orgUnit: ?OrgUnit,
formFoundation: RenderFoundation,
formFoundation: ?RenderFoundation,
}) => {
const dispatch = useDispatch();
const program = useMemo(() => programId && getEventProgramThrowIfNotFound(programId), [programId]);
Expand All @@ -25,7 +25,7 @@ export const useRulesEngine = ({
// Refactor the helper methods (getCurrentClientValues, getCurrentClientMainData in rules/actionsCreator) to be more explicit with the arguments.
const state = useSelector(stateArg => stateArg);
useEffect(() => {
if (orgUnit && program) {
if (orgUnit && program && !!formFoundation) {
dispatch(batchActions([
getRulesActions({
state,
Expand All @@ -42,6 +42,7 @@ export const useRulesEngine = ({
dispatch,
program,
orgUnit,
formFoundation,
]);

return !!orgUnit && orgUnitRef.current === orgUnit;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// @flow
import type {
CachedDataElement,
CachedOptionSet,
CachedProgramStage,
} from '../../../../storageControllers';
import type { DataEntryFormConfig } from '../TEIAndEnrollment';
import { getUserStorageController, userStores } from '../../../../storageControllers';
import { ProgramStageFactory } from '../../../../metaDataMemoryStoreBuilders/programs/factory/programStage';

export const buildProgramStageMetadata = async ({
cachedProgramStage,
programId,
cachedDataElements,
cachedOptionSets,
locale,
minorServerVersion,
dataEntryFormConfig,
}: {
cachedProgramStage: CachedProgramStage,
programId: string,
cachedOptionSets: Array<CachedOptionSet>,
cachedDataElements: Array<CachedDataElement>,
dataEntryFormConfig: ?DataEntryFormConfig,
locale: string,
minorServerVersion: number,
}) => {
const storageController = getUserStorageController();

const cachedRelationshipTypes = await storageController.getAll(userStores.RELATIONSHIP_TYPES);

const programStageFactory = new ProgramStageFactory({
cachedOptionSets: new Map<string, CachedOptionSet>(cachedOptionSets.map(optionSet => [optionSet.id, optionSet])),
cachedRelationshipTypes,
cachedDataElements: new Map<string, CachedDataElement>(cachedDataElements.map(dataElement => [dataElement.id, dataElement])),
locale,
minorServerVersion,
dataEntryFormConfig,
});

return programStageFactory.build(
cachedProgramStage,
programId,
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @flow

export { useDataElementsForStage } from './useDataElementsForStage';
export { buildProgramStageMetadata } from './buildProgramStageMetadata';
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// @flow
import { useIndexedDBQuery } from '../../../../utils/reactQueryHelpers';
import { getUserStorageController, userStores } from '../../../../storageControllers';

type Props = {|
programId: string,
dataElementIds: Array<string>,
stageId?: string,
|}

const getDataElementsForStage = async ({
dataElementIds,
}) => {
const storageController = getUserStorageController();

return storageController.getAll(userStores.DATA_ELEMENTS, {
predicate: dataElement => dataElementIds.includes(dataElement.id),
});
};

export const useDataElementsForStage = ({
dataElementIds,
programId,
stageId,
}: Props) => {
const { data, isLoading } = useIndexedDBQuery(
// $FlowFixMe
[programId, 'dataElements', stageId, { dataElementIds }],
() => getDataElementsForStage({
dataElementIds,
}),
{
enabled: !!dataElementIds,
},
);

return {
dataElements: data,
isLoading,
};
};
Loading

0 comments on commit 205b9f5

Please sign in to comment.