Skip to content

Commit

Permalink
v4/ Add Config tools for AttachmentList (#12250)
Browse files Browse the repository at this point in the history
* remove includePdf property

* init attachmentList + display all datatypes in combobox (minus dataTypes that is datamodel)

* filter added

* Add some texts and cover some scenarios

* include all attachments, all attach (exl. pdf), and sort them + description update

* added behaviour when special variants is used + more

* attachmentList functions as desired now

* Refactor attachmentList + Content, and add pending spinner

* remove unused file

* update type

* init tests for attachmentListComponent

* added unit tests, but need to add some more and clean up the code

* unit tests completed!

* unit test - completed v2

* Add spinner on higher lvl

* update code after ux feedback

* update tests

* other updates

* cover some tests

* naming

* cover more lines

* naming v2

* cover the uncovered partial lines

* one last adjustment + remove comments

* move function removeItemByValue to lib

* change text

* changes after feedback - next step implement indeterminate

* Merge branch 'main' into update-attachmentList-config-v4

* small fix

* remove console.log from App.tsx

* implement indeterminate checkbox + change logic of incoming and outcoming data

* assign state on currentTask to avoid saving to backend when no attachments/pdf selected

* some cleanup

* refactor outgoing data logic

* update component and content logic + refactor

* update onChangeTask - need to use an updated list of attachments

* add unit tests for external format

* add tests when converting to internal and external format

* update tests on converting internal external

* Refactor highest level to only handle incoming and outgoing data

* second level -> attachmentListInternalFormat

* Lvl 3 - internal format combobox

* Updated attachmentListUtils

* AttachmentListUtils - update naming + cleanup

* small change in naming

* Updated arrayUtils

* fix validation function + add tests for validation

* update ArrayUtils.intersection with tests and more use of it

* renaming and get rid of the last bug!

* temperoray status on attachmentListComponent tests before removing the once that are  unecessary

* check test coverage

* try to check coverage

* 3th attempt

* Update unit tests

* update unit tests

* add spinner when data is fetching + tests

* Some cleanup

* renaming convert functions

* update unit tests to use it.each format instead

* test driven dev - update external and internal convert functions to have the same inputs/outputs

* add fieldset wrapper

* Fix converting unit tests after feedback

* Updated convert functions, validation function + small adjustments in tests

* Refactor react logic to handle the updated convert functions

* remove console.log

* Fix: call OnChange when onChangeTask is called :D

* add types

* Update component unit tests

* move convert functions in separate files + own type file

* small adjustments in unit tests

* small changes

* convert functions - split to two files and their own tests files

* updated convertToInternalFunction

* renaming validation function

* refactor attachmentList: -Component, -Content, -InternalFormat files

* simplify convertToExternalFormat function

* rename file

* rename file back

* rename files lowercase

* reuse function extractCurrentAvailableAttachments

* solve a case when convert to internal format and only pdf is selected

* small adjustment in unit test

* fix breaking changes after merge with main

* update naming mockStore after merge with main

* fix - make sure that selected datatypes exists in available datatypes --> if selected data type is deleted or edited

* add useEffect

* Make sure initial selected values in combobox is displayed correctly immidiately when an attachment component is deleted

* removal of key index

* remove index*
  • Loading branch information
lassopicasso authored and Jondyr committed Jun 10, 2024
1 parent 71853e0 commit 03c6cf6
Show file tree
Hide file tree
Showing 28 changed files with 1,017 additions and 66 deletions.
7 changes: 7 additions & 0 deletions frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,7 @@
"ux_editor.component_properties.compact": "Bruk kompakt visning",
"ux_editor.component_properties.componentRef": "ID på komponenten det gjelder (componentRef)",
"ux_editor.component_properties.config_is_expression_message": "Denne egenskapen er konfigurert som et uttrykk, og kan foreløpig ikke redigeres her.",
"ux_editor.component_properties.current_task": "Kun gjeldende oppgave",
"ux_editor.component_properties.dataListId": "Id på dataliste",
"ux_editor.component_properties.dataTypeIds": "Liste med ID på datatyper som skal vises",
"ux_editor.component_properties.dateSent": "Dato instansen ble sendt inn",
Expand All @@ -1572,6 +1573,7 @@
"ux_editor.component_properties.largeGroup": "Stor gruppevisning/gruppe i gruppe",
"ux_editor.component_properties.layers": "Kartlag",
"ux_editor.component_properties.layout": "Visning",
"ux_editor.component_properties.loading": "Laster inn",
"ux_editor.component_properties.mapping": "Mapping",
"ux_editor.component_properties.maxCount": "Maks antall repetisjoner",
"ux_editor.component_properties.maxDate": "Seneste dato",
Expand Down Expand Up @@ -1602,6 +1604,9 @@
"ux_editor.component_properties.sandbox": "Sandbox",
"ux_editor.component_properties.saveWhileTyping": "Overstyr tidsintervall for lagring når bruker skriver",
"ux_editor.component_properties.secure": "Bruk sikret versjon av API for å hente valg (secure)",
"ux_editor.component_properties.select_all_attachments": "Alle vedlegg",
"ux_editor.component_properties.select_attachments": "Velg vedlegg",
"ux_editor.component_properties.select_pdf": "Inkluder skjemagenerert pdf",
"ux_editor.component_properties.sender": "Hvem som har sendt inn skjema",
"ux_editor.component_properties.severity": "Alvorlighetsgrad",
"ux_editor.component_properties.showAsCard": "Vis som kort",
Expand Down Expand Up @@ -1641,6 +1646,8 @@
"ux_editor.component_title.AddressComponent": "Adresse",
"ux_editor.component_title.Alert": "Varsel",
"ux_editor.component_title.AttachmentList": "Liste over vedlegg",
"ux_editor.component_title.AttachmentList_error": "Du må velge minst ett vedlegg eller PDF",
"ux_editor.component_title.AttachmentList_legend": "Vedleggsliste",
"ux_editor.component_title.Button": "Knapp",
"ux_editor.component_title.ButtonGroup": "Knappegruppe",
"ux_editor.component_title.Checkboxes": "Avkrysningsbokser",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ describe('ArrayUtils', () => {
});
});

describe('removeItemByValue', () => {
it('Deletes item from array by value', () => {
expect(ArrayUtils.removeItemByValue([1, 2, 3], 2)).toEqual([1, 3]);
expect(ArrayUtils.removeItemByValue(['a', 'b', 'c'], 'b')).toEqual(['a', 'c']);
expect(ArrayUtils.removeItemByValue(['a', 'b', 'c'], 'd')).toEqual(['a', 'b', 'c']);
expect(ArrayUtils.removeItemByValue([], 'a')).toEqual([]);
expect(ArrayUtils.removeItemByValue(['a', 'b', 'c', 'b', 'a'], 'b')).toEqual(['a', 'c', 'a']);
});
});

describe('last', () => {
it('Returns last item in array', () => {
expect(ArrayUtils.last([1, 2, 3])).toEqual(3);
Expand All @@ -47,6 +57,22 @@ describe('ArrayUtils', () => {
});
});

describe('ArrayUtils.intersection', () => {
it('Returns intersection of two arrays when included is true', () => {
expect(ArrayUtils.intersection([1, 2, 3], [3, '4', 5])).toStrictEqual([3]);
expect(ArrayUtils.intersection([1, 2, 3], [4, '4', 5])).toStrictEqual([]);
expect(ArrayUtils.intersection([1, 2, 3], [3, '4', 2])).toStrictEqual([2, 3]);
expect(ArrayUtils.intersection([1, 2, 3], [1, 2, 3])).toStrictEqual([1, 2, 3]);
});

it('Returns intersection of two arrays when included is false', () => {
expect(ArrayUtils.intersection([1, 2, 3], [3, '4', 5], false)).toStrictEqual([1, 2]);
expect(ArrayUtils.intersection([1, 2, 3], [4, '4', 5], false)).toStrictEqual([1, 2, 3]);
expect(ArrayUtils.intersection([1, 2, 3], [3, '4', 2], false)).toStrictEqual([1]);
expect(ArrayUtils.intersection([1, 2, 3], [1, 2, 3], false)).toStrictEqual([]);
});
});

describe('replaceByIndex', () => {
it('Replaces element in array with new value', () => {
const array1 = ['0', '1', '2'];
Expand Down
21 changes: 21 additions & 0 deletions frontend/libs/studio-pure-functions/src/ArrayUtils/ArrayUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,30 @@ export class ArrayUtils {
return givenIndex < 0 || givenIndex >= array.length ? array.length - 1 : givenIndex;
}

/**
* Removes item from array by value.
* @param array Array to delete item from.
* @param value Value to delete.
* @returns Array without the given value.
*/
public static removeItemByValue<T>(array: T[], value: T): T[] {
return array.filter((item) => item !== value);
}

/** Returns the last item of the given array */
public static last = <T>(array: T[]): T => array[array.length - 1];

/**
* Returns an array of which the element of arrA are either present or not present in arrB based on the include param.
* @param arrA The first array.
* @param arrB The second array.
* @param include Whether to include or exclude the elements of arrB from arrA. Defaults to true.
* @returns Array that contains the filtered elements based on the filtering condition.
*/
public static intersection = <T>(arrA: T[], arrB: T[], include: boolean = true): T[] => {
return arrA.filter((x) => (include ? arrB.includes(x) : !arrB.includes(x)));
};

/** Replaces an element in an array with a new value */
public static replaceByIndex = <T>(array: T[], index: number, newValue: T): T[] => {
if (index < 0 || index >= array.length) return array;
Expand Down
3 changes: 1 addition & 2 deletions frontend/packages/schema-model/src/lib/SchemaModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
generateUniqueStringWithNumber,
insertArrayElementAtPos,
moveArrayItem,
removeItemByValue,
replaceItemsByValue,
} from 'app-shared/utils/arrayUtils';
import { ROOT_POINTER } from './constants';
Expand Down Expand Up @@ -356,7 +355,7 @@ export class SchemaModel {

private removeNodeFromParent = (pointer: string): void => {
const parent = this.getParentNode(pointer);
parent.children = removeItemByValue(parent.children, pointer);
parent.children = ArrayUtils.removeItemByValue(parent.children, pointer);
};

public getParentNode(pointer: string): FieldNode | CombinationNode | undefined {
Expand Down
9 changes: 4 additions & 5 deletions frontend/packages/schema-model/src/lib/mappers/field-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
StrRestrictionKey,
} from '../../types';
import { getCombinationKind, getObjectKind, isField } from '../utils';
import { arrayIntersection } from 'app-shared/utils/arrayUtils';
import type { KeyValuePairs } from 'app-shared/types/KeyValuePairs';
import { ArrayUtils } from '@studio/pure-functions';

Expand All @@ -31,13 +30,13 @@ export const findUiFieldType = (schemaNode: KeyValuePairs) => {
} else if (isCompundFieldType(schemaNode.type)) {
// @see SeresNillable.json, we need to support types where stuff can be null.
return schemaNode.type.filter((t: FieldType) => t !== FieldType.Null).pop();
} else if (arrayIntersection(keys, Object.values(IntRestrictionKey)).length) {
} else if (ArrayUtils.intersection(keys, Object.values(IntRestrictionKey)).length) {
return FieldType.Number;
} else if (arrayIntersection(keys, Object.values(ArrRestrictionKey)).length) {
} else if (ArrayUtils.intersection(keys, Object.values(ArrRestrictionKey)).length) {
return FieldType.Boolean;
} else if (arrayIntersection(keys, Object.values(StrRestrictionKey)).length) {
} else if (ArrayUtils.intersection(keys, Object.values(StrRestrictionKey)).length) {
return FieldType.String;
} else if (arrayIntersection(keys, Object.values(ObjRestrictionKey)).length) {
} else if (ArrayUtils.intersection(keys, Object.values(ObjRestrictionKey)).length) {
return FieldType.Object;
} else if (Array.isArray(schemaNode.enum) && schemaNode.enum.length) {
return findEnumFieldType(schemaNode.enum);
Expand Down
5 changes: 3 additions & 2 deletions frontend/packages/schema-model/test/validateTestUiSchema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { UiSchemaNodes } from '../src';
import { FieldType, ObjectKind, ROOT_POINTER } from '../src';
import { getPointers } from '../src/lib/mappers/getPointers';
import { areItemsUnique, mapByKey, removeItemByValue } from 'app-shared/utils/arrayUtils';
import { areItemsUnique, mapByKey } from 'app-shared/utils/arrayUtils';
import {
isField,
isFieldOrCombination,
Expand All @@ -12,6 +12,7 @@ import {
isNotTheRootNode,
isTheRootNode,
} from '../src/lib/utils';
import { ArrayUtils } from '@studio/pure-functions';

/** Verifies that there is a root node */
export const hasRootNode = (uiSchema: UiSchemaNodes) =>
Expand All @@ -33,7 +34,7 @@ export const allPointersExist = (uiSchema: UiSchemaNodes) => {
/** Verifies that all nodes except the root node have a parent */
export const nodesHaveParent = (uiSchema: UiSchemaNodes) => {
const allChildPointers = mapByKey(uiSchema.filter(isFieldOrCombination), 'children').flat();
removeItemByValue(getPointers(uiSchema), ROOT_POINTER).forEach((pointer) => {
ArrayUtils.removeItemByValue(getPointers(uiSchema), ROOT_POINTER).forEach((pointer) => {
expect(allChildPointers).toContain(pointer);
});
};
Expand Down
21 changes: 0 additions & 21 deletions frontend/packages/shared/src/utils/arrayUtils.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import {
areItemsUnique,
arrayIntersection,
generateUniqueStringWithNumber,
insertArrayElementAtPos,
mapByKey,
moveArrayItem,
prepend,
removeEmptyStrings,
removeItemByValue,
replaceByPredicate,
replaceItemsByValue,
swapArrayElements,
Expand All @@ -21,16 +19,6 @@ describe('arrayUtils', () => {
});
});

describe('removeItemByValue', () => {
it('Deletes item from array by value', () => {
expect(removeItemByValue([1, 2, 3], 2)).toEqual([1, 3]);
expect(removeItemByValue(['a', 'b', 'c'], 'b')).toEqual(['a', 'c']);
expect(removeItemByValue(['a', 'b', 'c'], 'd')).toEqual(['a', 'b', 'c']);
expect(removeItemByValue([], 'a')).toEqual([]);
expect(removeItemByValue(['a', 'b', 'c', 'b', 'a'], 'b')).toEqual(['a', 'c', 'a']);
});
});

describe('areItemsUnique', () => {
it('Returns true if all items are unique', () => {
expect(areItemsUnique([1, 2, 3])).toBe(true);
Expand Down Expand Up @@ -81,15 +69,6 @@ describe('arrayUtils', () => {
});
});

describe('arrayIntersection', () => {
it('Returns intersection of two arrays', () => {
expect(arrayIntersection([1, 2, 3], [3, '4', 5])).toStrictEqual([3]);
expect(arrayIntersection([1, 2, 3], [4, '4', 5])).toStrictEqual([]);
expect(arrayIntersection([1, 2, 3], [3, '4', 2])).toStrictEqual([2, 3]);
expect(arrayIntersection([1, 2, 3], [1, 2, 3])).toStrictEqual([1, 2, 3]);
});
});

describe('mapByKey', () => {
it('Returns an array of values mapped by the given key', () => {
const array = [
Expand Down
22 changes: 4 additions & 18 deletions frontend/packages/shared/src/utils/arrayUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ArrayUtils } from '@studio/pure-functions';

/**
* Adds an item to the beginning of an array..
* @param array The array of interest.
Expand All @@ -17,15 +19,6 @@ export const replaceLastItem = <T>(array: T[], replaceWith: T): T[] => {
return array;
};

/**
* Removes item from array by value.
* @param array Array to delete item from.
* @param value Value to delete.
* @returns Array without the given value.
*/
export const removeItemByValue = <T>(array: T[], value: T): T[] =>
array.filter((item) => item !== value);

/**
* Checks if all items in the given array are unique.
* @param array The array of interest.
Expand Down Expand Up @@ -63,14 +56,6 @@ export const insertArrayElementAtPos = <T>(array: T[], item: T, targetPos: numbe
return out;
};

/**
* Returns an array of which the elements are present in both given arrays.
* @param arrA First array.
* @param arrB Second array.
* @returns Array of which the elements are present in both given arrays.
*/
export const arrayIntersection = <T>(arrA: T[], arrB: T[]) => arrA.filter((x) => arrB.includes(x));

/**
* Maps an array of objects by a given key.
* @param array The array of objects.
Expand Down Expand Up @@ -132,4 +117,5 @@ export const generateUniqueStringWithNumber = (array: string[], prefix: string =
};

/** Removes empty strings from a string array */
export const removeEmptyStrings = (array: string[]): string[] => removeItemByValue(array, '');
export const removeEmptyStrings = (array: string[]): string[] =>
ArrayUtils.removeItemByValue(array, '');
6 changes: 3 additions & 3 deletions frontend/packages/text-editor/src/RightMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { LangSelector } from './LangSelector';
import { getLangName, langOptions } from './utils';
import { Checkbox, Fieldset, Heading } from '@digdir/design-system-react';
import { defaultLangCode } from './constants';
import { removeItemByValue } from 'app-shared/utils/arrayUtils';
import { useTranslation } from 'react-i18next';
import { AltinnConfirmDialog } from 'app-shared/components';
import * as testids from '../../../testing/testids';
import { StudioButton } from '@studio/components';
import { ArrayUtils } from '@studio/pure-functions';

export interface RightMenuProps {
addLanguage: (langCode: LangCode) => void;
Expand All @@ -34,11 +34,11 @@ export const RightMenu = ({
const handleSelectChange = async ({ target }: React.ChangeEvent<HTMLInputElement>) => {
target.checked
? setSelectedLanguages([...selectedLanguages, target.name])
: setSelectedLanguages(removeItemByValue(selectedLanguages, target.name));
: setSelectedLanguages(ArrayUtils.removeItemByValue(selectedLanguages, target.name));
};

const handleDeleteLanguage = (langCode: LangCode) => {
setSelectedLanguages(removeItemByValue(selectedLanguages, langCode));
setSelectedLanguages(ArrayUtils.removeItemByValue(selectedLanguages, langCode));
deleteLanguage(langCode);
};

Expand Down
14 changes: 10 additions & 4 deletions frontend/packages/ux-editor-v3/src/utils/formLayoutUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import type {
IToolbarElement,
} from '../types/global';
import { BASE_CONTAINER_ID, MAX_NESTED_GROUP_LEVEL } from 'app-shared/constants';
import { ObjectUtils } from '@studio/pure-functions';
import { insertArrayElementAtPos, removeItemByValue } from 'app-shared/utils/arrayUtils';
import { insertArrayElementAtPos } from 'app-shared/utils/arrayUtils';
import { ArrayUtils, ObjectUtils } from '@studio/pure-functions';
import { ComponentTypeV3 } from 'app-shared/types/ComponentTypeV3';
import type { FormComponent } from '../types/FormComponent';
import { generateFormItem } from './component';
Expand Down Expand Up @@ -202,7 +202,10 @@ export const removeComponent = (layout: IInternalLayout, componentId: string): I
const newLayout = ObjectUtils.deepCopy(layout);
const containerId = findParentId(layout, componentId);
if (containerId) {
newLayout.order[containerId] = removeItemByValue(newLayout.order[containerId], componentId);
newLayout.order[containerId] = ArrayUtils.removeItemByValue(
newLayout.order[containerId],
componentId,
);
delete newLayout.components[componentId];
}
return newLayout;
Expand Down Expand Up @@ -294,7 +297,10 @@ export const moveLayoutItem = (
const item = findItem(newLayout, id);
item.pageIndex = calculateNewPageIndex(newLayout, newContainerId, newPosition);
if (oldContainerId) {
newLayout.order[oldContainerId] = removeItemByValue(newLayout.order[oldContainerId], id);
newLayout.order[oldContainerId] = ArrayUtils.removeItemByValue(
newLayout.order[oldContainerId],
id,
);
newLayout.order[newContainerId] = insertArrayElementAtPos(
newLayout.order[newContainerId],
id,
Expand Down
Loading

0 comments on commit 03c6cf6

Please sign in to comment.