refactor: rewrite useDynamicFields with RQ

This commit is contained in:
Rıdvan Akca
2024-03-25 17:34:48 +03:00
parent fc4eeed764
commit 1b5d3beeca
5 changed files with 44 additions and 75 deletions

View File

@@ -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;

View File

@@ -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,

View File

@@ -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
} }

View File

@@ -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>

View File

@@ -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,
},
}); });
}
}, [getDynamicFields, stepId, schema, computedVariables]); return data;
return { },
called, enabled: !!stepId && !!computedVariables,
data: data?.getDynamicFields, });
loading,
}; return query;
} }
export default useDynamicFields; export default useDynamicFields;