Skip to content

Commit

Permalink
Merge branch 'main' into bug/validations-not-showing
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/utils/layout/NodesContext.tsx
#	src/utils/layout/generator/NodeGenerator.tsx
  • Loading branch information
Ole Martin Handeland committed Sep 17, 2024
2 parents 7d8d057 + b65def9 commit 3074bd3
Show file tree
Hide file tree
Showing 30 changed files with 700 additions and 360 deletions.
15 changes: 5 additions & 10 deletions src/codegen/ComponentConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,6 @@ export class ComponentConfig {
from: 'src/layout/layout',
});

const BaseRow = new CG.import({
import: 'BaseRow',
from: 'src/utils/layout/types',
});

const isFormComponent = this.config.category === CompCategory.Form;
const isSummarizable = this.behaviors.isSummarizable;

Expand Down Expand Up @@ -418,11 +413,11 @@ export class ComponentConfig {
`pickDirectChildren(state: ${NodeData}<'${this.type}'>, restriction?: ${TraversalRestriction}) {
return [${pickDirectChildrenBody.join(', ')}];
}`,
`addChild(state: ${NodeData}<'${this.type}'>, childNode: ${LayoutNode}, { pluginKey, metadata }: ${ChildClaim}, row: ${BaseRow} | undefined) {
return this.plugins[pluginKey!].addChild(state as any, childNode, metadata, row) as Partial<${NodeData}<'${this.type}'>>;
`addChild(state: ${NodeData}<'${this.type}'>, childNode: ${LayoutNode}, { pluginKey, metadata }: ${ChildClaim}, rowIndex: number | undefined) {
return this.plugins[pluginKey!].addChild(state as any, childNode, metadata, rowIndex) as Partial<${NodeData}<'${this.type}'>>;
}`,
`removeChild(state: ${NodeData}<'${this.type}'>, childNode: ${LayoutNode}, { pluginKey, metadata }: ${ChildClaim}, row: ${BaseRow} | undefined) {
return this.plugins[pluginKey!].removeChild(state as any, childNode, metadata, row) as Partial<${NodeData}<'${this.type}'>>;
`removeChild(state: ${NodeData}<'${this.type}'>, childNode: ${LayoutNode}, { pluginKey, metadata }: ${ChildClaim}, rowIndex: number | undefined) {
return this.plugins[pluginKey!].removeChild(state as any, childNode, metadata, rowIndex) as Partial<${NodeData}<'${this.type}'>>;
}`,
`isChildHidden(state: ${NodeData}<'${this.type}'>, childNode: ${LayoutNode}) {
return [${isChildHiddenBody.join(', ')}].some((h) => h);
Expand Down Expand Up @@ -451,7 +446,7 @@ export class ComponentConfig {
item: undefined,
layout: props.item,
hidden: undefined,
row: props.row,
rowIndex: props.rowIndex,
errors: undefined,
};
${itemDef}
Expand Down
36 changes: 36 additions & 0 deletions src/features/formData/FormDataWrite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,42 @@ export const FD = {
return rawRows.map((row: any, index: number) => ({ uuid: row[ALTINN_ROW_ID], index }));
}),

/**
* Returns the number of rows in a repeating group. This will always be 'fresh', meaning it will update immediately
* when a new row is added/removed.
*/
useFreshNumRows: (binding: IDataModelReference | undefined): number =>
useMemoSelector((s) => {
if (!binding) {
return 0;
}

const rawRows = dot.pick(binding.field, s.dataModels[binding.dataType].currentData);
if (!Array.isArray(rawRows) || !rawRows.length) {
return 0;
}

return rawRows.length;
}),

/**
* Get the UUID of a row in a repeating group. This will always be 'fresh', meaning it will update immediately when
* a new row is added/removed.
*/
useFreshRowUuid: (binding: IDataModelReference | undefined, index: number): string | undefined =>
useMemoSelector((s) => {
if (!binding) {
return undefined;
}

const rawRows = dot.pick(binding.field, s.dataModels[binding.dataType].currentData);
if (!Array.isArray(rawRows) || !rawRows.length) {
return undefined;
}

return rawRows[index]?.[ALTINN_ROW_ID];
}),

/**
* Returns a function you can use to debounce saved form data
* This will work (and return immediately) even if there is no FormDataWriteProvider in the tree.
Expand Down
14 changes: 12 additions & 2 deletions src/features/formData/jsonPatch/createPatch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,12 +302,22 @@ describe('createPatch', () => {
});
});

describe('should avoid overwriting differing simple arrays in current', () => {
describe('should overwrite simple arrays in current if the next value is new', () => {
testPatch({
prev: { a: ['foo', 'bar', 'baz', 'qux'] },
next: { a: ['foo', 'bar2', 'baz', 'qux'] },
current: { a: ['foo', 'bar', 'baz', 'qux'] },
final: { a: ['foo', 'bar', 'baz', 'qux'] },
final: { a: ['foo', 'bar2', 'baz', 'qux'] },
expectedPatch: [{ op: 'replace', path: '/a', value: ['foo', 'bar2', 'baz', 'qux'] }],
});
});

describe('should avoid overwriting simple arrays in current if the current value is newer', () => {
testPatch({
prev: { a: ['foo', 'bar', 'baz', 'qux'] },
next: { a: ['foo', 'bar2', 'baz', 'qux'] },
current: { a: ['foo', 'bar', 'baz', 'qux2'] },
final: { a: ['foo', 'bar', 'baz', 'qux2'] },
expectedPatch: [],
});
});
Expand Down
43 changes: 23 additions & 20 deletions src/features/formData/jsonPatch/createPatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,17 @@ export function createPatch({ prev, next, current }: Props<object>): JsonPatch {
return patch;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isObject(value: any): value is object {
function isObject(value: unknown): value is object {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function compareAny(props: CompareProps<any>) {
function compareAny(props: CompareProps<unknown>) {
const { prev, next } = props;
if (isObject(prev) && isObject(next)) {
return compareObjects(props);
return compareObjects(props as CompareProps<object>);
}
if (Array.isArray(prev) && Array.isArray(next)) {
return compareArrays(props);
return compareArrays(props as CompareProps<unknown[]>);
}
compareValues(props);
}
Expand All @@ -74,8 +72,7 @@ function compareObjects({ prev, next, current, path, ...rest }: CompareProps<obj
* This comparison function is used to determine if two values in an array can be considered the same. This is used to
* determine if an item (i.e. a row in a repeating group) has been removed or added.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isSameRow(left: any, right: any): boolean {
function isSameRow(left: unknown, right: unknown): boolean {
if (isObject(left) && isObject(right)) {
if (!(ALTINN_ROW_ID in left && ALTINN_ROW_ID in right)) {
window.logWarn(
Expand All @@ -102,20 +99,20 @@ function isSameRow(left: any, right: any): boolean {
* produce the JsonPatch to create. Do not be fooled by the format returned from getPatch, it is not a JsonPatch
* even if it looks like it at a glance.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function compareArrays({ prev, next, current, hasCurrent, patch, path }: CompareProps<any[]>) {
function compareArrays({ prev, next, current, hasCurrent, patch, path }: CompareProps<unknown[]>) {
const diff = getPatch(prev, next, isSameRow);
const allNextValuesIsString = next.length > 0 && next.every((item) => typeof item === 'string');
if (allNextValuesIsString) {
const allValues = [...next, ...prev, ...(current || [])];
const allValuesAreStrings = allValues.length > 0 && allValues.every((item) => typeof item === 'string');
if (allValuesAreStrings) {
if (next.length > 0) {
if (current) {
if (current && !deepEqual(current, prev)) {
// Special case. If we have a current model, and that model is an array of strings that is different from
// next, we'll keep the current value. This can happen if you for example have a file uploader that saves
// IDs to a list in the data model. When one attachment got uploaded, and got saved to the backend, we started
// uploading a new attachment (which got added to the current model), but before the previous save response
// came back from the backend.
return;
} else {
} else if (!current) {
patch.push({
op: 'test',
path: pointer(path),
Expand All @@ -128,10 +125,17 @@ function compareArrays({ prev, next, current, hasCurrent, patch, path }: Compare
value: next,
});
} else {
patch.push({
op: 'remove',
path: pointer(path),
});
if (current && !deepEqual(current, prev)) {
// Same special case as above, but for when the next array is empty (i.e. the backend hasn't seen any values in
// this array yet, but while saving the user has added some values to the current model, for example by
// uploading an attachment which adds a UUID reference).
return;
} else {
patch.push({
op: 'remove',
path: pointer(path),
});
}
}
return;
}
Expand Down Expand Up @@ -196,8 +200,7 @@ function compareArrays({ prev, next, current, hasCurrent, patch, path }: Compare
patch.push(...childPatches);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function compareValues({ prev, next, hasCurrent, current, patch, path }: CompareProps<any>) {
function compareValues({ prev, next, hasCurrent, current, patch, path }: CompareProps<unknown>) {
if (prev === next) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/features/options/evalQueryParameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function resolveQueryParameters(
queryParameters: IQueryParameters | undefined,
node: LayoutNode<CompWithBehavior<'canHaveOptions'>>,
dataSources: ExpressionDataSources,
) {
): Record<string, string> | undefined {
return queryParameters
? Object.entries(queryParameters).reduce((obj, [key, expr]) => {
obj[key] = evalExpr(expr, node, dataSources, {
Expand Down
Loading

0 comments on commit 3074bd3

Please sign in to comment.