diff --git a/packages/backend/src/apps/signalwire/actions/add-voice-xml-node/index.js b/packages/backend/src/apps/signalwire/actions/add-voice-xml-node/index.js
index 0aa889a1..4d206c01 100644
--- a/packages/backend/src/apps/signalwire/actions/add-voice-xml-node/index.js
+++ b/packages/backend/src/apps/signalwire/actions/add-voice-xml-node/index.js
@@ -85,8 +85,8 @@ export default defineAction({
value: '{parameters.nodeName}',
},
{
- name: 'parameters.attributes',
- value: '{parameters.attributes}',
+ name: 'parameters.attributeKey',
+ value: '{fieldsEntry.key}',
},
],
},
@@ -112,10 +112,6 @@ export default defineAction({
name: 'key',
value: 'listNodeFields',
},
- {
- name: 'nodeIndex',
- value: 0,
- },
{
name: 'parameters.hasChildrenNodes',
value: '{parameters.hasChildrenNodes}',
diff --git a/packages/backend/src/apps/signalwire/dynamic-data/list-voice-xml-node-attribute-values/index.js b/packages/backend/src/apps/signalwire/dynamic-data/list-voice-xml-node-attribute-values/index.js
index 27f775c0..ac9bcac0 100644
--- a/packages/backend/src/apps/signalwire/dynamic-data/list-voice-xml-node-attribute-values/index.js
+++ b/packages/backend/src/apps/signalwire/dynamic-data/list-voice-xml-node-attribute-values/index.js
@@ -4,7 +4,7 @@ export default {
async run($) {
const nodeName = $.step.parameters.nodeName;
- const attributeName = $.step.parameters.attributeName;
+ const attributeKey = $.step.parameters.attributeKey;
// Node: Conference
const conferenceMutedAttributeValues = [
@@ -92,17 +92,6 @@ export default {
},
];
- const conferenceStayAloneAttributeValues = [
- {
- name: 'Yes',
- value: true,
- },
- {
- name: 'No',
- value: false,
- },
- ];
-
const conferenceJitterBufferAttributeValues = [
{
name: 'Off',
@@ -126,7 +115,6 @@ export default {
waitMethod: conferenceWaitMethodAttributeValues,
record: conferenceRecordAttributeValues,
trim: conferenceTrimAttributeValues,
- stayAlone: conferenceStayAloneAttributeValues,
jitterBuffer: conferenceJitterBufferAttributeValues,
};
@@ -232,10 +220,10 @@ export default {
};
const allNodeAttributeValues = {
- conference,
- say,
- sip,
- stream,
+ Conference: conference,
+ Say: say,
+ Sip: sip,
+ Stream: stream,
};
if (!nodeName) return { data: [] };
@@ -244,7 +232,7 @@ export default {
if (!selectedNodeAttributes) return { data: [] };
- const selectedNodeAttributeValues = selectedNodeAttributes[attributeName];
+ const selectedNodeAttributeValues = selectedNodeAttributes[attributeKey];
if (!selectedNodeAttributeValues) return { data: [] };
diff --git a/packages/backend/src/apps/signalwire/dynamic-fields/list-node-fields/index.js b/packages/backend/src/apps/signalwire/dynamic-fields/list-node-fields/index.js
index 692ffcb2..141cc7a6 100644
--- a/packages/backend/src/apps/signalwire/dynamic-fields/list-node-fields/index.js
+++ b/packages/backend/src/apps/signalwire/dynamic-fields/list-node-fields/index.js
@@ -65,16 +65,48 @@ export default {
{
label: 'Attribute name',
key: 'key',
- type: 'string',
+ type: 'dropdown',
required: false,
variables: true,
+ source: {
+ type: 'query',
+ name: 'getDynamicData',
+ arguments: [
+ {
+ name: 'key',
+ value: 'listVoiceXmlNodeAttributes',
+ },
+ {
+ name: 'parameters.nodeName',
+ value: '{outerFieldsEntry.nodeName}',
+ },
+ ],
+ },
},
{
label: 'Attribute value',
key: 'value',
- type: 'string',
+ type: 'dropdown',
required: false,
variables: true,
+ source: {
+ type: 'query',
+ name: 'getDynamicData',
+ arguments: [
+ {
+ name: 'key',
+ value: 'listVoiceXmlNodeAttributeValues',
+ },
+ {
+ name: 'parameters.nodeName',
+ value: '{outerFieldsEntry.nodeName}',
+ },
+ {
+ name: 'parameters.attributeKey',
+ value: '{fieldsEntry.key}',
+ },
+ ],
+ },
},
],
},
diff --git a/packages/web/src/components/DynamicField/DynamicFieldEntry.jsx b/packages/web/src/components/DynamicField/DynamicFieldEntry.jsx
new file mode 100644
index 00000000..9269cf12
--- /dev/null
+++ b/packages/web/src/components/DynamicField/DynamicFieldEntry.jsx
@@ -0,0 +1,50 @@
+import PropTypes from 'prop-types';
+import * as React from 'react';
+import Stack from '@mui/material/Stack';
+
+import InputCreator from 'components/InputCreator';
+import { EditorContext } from 'contexts/Editor';
+import { FieldsPropType } from 'propTypes/propTypes';
+import { FieldEntryProvider } from 'contexts/FieldEntry';
+import useFieldEntryContext from 'hooks/useFieldEntryContext';
+
+function DynamicFieldEntry(props) {
+ const { fields, stepId, namePrefix } = props;
+ const editorContext = React.useContext(EditorContext);
+ const fieldEntryContext = useFieldEntryContext();
+
+ const newFieldEntryPaths = [
+ ...(fieldEntryContext?.fieldEntryPaths || []),
+ namePrefix,
+ ];
+
+ return (
+
+ {fields.map((fieldSchema, fieldSchemaIndex) => (
+
+
+
+ ))}
+
+ );
+}
+
+DynamicFieldEntry.propTypes = {
+ stepId: PropTypes.string,
+ namePrefix: PropTypes.string,
+ index: PropTypes.number,
+ fields: FieldsPropType.isRequired,
+};
+
+export default DynamicFieldEntry;
diff --git a/packages/web/src/components/DynamicField/index.jsx b/packages/web/src/components/DynamicField/index.jsx
index 19aaad49..6822c1b2 100644
--- a/packages/web/src/components/DynamicField/index.jsx
+++ b/packages/web/src/components/DynamicField/index.jsx
@@ -7,15 +7,17 @@ import Stack from '@mui/material/Stack';
import IconButton from '@mui/material/IconButton';
import RemoveIcon from '@mui/icons-material/Remove';
import AddIcon from '@mui/icons-material/Add';
-import InputCreator from 'components/InputCreator';
-import { EditorContext } from 'contexts/Editor';
+
import { FieldsPropType } from 'propTypes/propTypes';
+import DynamicFieldEntry from './DynamicFieldEntry';
+import { FieldEntryProvider } from 'contexts/FieldEntry';
+import useFieldEntryContext from 'hooks/useFieldEntryContext';
function DynamicField(props) {
const { label, description, fields, name, defaultValue, stepId } = props;
const { control, setValue, getValues } = useFormContext();
const fieldsValue = useWatch({ control, name });
- const editorContext = React.useContext(EditorContext);
+ const fieldEntryContext = useFieldEntryContext();
const createEmptyItem = React.useCallback(() => {
return fields.reduce((previousValue, field) => {
@@ -60,7 +62,7 @@ function DynamicField(props) {
);
return (
-
+
{label}
{fieldsValue?.map?.((field, index) => (
@@ -76,22 +78,11 @@ function DynamicField(props) {
minWidth: 0,
}}
>
- {fields.map((fieldSchema, fieldSchemaIndex) => (
-
-
-
- ))}
+
{description}
-
+
);
}
diff --git a/packages/web/src/components/InputCreator/index.jsx b/packages/web/src/components/InputCreator/index.jsx
index 7177ed7b..aa04b09f 100644
--- a/packages/web/src/components/InputCreator/index.jsx
+++ b/packages/web/src/components/InputCreator/index.jsx
@@ -26,6 +26,7 @@ function InputCreator(props) {
showOptionValue,
shouldUnregister,
} = props;
+
const {
key: name,
label,
@@ -35,9 +36,11 @@ function InputCreator(props) {
description,
type,
} = schema;
+
const { data, loading } = useDynamicData(stepId, schema);
const { data: additionalFieldsData, isLoading: isDynamicFieldsLoading } =
useDynamicFields(stepId, schema);
+
const additionalFields = additionalFieldsData?.data;
const computedName = namePrefix ? `${namePrefix}.${name}` : name;
diff --git a/packages/web/src/contexts/Editor.jsx b/packages/web/src/contexts/Editor.jsx
index bb554fb8..2835a83f 100644
--- a/packages/web/src/contexts/Editor.jsx
+++ b/packages/web/src/contexts/Editor.jsx
@@ -7,6 +7,7 @@ export const EditorContext = React.createContext({
export const EditorProvider = (props) => {
const { children, value } = props;
+
return (
{children}
);
@@ -14,5 +15,7 @@ export const EditorProvider = (props) => {
EditorProvider.propTypes = {
children: PropTypes.node.isRequired,
- value: PropTypes.shape({ readOnly: PropTypes.bool.isRequired }).isRequired,
+ value: PropTypes.shape({
+ readOnly: PropTypes.bool.isRequired,
+ }).isRequired,
};
diff --git a/packages/web/src/contexts/FieldEntry.jsx b/packages/web/src/contexts/FieldEntry.jsx
new file mode 100644
index 00000000..9961b06d
--- /dev/null
+++ b/packages/web/src/contexts/FieldEntry.jsx
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+
+export const FieldEntryContext = React.createContext({});
+
+export const FieldEntryProvider = (props) => {
+ const { children, value } = props;
+
+ return (
+
+ {children}
+
+ );
+};
+
+FieldEntryProvider.propTypes = {
+ children: PropTypes.node.isRequired,
+ value: PropTypes.object,
+};
diff --git a/packages/web/src/contexts/StepExecutions.jsx b/packages/web/src/contexts/StepExecutions.jsx
index 2664c0ea..58ad62eb 100644
--- a/packages/web/src/contexts/StepExecutions.jsx
+++ b/packages/web/src/contexts/StepExecutions.jsx
@@ -6,6 +6,7 @@ export const StepExecutionsContext = React.createContext([]);
export const StepExecutionsProvider = (props) => {
const { children, value } = props;
+
return (
{children}
diff --git a/packages/web/src/hooks/useDynamicData.js b/packages/web/src/hooks/useDynamicData.js
index 79b02f35..c14b2a4c 100644
--- a/packages/web/src/hooks/useDynamicData.js
+++ b/packages/web/src/hooks/useDynamicData.js
@@ -1,24 +1,38 @@
import * as React from 'react';
import { useFormContext } from 'react-hook-form';
import set from 'lodash/set';
+import first from 'lodash/first';
+import last from 'lodash/last';
import { useMutation } from '@tanstack/react-query';
import isEqual from 'lodash/isEqual';
import api from 'helpers/api';
+import useFieldEntryContext from './useFieldEntryContext';
const variableRegExp = /({.*?})/;
-function computeArguments(args, getValues) {
+function computeArguments(args, getValues, fieldEntryPaths) {
const initialValue = {};
return args.reduce((result, { name, value }) => {
const isVariable = variableRegExp.test(value);
+
if (isVariable) {
- const sanitizedFieldPath = value.replace(/{|}/g, '');
+ const fieldsEntryPath = last(fieldEntryPaths);
+ const outerFieldsEntryPath = first(fieldEntryPaths);
+
+ const sanitizedFieldPath = value
+ .replace(/{|}/g, '')
+ .replace('fieldsEntry.', `${fieldsEntryPath}.`)
+ .replace('outerFieldsEntry.', `${outerFieldsEntryPath}.`);
+
const computedValue = getValues(sanitizedFieldPath);
+
if (computedValue === undefined)
throw new Error(`The ${sanitizedFieldPath} field is required.`);
+
set(result, name, computedValue);
+
return result;
}
@@ -52,7 +66,9 @@ function useDynamicData(stepId, schema) {
});
const { getValues } = useFormContext();
+ const { fieldEntryPaths } = useFieldEntryContext();
const formValues = getValues();
+
/**
* Return `null` when even a field is missing value.
*
@@ -62,23 +78,31 @@ function useDynamicData(stepId, schema) {
const computedVariables = React.useMemo(() => {
if (schema.type === 'dropdown' && schema.source) {
try {
- const variables = computeArguments(schema.source.arguments, getValues);
+ const variables = computeArguments(
+ schema.source.arguments,
+ getValues,
+ fieldEntryPaths,
+ );
+
// if computed variables are the same, return the last computed variables.
if (isEqual(variables, lastComputedVariables.current)) {
return lastComputedVariables.current;
}
+
lastComputedVariables.current = variables;
+
return variables;
} catch (err) {
return null;
}
}
+
return null;
/**
* `formValues` is to trigger recomputation when form is updated.
* `getValues` is for convenience as it supports paths for fields like `getValues('foo.bar.baz')`.
*/
- }, [schema, formValues, getValues]);
+ }, [schema, formValues, getValues, fieldEntryPaths]);
React.useEffect(() => {
if (
diff --git a/packages/web/src/hooks/useFieldEntryContext.jsx b/packages/web/src/hooks/useFieldEntryContext.jsx
new file mode 100644
index 00000000..612b3dfe
--- /dev/null
+++ b/packages/web/src/hooks/useFieldEntryContext.jsx
@@ -0,0 +1,8 @@
+import * as React from 'react';
+import { FieldEntryContext } from 'contexts/FieldEntry';
+
+export default function useFieldEntryContext() {
+ const fieldEntryContext = React.useContext(FieldEntryContext);
+
+ return fieldEntryContext;
+}