formik#FieldArray TypeScript Examples
The following examples show how to use
formik#FieldArray.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: MacIpMapping.tsx From assisted-ui-lib with Apache License 2.0 | 6 votes |
MacIpMapping: React.FC<{
fieldName: string;
macInterfaceMap: MacInterfaceMap;
hostIdx: number;
}> = ({ fieldName, macInterfaceMap, hostIdx }) => {
return (
<Grid className="mac-ip-mapping">
<GridItem span={6}>
<FieldArray
name={fieldName}
validateOnChange={false}
render={({ push, remove }) => (
<Grid hasGutter>
{macInterfaceMap.map((value, idx) => (
<MacMappingItem
key={getFormikArrayItemFieldName(fieldName, idx)}
fieldName={getFormikArrayItemFieldName(fieldName, idx)}
onRemove={() => remove(idx)}
mapIdx={idx}
enableRemove={idx > 0}
hostIdx={hostIdx}
/>
))}
<AddMapping onPush={push} />
</Grid>
)}
></FieldArray>
</GridItem>
</Grid>
);
}
Example #2
Source File: StaticIpHostsArray.tsx From assisted-ui-lib with Apache License 2.0 | 6 votes |
StaticIpHostsArray = <HostFieldType,>({ ...props }: HostArrayProps<HostFieldType>) => {
const renderHosts = React.useCallback(
(arrayRenderProps: FieldArrayRenderProps) => (
<Hosts {...Object.assign(props, arrayRenderProps)} />
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
return <FieldArray name={fieldName} render={renderHosts} />;
}
Example #3
Source File: OptionSet.tsx From amplication with Apache License 2.0 | 6 votes |
OptionSet = ({ name, label }: Props) => {
return (
<div className={CLASS_NAME}>
<FieldArray
name={name}
validateOnChange
render={(props) => <OptionSetOptions {...props} label={label} />}
/>
</div>
);
}
Example #4
Source File: TangServers.tsx From assisted-ui-lib with Apache License 2.0 | 5 votes |
TangServers: React.FC<{ isDisabled?: boolean }> = ({ isDisabled = false }) => {
const { values } = useFormikContext<ClusterDetailsValues>();
return (
<FieldArray name="diskEncryptionTangServers">
{({ remove, push }) => (
<Stack hasGutter>
{values.diskEncryptionTangServers.length > 0 &&
values.diskEncryptionTangServers.map((tang, index) => (
<StackItem key={index}>
<RemovableField
hideRemoveButton={index === 0 || isDisabled}
onRemove={() => remove(index)}
>
<div>
<InputField
type={TextInputTypes.url}
name={`diskEncryptionTangServers.${index}.url`}
placeholder="http//tang.srv"
label="Server URL"
isRequired
isDisabled={isDisabled}
/>
 
<InputField
type={TextInputTypes.text}
name={`diskEncryptionTangServers.${index}.thumbprint`}
label="Server Thumbprint"
isRequired
isDisabled={isDisabled}
/>
</div>
</RemovableField>{' '}
</StackItem>
))}
{!isDisabled && (
<StackItem>
<AddButton onAdd={() => push({ url: '', thumbprint: '' })} isInline>
Add another Tang server
</AddButton>
</StackItem>
)}
</Stack>
)}
</FieldArray>
);
}
Example #5
Source File: TextArrayField.tsx From netify with BSD 2-Clause "Simplified" License | 5 votes |
TextArrayField = memo<TextArrayFieldProps>(function TextArrayField(props) {
const {name, placeholder, addControlTitle = 'Add new one', removeControlTitle = 'Remove item'} = props;
return (
<ul className={styles.root}>
<FieldArray
name={name}
render={helpers => {
const list = getIn(helpers.form.values, name);
return list.map((_: any, index: number) => (
// eslint-disable-next-line react/no-array-index-key
<li key={index} className={styles.item}>
<div className={styles.entry}>
<TextField
className={styles.field}
name={`${name}[${index}]`}
placeholder={placeholder}
/>
{index === list.length - 1 ? (
<IconButton
className={styles.control}
icon={<AddIcon />}
tooltip={addControlTitle}
onClick={() => helpers.push('')}
/>
) : (
<IconButton
className={styles.control}
icon={<RemoveIcon />}
tooltip={removeControlTitle}
onClick={() => helpers.remove(index)}
/>
)}
</div>
<FieldError name={`${name}[${index}]`} />
</li>
));
}}
/>
</ul>
);
})
Example #6
Source File: KeyValueArrayField.tsx From netify with BSD 2-Clause "Simplified" License | 5 votes |
KeyValueArrayField = memo<KeyValueArrayFieldProps>(function KeyValueArrayField(props) {
const {
name,
keyNameSuffix,
valueNameSuffix,
keyPlaceholder,
valuePlaceholder,
addControlTitle = 'Add new one',
removeControlTitle = 'Remove item',
} = props;
return (
<ul className={styles.root}>
<FieldArray
name={name}
render={helpers => {
const list = getIn(helpers.form.values, name);
return list.map((_: any, index: number) => (
// eslint-disable-next-line react/no-array-index-key
<li key={index} className={styles.item}>
<div className={styles.entry}>
<TextField
className={styles.field}
name={`${name}[${index}].${keyNameSuffix}`}
placeholder={keyPlaceholder}
/>
<TextField
className={styles.field}
name={`${name}[${index}].${valueNameSuffix}`}
placeholder={valuePlaceholder}
/>
{index === list.length - 1 ? (
<IconButton
className={styles.control}
icon={<AddIcon />}
tooltip={addControlTitle}
onClick={() => helpers.push({[keyNameSuffix]: '', [valueNameSuffix]: ''})}
/>
) : (
<IconButton
className={styles.control}
icon={<RemoveIcon />}
tooltip={removeControlTitle}
onClick={() => helpers.remove(index)}
/>
)}
</div>
<FieldError name={`${name}[${index}].${keyNameSuffix}`} />
<FieldError name={`${name}[${index}].${valueNameSuffix}`} />
</li>
));
}}
/>
</ul>
);
})
Example #7
Source File: UpdateSliceZoneModalList.tsx From slice-machine with Apache License 2.0 | 5 votes |
UpdateSliceZoneModalList: React.FC<{
availableSlices: ReadonlyArray<SliceState>;
values: SliceZoneFormValues;
}> = ({ availableSlices, values }) => (
<FieldArray
name="sliceKeys"
render={(arrayHelpers) => (
<Grid
gridTemplateMinPx="200px"
elems={availableSlices}
defineElementKey={(slice: SliceState) => slice.model.name}
renderElem={(slice: SliceState) => {
return SharedSlice.render({
bordered: true,
displayStatus: false,
thumbnailHeightPx: "220px",
slice,
Wrapper: ({
slice,
children,
}: {
slice: SliceState;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
children: any;
}) => {
return (
<div
data-testid="slicezone-modal-item"
style={{ cursor: "pointer" }}
onClick={() => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
const isInSliceZone = values.sliceKeys.includes(
slice.model.id
);
if (isInSliceZone) {
return arrayHelpers.remove(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
values.sliceKeys.indexOf(slice.model.id)
);
}
arrayHelpers.push(slice.model.id);
}}
key={`${slice.from}-${slice.model.name}`}
>
{children}
</div>
);
},
CustomStatus: ({ slice }: { slice: SliceState }) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
const isInSliceZone = values.sliceKeys.includes(slice.model.id);
return isInSliceZone ? (
<Checkbox value="true" defaultChecked />
) : (
<Checkbox value="false" />
);
},
});
}}
/>
)}
/>
)
Example #8
Source File: AvailableSubnetsControl.tsx From assisted-ui-lib with Apache License 2.0 | 5 votes |
AvailableSubnetsControl = ({
clusterId,
hostSubnets,
isRequired = false,
}: AvailableSubnetsControlProps) => {
const { values, errors, setFieldValue } = useFormikContext<NetworkConfigurationValues>();
const isDualStack = values.stackType === DUAL_STACK;
const IPv4Subnets = hostSubnets.filter((subnet) => Address4.isValid(subnet.subnet));
const IPv6Subnets = hostSubnets.filter((subnet) => Address6.isValid(subnet.subnet));
const hasNoMachineNetworks = (values.machineNetworks ?? []).length === 0;
const cidr = hostSubnets.length >= 1 ? hostSubnets[0].subnet : NO_SUBNET_SET;
useAutoSelectSingleAvailableSubnet(hasNoMachineNetworks, setFieldValue, cidr, clusterId);
return (
<FormGroup
label="Machine network"
labelInfo={isDualStack && 'Primary'}
fieldId="machine-networks"
isRequired
>
<FieldArray name="machineNetworks">
{() => (
<Stack>
{isDualStack ? (
values.machineNetworks?.map((_machineNetwork, index) => {
return (
<StackItem key={index}>
<SelectField
name={`machineNetworks.${index}.cidr`}
options={
(index === 1 ? IPv6Subnets.length > 0 : IPv4Subnets.length > 0)
? [makeNoSubnetSelectedOption(hostSubnets)].concat(
toFormSelectOptions(index === 1 ? IPv6Subnets : IPv4Subnets),
)
: [makeNoSubnetAvailableOption()]
}
isDisabled={!hostSubnets.length}
isRequired={isRequired}
/>
</StackItem>
);
})
) : (
<StackItem>
<SelectField
isDisabled={!hostSubnets.length}
isRequired={isRequired}
name={`machineNetworks.0.cidr`}
options={
IPv4Subnets.length === 0
? [makeNoSubnetAvailableOption()]
: [makeNoSubnetSelectedOption(hostSubnets)].concat(
toFormSelectOptions(IPv4Subnets),
)
}
/>
</StackItem>
)}
</Stack>
)}
</FieldArray>
{typeof errors.machineNetworks === 'string' && (
<Alert variant={AlertVariant.warning} title={errors.machineNetworks} isInline />
)}
</FormGroup>
);
}
Example #9
Source File: BMCForm.tsx From assisted-ui-lib with Apache License 2.0 | 5 votes |
MacMapping = () => {
const [field, { touched, error }] = useField<{ macAddress: string; name: string }[]>({
name: 'macMapping',
});
const { errors } = useFormikContext();
const fieldId = getFieldId('macMapping', 'input');
const isValid = !(touched && error);
return (
<FormGroup
fieldId={fieldId}
label="Mac to interface name mapping"
validated={isValid ? 'default' : 'error'}
>
<FieldArray
name="macMapping"
render={({ push, remove }) => (
<Grid hasGutter>
<GridItem span={5}>
<Text component={TextVariants.small}>MAC address</Text>
</GridItem>
<GridItem span={5}>
<Text component={TextVariants.small}>NIC</Text>
</GridItem>
{field.value.map((mac, index) => {
const macField = `macMapping[${index}].macAddress`;
const nameField = `macMapping[${index}].name`;
return (
<React.Fragment key={index}>
<GridItem span={5}>
<InputField name={macField} inputError={errors[macField]?.[0]} />
</GridItem>
<GridItem span={5}>
<InputField name={nameField} inputError={errors[nameField]?.[0]} />
</GridItem>
{index !== 0 && (
<GridItem span={2}>
<MinusCircleIcon onClick={() => remove(index)} />
</GridItem>
)}
</React.Fragment>
);
})}
<GridItem span={6}>
<Button
icon={<PlusCircleIcon />}
onClick={() => push({ macAddress: '', name: '' })}
variant="link"
isInline
>
Add more
</Button>
</GridItem>
</Grid>
)}
/>
</FormGroup>
);
}
Example #10
Source File: configure.tsx From dendron with GNU Affero General Public License v3.0 | 5 votes |
export default function Config({
engine,
}: {
engine: engineSlice.EngineState;
}) {
const logger = createLogger("Config");
if (!engine.config) {
return <></>;
}
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 8 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
return (
<Formik initialValues={engine.config} onSubmit={() => {}}>
{({ handleSubmit, handleChange, handleBlur, values, errors }) => (
<Form {...formItemLayout}>
<Typography>
<Title>Publishing </Title>
</Typography>
<Form.Item name="siteHierarchies" label="Site Hierarchies">
<FieldArray
name="siteHierarchies"
render={(arrayHelpers) => {
const publishingConfig = ConfigUtils.getPublishingConfig(
values as IntermediateDendronConfig
);
return renderArray(
publishingConfig.siteHierarchies,
arrayHelpers
);
}}
/>
</Form.Item>
{createFormItem({ name: "site.siteRootDir", label: "Site Root Dir" })}
</Form>
)}
</Formik>
);
}
Example #11
Source File: SQFormInclusionList.tsx From SQForm with MIT License | 5 votes |
function SQFormInclusionList({
name,
children,
useSelectAll = false,
selectAllData = [],
selectAllContainerProps = {},
selectAllProps = {},
}: SQFormInclusionListProps): React.ReactElement {
const {values, setFieldValue} = useFormikContext<{selectAll: boolean}>();
const handleSelectAllChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
if (event.target.checked) {
setFieldValue(name, selectAllData);
} else {
setFieldValue(name, []);
}
setFieldValue('selectAll', !values?.selectAll);
};
if (!useSelectAll) {
return <FieldArray name={name}>{children}</FieldArray>;
}
return (
<>
<Grid container {...selectAllContainerProps}>
<SQFormInclusionListItem
label="Select All"
{...selectAllProps}
name="selectAll"
isChecked={values.selectAll}
onChange={handleSelectAllChange}
/>
</Grid>
<FieldArray name={name}>{children}</FieldArray>
</>
);
}
Example #12
Source File: DataDocScheduleForm.tsx From querybook with Apache License 2.0 | 4 votes |
ScheduleExportsForm: React.FC<{
docId: number;
exporters: IQueryResultExporter[];
}> = ({ docId, exporters }) => {
const name = 'kwargs.exports';
const { values, setFieldValue } = useFormikContext<IScheduleFormValues>();
const queryCellOptions = useSelector((state: IStoreState) =>
queryCellSelector(state, docId)
);
const exportsValues = values.kwargs.exports ?? [];
return (
<FieldArray
name={name}
render={(arrayHelpers) => {
const exportFields = exportsValues.map((exportConf, index) => {
const exportFormName = `${name}[${index}]`;
const cellPickerField = (
<SimpleField
label="Export Cell"
name={`${exportFormName}.exporter_cell_id`}
type="react-select"
options={queryCellOptions.map((val) => ({
value: val.id,
label: val.title,
}))}
withDeselect
/>
);
const exporter = exporters.find(
(exp) => exp.name === exportConf.exporter_name
);
const exporterPickerField = (
<SimpleField
label="Export with"
name={`${exportFormName}.exporter_name`}
type="react-select"
options={exporters.map((exp) => exp.name)}
onChange={(v) => {
setFieldValue(
`${exportFormName}.exporter_name`,
v
);
setFieldValue(
`${exportFormName}.exporter_params`,
exporter?.form
? getDefaultFormValue(exporter.form)
: {}
);
}}
/>
);
const exporterFormField = exporter?.form && (
<>
<FormSectionHeader>
Export Parameters
</FormSectionHeader>
<SmartForm
formField={exporter.form}
value={
values.kwargs.exports[index].exporter_params
}
onChange={(path, value) =>
setFieldValue(
`${exportFormName}.exporter_params`,
updateValue(
values.kwargs.exports[index]
.exporter_params,
path,
value,
[undefined, '']
)
)
}
/>
</>
);
return (
<div
key={index}
className="cell-export-field mb24 flex-row"
>
<div className="flex1 mr16">
{cellPickerField}
{exporterPickerField}
{exporterFormField}
</div>
<div>
<IconButton
icon="X"
onClick={() => arrayHelpers.remove(index)}
/>
</div>
</div>
);
});
const controlDOM = (
<div className="center-align mt8">
<SoftButton
icon="Plus"
title="New Query Cell Result Export"
onClick={() =>
arrayHelpers.push({
exporter_cell_id: null,
exporter_name: null,
exporter_params: {},
})
}
/>
</div>
);
return (
<div className="ScheduleExportsForm">
{exportFields}
{controlDOM}
</div>
);
}}
/>
);
}
Example #13
Source File: Metrics.tsx From abacus with GNU General Public License v2.0 | 4 votes |
EventEditor = ({
index,
completionBag: { eventCompletionDataSource },
exposureEvent: { event: name, props: propList },
onRemoveExposureEvent,
}: {
index: number
completionBag: ExperimentFormCompletionBag
exposureEvent: EventNew
onRemoveExposureEvent: () => void
}) => {
const classes = useEventEditorStyles()
const metricClasses = useMetricEditorStyles()
const { isLoading, data: propCompletions } = useDataSource(async () => name && getPropNameCompletions(name), [name])
return (
<TableRow>
<TableCell>
<div className={classes.exposureEventsEventNameCell}>
<Field
component={AbacusAutocomplete}
name={`experiment.exposureEvents[${index}].event`}
className={classes.exposureEventsEventName}
id={`experiment.exposureEvents[${index}].event`}
options={eventCompletionDataSource.data}
loading={eventCompletionDataSource.isLoading}
renderInput={(params: AutocompleteRenderInputParams) => (
<MuiTextField
{...params}
label='Event Name'
placeholder='event_name'
variant='outlined'
InputLabelProps={{
shrink: true,
}}
InputProps={{
...autocompleteInputProps(params, eventCompletionDataSource.isLoading),
'aria-label': 'Event Name',
}}
/>
)}
/>
<IconButton
className={classes.exposureEventsEventRemoveButton}
onClick={onRemoveExposureEvent}
aria-label='Remove exposure event'
>
<Clear />
</IconButton>
</div>
<FieldArray
name={`experiment.exposureEvents[${index}].props`}
render={(arrayHelpers) => {
const onAddExposureEventProperty = () => {
arrayHelpers.push({
key: '',
value: '',
})
}
return (
<div>
<div>
{propList &&
propList.map((_prop: unknown, propIndex: number) => {
const onRemoveExposureEventProperty = () => {
arrayHelpers.remove(propIndex)
}
return (
<div className={classes.exposureEventsEventPropertiesRow} key={propIndex}>
<Field
component={AbacusAutocomplete}
name={`experiment.exposureEvents[${index}].props[${propIndex}].key`}
id={`experiment.exposureEvents[${index}].props[${propIndex}].key`}
options={propCompletions || []}
loading={isLoading}
freeSolo={true}
className={classes.exposureEventsEventPropertiesKeyAutoComplete}
renderInput={(params: AutocompleteRenderInputParams) => (
<MuiTextField
{...params}
className={classes.exposureEventsEventPropertiesKey}
label='Key'
placeholder='key'
variant='outlined'
size='small'
InputProps={{
...autocompleteInputProps(params, isLoading),
'aria-label': 'Property Key',
}}
InputLabelProps={{
shrink: true,
}}
/>
)}
/>
<Field
component={TextField}
name={`experiment.exposureEvents[${index}].props[${propIndex}].value`}
id={`experiment.exposureEvents[${index}].props[${propIndex}].value`}
type='text'
variant='outlined'
placeholder='value'
label='Value'
size='small'
inputProps={{
'aria-label': 'Property Value',
}}
InputLabelProps={{
shrink: true,
}}
/>
<IconButton
className={classes.exposureEventsEventRemoveButton}
onClick={onRemoveExposureEventProperty}
aria-label='Remove exposure event property'
>
<Clear />
</IconButton>
</div>
)
})}
</div>
<div className={metricClasses.addMetric}>
<Add className={metricClasses.addMetricAddSymbol} />
<Button
variant='contained'
onClick={onAddExposureEventProperty}
disableElevation
size='small'
aria-label='Add Property'
>
Add Property
</Button>
</div>
</div>
)
}}
/>
</TableCell>
</TableRow>
)
}
Example #14
Source File: Metrics.tsx From abacus with GNU General Public License v2.0 | 4 votes |
Metrics = ({
indexedMetrics,
completionBag,
formikProps,
}: {
indexedMetrics: Record<number, Metric>
completionBag: ExperimentFormCompletionBag
formikProps: FormikProps<{ experiment: ExperimentFormData }>
}): JSX.Element => {
const classes = useStyles()
const metricEditorClasses = useMetricEditorStyles()
const decorationClasses = useDecorationStyles()
// Metric Assignments
const [metricAssignmentsField, _metricAssignmentsFieldMetaProps, metricAssignmentsFieldHelperProps] =
useField<MetricAssignment[]>('experiment.metricAssignments')
const [selectedMetric, setSelectedMetric] = useState<Metric | null>(null)
const onChangeSelectedMetricOption = (_event: unknown, value: Metric | null) => setSelectedMetric(value)
const makeMetricAssignmentPrimary = (indexToSet: number) => {
metricAssignmentsFieldHelperProps.setValue(
metricAssignmentsField.value.map((metricAssignment, index) => ({
...metricAssignment,
isPrimary: index === indexToSet,
})),
)
}
// This picks up the no metric assignments validation error
const metricAssignmentsError =
formikProps.touched.experiment?.metricAssignments &&
_.isString(formikProps.errors.experiment?.metricAssignments) &&
formikProps.errors.experiment?.metricAssignments
// ### Exposure Events
const [exposureEventsField, _exposureEventsFieldMetaProps, _exposureEventsFieldHelperProps] =
useField<EventNew[]>('experiment.exposureEvents')
return (
<div className={classes.root}>
<Typography variant='h4' gutterBottom>
Assign Metrics
</Typography>
<FieldArray
name='experiment.metricAssignments'
render={(arrayHelpers) => {
const onAddMetric = () => {
if (selectedMetric) {
const metricAssignment = createMetricAssignment(selectedMetric)
arrayHelpers.push({
...metricAssignment,
isPrimary: metricAssignmentsField.value.length === 0,
})
}
setSelectedMetric(null)
}
return (
<>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Metric</TableCell>
<TableCell>Attribution Window</TableCell>
<TableCell>Change Expected?</TableCell>
<TableCell>Minimum Practical Difference</TableCell>
<TableCell />
</TableRow>
</TableHead>
<TableBody>
{metricAssignmentsField.value.map((metricAssignment, index) => {
const onRemoveMetricAssignment = () => {
arrayHelpers.remove(index)
}
const onMakePrimary = () => {
makeMetricAssignmentPrimary(index)
}
const attributionWindowError =
(_.get(
formikProps.touched,
`experiment.metricAssignments[${index}].attributionWindowSeconds`,
) as boolean | undefined) &&
(_.get(
formikProps.errors,
`experiment.metricAssignments[${index}].attributionWindowSeconds`,
) as string | undefined)
return (
<TableRow key={index}>
<TableCell className={classes.metricNameCell}>
<Tooltip arrow title={indexedMetrics[metricAssignment.metricId].description}>
<span className={clsx(classes.metricName, decorationClasses.tooltipped)}>
{indexedMetrics[metricAssignment.metricId].name}
</span>
</Tooltip>
<br />
{metricAssignment.isPrimary && <Attribute name='primary' className={classes.monospaced} />}
</TableCell>
<TableCell>
<Field
className={classes.attributionWindowSelect}
component={Select}
name={`experiment.metricAssignments[${index}].attributionWindowSeconds`}
labelId={`experiment.metricAssignments[${index}].attributionWindowSeconds`}
size='small'
variant='outlined'
autoWidth
displayEmpty
error={!!attributionWindowError}
SelectDisplayProps={{
'aria-label': 'Attribution Window',
}}
>
<MenuItem value=''>-</MenuItem>
{Object.entries(AttributionWindowSecondsToHuman).map(
([attributionWindowSeconds, attributionWindowSecondsHuman]) => (
<MenuItem value={attributionWindowSeconds} key={attributionWindowSeconds}>
{attributionWindowSecondsHuman}
</MenuItem>
),
)}
</Field>
{_.isString(attributionWindowError) && (
<FormHelperText error>{attributionWindowError}</FormHelperText>
)}
</TableCell>
<TableCell className={classes.changeExpected}>
<Field
component={Switch}
name={`experiment.metricAssignments[${index}].changeExpected`}
id={`experiment.metricAssignments[${index}].changeExpected`}
type='checkbox'
aria-label='Change Expected'
variant='outlined'
/>
</TableCell>
<TableCell>
<MetricDifferenceField
className={classes.minDifferenceField}
name={`experiment.metricAssignments[${index}].minDifference`}
id={`experiment.metricAssignments[${index}].minDifference`}
metricParameterType={indexedMetrics[metricAssignment.metricId].parameterType}
/>
</TableCell>
<TableCell>
<MoreMenu>
<MenuItem onClick={onMakePrimary}>Set as Primary</MenuItem>
<MenuItem onClick={onRemoveMetricAssignment}>Remove</MenuItem>
</MoreMenu>
</TableCell>
</TableRow>
)
})}
{metricAssignmentsField.value.length === 0 && (
<TableRow>
<TableCell colSpan={5}>
<Typography variant='body1' align='center'>
You don't have any metric assignments yet.
</Typography>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
<div className={metricEditorClasses.addMetric}>
<Add className={metricEditorClasses.addMetricAddSymbol} />
<FormControl className={classes.addMetricSelect}>
<MetricAutocomplete
id='add-metric-select'
value={selectedMetric}
onChange={onChangeSelectedMetricOption}
options={Object.values(indexedMetrics)}
error={metricAssignmentsError}
fullWidth
/>
</FormControl>
<Button variant='contained' disableElevation size='small' onClick={onAddMetric} aria-label='Add metric'>
Assign
</Button>
</div>
</>
)
}}
/>
<Alert severity='info' className={classes.metricsInfo}>
<Link
underline='always'
href="https://github.com/Automattic/experimentation-platform/wiki/Experimenter's-Guide#how-do-i-choose-a-primary-metric"
target='_blank'
>
How do I choose a Primary Metric?
</Link>
<Link
underline='always'
href="https://github.com/Automattic/experimentation-platform/wiki/Experimenter's-Guide#what-does-change-expected-mean-for-a-metric"
target='_blank'
>
What is Change Expected?
</Link>
</Alert>
<CollapsibleAlert
id='attr-window-panel'
severity='info'
className={classes.attributionWindowInfo}
summary={'What is an Attribution Window?'}
>
<Link
underline='always'
href="https://github.com/Automattic/experimentation-platform/wiki/Experimenter's-Guide#what-is-an-attribution-window-for-a-metric"
target='_blank'
>
An Attribution Window
</Link>{' '}
is the window of time after exposure to an experiment that we capture metric events for a participant (exposure
can be from either assignment or specified exposure events). The refund window is the window of time after a
purchase event. Revenue metrics will automatically deduct transactions that have been refunded within the
metric’s refund window.
<br />
<div className={classes.attributionWindowDiagram}>
<AttributionWindowDiagram />
<RefundWindowDiagram />
</div>
</CollapsibleAlert>
<CollapsibleAlert
id='min-diff-panel'
severity='info'
className={classes.minDiffInfo}
summary={'How do I choose a Minimum Difference?'}
>
<Link
underline='always'
href="https://github.com/Automattic/experimentation-platform/wiki/Experimenter's-Guide#how-do-i-choose-a-minimum-difference-practically-equivalent-value-for-my-metrics"
target='_blank'
>
Minimum Practical Difference values
</Link>{' '}
are absolute differences from the baseline (not relative). For example, if the baseline conversion rate is 5%, a
minimum difference of 0.5 pp is equivalent to a 10% relative change.
<br />
<div className={classes.minDiffDiagram}>
<MinDiffDiagram />
</div>
</CollapsibleAlert>
<Alert severity='info' className={classes.requestMetricInfo}>
<Link underline='always' href='https://betterexperiments.wordpress.com/?start=metric-request' target='_blank'>
{"Can't find a metric? Request one!"}
</Link>
</Alert>
<Typography variant='h4' className={classes.exposureEventsTitle}>
Exposure Events
</Typography>
<FieldArray
name='experiment.exposureEvents'
render={(arrayHelpers) => {
const onAddExposureEvent = () => {
arrayHelpers.push({
event: '',
props: [],
})
}
return (
<>
<TableContainer>
<Table>
<TableBody>
{exposureEventsField.value.map((exposureEvent, index) => (
<EventEditor
key={index}
{...{ arrayHelpers, index, classes, completionBag, exposureEvent }}
onRemoveExposureEvent={() => arrayHelpers.remove(index)}
/>
))}
{exposureEventsField.value.length === 0 && (
<TableRow>
<TableCell colSpan={1}>
<Typography variant='body1' align='center'>
You don't have any exposure events.
{}
<br />
{}
We strongly suggest considering adding one to improve the accuracy of your metrics.
</Typography>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
<div className={metricEditorClasses.addMetric}>
<Add className={metricEditorClasses.addMetricAddSymbol} />
<Button
variant='contained'
disableElevation
size='small'
onClick={onAddExposureEvent}
aria-label='Add exposure event'
>
Add Event
</Button>
</div>
</>
)
}}
/>
<Alert severity='info' className={classes.exposureEventsInfo}>
<Link
underline='always'
href="https://github.com/Automattic/experimentation-platform/wiki/Experimenter's-Guide#what-is-an-exposure-event-and-when-do-i-need-it"
target='_blank'
>
What is an Exposure Event? And when do I need it?
</Link>
<br />
<span>Only validated events can be used as exposure events.</span>
</Alert>
<Alert severity='info' className={classes.multipleExposureEventsInfo}>
If you have multiple exposure events, then participants will be considered exposed if they trigger{' '}
<strong>any</strong> of the exposure events.
</Alert>
</div>
)
}
Example #15
Source File: OrderForm.tsx From mayoor with MIT License | 4 votes |
OrderForm: React.FC<Props> = (props) => {
const { t } = useTranslation();
const { currencyFormatter } = useCurrencyFormatter();
return (
<Formik<OrderFormValues>
initialValues={props.initialValues}
onSubmit={async (values, { resetForm }) => {
await props.onSubmit(values, resetForm);
}}
validationSchema={getOrderValidationSchema(t)}
enableReinitialize
>
{({ handleSubmit, values, handleChange, setFieldValue }) => (
<StyledForm onSubmit={handleSubmit}>
<Row gutter={8}>
<Col lg={4}>
<StyledOrderNumberWrapper>
<FormInput
name="number"
label={t('Order number')}
icon={<NumberOutlined />}
withLabel
type="number"
disabled={!props.isNumberEditable}
/>
</StyledOrderNumberWrapper>
</Col>
<Col lg={7}>
<CustomerPicker extraCustomer={props.extraCustomer} />
</Col>
<Col lg={6}>
<OrderStatusSelect />
</Col>
</Row>
<StyledDivider />
<Row gutter={6}>
<Col sm={4}>
<StyledLabel>{t('Material')}</StyledLabel>
</Col>
<Col sm={7}>
<StyledLabel>{t('Name')}</StyledLabel>
</Col>
<Col sm={2}>
<StyledLabel>{t('Width')}</StyledLabel>
</Col>
<Col sm={2}>
<StyledLabel>{t('Height')}</StyledLabel>
</Col>
<Col sm={2}>
<StyledLabel>{t('Pieces')}</StyledLabel>
</Col>
<Col sm={1}></Col>
<Col sm={3}>
<StyledLabel>{t('Price')}</StyledLabel>
</Col>
<Col sm={3}>
<StyledLabel>{t('Tax')}</StyledLabel>
</Col>
</Row>
<FieldArray
name="items"
render={(arrayHelpers) => (
<>
{values.items.length > 0 &&
values.items.map((item, index) => (
<OrderItemField
key={item.id || index}
index={index}
arrayHelpers={arrayHelpers}
/>
))}
<Row>
<Col>
<Button
icon={<PlusCircleOutlined />}
onClick={() => arrayHelpers.push(dummyMaterialItem)}
>
{t('Add item')}
</Button>
</Col>
<Col style={{ textAlign: 'right' }}>
<Button
icon={<CalculatorOutlined />}
onClick={() => {
const { totalPrice, totalTax } = calculateSummary(
values,
);
setFieldValue('totalPrice', totalPrice);
setFieldValue('totalTax', totalTax);
}}
style={{ marginLeft: 10 }}
data-test-id="order-sum-items-button"
>
{t('Sum items')}
</Button>
</Col>
</Row>
</>
)}
/>
<Row gutter={8} style={{ marginTop: 15 }}>
<Col sm={10}>
<StyledFormItem>
<StyledLabel>{t('Note')}</StyledLabel>
<Input.TextArea
rows={4}
name="note"
placeholder={t('note_placeholder')}
onChange={handleChange}
data-test-id="order-form-note"
value={values.note || ''}
/>
</StyledFormItem>
</Col>
<Col sm={8}>
<Row>
<Col sm={18} offset={3}>
<UrgentSlider />
</Col>
</Row>
</Col>
<Col sm={6}>
<OrderSummaryWrapper>
<FormInput
name="totalPrice"
label={t('Total price')}
type="number"
suffix={CURRENCY_SUFFIX}
withLabel
/>
<FormInput
name="totalTax"
label={t('Total tax')}
type="number"
suffix={CURRENCY_SUFFIX}
withLabel
/>
{!!getTotalPriceIncludingTax(values) && (
<div>
<StyledLabel>{t('Total price including tax')}</StyledLabel>
<span>
{currencyFormatter(
getTotalPriceIncludingTax(values) || 0,
)}
</span>
</div>
)}
</OrderSummaryWrapper>
</Col>
</Row>
{props.submitButton}
</StyledForm>
)}
</Formik>
);
}
Example #16
Source File: ArrayContainer.tsx From firecms with MIT License | 4 votes |
/**
* @category Form custom fields
*/
export function ArrayContainer<T>({
name,
value,
disabled,
buildEntry,
onInternalIdAdded,
includeAddButton
}: ArrayContainerProps<T>) {
const hasValue = value && Array.isArray(value) && value.length > 0;
const internalIdsMap: Record<string, number> = useMemo(() =>
hasValue
? value.map((v, index) => {
if (!v) return {};
return ({
[getHashValue(v) + index]: getRandomId()
});
}).reduce((a, b) => ({ ...a, ...b }), {})
: {},
[value, hasValue]);
const internalIdsRef = useRef<Record<string, number>>(internalIdsMap);
const [internalIds, setInternalIds] = useState<number[]>(
hasValue
? Object.values(internalIdsRef.current)
: []);
useEffect(() => {
if (hasValue && value && value.length !== internalIds.length) {
const newInternalIds = value.map((v, index) => {
const hashValue = getHashValue(v) + index;
if (hashValue in internalIdsRef.current) {
return internalIdsRef.current[hashValue];
} else {
const newInternalId = getRandomId();
internalIdsRef.current[hashValue] = newInternalId;
return newInternalId;
}
});
setInternalIds(newInternalIds);
}
}, [hasValue, internalIds.length, value]);
return <FieldArray
name={name}
validateOnChange={true}
render={arrayHelpers => {
const insertInEnd = () => {
if (disabled) return;
const id = getRandomId();
const newIds: number[] = [...internalIds, id];
if (onInternalIdAdded)
onInternalIdAdded(id);
setInternalIds(newIds);
arrayHelpers.push(null);
};
const remove = (index: number) => {
const newValue = [...internalIds];
newValue.splice(index, 1);
setInternalIds(newValue);
arrayHelpers.remove(index);
};
const onDragEnd = (result: any) => {
// dropped outside the list
if (!result.destination) {
return;
}
const sourceIndex = result.source.index;
const destinationIndex = result.destination.index;
const newIds = [...internalIds];
const temp = newIds[sourceIndex];
newIds[sourceIndex] = newIds[destinationIndex];
newIds[destinationIndex] = temp;
setInternalIds(newIds);
arrayHelpers.move(sourceIndex, destinationIndex);
}
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId={`droppable_${name}`}>
{(droppableProvided, droppableSnapshot) => (
<div
{...droppableProvided.droppableProps}
ref={droppableProvided.innerRef}>
{hasValue && internalIds.map((internalId: number, index: number) => {
return (
<Draggable
key={`array_field_${name}_${internalId}}`}
draggableId={`array_field_${name}_${internalId}}`}
isDragDisabled={disabled}
index={index}>
{(provided, snapshot) => (
<Box
ref={provided.innerRef}
{...provided.draggableProps}
style={
provided.draggableProps.style
}
sx={{
marginBottom: 1,
borderRadius: "4px",
opacity: 1
}}
>
<Box key={`field_${index}`}
display="flex">
<Box flexGrow={1}
width={"100%"}
key={`field_${name}_entryValue`}>
{buildEntry(index, internalId)}
</Box>
<Box width={"36px"}
display="flex"
flexDirection="column"
alignItems="center">
<div
{...provided.dragHandleProps}>
<DragHandleIcon
fontSize={"small"}
color={disabled ? "disabled" : "inherit"}
sx={{ cursor: disabled ? "inherit" : "move" }}/>
</div>
{!disabled &&
<IconButton
size="small"
aria-label="remove"
onClick={() => remove(index)}>
<ClearIcon
fontSize={"small"}/>
</IconButton>}
</Box>
</Box>
</Box>
)}
</Draggable>);
})}
{droppableProvided.placeholder}
{includeAddButton && !disabled && <Box p={1}
justifyContent="center"
textAlign={"left"}>
<Button variant="outlined"
color="primary"
disabled={disabled}
onClick={insertInEnd}>
Add
</Button>
</Box>}
</div>
)}
</Droppable>
</DragDropContext>
);
}}
/>;
}
Example #17
Source File: UDFForm.tsx From querybook with Apache License 2.0 | 4 votes |
UDFForm: React.FC<IUDFFormProps> = ({
onConfirm,
engineLanguage,
}) => {
const engineUDFConfig = UDFEngineConfigsByLanguage[engineLanguage];
const languageOptions: IOptions<string> = useMemo(
() =>
engineUDFConfig.supportedUDFLanguages.map((languageConfig) => ({
label: languageConfig.displayName ?? languageConfig.name,
value: languageConfig.name,
})),
[engineUDFConfig]
);
const initialValues: IUDFRendererValues = useMemo(
() => ({
functionName: '',
udfLanguage: languageOptions[0].value,
outputType: '',
parameters: [],
script: '',
...engineUDFConfig.prefills,
}),
[languageOptions, engineUDFConfig]
);
return (
<div className="UDFForm">
<Formik
validateOnMount
initialValues={initialValues}
onSubmit={(formValues: IUDFRendererValues) => {
onConfirm(engineUDFConfig.renderer(formValues));
}}
validationSchema={UDFFormValuesSchema}
>
{({ values, handleSubmit, isValid }) => {
const selectedLanguageConfig = engineUDFConfig.supportedUDFLanguages.find(
(l) => l.name === values.udfLanguage
);
const parametersDOM = selectedLanguageConfig?.noParameters ? null : (
<FieldArray
name="parameters"
render={(arrayHelper) => {
const parameterRowsDOM = values.parameters.map(
(_, idx) => (
<div key={idx} className="flex-row">
<div className="flex4">
<SimpleField
type="input"
name={`parameters[${idx}].name`}
label={() => null}
/>
</div>
<div className="flex4">
<SimpleField
type="react-select"
label={() => null}
name={`parameters[${idx}].type`}
options={
engineUDFConfig.dataTypes
}
selectProps={{
placeholder:
'Select types',
}}
creatable
/>
</div>
<div className="flex1">
<IconButton
icon="X"
onClick={() =>
arrayHelper.remove(idx)
}
/>
</div>
</div>
)
);
return (
<div className="UDFForm-parameters">
<Title size="smedium">Parameters</Title>
<div className="flex-row">
<Subtitle className="flex4 ml16">
Name
</Subtitle>
<Subtitle className="flex4 ml16">
Type
</Subtitle>
<div className="flex1" />
</div>
{parameterRowsDOM}
<div className="center-align">
<SoftButton
size="small"
title="Add New Parameter"
icon="Plus"
onClick={() =>
arrayHelper.push({
name: '',
type: '',
})
}
/>
</div>
</div>
);
}}
/>
);
return (
<>
{engineUDFConfig.docs?.length > 0 && (
<Message type="tip">
<div>
<b>UDF Docs:</b>
</div>
{engineUDFConfig.docs.map((doc, idx) => (
<Link key={idx} to={doc.url}>
{doc.name ?? doc.url}
</Link>
))}
</Message>
)}
<SimpleField
name="functionName"
type="input"
label="Function Name *"
stacked
required
/>
<div className="flex-row">
<div className="flex1 mr4">
<SimpleField
type="react-select"
name="udfLanguage"
label="Language *"
options={languageOptions}
stacked
/>
</div>
<div className="flex1 ml4">
{selectedLanguageConfig?.noOutputType ? null : (
<SimpleField
label="Output Type"
stacked
name="outputType"
type="react-select"
creatable
options={engineUDFConfig.dataTypes}
selectProps={{
placeholder: 'Select types',
}}
/>
)}
</div>
</div>
{parametersDOM}
<SimpleField
type="code-editor"
name="script"
label="Code *"
mode={
selectedLanguageConfig?.codeEditorMode ??
'sql'
}
stacked
required
/>
<div className="right-align">
<Button
onClick={() => handleSubmit()}
disabled={!isValid}
title="Submit"
/>
</div>
</>
);
}}
</Formik>
</div>
);
}
Example #18
Source File: TaskEditor.tsx From querybook with Apache License 2.0 | 4 votes |
TaskEditor: React.FunctionComponent<IProps> = ({
task,
onTaskUpdate,
onTaskDelete,
onTaskCreate,
}) => {
const [tab, setTab] = React.useState<TaskEditorTabs>('edit');
const { data: registeredTaskList } = useResource(
TaskScheduleResource.getRegisteredTasks
);
const { data: registeredTaskParamList } = useResource(
TaskScheduleResource.getRegisteredTaskParams
);
React.useEffect(() => {
setTab('edit');
}, [task.id]);
const runTask = React.useCallback(async () => {
await TaskScheduleResource.run(task.id);
toast.success('Task has started!');
onTaskUpdate?.();
}, [task]);
const handleTaskEditSubmit = React.useCallback(
(editedValues) => {
const editedCron = editedValues.isCron
? editedValues.cron
: recurrenceToCron(editedValues.recurrence);
const editedArgs = editedValues.args
.filter((arg) => !(arg === ''))
.map(stringToTypedVal);
const editedKwargs = {};
if (editedValues.kwargs.length) {
for (const kwarg of editedValues.kwargs) {
if (
kwarg[0].length &&
!Object.keys(editedKwargs).includes(kwarg[0]) &&
kwarg[1] != null &&
kwarg[1] !== ''
) {
editedKwargs[kwarg[0]] = stringToTypedVal(kwarg[1]);
}
}
}
if (task.id) {
TaskScheduleResource.update(task.id, {
cron: editedCron,
enabled: editedValues.enabled,
args: editedArgs,
kwargs: editedKwargs,
}).then(() => {
toast.success('Task saved!');
onTaskUpdate?.();
});
} else {
toast.promise(
TaskScheduleResource.create({
cron: editedCron,
name: editedValues.name,
task: editedValues.task,
task_type: isTaskUserTask(editedValues.task),
enabled: editedValues.enabled,
args: editedArgs,
kwargs: editedKwargs,
}).then(({ data }) => {
onTaskCreate?.(data.id);
}),
{
loading: 'Creating task...',
success: 'Task created!',
error:
'Task creation failed - task name must be unique',
}
);
}
},
[task]
);
const handleDeleteTask = React.useCallback(() => {
sendConfirm({
header: `Delete ${task.name}?`,
message: 'Deleted tasks cannot be recovered.',
onConfirm: () => {
TaskScheduleResource.delete(task.id).then(() => {
toast.success('Task deleted!');
onTaskDelete?.();
});
},
});
}, [task]);
const formValues = React.useMemo(() => {
const cron = task.cron || '0 0 * * *';
const recurrence = cronToRecurrence(cron);
return {
name: task.name || '',
task: task.task || '',
isCron: !validateCronForRecurrrence(cron),
recurrence,
cron,
enabled: task.enabled ?? true,
args: task.args || [],
kwargs: Object.entries(task.kwargs || {}),
};
}, [task]);
const getEditDOM = (
values: typeof formValues,
errors,
setFieldValue,
isValid: boolean,
submitForm
) => {
const argsDOM = (
<FieldArray
name="args"
render={(arrayHelpers) => {
const fields = values.args.length
? values.args.map((ignore, index) => (
<div key={index} className="flex-row">
<FormField>
<FormFieldInputSection>
<Field
name={`args[${index}]`}
placeholder="Insert arg"
/>
</FormFieldInputSection>
</FormField>
<div>
<IconButton
icon="X"
onClick={() =>
arrayHelpers.remove(index)
}
/>
</div>
</div>
))
: null;
const controlDOM = (
<div className="mv4 ml12">
<SoftButton
title="Add New Arg"
onClick={() => arrayHelpers.push('')}
/>
</div>
);
return (
<div className="TaskEditor-args">
<FormField stacked label="Args">
<fieldset>{fields}</fieldset>
</FormField>
{controlDOM}
</div>
);
}}
/>
);
const getKwargPlaceholder = (param: string) =>
registeredTaskParamList?.[values.task]?.[param] ?? 'Insert value';
const kwargsDOM = (
<div className="TaskEditor-kwargs mt12">
<FormField stacked label="Kwargs">
<FieldArray
name="kwargs"
render={(arrayHelpers) => {
const fields = values.kwargs.length
? values.kwargs.map((ignore, index) => (
<div key={index} className="flex-row">
<FormField>
<FormFieldInputSection className="mr16">
<Field
name={`kwargs[${index}][0]`}
placeholder="Insert key"
/>
</FormFieldInputSection>
<FormFieldInputSection>
<Field
name={`kwargs[${index}][1]`}
placeholder={getKwargPlaceholder(
values.kwargs[
index
][0]
)}
/>
</FormFieldInputSection>
</FormField>
<div>
<IconButton
icon="X"
onClick={() =>
arrayHelpers.remove(index)
}
/>
</div>
</div>
))
: null;
const controlDOM = (
<div className="TaskEditor-kwarg-button mt8 ml4 mb16">
<SoftButton
title="Add New Kwarg"
onClick={() =>
arrayHelpers.push(['', ''])
}
/>
</div>
);
return (
<div>
<fieldset>{fields}</fieldset>
{controlDOM}
</div>
);
}}
/>
</FormField>
</div>
);
const canUseRecurrence = validateCronForRecurrrence(values.cron);
return (
<div className="TaskEditor-form">
<FormWrapper minLabelWidth="180px" size={7}>
<Form>
<div className="TaskEditor-form-fields">
{task.id ? null : (
<>
<SimpleField
label="Name"
type="input"
name="name"
inputProps={{
placeholder:
'A unique task name must be supplied',
}}
help="Task name must be unique"
/>
<SimpleField
label="Task"
type="react-select"
name="task"
options={registeredTaskList}
onChange={(val) => {
setFieldValue('args', [], false);
setFieldValue(
'kwargs',
Object.keys(
registeredTaskParamList[
val
] ?? {}
).map((key) => [key, '']),
false
);
setFieldValue('task', val, true);
}}
/>
</>
)}
{argsDOM}
{kwargsDOM}
<div className="TaskEditor-toggle">
<SimpleField
label="Enable Schedule"
type="toggle"
name="enabled"
/>
</div>
{values.enabled ? (
<div className="TaskEditor-schedule horizontal-space-between">
{values.isCron || !canUseRecurrence ? (
<SimpleField
label="Cron Schedule"
type="input"
name="cron"
/>
) : (
<FormField stacked label="Schedule">
<RecurrenceEditor
recurrence={values.recurrence}
recurrenceError={
errors?.recurrence
}
setRecurrence={(val) =>
setFieldValue(
'recurrence',
val
)
}
/>
</FormField>
)}
{canUseRecurrence ? (
<div className="TaskEditor-schedule-toggle mr16">
<ToggleButton
checked={values.isCron}
onClick={(val: boolean) => {
setFieldValue(
'isCron',
val
);
if (val) {
setFieldValue(
'cron',
recurrenceToCron(
values.recurrence
)
);
} else {
setFieldValue(
'recurrence',
cronToRecurrence(
values.cron
)
);
}
}}
title={
values.isCron
? 'Use Recurrence Editor'
: 'Use Cron'
}
/>
</div>
) : null}
</div>
) : null}
</div>
<div className="TaskEditor-form-controls right-align mt16">
{task.id ? (
<Button
disabled={!isValid}
onClick={handleDeleteTask}
title={'Delete Task'}
color="cancel"
icon="Trash"
/>
) : null}
<AsyncButton
icon="Save"
color="accent"
disabled={!isValid}
onClick={submitForm}
title={task.id ? 'Update Task' : 'Create Task'}
/>
</div>
</Form>
</FormWrapper>
</div>
);
};
return (
<div className="TaskEditor">
<Formik
validateOnMount
initialValues={formValues}
validationSchema={taskFormSchema}
onSubmit={handleTaskEditSubmit}
enableReinitialize
>
{({ values, errors, setFieldValue, isValid, submitForm }) => (
<>
{task.id ? (
<>
<div className="TaskEditor-top horizontal-space-between mb24">
<div className="TaskEditor-info">
<Title size="xlarge" weight="bold">
{values.name}
</Title>
<div className="mb16">
{values.task}
</div>
<div>
Last Run:{' '}
{generateFormattedDate(
task.last_run_at,
'X'
)}
,{' '}
{moment
.utc(task.last_run_at, 'X')
.fromNow()}
</div>
<div>
Total Run Count:{' '}
{task.total_run_count}
</div>
</div>
<div className="TaskEditor-controls vertical-space-between">
<AdminAuditLogButton
itemType="task"
itemId={task.id}
/>
<div className="TaskEditor-run">
<AsyncButton
title="Run Task"
icon="Play"
onClick={runTask}
/>
</div>
</div>
</div>
<Tabs
selectedTabKey={tab}
items={[
{ name: 'Edit', key: 'edit' },
{ name: 'History', key: 'history' },
]}
onSelect={(key: TaskEditorTabs) => {
setTab(key);
}}
/>
</>
) : null}
<div className="TaskEditor-content">
{tab === 'edit' ? (
getEditDOM(
values,
errors,
setFieldValue,
isValid,
submitForm
)
) : (
<div className="TaskEditor-history">
<TaskStatus
taskId={task.id}
taskName={task.name}
taskRunCount={task.total_run_count}
/>
</div>
)}
</div>
</>
)}
</Formik>
</div>
);
}
Example #19
Source File: TableUploaderSpecForm.tsx From querybook with Apache License 2.0 | 4 votes |
TableUploaderSpecForm: React.FC = ({}) => {
const {
values,
setFieldValue,
} = useFormikContext<ITableUploadFormikForm>();
const possibleMetastores = useMetastoresForUpload();
const possibleQueryEngines = useQueryEnginesForUpload(values.metastore_id);
const loadColumnTypes = useCallback(() => {
TableUploadResource.previewColumns({
import_config: values.import_config,
file: values.file,
}).then(({ data }) => {
setFieldValue('table_config.column_name_types', data);
});
}, [values.file, values.import_config, setFieldValue]);
useEffect(() => {
if (!values.auto_generated_column_types) {
loadColumnTypes();
setFieldValue('auto_generated_column_types', true);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [values.auto_generated_column_types, loadColumnTypes, setFieldValue]);
// Auto set query engine to be the first one if none is selected
useEffect(() => {
if (possibleQueryEngines.length === 0) {
return;
}
if (
possibleQueryEngines.find(
(engine) => engine.id === values.engine_id
)
) {
return;
}
setFieldValue('engine_id', possibleQueryEngines[0].id);
}, [possibleQueryEngines, values.engine_id, setFieldValue]);
return (
<div>
<div>
<StyledText color="light" size="smedium" weight="bold">
Required Fields
</StyledText>
</div>
<FormWrapper minLabelWidth="140px">
<SimpleField
name="metastore_id"
type="react-select"
label="Metastore"
options={possibleMetastores.map((store) => ({
label: store.name,
value: store.id,
}))}
/>
{values.metastore_id != null && (
<SimpleField
name="engine_id"
type="react-select"
label="Query Engine"
options={possibleQueryEngines.map((engine) => ({
label: engine.name,
value: engine.id,
}))}
/>
)}
<SimpleField name="table_config.schema_name" type="input" />
<SimpleField name="table_config.table_name" type="input" />
<SimpleField
name="table_config.if_exists"
type="react-select"
options={
(UploadedTableIfExistOptions as unknown) as string[]
}
help="Behavior if the table is already defined. Note: append only works for some databases"
/>
</FormWrapper>
<FieldArray
name="table_config.column_name_types"
render={() => {
const columnRowDOMs = values.table_config.column_name_types.map(
(_, idx) => (
<div key={idx} className="flex-row">
<div className="flex1">
<SimpleField
name={`table_config.column_name_types[${idx}][0]`}
label={() => null}
type="input"
/>
</div>
<div className="flex1">
<SimpleField
name={`table_config.column_name_types[${idx}][1]`}
label={() => null}
type="react-select"
options={
(UploadedTableColumnTypes as unknown) as string[]
}
creatable
withDeselect
/>
</div>
</div>
)
);
return (
<div className="mt20">
<div className="horizontal-space-between">
<StyledText
color="light"
size="smedium"
weight="bold"
>
Columns
</StyledText>
<Button
icon="RefreshCw"
title="Reset columns"
onClick={loadColumnTypes}
/>
</div>
<div className="mv4">
<StyledText
color="lightest"
weight="light"
size="small"
>
The types are auto-generated will be
converted to the applicable type for the
query engine. You can also provide your own
typing.
</StyledText>
</div>
{columnRowDOMs}
</div>
);
}}
/>
</div>
);
}
Example #20
Source File: DataTableViewSamples.tsx From querybook with Apache License 2.0 | 4 votes |
DataTableViewSamples: React.FunctionComponent<IDataTableViewSamplesProps> = ({
table,
tableColumns,
schema,
}) => {
// Hide options such as where / order by
const [showAdvancedOptions, _, toggleShowAdvancedOptions] = useToggleState(
false
);
// Used to display the raw query that will be used for samples
// only shown if view query is clicked
const [rawSamplesQuery, setRawSamplesQuery] = useState<string>(null);
const tablePartitions: string[] = useMemo(
() => JSON.parse(table.latest_partitions ?? '[]'),
[table.latest_partitions]
);
const dispatch: Dispatch = useDispatch();
const queryEngines = useSelector((state: IStoreState) => {
const queryEngineIds =
state.environment.environmentEngineIds[
state.environment.currentEnvironmentId
] ?? [];
return queryEngineIds
.map((engineId) => state.queryEngine.queryEngineById[engineId])
.filter((engine) => engine?.metastore_id === schema.metastore_id);
});
const loadDataTableSamples = React.useCallback(
async () =>
dispatch(
dataSourcesActions.fetchDataTableSamplesIfNeeded(table.id)
),
[dispatch, table.id]
);
const createDataTableSamples = React.useCallback(
async (tableId, engineId, params?: ITableSampleParams) =>
dispatch(
dataSourcesActions.createDataTableSamples(
tableId,
engineId,
params
)
),
[]
);
const getDataTableSamplesQuery = React.useCallback(
async (tableId, params: ITableSampleParams, language: string) => {
try {
const { data: query } = await TableSamplesResource.getQuery(
tableId,
params
);
setRawSamplesQuery(format(query, language));
} catch (error) {
if (isAxiosError(error)) {
const possibleErrorMessage = error?.response?.data?.error;
if (possibleErrorMessage) {
toast.error(
`Failed to generate query, reason: ${possibleErrorMessage}`
);
}
}
}
},
[]
);
const pollDataTableSamples = React.useCallback(
() => dispatch(dataSourcesActions.pollDataTableSample(table.id)),
[table.id]
);
const controlDOM = (
<div className="samples-control">
<Formik<ITableSamplesFormValues>
initialValues={{
engineId: queryEngines?.[0]?.id,
partition:
tablePartitions && tablePartitions.length > 0
? tablePartitions[tablePartitions.length - 1]
: null,
where: [['', '=', '']] as [[string, string, string]],
order_by: null,
order_by_asc: true,
}}
onSubmit={(values) =>
createDataTableSamples(
table.id,
values.engineId,
tableSamplesFormValuesToParams(values)
)
}
>
{({ submitForm, isSubmitting, values }) => {
const engineField = (
<SimpleField
label="Engine"
type="react-select"
name="engineId"
options={queryEngines.map((engine) => ({
value: engine.id,
label: engine.name,
}))}
/>
);
const partitionField = (
<SimpleField
type="react-select"
name="partition"
options={tablePartitions}
withDeselect
className="ml16"
/>
);
const whereClauseField = (
<FieldArray
name="where"
render={(arrayHelpers) => {
const whereDOM = values.where.map(
(whereClause, idx) => (
<div className="flex-row" key={idx}>
<div
style={{
flex: 3,
}}
>
<SimpleField
label={'Where'}
type="react-select"
name={`where[${idx}][0]`}
options={tableColumns.map(
(col) => col.name
)}
withDeselect
/>
</div>
<div style={{ flex: 1 }}>
<SimpleField
label=" "
type="select"
name={`where[${idx}][1]`}
options={COMPARSION_OPS}
/>
</div>
<div style={{ flex: 5 }}>
{COMPARSION_OPS_WITH_VALUE.includes(
whereClause[1]
) && (
<SimpleField
label=" "
type="input"
name={`where[${idx}][2]`}
/>
)}
</div>
<IconButton
icon="X"
onClick={() =>
arrayHelpers.remove(idx)
}
className="mt8"
/>
</div>
)
);
return (
<>
{whereDOM}
<div className="center-align add-where-clause mt16">
<Button
title="New Where Clause"
icon="Plus"
onClick={() =>
arrayHelpers.push([
'',
'=',
'',
])
}
/>
</div>
</>
);
}}
/>
);
const orderByField = (
<SimpleField
type="react-select"
name="order_by"
options={tableColumns.map((col) => col.name)}
withDeselect
/>
);
const orderByAscOrDescField = values.order_by != null && (
<SimpleField
label="Order"
type="react-select"
name="order_by_asc"
options={[
{
label: 'Ascending',
value: true,
},
{
label: 'Descending',
value: false,
},
]}
/>
);
const controlsField = (
<div className="DataTableViewSamples-button flex-row">
<ToggleButton
checked={showAdvancedOptions}
onClick={toggleShowAdvancedOptions}
title={
showAdvancedOptions
? 'Hide Advanced Options'
: 'Show Advanced Options'
}
/>
<AsyncButton
icon="Eye"
title="View Sample Query"
onClick={() =>
getDataTableSamplesQuery(
table.id,
tableSamplesFormValuesToParams(values),
queryEngines.find(
(engine) =>
values.engineId === engine.id
)?.language
)
}
/>
<AsyncButton
icon="Play"
color="accent"
title="Generate Samples"
onClick={submitForm}
disabled={isSubmitting}
/>
</div>
);
const formFields = showAdvancedOptions && (
<div className="DataTableViewSamples-form mb12">
<div className="DataTableViewSamples-top flex-row">
{engineField}
{partitionField}
</div>
<div className="DataTableViewSamples-mid">
{whereClauseField}
</div>
<div className="DataTableViewSamples-bottom">
<div className="flex-row">
{orderByField}
{orderByAscOrDescField}
</div>
</div>
</div>
);
return (
<div className="mb12">
{formFields}
{controlsField}
</div>
);
}}
</Formik>
</div>
);
return (
<div className="DataTableViewSamples">
{controlDOM}
<DataTableViewSamplesTable
tableId={table.id}
tableName={table.name}
loadDataTableSamples={loadDataTableSamples}
pollDataTableSamples={pollDataTableSamples}
/>
{rawSamplesQuery != null && (
<CopyPasteModal
text={rawSamplesQuery}
displayText
onHide={() => setRawSamplesQuery(null)}
/>
)}
</div>
);
}
Example #21
Source File: DataDocTemplateVarForm.tsx From querybook with Apache License 2.0 | 4 votes |
DataDocTemplateVarForm: React.FunctionComponent<IDataDocTemplateVarFormProps> = ({
onSave,
templatedVariables,
defaultTemplatedVariables = defaultTemplatedVariablesValue,
isEditable,
}) => {
const initialValue = useMemo(
() => ({
variables: Object.entries(
isEmpty(templatedVariables)
? defaultTemplatedVariables
: templatedVariables
).map(
([key, value]) =>
[key, detectVariableType(value), value] as const
),
}),
[defaultTemplatedVariables, templatedVariables]
);
return (
<Formik
enableReinitialize
validationSchema={templatedVarSchema}
initialValues={initialValue}
onSubmit={({ variables }) =>
onSave(
variables.reduce((hash, [name, _, value]) => {
hash[name] = value;
return hash;
}, {})
)
}
>
{({ handleSubmit, isSubmitting, isValid, values, dirty }) => {
const variablesField = (
<FieldArray
name="variables"
render={(arrayHelpers) => {
const fields = values.variables.length
? values.variables.map(
([_, valueType, __], index) => (
<div
key={index}
className="flex-row template-key-value-row"
>
<div className="flex-row-top flex1">
<SimpleField
label={() => null}
type="input"
name={`variables.${index}[0]`}
inputProps={{
placeholder:
'variable name',
}}
/>
<SimpleField
label={() => null}
type="react-select"
name={`variables.${index}[1]`}
options={
(SUPPORTED_TYPES as any) as string[]
}
isDisabled={!isEditable}
/>
{valueType === 'boolean' ? (
<SimpleField
label={() => null}
type="react-select"
name={`variables.${index}[2]`}
options={[
{
label: 'True',
value: true,
},
{
label:
'False',
value: false,
},
]}
/>
) : valueType === 'number' ? (
<SimpleField
label={() => null}
type={'number'}
name={`variables.${index}[2]`}
placeholder="variable value"
/>
) : (
<SimpleField
label={() => null}
type={'input'}
name={`variables.${index}[2]`}
inputProps={{
placeholder:
'variable value',
}}
/>
)}
</div>
{isEditable && (
<IconButton
icon="X"
onClick={() =>
arrayHelpers.remove(
index
)
}
/>
)}
</div>
)
)
: null;
const controlDOM = isEditable && (
<div className="horizontal-space-between mt4">
<TextButton
icon="Plus"
title="New Variable"
onClick={() =>
arrayHelpers.push([
'',
'string',
'',
])
}
/>
{dirty && (
<Button
onClick={() => handleSubmit()}
title="Save Changes"
disabled={isSubmitting || !isValid}
/>
)}
</div>
);
return (
<div className="DataDocTemplateVarForm-content mh4">
<fieldset
disabled={!isEditable}
className="mb4"
>
{fields}
</fieldset>
{controlDOM}
</div>
);
}}
/>
);
return (
<div className="DataDocTemplateVarForm">
<Form>{variablesField}</Form>
</div>
);
}}
</Formik>
);
}
Example #22
Source File: ListReplyTemplate.tsx From glific-frontend with GNU Affero General Public License v3.0 | 4 votes |
ListReplyTemplate: React.SFC<ListReplyTemplateProps> = (props) => {
const {
index,
inputFields,
form: { touched, errors, values },
onListAddClick,
onListRemoveClick,
onListItemAddClick,
onListItemRemoveClick,
onInputChange,
translation,
} = props;
const { t } = useTranslation();
const isError = (key: string, itemIdx: number) => {
const error =
errors.templateButtons &&
touched.templateButtons &&
errors.templateButtons[index] &&
touched.templateButtons[index] &&
errors.templateButtons[index].options &&
touched.templateButtons[index].options &&
errors.templateButtons[index].options[itemIdx] &&
errors.templateButtons[index].options[itemIdx][key];
return !!error;
};
const isListTitleError = (() => {
const error =
errors.templateButtons &&
touched.templateButtons &&
errors.templateButtons[index] &&
touched.templateButtons[index] &&
errors.templateButtons[index].title;
return !!error;
})();
const sectionLabel = `Enter list ${index + 1} title*`;
const { templateButtons } = values;
const { options } = templateButtons[index];
if (!options) {
return null;
}
const showDeleteIcon = inputFields[index]?.options && inputFields[index]?.options.length > 1;
const defaultTitle = inputFields[index]?.title;
const isAddMoreOptionAllowed = inputFields.reduce((result: number, field: any) => {
const { options: optn } = field;
return result + (optn ? optn.length : 0);
}, 0);
const handleAddListItem = (helper: any) => {
helper.push({ title: '', description: '' });
onListItemAddClick(options);
};
const handleRemoveListItem = (helper: any, idx: number) => {
helper.remove(idx);
onListItemRemoveClick(idx);
};
const handleInputChange = (
event: any,
key: string,
itemIndex: number | null = null,
isOption: boolean = false
) => {
const { value } = event.target;
const payload = { key, itemIndex, isOption };
onInputChange(value, payload);
};
return (
<div className={styles.WrapperBackground} key={index.toString()}>
<div className={styles.Section}>
<div>List {index + 1}</div>
<div>
{inputFields.length > 1 && (
<DeleteIcon
title="Remove"
className={styles.ListDeleteIcon}
onClick={onListRemoveClick}
/>
)}
</div>
</div>
<div className={styles.ListReplyWrapper}>
{translation && <div className={styles.Translation}>{translation.title}</div>}
<FormControl fullWidth error={isListTitleError} className={styles.FormControl}>
<TextField
label={sectionLabel}
placeholder={t(`List ${index + 1} title (Max 24 char.)`)}
variant="outlined"
onChange={(e: any) => handleInputChange(e, 'title')}
className={styles.TextField}
error={isListTitleError}
value={defaultTitle}
/>
{errors.templateButtons && touched.templateButtons && touched.templateButtons[index] ? (
<FormHelperText>{errors.templateButtons[index]?.title}</FormHelperText>
) : null}
</FormControl>
<div>
<FieldArray
name={`templateButtons[${index}].options`}
render={(arrayHelpers) =>
options.map((itemRow: any, itemIndex: any) => (
// disabling eslint for this as we have no other unique way to define a key
// eslint-disable-next-line react/no-array-index-key
<div key={itemIndex}>
<div className={styles.ListReplyItemWrapper}>
<div className={styles.ListReplyItemContent}>
<div className={styles.TextFieldWrapper}>
{translation?.options && translation.options.length > itemIndex && (
<div className={styles.Translation}>
{translation.options[itemIndex].title}
</div>
)}
<FormControl
fullWidth
error={isError('title', itemIndex)}
className={styles.FormControl}
>
<TextField
placeholder={`Title ${itemIndex + 1} (Max 24 char.)`}
variant="outlined"
label={`Enter list item ${itemIndex + 1} title*`}
onChange={(e: any) => handleInputChange(e, 'title', itemIndex, true)}
className={styles.TextField}
error={isError('title', itemIndex)}
value={itemRow.title}
InputProps={{
endAdornment: itemIndex !== 0 && showDeleteIcon && (
<CrossIcon
title="Remove"
className={styles.ListDeleteIcon}
onClick={() => handleRemoveListItem(arrayHelpers, itemIndex)}
/>
),
}}
/>
{isError('title', itemIndex) && (
<FormHelperText className={styles.HelperText}>
{errors.templateButtons[index].options[itemIndex].title}
</FormHelperText>
)}
</FormControl>
</div>
<div className={styles.TextFieldWrapper}>
{translation?.options &&
translation.options.length > itemIndex &&
translation.options[itemIndex].description && (
<div className={styles.Translation}>
{translation.options[itemIndex].description}
</div>
)}
<FormControl
fullWidth
error={isError('description', itemIndex)}
className={styles.FormControl}
>
<TextField
placeholder={`Description ${itemIndex + 1} (Max 60 char.)`}
variant="outlined"
label={`Enter list item ${itemIndex + 1} description`}
onChange={(e: any) =>
handleInputChange(e, 'description', itemIndex, true)
}
className={styles.TextField}
error={isError('description', itemIndex)}
value={itemRow.description}
/>
{isError('description', itemIndex) ? (
<FormHelperText>
{errors.templateButtons[index].options[itemIndex].description}
</FormHelperText>
) : null}
</FormControl>
</div>
</div>
</div>
<div className={styles.ActionButtons}>
{isAddMoreOptionAllowed < 10 &&
inputFields.length === index + 1 &&
options.length === itemIndex + 1 && (
<Button
color="primary"
className={styles.AddButton}
onClick={onListAddClick}
startIcon={<AddIcon className={styles.AddIcon} />}
>
{t('Add another list')}
</Button>
)}
{isAddMoreOptionAllowed < 10 && options.length === itemIndex + 1 && (
<Button
color="primary"
className={styles.AddButton}
onClick={() => handleAddListItem(arrayHelpers)}
startIcon={<AddIcon className={styles.AddIcon} />}
>
{t('Add another list item')}
</Button>
)}
</div>
</div>
))
}
/>
</div>
</div>
</div>
);
}
Example #23
Source File: EntitiesDiagramEntity.tsx From amplication with Apache License 2.0 | 4 votes |
EntitiesDiagramEntity = React.memo(
({
entity,
entityIndex,
editedFieldIdentifier,
editedEntity,
positionData,
zoomLevel,
onDrag,
onEditField,
onEditEntity,
onDeleteEntity,
onAddEntity,
}: Props) => {
const handleAddEntity = useCallback(() => {
onAddEntity && onAddEntity(entityIndex);
}, [entityIndex, onAddEntity]);
const handleDeleteEntity = useCallback(() => {
onDeleteEntity && onDeleteEntity(entityIndex);
}, [entityIndex, onDeleteEntity]);
const handleDrag = useCallback(
(e: DraggableEvent, data: DraggableData) => {
onDrag && onDrag(entityIndex, data);
},
[onDrag, entityIndex]
);
const handleEditEntity = useCallback(() => {
onEditEntity && onEditEntity(entityIndex);
}, [onEditEntity, entityIndex]);
const selected = entityIndex === editedEntity;
const handlers = {
CLOSE_MODAL: () => {
onEditEntity(null);
},
};
return (
<DraggableCore handle=".handle" onDrag={handleDrag}>
<div
id={`entity${entityIndex}`}
className={`${CLASS_NAME}__entities__entity`}
style={{ top: positionData?.top, left: positionData?.left }}
>
<div>
<HotKeys keyMap={keyMap} handlers={handlers}>
<div className={`${CLASS_NAME}__entities__entity__name-wrapper `}>
{selected ? (
<TextField
name={`entities.${entityIndex}.name`}
autoFocus
autoComplete="off"
label=""
placeholder="Entity Name"
required
/>
) : (
<>
<div
className={classNames(
`${CLASS_NAME}__entities__entity__name`,
"handle"
)}
>
<Icon icon="database" />
<span>{entity.name}</span>
<span className="spacer" />
</div>
<span className="spacer" />
{entityIndex > 0 && (
<Button
className={`${CLASS_NAME}__entities__entity__delete`}
buttonStyle={EnumButtonStyle.Clear}
type="button"
onClick={handleDeleteEntity}
icon="trash_2"
/>
)}
<Button
className={`${CLASS_NAME}__entities__entity__edit`}
buttonStyle={EnumButtonStyle.Clear}
type="button"
onClick={handleEditEntity}
icon="edit_2"
/>
</>
)}
<Button
className={`${CLASS_NAME}__entities__entity__add`}
buttonStyle={EnumButtonStyle.Primary}
onClick={handleAddEntity}
type="button"
icon="plus"
/>
</div>
</HotKeys>
<FieldArray
name={`entities.${entityIndex}.fields`}
render={(fieldsArrayHelpers) => (
<div className={`${CLASS_NAME}__fields`}>
{COMMON_FIELDS.map((field, fieldIndex) => (
<EntitiesDiagramStaticField
key={`static_${entityIndex}_${fieldIndex}`}
field={field}
/>
))}
<Droppable
droppableId={`droppable_${entityIndex}`}
//we use re-parenting to allow frag when the canvas is scaled (using transform: scale())
//https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/guides/reparenting.md
renderClone={(provided, snapshot, rubric) => (
<div
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
<div
style={{ transform: `scale(${zoomLevel})` }}
className={classNames(
`${CLASS_NAME}__fields__field`,
`${CLASS_NAME}__fields__field--dragged`
)}
>
<Icon
size="xsmall"
icon={
DATA_TYPE_TO_LABEL_AND_ICON[
entity.fields[rubric.source.index].dataType ||
models.EnumDataType.SingleLineText
].icon
}
/>
<span>{entity.fields[rubric.source.index].name}</span>
<span className="spacer" />
</div>
</div>
)}
>
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
className={classNames(`${CLASS_NAME}__droppable`, {
[`${CLASS_NAME}__droppable--over`]: snapshot.isDraggingOver,
})}
>
{entity.fields.map((field, fieldIndex) => (
<EntitiesDiagramField
key={`${entityIndex}_${fieldIndex}`}
field={field}
entityIndex={entityIndex}
fieldIndex={fieldIndex}
onEdit={onEditField}
editedFieldIdentifier={editedFieldIdentifier}
/>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</div>
)}
/>
</div>
</div>
</DraggableCore>
);
}
)
Example #24
Source File: AdvancedNetworkFields.tsx From assisted-ui-lib with Apache License 2.0 | 4 votes |
AdvancedNetworkFields: React.FC<AdvancedNetworkFieldsProps> = ({ isSDNSelectable }) => {
const { setFieldValue, values, errors } = useFormikContext<NetworkConfigurationValues>();
const isNetworkTypeSelectionEnabled = useFeature(
'ASSISTED_INSTALLER_NETWORK_TYPE_SELECTION_FEATURE',
);
const isDualStack = values.stackType === DUAL_STACK;
const clusterNetworkCidrPrefix = (index: number) =>
parseInt(
((values.clusterNetworks && values.clusterNetworks[index].cidr) || '').split('/')[1],
) || 1;
const formatClusterNetworkHostPrefix = (
e: React.ChangeEvent<HTMLInputElement>,
index: number,
) => {
if (isNaN(parseInt(e.target.value))) {
setFieldValue(`clusterNetworks.${index}.hostPrefix`, clusterNetworkCidrPrefix(index));
}
};
const isSubnetIPv6 = (index: number) => (isDualStack ? !!index : false);
const clusterNetworkHostPrefixHelperText = (index: number) =>
isSubnetIPv6(index) ? IPv6PrefixHelperText : IPv4PrefixHelperText;
return (
<Grid hasGutter>
<FieldArray name="clusterNetworks">
{() => (
<FormGroup fieldId="clusterNetworks" labelInfo={isDualStack && 'Primary'}>
{values.clusterNetworks?.map((_, index) => {
const networkSuffix = getNextworkLabelSuffix(index, isDualStack);
return (
<StackItem key={index} className={'network-field-group'}>
<InputField
name={`clusterNetworks.${index}.cidr`}
label={`Cluster network CIDR${networkSuffix}`}
helperText={clusterCidrHelperText}
isRequired
labelInfo={index === 0 && isDualStack ? 'Primary' : ''}
/>
<InputField
name={`clusterNetworks.${index}.hostPrefix`}
label={`Cluster network host prefix${networkSuffix}`}
type={TextInputTypes.number}
min={clusterNetworkCidrPrefix(index)}
max={
isSubnetIPv6(index)
? PREFIX_MAX_RESTRICTION.IPv6
: PREFIX_MAX_RESTRICTION.IPv4
}
onBlur={(e) =>
formatClusterNetworkHostPrefix(
e as React.ChangeEvent<HTMLInputElement>,
index,
)
}
helperText={clusterNetworkHostPrefixHelperText(index)}
isRequired
/>
</StackItem>
);
})}
</FormGroup>
)}
</FieldArray>
{typeof errors.clusterNetworks === 'string' && (
<Alert variant={AlertVariant.warning} title={errors.clusterNetworks} isInline />
)}
<FieldArray name="serviceNetworks">
{() => (
<FormGroup fieldId="serviceNetworks" labelInfo={isDualStack && 'Primary'}>
{values.serviceNetworks?.map((_, index) => (
<StackItem key={index} className={'network-field-group'}>
<InputField
name={`serviceNetworks.${index}.cidr`}
label={`Service network CIDR${getNextworkLabelSuffix(index, isDualStack)}`}
helperText={serviceCidrHelperText}
isRequired
labelInfo={index === 0 && isDualStack ? 'Primary' : ''}
/>
</StackItem>
))}
</FormGroup>
)}
</FieldArray>
{typeof errors.serviceNetworks === 'string' && (
<Alert variant={AlertVariant.warning} title={errors.serviceNetworks} isInline />
)}
{isNetworkTypeSelectionEnabled && (
<NetworkTypeControlGroup isSDNSelectable={isSDNSelectable} />
)}
</Grid>
);
}
Example #25
Source File: Audience.tsx From abacus with GNU General Public License v2.0 | 4 votes |
Audience = ({
indexedSegments,
formikProps,
completionBag,
}: {
indexedSegments: Record<number, Segment>
formikProps: FormikProps<{ experiment: ExperimentFormData }>
completionBag: ExperimentFormCompletionBag
}): JSX.Element => {
const classes = useStyles()
// The segmentExclusion code is currently split between here and SegmentAutocomplete
// An improvement might be to have SegmentAutocomplete only handle Segment[] and for code here
// to translate Segment <-> SegmentAssignment
const [segmentAssignmentsField, _segmentAssignmentsFieldMeta, segmentAssignmentsFieldHelper] = useField(
'experiment.segmentAssignments',
)
const [segmentExclusionState, setSegmentExclusionState] = useState<SegmentExclusionState>(() => {
// We initialize the segmentExclusionState from existing data if there is any
const firstSegmentAssignment = (segmentAssignmentsField.value as SegmentAssignmentNew[])[0]
return firstSegmentAssignment && firstSegmentAssignment.isExcluded
? SegmentExclusionState.Exclude
: SegmentExclusionState.Include
})
const onChangeSegmentExclusionState = (event: React.SyntheticEvent<HTMLInputElement>, value: string) => {
setSegmentExclusionState(value as SegmentExclusionState)
segmentAssignmentsFieldHelper.setValue(
(segmentAssignmentsField.value as SegmentAssignmentNew[]).map((segmentAssignment: SegmentAssignmentNew) => {
return {
...segmentAssignment,
isExcluded: value === SegmentExclusionState.Exclude,
}
}),
)
}
const platformError = formikProps.touched.experiment?.platform && formikProps.errors.experiment?.platform
const variationsError =
formikProps.touched.experiment?.variations && _.isString(formikProps.errors.experiment?.variations)
? formikProps.errors.experiment?.variations
: undefined
return (
<div className={classes.root}>
<Typography variant='h4' gutterBottom>
Define Your Audience
</Typography>
<div className={classes.row}>
<FormControl component='fieldset'>
<FormLabel required>Platform</FormLabel>
<Field component={Select} name='experiment.platform' displayEmpty error={!!platformError}>
<MenuItem value='' disabled>
Select a Platform
</MenuItem>
{Object.values(Platform).map((platform) => (
<MenuItem key={platform} value={platform}>
{platform}: {PlatformToHuman[platform]}
</MenuItem>
))}
</Field>
<FormHelperText error={!!platformError}>
{_.isString(platformError) ? platformError : undefined}
</FormHelperText>
</FormControl>
</div>
<div className={classes.row}>
<FormControl component='fieldset'>
<FormLabel required>User type</FormLabel>
<FormHelperText>Types of users to include in experiment</FormHelperText>
<Field component={FormikMuiRadioGroup} name='experiment.existingUsersAllowed' required>
<FormControlLabel
value='true'
label='All users (new + existing + anonymous)'
control={<Radio disabled={formikProps.isSubmitting} />}
disabled={formikProps.isSubmitting}
/>
<FormControlLabel
value='false'
label='Filter for newly signed up users (they must be also logged in)'
control={<Radio disabled={formikProps.isSubmitting} />}
disabled={formikProps.isSubmitting}
/>
</Field>
</FormControl>
</div>
<div className={classes.row}>
<FormControl component='fieldset' className={classes.segmentationFieldSet}>
<FormLabel htmlFor='segments-select'>Targeting</FormLabel>
<FormHelperText className={classes.segmentationHelperText}>
Who should see this experiment? <br /> Add optional filters to include or exclude specific target audience
segments.
</FormHelperText>
<MuiRadioGroup
aria-label='include-or-exclude-segments'
className={classes.segmentationExclusionState}
value={segmentExclusionState}
onChange={onChangeSegmentExclusionState}
>
<FormControlLabel
value={SegmentExclusionState.Include}
control={<Radio />}
label='Include'
name='non-formik-segment-exclusion-state-include'
/>
<FormControlLabel
value={SegmentExclusionState.Exclude}
control={<Radio />}
label='Exclude'
name='non-formik-segment-exclusion-state-exclude'
/>
</MuiRadioGroup>
<Field
name='experiment.segmentAssignments'
component={SegmentsAutocomplete}
options={Object.values(indexedSegments)}
// TODO: Error state, see https://stackworx.github.io/formik-material-ui/docs/api/material-ui-lab
renderInput={(params: AutocompleteRenderInputParams) => (
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
<MuiTextField
{...params}
variant='outlined'
placeholder={segmentAssignmentsField.value.length === 0 ? 'Search and select to customize' : undefined}
/>
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
)}
segmentExclusionState={segmentExclusionState}
indexedSegments={indexedSegments}
fullWidth
id='segments-select'
/>
</FormControl>
</div>
<div className={classes.row}>
<FormControl component='fieldset' className={classes.segmentationFieldSet}>
<FormLabel htmlFor='variations-select'>Variations</FormLabel>
<FormHelperText className={classes.segmentationHelperText}>
Set the percentage of traffic allocated to each variation. Percentages may sum to less than 100 to avoid
allocating the entire userbase. <br /> Use “control” for the default (fallback) experience.
</FormHelperText>
{variationsError && <FormHelperText error>{variationsError}</FormHelperText>}
<TableContainer>
<Table className={classes.variants}>
<TableHead>
<TableRow>
<TableCell> Name </TableCell>
<TableCell> Allocated Percentage </TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
<FieldArray
name={`experiment.variations`}
render={(arrayHelpers) => {
const onAddVariation = () => {
arrayHelpers.push({
name: ``,
isDefault: false,
allocatedPercentage: '',
})
}
const onRemoveVariation = (index: number) => arrayHelpers.remove(index)
const variations = formikProps.values.experiment.variations
return (
<>
{variations.map((variation, index) => {
return (
// The key here needs to be changed for variable variations
<TableRow key={index}>
<TableCell>
{variation.isDefault ? (
variation.name
) : (
<Field
component={FormikMuiTextField}
name={`experiment.variations[${index}].name`}
size='small'
variant='outlined'
required
inputProps={{
'aria-label': 'Variation Name',
}}
/>
)}
</TableCell>
<TableCell>
<Field
className={classes.variationAllocatedPercentage}
component={FormikMuiTextField}
name={`experiment.variations[${index}].allocatedPercentage`}
type='number'
size='small'
variant='outlined'
inputProps={{ min: 1, max: 99, 'aria-label': 'Allocated Percentage' }}
required
InputProps={{
endAdornment: <InputAdornment position='end'>%</InputAdornment>,
}}
/>
</TableCell>
<TableCell>
{!variation.isDefault && 2 < variations.length && (
<IconButton onClick={() => onRemoveVariation(index)} aria-label='Remove variation'>
<Clear />
</IconButton>
)}
</TableCell>
</TableRow>
)
})}
<TableRow>
<TableCell colSpan={3}>
<Alert severity='warning' className={classes.abnWarning}>
<strong> Manual analysis only A/B/n </strong>
<br />
<p>
Experiments with more than a single treatment variation are in an early alpha stage.
</p>
<p>No results will be displayed.</p>
<p>
Please do not set up such experiments in production without consulting the ExPlat team
first.
</p>
<div className={classes.addVariation}>
<Add className={classes.addVariationIcon} />
<Button
variant='contained'
onClick={onAddVariation}
disableElevation
size='small'
aria-label='Add Variation'
>
Add Variation
</Button>
</div>
</Alert>
</TableCell>
</TableRow>
</>
)
}}
/>
</TableBody>
</Table>
</TableContainer>
</FormControl>
</div>
{isDebugMode() && (
<div className={classes.row}>
<FormControl component='fieldset'>
<FormLabel htmlFor='experiment.exclusionGroupTagIds'>Exclusion Groups</FormLabel>
<FormHelperText>Optionally add this experiment to a mutually exclusive experiment group.</FormHelperText>
<br />
<Field
component={AbacusAutocomplete}
name='experiment.exclusionGroupTagIds'
id='experiment.exclusionGroupTagIds'
fullWidth
options={
// istanbul ignore next; trivial
completionBag.exclusionGroupCompletionDataSource.data ?? []
}
loading={completionBag.exclusionGroupCompletionDataSource.isLoading}
multiple
renderOption={(option: AutocompleteItem) => <Chip label={option.name} />}
renderInput={(params: AutocompleteRenderInputParams) => (
<MuiTextField
{...params}
variant='outlined'
InputProps={{
...autocompleteInputProps(params, completionBag.exclusionGroupCompletionDataSource.isLoading),
}}
InputLabelProps={{
shrink: true,
}}
/>
)}
/>
</FormControl>
</div>
)}
</div>
)
}
Example #26
Source File: InteractiveOptions.tsx From glific-frontend with GNU Affero General Public License v3.0 | 4 votes |
InteractiveOptions: React.SFC<InteractiveOptionsProps> = ({
isAddButtonChecked,
templateType,
inputFields,
form,
onAddClick,
onRemoveClick,
onTemplateTypeChange,
onInputChange,
onGlobalButtonInputChange,
onListItemAddClick,
onListItemRemoveClick,
disabled = false,
translation,
disabledType,
}) => {
const { values, errors, touched, setFieldValue } = form;
const handleAddClick = (helper: any, type: string) => {
const obj = type === LIST ? { title: '', options: [] } : { value: '' };
helper.push(obj);
onAddClick(true, type);
};
const handleRemoveClick = (helper: any, idx: number) => {
helper.remove(idx);
onRemoveClick(idx);
};
const getButtons = (index: number, arrayHelpers: any) => {
let template: any = null;
if (templateType === LIST) {
template = (
<ListReplyTemplate
translation={translation && translation.items[index]}
key={index}
index={index}
inputFields={inputFields}
form={form}
onListAddClick={() => handleAddClick(arrayHelpers, LIST)}
onListRemoveClick={() => handleRemoveClick(arrayHelpers, index)}
onListItemAddClick={(options: Array<any>) => onListItemAddClick(index, options)}
onListItemRemoveClick={(itemIndex: number) => onListItemRemoveClick(index, itemIndex)}
onInputChange={(value: string, payload: any) =>
onInputChange(LIST, index, value, payload, setFieldValue)
}
/>
);
}
if (templateType === QUICK_REPLY) {
template = (
<QuickReplyTemplate
translation={translation && translation[index]}
key={index}
index={index}
inputFields={inputFields}
form={form}
onInputChange={(value: string, payload: any) =>
onInputChange(QUICK_REPLY, index, value, payload, setFieldValue)
}
onAddClick={() => handleAddClick(arrayHelpers, QUICK_REPLY)}
onRemoveClick={() => handleRemoveClick(arrayHelpers, index)}
/>
);
}
return template;
};
const radioTemplateType = (
<div>
<RadioGroup
aria-label="template-type"
name="template-type"
row
value={templateType}
onChange={(event) => onTemplateTypeChange(event.target.value)}
>
<div className={styles.RadioLabelWrapper}>
<FormControlLabel
value={QUICK_REPLY}
control={
<Radio
disabled={disabledType}
color="primary"
checkedIcon={<ApprovedIcon className={styles.CheckedIcon} />}
size="small"
/>
}
className={templateType === QUICK_REPLY ? styles.SelectedLabel : ''}
classes={{ root: styles.RadioLabel }}
label="Reply buttons"
/>
</div>
<div className={styles.RadioLabelWrapper}>
<FormControlLabel
value={LIST}
control={
<Radio
disabled={disabledType}
color="primary"
checkedIcon={<ApprovedIcon className={styles.CheckedIcon} />}
size="small"
/>
}
className={templateType === LIST ? styles.SelectedLabel : ''}
classes={{ root: styles.RadioLabel }}
label="List message"
/>
</div>
</RadioGroup>
{templateType && templateType === LIST && (
<div className={styles.GlobalButton}>
{translation && <div className={styles.Translation}>{translation.globalButton}</div>}
<FormControl
fullWidth
error={!!(errors.globalButton && touched.globalButton)}
className={styles.FormControl}
>
<TextField
placeholder="List header"
variant="outlined"
label="List header*"
className={styles.TextField}
onChange={(e: any) => {
setFieldValue('globalButton', e.target.value);
onGlobalButtonInputChange(e.target.value);
}}
value={values.globalButton}
error={!!errors.globalButton && touched.globalButton}
/>
{errors.globalButton && touched.globalButton && (
<FormHelperText>{errors.globalButton}</FormHelperText>
)}
</FormControl>
</div>
)}
{templateType && (
<div className={templateType === QUICK_REPLY ? styles.TemplateFields : ''}>
<FieldArray
name="templateButtons"
render={(arrayHelpers) =>
values.templateButtons.map((row: any, index: any) => getButtons(index, arrayHelpers))
}
/>
</div>
)}
</div>
);
return <div>{isAddButtonChecked && !disabled && radioTemplateType}</div>;
}
Example #27
Source File: relationships-section.component.tsx From openmrs-esm-patient-registration with MIT License | 4 votes |
RelationshipsSection: React.FC = () => {
const { relationshipTypes } = useContext(ResourcesContext);
const [displayRelationshipTypes, setDisplayRelationshipTypes] = useState<RelationshipType[]>([]);
const { setFieldValue } = React.useContext(PatientRegistrationContext);
const { t } = useTranslation();
useEffect(() => {
const tmp: RelationshipType[] = [];
relationshipTypes.results.forEach(type => {
const aIsToB = {
display: type.aIsToB,
uuid: type.uuid,
direction: 'aIsToB',
};
const bIsToA = {
display: type.bIsToA,
uuid: type.uuid,
direction: 'bIsToA',
};
aIsToB.display === bIsToA.display ? tmp.push(aIsToB) : tmp.push(aIsToB, bIsToA);
});
setDisplayRelationshipTypes(tmp);
}, [relationshipTypes]);
const handleRelationshipTypeChange = event => {
const { target } = event;
const field = target.name;
const value = target.options[target.selectedIndex].value;
setFieldValue(field, value);
};
const handleSuggestionSelected = (field: string, selectedSuggestion: string) => {
setFieldValue(field, selectedSuggestion);
};
const searchPerson = async (query: string) => {
const abortController = new AbortController();
const searchResults = await fetchPerson(query, abortController);
return searchResults.data.results;
};
return (
<section className={sectionStyles.formSection} aria-label="Relationships Section">
<section className={sectionStyles.fieldGroup}>
<FieldArray name="relationships">
{({
push,
remove,
form: {
values: { relationships },
},
}) => (
<div>
{relationships && relationships.length > 0 ? (
<div>
<br />
{relationships.map((_relationship: any, index: React.Key) => (
<div key={index} className={styles.relationship}>
<div className={styles.searchBox} style={{ marginBottom: '1rem' }}>
<Autosuggest
name={`relationships[${index}].relatedPerson`}
placeholder="Find person"
onSuggestionSelected={handleSuggestionSelected}
getSearchResults={searchPerson}
getDisplayValue={item => item.display}
getFieldValue={item => item.uuid}
/>
</div>
<div className={`${styles.selectRelationshipType}`} style={{ marginBottom: '1rem' }}>
<Select
light={true}
id="select"
defaultValue="placeholder-item"
labelText={t('relationship', 'Relationship')}
onChange={handleRelationshipTypeChange}
name={`relationships[${index}].relationship`}>
<SelectItem
disabled
hidden
value="placeholder-item"
text={t('relationshipToPatient', 'Relationship to patient')}
/>
{displayRelationshipTypes.map(type => (
<SelectItem text={type.display} value={`${type.uuid}/${type.direction}`} key={index} />
))}
</Select>
</div>
<div className={styles.actions}>
{relationships.length - 1 === index && (
<Button kind="ghost" onClick={() => push({})}>
{t('addRelationshipButtonText', 'Add Relationship')}
</Button>
)}
</div>
</div>
))}
</div>
) : null}
</div>
)}
</FieldArray>
</section>
</section>
);
}
Example #28
Source File: Response.tsx From Mokku with MIT License | 4 votes |
Response = ({
mock,
jsonEditor,
values,
errors,
setFieldValue,
setFieldError,
handleChange,
handleBlur,
}: IProps) => {
const [tab, setTab] = React.useState(0);
const [responseType, setResponseType] = React.useState(
!!mock?.response && isValidJSON(mock.response).error ? "TEXT" : "JSON"
);
return (
<FieldWrapper
className="mock-create-response h-100"
style={{ height: "100%" }}
>
<Group>
<StyledTabs
selected={tab}
tabs={["Body", "Headers"]}
onChange={(selected) => {
if (selected === 1 && values.headers.length === 0) {
setFieldValue("headers", [{ name: "", value: "" }]);
}
if (
selected === 0 &&
values.headers.length &&
!values.headers[values.headers.length - 1].name &&
!values.headers[values.headers.length - 1].value
) {
setFieldValue("headers", []);
}
setTab(selected);
}}
/>
</Group>
{tab === 0 && (
<ResponseWrapper>
<Select
onChange={(event) => setResponseType(event.target.value)}
value={responseType}
>
<option value="JSON">JSON</option>
<option value="TEXT">Text</option>
</Select>
{responseType === "JSON" ? (
<JSONEditor
name="response"
value={values.response}
onChange={(v) => setFieldValue("response", v)}
onError={(v) => setFieldError("response", v)}
error={!!errors.response}
{...jsonEditor}
/>
) : (
<Input
style={{ height: "100%", width: "100%" }}
as="textarea"
name="response"
value={values.response}
onChange={handleChange}
onBlur={handleBlur}
/>
)}
</ResponseWrapper>
)}
{tab === 1 && (
<FieldArray
name="headers"
render={(arrayHelpers) => {
const disabled = values.headers.some(
(header) => !header.name || !header.value
);
return (
<>
{values.headers.length > 0 && (
<div style={{ flexGrow: 2, overflow: "scroll" }}>
{values.headers.map((header, index) => (
<HeaderWrapper className="mock-create-header">
<Input
marginRight
name={`headers.${index}.name`}
autoFocus
></Input>
<Input
marginRight
name={`headers.${index}.value`}
></Input>
<Icon
onClick={() => {
arrayHelpers.remove(index);
}}
>
close
</Icon>
</HeaderWrapper>
))}
</div>
)}
<Button
style={{ height: 24, fontSize: 12 }}
transparent
color="primary"
disabled={disabled}
onClick={() => arrayHelpers.push({ name: "", value: "" })}
>
Add Header
</Button>
</>
);
}}
/>
)}
</FieldWrapper>
);
}
Example #29
Source File: TemplateOptions.tsx From glific-frontend with GNU Affero General Public License v3.0 | 4 votes |
TemplateOptions: React.SFC<TemplateOptionsProps> = ({
isAddButtonChecked,
templateType,
inputFields,
form: { touched, errors, values },
onAddClick,
onRemoveClick,
onTemplateTypeChange,
onInputChange,
disabled = false,
}) => {
const buttonTitle = 'Button Title';
const buttonValue = 'Button Value';
const buttonTitles: any = {
CALL_TO_ACTION: 'Call to action',
QUICK_REPLY: 'Quick Reply',
};
const handleAddClick = (helper: any, type: boolean) => {
const obj = type ? { type: '', value: '', title: '' } : { value: '' };
helper.push(obj);
onAddClick();
};
const handleRemoveClick = (helper: any, idx: number) => {
helper.remove(idx);
onRemoveClick(idx);
};
const addButton = (helper: any, type: boolean = false) => {
const title = templateType ? buttonTitles[templateType] : '';
const buttonClass =
templateType === QUICK_REPLY ? styles.QuickReplyAddButton : styles.CallToActionAddButton;
return (
<Button
className={buttonClass}
variant="outlined"
color="primary"
onClick={() => handleAddClick(helper, type)}
>
Add {title}
</Button>
);
};
const getButtons = (row: any, index: number, arrayHelpers: any) => {
const { type, title, value }: any = row;
let template: any = null;
const isError = (key: string) =>
!!(
errors.templateButtons &&
touched.templateButtons &&
errors.templateButtons[index] &&
errors.templateButtons[index][key]
);
if (templateType === CALL_TO_ACTION) {
template = (
<div className={styles.CallToActionContainer} key={index.toString()}>
<div className={styles.CallToActionWrapper}>
<div>
<div className={styles.RadioStyles}>
<FormControl fullWidth error={isError('type')} className={styles.FormControl}>
<RadioGroup
aria-label="action-radio-buttons"
name="action-radio-buttons"
row
value={type}
onChange={(e: any) => onInputChange(e, row, index, 'type')}
className={styles.RadioGroup}
>
<FormControlLabel
value="phone_number"
control={
<Radio
color="primary"
disabled={
disabled ||
(index === 0 &&
inputFields.length > 1 &&
inputFields[0].type !== 'phone_number') ||
(index > 0 &&
inputFields[0].type &&
inputFields[0].type === 'phone_number')
}
/>
}
label="Phone number"
/>
<FormControlLabel
value="url"
control={
<Radio
color="primary"
disabled={
disabled ||
(index === 0 &&
inputFields.length > 1 &&
inputFields[0].type !== 'url') ||
(index > 0 && inputFields[0].type && inputFields[0].type === 'url')
}
/>
}
label="URL"
/>
</RadioGroup>
{errors.templateButtons &&
touched.templateButtons &&
touched.templateButtons[index] ? (
<FormHelperText>{errors.templateButtons[index]?.type}</FormHelperText>
) : null}
</FormControl>
</div>
<div>
{inputFields.length > 1 ? (
<DeleteIcon onClick={() => handleRemoveClick(arrayHelpers, index)} />
) : null}
</div>
</div>
<div className={styles.TextFieldWrapper}>
<FormControl fullWidth error={isError('title')} className={styles.FormControl}>
<TextField
disabled={disabled}
title={title}
defaultValue={value}
placeholder={buttonTitle}
variant="outlined"
label={buttonTitle}
onBlur={(e: any) => onInputChange(e, row, index, 'title')}
className={styles.TextField}
error={isError('title')}
/>
{errors.templateButtons &&
touched.templateButtons &&
touched.templateButtons[index] ? (
<FormHelperText>{errors.templateButtons[index]?.title}</FormHelperText>
) : null}
</FormControl>
</div>
<div className={styles.TextFieldWrapper}>
<FormControl fullWidth error={isError('value')} className={styles.FormControl}>
<TextField
title={value}
defaultValue={value}
disabled={disabled}
placeholder={buttonValue}
variant="outlined"
label={buttonValue}
onBlur={(e: any) => onInputChange(e, row, index, 'value')}
className={styles.TextField}
error={isError('value')}
/>
{errors.templateButtons &&
touched.templateButtons &&
touched.templateButtons[index] ? (
<FormHelperText>{errors.templateButtons[index]?.value}</FormHelperText>
) : null}
</FormControl>
</div>
</div>
<div>
{inputFields.length === index + 1 && inputFields.length !== 2
? addButton(arrayHelpers, true)
: null}
</div>
</div>
);
}
if (templateType === QUICK_REPLY) {
template = (
<div className={styles.QuickReplyContainer} key={index.toString()}>
<div className={styles.QuickReplyWrapper}>
<FormControl fullWidth error={isError('value')} className={styles.FormControl}>
<TextField
disabled={disabled}
defaultValue={value}
title={title}
placeholder={`Quick reply ${index + 1} title`}
label={`Quick reply ${index + 1} title`}
variant="outlined"
onBlur={(e: any) => onInputChange(e, row, index, 'value')}
className={styles.TextField}
error={isError('value')}
InputProps={{
endAdornment: inputFields.length > 1 && !disabled && (
<CrossIcon
className={styles.RemoveIcon}
title="Remove"
onClick={() => handleRemoveClick(arrayHelpers, index)}
/>
),
}}
/>
{errors.templateButtons &&
touched.templateButtons &&
touched.templateButtons[index] ? (
<FormHelperText>{errors.templateButtons[index]?.value}</FormHelperText>
) : null}
</FormControl>
</div>
<div>
{inputFields.length === index + 1 && inputFields.length !== 3
? addButton(arrayHelpers)
: null}
</div>
</div>
);
}
return template;
};
const radioTemplateType = (
<div>
<RadioGroup
aria-label="template-type"
name="template-type"
row
value={templateType}
onChange={(event) => onTemplateTypeChange(event.target.value)}
>
<div className={styles.RadioLabelWrapper}>
<FormControlLabel
value={CALL_TO_ACTION}
control={<Radio color="primary" disabled={disabled} />}
label="Call to actions"
classes={{ root: styles.RadioLabel }}
/>
<Tooltip title={GUPSHUP_CALL_TO_ACTION} placement="right" tooltipClass={styles.Tooltip}>
<InfoIcon />
</Tooltip>
</div>
<div className={styles.RadioLabelWrapper}>
<FormControlLabel
value={QUICK_REPLY}
control={<Radio color="primary" disabled={disabled} />}
label="Quick replies"
className={styles.RadioLabel}
/>
<Tooltip title={GUPSHUP_QUICK_REPLY} placement="right" tooltipClass={styles.Tooltip}>
<InfoIcon />
</Tooltip>
</div>
</RadioGroup>
{templateType ? (
<div
className={
templateType === QUICK_REPLY
? styles.QuickTemplateFields
: styles.CallToActionTemplateFields
}
>
<FieldArray
name="templateButtons"
render={(arrayHelpers) =>
values.templateButtons.map((row: any, index: any) =>
getButtons(row, index, arrayHelpers)
)
}
/>
</div>
) : null}
</div>
);
return <div>{isAddButtonChecked && radioTemplateType}</div>;
}