Skip to content

Commit

Permalink
Strict typing for textResourceBindings and dataModelBindings (#1315)
Browse files Browse the repository at this point in the history
Co-authored-by: Ole Martin Handeland <git@olemartin.org>
  • Loading branch information
olemartinorg and Ole Martin Handeland authored Jul 7, 2023
1 parent c09cc43 commit b2892e6
Show file tree
Hide file tree
Showing 82 changed files with 384 additions and 313 deletions.
2 changes: 1 addition & 1 deletion src/components/table/AltinnMobileTableItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { AltinnStudioTheme } from 'src/theme/altinnStudioTheme';
import { useResolvedNode } from 'src/utils/layout/ExprContext';
import type { IUseLanguage } from 'src/hooks/useLanguage';
import type { ILayoutGroup } from 'src/layout/Group/types';
import type { ITextResourceBindings } from 'src/types';
import type { ITextResourceBindings } from 'src/layout/layout';

export interface IMobileTableItem {
key: React.Key;
Expand Down
2 changes: 1 addition & 1 deletion src/features/attachments/delete/deleteAttachmentActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export interface IDeleteAttachmentAction {
attachment: IAttachment;
attachmentType: string;
componentId: string;
dataModelBindings: IDataModelBindings | undefined;
dataModelBindings: IDataModelBindings<'FileUpload' | 'FileUploadWithTag'> | undefined;
}

export interface IDeleteAttachmentActionFulfilled {
Expand Down
2 changes: 1 addition & 1 deletion src/features/attachments/upload/uploadAttachmentActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface IUploadAttachmentAction {
attachmentType: string;
tmpAttachmentId: string;
componentId: string;
dataModelBindings?: IDataModelBindings;
dataModelBindings?: IDataModelBindings<'FileUpload' | 'FileUploadWithTag'>;
index: number;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function NodeInspectorDataModelBindings({ dataModelBindings }: Props) {
const schema = useBindingSchema(dataModelBindings);
const formData = useAppSelector((state) => state.formData.formData);
const asObject = dot.object(structuredClone(formData || {}));
const bindings = dataModelBindings || {};

return (
<Value
Expand All @@ -24,19 +25,17 @@ export function NodeInspectorDataModelBindings({ dataModelBindings }: Props) {
className={classes.typeObject}
>
<dl className={classes.propertyList}>
{Object.keys(dataModelBindings).map((key) => (
{Object.keys(bindings).map((key) => (
<Value
key={key}
property={key}
className={classes.typeLongString}
>
<em>Råverdi: </em>
{dataModelBindings[key]}
{bindings[key]}
<br />
<em>Resultat: </em>
<div className={classes.json}>
{JSON.stringify(dot.pick(dataModelBindings[key], asObject) || null, null, 2)}
</div>
<div className={classes.json}>{JSON.stringify(dot.pick(bindings[key], asObject) || null, null, 2)}</div>
<br />
<em>Datamodell: </em>
<div className={classes.json}>{JSON.stringify(schema?.[key] || null, null, 2)}</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Value } from 'src/features/devtools/components/NodeInspector/NodeInspec
import { canBeExpression } from 'src/features/expressions/validation';
import { useAppSelector } from 'src/hooks/useAppSelector';
import { useLanguage } from 'src/hooks/useLanguage';
import type { ITextResourceBindings } from 'src/types';
import type { ITextResourceBindings } from 'src/layout/layout';
import type { LayoutNode } from 'src/utils/layout/LayoutNode';

interface Props {
Expand All @@ -17,7 +17,7 @@ export function NodeInspectorTextResourceBindings({ node, textResourceBindings }
const textResources = useAppSelector((state) => state.textResources.resources);
const { langAsString } = useLanguage();

let actualTextResourceBindings = textResourceBindings;
let actualTextResourceBindings = textResourceBindings || {};
let isRepGroup = false;
if (node.isRepGroup()) {
// Text resource bindings are resolved per-row for repeating groups. We'll show the
Expand All @@ -44,7 +44,7 @@ export function NodeInspectorTextResourceBindings({ node, textResourceBindings }
</div>
)}
{Object.keys(actualTextResourceBindings).map((key) => {
const inResources = textResources.find((resource) => resource.id === textResourceBindings[key]);
const inResources = textResources.find((resource) => resource.id === actualTextResourceBindings[key]);
const value = actualTextResourceBindings[key];
const isExpression = canBeExpression(value, true);

Expand Down
5 changes: 3 additions & 2 deletions src/features/expressions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type {
FuncDef,
} from 'src/features/expressions/types';
import type { ILayoutGroup } from 'src/layout/Group/types';
import type { ILayoutComponent } from 'src/layout/layout';
import type { IDataModelBindings, ILayoutComponent } from 'src/layout/layout';
import type { IAuthContext, IInstanceContext } from 'src/types/shared';

export interface EvalExprOptions {
Expand Down Expand Up @@ -476,7 +476,8 @@ export const ExprFunctions = {
const node = this.failWithoutNode();
const closestComponent = node.closest((c) => c.id === id || c.baseComponentId === id);
const component = closestComponent ?? (node instanceof LayoutPage ? node.findById(id) : node.top.findById(id));
const binding = component?.item?.dataModelBindings?.simpleBinding;
const dataModelBindings = (component?.item.dataModelBindings as IDataModelBindings) || undefined;
const binding = dataModelBindings?.simpleBinding;
if (component && binding) {
if (component.isHidden()) {
return null;
Expand Down
2 changes: 1 addition & 1 deletion src/features/formData/formDataTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ export interface IUpdateFormData {
export interface IDeleteAttachmentReference {
attachmentId: string;
componentId: string;
dataModelBindings: IDataModelBindings;
dataModelBindings: IDataModelBindings<'FileUpload' | 'FileUploadWithTag'>;
}
10 changes: 5 additions & 5 deletions src/hooks/useBindingSchema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,27 @@ type AsSchema<T> = {

export function useBindingSchema<T extends IDataModelBindings | undefined>(bindings: T): AsSchema<T> | undefined {
const currentSchema = useDataModelSchema();

const resolvedBindings = bindings && Object.values(bindings).length ? { ...bindings } : undefined;

if (resolvedBindings && currentSchema) {
const out = {} as AsSchema<T>;
for (const [key, value] of Object.entries(resolvedBindings)) {
// Converts dot-notation to JsonPointer (including support for repeating groups)
const schemaPath = `/${value.replace(/\./g, '/')}`.replace(/\[(\d+)]\//g, (...a) => `/${a[1]}/`);

try {
const bindingSchema = currentSchema?.getSchema(schemaPath);
if (bindingSchema?.type === 'error') {
resolvedBindings[key] = null;
out[key] = null;
} else {
resolvedBindings[key] = bindingSchema;
out[key] = bindingSchema;
}
} catch {
resolvedBindings[key] = null;
out[key] = null;
}
}

return resolvedBindings as AsSchema<T>;
return out;
}

return undefined;
Expand Down
7 changes: 3 additions & 4 deletions src/layout/ActionButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,17 @@ export class ActionButton extends ActionComponent<'ActionButton'> {
canRenderInButtonGroup(): boolean {
return true;
}

renderWithLabel(): boolean {
return false;
}
}

export const Config = {
def: new ActionButton(),
rendersWithLabel: false as const,
};

export type TypeConfig = {
layout: ILayoutCompActionButton;
nodeItem: ExprResolved<ILayoutCompActionButton>;
nodeObj: LayoutNode;
validTextResourceBindings: 'title';
validDataModelBindings: undefined;
};
9 changes: 4 additions & 5 deletions src/layout/Address/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FormComponent } from 'src/layout/LayoutComponent';
import { SummaryItemSimple } from 'src/layout/Summary/SummaryItemSimple';
import type { ExprResolved } from 'src/features/expressions/types';
import type { PropsFromGenericComponent } from 'src/layout';
import type { ILayoutCompAddress } from 'src/layout/Address/types';
import type { IDataModelBindingsForAddress, ILayoutCompAddress } from 'src/layout/Address/types';
import type { SummaryRendererProps } from 'src/layout/LayoutComponent';
import type { LayoutNodeFromType } from 'src/utils/layout/hierarchy.types';
import type { LayoutNode } from 'src/utils/layout/LayoutNode';
Expand All @@ -15,10 +15,6 @@ export class Address extends FormComponent<'AddressComponent'> {
return <AddressComponent {...props} />;
}

renderWithLabel(): boolean {
return false;
}

useDisplayData(node: LayoutNodeFromType<'AddressComponent'>): string {
const data = node.getFormData();
return Object.values(data).join(' ');
Expand All @@ -36,10 +32,13 @@ export class Address extends FormComponent<'AddressComponent'> {

export const Config = {
def: new Address(),
rendersWithLabel: false as const,
};

export type TypeConfig = {
layout: ILayoutCompAddress;
nodeItem: ExprResolved<ILayoutCompAddress>;
nodeObj: LayoutNode;
validTextResourceBindings: undefined;
validDataModelBindings: IDataModelBindingsForAddress;
};
9 changes: 6 additions & 3 deletions src/layout/Address/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ export interface ILayoutCompAddress extends ILayoutCompBase<'AddressComponent'>,
}

export interface IDataModelBindingsForAddress {
address: string;
zipCode: string;
postPlace: string;
// Usually required, but we need to check
address?: string;
zipCode?: string;
postPlace?: string;

// Optional fields
careOf?: string;
houseNumber?: string;
}
7 changes: 3 additions & 4 deletions src/layout/AttachmentList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,20 @@ export class AttachmentList extends PresentationComponent<'AttachmentList'> {
return <AttachmentListComponent {...props} />;
}

renderWithLabel(): boolean {
return false;
}

canRenderInTable(): boolean {
return false;
}
}

export const Config = {
def: new AttachmentList(),
rendersWithLabel: false as const,
};

export type TypeConfig = {
layout: ILayoutCompAttachmentList;
nodeItem: ExprResolved<ILayoutCompAttachmentList>;
nodeObj: LayoutNode;
validTextResourceBindings: 'title';
validDataModelBindings: undefined;
};
7 changes: 3 additions & 4 deletions src/layout/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,17 @@ export class Button extends ActionComponent<'Button'> {
canRenderInButtonGroup(): boolean {
return true;
}

renderWithLabel(): boolean {
return false;
}
}

export const Config = {
def: new Button(),
rendersWithLabel: false as const,
};

export type TypeConfig = {
layout: ILayoutCompButton;
nodeItem: ExprResolved<ILayoutCompButton>;
nodeObj: LayoutNode;
validTextResourceBindings: 'title';
validDataModelBindings: undefined;
};
3 changes: 3 additions & 0 deletions src/layout/ButtonGroup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@ export class ButtonGroup extends ContainerComponent<'ButtonGroup'> {

export const Config = {
def: new ButtonGroup(),
rendersWithLabel: true as const,
};

export type TypeConfig = {
layout: ILayoutCompButtonGroup;
nodeItem: ILayoutCompButtonGroupInHierarchy;
nodeObj: LayoutNode;
validTextResourceBindings: 'title';
validDataModelBindings: undefined;
};
6 changes: 4 additions & 2 deletions src/layout/ButtonGroup/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import type { ExprResolved } from 'src/features/expressions/types';
import type { ILayoutCompBase } from 'src/layout/layout';
import type { LayoutNode } from 'src/utils/layout/LayoutNode';

export interface ILayoutCompButtonGroupInHierarchy extends ExprResolved<ILayoutCompBase<'ButtonGroup'>> {
type Base = ILayoutCompBase<'ButtonGroup'>;

export interface ILayoutCompButtonGroupInHierarchy extends ExprResolved<Base> {
childComponents: LayoutNode[];
}

export interface ILayoutCompButtonGroup extends ILayoutCompBase<'ButtonGroup'> {
export interface ILayoutCompButtonGroup extends Base {
children: string[];
}
10 changes: 6 additions & 4 deletions src/layout/Checkboxes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { FormComponent } from 'src/layout/LayoutComponent';
import type { ExprResolved } from 'src/features/expressions/types';
import type { PropsFromGenericComponent } from 'src/layout';
import type { ILayoutCompCheckboxes } from 'src/layout/Checkboxes/types';
import type { IDataModelBindingsSimple, TextBindingsForFormComponents, TextBindingsForLabel } from 'src/layout/layout';
import type { SummaryRendererProps } from 'src/layout/LayoutComponent';
import type { LayoutNodeFromType } from 'src/utils/layout/hierarchy.types';
import type { LayoutNode } from 'src/utils/layout/LayoutNode';
Expand All @@ -17,10 +18,6 @@ export class Checkboxes extends FormComponent<'Checkboxes'> {
return <CheckboxContainerComponent {...props} />;
}

renderWithLabel(): boolean {
return false;
}

private useSummaryData(node: LayoutNodeFromType<'Checkboxes'>): { [key: string]: string } {
const formData = useAppSelector((state) => state.formData.formData);
const value = node.item.dataModelBindings?.simpleBinding
Expand All @@ -41,10 +38,15 @@ export class Checkboxes extends FormComponent<'Checkboxes'> {

export const Config = {
def: new Checkboxes(),
rendersWithLabel: false as const,
};

export type TypeConfig = {
layout: ILayoutCompCheckboxes;
nodeItem: ExprResolved<ILayoutCompCheckboxes>;
nodeObj: LayoutNode;
// We don't render the label in GenericComponent, but we still need the
// text resource bindings for rendering them on our own
validTextResourceBindings: TextBindingsForLabel | TextBindingsForFormComponents;
validDataModelBindings: IDataModelBindingsSimple;
};
8 changes: 6 additions & 2 deletions src/layout/Checkboxes/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import type { IComponentCheckbox } from 'src/layout/layout';
import type { ILayoutCompBase, ISelectionComponent } from 'src/layout/layout';
import type { LayoutStyle } from 'src/types';

export type ILayoutCompCheckboxes = IComponentCheckbox<'Checkboxes'>;
export type ILayoutCompCheckboxes = ILayoutCompBase<'Checkboxes'> &
ISelectionComponent & {
layout?: LayoutStyle;
};
11 changes: 6 additions & 5 deletions src/layout/Custom/CustomWebComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ReactDOMServer from 'react-dom/server';
import { useLanguage } from 'src/hooks/useLanguage';
import type { IUseLanguage } from 'src/hooks/useLanguage';
import type { PropsFromGenericComponent } from 'src/layout';
import type { ITextResourceBindings } from 'src/types';
import type { ITextResourceBindings } from 'src/layout/layout';
import type { AnyItem } from 'src/utils/layout/hierarchy.types';

export type ICustomComponentProps = PropsFromGenericComponent<'Custom'> & {
Expand Down Expand Up @@ -58,7 +58,7 @@ export function CustomWebComponent({
React.useLayoutEffect(() => {
const { current } = wcRef;
if (current) {
current.texts = getTextsForComponent(textResourceBindings || {}, langTools);
current.texts = getTextsForComponent(textResourceBindings, langTools);
current.dataModelBindings = dataModelBindings;
current.language = language;
}
Expand Down Expand Up @@ -98,10 +98,11 @@ export function CustomWebComponent({
);
}

function getTextsForComponent(textResourceBindings: ITextResourceBindings, langTools: IUseLanguage) {
function getTextsForComponent(textResourceBindings: ITextResourceBindings<'Custom'>, langTools: IUseLanguage) {
const result: any = {};
Object.keys(textResourceBindings).forEach((key) => {
result[key] = langTools.langAsString(textResourceBindings[key]);
const bindings = textResourceBindings || {};
Object.keys(bindings).forEach((key) => {
result[key] = langTools.langAsString(bindings[key]);
});
return result;
}
7 changes: 3 additions & 4 deletions src/layout/Custom/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ export class Custom extends FormComponent<'Custom'> {
return <CustomWebComponent {...props} />;
}

renderWithLabel(): boolean {
return false;
}

useDisplayData(node: LayoutNodeFromType<'Custom'>): string {
const data = node.getFormData();
return Object.values(data).join(', ');
Expand All @@ -40,10 +36,13 @@ export class Custom extends FormComponent<'Custom'> {

export const Config = {
def: new Custom(),
rendersWithLabel: false as const,
};

export type TypeConfig = {
layout: ILayoutCompCustom;
nodeItem: ExprResolved<ILayoutCompCustom>;
nodeObj: LayoutNode;
validTextResourceBindings: 'title';
validDataModelBindings: undefined;
};
Loading

0 comments on commit b2892e6

Please sign in to comment.