Skip to content

Commit

Permalink
feat: [DHIS2-15475] Form Field Plugins (#3502)
Browse files Browse the repository at this point in the history
  • Loading branch information
eirikhaugstulen authored Jan 26, 2024
1 parent 817ba28 commit 2ccb87b
Show file tree
Hide file tree
Showing 13 changed files with 3,635 additions and 2,425 deletions.
3 changes: 0 additions & 3 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,6 @@ msgstr "error encountered during field validation"
msgid "error"
msgstr "error"

msgid "Plugins are not yet available - Please contact your system administrator"
msgstr "Plugins are not yet available - Please contact your system administrator"

msgid "This value is validating"
msgstr "This value is validating"

Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
"@babel/preset-react": "^7.16.7",
"@badeball/cypress-cucumber-preprocessor": "17.2.1",
"@cypress/webpack-preprocessor": "^6.0.0",
"@dhis2/cli-app-scripts": "^9.0.1",
"@dhis2/cli-app-scripts": "^10.4.0",
"@dhis2/cli-helpers-engine": "^3.2.1",
"@dhis2/cli-style": "^10.4.1",
"@dhis2/cli-utils-cypress": "^9.0.2",
Expand Down Expand Up @@ -132,6 +132,9 @@
"wait-on": "^6.0.1"
},
"resolutions": {
"@dhis2/cli-app-scripts": "^10.4.0",
"@dhis2/app-runtime": "^3.10.2",
"react-scripts": "4.0.3",
"@babel/preset-react": "7.16.7",
"@dhis2/ui": "^9.1.1",
"@js-temporal/polyfill": "0.4.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,11 @@ export class D2SectionFieldsComponent extends Component<Props> {
component: FormFieldPlugin,
plugin: true,
props: {
pluginId: metaDataElement.id,
name: metaDataElement.name,
pluginSource: metaDataElement.pluginSource,
fieldsMetadata: metaDataElement.fields,
customAttributes: metaDataElement.customAttributes,
formId: props.formId,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -537,17 +537,21 @@ export class FormBuilder extends React.Component<Props> {
pluginContext,
} = this.props;
const {
fieldsMetadata,
pluginId,
pluginSource,
fieldsMetadata,
customAttributes,
name,
formId,
} = field.props;

return (
<field.component
name={name}
fieldsMetadata={fieldsMetadata}
pluginId={pluginId}
customAttributes={customAttributes}
pluginSource={pluginSource}
fieldsMetadata={fieldsMetadata}
formId={formId}
onUpdateField={this.commitFieldUpdateFromPlugin.bind(this)}
pluginContext={pluginContext}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
// @flow
import React from 'react';
import i18n from '@dhis2/d2-i18n';
import React, { useEffect, useRef, useState } from 'react';
import { Plugin } from '@dhis2/app-runtime/build/es/experimental';
import type { ComponentProps } from './FormFieldPlugin.types';

export const FormFieldPluginComponent = (props: ComponentProps) => {
// eslint-disable-next-line no-unused-vars
const { pluginSource, ...passOnProps } = props;
const containerRef = useRef<?HTMLDivElement>(null);
const [pluginWidth, setPluginWidth] = useState(0);

useEffect(() => {
const { current: container } = containerRef;
if (!container) return () => {};

const resizeObserver = new ResizeObserver((entries) => {
entries.forEach(entry => setPluginWidth(entry.contentRect.width));
});

resizeObserver.observe(container);

// Cleanup function
return () => {
resizeObserver.unobserve(container);
resizeObserver.disconnect();
};
}, [containerRef]);


return (
<p>
{i18n.t('Plugins are not yet available - Please contact your system administrator')}
</p>
<div ref={containerRef}>
<Plugin
pluginSource={pluginSource}
width={pluginWidth}
{...passOnProps}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @flow
import React, { useEffect, useMemo } from 'react';
import React, { useMemo } from 'react';
import { FormFieldPluginComponent } from './FormFieldPlugin.component';
import type { ContainerProps } from './FormFieldPlugin.types';
import { usePluginMessages } from './hooks/usePluginMessages';
Expand All @@ -9,7 +9,14 @@ import { formatPluginConfig } from './formatPluginConfig';
import { useLocationQuery } from '../../../utils/routing';

export const FormFieldPlugin = (props: ContainerProps) => {
const { pluginSource, fieldsMetadata, formId, onUpdateField, pluginContext } = props;
const {
pluginSource,
fieldsMetadata,
formId,
onUpdateField,
customAttributes,
pluginContext,
} = props;
const metadataByPluginId = useMemo(() => Object.fromEntries(fieldsMetadata), [fieldsMetadata]);
const configuredPluginIds = useMemo(() => Object.keys(metadataByPluginId), [metadataByPluginId]);
const { orgUnitId } = useLocationQuery();
Expand All @@ -24,20 +31,18 @@ export const FormFieldPlugin = (props: ContainerProps) => {
pluginContext,
});

// Expanding iframe height temporarily to fit content - LIBS-487
useEffect(() => {
const iframe = document.querySelector('iframe');
if (iframe) iframe.style.height = '500px';
}, []);

// Remove ids from plugin metadata before passing to plugin
const formattedMetadata = useMemo(() => {
const metadata = [...fieldsMetadata.entries()];
return metadata.reduce((acc, [pluginId, pluginMetadata]) => {
const formattedPluginMetadata = formatPluginConfig(pluginMetadata, { keysToOmit: ['id'] });
return { ...acc, [pluginId]: formattedPluginMetadata };

return metadata.reduce((acc, [pluginFieldId, pluginMetadata]) => {
const formattedPluginMetadata = formatPluginConfig(pluginMetadata, {
attributes: customAttributes,
keysToOmit: ['id', 'dataElement', 'section'],
});
return { ...acc, [pluginFieldId]: formattedPluginMetadata };
}, {});
}, [fieldsMetadata]);
}, [customAttributes, fieldsMetadata]);

return (
<FormFieldPluginComponent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type PluginFormFieldMetadata = {|
searchable: ?boolean;
url: ?string;
attributeValues?: { [pluginId: string]: any }

|}

type FieldValueOptions = {|
Expand All @@ -45,10 +46,12 @@ export type PluginContext = {|
|}

export type ContainerProps = {|
pluginId: string,
pluginSource: string,
fieldsMetadata: Map<string, PluginFormFieldMetadata>,
pluginContext: PluginContext,
formId: string,
customAttributes: { [id: string]: { IdFromPlugin: string, IdFromApp: string } },
onUpdateField: (fieldMetadata: PluginFormFieldMetadata, value: any, options?: FieldValueOptions) => void,
|}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,17 @@ export const formatPluginConfig = <TConfigReturn = PluginFormFieldMetadata>(
}

// Recursively process nested objects and arrays
if (value && typeof value === 'object') {
if (Array.isArray(value)) {
acc[modifiedKey] = value.map(removeUnderscoreFromObjectAttributes).filter(Boolean);
if (value !== null) {
if (typeof value === 'object') {
if (Array.isArray(value)) {
acc[modifiedKey] = value.map(removeUnderscoreFromObjectAttributes)
.filter(Boolean);
} else {
acc[modifiedKey] = removeUnderscoreFromObjectAttributes(value);
}
} else {
acc[modifiedKey] = removeUnderscoreFromObjectAttributes(value);
acc[modifiedKey] = value;
}
} else {
acc[modifiedKey] = value;
}

return acc;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { MetadataByPluginId, PluginContext } from '../FormFieldPlugin.types
export const usePluginValues = (
formId: string,
metadataByPluginId: MetadataByPluginId,
pluginContext: PluginContext,
pluginContext: PluginContext = {},
) => {
const formValuesRedux = useSelector(({ formsValues }) => formsValues[formId]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
/* eslint-disable no-restricted-syntax */

import isFunction from 'd2-utilizr/lib/isFunction';
import type { PluginFormFieldMetadata } from '../../components/D2Form/FormFieldPlugin';
import { DataElement } from '../DataElement';

export class FormFieldPluginConfig {
_id: string;
_name: string;
_pluginSource: string;
_fields: Map<string, PluginFormFieldMetadata>;
_fields: Map<string, DataElement>;
_customAttributes: Map<string, { IdFromPlugin: string, IdFromApp: string }>;

constructor(initFn: ?(_this: FormFieldPluginConfig) => void) {
initFn && isFunction(initFn) && initFn(this);
Expand All @@ -32,23 +33,31 @@ export class FormFieldPluginConfig {
this._name = value;
}

get fields(): Map<string, PluginFormFieldMetadata> {
get fields(): Map<string, DataElement> {
return this._fields;
}

set fields(value: Map<string, PluginFormFieldMetadata>) {
set fields(value: Map<string, DataElement>) {
this._fields = value;
}

get pluginSource(): string {
return this._pluginSource;
}

get customAttributes(): Map<string, { IdFromPlugin: string, IdFromApp: string }> {
return this._customAttributes;
}

set customAttributes(value: Map<string, { IdFromPlugin: string, IdFromApp: string }>) {
this._customAttributes = value;
}

set pluginSource(value: string) {
this._pluginSource = value;
}

addField(idFromPlugin: string, field: PluginFormFieldMetadata) {
addField(idFromPlugin: string, field: DataElement) {
if (!this.fields.has(idFromPlugin)) {
this.fields.set(idFromPlugin, field);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { FormFieldPluginConfig } from '../../../../metaData/FormFieldPluginConfi
import type { DataEntryFormConfig } from '../../../../components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm/types';
import { FormFieldTypes } from '../../../../components/D2Form/FormFieldPlugin/FormFieldPlugin.const';
import {
formatPluginConfig,
} from '../../../../components/D2Form/FormFieldPlugin/formatPluginConfig';
FieldElementObjectTypes,
} from '../../../../components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm';

export class EnrollmentFactory {
static errorMessages = {
Expand Down Expand Up @@ -141,28 +141,27 @@ export class EnrollmentFactory {
// $FlowFixMe
await cachedProgramTrackedEntityAttributes.asyncForEach(async (trackedEntityAttribute) => {
if (trackedEntityAttribute?.type === FormFieldTypes.PLUGIN) {
const attributes = trackedEntityAttribute.fieldMap
.filter(attributeField => attributeField.objectType === FieldElementObjectTypes.ATTRIBUTE)
.reduce((acc, attribute) => {
acc[attribute.IdFromApp] = attribute;
return acc;
}, {});

const element = new FormFieldPluginConfig((o) => {
o.id = trackedEntityAttribute.id;
o.name = trackedEntityAttribute.name;
o.pluginSource = trackedEntityAttribute.pluginSource;
o.fields = new Map();
o.customAttributes = attributes;
});

const attributes = trackedEntityAttribute.fieldMap
.filter(attributeField => attributeField.objectType === 'Attribute')
.reduce((acc, attribute) => {
acc[attribute.IdFromApp] = attribute;
return acc;
}, {});

await trackedEntityAttribute.fieldMap.asyncForEach(async (field) => {
if (field.objectType) {
const fieldElement = await this.dataElementFactory.build(field);
if (field.objectType && field.objectType === FieldElementObjectTypes.TRACKED_ENTITY_ATTRIBUTE) {
const fieldElement = await this.dataElementFactory.build(field, section);
if (!fieldElement) return;

const fieldMetadata = formatPluginConfig(fieldElement, { attributes });

element.addField(field.IdFromPlugin, fieldMetadata);
element.addField(field.IdFromPlugin, fieldElement);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import type { ConstructorInput } from './teiRegistrationFactory.types';
import { FormFieldPluginConfig } from '../../../../metaData/FormFieldPluginConfig';
import type { DataEntryFormConfig } from '../../../../components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm/types';
import { FormFieldTypes } from '../../../../components/D2Form/FormFieldPlugin/FormFieldPlugin.const';
import { formatPluginConfig } from '../../../../components/D2Form/FormFieldPlugin/formatPluginConfig';
import {
FieldElementObjectTypes,
} from '../../../../components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm';
Expand Down Expand Up @@ -123,27 +122,27 @@ export class TeiRegistrationFactory {

await fieldElements.asyncForEach(async (trackedEntityAttribute) => {
if (trackedEntityAttribute?.type === FormFieldTypes.PLUGIN) {
const attributes = trackedEntityAttribute.fieldMap
.filter(attributeField => attributeField.objectType === 'Attribute')
.reduce((acc, attribute) => {
acc[attribute.IdFromApp] = attribute;
return acc;
}, {});
const element = new FormFieldPluginConfig((o) => {
o.id = trackedEntityAttribute.id;
o.name = trackedEntityAttribute.name;
o.pluginSource = trackedEntityAttribute.pluginSource;
o.fields = new Map();
o.customAttributes = attributes;
});

const attributes = trackedEntityAttribute.fieldMap
.filter(attributeField => attributeField.objectType === 'Attribute')
.reduce((acc, attribute) => {
acc[attribute.IdFromApp] = attribute;
return acc;
}, {});

await trackedEntityAttribute.fieldMap.asyncForEach(async (field) => {
if (field.objectType === FieldElementObjectTypes.TRACKED_ENTITY_ATTRIBUTE) {
const dataElement = await this.dataElementFactory.build(field);
if (!dataElement) return;

const fieldMetadata = formatPluginConfig(dataElement, { attributes });
element.addField(field.IdFromPlugin, fieldMetadata);
element.addField(field.IdFromPlugin, dataElement);
}
});

Expand Down
Loading

0 comments on commit 2ccb87b

Please sign in to comment.