Skip to content

Commit

Permalink
feat: full plugins support with experimental release
Browse files Browse the repository at this point in the history
  • Loading branch information
eirikhaugstulen committed Jan 2, 2024
1 parent faba67f commit dcdecd9
Show file tree
Hide file tree
Showing 15 changed files with 193 additions and 77 deletions.
6 changes: 3 additions & 3 deletions d2.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const config = {
name: 'capture-1312',
title: 'Capture - 1312',
name: 'capture',
title: 'Capture',
type: 'app',

id: '92b75fd0-34cc-451c-942f-3dd0f283bcbd',
minDHIS2Version: '2.38',
coreApp: false,
coreApp: true,

entryPoints: {
app: './src/index',
Expand Down
7 changes: 2 additions & 5 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: 2023-12-13T09:06:59.302Z\n"
"PO-Revision-Date: 2023-12-13T09:06:59.302Z\n"
"POT-Creation-Date: 2023-12-19T14:37:21.237Z\n"
"PO-Revision-Date: 2023-12-19T14:37:21.237Z\n"

msgid "Choose one or more dates..."
msgstr "Choose one or more dates..."
Expand Down 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
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,40 @@
// @flow
import React from 'react';
import i18n from '@dhis2/d2-i18n';
import React, { useEffect, useRef, useState } from 'react';
// $FlowFixMe - Export will be part of the next release
import { Plugin } from '@dhis2/app-runtime';
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
// eslint-disable-next-line consistent-return
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
@@ -1,30 +1,22 @@
// @flow

import { useDataEngine } from '@dhis2/app-runtime';
import { useQuery } from 'react-query';
import { useApiMetadataQuery } from '../../../../../../utils/reactQueryHelpers';

type Props = {|
selectedScopeId: string,
|}

const configQuery = {
dataEntryFormConfigQuery: {
resource: 'dataStore/capture/dataEntryForms',
},
resource: 'dataStore/capture/dataEntryForms',
};

export const useDataEntryFormConfig = ({ selectedScopeId }: Props) => {
const dataEngine = useDataEngine();


const { data: dataEntryFormConfig, isFetched: configIsFetched } = useQuery(
['dataEntryFormConfig'],
() => dataEngine.query(configQuery),
const { data: dataEntryFormConfig, isFetched: configIsFetched } = useApiMetadataQuery(
['dataEntryFormConfig', selectedScopeId],
configQuery,
{
enabled: !!selectedScopeId,
select: ({ dataEntryFormConfigQuery }) => dataEntryFormConfigQuery?.[selectedScopeId],
cacheTime: Infinity,
staleTime: Infinity,
select: dataEntryFormConfigQuery => dataEntryFormConfigQuery[selectedScopeId],
},
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,19 @@ type DefaultComponents = 'QuickActions'
| 'ProfileWidget'
| 'EnrollmentWidget';

export type ColumnConfig = {
type: $Values<typeof WidgetTypes>,
export type DefaultWidgetColumnComfig = {
type: typeof WidgetTypes.COMPONENT,
name: DefaultComponents,
settings?: Object,
}

export type PluginWidgetColumnConfig = {
type: typeof WidgetTypes.PLUGIN,
source: string,
}

export type ColumnConfig = DefaultWidgetColumnComfig | PluginWidgetColumnConfig;

export type PageLayoutConfig = {
title: ?string,
leftColumn: ?Array<ColumnConfig>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
// @flow
import React, { useCallback, useMemo } from 'react';
import log from 'loglevel';
import { Plugin } from '@dhis2/app-runtime';
import { errorCreator } from '../../../../../../../capture-core-utils';

import { WidgetTypes } from '../DefaultEnrollmentLayout.constants';
import type { ColumnConfig, PageLayoutConfig, WidgetConfig } from '../DefaultEnrollmentLayout.types';
import { Widget } from '../../../../../Widget';
import { EnrollmentPlugin } from '../../../EnrollmentPlugin';
import type {
ColumnConfig,
PageLayoutConfig,
WidgetConfig,
DefaultWidgetColumnComfig,
PluginWidgetColumnConfig,
} from '../DefaultEnrollmentLayout.types';

type Props = {
pageLayout: PageLayoutConfig,
Expand All @@ -21,7 +26,8 @@ const renderWidget = (widget: ColumnConfig, availableWidgets, props) => {
const { type } = widget;

if (type.toLowerCase() === WidgetTypes.COMPONENT) {
const { name, settings = {} } = widget;
// Manually casting type to DefaultWidgetColumnComfig
const { name, settings = {} } = ((widget: any): DefaultWidgetColumnComfig);
const widgetConfig = availableWidgets[name];

if (!widgetConfig) {
Expand Down Expand Up @@ -62,15 +68,31 @@ const renderWidget = (widget: ColumnConfig, availableWidgets, props) => {
/>
);
} else if (type.toLowerCase() === WidgetTypes.PLUGIN) {
const { source } = widget;
// Manually casting type to PluginWidgetColumnConfig
const { source } = ((widget: any): PluginWidgetColumnConfig);
let PluginWidget = MemoizedWidgets[source];

if (!PluginWidget) {
PluginWidget = Plugin;
PluginWidget = EnrollmentPlugin;
MemoizedWidgets[source] = (PluginWidget);
}

return <PluginWidget key={source} pluginSource={source} />
const getProps = ({ program, enrollmentId, teiId, orgUnitId }) => ({
programId: program.id,
enrollmentId,
teiId,
orgUnitId,
});

const widgetProps = getProps(props);

return (
<PluginWidget
key={source}
pluginSource={source}
{...widgetProps}
/>
);
}

log.error(errorCreator(`Widget type ${type} is not supported`)({ type }));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// @flow
import React, { useEffect, useRef, useState } from 'react';
// $FlowFixMe - Export will be available in next app-runtime release
import { Plugin } from '@dhis2/app-runtime';
import { useHistory } from 'react-router-dom';

type EnrollmentPluginProps = {|
enrollmentId: string,
programId?: string,
teiId: string,
orgUnitId: string,
pluginSource: string,
|};

export const EnrollmentPlugin = ({ pluginSource, ...passOnProps }: EnrollmentPluginProps) => {
const [pluginWidth, setPluginWidth] = useState(undefined);
const history = useHistory();
const containerRef = useRef<?HTMLDivElement>();

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
// eslint-disable-next-line consistent-return
return () => {
resizeObserver.unobserve(container);
resizeObserver.disconnect();
};
}, [containerRef]);

return (
<div ref={containerRef}>
<Plugin
pluginSource={pluginSource}
width={pluginWidth}
navigate={history.push}
{...passOnProps}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @flow
export { EnrollmentPlugin } from './EnrollmentPlugin';
Loading

0 comments on commit dcdecd9

Please sign in to comment.