@evoke-platform/ui-components 1.13.0-dev.6 → 1.13.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.d.ts +4 -4
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +72 -145
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +67 -189
- package/dist/published/components/custom/CriteriaBuilder/PropertyTree.d.ts +6 -6
- package/dist/published/components/custom/CriteriaBuilder/PropertyTree.js +25 -12
- package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.d.ts +5 -4
- package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.js +22 -34
- package/dist/published/components/custom/CriteriaBuilder/types.d.ts +11 -2
- package/dist/published/components/custom/CriteriaBuilder/utils.d.ts +34 -6
- package/dist/published/components/custom/CriteriaBuilder/utils.js +89 -18
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +1 -1
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js +3 -6
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
- package/dist/published/components/custom/Form/utils.d.ts +0 -1
- package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +1 -2
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +1 -1
- package/dist/published/components/custom/FormV2/FormRenderer.js +2 -1
- package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +3 -1
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +23 -26
- package/dist/published/components/custom/FormV2/components/Body.js +1 -1
- package/dist/published/components/custom/FormV2/components/Footer.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +0 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +0 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +3 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +2 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +10 -46
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +3 -4
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +23 -29
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormSections.js +0 -1
- package/dist/published/components/custom/FormV2/components/Header.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/Header.js +19 -8
- package/dist/published/components/custom/FormV2/components/HtmlView.d.ts +9 -0
- package/dist/published/components/custom/FormV2/components/HtmlView.js +42 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +4 -8
- package/dist/published/components/custom/FormV2/components/types.d.ts +1 -6
- package/dist/published/components/custom/FormV2/components/utils.d.ts +7 -5
- package/dist/published/components/custom/FormV2/components/utils.js +79 -156
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +2 -2
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +84 -0
- package/dist/published/components/custom/HistoryLog/HistoryData.js +1 -2
- package/dist/published/components/custom/HistoryLog/index.js +1 -2
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +10 -24
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.d.ts +3 -0
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +3 -1
- package/dist/published/stories/Backdrop.stories.d.ts +2 -2
- package/dist/published/stories/CriteriaBuilder.stories.js +22 -70
- package/dist/published/stories/FormLabel.stories.d.ts +2 -2
- package/dist/published/stories/FormRenderer.stories.d.ts +3 -3
- package/dist/published/stories/FormRendererContainer.stories.d.ts +15 -5
- package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +9 -0
- package/dist/published/theme/hooks.d.ts +1 -2
- package/package.json +10 -15
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Property } from '@evoke-platform/context';
|
|
2
1
|
import React from 'react';
|
|
3
2
|
import 'react-querybuilder/dist/query-builder.css';
|
|
4
|
-
import {
|
|
3
|
+
import { EvokeObject } from '../../../types';
|
|
4
|
+
import { ObjectProperty, Operator, PresetValue, TreeViewObject } from './types';
|
|
5
5
|
import { ValueEditorProps } from './ValueEditor';
|
|
6
6
|
export type CriteriaInputProps = {
|
|
7
|
-
properties:
|
|
7
|
+
properties: ObjectProperty[];
|
|
8
8
|
setCriteria: (criteria?: Record<string, unknown> | undefined) => void;
|
|
9
9
|
criteria?: Record<string, unknown>;
|
|
10
10
|
originalCriteria?: Record<string, unknown>;
|
|
@@ -23,7 +23,7 @@ export type CriteriaInputProps = {
|
|
|
23
23
|
hideBorder?: boolean;
|
|
24
24
|
presetGroupLabel?: string;
|
|
25
25
|
treeViewOpts?: {
|
|
26
|
-
fetchObject?: (objectId: string) => Promise<
|
|
26
|
+
fetchObject?: (objectId: string) => Promise<EvokeObject | undefined>;
|
|
27
27
|
object: TreeViewObject;
|
|
28
28
|
};
|
|
29
29
|
/**
|
|
@@ -11,7 +11,7 @@ import { Box } from '../../layout';
|
|
|
11
11
|
import { OverflowTextField } from '../OverflowTextField';
|
|
12
12
|
import { difference } from '../util';
|
|
13
13
|
import PropertyTree from './PropertyTree';
|
|
14
|
-
import { ALL_OPERATORS, parseMongoDB } from './utils';
|
|
14
|
+
import { ALL_OPERATORS, parseMongoDB, traversePropertyPath } from './utils';
|
|
15
15
|
import ValueEditor from './ValueEditor';
|
|
16
16
|
const styles = {
|
|
17
17
|
buttons: {
|
|
@@ -103,7 +103,6 @@ const customSelector = (props) => {
|
|
|
103
103
|
const isTreeViewEnabled = context.treeViewOpts && title === 'Fields';
|
|
104
104
|
const fetchObject = context.treeViewOpts?.fetchObject;
|
|
105
105
|
const object = context.treeViewOpts?.object;
|
|
106
|
-
const setObject = context.treeViewOpts?.setObject;
|
|
107
106
|
let readOnly = context.disabled;
|
|
108
107
|
if (!readOnly && context.disabledCriteria) {
|
|
109
108
|
readOnly =
|
|
@@ -174,7 +173,14 @@ const customSelector = (props) => {
|
|
|
174
173
|
val = options.find((option) => option.name === val)?.name;
|
|
175
174
|
break;
|
|
176
175
|
}
|
|
177
|
-
|
|
176
|
+
const handleTreePropertySelect = async (propertyId) => {
|
|
177
|
+
const propertyInfo = await traversePropertyPath(propertyId, object, fetchObject);
|
|
178
|
+
context.setPropertyTreeMap((prev) => {
|
|
179
|
+
return { ...prev, [propertyId]: propertyInfo };
|
|
180
|
+
});
|
|
181
|
+
handleOnChange(propertyId);
|
|
182
|
+
};
|
|
183
|
+
return (React.createElement(React.Fragment, null, isTreeViewEnabled ? (React.createElement(PropertyTree, { value: val ?? value, rootObject: object, fetchObject: fetchObject, propertyTreeMap: context.propertyTreeMap ?? {}, handleTreePropertySelect: handleTreePropertySelect })) : (React.createElement(Autocomplete, { options: opts, value: val ?? null, getOptionLabel: (option) => {
|
|
178
184
|
if (typeof option === 'string') {
|
|
179
185
|
return opts.find((o) => option === o.name)?.label || option;
|
|
180
186
|
}
|
|
@@ -269,159 +275,77 @@ const getAllRuleIds = (rules) => {
|
|
|
269
275
|
});
|
|
270
276
|
return ids;
|
|
271
277
|
};
|
|
272
|
-
const processRules = (rules, properties) => {
|
|
273
|
-
return rules.map((rule) => {
|
|
274
|
-
if ('rules' in rule) {
|
|
275
|
-
return {
|
|
276
|
-
...rule,
|
|
277
|
-
rules: processRules(rule.rules, properties),
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
const propertyType = properties.find((property) => property.id === rule.field)?.type;
|
|
282
|
-
let adjustedValue = rule.value;
|
|
283
|
-
if ((rule.operator === 'null' || rule.operator === 'notNull') && rule.value) {
|
|
284
|
-
adjustedValue = null;
|
|
285
|
-
}
|
|
286
|
-
return {
|
|
287
|
-
...rule,
|
|
288
|
-
operator: propertyType === 'array' && rule.operator === '=' ? 'in' : rule.operator,
|
|
289
|
-
value: adjustedValue,
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
};
|
|
294
|
-
const insertChangesOnly = (prev, newObject) => {
|
|
295
|
-
const mergedProperties = newObject.properties.map((newProp) => {
|
|
296
|
-
const prevProp = prev.properties.find((prop) => prop.id === newProp.id);
|
|
297
|
-
if (prevProp) {
|
|
298
|
-
return {
|
|
299
|
-
...newProp,
|
|
300
|
-
children: newProp.children &&
|
|
301
|
-
newProp.children[0].type === 'loading' &&
|
|
302
|
-
prevProp.children
|
|
303
|
-
? prevProp.children
|
|
304
|
-
: newProp.children && prevProp.children
|
|
305
|
-
? insertChangesOnly({ id: prevProp.id, name: prevProp.name, properties: prevProp.children }, { id: newProp.id, name: newProp.name, properties: newProp.children }).properties
|
|
306
|
-
: newProp.children,
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
return newProp;
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
return {
|
|
314
|
-
...newObject,
|
|
315
|
-
properties: mergedProperties,
|
|
316
|
-
};
|
|
317
|
-
};
|
|
318
278
|
const CriteriaBuilder = (props) => {
|
|
319
279
|
const { properties, criteria, setCriteria, originalCriteria, enablePresetValues, presetValues, operators, disabled, disabledCriteria, hideBorder, presetGroupLabel, customValueEditor, treeViewOpts, disableRegexEscapeChars, } = props;
|
|
320
|
-
const [propertyTreeMap, setPropertyTreeMap] = useState(
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
properties: treeViewOpts.object.properties
|
|
328
|
-
.filter((prop) => prop.type !== 'collection')
|
|
329
|
-
.map((prop) => ({
|
|
330
|
-
...prop,
|
|
331
|
-
children: prop.children
|
|
332
|
-
?.filter((child) => child.type !== 'collection')
|
|
333
|
-
.map((child) => ({
|
|
334
|
-
...child,
|
|
335
|
-
id: `${prop.id}.${child.id}`,
|
|
336
|
-
})),
|
|
337
|
-
})),
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
if ((criteria || originalCriteria) && obj && treeViewOpts?.fetchObject) {
|
|
341
|
-
const updateTreeViewObject = async (criteria, treeViewObject, fetchObject) => {
|
|
342
|
-
const newQuery = parseMongoDB(criteria);
|
|
343
|
-
const ids = getAllRuleIds(newQuery.rules);
|
|
344
|
-
const newTreeViewObject = { ...treeViewObject };
|
|
345
|
-
const traversePath = async (path, properties, fetchObject) => {
|
|
346
|
-
const prop = properties?.find((prop) => path.startsWith(`${prop.id}.`) || path === prop.id);
|
|
347
|
-
if (prop?.type === 'object' && prop.objectId && prop.children) {
|
|
348
|
-
if (prop.children.length === 1 &&
|
|
349
|
-
prop.children[0]?.type === 'loading') {
|
|
350
|
-
try {
|
|
351
|
-
const fetchedObject = await fetchObject(prop.objectId);
|
|
352
|
-
prop.children = fetchedObject?.properties
|
|
353
|
-
?.filter((item) => item.type !== 'collection')
|
|
354
|
-
.map((item) => {
|
|
355
|
-
return {
|
|
356
|
-
...item,
|
|
357
|
-
id: `${prop.id}.${item.id}`,
|
|
358
|
-
children: item.children?.map((child) => ({
|
|
359
|
-
...child,
|
|
360
|
-
id: `${prop.id}.${item.id}.${child.id}`,
|
|
361
|
-
})),
|
|
362
|
-
};
|
|
363
|
-
});
|
|
364
|
-
fetchedObject && (await traversePath(path, fetchedObject.properties, fetchObject));
|
|
365
|
-
}
|
|
366
|
-
catch (error) {
|
|
367
|
-
prop.children = [
|
|
368
|
-
{
|
|
369
|
-
id: `${prop.id}-failed`,
|
|
370
|
-
name: 'Loading Failed',
|
|
371
|
-
type: 'loadingFailed',
|
|
372
|
-
},
|
|
373
|
-
];
|
|
374
|
-
console.error('Error fetching object for criteria builder:', error);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
|
-
await traversePath(path, prop.children, fetchObject);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
280
|
+
const [propertyTreeMap, setPropertyTreeMap] = useState();
|
|
281
|
+
const processRules = (rules) => {
|
|
282
|
+
return rules.map((rule) => {
|
|
283
|
+
if ('rules' in rule) {
|
|
284
|
+
return {
|
|
285
|
+
...rule,
|
|
286
|
+
rules: processRules(rule.rules),
|
|
381
287
|
};
|
|
382
|
-
|
|
383
|
-
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
const propertyType = properties.find((property) => property.id === rule.field)?.type;
|
|
291
|
+
let adjustedValue = rule.value;
|
|
292
|
+
if ((rule.operator === 'null' || rule.operator === 'notNull') && rule.value) {
|
|
293
|
+
adjustedValue = null;
|
|
384
294
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
295
|
+
return {
|
|
296
|
+
...rule,
|
|
297
|
+
operator: propertyType === 'array' && rule.operator === '=' ? 'in' : rule.operator,
|
|
298
|
+
value: adjustedValue,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
};
|
|
390
303
|
useEffect(() => {
|
|
391
|
-
if (
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
304
|
+
if ((criteria || originalCriteria) &&
|
|
305
|
+
!isEmpty(treeViewOpts) &&
|
|
306
|
+
treeViewOpts.object &&
|
|
307
|
+
treeViewOpts.fetchObject) {
|
|
308
|
+
const { object, fetchObject } = treeViewOpts;
|
|
309
|
+
// this retrieves the properties from a treeview for each property in the query
|
|
310
|
+
// they are then used in the custom query builder components to determine the input type etc
|
|
311
|
+
const updatePropertyTreeMap = async () => {
|
|
312
|
+
const newQuery = parseMongoDB(criteria || originalCriteria || {});
|
|
313
|
+
const ids = getAllRuleIds(newQuery.rules);
|
|
314
|
+
let newPropertyTreeMap = {};
|
|
315
|
+
const newPropertyTreeMapPromises = [];
|
|
316
|
+
for (const id of ids) {
|
|
317
|
+
if (!propertyTreeMap?.[id]) {
|
|
318
|
+
newPropertyTreeMapPromises.push(traversePropertyPath(id, object, fetchObject)
|
|
319
|
+
.then((property) => {
|
|
320
|
+
if (property) {
|
|
321
|
+
return {
|
|
322
|
+
[id]: property,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return {};
|
|
326
|
+
})
|
|
327
|
+
.catch((err) => {
|
|
328
|
+
console.error(err);
|
|
329
|
+
return {};
|
|
330
|
+
}));
|
|
404
331
|
}
|
|
405
332
|
}
|
|
406
|
-
|
|
333
|
+
newPropertyTreeMap = (await Promise.all(newPropertyTreeMapPromises)).reduce((acc, currentProperty) => ({ ...acc, ...currentProperty }), {});
|
|
334
|
+
setPropertyTreeMap((prevPropertyTreeMap) => ({
|
|
335
|
+
...prevPropertyTreeMap,
|
|
336
|
+
...newPropertyTreeMap,
|
|
337
|
+
}));
|
|
407
338
|
};
|
|
408
|
-
|
|
409
|
-
};
|
|
410
|
-
const newQuery = parseMongoDB(criteria || originalCriteria || {});
|
|
411
|
-
const ids = getAllRuleIds(newQuery.rules);
|
|
412
|
-
const result = {};
|
|
413
|
-
for (const id of ids) {
|
|
414
|
-
result[id] = getNamePath(id, treeViewObject);
|
|
339
|
+
updatePropertyTreeMap().catch((err) => console.error(err));
|
|
415
340
|
}
|
|
416
|
-
|
|
417
|
-
}, [criteria, originalCriteria, treeViewObject]);
|
|
341
|
+
}, [criteria, originalCriteria, treeViewOpts]);
|
|
418
342
|
const initializeQuery = () => {
|
|
419
343
|
const criteriaToParse = criteria || originalCriteria;
|
|
420
344
|
const updatedQuery = criteriaToParse ? parseMongoDB(criteriaToParse || {}) : undefined;
|
|
421
345
|
return updatedQuery
|
|
422
346
|
? {
|
|
423
347
|
...updatedQuery,
|
|
424
|
-
rules: processRules(updatedQuery.rules
|
|
348
|
+
rules: processRules(updatedQuery.rules),
|
|
425
349
|
}
|
|
426
350
|
: { combinator: 'and', rules: [] };
|
|
427
351
|
};
|
|
@@ -447,7 +371,7 @@ const CriteriaBuilder = (props) => {
|
|
|
447
371
|
const handleQueryChange = (q) => {
|
|
448
372
|
const processedQuery = {
|
|
449
373
|
...q,
|
|
450
|
-
rules: processRules(q.rules
|
|
374
|
+
rules: processRules(q.rules),
|
|
451
375
|
};
|
|
452
376
|
setQuery(processedQuery);
|
|
453
377
|
const newCriteria = JSON.parse(formatQuery(processedQuery, {
|
|
@@ -601,14 +525,17 @@ const CriteriaBuilder = (props) => {
|
|
|
601
525
|
presetGroupLabel,
|
|
602
526
|
disabled,
|
|
603
527
|
disabledCriteria,
|
|
604
|
-
treeViewOpts:
|
|
528
|
+
treeViewOpts: treeViewOpts
|
|
605
529
|
? {
|
|
606
530
|
...treeViewOpts,
|
|
607
|
-
object:
|
|
608
|
-
|
|
531
|
+
object: {
|
|
532
|
+
...treeViewOpts?.object,
|
|
533
|
+
properties: treeViewOpts?.object.properties.filter(({ type }) => type !== 'collection'),
|
|
534
|
+
},
|
|
609
535
|
}
|
|
610
536
|
: undefined,
|
|
611
537
|
propertyTreeMap,
|
|
538
|
+
setPropertyTreeMap,
|
|
612
539
|
}, controlClassnames: {
|
|
613
540
|
ruleGroup: 'container',
|
|
614
541
|
}, operators: operators
|
|
@@ -1,196 +1,74 @@
|
|
|
1
|
-
import { render, screen
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { expect, it } from 'vitest';
|
|
5
5
|
import CriteriaBuilder from './CriteriaBuilder';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// Step 5: Wait for the related object properties to load and verify ID and Name are visible
|
|
72
|
-
await waitFor(() => {
|
|
73
|
-
expect(screen.getByText('ID')).toBeInTheDocument();
|
|
74
|
-
// eslint-disable-next-line testing-library/no-wait-for-multiple-assertions
|
|
75
|
-
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
76
|
-
}, { timeout: 6000 });
|
|
77
|
-
});
|
|
78
|
-
it('fails to expand related object property', async () => {
|
|
79
|
-
const user = userEvent.setup();
|
|
80
|
-
render(React.createElement(CriteriaBuilder, { treeViewOpts: {
|
|
81
|
-
object: {
|
|
82
|
-
id: 'objectA',
|
|
83
|
-
name: 'Object A',
|
|
84
|
-
properties: [
|
|
85
|
-
{
|
|
86
|
-
id: 'relatedObjectProp',
|
|
87
|
-
name: 'Related Object Prop',
|
|
88
|
-
type: 'object',
|
|
89
|
-
objectId: 'objectB',
|
|
90
|
-
children: [
|
|
91
|
-
{
|
|
92
|
-
id: 'relatedObjectProp-loading',
|
|
93
|
-
name: 'Loading...',
|
|
94
|
-
type: 'loading',
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
},
|
|
100
|
-
fetchObject: async (id) => {
|
|
101
|
-
if (id === 'objectB') {
|
|
102
|
-
return new Promise((resolve, reject) => {
|
|
103
|
-
setTimeout(() => {
|
|
104
|
-
reject('Failed to fetch objectB');
|
|
105
|
-
}, 5000);
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
return undefined;
|
|
109
|
-
},
|
|
110
|
-
}, properties: [], criteria: {}, setCriteria: setCriteriaMock }));
|
|
111
|
-
// Step 1: Click "Add Condition" button to create a new rule
|
|
112
|
-
const addConditionButton = screen.getByRole('button', { name: /^add condition$/i });
|
|
113
|
-
await user.click(addConditionButton);
|
|
114
|
-
// Step 2: Click on the property selector to open the dropdown
|
|
115
|
-
const propertySelector = screen.getByPlaceholderText(/select a property/i);
|
|
116
|
-
await user.click(propertySelector);
|
|
117
|
-
// Step 3: Click on "Related Object Prop" to expand it and fetch its children
|
|
118
|
-
const relatedObjectPropItem = screen.getByText('Related Object Prop');
|
|
119
|
-
await user.click(relatedObjectPropItem);
|
|
120
|
-
// Step 4: Verify loading indicator is shown
|
|
121
|
-
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
|
122
|
-
// Step 5: Wait for the related object properties to load and verify ID and Name are visible
|
|
123
|
-
await waitFor(() => {
|
|
124
|
-
expect(screen.getByText('Loading Failed')).toBeInTheDocument();
|
|
125
|
-
}, { timeout: 6000 });
|
|
126
|
-
});
|
|
127
|
-
});
|
|
6
|
+
const mockProperties = [
|
|
7
|
+
{
|
|
8
|
+
id: 'name',
|
|
9
|
+
name: 'Name',
|
|
10
|
+
type: 'string',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: 'age',
|
|
14
|
+
name: 'Age',
|
|
15
|
+
type: 'integer',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'birthDate',
|
|
19
|
+
name: 'Birth Date',
|
|
20
|
+
type: 'date',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'createdTime',
|
|
24
|
+
name: 'Created Time',
|
|
25
|
+
type: 'time',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'tags',
|
|
29
|
+
name: 'Tags',
|
|
30
|
+
type: 'array',
|
|
31
|
+
enum: ['tag1', 'tag2', 'tag3'],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'status',
|
|
35
|
+
name: 'Status',
|
|
36
|
+
type: 'string',
|
|
37
|
+
enum: ['active', 'inactive', 'pending'],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'profilePic',
|
|
41
|
+
name: 'Profile Picture',
|
|
42
|
+
type: 'image',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'metadata',
|
|
46
|
+
name: 'Metadata',
|
|
47
|
+
type: 'document',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'percentCompleted',
|
|
51
|
+
name: 'Percent Completed',
|
|
52
|
+
type: 'number',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'boolean',
|
|
56
|
+
name: 'Boolean',
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'regularRelatedObject',
|
|
61
|
+
name: 'Regular Related Object',
|
|
62
|
+
type: 'object',
|
|
63
|
+
objectId: 'relatedObjectId',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'dynamicRelatedObject',
|
|
67
|
+
name: 'Dynamic Related Object',
|
|
68
|
+
type: 'object',
|
|
69
|
+
},
|
|
70
|
+
];
|
|
128
71
|
describe('CriteriaBuilder', () => {
|
|
129
|
-
const mockProperties = [
|
|
130
|
-
{
|
|
131
|
-
id: 'name',
|
|
132
|
-
name: 'Name',
|
|
133
|
-
type: 'string',
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
id: 'age',
|
|
137
|
-
name: 'Age',
|
|
138
|
-
type: 'integer',
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
id: 'birthDate',
|
|
142
|
-
name: 'Birth Date',
|
|
143
|
-
type: 'date',
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
id: 'createdTime',
|
|
147
|
-
name: 'Created Time',
|
|
148
|
-
type: 'time',
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
id: 'tags',
|
|
152
|
-
name: 'Tags',
|
|
153
|
-
type: 'array',
|
|
154
|
-
enum: ['tag1', 'tag2', 'tag3'],
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
id: 'status',
|
|
158
|
-
name: 'Status',
|
|
159
|
-
type: 'string',
|
|
160
|
-
enum: ['active', 'inactive', 'pending'],
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
id: 'profilePic',
|
|
164
|
-
name: 'Profile Picture',
|
|
165
|
-
type: 'image',
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
id: 'metadata',
|
|
169
|
-
name: 'Metadata',
|
|
170
|
-
type: 'document',
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
id: 'percentCompleted',
|
|
174
|
-
name: 'Percent Completed',
|
|
175
|
-
type: 'number',
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
id: 'boolean',
|
|
179
|
-
name: 'Boolean',
|
|
180
|
-
type: 'boolean',
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
id: 'regularRelatedObject',
|
|
184
|
-
name: 'Regular Related Object',
|
|
185
|
-
type: 'object',
|
|
186
|
-
objectId: 'relatedObjectId',
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
id: 'dynamicRelatedObject',
|
|
190
|
-
name: 'Dynamic Related Object',
|
|
191
|
-
type: 'object',
|
|
192
|
-
},
|
|
193
|
-
];
|
|
194
72
|
// Mock function for setCriteria
|
|
195
73
|
const setCriteriaMock = vi.fn();
|
|
196
74
|
beforeEach(() => {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { Property } from '@evoke-platform/context';
|
|
1
2
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
+
import { EvokeObject } from '../../../types';
|
|
3
4
|
type PropertyTreeProps = {
|
|
4
|
-
fetchObject: (id: string) => Promise<
|
|
5
|
-
rootObject:
|
|
6
|
-
|
|
7
|
-
handleTreePropertySelect: (propertyId: string) => void;
|
|
5
|
+
fetchObject: (id: string) => Promise<EvokeObject | undefined>;
|
|
6
|
+
rootObject: EvokeObject;
|
|
7
|
+
handleTreePropertySelect: (propertyId: string) => Promise<void>;
|
|
8
8
|
value: string | undefined;
|
|
9
|
-
propertyTreeMap: Record<string,
|
|
9
|
+
propertyTreeMap: Record<string, Property>;
|
|
10
10
|
};
|
|
11
11
|
declare const PropertyTree: (props: PropertyTreeProps) => React.JSX.Element;
|
|
12
12
|
export default PropertyTree;
|
|
@@ -4,25 +4,38 @@ import { Autocomplete, RichTreeView } from '../../core';
|
|
|
4
4
|
import { Box } from '../../layout';
|
|
5
5
|
import { OverflowTextField } from '../OverflowTextField';
|
|
6
6
|
import { PropertyTreeItem } from './PropertyTreeItem';
|
|
7
|
-
import {
|
|
7
|
+
import { findTreeItemById, truncateNamePath, updateTreeNode } from './utils';
|
|
8
8
|
const NAME_PATH_LIMIT = 35;
|
|
9
9
|
const PropertyTree = (props) => {
|
|
10
|
-
const { fetchObject, handleTreePropertySelect, rootObject,
|
|
10
|
+
const { fetchObject, handleTreePropertySelect, rootObject, value, propertyTreeMap } = props;
|
|
11
11
|
const [options, setOptions] = useState([]);
|
|
12
12
|
const [expandedItems, setExpandedItems] = useState([]);
|
|
13
13
|
const [openDropdown, setOpenDropdown] = useState(false);
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
// Transform rootObject properties to TreeItem format
|
|
16
|
-
setOptions(rootObject.properties.map(
|
|
16
|
+
setOptions(rootObject.properties.map((property) => ({
|
|
17
|
+
id: property.id,
|
|
18
|
+
label: property.name,
|
|
19
|
+
value: property.id,
|
|
20
|
+
type: property.type,
|
|
21
|
+
objectId: property.objectId,
|
|
22
|
+
children: property.children
|
|
23
|
+
? property.children.map((child) => ({
|
|
24
|
+
id: child.id,
|
|
25
|
+
label: child.name,
|
|
26
|
+
value: child.id,
|
|
27
|
+
type: child.type,
|
|
28
|
+
objectId: child.objectId,
|
|
29
|
+
}))
|
|
30
|
+
: undefined,
|
|
31
|
+
})));
|
|
32
|
+
setExpandedItems([]);
|
|
17
33
|
}, [rootObject.properties]);
|
|
18
34
|
const handleExpandedItemsChange = (e, itemIds) => {
|
|
19
35
|
setExpandedItems(itemIds);
|
|
20
36
|
};
|
|
21
37
|
const handleUpdateNodeChildren = (nodeId, children) => {
|
|
22
|
-
|
|
23
|
-
...prevObject,
|
|
24
|
-
properties: updateTreeViewProperty(prevObject.properties, nodeId, (node) => ({ ...node, children })),
|
|
25
|
-
}));
|
|
38
|
+
setOptions((prevOptions) => updateTreeNode(prevOptions, nodeId, (node) => ({ ...node, children })));
|
|
26
39
|
};
|
|
27
40
|
return (React.createElement(Autocomplete, { "aria-label": "Property Selector", value: value, fullWidth: true, sx: { width: '37%' }, size: "small", disableClearable: true, options: options, open: openDropdown, onBlur: (e) => {
|
|
28
41
|
const targetComponents = e.relatedTarget?.getAttribute('id')?.split('-');
|
|
@@ -45,11 +58,11 @@ const PropertyTree = (props) => {
|
|
|
45
58
|
}, onFocus: () => setOpenDropdown(true), getOptionLabel: (option) => {
|
|
46
59
|
// Retrieve the full name path from the map
|
|
47
60
|
const namePath = typeof option === 'string'
|
|
48
|
-
? (propertyTreeMap[option] ?? '')
|
|
49
|
-
: propertyTreeMap[option.value]
|
|
61
|
+
? ((propertyTreeMap[option] ?? '')?.name ?? option)
|
|
62
|
+
: (propertyTreeMap[option.value]?.name ?? option.value);
|
|
50
63
|
return truncateNamePath(namePath, NAME_PATH_LIMIT);
|
|
51
64
|
}, renderInput: (params) => {
|
|
52
|
-
const fullDisplayName = value && propertyTreeMap[value];
|
|
65
|
+
const fullDisplayName = value && propertyTreeMap[value]?.name;
|
|
53
66
|
return (React.createElement(OverflowTextField, { ...params, "aria-label": fullDisplayName, value: fullDisplayName, placeholder: "Select a property" }));
|
|
54
67
|
}, ListboxComponent: (props) => {
|
|
55
68
|
return (React.createElement(ClickAwayListener, { onClickAway: (e) => {
|
|
@@ -60,8 +73,8 @@ const PropertyTree = (props) => {
|
|
|
60
73
|
} },
|
|
61
74
|
React.createElement(Box, { ...props },
|
|
62
75
|
React.createElement(RichTreeView, { sx: { width: '100%', paddingLeft: 0 }, role: "menu", "aria-label": "Property Tree", expandedItems: expandedItems, onExpandedItemsChange: handleExpandedItemsChange, items: options, slots: {
|
|
63
|
-
item: (itemProps) => (React.createElement(PropertyTreeItem, { ...itemProps, items: options, expanded: expandedItems, setExpanded: setExpandedItems, fetchObject: fetchObject, updateNodeChildren: handleUpdateNodeChildren, handleTreePropertySelect: (propertyId) => {
|
|
64
|
-
handleTreePropertySelect(propertyId);
|
|
76
|
+
item: (itemProps) => (React.createElement(PropertyTreeItem, { ...itemProps, items: options, expanded: expandedItems, setExpanded: setExpandedItems, fetchObject: fetchObject, updateNodeChildren: handleUpdateNodeChildren, handleTreePropertySelect: async (propertyId) => {
|
|
77
|
+
await handleTreePropertySelect(propertyId);
|
|
65
78
|
setOpenDropdown(false);
|
|
66
79
|
} })),
|
|
67
80
|
} }))));
|