refactor: rewrite useDynamicFields with RQ
This commit is contained in:
@@ -1,40 +0,0 @@
|
|||||||
import App from '../../models/app.js';
|
|
||||||
import Step from '../../models/step.js';
|
|
||||||
import globalVariable from '../../helpers/global-variable.js';
|
|
||||||
|
|
||||||
const getDynamicFields = async (_parent, params, context) => {
|
|
||||||
const conditions = context.currentUser.can('update', 'Flow');
|
|
||||||
const userSteps = context.currentUser.$relatedQuery('steps');
|
|
||||||
const allSteps = Step.query();
|
|
||||||
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
|
|
||||||
|
|
||||||
const step = await stepBaseQuery
|
|
||||||
.clone()
|
|
||||||
.withGraphFetched({
|
|
||||||
connection: true,
|
|
||||||
flow: true,
|
|
||||||
})
|
|
||||||
.findById(params.stepId);
|
|
||||||
|
|
||||||
if (!step) return null;
|
|
||||||
|
|
||||||
const connection = step.connection;
|
|
||||||
|
|
||||||
if (!step.appKey) return null;
|
|
||||||
|
|
||||||
const app = await App.findOneByKey(step.appKey);
|
|
||||||
const $ = await globalVariable({ connection, app, flow: step.flow, step });
|
|
||||||
|
|
||||||
const command = app.dynamicFields.find((data) => data.key === params.key);
|
|
||||||
|
|
||||||
for (const parameterKey in params.parameters) {
|
|
||||||
const parameterValue = params.parameters[parameterKey];
|
|
||||||
$.step.parameters[parameterKey] = parameterValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const additionalFields = (await command.run($)) || [];
|
|
||||||
|
|
||||||
return additionalFields;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getDynamicFields;
|
|
@@ -4,7 +4,6 @@ import getAppAuthClients from './queries/get-app-auth-clients.ee.js';
|
|||||||
import getBillingAndUsage from './queries/get-billing-and-usage.ee.js';
|
import getBillingAndUsage from './queries/get-billing-and-usage.ee.js';
|
||||||
import getConnectedApps from './queries/get-connected-apps.js';
|
import getConnectedApps from './queries/get-connected-apps.js';
|
||||||
import getDynamicData from './queries/get-dynamic-data.js';
|
import getDynamicData from './queries/get-dynamic-data.js';
|
||||||
import getDynamicFields from './queries/get-dynamic-fields.js';
|
|
||||||
import getFlow from './queries/get-flow.js';
|
import getFlow from './queries/get-flow.js';
|
||||||
import getStepWithTestExecutions from './queries/get-step-with-test-executions.js';
|
import getStepWithTestExecutions from './queries/get-step-with-test-executions.js';
|
||||||
import testConnection from './queries/test-connection.js';
|
import testConnection from './queries/test-connection.js';
|
||||||
@@ -16,7 +15,6 @@ const queryResolvers = {
|
|||||||
getBillingAndUsage,
|
getBillingAndUsage,
|
||||||
getConnectedApps,
|
getConnectedApps,
|
||||||
getDynamicData,
|
getDynamicData,
|
||||||
getDynamicFields,
|
|
||||||
getFlow,
|
getFlow,
|
||||||
getStepWithTestExecutions,
|
getStepWithTestExecutions,
|
||||||
testConnection,
|
testConnection,
|
||||||
|
@@ -11,11 +11,6 @@ type Query {
|
|||||||
key: String!
|
key: String!
|
||||||
parameters: JSONObject
|
parameters: JSONObject
|
||||||
): JSONObject
|
): JSONObject
|
||||||
getDynamicFields(
|
|
||||||
stepId: String!
|
|
||||||
key: String!
|
|
||||||
parameters: JSONObject
|
|
||||||
): [SubstepArgument]
|
|
||||||
getBillingAndUsage: GetBillingAndUsage
|
getBillingAndUsage: GetBillingAndUsage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import MuiTextField from '@mui/material/TextField';
|
import MuiTextField from '@mui/material/TextField';
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
|
||||||
import useDynamicFields from 'hooks/useDynamicFields';
|
import useDynamicFields from 'hooks/useDynamicFields';
|
||||||
import useDynamicData from 'hooks/useDynamicData';
|
import useDynamicData from 'hooks/useDynamicData';
|
||||||
import PowerInput from 'components/PowerInput';
|
import PowerInput from 'components/PowerInput';
|
||||||
@@ -8,8 +9,10 @@ import TextField from 'components/TextField';
|
|||||||
import ControlledAutocomplete from 'components/ControlledAutocomplete';
|
import ControlledAutocomplete from 'components/ControlledAutocomplete';
|
||||||
import ControlledCustomAutocomplete from 'components/ControlledCustomAutocomplete';
|
import ControlledCustomAutocomplete from 'components/ControlledCustomAutocomplete';
|
||||||
import DynamicField from 'components/DynamicField';
|
import DynamicField from 'components/DynamicField';
|
||||||
|
|
||||||
const optionGenerator = (options) =>
|
const optionGenerator = (options) =>
|
||||||
options?.map(({ name, value }) => ({ label: name, value: value }));
|
options?.map(({ name, value }) => ({ label: name, value: value }));
|
||||||
|
|
||||||
export default function InputCreator(props) {
|
export default function InputCreator(props) {
|
||||||
const {
|
const {
|
||||||
onChange,
|
onChange,
|
||||||
@@ -31,9 +34,12 @@ export default function InputCreator(props) {
|
|||||||
type,
|
type,
|
||||||
} = schema;
|
} = schema;
|
||||||
const { data, loading } = useDynamicData(stepId, schema);
|
const { data, loading } = useDynamicData(stepId, schema);
|
||||||
const { data: additionalFields, loading: additionalFieldsLoading } =
|
const { data: additionalFieldsData, isLoading: isDynamicFieldsLoading } =
|
||||||
useDynamicFields(stepId, schema);
|
useDynamicFields(stepId, schema);
|
||||||
|
const additionalFields = additionalFieldsData?.data;
|
||||||
|
|
||||||
const computedName = namePrefix ? `${namePrefix}.${name}` : name;
|
const computedName = namePrefix ? `${namePrefix}.${name}` : name;
|
||||||
|
|
||||||
if (type === 'dynamic') {
|
if (type === 'dynamic') {
|
||||||
return (
|
return (
|
||||||
<DynamicField
|
<DynamicField
|
||||||
@@ -50,8 +56,10 @@ export default function InputCreator(props) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'dropdown') {
|
if (type === 'dropdown') {
|
||||||
const preparedOptions = schema.options || optionGenerator(data);
|
const preparedOptions = schema.options || optionGenerator(data);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{!schema.variables && (
|
{!schema.variables && (
|
||||||
@@ -63,7 +71,9 @@ export default function InputCreator(props) {
|
|||||||
disablePortal
|
disablePortal
|
||||||
disableClearable={required}
|
disableClearable={required}
|
||||||
options={preparedOptions}
|
options={preparedOptions}
|
||||||
renderInput={(params) => <MuiTextField {...params} label={label} required={required}/>}
|
renderInput={(params) => (
|
||||||
|
<MuiTextField {...params} label={label} required={required} />
|
||||||
|
)}
|
||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
description={description}
|
description={description}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
@@ -93,7 +103,7 @@ export default function InputCreator(props) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{additionalFieldsLoading && !additionalFields?.length && (
|
{isDynamicFieldsLoading && !additionalFields?.length && (
|
||||||
<div>
|
<div>
|
||||||
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
||||||
</div>
|
</div>
|
||||||
@@ -113,6 +123,7 @@ export default function InputCreator(props) {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'string') {
|
if (type === 'string') {
|
||||||
if (schema.variables) {
|
if (schema.variables) {
|
||||||
return (
|
return (
|
||||||
@@ -127,7 +138,7 @@ export default function InputCreator(props) {
|
|||||||
shouldUnregister={shouldUnregister}
|
shouldUnregister={shouldUnregister}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{additionalFieldsLoading && !additionalFields?.length && (
|
{isDynamicFieldsLoading && !additionalFields?.length && (
|
||||||
<div>
|
<div>
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
sx={{ display: 'block', margin: '20px auto' }}
|
sx={{ display: 'block', margin: '20px auto' }}
|
||||||
@@ -149,6 +160,7 @@ export default function InputCreator(props) {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -168,7 +180,7 @@ export default function InputCreator(props) {
|
|||||||
shouldUnregister={shouldUnregister}
|
shouldUnregister={shouldUnregister}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{additionalFieldsLoading && !additionalFields?.length && (
|
{isDynamicFieldsLoading && !additionalFields?.length && (
|
||||||
<div>
|
<div>
|
||||||
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,27 +1,35 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useLazyQuery } from '@apollo/client';
|
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import set from 'lodash/set';
|
import set from 'lodash/set';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
import { GET_DYNAMIC_FIELDS } from 'graphql/queries/get-dynamic-fields';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import api from 'helpers/api';
|
||||||
|
|
||||||
const variableRegExp = /({.*?})/;
|
const variableRegExp = /({.*?})/;
|
||||||
// TODO: extract this function to a separate file
|
// TODO: extract this function to a separate file
|
||||||
function computeArguments(args, getValues) {
|
function computeArguments(args, getValues) {
|
||||||
const initialValue = {};
|
const initialValue = {};
|
||||||
|
|
||||||
return args.reduce((result, { name, value }) => {
|
return args.reduce((result, { name, value }) => {
|
||||||
const isVariable = variableRegExp.test(value);
|
const isVariable = variableRegExp.test(value);
|
||||||
|
|
||||||
if (isVariable) {
|
if (isVariable) {
|
||||||
const sanitizedFieldPath = value.replace(/{|}/g, '');
|
const sanitizedFieldPath = value.replace(/{|}/g, '');
|
||||||
const computedValue = getValues(sanitizedFieldPath);
|
const computedValue = getValues(sanitizedFieldPath);
|
||||||
|
|
||||||
if (computedValue === undefined || computedValue === '')
|
if (computedValue === undefined || computedValue === '')
|
||||||
throw new Error(`The ${sanitizedFieldPath} field is required.`);
|
throw new Error(`The ${sanitizedFieldPath} field is required.`);
|
||||||
|
|
||||||
set(result, name, computedValue);
|
set(result, name, computedValue);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(result, name, value);
|
set(result, name, value);
|
||||||
return result;
|
return result;
|
||||||
}, initialValue);
|
}, initialValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the dynamic fields for the given step.
|
* Fetch the dynamic fields for the given step.
|
||||||
* This hook must be within a react-hook-form context.
|
* This hook must be within a react-hook-form context.
|
||||||
@@ -31,10 +39,9 @@ function computeArguments(args, getValues) {
|
|||||||
*/
|
*/
|
||||||
function useDynamicFields(stepId, schema) {
|
function useDynamicFields(stepId, schema) {
|
||||||
const lastComputedVariables = React.useRef({});
|
const lastComputedVariables = React.useRef({});
|
||||||
const [getDynamicFields, { called, data, loading }] =
|
|
||||||
useLazyQuery(GET_DYNAMIC_FIELDS);
|
|
||||||
const { getValues } = useFormContext();
|
const { getValues } = useFormContext();
|
||||||
const formValues = getValues();
|
const formValues = getValues();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return `null` when even a field is missing value.
|
* Return `null` when even a field is missing value.
|
||||||
*
|
*
|
||||||
@@ -58,31 +65,28 @@ function useDynamicFields(stepId, schema) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
/**
|
/**
|
||||||
* `formValues` is to trigger recomputation when form is updated.
|
* `formValues` is to trigger recomputation when form is updated.
|
||||||
* `getValues` is for convenience as it supports paths for fields like `getValues('foo.bar.baz')`.
|
* `getValues` is for convenience as it supports paths for fields like `getValues('foo.bar.baz')`.
|
||||||
*/
|
*/
|
||||||
}, [schema, formValues, getValues]);
|
}, [schema, formValues, getValues]);
|
||||||
React.useEffect(() => {
|
|
||||||
if (
|
const query = useQuery({
|
||||||
schema.type === 'dropdown' &&
|
queryKey: ['dynamicFields', stepId, computedVariables],
|
||||||
stepId &&
|
queryFn: async () => {
|
||||||
schema.additionalFields &&
|
const { data } = await api.post(`/v1/steps/${stepId}/dynamic-fields`, {
|
||||||
computedVariables
|
dynamicFieldsKey: computedVariables.key,
|
||||||
) {
|
parameters: computedVariables.parameters,
|
||||||
getDynamicFields({
|
|
||||||
variables: {
|
|
||||||
stepId,
|
|
||||||
...computedVariables,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled: !!stepId && !!computedVariables,
|
||||||
|
});
|
||||||
|
|
||||||
|
return query;
|
||||||
}
|
}
|
||||||
}, [getDynamicFields, stepId, schema, computedVariables]);
|
|
||||||
return {
|
|
||||||
called,
|
|
||||||
data: data?.getDynamicFields,
|
|
||||||
loading,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export default useDynamicFields;
|
export default useDynamicFields;
|
||||||
|
Reference in New Issue
Block a user