@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.
Files changed (53) hide show
  1. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.d.ts +4 -4
  2. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +72 -145
  3. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +67 -189
  4. package/dist/published/components/custom/CriteriaBuilder/PropertyTree.d.ts +6 -6
  5. package/dist/published/components/custom/CriteriaBuilder/PropertyTree.js +25 -12
  6. package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.d.ts +5 -4
  7. package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.js +22 -34
  8. package/dist/published/components/custom/CriteriaBuilder/types.d.ts +11 -2
  9. package/dist/published/components/custom/CriteriaBuilder/utils.d.ts +34 -6
  10. package/dist/published/components/custom/CriteriaBuilder/utils.js +89 -18
  11. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +1 -1
  12. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js +3 -6
  13. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
  14. package/dist/published/components/custom/Form/utils.d.ts +0 -1
  15. package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +1 -2
  16. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +1 -1
  17. package/dist/published/components/custom/FormV2/FormRenderer.js +2 -1
  18. package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +3 -1
  19. package/dist/published/components/custom/FormV2/FormRendererContainer.js +23 -26
  20. package/dist/published/components/custom/FormV2/components/Body.js +1 -1
  21. package/dist/published/components/custom/FormV2/components/Footer.js +1 -1
  22. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +0 -1
  23. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +0 -1
  24. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +3 -3
  25. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +2 -3
  26. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +10 -46
  27. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +3 -4
  28. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +23 -29
  29. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +1 -1
  30. package/dist/published/components/custom/FormV2/components/FormSections.js +0 -1
  31. package/dist/published/components/custom/FormV2/components/Header.d.ts +1 -0
  32. package/dist/published/components/custom/FormV2/components/Header.js +19 -8
  33. package/dist/published/components/custom/FormV2/components/HtmlView.d.ts +9 -0
  34. package/dist/published/components/custom/FormV2/components/HtmlView.js +42 -0
  35. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +4 -8
  36. package/dist/published/components/custom/FormV2/components/types.d.ts +1 -6
  37. package/dist/published/components/custom/FormV2/components/utils.d.ts +7 -5
  38. package/dist/published/components/custom/FormV2/components/utils.js +79 -156
  39. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +2 -2
  40. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +84 -0
  41. package/dist/published/components/custom/HistoryLog/HistoryData.js +1 -2
  42. package/dist/published/components/custom/HistoryLog/index.js +1 -2
  43. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +10 -24
  44. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.d.ts +3 -0
  45. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +3 -1
  46. package/dist/published/stories/Backdrop.stories.d.ts +2 -2
  47. package/dist/published/stories/CriteriaBuilder.stories.js +22 -70
  48. package/dist/published/stories/FormLabel.stories.d.ts +2 -2
  49. package/dist/published/stories/FormRenderer.stories.d.ts +3 -3
  50. package/dist/published/stories/FormRendererContainer.stories.d.ts +15 -5
  51. package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +9 -0
  52. package/dist/published/theme/hooks.d.ts +1 -2
  53. 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 { Operator, PresetValue, TreeViewObject } from './types';
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: Property[];
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<TreeViewObject | undefined>;
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
- return (React.createElement(React.Fragment, null, isTreeViewEnabled ? (React.createElement(PropertyTree, { value: val ?? value, rootObject: object, setRootObject: setObject, fetchObject: fetchObject, propertyTreeMap: context.propertyTreeMap ?? {}, handleTreePropertySelect: handleOnChange })) : (React.createElement(Autocomplete, { options: opts, value: val ?? null, getOptionLabel: (option) => {
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 [treeViewObject, setTreeViewObject] = useState();
322
- useEffect(() => {
323
- let obj;
324
- if (treeViewOpts?.object) {
325
- obj = {
326
- ...treeViewOpts.object,
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
- for (const id of ids) {
383
- await traversePath(id, newTreeViewObject.properties, fetchObject);
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
- setTreeViewObject((prevTreeViewObject) => prevTreeViewObject ? insertChangesOnly(prevTreeViewObject, newTreeViewObject) : newTreeViewObject);
386
- };
387
- updateTreeViewObject(criteria || originalCriteria || {}, obj, treeViewOpts.fetchObject);
388
- }
389
- }, [treeViewOpts, criteria, originalCriteria]);
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 (!treeViewObject)
392
- return;
393
- const getNamePath = (path, object) => {
394
- const helper = (path, object) => {
395
- let namePath = '';
396
- const prop = (object.properties ?? []).find((prop) => path.startsWith(`${prop.id}.`) || path === prop.id);
397
- if (prop) {
398
- if (prop.children) {
399
- const result = helper(path, { id: prop.id, name: prop.name, properties: prop.children });
400
- namePath = result ? prop.name + ' / ' + result : '';
401
- }
402
- else {
403
- namePath = prop.name;
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
- return namePath;
333
+ newPropertyTreeMap = (await Promise.all(newPropertyTreeMapPromises)).reduce((acc, currentProperty) => ({ ...acc, ...currentProperty }), {});
334
+ setPropertyTreeMap((prevPropertyTreeMap) => ({
335
+ ...prevPropertyTreeMap,
336
+ ...newPropertyTreeMap,
337
+ }));
407
338
  };
408
- return helper(path, object) || path;
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
- setPropertyTreeMap(result);
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, properties),
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, properties),
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: treeViewObject && treeViewOpts
528
+ treeViewOpts: treeViewOpts
605
529
  ? {
606
530
  ...treeViewOpts,
607
- object: treeViewObject,
608
- setObject: setTreeViewObject,
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, waitFor } from '@testing-library/react';
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 { beforeEach, describe, it, vi } from 'vitest';
4
+ import { expect, it } from 'vitest';
5
5
  import CriteriaBuilder from './CriteriaBuilder';
6
- describe('CriteriaBuilderWithTreeView', () => {
7
- // Mock function for setCriteria
8
- const setCriteriaMock = vi.fn();
9
- beforeEach(() => {
10
- // Reset the mock before each test
11
- setCriteriaMock.mockReset();
12
- });
13
- it('expands related object property', async () => {
14
- const user = userEvent.setup();
15
- render(React.createElement(CriteriaBuilder, { treeViewOpts: {
16
- object: {
17
- id: 'objectA',
18
- name: 'Object A',
19
- properties: [
20
- {
21
- id: 'relatedObjectProp',
22
- name: 'Related Object Prop',
23
- type: 'object',
24
- objectId: 'objectB',
25
- children: [
26
- {
27
- id: 'relatedObjectProp-loading',
28
- name: 'Loading...',
29
- type: 'loading',
30
- },
31
- ],
32
- },
33
- ],
34
- },
35
- fetchObject: async (id) => {
36
- if (id === 'objectB') {
37
- // add delay to simulate network request
38
- return new Promise((resolve) => setTimeout(() => {
39
- resolve({
40
- id: 'objectB',
41
- name: 'Object B',
42
- properties: [
43
- {
44
- id: 'id',
45
- name: 'ID',
46
- type: 'string',
47
- },
48
- {
49
- id: 'name',
50
- name: 'Name',
51
- type: 'string',
52
- },
53
- ],
54
- });
55
- }, 5000));
56
- }
57
- return undefined;
58
- },
59
- }, properties: [], criteria: {}, setCriteria: setCriteriaMock }));
60
- // Step 1: Click "Add Condition" button to create a new rule
61
- const addConditionButton = screen.getByRole('button', { name: /^add condition$/i });
62
- await user.click(addConditionButton);
63
- // Step 2: Click on the property selector to open the dropdown
64
- const propertySelector = screen.getByPlaceholderText(/select a property/i);
65
- await user.click(propertySelector);
66
- // Step 3: Click on "Related Object Prop" to expand it and fetch its children
67
- const relatedObjectPropItem = screen.getByText('Related Object Prop');
68
- await user.click(relatedObjectPropItem);
69
- // Step 4: Verify loading indicator is shown
70
- expect(screen.getByText('Loading...')).toBeInTheDocument();
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 { TreeViewObject } from './types';
3
+ import { EvokeObject } from '../../../types';
3
4
  type PropertyTreeProps = {
4
- fetchObject: (id: string) => Promise<TreeViewObject | undefined>;
5
- rootObject: TreeViewObject;
6
- setRootObject: React.Dispatch<React.SetStateAction<TreeViewObject>>;
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, 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 { convertTreeViewPropertyToTreeItem, findTreeItemById, truncateNamePath, updateTreeViewProperty } from './utils';
7
+ import { findTreeItemById, truncateNamePath, updateTreeNode } from './utils';
8
8
  const NAME_PATH_LIMIT = 35;
9
9
  const PropertyTree = (props) => {
10
- const { fetchObject, handleTreePropertySelect, rootObject, setRootObject, value, propertyTreeMap } = props;
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(convertTreeViewPropertyToTreeItem));
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
- setRootObject((prevObject) => ({
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] ?? '') || option
49
- : propertyTreeMap[option.value] || 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
  } }))));