feat: support interactive fields
This commit is contained in:
@@ -107,6 +107,8 @@ type SubstepArgument {
|
|||||||
source: SubstepArgumentSource
|
source: SubstepArgumentSource
|
||||||
additionalFields: SubstepArgumentAdditionalFields
|
additionalFields: SubstepArgumentAdditionalFields
|
||||||
dependsOn: [String]
|
dependsOn: [String]
|
||||||
|
fields: [SubstepArgument]
|
||||||
|
value: JSONObject
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubstepArgumentOption {
|
type SubstepArgumentOption {
|
||||||
|
13
packages/types/index.d.ts
vendored
13
packages/types/index.d.ts
vendored
@@ -145,7 +145,18 @@ export interface IFieldText {
|
|||||||
dependsOn?: string[];
|
dependsOn?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IField = IFieldDropdown | IFieldText;
|
export interface IFieldDynamic {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
type: 'dynamic';
|
||||||
|
required?: boolean;
|
||||||
|
readOnly?: boolean;
|
||||||
|
description?: string;
|
||||||
|
value?: Record<string, unknown>[];
|
||||||
|
fields: (IFieldDropdown | IFieldText)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IField = IFieldDropdown | IFieldText | IFieldDynamic;
|
||||||
|
|
||||||
export interface IAuthenticationStepField {
|
export interface IAuthenticationStepField {
|
||||||
name: string;
|
name: string;
|
||||||
|
126
packages/web/src/components/DynamicField/index.tsx
Normal file
126
packages/web/src/components/DynamicField/index.tsx
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { useFormContext, useWatch } from 'react-hook-form';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import RemoveIcon from '@mui/icons-material/Remove';
|
||||||
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
|
||||||
|
import { IFieldDynamic } from '@automatisch/types';
|
||||||
|
import InputCreator from 'components/InputCreator';
|
||||||
|
import { EditorContext } from 'contexts/Editor';
|
||||||
|
|
||||||
|
interface DynamicFieldProps {
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
onBlur?: (value: string) => void;
|
||||||
|
defaultValue?: Record<string, unknown>[];
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
type?: string;
|
||||||
|
required?: boolean;
|
||||||
|
readOnly?: boolean;
|
||||||
|
description?: string;
|
||||||
|
docUrl?: string;
|
||||||
|
clickToCopy?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
fields: IFieldDynamic["fields"];
|
||||||
|
shouldUnregister?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DynamicField(
|
||||||
|
props: DynamicFieldProps
|
||||||
|
): React.ReactElement {
|
||||||
|
const { label, description, fields, name, defaultValue } = props;
|
||||||
|
const { control, setValue, getValues } = useFormContext();
|
||||||
|
const fieldsValue = useWatch({ control, name }) as Record<string, unknown>[];
|
||||||
|
const editorContext = React.useContext(EditorContext);
|
||||||
|
|
||||||
|
const createEmptyItem = React.useCallback(() => {
|
||||||
|
return fields.reduce((previousValue, field) => {
|
||||||
|
return {
|
||||||
|
...previousValue,
|
||||||
|
[field.key]: '',
|
||||||
|
__id: uuidv4(),
|
||||||
|
}
|
||||||
|
}, {});
|
||||||
|
}, [fields]);
|
||||||
|
|
||||||
|
const addItem = React.useCallback(() => {
|
||||||
|
const values = getValues(name);
|
||||||
|
|
||||||
|
if (!values) {
|
||||||
|
setValue(name, [createEmptyItem()]);
|
||||||
|
} else {
|
||||||
|
setValue(name, values.concat(createEmptyItem()));
|
||||||
|
}
|
||||||
|
}, [getValues, createEmptyItem]);
|
||||||
|
|
||||||
|
const removeItem = React.useCallback((index) => {
|
||||||
|
if (fieldsValue.length === 1) return;
|
||||||
|
|
||||||
|
const newFieldsValue = fieldsValue.filter((fieldValue, fieldIndex) => fieldIndex !== index);
|
||||||
|
|
||||||
|
setValue(name, newFieldsValue);
|
||||||
|
}, [fieldsValue]);
|
||||||
|
|
||||||
|
React.useEffect(function addInitialGroupWhenEmpty() {
|
||||||
|
const fieldValues = getValues(name);
|
||||||
|
|
||||||
|
if (!fieldValues && defaultValue) {
|
||||||
|
setValue(name, defaultValue);
|
||||||
|
} else if (!fieldValues) {
|
||||||
|
setValue(name, [createEmptyItem()]);
|
||||||
|
}
|
||||||
|
}, [createEmptyItem, defaultValue]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Typography variant="subtitle2">{label}</Typography>
|
||||||
|
|
||||||
|
{fieldsValue?.map((field, index) => (
|
||||||
|
<Stack direction="row" spacing={2} key={`fieldGroup-${field.__id}`}>
|
||||||
|
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={{ xs: 2 }} sx={{ display: 'flex', flex: 1 }}>
|
||||||
|
{fields.map((fieldSchema, fieldSchemaIndex) => (
|
||||||
|
<Box sx={{ display: 'flex', flex: '1 0 0px' }} key={`field-${field.__id}-${fieldSchemaIndex}`}>
|
||||||
|
<InputCreator
|
||||||
|
schema={fieldSchema}
|
||||||
|
namePrefix={`${name}.${index}`}
|
||||||
|
disabled={editorContext.readOnly}
|
||||||
|
shouldUnregister={false}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
edge="start"
|
||||||
|
onClick={() => removeItem(index)}
|
||||||
|
sx={{ width: 61, height: 61 }}
|
||||||
|
>
|
||||||
|
<RemoveIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Stack direction="row" spacing={2}>
|
||||||
|
<Stack spacing={{ xs: 2 }} sx={{ display: 'flex', flex: 1 }} />
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
edge="start"
|
||||||
|
onClick={addItem}
|
||||||
|
sx={{ width: 61, height: 61 }}
|
||||||
|
>
|
||||||
|
<AddIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Typography variant="caption">{description}</Typography>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DynamicField;
|
@@ -63,14 +63,14 @@ function generateValidationSchema(substeps: ISubstep[]) {
|
|||||||
const substepArgumentValidations: Record<string, BaseSchema> = {};
|
const substepArgumentValidations: Record<string, BaseSchema> = {};
|
||||||
|
|
||||||
for (const arg of args) {
|
for (const arg of args) {
|
||||||
const { key, required, dependsOn } = arg;
|
const { key, required } = arg;
|
||||||
|
|
||||||
// base validation for the field if not exists
|
// base validation for the field if not exists
|
||||||
if (!substepArgumentValidations[key]) {
|
if (!substepArgumentValidations[key]) {
|
||||||
substepArgumentValidations[key] = yup.mixed();
|
substepArgumentValidations[key] = yup.mixed();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof substepArgumentValidations[key] === 'object') {
|
if (typeof substepArgumentValidations[key] === 'object' && (arg.type === 'string' || arg.type === 'dropdown')) {
|
||||||
// if the field is required, add the required validation
|
// if the field is required, add the required validation
|
||||||
if (required) {
|
if (required) {
|
||||||
substepArgumentValidations[key] = substepArgumentValidations[
|
substepArgumentValidations[key] = substepArgumentValidations[
|
||||||
@@ -79,8 +79,8 @@ function generateValidationSchema(substeps: ISubstep[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if the field depends on another field, add the dependsOn required validation
|
// if the field depends on another field, add the dependsOn required validation
|
||||||
if (Array.isArray(dependsOn) && dependsOn.length > 0) {
|
if (Array.isArray(arg.dependsOn) && arg.dependsOn.length > 0) {
|
||||||
for (const dependsOnKey of dependsOn) {
|
for (const dependsOnKey of arg.dependsOn) {
|
||||||
const missingDependencyValueMessage = `We're having trouble loading '${key}' data as required field '${dependsOnKey}' is missing.`;
|
const missingDependencyValueMessage = `We're having trouble loading '${key}' data as required field '${dependsOnKey}' is missing.`;
|
||||||
|
|
||||||
// TODO: make `dependsOnKey` agnostic to the field. However, nested validation schema is not supported.
|
// TODO: make `dependsOnKey` agnostic to the field. However, nested validation schema is not supported.
|
||||||
|
@@ -144,7 +144,7 @@ function FilterConditions(props: FilterConditionsProps): React.ReactElement {
|
|||||||
|
|
||||||
{group?.and?.map((groupItem: TGroupItem, groupItemIndex: number) => (
|
{group?.and?.map((groupItem: TGroupItem, groupItemIndex: number) => (
|
||||||
<Stack direction="row" spacing={2} key={`item-${groupItem.id}`}>
|
<Stack direction="row" spacing={2} key={`item-${groupItem.id}`}>
|
||||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={{ xs: 2}} sx={{ display: 'flex', flex: 1 }}>
|
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={{ xs: 2 }} sx={{ display: 'flex', flex: 1 }}>
|
||||||
<Box sx={{ display: 'flex', flex: '1 0 0px', maxWidth: ['100%', '33%'] }}>
|
<Box sx={{ display: 'flex', flex: '1 0 0px', maxWidth: ['100%', '33%'] }}>
|
||||||
<InputCreator
|
<InputCreator
|
||||||
schema={createStringArgument({ key: `or.${groupIndex}.and.${groupItemIndex}.key`, label: 'Choose field' })}
|
schema={createStringArgument({ key: `or.${groupIndex}.and.${groupItemIndex}.key`, label: 'Choose field' })}
|
||||||
|
@@ -8,6 +8,7 @@ import useDynamicData from 'hooks/useDynamicData';
|
|||||||
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';
|
import ControlledAutocomplete from 'components/ControlledAutocomplete';
|
||||||
|
import DynamicField from 'components/DynamicField';
|
||||||
|
|
||||||
type InputCreatorProps = {
|
type InputCreatorProps = {
|
||||||
onChange?: React.ChangeEventHandler;
|
onChange?: React.ChangeEventHandler;
|
||||||
@@ -17,6 +18,7 @@ type InputCreatorProps = {
|
|||||||
stepId?: string;
|
stepId?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
showOptionValue?: boolean;
|
showOptionValue?: boolean;
|
||||||
|
shouldUnregister?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type RawOption = {
|
type RawOption = {
|
||||||
@@ -38,6 +40,7 @@ export default function InputCreator(
|
|||||||
stepId,
|
stepId,
|
||||||
disabled,
|
disabled,
|
||||||
showOptionValue,
|
showOptionValue,
|
||||||
|
shouldUnregister,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -47,10 +50,7 @@ export default function InputCreator(
|
|||||||
readOnly = false,
|
readOnly = false,
|
||||||
value,
|
value,
|
||||||
description,
|
description,
|
||||||
clickToCopy,
|
|
||||||
variables,
|
|
||||||
type,
|
type,
|
||||||
dependsOn,
|
|
||||||
} = schema;
|
} = schema;
|
||||||
|
|
||||||
const { data, loading } = useDynamicData(stepId, schema);
|
const { data, loading } = useDynamicData(stepId, schema);
|
||||||
@@ -60,14 +60,31 @@ export default function InputCreator(
|
|||||||
} = useDynamicFields(stepId, schema);
|
} = useDynamicFields(stepId, schema);
|
||||||
const computedName = namePrefix ? `${namePrefix}.${name}` : name;
|
const computedName = namePrefix ? `${namePrefix}.${name}` : name;
|
||||||
|
|
||||||
|
if (type === 'dynamic') {
|
||||||
|
return (
|
||||||
|
<DynamicField
|
||||||
|
label={label}
|
||||||
|
description={description}
|
||||||
|
defaultValue={value}
|
||||||
|
name={computedName}
|
||||||
|
key={computedName}
|
||||||
|
required={required}
|
||||||
|
disabled={disabled}
|
||||||
|
fields={schema.fields}
|
||||||
|
shouldUnregister={shouldUnregister}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (type === 'dropdown') {
|
if (type === 'dropdown') {
|
||||||
const preparedOptions = schema.options || optionGenerator(data);
|
const preparedOptions = schema.options || optionGenerator(data);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
|
key={computedName}
|
||||||
name={computedName}
|
name={computedName}
|
||||||
dependsOn={dependsOn}
|
dependsOn={schema.dependsOn}
|
||||||
fullWidth
|
fullWidth
|
||||||
disablePortal
|
disablePortal
|
||||||
disableClearable={required}
|
disableClearable={required}
|
||||||
@@ -78,6 +95,7 @@ export default function InputCreator(
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
showOptionValue={showOptionValue}
|
showOptionValue={showOptionValue}
|
||||||
|
shouldUnregister={shouldUnregister}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{(additionalFieldsLoading && !additionalFields?.length) && <div>
|
{(additionalFieldsLoading && !additionalFields?.length) && <div>
|
||||||
@@ -92,6 +110,7 @@ export default function InputCreator(
|
|||||||
stepId={stepId}
|
stepId={stepId}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
showOptionValue={true}
|
showOptionValue={true}
|
||||||
|
shouldUnregister={shouldUnregister}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
@@ -99,15 +118,17 @@ export default function InputCreator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'string') {
|
if (type === 'string') {
|
||||||
if (variables) {
|
if (schema.variables) {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<PowerInput
|
<PowerInput
|
||||||
|
key={computedName}
|
||||||
label={label}
|
label={label}
|
||||||
description={description}
|
description={description}
|
||||||
name={computedName}
|
name={computedName}
|
||||||
required={required}
|
required={required}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
shouldUnregister={shouldUnregister}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{(additionalFieldsLoading && !additionalFields?.length) && <div>
|
{(additionalFieldsLoading && !additionalFields?.length) && <div>
|
||||||
@@ -122,6 +143,7 @@ export default function InputCreator(
|
|||||||
stepId={stepId}
|
stepId={stepId}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
showOptionValue={true}
|
showOptionValue={true}
|
||||||
|
shouldUnregister={shouldUnregister}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
@@ -131,6 +153,7 @@ export default function InputCreator(
|
|||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<TextField
|
<TextField
|
||||||
|
key={computedName}
|
||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
required={required}
|
required={required}
|
||||||
placeholder=""
|
placeholder=""
|
||||||
@@ -141,7 +164,8 @@ export default function InputCreator(
|
|||||||
label={label}
|
label={label}
|
||||||
fullWidth
|
fullWidth
|
||||||
helperText={description}
|
helperText={description}
|
||||||
clickToCopy={clickToCopy}
|
clickToCopy={schema.clickToCopy}
|
||||||
|
shouldUnregister={shouldUnregister}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{(additionalFieldsLoading && !additionalFields?.length) && <div>
|
{(additionalFieldsLoading && !additionalFields?.length) && <div>
|
||||||
@@ -156,6 +180,7 @@ export default function InputCreator(
|
|||||||
stepId={stepId}
|
stepId={stepId}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
showOptionValue={true}
|
showOptionValue={true}
|
||||||
|
shouldUnregister={shouldUnregister}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
@@ -5,7 +5,7 @@ import Popper from '@mui/material/Popper';
|
|||||||
import InputLabel from '@mui/material/InputLabel';
|
import InputLabel from '@mui/material/InputLabel';
|
||||||
import FormHelperText from '@mui/material/FormHelperText';
|
import FormHelperText from '@mui/material/FormHelperText';
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
import { Editor, Transforms, Range, createEditor } from 'slate';
|
import { createEditor } from 'slate';
|
||||||
import { Slate, Editable, useSelected, useFocused } from 'slate-react';
|
import { Slate, Editable, useSelected, useFocused } from 'slate-react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
import Suggestions from './Suggestions';
|
import Suggestions from './Suggestions';
|
||||||
import { StepExecutionsContext } from 'contexts/StepExecutions';
|
import { StepExecutionsContext } from 'contexts/StepExecutions';
|
||||||
|
|
||||||
import { FakeInput, InputLabelWrapper } from './style';
|
import { FakeInput, InputLabelWrapper, ChildrenWrapper } from './style';
|
||||||
import { VariableElement } from './types';
|
import { VariableElement } from './types';
|
||||||
import { processStepWithExecutions } from './data';
|
import { processStepWithExecutions } from './data';
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ type PowerInputProps = {
|
|||||||
docUrl?: string;
|
docUrl?: string;
|
||||||
clickToCopy?: boolean;
|
clickToCopy?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
shouldUnregister?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PowerInput = (props: PowerInputProps) => {
|
const PowerInput = (props: PowerInputProps) => {
|
||||||
@@ -46,6 +47,7 @@ const PowerInput = (props: PowerInputProps) => {
|
|||||||
required,
|
required,
|
||||||
description,
|
description,
|
||||||
disabled,
|
disabled,
|
||||||
|
shouldUnregister,
|
||||||
} = props;
|
} = props;
|
||||||
const priorStepsWithExecutions = React.useContext(StepExecutionsContext);
|
const priorStepsWithExecutions = React.useContext(StepExecutionsContext);
|
||||||
const editorRef = React.useRef<HTMLDivElement | null>(null);
|
const editorRef = React.useRef<HTMLDivElement | null>(null);
|
||||||
@@ -81,7 +83,7 @@ const PowerInput = (props: PowerInputProps) => {
|
|||||||
name={name}
|
name={name}
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
shouldUnregister={true}
|
shouldUnregister={shouldUnregister ?? true}
|
||||||
render={({
|
render={({
|
||||||
field: {
|
field: {
|
||||||
value,
|
value,
|
||||||
@@ -103,7 +105,7 @@ const PowerInput = (props: PowerInputProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* ref-able single child for ClickAwayListener */}
|
{/* ref-able single child for ClickAwayListener */}
|
||||||
<div style={{ width: '100%' }} data-test="power-input">
|
<ChildrenWrapper style={{ width: '100%' }} data-test="power-input">
|
||||||
<FakeInput disabled={disabled}>
|
<FakeInput disabled={disabled}>
|
||||||
<InputLabelWrapper>
|
<InputLabelWrapper>
|
||||||
<InputLabel
|
<InputLabel
|
||||||
@@ -140,7 +142,7 @@ const PowerInput = (props: PowerInputProps) => {
|
|||||||
data={stepsWithVariables}
|
data={stepsWithVariables}
|
||||||
onSuggestionClick={handleVariableSuggestionClick}
|
onSuggestionClick={handleVariableSuggestionClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</ChildrenWrapper>
|
||||||
</ClickAwayListener>
|
</ClickAwayListener>
|
||||||
</Slate>
|
</Slate>
|
||||||
)}
|
)}
|
||||||
|
@@ -1,5 +1,12 @@
|
|||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
|
|
||||||
|
export const ChildrenWrapper = styled('div')`
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
hyphens: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
export const InputLabelWrapper = styled('div')`
|
export const InputLabelWrapper = styled('div')`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: ${({ theme }) => theme.spacing(1.75)};
|
left: ${({ theme }) => theme.spacing(1.75)};
|
||||||
@@ -9,7 +16,7 @@ export const InputLabelWrapper = styled('div')`
|
|||||||
|
|
||||||
export const FakeInput = styled('div', {
|
export const FakeInput = styled('div', {
|
||||||
shouldForwardProp: (prop) => prop !== 'disabled',
|
shouldForwardProp: (prop) => prop !== 'disabled',
|
||||||
})<{ disabled?: boolean }>`
|
}) <{ disabled?: boolean }>`
|
||||||
border: 1px solid #eee;
|
border: 1px solid #eee;
|
||||||
min-height: 52px;
|
min-height: 52px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@@ -110,6 +110,7 @@ export const GET_APPS = gql`
|
|||||||
description
|
description
|
||||||
variables
|
variables
|
||||||
dependsOn
|
dependsOn
|
||||||
|
value
|
||||||
options {
|
options {
|
||||||
label
|
label
|
||||||
value
|
value
|
||||||
@@ -130,6 +131,36 @@ export const GET_APPS = gql`
|
|||||||
value
|
value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fields {
|
||||||
|
label
|
||||||
|
key
|
||||||
|
type
|
||||||
|
required
|
||||||
|
description
|
||||||
|
variables
|
||||||
|
value
|
||||||
|
dependsOn
|
||||||
|
options {
|
||||||
|
label
|
||||||
|
value
|
||||||
|
}
|
||||||
|
source {
|
||||||
|
type
|
||||||
|
name
|
||||||
|
arguments {
|
||||||
|
name
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
additionalFields {
|
||||||
|
type
|
||||||
|
name
|
||||||
|
arguments {
|
||||||
|
name
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user