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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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}
                    />
                    &thinsp;
                    <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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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&apos;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>
        &nbsp;
        <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&apos;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 vote down vote up
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 vote down vote up
/**
 * @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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 &ldquo;control&rdquo; 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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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>;
}