feat: add dropdown field with dynamic data in flow

This commit is contained in:
Ali BARIN
2022-03-19 21:32:45 +01:00
committed by Ömer Faruk Aydın
parent 5e1fc510ff
commit 932145e3a1
11 changed files with 174 additions and 43 deletions

View File

@@ -122,10 +122,6 @@
"type": "query", "type": "query",
"name": "getData", "name": "getData",
"arguments": [ "arguments": [
{
"name": "stepId",
"value": "{step.id}"
},
{ {
"name": "key", "name": "key",
"value": "listChannels" "value": "listChannels"

View File

@@ -1,9 +1,5 @@
import App from '../../models/app'; import App from '../../models/app';
import Connection from '../../models/connection';
import Step from '../../models/step';
import { IApp } from '@automatisch/types';
import Context from '../../types/express/context'; import Context from '../../types/express/context';
import ListData from '../../apps/slack/data/list-channels';
type Params = { type Params = {
stepId: string; stepId: string;

View File

@@ -96,7 +96,7 @@ export interface IFieldDropdown {
export interface IFieldText { export interface IFieldText {
key: string; key: string;
label: string; label: string;
type: string; type: 'string';
required: boolean; required: boolean;
readOnly: boolean; readOnly: boolean;
value: string; value: string;

View File

@@ -3,7 +3,6 @@ import type { ContainerProps } from '@mui/material/Container';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery'; import useMediaQuery from '@mui/material/useMediaQuery';
import MuiAppBar from '@mui/material/AppBar'; import MuiAppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';

View File

@@ -7,7 +7,7 @@ import ListItem from '@mui/material/ListItem';
import Autocomplete from '@mui/material/Autocomplete'; import Autocomplete from '@mui/material/Autocomplete';
import FlowSubstepTitle from 'components/FlowSubstepTitle'; import FlowSubstepTitle from 'components/FlowSubstepTitle';
import type { IApp, IConnection, IStep, ISubstep, IJSONObject } from '@automatisch/types'; import type { IApp, IConnection, IStep, ISubstep } from '@automatisch/types';
import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections'; import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections';
import { TEST_CONNECTION } from 'graphql/queries/test-connection'; import { TEST_CONNECTION } from 'graphql/queries/test-connection';

View File

@@ -0,0 +1,63 @@
import * as React from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import Autocomplete, { AutocompleteProps } from '@mui/material/Autocomplete';
interface ControlledAutocompleteProps extends AutocompleteProps<Option, boolean, boolean, boolean> {
shouldUnregister?: boolean;
name: string;
required?: boolean;
}
type Option = {
label: string;
value: string;
}
const getOption = (options: readonly Option[], value: string) => options.find(option => option.value === value);
function ControlledAutocomplete(props: ControlledAutocompleteProps): React.ReactElement {
const { control } = useFormContext();
const {
required = false,
name,
defaultValue,
shouldUnregister,
onBlur,
onChange,
...autocompleteProps
} = props;
if (!autocompleteProps.options) return (<React.Fragment />);
return (
<Controller
rules={{ required }}
name={name}
defaultValue={defaultValue || ''}
control={control}
shouldUnregister={shouldUnregister}
render={({ field: { ref, onChange: controllerOnChange, onBlur: controllerOnBlur, ...field } }) => (
<Autocomplete
{...autocompleteProps}
{...field}
value={getOption(autocompleteProps.options, field.value)}
onChange={(event, selectedOption, reason, details) => {
const typedSelectedOption = selectedOption as Option;
if (typedSelectedOption?.value) {
controllerOnChange(typedSelectedOption.value);
} else {
controllerOnChange(typedSelectedOption);
}
onChange?.(event, selectedOption, reason, details);
}}
onBlur={(...args) => { controllerOnBlur(); onBlur?.(...args); }}
ref={ref}
/>
)}
/>
);
}
export default ControlledAutocomplete;

View File

@@ -3,6 +3,7 @@ import { useFormContext } from 'react-hook-form';
import Collapse from '@mui/material/Collapse'; import Collapse from '@mui/material/Collapse';
import ListItem from '@mui/material/ListItem'; import ListItem from '@mui/material/ListItem';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import FlowSubstepTitle from 'components/FlowSubstepTitle'; import FlowSubstepTitle from 'components/FlowSubstepTitle';
import InputCreator from 'components/InputCreator'; import InputCreator from 'components/InputCreator';
@@ -92,13 +93,19 @@ function FlowSubstep(props: FlowSubstepProps): React.ReactElement {
/> />
<Collapse in={expanded} timeout="auto" unmountOnExit> <Collapse in={expanded} timeout="auto" unmountOnExit>
<ListItem sx={{ pt: 2, pb: 3, flexDirection: 'column', alignItems: 'flex-start' }}> <ListItem sx={{ pt: 2, pb: 3, flexDirection: 'column', alignItems: 'flex-start' }}>
<Stack
width='100%'
spacing={2}
>
{args?.map((argument) => ( {args?.map((argument) => (
<InputCreator <InputCreator
key={argument.key} key={argument.key}
schema={argument} schema={argument}
namePrefix="parameters" namePrefix="parameters"
stepId={step.id}
/> />
))} ))}
</Stack>
<Button <Button
fullWidth fullWidth

View File

@@ -1,22 +1,42 @@
import * as React from 'react'; import * as React from 'react';
import type { IField } from '@automatisch/types'; import { useLazyQuery } from '@apollo/client';
import MuiTextField from '@mui/material/TextField';
import type { IField, IFieldDropdown, IJSONObject } from '@automatisch/types';
import { GET_DATA } from 'graphql/queries/get-data';
import PowerInput from 'components/PowerInput'; import PowerInput from 'components/PowerInput';
import TextField from 'components/TextField'; import TextField from 'components/TextField';
import ControlledAutocomplete from 'components/ControlledAutocomplete';
type InputCreatorProps = { type InputCreatorProps = {
onChange?: React.ChangeEventHandler; onChange?: React.ChangeEventHandler;
onBlur?: React.FocusEventHandler; onBlur?: React.FocusEventHandler;
schema: IField; schema: IField;
namePrefix?: string; namePrefix?: string;
stepId?: string;
}; };
type RawOption = {
name: string;
value: string;
};
type Option = {
label: string;
value: string;
};
const computeArguments = (args: IFieldDropdown["source"]["arguments"]): IJSONObject => args.reduce((result, { name, value }) => ({ ...result, [name as string]: value as string }), {});
const optionGenerator = (options: RawOption[]): Option[] => options?.map(({ name, value }) => ({ label: name as string, value: value as string }));
const getOption = (options: Option[], value: string) => options?.find(option => option.value === value);
export default function InputCreator(props: InputCreatorProps): React.ReactElement { export default function InputCreator(props: InputCreatorProps): React.ReactElement {
const { const {
onChange, onChange,
onBlur, onBlur,
schema, schema,
namePrefix, namePrefix,
stepId,
} = props; } = props;
const { const {
@@ -28,10 +48,44 @@ export default function InputCreator(props: InputCreatorProps): React.ReactEleme
description, description,
clickToCopy, clickToCopy,
variables, variables,
type,
} = schema; } = schema;
const [getData, { called, data }] = useLazyQuery(GET_DATA);
React.useEffect(() => {
if (schema.type === 'dropdown' && stepId && schema.source && !called) {
getData({
variables: {
stepId,
...computeArguments(schema.source.arguments),
}
})
}
}, [getData, called, stepId, schema]);
const computedName = namePrefix ? `${namePrefix}.${name}` : name; const computedName = namePrefix ? `${namePrefix}.${name}` : name;
if (type === 'dropdown') {
const options = optionGenerator(data?.getData);
return (
<ControlledAutocomplete
name={computedName}
fullWidth
disablePortal
disableClearable={true}
options={options}
renderInput={(params) => <MuiTextField {...params} label={label} />}
value={getOption(options, value)}
onChange={console.log}
/>
);
}
if (type === 'string') {
if (variables) { if (variables) {
return ( return (
<PowerInput <PowerInput
@@ -39,7 +93,6 @@ export default function InputCreator(props: InputCreatorProps): React.ReactEleme
description={description} description={description}
name={computedName} name={computedName}
required={required} required={required}
// onBlur={onBlur}
/> />
); );
} }
@@ -61,4 +114,7 @@ export default function InputCreator(props: InputCreatorProps): React.ReactEleme
clickToCopy={clickToCopy} clickToCopy={clickToCopy}
/> />
); );
}
return (<React.Fragment />)
}; };

View File

@@ -8,7 +8,6 @@ import LoadingButton from '@mui/lab/LoadingButton';
import useAuthentication from 'hooks/useAuthentication'; import useAuthentication from 'hooks/useAuthentication';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
import { setItem } from 'helpers/storage';
import { LOGIN } from 'graphql/mutations/login'; import { LOGIN } from 'graphql/mutations/login';
import Form from 'components/Form'; import Form from 'components/Form';
import TextField from 'components/TextField'; import TextField from 'components/TextField';

View File

@@ -78,6 +78,14 @@ export const GET_APPS = gql`
required required
description description
variables variables
source {
type
name
arguments {
name
value
}
}
} }
} }
} }

View File

@@ -0,0 +1,7 @@
import { gql } from '@apollo/client';
export const GET_DATA = gql`
query GetData($stepId: String!, $key: String!) {
getData(stepId: $stepId, key: $key)
}
`;