@evoke-platform/ui-components 1.10.0-dev.33 → 1.10.0-dev.34
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 +24 -2
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +45 -0
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +66 -83
- package/dist/published/components/custom/FormV2/components/DefaultValues.d.ts +2 -2
- package/dist/published/components/custom/FormV2/components/DefaultValues.js +36 -28
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +13 -13
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +2 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +5 -4
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +1 -1
- package/dist/published/components/custom/FormV2/components/types.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/utils.js +10 -4
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +127 -1
- package/dist/published/stories/CriteriaBuilder.stories.js +6 -0
- package/package.json +1 -1
|
@@ -400,9 +400,31 @@ const CriteriaBuilder = (props) => {
|
|
|
400
400
|
const fields = useMemo(() => {
|
|
401
401
|
return properties
|
|
402
402
|
.filter(({ type }) => type !== 'collection')
|
|
403
|
-
.
|
|
403
|
+
.flatMap((property) => {
|
|
404
|
+
if (property.type === 'object') {
|
|
405
|
+
const result = [
|
|
406
|
+
{
|
|
407
|
+
name: `${property.id}.id`,
|
|
408
|
+
label: `${property.name} ID`,
|
|
409
|
+
inputType: property.type,
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: `${property.id}.name`,
|
|
413
|
+
label: `${property.name} Name`,
|
|
414
|
+
inputType: property.type,
|
|
415
|
+
},
|
|
416
|
+
];
|
|
417
|
+
if (!property.objectId) {
|
|
418
|
+
result.push({
|
|
419
|
+
name: `${property.id}.objectId`,
|
|
420
|
+
label: `${property.name} Object ID`,
|
|
421
|
+
inputType: property.type,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
404
426
|
return {
|
|
405
|
-
name: property.
|
|
427
|
+
name: property.id,
|
|
406
428
|
label: property.name,
|
|
407
429
|
inputType: property.type,
|
|
408
430
|
...(property.enum && {
|
|
@@ -56,6 +56,17 @@ const mockProperties = [
|
|
|
56
56
|
name: 'Boolean',
|
|
57
57
|
type: 'boolean',
|
|
58
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
|
+
},
|
|
59
70
|
];
|
|
60
71
|
describe('CriteriaBuilder', () => {
|
|
61
72
|
// Mock function for setCriteria
|
|
@@ -64,6 +75,40 @@ describe('CriteriaBuilder', () => {
|
|
|
64
75
|
// Reset the mock before each test
|
|
65
76
|
setCriteriaMock.mockReset();
|
|
66
77
|
});
|
|
78
|
+
describe('when passed regular related object fields', () => {
|
|
79
|
+
it('should render the field ID', () => {
|
|
80
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
81
|
+
'regularRelatedObject.id': 'relatedInstanceId',
|
|
82
|
+
}, setCriteria: setCriteriaMock }));
|
|
83
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Regular Related Object ID');
|
|
84
|
+
});
|
|
85
|
+
it('should render the field Name', () => {
|
|
86
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
87
|
+
'regularRelatedObject.name': 'relatedInstanceName',
|
|
88
|
+
}, setCriteria: setCriteriaMock }));
|
|
89
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Regular Related Object Name');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('when passed dynamic related object fields', () => {
|
|
93
|
+
it('should render the field ID', () => {
|
|
94
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
95
|
+
'dynamicRelatedObject.id': 'relatedInstanceId',
|
|
96
|
+
}, setCriteria: setCriteriaMock }));
|
|
97
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Dynamic Related Object ID');
|
|
98
|
+
});
|
|
99
|
+
it('should render the field Name', () => {
|
|
100
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
101
|
+
'dynamicRelatedObject.name': 'relatedInstanceName',
|
|
102
|
+
}, setCriteria: setCriteriaMock }));
|
|
103
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Dynamic Related Object Name');
|
|
104
|
+
});
|
|
105
|
+
it('should render the field Object ID', () => {
|
|
106
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
107
|
+
'dynamicRelatedObject.objectId': 'relatedInstanceObjectId',
|
|
108
|
+
}, setCriteria: setCriteriaMock }));
|
|
109
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Dynamic Related Object Object ID');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
67
112
|
describe('when passed single-select fields', () => {
|
|
68
113
|
it('should render the field name', () => {
|
|
69
114
|
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useApiServices, useApp, useAuthenticationContext, useNavigate, useObject, } from '@evoke-platform/context';
|
|
2
2
|
import axios from 'axios';
|
|
3
|
-
import { cloneDeep, get, isArray, isEmpty, isEqual,
|
|
3
|
+
import { cloneDeep, get, isArray, isEmpty, isEqual, omit, pick, set, uniq } from 'lodash';
|
|
4
4
|
import React, { useEffect, useRef, useState } from 'react';
|
|
5
5
|
import { Skeleton, Snackbar } from '../../core';
|
|
6
6
|
import { Box } from '../../layout';
|
|
@@ -278,100 +278,83 @@ function FormRendererContainer(props) {
|
|
|
278
278
|
};
|
|
279
279
|
const getDefaultValues = async (entries, instanceData) => {
|
|
280
280
|
const result = {};
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
281
|
+
const unnestedEntries = getUnnestedEntries(entries);
|
|
282
|
+
for (const entry of unnestedEntries) {
|
|
283
|
+
if ((entry.type === 'input' || entry.type === 'inputField') &&
|
|
284
|
+
isAddressProperty(entry.parameterId || entry.input?.id)) {
|
|
285
|
+
const fieldId = getEntryId(entry);
|
|
286
|
+
if (!fieldId)
|
|
287
|
+
continue;
|
|
288
|
+
const fieldValue = get(instanceData, fieldId);
|
|
289
|
+
if ((isEmpty(instanceData) || fieldValue === undefined || fieldValue === null || fieldValue === '') &&
|
|
290
|
+
entry?.display?.defaultValue &&
|
|
291
|
+
parameters) {
|
|
292
|
+
const defaultValuesArray = await evalDefaultVals(parameters, unnestedEntries, entry, fieldValue, fieldId, apiServices, userAccount, instanceData);
|
|
293
|
+
if (isArray(defaultValuesArray)) {
|
|
294
|
+
defaultValuesArray.forEach(({ fieldId, fieldValue }) => {
|
|
295
|
+
set(result, fieldId, fieldValue);
|
|
296
|
+
});
|
|
292
297
|
}
|
|
293
298
|
}
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
const fieldId = getEntryId(entry);
|
|
297
|
-
if (!fieldId)
|
|
298
|
-
return;
|
|
299
|
-
const fieldValue = get(instanceData, fieldId);
|
|
300
|
-
if ((isEmpty(instanceData) ||
|
|
301
|
-
fieldValue === undefined ||
|
|
302
|
-
fieldValue === null ||
|
|
303
|
-
fieldValue === '') &&
|
|
304
|
-
entry?.display?.defaultValue &&
|
|
305
|
-
parameters) {
|
|
306
|
-
const defaultValuesArray = await evalDefaultVals(parameters, entry, fieldValue, fieldId, apiServices, userAccount, instanceData);
|
|
307
|
-
if (isArray(defaultValuesArray)) {
|
|
308
|
-
defaultValuesArray.forEach(({ fieldId, fieldValue }) => {
|
|
309
|
-
set(result, fieldId, fieldValue);
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
else if (fieldValue !== undefined && fieldValue !== null) {
|
|
314
|
-
set(result, fieldId, fieldValue);
|
|
315
|
-
}
|
|
299
|
+
else if (fieldValue !== undefined && fieldValue !== null) {
|
|
300
|
+
set(result, fieldId, fieldValue);
|
|
316
301
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
catch (error) {
|
|
336
|
-
console.error(error);
|
|
337
|
-
}
|
|
302
|
+
}
|
|
303
|
+
else if (entry.type !== 'sections' && entry.type !== 'columns' && entry.type !== 'content') {
|
|
304
|
+
const fieldId = entry.type === 'input'
|
|
305
|
+
? entry.parameterId
|
|
306
|
+
: entry.type === 'inputField'
|
|
307
|
+
? entry.input?.id
|
|
308
|
+
: undefined;
|
|
309
|
+
if (fieldId) {
|
|
310
|
+
const fieldValue = instanceData?.[fieldId] ??
|
|
311
|
+
instanceData?.metadata?.[fieldId];
|
|
312
|
+
const parameter = parameters?.find((param) => param.id === fieldId);
|
|
313
|
+
if (associatedObject?.propertyId === fieldId &&
|
|
314
|
+
associatedObject?.instanceId &&
|
|
315
|
+
parameter &&
|
|
316
|
+
action?.type === 'create') {
|
|
317
|
+
try {
|
|
318
|
+
const instance = await apiServices.get(getPrefixedUrl(`/objects/${parameter.objectId}/instances/${associatedObject.instanceId}`));
|
|
319
|
+
result[associatedObject.propertyId] = instance;
|
|
338
320
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
321
|
+
catch (error) {
|
|
322
|
+
console.error(error);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
else if (entry.type !== 'readonlyField' && isEmptyWithDefault(fieldValue, entry, instanceData)) {
|
|
326
|
+
if (fieldId && parameters && parameters.length > 0) {
|
|
327
|
+
const defaultValuesArray = await evalDefaultVals(parameters, unnestedEntries, entry, fieldValue, fieldId, apiServices, userAccount, instanceData);
|
|
328
|
+
for (const { fieldId, fieldValue } of defaultValuesArray) {
|
|
329
|
+
const parameter = parameters?.find((param) => param.id === fieldId);
|
|
330
|
+
if (parameter?.type === 'object') {
|
|
331
|
+
const dependentFields = await processValueUpdate(unnestedEntries, parameters, fieldValue, apiServices, fieldId, formDataRef.current, userAccount);
|
|
332
|
+
for (const field of dependentFields) {
|
|
333
|
+
set(result, field.fieldId, field.fieldValue);
|
|
350
334
|
}
|
|
351
|
-
set(result, fieldId, fieldValue);
|
|
352
335
|
}
|
|
336
|
+
set(result, fieldId, fieldValue);
|
|
353
337
|
}
|
|
354
338
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
result[fieldId] = RTFFieldValue;
|
|
365
|
-
}
|
|
366
|
-
else {
|
|
367
|
-
result[fieldId] = fieldValue;
|
|
339
|
+
}
|
|
340
|
+
else if (parameter?.type === 'boolean' && (fieldValue === undefined || fieldValue === null)) {
|
|
341
|
+
result[fieldId] = false;
|
|
342
|
+
}
|
|
343
|
+
else if (fieldValue !== undefined && fieldValue !== null) {
|
|
344
|
+
if (parameter?.type === 'richText' && typeof fieldValue === 'string') {
|
|
345
|
+
let RTFFieldValue = fieldValue;
|
|
346
|
+
if (!fieldValue.trim().startsWith('{\\rtf')) {
|
|
347
|
+
RTFFieldValue = plainTextToRtf(fieldValue);
|
|
368
348
|
}
|
|
349
|
+
result[fieldId] = RTFFieldValue;
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
result[fieldId] = fieldValue;
|
|
369
353
|
}
|
|
370
354
|
}
|
|
371
355
|
}
|
|
372
356
|
}
|
|
373
|
-
}
|
|
374
|
-
await processEntries(entries);
|
|
357
|
+
}
|
|
375
358
|
return result;
|
|
376
359
|
};
|
|
377
360
|
const handleAutosave = async (fieldId) => {
|
|
@@ -423,7 +406,7 @@ function FormRendererContainer(props) {
|
|
|
423
406
|
if (parameter) {
|
|
424
407
|
if (parameter.type === 'object' && parameters && parameters.length > 0) {
|
|
425
408
|
// On change of a related object, update default values dependent on that object
|
|
426
|
-
const dependentFields = await processValueUpdate(
|
|
409
|
+
const dependentFields = await processValueUpdate(entries, parameters, value, apiServices, id, formDataRef.current, userAccount);
|
|
427
410
|
for (const field of dependentFields) {
|
|
428
411
|
onChange(field.fieldId, field.fieldValue);
|
|
429
412
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ApiServices, FormEntry, InputField, InputParameter, InputParameterReference, ObjectInstance, Reference, UserAccount } from '@evoke-platform/context';
|
|
2
2
|
import { FieldValues } from 'react-hook-form';
|
|
3
|
-
export declare function evalDefaultVals(parameters: InputParameter[], entry: InputParameterReference | InputField, fieldValue: unknown, fieldId: string, apiServices: ApiServices, userAccount?: UserAccount, formValues?: FieldValues, updatedRelatedObjectValue?: ObjectInstance | null | Reference): Promise<{
|
|
3
|
+
export declare function evalDefaultVals(parameters: InputParameter[], unnestedEntries: FormEntry[], entry: InputParameterReference | InputField, fieldValue: unknown, fieldId: string, apiServices: ApiServices, userAccount?: UserAccount, formValues?: FieldValues, updatedRelatedObjectValue?: ObjectInstance | null | Reference): Promise<{
|
|
4
4
|
fieldId: string;
|
|
5
5
|
fieldValue: unknown;
|
|
6
6
|
}[]>;
|
|
7
|
-
export declare function processValueUpdate(
|
|
7
|
+
export declare function processValueUpdate(unnestedEntries: FormEntry[], parameters: InputParameter[], updatedRelatedObjectValue: ObjectInstance | null | Reference, apiServices: ApiServices, changedEntryId?: string, formValues?: FieldValues, userAccount?: UserAccount): Promise<{
|
|
8
8
|
fieldId: string;
|
|
9
9
|
fieldValue: unknown;
|
|
10
10
|
}[]>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isArray, isEmpty, uniq } from 'lodash';
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
3
|
import { getEntryId, getPrefixedUrl, isAddressProperty } from './utils';
|
|
4
|
-
export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, apiServices, userAccount, formValues, updatedRelatedObjectValue) {
|
|
4
|
+
export async function evalDefaultVals(parameters, unnestedEntries, entry, fieldValue, fieldId, apiServices, userAccount, formValues, updatedRelatedObjectValue) {
|
|
5
5
|
const updates = [];
|
|
6
6
|
const parameter = parameters.find((param) => param.id === fieldId);
|
|
7
7
|
const defaultValue = entry.display?.defaultValue;
|
|
@@ -15,9 +15,9 @@ export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, ap
|
|
|
15
15
|
const groups = regex.exec(item)?.groups;
|
|
16
16
|
if (groups?.relatedObjectProperty && groups?.nestedProperty) {
|
|
17
17
|
const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
|
|
18
|
-
let
|
|
19
|
-
if (!
|
|
20
|
-
|
|
18
|
+
let relatedObjectInstance = updatedRelatedObjectValue;
|
|
19
|
+
if (!relatedObjectInstance && !isEmpty(formValues)) {
|
|
20
|
+
relatedObjectInstance = formValues[groups.relatedObjectProperty];
|
|
21
21
|
}
|
|
22
22
|
if (updatedRelatedObjectValue?.[groups.nestedProperty]) {
|
|
23
23
|
fieldValue = uniq([
|
|
@@ -28,9 +28,14 @@ export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, ap
|
|
|
28
28
|
]);
|
|
29
29
|
updates.push({ fieldId, fieldValue });
|
|
30
30
|
}
|
|
31
|
-
else if (
|
|
31
|
+
else if (relatedObjectInstance?.id && relatedObjectParameter) {
|
|
32
|
+
let relatedObjectId = relatedObjectParameter.objectId;
|
|
33
|
+
if (!relatedObjectId) {
|
|
34
|
+
const relatedObjectParamEntry = unnestedEntries.find((e) => getEntryId(e) === relatedObjectParameter.id);
|
|
35
|
+
relatedObjectId = relatedObjectParamEntry?.display?.relatedObjectId;
|
|
36
|
+
}
|
|
32
37
|
const instance = await new Promise((resolve) => {
|
|
33
|
-
apiServices.get(getPrefixedUrl(`/objects/${
|
|
38
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${relatedObjectInstance?.id}`), (error, instance) => {
|
|
34
39
|
if (error) {
|
|
35
40
|
console.error(error);
|
|
36
41
|
return resolve(undefined);
|
|
@@ -64,17 +69,22 @@ export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, ap
|
|
|
64
69
|
const groups = regex.exec(defaultValue)?.groups;
|
|
65
70
|
if (groups?.relatedObjectProperty && groups?.addressProperty && groups?.nestedAddressProperty) {
|
|
66
71
|
const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
|
|
67
|
-
let
|
|
68
|
-
if (!
|
|
69
|
-
|
|
72
|
+
let relatedObjectInstance = updatedRelatedObjectValue;
|
|
73
|
+
if (!relatedObjectInstance && !isEmpty(formValues)) {
|
|
74
|
+
relatedObjectInstance = formValues[groups.relatedObjectProperty];
|
|
70
75
|
}
|
|
71
76
|
if (updatedRelatedObjectValue?.[groups.addressProperty]?.[groups.nestedAddressProperty]) {
|
|
72
77
|
fieldValue = updatedRelatedObjectValue?.[groups.addressProperty]?.[groups.nestedAddressProperty];
|
|
73
78
|
updates.push({ fieldId, fieldValue });
|
|
74
79
|
}
|
|
75
|
-
else if (
|
|
80
|
+
else if (relatedObjectInstance?.id && relatedObjectParameter) {
|
|
81
|
+
let relatedObjectId = relatedObjectParameter.objectId;
|
|
82
|
+
if (!relatedObjectId) {
|
|
83
|
+
const relatedObjectParamEntry = unnestedEntries.find((e) => getEntryId(e) === relatedObjectParameter.id);
|
|
84
|
+
relatedObjectId = relatedObjectParamEntry?.display?.relatedObjectId;
|
|
85
|
+
}
|
|
76
86
|
const instance = await new Promise((resolve) => {
|
|
77
|
-
apiServices.get(getPrefixedUrl(`/objects/${
|
|
87
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${relatedObjectInstance?.id}`), (error, instance) => {
|
|
78
88
|
if (error) {
|
|
79
89
|
console.error(error);
|
|
80
90
|
return resolve(undefined);
|
|
@@ -99,17 +109,22 @@ export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, ap
|
|
|
99
109
|
const groups = regex.exec(defaultValue)?.groups;
|
|
100
110
|
if (groups?.relatedObjectProperty && groups?.nestedProperty) {
|
|
101
111
|
const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
|
|
102
|
-
let
|
|
103
|
-
if (!
|
|
104
|
-
|
|
112
|
+
let relatedObjectInstance = updatedRelatedObjectValue;
|
|
113
|
+
if (!relatedObjectInstance && !isEmpty(formValues)) {
|
|
114
|
+
relatedObjectInstance = formValues[groups.relatedObjectProperty];
|
|
105
115
|
}
|
|
106
116
|
if (updatedRelatedObjectValue?.[groups.nestedProperty]) {
|
|
107
117
|
fieldValue = updatedRelatedObjectValue[groups.nestedProperty];
|
|
108
118
|
updates.push({ fieldId, fieldValue });
|
|
109
119
|
}
|
|
110
|
-
else if (
|
|
120
|
+
else if (relatedObjectInstance?.id && relatedObjectParameter) {
|
|
121
|
+
let relatedObjectId = relatedObjectParameter.objectId;
|
|
122
|
+
if (!relatedObjectId) {
|
|
123
|
+
const relatedObjectParamEntry = unnestedEntries.find((e) => getEntryId(e) === relatedObjectParameter.id);
|
|
124
|
+
relatedObjectId = relatedObjectParamEntry?.display?.relatedObjectId;
|
|
125
|
+
}
|
|
111
126
|
const instance = await new Promise((resolve) => {
|
|
112
|
-
apiServices.get(getPrefixedUrl(`/objects/${
|
|
127
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${relatedObjectInstance?.id}`), (error, instance) => {
|
|
113
128
|
if (error) {
|
|
114
129
|
console.error(error);
|
|
115
130
|
return resolve(undefined);
|
|
@@ -153,16 +168,9 @@ export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, ap
|
|
|
153
168
|
}
|
|
154
169
|
return updates;
|
|
155
170
|
}
|
|
156
|
-
export async function processValueUpdate(
|
|
171
|
+
export async function processValueUpdate(unnestedEntries, parameters, updatedRelatedObjectValue, apiServices, changedEntryId, formValues, userAccount) {
|
|
157
172
|
const updates = [];
|
|
158
|
-
for (const entry of
|
|
159
|
-
if (entry.type === 'sections' || entry.type === 'columns') {
|
|
160
|
-
const subEntries = entry.type === 'sections' ? entry.sections : entry.columns;
|
|
161
|
-
for (const subEntry of subEntries) {
|
|
162
|
-
const subUpdates = await processValueUpdate(subEntry.entries, parameters, updatedRelatedObjectValue, apiServices, changedEntryId, formValues, userAccount);
|
|
163
|
-
updates.push(...subUpdates);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
173
|
+
for (const entry of unnestedEntries) {
|
|
166
174
|
if ((entry.type === 'input' || entry.type === 'inputField') && entry?.display?.defaultValue) {
|
|
167
175
|
const parameterId = getEntryId(entry);
|
|
168
176
|
if (!parameterId)
|
|
@@ -175,7 +183,7 @@ export async function processValueUpdate(entries, parameters, updatedRelatedObje
|
|
|
175
183
|
groups?.addressProperty &&
|
|
176
184
|
groups?.nestedAddressProperty &&
|
|
177
185
|
changedEntryId === groups.relatedObjectProperty) {
|
|
178
|
-
const result = await evalDefaultVals(parameters, entry, formValues?.[addressObject]?.[addressField], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
|
|
186
|
+
const result = await evalDefaultVals(parameters, unnestedEntries, entry, formValues?.[addressObject]?.[addressField], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
|
|
179
187
|
updates.push(...result);
|
|
180
188
|
}
|
|
181
189
|
}
|
|
@@ -187,7 +195,7 @@ export async function processValueUpdate(entries, parameters, updatedRelatedObje
|
|
|
187
195
|
if (groups?.relatedObjectProperty &&
|
|
188
196
|
groups?.nestedProperty &&
|
|
189
197
|
changedEntryId === groups.relatedObjectProperty) {
|
|
190
|
-
const result = await evalDefaultVals(parameters, entry, entry.display.defaultValue, parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
|
|
198
|
+
const result = await evalDefaultVals(parameters, unnestedEntries, entry, entry.display.defaultValue, parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
|
|
191
199
|
updates.push(...result);
|
|
192
200
|
}
|
|
193
201
|
}
|
|
@@ -198,7 +206,7 @@ export async function processValueUpdate(entries, parameters, updatedRelatedObje
|
|
|
198
206
|
if (groups?.relatedObjectProperty &&
|
|
199
207
|
groups?.nestedProperty &&
|
|
200
208
|
changedEntryId === groups.relatedObjectProperty) {
|
|
201
|
-
const result = await evalDefaultVals(parameters, entry, formValues?.[parameterId], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
|
|
209
|
+
const result = await evalDefaultVals(parameters, unnestedEntries, entry, formValues?.[parameterId], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
|
|
202
210
|
updates.push(...result);
|
|
203
211
|
}
|
|
204
212
|
}
|
|
@@ -10,7 +10,7 @@ import { Box } from '../../../../../layout';
|
|
|
10
10
|
import { getDefaultPages, getPrefixedUrl, transformToWhere } from '../../utils';
|
|
11
11
|
import RelatedObjectInstance from './RelatedObjectInstance';
|
|
12
12
|
const ObjectPropertyInput = (props) => {
|
|
13
|
-
const { id, fieldDefinition, readOnly, error, mode, displayOption, filter, defaultValueCriteria, sortBy, orderBy, isModal, initialValue, viewLayout, hasDescription, createActionId, formId, } = props;
|
|
13
|
+
const { id, fieldDefinition, readOnly, error, mode, displayOption, filter, defaultValueCriteria, sortBy, orderBy, isModal, initialValue, viewLayout, hasDescription, createActionId, formId, relatedObjectId, } = props;
|
|
14
14
|
const { fetchedOptions, setFetchedOptions, parameters, fieldHeight, handleChange: handleChangeObjectField, onAutosave: onAutosave, instance, } = useFormContext();
|
|
15
15
|
const { defaultPages, findDefaultPageSlugFor } = useApp();
|
|
16
16
|
const [selectedInstance, setSelectedInstance] = useState(initialValue || undefined);
|
|
@@ -88,7 +88,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
88
88
|
});
|
|
89
89
|
if (updatedFilter.where) {
|
|
90
90
|
setLoadingOptions(true);
|
|
91
|
-
apiServices.get(getPrefixedUrl(`/objects/${
|
|
91
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances?filter=${encodeURIComponent(JSON.stringify(updatedFilter))}`), async (error, instances) => {
|
|
92
92
|
if (error) {
|
|
93
93
|
console.error(error);
|
|
94
94
|
setLoadingOptions(false);
|
|
@@ -114,7 +114,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
-
}, [
|
|
117
|
+
}, [relatedObjectId, defaultValueCriteria, sortBy, orderBy]);
|
|
118
118
|
const getDropdownOptions = useCallback(() => {
|
|
119
119
|
if (((!fetchedOptions?.[`${id}Options`] ||
|
|
120
120
|
(fetchedOptions?.[`${id}Options`]).length === 0) &&
|
|
@@ -129,7 +129,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
129
129
|
direction: 'asc',
|
|
130
130
|
};
|
|
131
131
|
updatedFilter.order = `${propertyId} ${direction}`;
|
|
132
|
-
apiServices.get(getPrefixedUrl(`/objects/${
|
|
132
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances?filter=${JSON.stringify(updatedFilter)}`), (error, instances) => {
|
|
133
133
|
if (error) {
|
|
134
134
|
console.error(error);
|
|
135
135
|
setLoadingOptions(false);
|
|
@@ -143,7 +143,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
143
143
|
});
|
|
144
144
|
}
|
|
145
145
|
}, [
|
|
146
|
-
|
|
146
|
+
relatedObjectId,
|
|
147
147
|
updatedCriteria,
|
|
148
148
|
layout,
|
|
149
149
|
fetchedOptions?.[`${id}Options`],
|
|
@@ -183,10 +183,10 @@ const ObjectPropertyInput = (props) => {
|
|
|
183
183
|
}
|
|
184
184
|
};
|
|
185
185
|
fetchForm();
|
|
186
|
-
}, [action, formId, id,
|
|
186
|
+
}, [action, formId, id, apiServices, fetchedOptions]);
|
|
187
187
|
useEffect(() => {
|
|
188
188
|
if (!fetchedOptions[`${id}RelatedObject`]) {
|
|
189
|
-
apiServices.get(getPrefixedUrl(`/objects/${
|
|
189
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/effective?sanitizedVersion=true`), (error, object) => {
|
|
190
190
|
if (error) {
|
|
191
191
|
console.error(error);
|
|
192
192
|
}
|
|
@@ -195,15 +195,15 @@ const ObjectPropertyInput = (props) => {
|
|
|
195
195
|
}
|
|
196
196
|
});
|
|
197
197
|
}
|
|
198
|
-
}, [
|
|
198
|
+
}, [relatedObjectId, fetchedOptions, id]);
|
|
199
199
|
useEffect(() => {
|
|
200
200
|
(async () => {
|
|
201
201
|
if (parameters && fetchedOptions[`${id}NavigationSlug`] === undefined) {
|
|
202
202
|
const pages = await getDefaultPages(parameters, defaultPages, findDefaultPageSlugFor);
|
|
203
|
-
if (
|
|
204
|
-
setNavigationSlug(pages[
|
|
203
|
+
if (relatedObjectId && pages[relatedObjectId]) {
|
|
204
|
+
setNavigationSlug(pages[relatedObjectId]);
|
|
205
205
|
setFetchedOptions({
|
|
206
|
-
[`${id}NavigationSlug`]: pages[
|
|
206
|
+
[`${id}NavigationSlug`]: pages[relatedObjectId],
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
209
|
else {
|
|
@@ -214,7 +214,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
})();
|
|
217
|
-
}, [parameters, defaultPages, findDefaultPageSlugFor,
|
|
217
|
+
}, [parameters, defaultPages, findDefaultPageSlugFor, relatedObjectId, fetchedOptions]);
|
|
218
218
|
const handleClose = () => {
|
|
219
219
|
setOpenCreateDialog(false);
|
|
220
220
|
};
|
|
@@ -522,7 +522,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
522
522
|
event.stopPropagation();
|
|
523
523
|
setOpenCreateDialog(true);
|
|
524
524
|
}, "aria-label": `Add` }, "Add")))),
|
|
525
|
-
React.createElement(RelatedObjectInstance, { open: openCreateDialog, title: form?.name ?? `Add ${fieldDefinition.name}`, handleClose: handleClose, setSelectedInstance: setSelectedInstance, relatedObject: relatedObject, id: id, mode: mode, displayOption: displayOption, setOptions: setOptions, options: options, filter: updatedCriteria, layout: layout, formId: formId ?? form?.id, actionId: createActionId, setSnackbarError: setSnackbarError
|
|
525
|
+
React.createElement(RelatedObjectInstance, { open: openCreateDialog, title: form?.name ?? `Add ${fieldDefinition.name}`, handleClose: handleClose, setSelectedInstance: setSelectedInstance, relatedObject: relatedObject, id: id, mode: mode, displayOption: displayOption, setOptions: setOptions, options: options, filter: updatedCriteria, layout: layout, formId: formId ?? form?.id, actionId: createActionId, setSnackbarError: setSnackbarError }),
|
|
526
526
|
React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({
|
|
527
527
|
isError: snackbarError.isError,
|
|
528
528
|
showAlert: false,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Obj, ObjectInstance, TableViewLayout } from '@evoke-platform/context';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { BaseProps } from '../../types';
|
|
4
4
|
export type RelatedObjectInstanceProps = BaseProps & {
|
|
5
5
|
id: string;
|
|
6
6
|
open: boolean;
|
|
7
7
|
title: string;
|
|
8
|
-
relatedObject
|
|
8
|
+
relatedObject?: Obj;
|
|
9
9
|
setSelectedInstance: (selectedInstance: ObjectInstance) => void;
|
|
10
10
|
handleClose: () => void;
|
|
11
11
|
mode: 'default' | 'existingOnly' | 'newOnly';
|
|
@@ -21,7 +21,6 @@ export type RelatedObjectInstanceProps = BaseProps & {
|
|
|
21
21
|
layout?: TableViewLayout;
|
|
22
22
|
formId?: string;
|
|
23
23
|
actionId?: string;
|
|
24
|
-
fieldDefinition: InputParameter;
|
|
25
24
|
};
|
|
26
25
|
declare const RelatedObjectInstance: (props: RelatedObjectInstanceProps) => React.JSX.Element;
|
|
27
26
|
export default RelatedObjectInstance;
|
|
@@ -6,6 +6,7 @@ import { Close } from '../../../../../../icons';
|
|
|
6
6
|
import useWidgetSize, { useFormContext } from '../../../../../../theme/hooks';
|
|
7
7
|
import { Button, Dialog, DialogContent, DialogTitle, FormControlLabel, IconButton, Radio, RadioGroup, } from '../../../../../core';
|
|
8
8
|
import Box from '../../../../../layout/Box/Box';
|
|
9
|
+
import ErrorComponent from '../../../../ErrorComponent';
|
|
9
10
|
import FormRenderer from '../../../FormRenderer';
|
|
10
11
|
import FormRendererContainer from '../../../FormRendererContainer';
|
|
11
12
|
import Body from '../../Body';
|
|
@@ -28,7 +29,7 @@ const styles = {
|
|
|
28
29
|
},
|
|
29
30
|
};
|
|
30
31
|
const RelatedObjectInstance = (props) => {
|
|
31
|
-
const { relatedObject, open, title, id, setSelectedInstance, handleClose, mode, displayOption, filter, layout, formId, actionId,
|
|
32
|
+
const { relatedObject, open, title, id, setSelectedInstance, handleClose, mode, displayOption, filter, layout, formId, actionId, setSnackbarError, setOptions, options, } = props;
|
|
32
33
|
const { handleChange: handleChangeObjectField, onAutosave, richTextEditor, fieldHeight, width } = useFormContext();
|
|
33
34
|
const [selectedRow, setSelectedRow] = useState();
|
|
34
35
|
const [relationType, setRelationType] = useState(displayOption === 'dropdown' || mode === 'newOnly' ? 'new' : 'existing');
|
|
@@ -120,7 +121,7 @@ const RelatedObjectInstance = (props) => {
|
|
|
120
121
|
}, value: relationType },
|
|
121
122
|
React.createElement(FormControlLabel, { value: "existing", control: React.createElement(Radio, { sx: { '&.Mui-checked': { color: 'primary' } } }), label: "Existing" }),
|
|
122
123
|
React.createElement(FormControlLabel, { value: "new", control: React.createElement(Radio, { sx: { '&.Mui-checked': { color: 'primary' } } }), label: "New" }))) : null;
|
|
123
|
-
const DialogForm = useCallback(() => (React.createElement(FormRendererContainer, { formId: formId, display: { fieldHeight: fieldHeight ?? 'medium' }, actionId: actionId, objectId:
|
|
124
|
+
const DialogForm = useCallback(() => (React.createElement(FormRendererContainer, { formId: formId, display: { fieldHeight: fieldHeight ?? 'medium' }, actionId: actionId, objectId: relatedObject.id, onSubmit: createNewInstance, onDiscardChanges: onClose, onSubmitError: handleSubmitError, richTextEditor: richTextEditor, renderHeader: () => null, renderBody: (bodyProps) => (React.createElement(DialogContent, { sx: styles.dialogContent },
|
|
124
125
|
relationType === 'new' ? (React.createElement("div", { ref: validationErrorsRef }, !isEmpty(bodyProps.errors) && bodyProps.shouldShowValidationErrors ? (React.createElement(FormRenderer.ValidationErrors, { errors: bodyProps.errors, sx: {
|
|
125
126
|
my: isSm || isXs ? 2 : 3,
|
|
126
127
|
} })) : null)) : null,
|
|
@@ -136,7 +137,7 @@ const RelatedObjectInstance = (props) => {
|
|
|
136
137
|
React.createElement(RadioButtons, null),
|
|
137
138
|
defaultContainer)),
|
|
138
139
|
status === 'ready' && defaultContainer));
|
|
139
|
-
}, sx: { border: 'none' } })), [formId, actionId,
|
|
140
|
+
}, sx: { border: 'none' } })), [formId, actionId, relatedObject, fieldHeight, richTextEditor, RadioButtons]);
|
|
140
141
|
return (React.createElement(Dialog, { fullWidth: true, maxWidth: "md", open: open, onClose: (e, reason) => reason !== 'backdropClick' && handleClose(), sx: {
|
|
141
142
|
background: 'none',
|
|
142
143
|
}, PaperProps: {
|
|
@@ -159,7 +160,7 @@ const RelatedObjectInstance = (props) => {
|
|
|
159
160
|
title,
|
|
160
161
|
React.createElement(IconButton, { onClick: onClose, "aria-label": "Close" },
|
|
161
162
|
React.createElement(Close, { fontSize: "small" })))),
|
|
162
|
-
relationType === 'new' ? (React.createElement(DialogForm, null)) : ((mode === 'default' || mode === 'existingOnly') &&
|
|
163
|
+
relationType === 'new' ? (relatedObject ? (React.createElement(DialogForm, null)) : (React.createElement(ErrorComponent, { code: "Misconfigured" }))) : ((mode === 'default' || mode === 'existingOnly') &&
|
|
163
164
|
relatedObject && (React.createElement(React.Fragment, null,
|
|
164
165
|
React.createElement(DialogContent, { sx: styles.dialogContent },
|
|
165
166
|
shouldShowRadioButtons && React.createElement(RadioButtons, null),
|
|
@@ -85,7 +85,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
85
85
|
}
|
|
86
86
|
else if (fieldDefinition.type === 'object') {
|
|
87
87
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
88
|
-
React.createElement(ObjectPropertyInput, { fieldDefinition: fieldDefinition, id: entryId, mode: display?.mode || 'default', error: !!errors?.[entryId], displayOption: display?.relatedObjectDisplay || 'dialogBox', initialValue: fieldValue, readOnly: entry.type === 'readonlyField', filter: validation?.criteria
|
|
88
|
+
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: validation?.criteria
|
|
89
89
|
? updateCriteriaInputs(validation.criteria, getValues(), userAccount)
|
|
90
90
|
: undefined, sortBy: typeof display?.defaultValue === 'object' && 'sortBy' in display.defaultValue
|
|
91
91
|
? display?.defaultValue.sortBy
|
|
@@ -622,14 +622,14 @@ export const deleteDocuments = async (submittedFields, requestSuccess, apiServic
|
|
|
622
622
|
* Returns the cleaned submission ready for submitting.
|
|
623
623
|
*/
|
|
624
624
|
export async function formatSubmission(submission, apiServices, objectId, instanceId, form, setSnackbarError) {
|
|
625
|
+
const allEntries = getUnnestedEntries(form?.entries ?? []) ?? [];
|
|
625
626
|
for (const [key, value] of Object.entries(submission)) {
|
|
627
|
+
const entry = allEntries?.find((entry) => getEntryId(entry) === key);
|
|
626
628
|
if (isArray(value)) {
|
|
627
629
|
// Only upload if array contains File instances (not SavedDocumentReference)
|
|
628
630
|
const fileInArray = value.some((item) => item instanceof File);
|
|
629
631
|
if (fileInArray && instanceId && apiServices && objectId) {
|
|
630
632
|
try {
|
|
631
|
-
const allEntries = getUnnestedEntries(form?.entries ?? []) ?? [];
|
|
632
|
-
const entry = allEntries?.find((entry) => getEntryId(entry) === key);
|
|
633
633
|
const uploadedDocuments = await uploadDocuments(value, {
|
|
634
634
|
type: '',
|
|
635
635
|
view_permission: '',
|
|
@@ -654,10 +654,16 @@ export async function formatSubmission(submission, apiServices, objectId, instan
|
|
|
654
654
|
else if (typeof value === 'object' && value !== null) {
|
|
655
655
|
if (Object.values(value).every((v) => v === undefined)) {
|
|
656
656
|
submission[key] = undefined;
|
|
657
|
-
// only submit the name and id of a related object
|
|
657
|
+
// only submit the name and id of a regular related object
|
|
658
|
+
// and include objectId if it is a dynamic related object
|
|
658
659
|
}
|
|
659
660
|
else if ('id' in value && 'name' in value) {
|
|
660
|
-
submission[key] =
|
|
661
|
+
submission[key] =
|
|
662
|
+
entry &&
|
|
663
|
+
['input', 'inputField'].includes(entry.type) &&
|
|
664
|
+
entry.display?.relatedObjectId
|
|
665
|
+
? pick(value, 'id', 'name', 'objectId')
|
|
666
|
+
: pick(value, 'id', 'name');
|
|
661
667
|
}
|
|
662
668
|
else if (value instanceof LocalDateTime) {
|
|
663
669
|
submission[key] = normalizeDateTime(value);
|
|
@@ -363,7 +363,7 @@ describe('FormRenderer', () => {
|
|
|
363
363
|
});
|
|
364
364
|
});
|
|
365
365
|
});
|
|
366
|
-
describe('when passed a related object entry', () => {
|
|
366
|
+
describe('when passed a regular related object entry', () => {
|
|
367
367
|
const setupTestMocks = (object, form, instances) => {
|
|
368
368
|
server.use(http.get(`/api/data/objects/${object.id}/effective`, () => HttpResponse.json(object)), http.get(`/api/data/forms/${form.id}`, () => HttpResponse.json(form)), http.get(`/api/data/forms?filter={"where":{"actionId":"${form.actionId}","objectId":"${object.id}"}}`, () => HttpResponse.json([form])), http.get(`/api/data/objects/${object.id}/instances`, () => HttpResponse.json(instances || [])));
|
|
369
369
|
};
|
|
@@ -1127,6 +1127,132 @@ describe('FormRenderer', () => {
|
|
|
1127
1127
|
});
|
|
1128
1128
|
});
|
|
1129
1129
|
});
|
|
1130
|
+
describe('when passed a dynamic related object entry', () => {
|
|
1131
|
+
const setupTestMocks = (object, form, instances) => {
|
|
1132
|
+
server.use(http.get(`/api/data/objects/${object.id}/effective`, () => HttpResponse.json(object)), http.get(`/api/data/forms/${form.id}`, () => HttpResponse.json(form)), http.get(`/api/data/forms?filter={"where":{"actionId":"${form.actionId}","objectId":"${object.id}"}}`, () => HttpResponse.json([form])), http.get(`/api/data/objects/${object.id}/instances`, () => HttpResponse.json(instances || [])));
|
|
1133
|
+
};
|
|
1134
|
+
const form = {
|
|
1135
|
+
id: 'relatedObjectTestForm',
|
|
1136
|
+
name: 'Related Object Test Form',
|
|
1137
|
+
entries: [
|
|
1138
|
+
{
|
|
1139
|
+
type: 'input',
|
|
1140
|
+
parameterId: 'specialtyType',
|
|
1141
|
+
display: {
|
|
1142
|
+
label: 'Speciality Type',
|
|
1143
|
+
createActionId: '_create',
|
|
1144
|
+
relatedObjectId: 'specialtyType',
|
|
1145
|
+
},
|
|
1146
|
+
},
|
|
1147
|
+
],
|
|
1148
|
+
actionId: '_update',
|
|
1149
|
+
objectId: 'relatedObjectTestForm',
|
|
1150
|
+
};
|
|
1151
|
+
beforeEach(async () => {
|
|
1152
|
+
const relatedObjectTestFormObject = {
|
|
1153
|
+
id: 'relatedObjectTestForm',
|
|
1154
|
+
name: 'Related Object Test Form',
|
|
1155
|
+
actions: [
|
|
1156
|
+
{
|
|
1157
|
+
id: '_update',
|
|
1158
|
+
name: 'Update',
|
|
1159
|
+
type: 'update',
|
|
1160
|
+
parameters: [
|
|
1161
|
+
{
|
|
1162
|
+
id: 'specialtyType',
|
|
1163
|
+
name: 'Related Object',
|
|
1164
|
+
type: 'object',
|
|
1165
|
+
},
|
|
1166
|
+
],
|
|
1167
|
+
outputEvent: 'updated',
|
|
1168
|
+
},
|
|
1169
|
+
],
|
|
1170
|
+
properties: [
|
|
1171
|
+
{
|
|
1172
|
+
id: 'specialtyType',
|
|
1173
|
+
name: 'Related Object',
|
|
1174
|
+
type: 'object',
|
|
1175
|
+
},
|
|
1176
|
+
],
|
|
1177
|
+
};
|
|
1178
|
+
setupTestMocks(relatedObjectTestFormObject, form);
|
|
1179
|
+
const specialtyTypeForm = {
|
|
1180
|
+
id: 'specialtyTypeForm',
|
|
1181
|
+
name: 'Specialty Type Form',
|
|
1182
|
+
entries: [
|
|
1183
|
+
{
|
|
1184
|
+
type: 'content',
|
|
1185
|
+
html: '<div>Specialty Type Form Content</div>',
|
|
1186
|
+
},
|
|
1187
|
+
{
|
|
1188
|
+
type: 'input',
|
|
1189
|
+
parameterId: 'requiredField',
|
|
1190
|
+
display: {
|
|
1191
|
+
label: 'Required Field',
|
|
1192
|
+
required: true,
|
|
1193
|
+
},
|
|
1194
|
+
},
|
|
1195
|
+
],
|
|
1196
|
+
actionId: '_create',
|
|
1197
|
+
objectId: 'specialtyType',
|
|
1198
|
+
display: {
|
|
1199
|
+
submitLabel: 'Create Specialty Type',
|
|
1200
|
+
},
|
|
1201
|
+
};
|
|
1202
|
+
const specialtyTypeObject = {
|
|
1203
|
+
id: 'specialtyType',
|
|
1204
|
+
name: 'Specialty Type',
|
|
1205
|
+
actions: [
|
|
1206
|
+
{
|
|
1207
|
+
id: '_create',
|
|
1208
|
+
name: 'Create',
|
|
1209
|
+
type: 'create',
|
|
1210
|
+
parameters: [
|
|
1211
|
+
{
|
|
1212
|
+
id: 'requiredField',
|
|
1213
|
+
name: 'Required Field',
|
|
1214
|
+
type: 'string',
|
|
1215
|
+
required: true,
|
|
1216
|
+
},
|
|
1217
|
+
],
|
|
1218
|
+
outputEvent: 'created',
|
|
1219
|
+
defaultFormId: 'specialtyTypeForm',
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
id: '_update',
|
|
1223
|
+
name: 'Update',
|
|
1224
|
+
type: 'update',
|
|
1225
|
+
parameters: [],
|
|
1226
|
+
outputEvent: 'updated',
|
|
1227
|
+
},
|
|
1228
|
+
],
|
|
1229
|
+
properties: [],
|
|
1230
|
+
};
|
|
1231
|
+
setupTestMocks(specialtyTypeObject, specialtyTypeForm);
|
|
1232
|
+
});
|
|
1233
|
+
it('displays form if user switches to creating a new record', async () => {
|
|
1234
|
+
const user = userEvent.setup();
|
|
1235
|
+
render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
|
|
1236
|
+
await user.click(await screen.findByRole('button', { name: 'Add' }));
|
|
1237
|
+
await screen.findByRole('radiogroup', { name: 'Relation Type' });
|
|
1238
|
+
const existingRecordButton = await screen.findByRole('radio', { name: /existing/i });
|
|
1239
|
+
expect(existingRecordButton).toBeChecked();
|
|
1240
|
+
const newRecordButton = await screen.findByRole('radio', { name: /new/i });
|
|
1241
|
+
await user.click(newRecordButton);
|
|
1242
|
+
await screen.findByText('Specialty Type Form Content');
|
|
1243
|
+
});
|
|
1244
|
+
it('displays a not found error in record creation mode if a form could not be found', async () => {
|
|
1245
|
+
const user = userEvent.setup();
|
|
1246
|
+
server.use(http.get('/api/data/forms/specialtyTypeForm', () => {
|
|
1247
|
+
return HttpResponse.json({ error: 'Not found' }, { status: 404 });
|
|
1248
|
+
}));
|
|
1249
|
+
render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
|
|
1250
|
+
await user.click(await screen.findByRole('button', { name: 'Add' }));
|
|
1251
|
+
const newRecordButton = await screen.findByRole('radio', { name: /new/i });
|
|
1252
|
+
await user.click(newRecordButton);
|
|
1253
|
+
await screen.findByText(/not found/i);
|
|
1254
|
+
});
|
|
1255
|
+
});
|
|
1130
1256
|
describe('when passed a one-to-many collection entry', () => {
|
|
1131
1257
|
const setupTestMocks = (object, form, instances) => {
|
|
1132
1258
|
server.use(http.get(`/api/data/objects/${object.id}/effective`, () => HttpResponse.json(object)), http.get(`/api/data/forms/${form.id}`, () => HttpResponse.json(form)), http.get(`/api/data/forms?filter={"where":{"actionId":"${form.actionId}","objectId":"${object.id}"}}`, () => HttpResponse.json([form])), http.get(`/api/data/objects/${object.id}/instances`, () => HttpResponse.json(instances || [])), http.get(`/api/data/objects/${object.id}/instances/checkAccess`, () => HttpResponse.json({
|