@evoke-platform/ui-components 1.13.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +2 -2
- package/dist/published/components/custom/CriteriaBuilder/types.d.ts +0 -15
- package/dist/published/components/custom/CriteriaBuilder/utils.d.ts +0 -10
- package/dist/published/components/custom/CriteriaBuilder/utils.js +2 -161
- package/dist/published/components/custom/Form/utils.js +3 -2
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +1 -1
- package/dist/published/components/custom/FormV2/FormRenderer.js +25 -27
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +132 -101
- package/dist/published/components/custom/FormV2/components/ConditionalQueryClientProvider.d.ts +5 -0
- package/dist/published/components/custom/FormV2/components/ConditionalQueryClientProvider.js +21 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.js +86 -143
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.d.ts +0 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +1 -4
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +105 -185
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +36 -49
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +18 -26
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +18 -18
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +17 -21
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +96 -169
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +0 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +57 -13
- package/dist/published/components/custom/FormV2/components/HtmlView.js +5 -1
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.d.ts +2 -1
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +61 -29
- package/dist/published/components/custom/FormV2/components/utils.d.ts +23 -4
- package/dist/published/components/custom/FormV2/components/utils.js +136 -26
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +28 -14
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +41 -48
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +2 -1
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +56 -19
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +7 -2
- package/dist/published/components/custom/index.d.ts +2 -0
- package/dist/published/components/custom/index.js +1 -0
- package/dist/published/components/custom/types.d.ts +15 -0
- package/dist/published/components/custom/types.js +1 -0
- package/dist/published/components/custom/util.d.ts +10 -0
- package/dist/published/components/custom/util.js +161 -1
- package/dist/published/index.d.ts +2 -2
- package/dist/published/index.js +1 -1
- package/package.json +3 -4
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useApiServices, useAuthenticationContext, } from '@evoke-platform/context';
|
|
2
2
|
import { WarningRounded } from '@mui/icons-material';
|
|
3
3
|
import { isEmpty } from 'lodash';
|
|
4
|
-
import React, {
|
|
4
|
+
import React, { useMemo } from 'react';
|
|
5
5
|
import useWidgetSize, { useFormContext } from '../../../../theme/hooks';
|
|
6
|
-
import { TextField, Typography } from '../../../core';
|
|
6
|
+
import { Skeleton, TextField, Typography } from '../../../core';
|
|
7
7
|
import { Box } from '../../../layout';
|
|
8
8
|
import FormField from '../../FormField';
|
|
9
9
|
import AccordionSections from './AccordionSections';
|
|
@@ -17,8 +17,9 @@ import { Image } from './FormFieldTypes/Image';
|
|
|
17
17
|
import ObjectPropertyInput from './FormFieldTypes/relatedObjectFiles/ObjectPropertyInput';
|
|
18
18
|
import UserProperty from './FormFieldTypes/UserProperty';
|
|
19
19
|
import FormSections from './FormSections';
|
|
20
|
-
import { entryIsVisible,
|
|
20
|
+
import { entryIsVisible, fetchInitialMiddleObjectInstances, fetchMiddleObject, filterEmptySections, getEntryId, getFieldDefinition, isAddressProperty, isOptionEqualToValue, updateCriteriaInputs, } from './utils';
|
|
21
21
|
import HtmlView from './HtmlView';
|
|
22
|
+
import { useQuery } from '@tanstack/react-query';
|
|
22
23
|
function getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors, validation) {
|
|
23
24
|
return {
|
|
24
25
|
inputId: entryId,
|
|
@@ -38,7 +39,7 @@ function getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, displ
|
|
|
38
39
|
}
|
|
39
40
|
export function RecursiveEntryRenderer(props) {
|
|
40
41
|
const { entry } = props;
|
|
41
|
-
const {
|
|
42
|
+
const { object, getValues, errors, instance, richTextEditor: RichTextEditor, parameters, handleChange, onAutosave, fieldHeight, triggerFieldReset, associatedObject, width, } = useFormContext();
|
|
42
43
|
const { isBelow, breakpoints } = useWidgetSize({
|
|
43
44
|
scroll: false,
|
|
44
45
|
defaultWidth: width,
|
|
@@ -50,19 +51,55 @@ export function RecursiveEntryRenderer(props) {
|
|
|
50
51
|
const entryId = getEntryId(entry) || 'defaultId';
|
|
51
52
|
const display = 'display' in entry ? entry.display : undefined;
|
|
52
53
|
const fieldValue = entry.type === 'readonlyField' ? instance?.[entryId] : getValues ? getValues(entryId) : undefined;
|
|
53
|
-
const initialMiddleObjectInstances = fetchedOptions[`${entryId}InitialMiddleObjectInstances`];
|
|
54
|
-
const middleObject = fetchedOptions[`${entryId}MiddleObject`];
|
|
55
54
|
const fieldDefinition = useMemo(() => {
|
|
56
55
|
if (!object)
|
|
57
56
|
return undefined;
|
|
58
57
|
return getFieldDefinition(entry, object, parameters);
|
|
59
58
|
}, [entry, parameters, object]);
|
|
60
59
|
const validation = fieldDefinition?.validation || {};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
const { data: middleObject } = useQuery({
|
|
61
|
+
queryKey: [fieldDefinition?.objectId, 'MiddleObject'],
|
|
62
|
+
queryFn: () => fetchMiddleObject(fieldDefinition, apiServices),
|
|
63
|
+
staleTime: Infinity,
|
|
64
|
+
enabled: !!(fieldDefinition?.objectId &&
|
|
65
|
+
fieldDefinition?.type === 'collection' &&
|
|
66
|
+
fieldDefinition?.manyToManyPropertyId),
|
|
67
|
+
meta: {
|
|
68
|
+
errorMessage: 'Failed to fetch middle object: ',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
const { data: initialMiddleObjectInstances = [], isLoading: isLoadingInstances } = useQuery({
|
|
72
|
+
queryKey: [
|
|
73
|
+
fieldDefinition?.objectId,
|
|
74
|
+
instance?.id,
|
|
75
|
+
fieldDefinition?.relatedPropertyId,
|
|
76
|
+
'InitialMiddleObjectInstances',
|
|
77
|
+
],
|
|
78
|
+
queryFn: () => fetchInitialMiddleObjectInstances(apiServices, fieldDefinition, instance?.id),
|
|
79
|
+
staleTime: Infinity,
|
|
80
|
+
enabled: !!(fieldDefinition?.objectId &&
|
|
81
|
+
instance?.id &&
|
|
82
|
+
fieldDefinition?.type === 'collection' &&
|
|
83
|
+
fieldDefinition?.manyToManyPropertyId &&
|
|
84
|
+
fieldDefinition?.relatedPropertyId),
|
|
85
|
+
meta: {
|
|
86
|
+
errorMessage: 'Failed to fetch middle object instances: ',
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
const memorizedCriteria = useMemo(() => {
|
|
90
|
+
return 'criteria' in validation && validation.criteria && getValues
|
|
91
|
+
? updateCriteriaInputs(validation.criteria, getValues(), userAccount, instance)
|
|
92
|
+
: undefined;
|
|
93
|
+
}, [validation, getValues && getValues(), userAccount, instance]);
|
|
94
|
+
const memorizedDefaultValueCriteria = useMemo(() => {
|
|
95
|
+
return display?.defaultValue &&
|
|
96
|
+
typeof display.defaultValue === 'object' &&
|
|
97
|
+
'criteria' in display.defaultValue &&
|
|
98
|
+
display.defaultValue.criteria &&
|
|
99
|
+
getValues
|
|
100
|
+
? updateCriteriaInputs(display.defaultValue.criteria, getValues(), userAccount, instance)
|
|
101
|
+
: undefined;
|
|
102
|
+
}, [display, getValues && getValues(), userAccount, instance]);
|
|
66
103
|
if (associatedObject?.propertyId === entryId)
|
|
67
104
|
return null;
|
|
68
105
|
// If the entry is hidden, clear its value and any nested values, and skip rendering
|
|
@@ -83,15 +120,15 @@ export function RecursiveEntryRenderer(props) {
|
|
|
83
120
|
}
|
|
84
121
|
else if (fieldDefinition.type === 'object') {
|
|
85
122
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
86
|
-
React.createElement(ObjectPropertyInput, { relatedObjectId: !fieldDefinition.objectId ? display?.relatedObjectId : fieldDefinition.objectId, fieldDefinition: fieldDefinition, id: entryId, mode: display?.mode || 'default', error: !!errors?.[entryId], displayOption: display?.relatedObjectDisplay || 'dialogBox', initialValue: fieldValue, readOnly: entry.type === 'readonlyField', filter:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
? display
|
|
90
|
-
: undefined, orderBy:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
? display
|
|
94
|
-
: undefined, viewLayout: display?.viewLayout, hasDescription: !!display?.description, formId: display?.createFormId, createActionId: display?.createActionId })));
|
|
123
|
+
React.createElement(ObjectPropertyInput, { relatedObjectId: !fieldDefinition.objectId ? display?.relatedObjectId : fieldDefinition.objectId, fieldDefinition: fieldDefinition, id: entryId, mode: display?.mode || 'default', error: !!errors?.[entryId], displayOption: display?.relatedObjectDisplay || 'dialogBox', initialValue: fieldValue, readOnly: entry.type === 'readonlyField', filter: memorizedCriteria, sortBy: display?.defaultValue &&
|
|
124
|
+
typeof display.defaultValue === 'object' &&
|
|
125
|
+
'sortBy' in display.defaultValue
|
|
126
|
+
? display.defaultValue.sortBy
|
|
127
|
+
: undefined, orderBy: display?.defaultValue &&
|
|
128
|
+
typeof display.defaultValue === 'object' &&
|
|
129
|
+
'orderBy' in display.defaultValue
|
|
130
|
+
? display.defaultValue.orderBy
|
|
131
|
+
: undefined, defaultValueCriteria: memorizedDefaultValueCriteria, viewLayout: display?.viewLayout, hasDescription: !!display?.description, formId: display?.createFormId, createActionId: display?.createActionId })));
|
|
95
132
|
}
|
|
96
133
|
else if (fieldDefinition.type === 'user') {
|
|
97
134
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
@@ -99,12 +136,9 @@ export function RecursiveEntryRenderer(props) {
|
|
|
99
136
|
}
|
|
100
137
|
else if (fieldDefinition.type === 'collection') {
|
|
101
138
|
if (fieldDefinition?.manyToManyPropertyId) {
|
|
102
|
-
if (
|
|
103
|
-
return (
|
|
104
|
-
React.createElement(DropdownRepeatableField, { initialMiddleObjectInstances:
|
|
105
|
-
initialMiddleObjectInstances, fieldDefinition: fieldDefinition, id: entryId, middleObject: middleObject, readOnly: entry.type === 'readonlyField', criteria: 'criteria' in validation && validation.criteria
|
|
106
|
-
? updateCriteriaInputs(validation.criteria, getValues(), userAccount, instance)
|
|
107
|
-
: undefined, hasDescription: !!display?.description }))));
|
|
139
|
+
if (!isEmpty(middleObject)) {
|
|
140
|
+
return isLoadingInstances ? (React.createElement(Skeleton, null)) : (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
141
|
+
React.createElement(DropdownRepeatableField, { initialMiddleObjectInstances: initialMiddleObjectInstances, fieldDefinition: fieldDefinition, id: entryId, middleObject: middleObject, readOnly: entry.type === 'readonlyField', criteria: memorizedCriteria, hasDescription: !!display?.description })));
|
|
108
142
|
}
|
|
109
143
|
else {
|
|
110
144
|
// when in the builder preview, the middle object won't be fetched so instead show an empty field
|
|
@@ -116,9 +150,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
116
150
|
}
|
|
117
151
|
else {
|
|
118
152
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
119
|
-
React.createElement(RepeatableField, { fieldDefinition: fieldDefinition, canUpdateProperty: entry.type !== 'readonlyField', criteria:
|
|
120
|
-
? updateCriteriaInputs(validation.criteria, getValues(), userAccount, instance)
|
|
121
|
-
: undefined, viewLayout: display?.viewLayout, entry: entry })));
|
|
153
|
+
React.createElement(RepeatableField, { fieldDefinition: fieldDefinition, canUpdateProperty: entry.type !== 'readonlyField', criteria: memorizedCriteria, viewLayout: display?.viewLayout, entry: entry })));
|
|
122
154
|
}
|
|
123
155
|
}
|
|
124
156
|
else if (fieldDefinition.type === 'richText') {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { Action, ApiServices, Column, Columns, EvokeForm, FormEntry, InputField, InputParameter, InputParameterReference, Obj, ObjectInstance, Property, Section, Sections, UserAccount } from '@evoke-platform/context';
|
|
2
|
+
import { Action, ApiServices, Column, Columns, EvokeForm, FormEntry, InputField, InputParameter, InputParameterReference, Obj, ObjectInstance, Property, Section, Sections, UserAccount, ObjWithRoot, PanelViewEntry } from '@evoke-platform/context';
|
|
3
3
|
import { LocalDateTime } from '@js-joda/core';
|
|
4
4
|
import { FieldErrors, FieldValues } from 'react-hook-form';
|
|
5
5
|
import { ObjectProperty } from '../../../../types';
|
|
@@ -41,7 +41,8 @@ export declare function getDefaultPages(parameters: InputParameter[], defaultPag
|
|
|
41
41
|
[x: string]: string;
|
|
42
42
|
}>;
|
|
43
43
|
export declare function updateCriteriaInputs(criteria: Record<string, unknown>, data: Record<string, unknown>, user?: UserAccount, instance?: Record<string, unknown>): Record<string, unknown>;
|
|
44
|
-
export declare
|
|
44
|
+
export declare const fetchMiddleObject: (fieldDefinition: InputParameter | Property, apiServices: ApiServices) => Promise<ObjWithRoot | undefined>;
|
|
45
|
+
export declare const fetchInitialMiddleObjectInstances: (apiServices: ApiServices, fieldDefinition: InputParameter | Property, instanceId: string) => Promise<ObjectInstance[]>;
|
|
45
46
|
export declare const getErrorCountForSection: (section: Section | Column, errors?: FieldErrors) => number;
|
|
46
47
|
export declare const propertyToParameter: (property: Property) => InputParameter;
|
|
47
48
|
export declare const propertyValidationToParameterValidation: (property: Property) => InputParameter['validation'];
|
|
@@ -86,7 +87,7 @@ export declare function formatSubmission(submission: FieldValues, apiServices?:
|
|
|
86
87
|
propertyId: string;
|
|
87
88
|
}, parameters?: InputParameter[]): Promise<FieldValues>;
|
|
88
89
|
export declare function filterEmptySections(entry: Sections | Columns, instance?: FieldValues, formData?: FieldValues): Sections | Columns | null;
|
|
89
|
-
export declare function assignIdsToSectionsAndRichText(entries: FormEntry[], object: Obj, parameters?: InputParameter[]): FormEntry[];
|
|
90
|
+
export declare function assignIdsToSectionsAndRichText(entries: FormEntry[] | PanelViewEntry[], object: Obj, parameters?: InputParameter[]): FormEntry[] | PanelViewEntry[];
|
|
90
91
|
/**
|
|
91
92
|
* Converts a plain text string to RTF format suitable for a RichTextEditor.
|
|
92
93
|
*
|
|
@@ -100,5 +101,23 @@ export declare function assignIdsToSectionsAndRichText(entries: FormEntry[], obj
|
|
|
100
101
|
* This ensures that any plain text input will be safely represented in RTF without losing formatting or characters.
|
|
101
102
|
*/
|
|
102
103
|
export declare function plainTextToRtf(plainText: string): string;
|
|
103
|
-
export declare function getFieldDefinition(entry: FormEntry, object: Obj, parameters?: InputParameter[]): InputParameter | Property | undefined;
|
|
104
|
+
export declare function getFieldDefinition(entry: FormEntry | PanelViewEntry, object: Obj, parameters?: InputParameter[]): InputParameter | Property | undefined;
|
|
104
105
|
export declare function obfuscateValue(value: unknown, property?: Partial<Property> | Partial<ObjectProperty>): unknown;
|
|
106
|
+
export declare function useFormById(formId: string, apiServices: ApiServices, errorMessage?: string): import("@tanstack/react-query/build/legacy/types").UseQueryResult<EvokeForm, Error>;
|
|
107
|
+
/**
|
|
108
|
+
* Extract all values from a criteria/filter object.
|
|
109
|
+
* Handles MongoDB-style query filters with operators like $eq, $in, $and, $or, etc.
|
|
110
|
+
*
|
|
111
|
+
* @param criteria - Filter object with examples:
|
|
112
|
+
* 1. { "propertyId": { "$eq": "value" } }
|
|
113
|
+
* 2. { "propertyId": { "$in": ["value1", "value2"] } }
|
|
114
|
+
* 3. { "propertyId": "value"}
|
|
115
|
+
* 4. { "$and": [ { "propertyId1": { "$eq": "value1" } }, { "propertyId2": { "$eq": "value2" } } ] }
|
|
116
|
+
* 5. { "$or": [ { "propertyId1": { "$eq": "value1" } }, { "propertyId2": { "$eq": "value2" } } ] }
|
|
117
|
+
* 6. Nested combinations of the above
|
|
118
|
+
*
|
|
119
|
+
* @returns Array of all values found in the criteria object
|
|
120
|
+
*/
|
|
121
|
+
export declare const extractPresetValuesFromCriteria: (criteria: Record<string, unknown>) => string[];
|
|
122
|
+
export declare const extractAllCriteria: (flattenFormEntries: FormEntry[], parameters: InputParameter[]) => Record<string, unknown>[];
|
|
123
|
+
export declare const extractPresetValuesFromDynamicDefaultValues: (flattenFormEntries: FormEntry[]) => string[];
|
|
@@ -4,7 +4,9 @@ import { get, isArray, isEmpty, isObject, omit, pick, startCase, transform } fro
|
|
|
4
4
|
import { DateTime } from 'luxon';
|
|
5
5
|
import { nanoid } from 'nanoid';
|
|
6
6
|
import Handlebars from 'no-eval-handlebars';
|
|
7
|
-
import { defaultRuleProcessorMongoDB, formatQuery
|
|
7
|
+
import { defaultRuleProcessorMongoDB, formatQuery } from 'react-querybuilder';
|
|
8
|
+
import { parseMongoDB } from '../../util';
|
|
9
|
+
import { useQuery } from '@tanstack/react-query';
|
|
8
10
|
export const scrollIntoViewWithOffset = (el, offset, container) => {
|
|
9
11
|
const elementRect = el.getBoundingClientRect();
|
|
10
12
|
const containerRect = container ? container.getBoundingClientRect() : document.body.getBoundingClientRect();
|
|
@@ -332,31 +334,19 @@ export function updateCriteriaInputs(criteria, data, user, instance) {
|
|
|
332
334
|
},
|
|
333
335
|
}));
|
|
334
336
|
}
|
|
335
|
-
export async
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const fetchedInitialMiddleObjectInstances = await apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances`), {
|
|
349
|
-
params: { filter: JSON.stringify(filter) },
|
|
350
|
-
});
|
|
351
|
-
setFetchedOptions({
|
|
352
|
-
[`${fieldDefinition.id}InitialMiddleObjectInstances`]: fetchedInitialMiddleObjectInstances,
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
catch (error) {
|
|
357
|
-
console.error('Error fetching collection data:', error);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
337
|
+
export const fetchMiddleObject = async (fieldDefinition, apiServices) => {
|
|
338
|
+
return await apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/effective?sanitizedVersion=true`), {
|
|
339
|
+
params: {
|
|
340
|
+
filter: { fields: ['properties', 'actions', 'rootObjectId'] },
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
export const fetchInitialMiddleObjectInstances = async (apiServices, fieldDefinition, instanceId) => {
|
|
345
|
+
const filter = getMiddleObjectFilter(fieldDefinition, instanceId);
|
|
346
|
+
return await apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances`), {
|
|
347
|
+
params: { filter: JSON.stringify(filter) },
|
|
348
|
+
});
|
|
349
|
+
};
|
|
360
350
|
export const getErrorCountForSection = (section, errors) => {
|
|
361
351
|
const entries = section.entries || [];
|
|
362
352
|
return isArray(section.entries)
|
|
@@ -881,3 +871,123 @@ function applyMaskToObfuscatedValue(value, mask) {
|
|
|
881
871
|
}
|
|
882
872
|
return maskedValue;
|
|
883
873
|
}
|
|
874
|
+
export function useFormById(formId, apiServices, errorMessage) {
|
|
875
|
+
return useQuery({
|
|
876
|
+
queryKey: ['form', formId],
|
|
877
|
+
enabled: formId !== '_auto_' && !!formId,
|
|
878
|
+
staleTime: Infinity,
|
|
879
|
+
queryFn: () => apiServices.get(getPrefixedUrl(`/forms/${formId}`)),
|
|
880
|
+
meta: {
|
|
881
|
+
errorMessage,
|
|
882
|
+
},
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Extract all values from a criteria/filter object.
|
|
887
|
+
* Handles MongoDB-style query filters with operators like $eq, $in, $and, $or, etc.
|
|
888
|
+
*
|
|
889
|
+
* @param criteria - Filter object with examples:
|
|
890
|
+
* 1. { "propertyId": { "$eq": "value" } }
|
|
891
|
+
* 2. { "propertyId": { "$in": ["value1", "value2"] } }
|
|
892
|
+
* 3. { "propertyId": "value"}
|
|
893
|
+
* 4. { "$and": [ { "propertyId1": { "$eq": "value1" } }, { "propertyId2": { "$eq": "value2" } } ] }
|
|
894
|
+
* 5. { "$or": [ { "propertyId1": { "$eq": "value1" } }, { "propertyId2": { "$eq": "value2" } } ] }
|
|
895
|
+
* 6. Nested combinations of the above
|
|
896
|
+
*
|
|
897
|
+
* @returns Array of all values found in the criteria object
|
|
898
|
+
*/
|
|
899
|
+
export const extractPresetValuesFromCriteria = (criteria) => {
|
|
900
|
+
const values = [];
|
|
901
|
+
if (!isObject(criteria) || isArray(criteria)) {
|
|
902
|
+
return values;
|
|
903
|
+
}
|
|
904
|
+
for (const [key, value] of Object.entries(criteria)) {
|
|
905
|
+
if (key === '$and' || key === '$or') {
|
|
906
|
+
// Handle logical operators with array of conditions
|
|
907
|
+
if (isArray(value)) {
|
|
908
|
+
for (const item of value) {
|
|
909
|
+
if (isObject(item)) {
|
|
910
|
+
values.push(...extractPresetValuesFromCriteria(item));
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
else if (key.startsWith('$')) {
|
|
916
|
+
// Handle query operators like $eq, $in, $ne, etc.
|
|
917
|
+
if (isArray(value)) {
|
|
918
|
+
// For operators like $in that contain arrays
|
|
919
|
+
values.push(...value.filter((v) => typeof v === 'string' && v.startsWith('{{{') && v.endsWith('}}}')));
|
|
920
|
+
}
|
|
921
|
+
else {
|
|
922
|
+
// For operators like $eq that contain single values
|
|
923
|
+
if (typeof value === 'string' && value.startsWith('{{{') && value.endsWith('}}}'))
|
|
924
|
+
values.push(value);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
else if (isObject(value)) {
|
|
928
|
+
values.push(...extractPresetValuesFromCriteria(value));
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
// Direct property value
|
|
932
|
+
if (typeof value === 'string' && value.startsWith('{{{') && value.endsWith('}}}'))
|
|
933
|
+
values.push(value);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return values;
|
|
937
|
+
};
|
|
938
|
+
export const extractAllCriteria = (flattenFormEntries, parameters) => {
|
|
939
|
+
const allCriterias = [];
|
|
940
|
+
for (const entry of flattenFormEntries) {
|
|
941
|
+
if (entry.type === 'input' && entry.parameterId) {
|
|
942
|
+
const param = parameters?.find((param) => param.id === entry.parameterId &&
|
|
943
|
+
param.type === 'object' &&
|
|
944
|
+
param.objectId &&
|
|
945
|
+
!isEmpty(param.validation?.criteria));
|
|
946
|
+
if (param) {
|
|
947
|
+
allCriterias.push(param.validation.criteria);
|
|
948
|
+
}
|
|
949
|
+
const defaultValue = entry.display?.defaultValue;
|
|
950
|
+
if (isObject(defaultValue) && 'criteria' in defaultValue) {
|
|
951
|
+
const defaultValueCriteria = defaultValue.criteria;
|
|
952
|
+
if (!isEmpty(defaultValueCriteria)) {
|
|
953
|
+
allCriterias.push(defaultValueCriteria);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
else if (entry.type === 'inputField' && ['collection', 'object'].includes(entry.input?.type || '')) {
|
|
958
|
+
const validationCriteria = entry.input?.validation?.criteria;
|
|
959
|
+
if (!isEmpty(validationCriteria)) {
|
|
960
|
+
allCriterias.push(validationCriteria);
|
|
961
|
+
}
|
|
962
|
+
const defaultValue = entry.display?.defaultValue;
|
|
963
|
+
if (isObject(defaultValue) && 'criteria' in defaultValue) {
|
|
964
|
+
const defaultValueCriteria = defaultValue.criteria;
|
|
965
|
+
if (!isEmpty(defaultValueCriteria)) {
|
|
966
|
+
allCriterias.push(defaultValueCriteria);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return allCriterias;
|
|
972
|
+
};
|
|
973
|
+
export const extractPresetValuesFromDynamicDefaultValues = (flattenFormEntries) => {
|
|
974
|
+
const allPresetValues = [];
|
|
975
|
+
for (const entry of flattenFormEntries) {
|
|
976
|
+
if (entry.type === 'input' || entry.type === 'inputField') {
|
|
977
|
+
const defaultValue = entry.display?.defaultValue;
|
|
978
|
+
if (defaultValue) {
|
|
979
|
+
if (typeof defaultValue === 'string' && defaultValue.startsWith('{{') && defaultValue.endsWith('}}')) {
|
|
980
|
+
allPresetValues.push(defaultValue);
|
|
981
|
+
}
|
|
982
|
+
else if (Array.isArray(defaultValue)) {
|
|
983
|
+
for (const val of defaultValue) {
|
|
984
|
+
if (typeof val === 'string' && val.startsWith('{{') && val.endsWith('}}')) {
|
|
985
|
+
allPresetValues.push(val);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return allPresetValues;
|
|
993
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
1
2
|
import { render as baseRender, screen, waitFor, within } from '@testing-library/react';
|
|
2
3
|
import userEvent from '@testing-library/user-event';
|
|
3
4
|
import { isEmpty, isEqual, set } from 'lodash';
|
|
@@ -14,12 +15,9 @@ global.ResizeObserver = class ResizeObserver {
|
|
|
14
15
|
unobserve() { }
|
|
15
16
|
disconnect() { }
|
|
16
17
|
};
|
|
17
|
-
const WithProviders = ({ children }) => {
|
|
18
|
-
return React.createElement(MemoryRouter, null, children);
|
|
19
|
-
};
|
|
20
|
-
const render = (ui, options) => baseRender(ui, { wrapper: WithProviders, ...options });
|
|
21
18
|
describe('FormRenderer', () => {
|
|
22
19
|
let server;
|
|
20
|
+
let queryClient;
|
|
23
21
|
beforeAll(() => {
|
|
24
22
|
server = setupServer(http.get('/api/data/objects/specialtyType/effective', () => HttpResponse.json(specialtyTypeObject)), http.get('/api/data/objects/specialtyType/effective', (req) => {
|
|
25
23
|
const sanitizedVersion = new URL(req.request.url).searchParams.get('sanitizedVersion');
|
|
@@ -65,12 +63,28 @@ describe('FormRenderer', () => {
|
|
|
65
63
|
}), http.get('/api/accessManagement/users', () => HttpResponse.json(users)));
|
|
66
64
|
server.listen();
|
|
67
65
|
});
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
// Create a fresh QueryClient for each test, need to pass `retry: false` to avoid retries interfering with error state tests
|
|
68
|
+
queryClient = new QueryClient({
|
|
69
|
+
defaultOptions: {
|
|
70
|
+
queries: {
|
|
71
|
+
retry: false,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
server.resetHandlers();
|
|
78
|
+
queryClient.clear();
|
|
70
79
|
});
|
|
71
80
|
afterEach(() => {
|
|
72
81
|
server.resetHandlers();
|
|
73
82
|
});
|
|
83
|
+
const WithProviders = ({ children }) => {
|
|
84
|
+
return (React.createElement(QueryClientProvider, { client: queryClient },
|
|
85
|
+
React.createElement(MemoryRouter, null, children)));
|
|
86
|
+
};
|
|
87
|
+
const render = (ui, options) => baseRender(ui, { wrapper: WithProviders, ...options });
|
|
74
88
|
describe('validation criteria', () => {
|
|
75
89
|
it(`filters related object field with validation criteria that references a related object's nested data`, async () => {
|
|
76
90
|
const user = userEvent.setup();
|
|
@@ -414,7 +428,7 @@ describe('FormRenderer', () => {
|
|
|
414
428
|
type: 'input',
|
|
415
429
|
parameterId: 'specialtyType',
|
|
416
430
|
display: {
|
|
417
|
-
label: '
|
|
431
|
+
label: 'Specialty Type',
|
|
418
432
|
mode: 'existingOnly',
|
|
419
433
|
},
|
|
420
434
|
},
|
|
@@ -500,7 +514,7 @@ describe('FormRenderer', () => {
|
|
|
500
514
|
type: 'input',
|
|
501
515
|
parameterId: 'specialtyType',
|
|
502
516
|
display: {
|
|
503
|
-
label: '
|
|
517
|
+
label: 'Specialty Type',
|
|
504
518
|
},
|
|
505
519
|
},
|
|
506
520
|
],
|
|
@@ -586,7 +600,7 @@ describe('FormRenderer', () => {
|
|
|
586
600
|
type: 'input',
|
|
587
601
|
parameterId: 'specialtyType',
|
|
588
602
|
display: {
|
|
589
|
-
label: '
|
|
603
|
+
label: 'Specialty Type',
|
|
590
604
|
relatedObjectDisplay: 'dialogBox',
|
|
591
605
|
mode: 'existingOnly',
|
|
592
606
|
},
|
|
@@ -654,7 +668,7 @@ describe('FormRenderer', () => {
|
|
|
654
668
|
type: 'input',
|
|
655
669
|
parameterId: 'specialtyType',
|
|
656
670
|
display: {
|
|
657
|
-
label: '
|
|
671
|
+
label: 'Specialty Type',
|
|
658
672
|
relatedObjectDisplay: 'dialogBox',
|
|
659
673
|
mode: 'existingOnly',
|
|
660
674
|
},
|
|
@@ -684,7 +698,7 @@ describe('FormRenderer', () => {
|
|
|
684
698
|
type: 'input',
|
|
685
699
|
parameterId: 'specialtyType',
|
|
686
700
|
display: {
|
|
687
|
-
label: '
|
|
701
|
+
label: 'Specialty Type',
|
|
688
702
|
createActionId: '_create',
|
|
689
703
|
},
|
|
690
704
|
},
|
|
@@ -944,7 +958,7 @@ describe('FormRenderer', () => {
|
|
|
944
958
|
type: 'input',
|
|
945
959
|
parameterId: 'specialtyType',
|
|
946
960
|
display: {
|
|
947
|
-
label: '
|
|
961
|
+
label: 'Specialty Type',
|
|
948
962
|
relatedObjectDisplay: 'dropdown',
|
|
949
963
|
mode: 'existingOnly',
|
|
950
964
|
},
|
|
@@ -1021,7 +1035,7 @@ describe('FormRenderer', () => {
|
|
|
1021
1035
|
const user = userEvent.setup();
|
|
1022
1036
|
render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
|
|
1023
1037
|
// Navigate to and open dropdown
|
|
1024
|
-
const dropdown = await screen.findByRole('combobox', { name: '
|
|
1038
|
+
const dropdown = await screen.findByRole('combobox', { name: 'Specialty Type' });
|
|
1025
1039
|
await user.click(dropdown);
|
|
1026
1040
|
await screen.findByRole('listbox');
|
|
1027
1041
|
// Verify that the existing option is present
|
|
@@ -1176,7 +1190,7 @@ describe('FormRenderer', () => {
|
|
|
1176
1190
|
type: 'input',
|
|
1177
1191
|
parameterId: 'specialtyType',
|
|
1178
1192
|
display: {
|
|
1179
|
-
label: '
|
|
1193
|
+
label: 'Specialty Type',
|
|
1180
1194
|
createActionId: '_create',
|
|
1181
1195
|
relatedObjectId: 'specialtyType',
|
|
1182
1196
|
},
|