diff --git a/frontend/src/metabase-types/forms/legacy.ts b/frontend/src/metabase-types/forms/legacy.ts deleted file mode 100644 index c7e4b5fd44c3395fc4c4d08f0e2758bd36845631..0000000000000000000000000000000000000000 --- a/frontend/src/metabase-types/forms/legacy.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * @deprecated - */ -export type FieldName = string; - -/** - * @deprecated - */ -export type DefaultFieldValue = unknown; - -/** - * @deprecated - */ -export type FieldValues = Record<FieldName, DefaultFieldValue>; - -type FieldValidateResultOK = undefined; -type FieldValidateResultError = string; - -// Extending Record type here as field definition's props -// will be just spread to the final field widget -// (e.g. autoFocus, placeholder) -/** - * @deprecated - */ -export type BaseFieldDefinition = Record<string, unknown> & { - name: string; - type?: string; - title?: string; - description?: string; - placeholder?: string; - hidden?: boolean; - - info?: string; - infoLabel?: string; - infoLabelTooltip?: string; - - align?: "left" | "right"; - horizontal?: boolean; - descriptionPosition?: "top" | "bottom"; - visibleIf?: Record<FieldName, unknown>; - - initial?: (value: unknown) => DefaultFieldValue; - validate?: ( - value: DefaultFieldValue, - ) => FieldValidateResultOK | FieldValidateResultError; - normalize?: (value: unknown) => DefaultFieldValue; -}; - -/** - * @deprecated - */ -export type StandardFormFieldDefinition = BaseFieldDefinition & { - // If not is not provided, we're going to use default text input - type?: string | (() => JSX.Element); -}; - -/** - * @deprecated - */ -export type CustomFormFieldDefinition = BaseFieldDefinition & { - widget: () => JSX.Element; -}; - -/** - * @deprecated - */ -export type FormFieldDefinition = - | StandardFormFieldDefinition - | CustomFormFieldDefinition; - -/** - * @deprecated - */ -export type FormField<Value = DefaultFieldValue> = { - name: FieldName; - value: Value; - error?: string; - initialValue: Value; - - active: boolean; - dirty: boolean; - invalid: boolean; - pristine: boolean; - touched: boolean; - valid: boolean; - visited: boolean; - - onBlur: () => void; - onFocus: () => void; -}; - -/** - * @deprecated - */ -export type FormObject = { - fields: - | FormFieldDefinition[] - | ((values?: FieldValues) => FormFieldDefinition[]); -}; - -/** - * @deprecated - */ -export type PopulatedFormObject = { - fields: (values?: FieldValues) => FormFieldDefinition[]; - fieldNames: (values: FieldValues) => FieldName[]; - hidden: (obj: unknown) => void; - initial: (obj: unknown) => void; - normalize: (obj: unknown) => void; - validate: (obj: unknown) => void; - disablePristineSubmit?: boolean; -}; diff --git a/frontend/src/metabase-types/guards/forms-legacy.ts b/frontend/src/metabase-types/guards/forms-legacy.ts deleted file mode 100644 index 29fc6a0b1a4c673e22fc65bfdcc06e397824be27..0000000000000000000000000000000000000000 --- a/frontend/src/metabase-types/guards/forms-legacy.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { - StandardFormFieldDefinition, - CustomFormFieldDefinition, - FormFieldDefinition, -} from "metabase-types/forms/legacy"; - -export function isCustomWidget( - formField: FormFieldDefinition, -): formField is CustomFormFieldDefinition { - return ( - !(formField as StandardFormFieldDefinition).type && - typeof (formField as CustomFormFieldDefinition).widget === "function" - ); -} diff --git a/frontend/src/metabase/components/form/CustomForm/CustomForm.tsx b/frontend/src/metabase/components/form/CustomForm/CustomForm.tsx deleted file mode 100644 index a6e61319a4e7ab3a5ef6e8998a4664c35609ae7d..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/CustomForm/CustomForm.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React from "react"; -import _ from "underscore"; - -import { - FormFieldDefinition, - PopulatedFormObject, -} from "metabase-types/forms/legacy"; - -import { - BaseFormProps, - OptionalFormViewProps, - CustomFormLegacyContext, - LegacyContextTypes, -} from "./types"; - -import CustomFormField, { CustomFormFieldProps } from "./CustomFormField"; -import CustomFormFooter, { CustomFormFooterProps } from "./CustomFormFooter"; -import CustomFormMessage, { CustomFormMessageProps } from "./CustomFormMessage"; -import CustomFormSubmit from "./CustomFormSubmit"; -import Form from "./Form"; - -interface FormRenderProps extends BaseFormProps { - form: PopulatedFormObject; - formFields: FormFieldDefinition[]; - Form: React.ComponentType<{ children: React.ReactNode }>; - FormField: React.ComponentType<CustomFormFieldProps>; - FormSubmit: React.ComponentType<{ children: React.ReactNode }>; - FormMessage: React.ComponentType<CustomFormMessageProps>; - FormFooter: React.ComponentType<CustomFormFooterProps>; -} - -interface CustomFormProps extends BaseFormProps, OptionalFormViewProps { - children: React.ReactNode | ((props: FormRenderProps) => JSX.Element); -} - -function CustomForm(props: CustomFormProps) { - const { formObject: form, values, children } = props; - if (typeof children === "function") { - return children({ - ...props, - form, - formFields: form.fields(values), - Form: Form, - FormField: CustomFormField, - FormSubmit: CustomFormSubmit, - FormMessage: CustomFormMessage, - FormFooter: CustomFormFooter, - }); - } - return <Form {...props} />; -} - -/** - * @deprecated - */ -class CustomFormWithLegacyContext extends React.Component<CustomFormProps> { - static childContextTypes = LegacyContextTypes; - - getChildContext(): CustomFormLegacyContext { - const { - fields, - values, - formObject: form, - submitting, - invalid, - pristine, - error, - handleSubmit, - submitTitle, - renderSubmit, - className, - style, - onChangeField, - } = this.props; - const { disablePristineSubmit } = form; - const formFields = form.fields(values); - const formFieldsByName = _.indexBy(formFields, "name"); - - return { - handleSubmit, - submitTitle, - renderSubmit, - className, - style, - fields, - formFields, - formFieldsByName, - values, - submitting, - invalid, - pristine, - error, - onChangeField, - disablePristineSubmit, - }; - } - - render() { - return <CustomForm {...this.props} />; - } -} - -export default CustomFormWithLegacyContext; diff --git a/frontend/src/metabase/components/form/CustomForm/CustomFormField.tsx b/frontend/src/metabase/components/form/CustomForm/CustomFormField.tsx deleted file mode 100644 index 537f7acbd4931de7e76fade0bb2ccd7097045d6b..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/CustomForm/CustomFormField.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import React, { useCallback, useMemo } from "react"; -import PropTypes from "prop-types"; -import { getIn } from "icepick"; -import _ from "underscore"; - -import FormField from "metabase/components/form/FormField"; -import FormWidget from "metabase/components/form/FormWidget"; - -import { useOnMount } from "metabase/hooks/use-on-mount"; -import { useOnUnmount } from "metabase/hooks/use-on-unmount"; - -import { BaseFieldDefinition } from "metabase-types/forms/legacy"; -import { isCustomWidget } from "metabase-types/guards/forms-legacy"; - -import { - CustomFormLegacyContext, - FormContainerLegacyContext, - LegacyContextTypes, -} from "./types"; - -export interface CustomFormFieldProps extends BaseFieldDefinition { - onChange?: (e: unknown) => void; -} -interface LegacyContextProps - extends CustomFormLegacyContext, - FormContainerLegacyContext {} - -function getFieldDefinition(props: CustomFormFieldProps): BaseFieldDefinition { - return _.pick( - props, - "name", - "type", - "title", - "description", - "initial", - "validate", - "normalize", - ); -} - -function RawCustomFormField({ - fields, - formFieldsByName, - values, - onChangeField, - registerFormField, - unregisterFormField, - ...props -}: CustomFormFieldProps & LegacyContextProps & { forwardedRef?: any }) { - const { name, onChange, forwardedRef } = props; - - const field = getIn(fields, name.split(".")); - const formField = formFieldsByName[name]; - - useOnMount(() => { - registerFormField?.(getFieldDefinition(props)); - }); - - useOnUnmount(() => { - unregisterFormField?.(getFieldDefinition(props)); - }); - - const handleChange = useCallback( - e => { - field.onChange(e); - onChange?.(e); - }, - [field, onChange], - ); - - const fieldProps = useMemo( - () => ({ - ...props, - values, - onChangeField, - formField, - field: - typeof onChange === "function" - ? { - ...field, - onChange: handleChange, - } - : field, - }), - [props, values, formField, field, onChange, onChangeField, handleChange], - ); - - if (!field || !formField) { - return null; - } - - const hasCustomWidget = isCustomWidget(formField); - const Widget = hasCustomWidget ? formField.widget : FormWidget; - - return ( - <FormField {...fieldProps}> - <Widget {...fieldProps} ref={forwardedRef} /> - </FormField> - ); -} - -const CustomFormFieldLegacyContext = ( - props: CustomFormFieldProps & { forwardedRef?: any }, - context: LegacyContextProps, -) => <RawCustomFormField {...props} {...context} />; - -CustomFormFieldLegacyContext.contextTypes = { - ..._.pick( - LegacyContextTypes, - "fields", - "formFieldsByName", - "values", - "onChangeField", - ), - registerFormField: PropTypes.func, - unregisterFormField: PropTypes.func, -}; - -/** - * @deprecated - */ -const CustomFormField = React.forwardRef< - HTMLInputElement, - CustomFormFieldProps ->(function CustomFormField(props, ref) { - return <CustomFormFieldLegacyContext {...props} forwardedRef={ref} />; -}); - -export default CustomFormField; diff --git a/frontend/src/metabase/components/form/CustomForm/CustomFormFooter/CustomFormFooter.styled.tsx b/frontend/src/metabase/components/form/CustomForm/CustomFormFooter/CustomFormFooter.styled.tsx deleted file mode 100644 index 5e29a2fdd29d5bde5e821cdb48b579202af429af..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/CustomForm/CustomFormFooter/CustomFormFooter.styled.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import styled from "@emotion/styled"; - -interface CustomFormFooterStyledProps { - shouldReverse?: boolean; -} -export const CustomFormFooterStyled = styled.div<CustomFormFooterStyledProps>` - display: flex; - align-items: flex-start; - flex-direction: ${({ shouldReverse }) => - shouldReverse ? "row-reverse" : "column"}; -`; diff --git a/frontend/src/metabase/components/form/CustomForm/CustomFormFooter/CustomFormFooter.tsx b/frontend/src/metabase/components/form/CustomForm/CustomFormFooter/CustomFormFooter.tsx deleted file mode 100644 index fb0f108256fcf723e84a23862337f9f85dce3b7a..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/CustomForm/CustomFormFooter/CustomFormFooter.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { t } from "ttag"; - -import Button from "metabase/core/components/Button"; - -import CustomFormMessage from "../CustomFormMessage"; -import CustomFormSubmit from "../CustomFormSubmit"; -import { CustomFormFooterStyled } from "./CustomFormFooter.styled"; - -import { CustomFormFooterProps } from "./CustomFormFooterTypes"; - -interface LegacyContextProps { - isModal?: boolean; -} - -function CustomFormFooter({ - submitTitle, - cancelTitle = t`Cancel`, - onCancel, - footerExtraButtons, - fullWidth, - isModal, - isContextModal, -}: CustomFormFooterProps & { isContextModal?: boolean }) { - return ( - <CustomFormFooterStyled shouldReverse={isModal || isContextModal}> - <CustomFormSubmit fullWidth={fullWidth}>{submitTitle}</CustomFormSubmit> - {onCancel && ( - <Button className="mx1" type="button" onClick={onCancel}> - {cancelTitle} - </Button> - )} - <CustomFormMessage className="mt1" /> - {footerExtraButtons} - </CustomFormFooterStyled> - ); -} - -/** - * @deprecated - */ -const CustomFormFooterLegacyContext = ( - props: CustomFormFooterProps, - { isModal: isContextModal }: LegacyContextProps, -) => <CustomFormFooter {...props} isContextModal={isContextModal} />; - -CustomFormFooterLegacyContext.contextTypes = { - isModal: PropTypes.bool, -}; - -export default CustomFormFooterLegacyContext; diff --git a/frontend/src/metabase/components/form/CustomForm/CustomFormFooter/CustomFormFooterTypes.ts b/frontend/src/metabase/components/form/CustomForm/CustomFormFooter/CustomFormFooterTypes.ts deleted file mode 100644 index 4de033991ff7e33b324fbabcf617f62907951b97..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/CustomForm/CustomFormFooter/CustomFormFooterTypes.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface CustomFormFooterProps { - submitTitle: string; - cancelTitle?: string; - fullWidth?: boolean; - isModal?: boolean; - footerExtraButtons: React.ReactElement[]; - onCancel?: () => void; -} diff --git a/frontend/src/metabase/components/form/CustomForm/CustomFormFooter/index.ts b/frontend/src/metabase/components/form/CustomForm/CustomFormFooter/index.ts deleted file mode 100644 index c404ea9994c6db74449bfa4654f1dbc714350ede..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/CustomForm/CustomFormFooter/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./CustomFormFooter"; -export * from "./CustomFormFooterTypes"; diff --git a/frontend/src/metabase/components/form/CustomForm/CustomFormMessage.tsx b/frontend/src/metabase/components/form/CustomForm/CustomFormMessage.tsx deleted file mode 100644 index b293875b8376287852f49e0b558f079f80a60963..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/CustomForm/CustomFormMessage.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react"; -import _ from "underscore"; - -import FormMessage from "metabase/components/form/FormMessage"; - -import { CustomFormLegacyContext, LegacyContextTypes } from "./types"; - -export interface CustomFormMessageProps { - className?: string; - noPadding?: boolean; -} - -function CustomFormMessage({ - error, - ...props -}: CustomFormMessageProps & CustomFormLegacyContext) { - if (error) { - return <FormMessage {...props} message={error} />; - } - return null; -} - -/** - * @deprecated - */ -const CustomFormMessageLegacyContext = ( - props: CustomFormMessageProps, - context: CustomFormLegacyContext, -) => <CustomFormMessage {...props} {...context} />; - -CustomFormMessageLegacyContext.contextTypes = _.pick( - LegacyContextTypes, - "error", -); - -export default CustomFormMessageLegacyContext; diff --git a/frontend/src/metabase/components/form/CustomForm/CustomFormSection.tsx b/frontend/src/metabase/components/form/CustomForm/CustomFormSection.tsx deleted file mode 100644 index edc83572a40c31afd355b8d5523c555cec678ef0..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/CustomForm/CustomFormSection.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from "react"; - -import DisclosureTriangle from "metabase/components/DisclosureTriangle"; - -import { useToggle } from "metabase/hooks/use-toggle"; - -interface SectionProps { - title?: string; - children: React.ReactNode; -} - -function StandardSection({ title, children }: SectionProps) { - return ( - <section className="mb4"> - {title && <h2 className="mb2">{title}</h2>} - {children} - </section> - ); -} - -function CollapsibleSection({ title, children }: SectionProps) { - const [isExpanded, { toggle: handleToggle }] = useToggle(false); - return ( - <section className="mb4"> - <div - className="mb2 flex align-center cursor-pointer text-brand-hover" - onClick={handleToggle} - > - <DisclosureTriangle className="mr1" open={isExpanded} /> - <h3>{title}</h3> - </div> - <div className={isExpanded ? undefined : "hide"}>{children}</div> - </section> - ); -} - -interface CustomFormSectionProps extends SectionProps { - collapsible?: boolean; -} - -/** - * @deprecated - */ -function CustomFormSection({ collapsible, ...props }: CustomFormSectionProps) { - const Section = collapsible ? CollapsibleSection : StandardSection; - return <Section {...props} />; -} - -export default CustomFormSection; diff --git a/frontend/src/metabase/components/form/CustomForm/CustomFormSubmit.tsx b/frontend/src/metabase/components/form/CustomForm/CustomFormSubmit.tsx deleted file mode 100644 index f3cbe5b5b3c579e6b8387ce5846bc300feb5312f..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/CustomForm/CustomFormSubmit.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from "react"; -import { t } from "ttag"; -import _ from "underscore"; - -import ActionButton from "metabase/components/ActionButton"; - -import { CustomFormLegacyContext, LegacyContextTypes } from "./types"; - -export interface CustomFormSubmitProps { - children: React.ReactNode; - - // ActionButton props - fullWidth?: boolean; -} - -function CustomFormSubmit({ - submitting, - invalid, - pristine, - handleSubmit, - submitTitle, - renderSubmit, - disablePristineSubmit, - children, - ...props -}: CustomFormSubmitProps & CustomFormLegacyContext) { - const title = children || submitTitle || t`Submit`; - const canSubmit = !( - submitting || - invalid || - (pristine && disablePristineSubmit) - ); - - if (renderSubmit) { - return renderSubmit({ title, canSubmit, handleSubmit }); - } - - return ( - <ActionButton - normalText={title} - activeText={title} - failedText={t`Failed`} - successText={t`Success`} - primary={canSubmit} - disabled={!canSubmit} - {...props} - type="submit" - actionFn={handleSubmit} - /> - ); -} - -/** - * @deprecated - */ -const CustomFormSubmitLegacyContext = ( - props: CustomFormSubmitProps, - context: CustomFormLegacyContext, -) => <CustomFormSubmit {...props} {...context} />; - -CustomFormSubmitLegacyContext.contextTypes = _.pick( - LegacyContextTypes, - "values", - "submitting", - "invalid", - "pristine", - "handleSubmit", - "submitTitle", - "renderSubmit", - "disablePristineSubmit", -); - -export default CustomFormSubmitLegacyContext; diff --git a/frontend/src/metabase/components/form/CustomForm/Form.tsx b/frontend/src/metabase/components/form/CustomForm/Form.tsx deleted file mode 100644 index 4d4233a3e411e80e1f014e668e6f084f597670a8..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/CustomForm/Form.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from "react"; -import _ from "underscore"; -import { CustomFormLegacyContext, LegacyContextTypes } from "./types"; - -type Props = { - children: React.ReactNode; -}; - -function Form({ - children, - handleSubmit, - className, - style, -}: Props & CustomFormLegacyContext) { - return ( - <form onSubmit={handleSubmit} className={className} style={style}> - {children} - </form> - ); -} - -/** - * @deprecated - */ -const FormUsingLegacyContext = ( - props: Props, - context: CustomFormLegacyContext, -) => <Form {...props} {...context} />; - -FormUsingLegacyContext.contextTypes = _.pick( - LegacyContextTypes, - "handleSubmit", - "className", - "style", -); - -export default FormUsingLegacyContext; diff --git a/frontend/src/metabase/components/form/CustomForm/index.ts b/frontend/src/metabase/components/form/CustomForm/index.ts deleted file mode 100644 index d7501a864a644ec20731b5ad0c4fafacecce9b42..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/CustomForm/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { default as CustomFormField } from "./CustomFormField"; -export { default as CustomFormFooter } from "./CustomFormFooter"; -export { default as CustomFormMessage } from "./CustomFormMessage"; -export { default as CustomFormSection } from "./CustomFormSection"; -export { default as CustomFormSubmit } from "./CustomFormSubmit"; - -export { default } from "./CustomForm"; diff --git a/frontend/src/metabase/components/form/CustomForm/types.ts b/frontend/src/metabase/components/form/CustomForm/types.ts deleted file mode 100644 index e152ecb0725aa6446d7f4a62cbdb3a50e5dd78f4..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/CustomForm/types.ts +++ /dev/null @@ -1,96 +0,0 @@ -import PropTypes from "prop-types"; -import { - BaseFieldDefinition, - FieldName, - DefaultFieldValue, - FieldValues, - FormFieldDefinition, - FormField, - PopulatedFormObject, -} from "metabase-types/forms/legacy"; - -export interface BaseFormProps { - formKey?: string; - formObject: PopulatedFormObject; - - formFields: FormFieldDefinition[]; - formFieldsByName: Record<FieldName, FormFieldDefinition>; - disablePristineSubmit?: boolean; - - fields: Record<string, FormField>; - values: FieldValues; - errors: Record<FieldName, string>; - - active?: boolean; - asyncValidating?: boolean; - dirty: boolean; - error?: string; - invalid: boolean; - overwriteOnInitialValuesChange?: boolean; - pristine: boolean; - readonly?: boolean; - submitFailed: boolean; - submitting: boolean; - valid: boolean; - - asyncValidate: () => void; - destroyForm: () => void; - handleSubmit: () => void; - initializeForm: () => void; - onChangeField: (fieldName: FieldName, value: DefaultFieldValue) => void; - onSubmitSuccess: () => void; - resetForm: () => void; -} - -type RenderSubmitProps = { - title: React.ReactNode; - canSubmit: boolean; - handleSubmit: () => void; -}; - -export interface OptionalFormViewProps { - submitTitle?: string; - renderSubmit?: (props: RenderSubmitProps) => JSX.Element; - className?: string; - style?: React.CSSProperties; -} - -export interface CustomFormLegacyContext - extends OptionalFormViewProps, - Pick< - BaseFormProps, - | "formFields" - | "formFieldsByName" - | "disablePristineSubmit" - | "handleSubmit" - | "fields" - | "values" - | "submitting" - | "invalid" - | "pristine" - | "error" - | "onChangeField" - > {} - -export interface FormContainerLegacyContext { - registerFormField: (fieldDef: BaseFieldDefinition) => void; - unregisterFormField: (fieldDef: BaseFieldDefinition) => void; -} - -export const LegacyContextTypes = { - handleSubmit: PropTypes.func, - submitTitle: PropTypes.string, - renderSubmit: PropTypes.func, - className: PropTypes.string, - style: PropTypes.object, - fields: PropTypes.object, - formFields: PropTypes.array, - formFieldsByName: PropTypes.object, - values: PropTypes.object, - submitting: PropTypes.bool, - invalid: PropTypes.bool, - pristine: PropTypes.bool, - error: PropTypes.string, - onChangeField: PropTypes.func, - disablePristineSubmit: PropTypes.bool, -}; diff --git a/frontend/src/metabase/components/form/FormField/FormField.styled.tsx b/frontend/src/metabase/components/form/FormField/FormField.styled.tsx deleted file mode 100644 index f5e8e420e1a941881b08132adfd410b36f27171d..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/FormField/FormField.styled.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import styled from "@emotion/styled"; -import { css } from "@emotion/react"; -import { color } from "metabase/lib/colors"; -import Icon from "metabase/components/Icon"; - -export const FieldRow = styled.div` - display: flex; - align-items: center; - margin-bottom: 0.5em; -`; - -export const Label = styled.label<{ - horizontal?: boolean; - standAlone?: boolean; -}>` - margin-bottom: 0; - ${props => - props.horizontal && - css` - margin-right: auto; - `} - ${props => - props.standAlone && - css` - margin-top: 0.8em; - `} -`; - -Label.defaultProps = { className: "Form-label" }; - -export const InfoIcon = styled(Icon)` - margin-left: 8px; - color: ${color("bg-dark")}; - - &:hover { - color: ${() => color("brand")}; - } -`; - -export const InfoLabel = styled.span` - color: ${color("text-medium")}; - font-size: 0.88em; - margin-left: auto; - cursor: default; -`; - -export const FieldContainer = styled.div<{ - horizontal?: boolean; - align?: "left" | "right"; -}>` - width: 100%; - margin-right: ${props => (props.horizontal ? "1rem" : "")}; - margin-left: ${props => (props.align === "left" ? "0.5rem" : "")}; -`; - -export const InputContainer = styled.div` - flex-shrink: 0; -`; diff --git a/frontend/src/metabase/components/form/FormField/FormField.tsx b/frontend/src/metabase/components/form/FormField/FormField.tsx deleted file mode 100644 index 1c2aed106c8f150f0cc2c7a304bf19b9e0078b83..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/FormField/FormField.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React from "react"; - -import { - FieldName, - FieldValues, - FormField as FormFieldType, - BaseFieldDefinition, - FormFieldDefinition, -} from "metabase-types/forms/legacy"; - -import FormFieldView from "./FormFieldView"; - -type ReduxFormProps = Pick<FormFieldType, "name"> & - Partial<Pick<FormFieldType, "error" | "visited" | "active">>; - -interface FormFieldProps extends BaseFieldDefinition, ReduxFormProps { - field: FormFieldType; - formField: FormFieldDefinition; - values: FieldValues; - className?: string; - children: React.ReactNode; - onChangeField: (fieldName: FieldName, value: unknown) => void; -} - -const ALL_DOT_CHARS = /\./g; - -function getFieldId(formFieldName: FieldName) { - return `formField-${formFieldName.replace(ALL_DOT_CHARS, "-")}`; -} - -function getDescriptionPositionPropValue( - descriptionPosition?: "top" | "bottom", - formField?: FormFieldDefinition, -) { - return descriptionPosition ?? formField?.descriptionPosition ?? "top"; -} - -function getHiddenPropValue(hidden?: boolean, formField?: FormFieldDefinition) { - if (typeof hidden === "boolean") { - return hidden; - } - if (formField) { - return formField.hidden || formField.type === "hidden"; - } - return false; -} - -function getHorizontalPropValue( - horizontal?: boolean, - formField?: FormFieldDefinition, -) { - if (typeof horizontal === "boolean") { - return horizontal; - } - if (formField) { - return formField.horizontal || formField.type === "boolean"; - } - return false; -} - -/** - * @deprecated - */ -function FormField({ - className, - formField, - children, - ...props -}: FormFieldProps) { - const title = props.title ?? formField?.title; - const type = props.type ?? formField.type; - const description = props.description ?? formField?.description; - const descriptionPosition = getDescriptionPositionPropValue( - props.descriptionPosition, - formField, - ); - - const info = props.info ?? formField?.info; - const infoLabel = props.infoLabel ?? formField?.infoLabel; - const infoLabelTooltip = - props.infoLabelTooltip ?? formField?.infoLabelTooltip; - - const align = props.align ?? formField?.align ?? "right"; - const hidden = getHiddenPropValue(props.hidden, formField); - const horizontal = getHorizontalPropValue(props.horizontal, formField); - - const isToggle = type === "boolean"; - const standAloneLabel = isToggle && align === "right" && !description; - - if (hidden) { - return null; - } - - const { - name, - error: errorProp, - visited, - active, - } = { - ...(props.field || {}), - ...props, - }; - - const shouldShowError = visited && !active; - const error = !shouldShowError ? undefined : errorProp; - - return ( - <FormFieldView - fieldId={getFieldId(name)} - className={className} - name={name} - error={error} - title={title} - description={description} - descriptionPosition={descriptionPosition} - info={info} - infoLabel={infoLabel} - infoLabelTooltip={infoLabelTooltip} - align={align} - standAloneLabel={standAloneLabel} - horizontal={horizontal} - > - {children} - </FormFieldView> - ); -} - -export default FormField; diff --git a/frontend/src/metabase/components/form/FormField/FormFieldDescription.tsx b/frontend/src/metabase/components/form/FormField/FormFieldDescription.tsx deleted file mode 100644 index 8f0f1af812889f476a53d566ab282afd7b251da8..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/FormField/FormFieldDescription.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from "react"; - -interface FormFieldDescriptionProps { - className: string; - description: string; -} - -export const FormFieldDescription = ({ - className, - description, -}: FormFieldDescriptionProps) => { - if (typeof description === "string") { - return ( - <div - className={className} - dangerouslySetInnerHTML={{ - __html: description, - }} - /> - ); - } - return <div className={className}>{description}</div>; -}; diff --git a/frontend/src/metabase/components/form/FormField/FormFieldView.tsx b/frontend/src/metabase/components/form/FormField/FormFieldView.tsx deleted file mode 100644 index d595bb9d350a07ac2958b68fbef880e378d66913..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/FormField/FormFieldView.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from "react"; -import cx from "classnames"; - -import Tooltip from "metabase/components/Tooltip"; - -import { BaseFieldDefinition } from "metabase-types/forms/legacy"; - -import { FormFieldDescription } from "./FormFieldDescription"; -import { - FieldRow, - Label, - InfoIcon, - InputContainer, - FieldContainer, - InfoLabel, -} from "./FormField.styled"; - -interface FormFieldViewProps extends BaseFieldDefinition { - fieldId: string; - error?: string; - className?: string; - standAloneLabel?: boolean; - children: React.ReactNode; -} - -function FormFieldView({ - fieldId, - className, - name, - error, - title, - description, - descriptionPosition, - info, - infoLabel, - infoLabelTooltip, - align, - horizontal, - standAloneLabel, - children, -}: FormFieldViewProps) { - const rootClassNames = cx("Form-field", className, { - "Form--fieldError": !!error, - flex: horizontal, - }); - - return ( - <div id={fieldId} className={rootClassNames}> - {align === "left" && <InputContainer>{children}</InputContainer>} - {(title || description) && ( - <FieldContainer horizontal={horizontal} align={align}> - <FieldRow> - {title && ( - <Label - id={`${name}-label`} - htmlFor={name} - horizontal={horizontal} - standAlone={standAloneLabel} - > - {title} - {error && <span className="text-error">: {error}</span>} - </Label> - )} - {info && ( - <Tooltip tooltip={info}> - <InfoIcon name="info" size={12} /> - </Tooltip> - )} - {infoLabel && ( - <Tooltip tooltip={infoLabelTooltip} maxWidth="100%"> - <InfoLabel>{infoLabel}</InfoLabel> - </Tooltip> - )} - </FieldRow> - {description && descriptionPosition === "top" && ( - <FormFieldDescription className="mb1" description={description} /> - )} - </FieldContainer> - )} - {align !== "left" && <InputContainer>{children}</InputContainer>} - {description && descriptionPosition === "bottom" && ( - <FormFieldDescription className="mt1" description={description} /> - )} - </div> - ); -} - -export default FormFieldView; diff --git a/frontend/src/metabase/components/form/FormField/index.ts b/frontend/src/metabase/components/form/FormField/index.ts deleted file mode 100644 index e2d6f74d4381bd3560ca0441c57cba1acd4dc081..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/FormField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./FormField"; diff --git a/frontend/src/metabase/components/form/FormikCustomForm/CustomFormField.tsx b/frontend/src/metabase/components/form/FormikCustomForm/CustomFormField.tsx index 072678a5f5b985ec9fb294f07d7fb82a98ed3cec..0c13bcc1a9da042136edc652d8d9921c33edc100 100644 --- a/frontend/src/metabase/components/form/FormikCustomForm/CustomFormField.tsx +++ b/frontend/src/metabase/components/form/FormikCustomForm/CustomFormField.tsx @@ -1,5 +1,4 @@ import React, { useCallback, useMemo } from "react"; -import { getIn } from "icepick"; import _ from "underscore"; import { isCustomWidget } from "metabase-types/guards"; diff --git a/frontend/src/metabase/components/form/StandardForm.tsx b/frontend/src/metabase/components/form/StandardForm.tsx deleted file mode 100644 index 2e9df996f60019bb99884bcd3346bc8bad94611c..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/components/form/StandardForm.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from "react"; -import { t } from "ttag"; - -import { BaseFormProps } from "./CustomForm/types"; -import { CustomFormFooterProps } from "./CustomForm/CustomFormFooter"; -import CustomForm from "./CustomForm"; - -interface Props extends BaseFormProps, CustomFormFooterProps { - submitFullWidth?: boolean; - onClose?: () => void; -} - -/** - * @deprecated - */ -const StandardForm = ({ - submitTitle, - submitFullWidth, - onClose, - ...props -}: Props) => ( - <CustomForm {...props}> - {({ values, formFields, Form, FormField, FormFooter }) => ( - <Form> - {formFields.map(formField => ( - <FormField key={formField.name} name={formField.name} /> - ))} - <FormFooter - isModal={props.isModal} - footerExtraButtons={props.footerExtraButtons} - onCancel={onClose} - submitTitle={ - submitTitle || (values.id != null ? t`Update` : t`Create`) - } - fullWidth={submitFullWidth} - /> - </Form> - )} - </CustomForm> -); - -export default StandardForm; diff --git a/frontend/src/metabase/containers/Form.info.js b/frontend/src/metabase/containers/Form.info.js deleted file mode 100644 index 92187203ae169a98f95934f475b5131f0f158f7e..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/containers/Form.info.js +++ /dev/null @@ -1,49 +0,0 @@ -import React from "react"; -import Form, { FormField, FormFooter } from "metabase/containers/Form"; -import validate from "metabase/lib/validate"; - -const FORM = { - fields: [ - { - name: "email", - placeholder: "bob@metabase.com", - validate: validate.required().email(), - }, - { - name: "password", - type: "password", - validate: validate.required().passwordComplexity(), - }, - ], -}; - -export const component = Form; -export const category = "form"; -export const description = `A standard form component.`; -export const examples = { - "with form prop": ( - <Form form={FORM} onSubmit={values => alert(JSON.stringify(values))} /> - ), - "with inline fields": ( - <Form onSubmit={values => alert(JSON.stringify(values))}> - <FormField - name="email" - placeholder="bob@metabase.com" - validate={validate.required()} - /> - <FormField - name="password" - type="password" - validate={validate.required().passwordComplexity()} - /> - <FormFooter /> - </Form> - ), - "with form prop and custom layout fields": ( - <Form form={FORM} onSubmit={values => alert(JSON.stringify(values))}> - <FormField name="password" /> - <FormField name="email" /> - <FormFooter /> - </Form> - ), -}; diff --git a/frontend/src/metabase/containers/Form.jsx b/frontend/src/metabase/containers/Form.jsx deleted file mode 100644 index 53ad7c61952b2783bc5a2130c4229ebe320c0a37..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/containers/Form.jsx +++ /dev/null @@ -1,299 +0,0 @@ -/* eslint-disable react/prop-types */ -import React from "react"; -import PropTypes from "prop-types"; - -import { connect } from "react-redux"; -import { createSelector } from "reselect"; -import { reduxForm, getValues, initialize, change } from "redux-form"; -import { assocIn } from "icepick"; -import _ from "underscore"; -import { t } from "ttag"; - -import CustomForm from "metabase/components/form/CustomForm"; -import StandardForm from "metabase/components/form/StandardForm"; - -export { - CustomFormField as FormField, - CustomFormSubmit as FormSubmit, - CustomFormMessage as FormMessage, - CustomFormFooter as FormFooter, - CustomFormSection as FormSection, -} from "metabase/components/form/CustomForm"; - -import { makeFormObject, getValue } from "./formUtils"; - -let FORM_ID = 0; -// use makeMapStateToProps so each component gets it's own unique formId -const makeMapStateToProps = () => { - const formId = FORM_ID++; - return (state, ownProps) => { - const formName = ownProps.formName || `form_${formId}`; - return { - formName: formName, - values: getValues(state.form[formName]), - }; - }; -}; - -const ReduxFormComponent = reduxForm()( - ({ handleSubmit, submitState, ...props }) => { - const FormComponent = - props.formComponent || (props.children ? CustomForm : StandardForm); - return ( - <FormComponent - {...props} - handleSubmit={async (...args) => { - await handleSubmit(...args); - // normally handleSubmit swallows the result/error, but we want to make it available to things like ActionButton - if (submitState.failed) { - throw submitState.result; - } else { - return submitState.result; - } - }} - /> - ); - }, -); - -/** - * @deprecated - */ -class Form extends React.Component { - _state = { - submitting: false, - failed: false, - result: undefined, - }; - - constructor(props) { - super(props); - - this.state = { - // fields defined via child FormField elements - inlineFields: {}, - }; - - // memoized functions - const getFormDefinition = createSelector( - [ - (state, props) => props.form, - (state, props) => props.validate, - (state, props) => props.initial, - (state, props) => props.normalize, - (state, props) => props.fields, - (state, props) => state.inlineFields, - ], - (form, validate, initial, normalize, fields, inlineFields) => { - // use props.form if provided, otherwise generate from props.{fields,initial,validate,normalize} - const formDef = form || { - validate, - initial, - normalize, - fields: fields || Object.values(inlineFields), - }; - return { - ...formDef, - fields: (...args) => - // merge inlineFields in - getValue(formDef.fields, ...args).map(fieldDef => ({ - ...fieldDef, - ...inlineFields[fieldDef.name], - })), - }; - }, - ); - const getFormObject = createSelector([getFormDefinition], formDef => - makeFormObject(formDef), - ); - const getInitialValues = createSelector( - [ - getFormObject, - (state, props) => props.initialValues || {}, - (state, props) => props.values || {}, - ], - (formObject, initialValues, values) => { - const formInitialValues = formObject.initial(values); - // merge nested fields: {details: {foo: 123}} + {details: {bar: 321}} => {details: {foo: 123, bar: 321}} - const merged = {}; - for (const k of Object.keys(initialValues)) { - if ( - typeof initialValues[k] === "object" && - typeof formInitialValues[k] === "object" - ) { - merged[k] = { ...formInitialValues[k], ...initialValues[k] }; - } - } - return { - ...initialValues, - ...formInitialValues, - ...merged, - }; - }, - ); - const getFieldNames = createSelector( - [getFormObject, getInitialValues, (state, props) => props.values || {}], - (formObject, initialValues, values) => - formObject.fieldNames({ - ...initialValues, - ...values, - }), - ); - this._getFormObject = () => getFormObject(this.state, this.props); - this._getFormDefinition = () => getFormDefinition(this.state, this.props); - this._getInitialValues = () => getInitialValues(this.state, this.props); - this._getFieldNames = () => getFieldNames(this.state, this.props); - } - - static propTypes = { - form: PropTypes.object, - onSubmit: PropTypes.func.isRequired, - initialValues: PropTypes.object, - formName: PropTypes.string, - overwriteOnInitialValuesChange: PropTypes.bool, - }; - - static defaultProps = { - overwriteOnInitialValuesChange: false, - }; - - static childContextTypes = { - registerFormField: PropTypes.func, - unregisterFormField: PropTypes.func, - fieldNames: PropTypes.array, - }; - - componentDidUpdate(prevProps, prevState) { - // HACK: when new fields are added they aren't initialized with their intialValues, so we have to force it here: - const newFields = _.difference( - Object.keys(this.state.inlineFields), - Object.keys(prevState.inlineFields), - ); - if (newFields.length > 0) { - this.props.dispatch( - initialize(this.props.formName, this._getInitialValues(), newFields), - ); - } - this.props.onChange?.(this.props.values); - } - - _registerFormField = field => { - if (!_.isEqual(this.state.inlineFields[field.name], field)) { - this.setState(prevState => - assocIn(prevState, ["inlineFields", field.name], field), - ); - } - }; - - _unregisterFormField = field => { - if (this.state.inlineFields[field.name]) { - // this.setState(prevState => - // dissocIn(prevState, ["inlineFields", field.name]), - // ); - } - }; - - getChildContext() { - return { - registerFormField: this._registerFormField, - unregisterFormField: this._unregisterFormField, - }; - } - - _validate = (values, props) => { - // HACK: clears failed state for global error - if (!this._state.submitting && this._state.failed) { - this._state.failed = false; - props.stopSubmit(); - } - const formObject = this._getFormObject(); - return formObject.validate(values, props); - }; - - _onSubmit = async values => { - const formObject = this._getFormObject(); - // HACK: clears failed state for global error - this._state.submitting = true; - try { - const normalized = formObject.normalize(values); - return (this._state.result = await this.props.onSubmit(normalized)); - } catch (error) { - console.error("Form submission error:", error); - this._state.failed = true; - this._state.result = error; - // redux-form expects { "FIELD NAME": "FIELD ERROR STRING" } or {"_error": "GLOBAL ERROR STRING" } - if (error && error.data && error.data.errors) { - try { - // HACK: blur the current element to ensure we show the error - document.activeElement.blur(); - } catch (e) {} - // if there are errors for fields we don't know about then inject a generic top-level _error key - const fieldNames = new Set(this._getFieldNames()); - const errorNames = Object.keys(error.data.errors); - const hasUnknownFields = errorNames.some(name => !fieldNames.has(name)); - throw { - _error: - error.data?.message || - error.message || - (hasUnknownFields ? t`An error occurred` : null), - ...error.data.errors, - }; - } else if (error) { - throw { - _error: - error.data?.message || - error.message || - error.data || - t`An error occurred`, - }; - } - } finally { - setTimeout(() => (this._state.submitting = false)); - } - }; - - _handleSubmitSuccess = async action => { - if (this.props.onSubmitSuccess) { - await this.props.onSubmitSuccess(action); - } - this.props.dispatch( - initialize(this.props.formName, this.props.values, this._getFieldNames()), - ); - }; - - _handleChangeField = (fieldName, value) => { - return this.props.dispatch(change(this.props.formName, fieldName, value)); - }; - - render() { - // eslint-disable-next-line - const { formName, overwriteOnInitialValuesChange } = this.props; - const formObject = this._getFormObject(); - const initialValues = this._getInitialValues(); - const fieldNames = this._getFieldNames(); - return ( - <ReduxFormComponent - {...this.props} - overwriteOnInitialValuesChange={overwriteOnInitialValuesChange} - formObject={formObject} - // redux-form props: - form={formName} - fields={fieldNames} - initialValues={initialValues} - validate={this._validate} - onSubmit={this._onSubmit} - onSubmitSuccess={this._handleSubmitSuccess} - onChangeField={this._handleChangeField} - // HACK: _state is a mutable object so we can pass by reference into the ReduxFormComponent - submitState={this._state} - /> - ); - } -} - -/** - * @deprecated - */ -const DeprecatedForm = connect(makeMapStateToProps)(Form); - -export default DeprecatedForm; diff --git a/frontend/src/metabase/entities/containers/EntityForm.jsx b/frontend/src/metabase/entities/containers/EntityForm.jsx index 23d39bb811891fc5c2dd43b47071ddac9893b31d..1fbda889dca5a2b7071b1e54c6d89a61ead96419 100644 --- a/frontend/src/metabase/entities/containers/EntityForm.jsx +++ b/frontend/src/metabase/entities/containers/EntityForm.jsx @@ -2,7 +2,6 @@ import React from "react"; import { t } from "ttag"; -import LegacyForm from "metabase/containers/Form"; import Form from "metabase/containers/FormikForm"; import ModalContent from "metabase/components/ModalContent"; @@ -22,21 +21,8 @@ const EForm = ({ create, onSubmit = object => (object.id ? update(object) : create(object)), onSaved, - useLegacyForm, ...props }) => { - if (useLegacyForm) { - return ( - <LegacyForm - {...props} - form={form} - initialValues={entityObject} - onSubmit={onSubmit} - onSubmitSuccess={action => onSaved && onSaved(action.payload.object)} - /> - ); - } - return ( <Form {...props} diff --git a/frontend/src/metabase/entities/databases.js b/frontend/src/metabase/entities/databases.js index bf6cfe5eeff21735fd5e5fcfae1494298dd7cf1f..6925a42ef7161146a009812058ca0e73b0d84735 100644 --- a/frontend/src/metabase/entities/databases.js +++ b/frontend/src/metabase/entities/databases.js @@ -14,8 +14,6 @@ import Schemas from "metabase/entities/schemas"; import { getMetadata, getFields } from "metabase/selectors/metadata"; -import forms from "./databases/forms"; - // OBJECT ACTIONS export const FETCH_DATABASE_METADATA = "metabase/entities/database/FETCH_DATABASE_METADATA"; @@ -89,9 +87,6 @@ const Databases = createEntity({ }), ), }, - - // FORM - forms, }); export default Databases; diff --git a/frontend/src/metabase/entities/databases/big-query-fields.js b/frontend/src/metabase/entities/databases/big-query-fields.js deleted file mode 100644 index d616c87ac2aa61e189e4efac251775136edd6909..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/entities/databases/big-query-fields.js +++ /dev/null @@ -1,179 +0,0 @@ -/* eslint-disable react/prop-types */ -import React from "react"; -import { t, jt } from "ttag"; - -import { color } from "metabase/lib/colors"; -import ExternalLink from "metabase/core/components/ExternalLink"; -import Link from "metabase/core/components/Link"; - -import MetadataSyncScheduleWidget from "metabase/admin/databases/components/widgets/MetadataSyncScheduleWidget"; -import CacheFieldValuesScheduleWidget from "metabase/admin/databases/components/widgets/CacheFieldValuesScheduleWidget"; - -const SERVICE_ACCOUNT_DOCS_URL = - "https://developers.google.com/identity/protocols/OAuth2ServiceAccount"; -function BigQueryServiceAccountToggle({ - field: { value, onChange }, - values: { details }, -}) { - const saLink = ( - <ExternalLink - href={SERVICE_ACCOUNT_DOCS_URL} - >{t`Service Accounts`}</ExternalLink> - ); - - const hasNoOldStyleData = ["client-id", "client-secret"].every( - key => details[key] == null, - ); - - return (!value && hasNoOldStyleData) || value === true ? ( - <div> - <p>{jt`Metabase connects to Big Query via ${saLink}.`}</p> - {value === true && ( - <Link className="link" onClick={() => onChange(false)}> - {t`Continue using an OAuth application to connect`} - </Link> - )} - </div> - ) : ( - <div - style={{ - borderLeftWidth: 3, - borderLeftStyle: "solid", - borderLeftColor: color("brand"), - }} - className="pl1" - > - <p> - {jt`We recommend switching to use ${saLink} instead of an OAuth application to connect to BigQuery`} - </p> - <Link - className="link" - onClick={() => onChange(true)} - >{t`Connect to a Service Account instead`}</Link> - </div> - ); -} - -export default function getFieldsForBigQuery(details) { - const useServiceAccount = - // If this field is unset, show the service account form unless an old-style connection exists. - details["use-service-account"] == null - ? ["client-id", "client-secret"].every(key => details[key] == null) - : details["use-service-account"]; - return { - "details-fields": [ - { - name: "use-service-account", - type: BigQueryServiceAccountToggle, - hidden: true, - }, - ...(useServiceAccount - ? [] - : [ - { - name: "project-id", - "display-name": t`Project ID`, - "helper-text": t`Project ID to be used for authentication. You can omit this field if you are only querying datasets owned by your organization.`, - placeholder: "1w08oDRKPrOqBt06yxY8uiCz2sSvOp3u", - required: true, - }, - ]), - { - name: "dataset-id", - "display-name": t`Dataset ID`, - "helper-text": t`Make sure to leave out the Project ID prefix in "project_name:dataset_id" and only enter “dataset_idâ€`, - placeholder: "dataset_id", - required: true, - }, - ...(useServiceAccount - ? [ - { - name: "service-account-json", - "display-name": t`Service account JSON file`, - "helper-text": t`This JSON file contains the credentials Metabase needs to read and query your dataset.`, - type: "textFile", - required: true, - }, - ] - : [ - { - name: "client-id", - "display-name": t`Client ID`, - placeholder: - "1201327674725-y6ferb0feo1hfssr7t40o4aikqll46d4.apps.googleusercontent.com", - required: true, - }, - { - name: "client-secret", - "display-name": t`Client Secret`, - placeholder: "dJNi4utWgMzyIFo2JbnsK6Np", - required: true, - }, - { - name: "auth-code", - "display-name": t`Auth Code`, - placeholder: "4/HSk-KtxkSzTt61j5zcbee2Rmm5JHkRFbL5gD5lgkXek", - required: true, - }, - ]), - { - name: "advanced-options", - type: "section", - default: false, - }, - { - name: "use-jvm-timezone", - "display-name": t`Use the Java Virtual Machine (JVM) timezone`, - default: false, - type: "boolean", - "visible-if": { "advanced-options": true }, - }, - { - name: "include-user-id-and-hash", - "display-name": t`Include User ID and query hash in queries`, - default: true, - type: "boolean", - "visible-if": { "advanced-options": true }, - }, - { - name: "auto_run_queries", - type: "boolean", - default: true, - "display-name": t`Rerun queries for simple explorations`, - description: t`We execute the underlying query when you explore data using Summarize or Filter. This is on by default but you can turn it off if performance is slow.`, - "visible-if": { "advanced-options": true }, - }, - { - name: "let-user-control-scheduling", - type: "boolean", - "display-name": t`Choose when syncs and scans happen`, - description: t`By default, Metabase does a lightweight hourly sync and an intensive daily scan of field values. If you have a large database, turn this on to make changes.`, - "visible-if": { "advanced-options": true }, - }, - { - name: "schedules.metadata_sync", - "display-name": t`Database syncing`, - type: MetadataSyncScheduleWidget, - description: t`This is a lightweight process that checks for updates to this database’s schema. In most cases, you should be fine leaving this set to sync hourly.`, - "visible-if": { "let-user-control-scheduling": true }, - }, - { - name: "schedules.cache_field_values", - "display-name": t`Scanning for Filter Values`, - type: CacheFieldValuesScheduleWidget, - description: - t`Metabase can scan the values present in each field in this database to enable checkbox filters in dashboards and questions. This can be a somewhat resource-intensive process, particularly if you have a very large database.` + - " " + - t`When should Metabase automatically scan and cache field values?`, - "visible-if": { "let-user-control-scheduling": true }, - }, - { - name: "refingerprint", - type: "boolean", - "display-name": t`Periodically refingerprint tables`, - description: t`This enables Metabase to scan for additional field values during syncs allowing smarter behavior, like improved auto-binning on your bar charts.`, - "visible-if": { "advanced-options": true }, - }, - ], - }; -} diff --git a/frontend/src/metabase/entities/databases/forms.js b/frontend/src/metabase/entities/databases/forms.js deleted file mode 100644 index 0193db9da655159f1fae400a87f7ef38eab8fd3d..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/entities/databases/forms.js +++ /dev/null @@ -1,401 +0,0 @@ -import React from "react"; -import { jt, t } from "ttag"; - -import MetabaseSettings from "metabase/lib/settings"; -import { getElevatedEngines } from "metabase/lib/engine"; -import ExternalLink from "metabase/core/components/ExternalLink"; -import { PLUGIN_CACHING } from "metabase/plugins"; - -import MetadataSyncScheduleWidget from "metabase/admin/databases/components/widgets/MetadataSyncScheduleWidget"; -import CacheFieldValuesScheduleWidget from "metabase/admin/databases/components/widgets/CacheFieldValuesScheduleWidget"; -import EngineWidget from "metabase/admin/databases/components/widgets/EngineWidget"; -import getFieldsForMongo from "./mongo-fields"; -import getFieldsForBigQuery from "./big-query-fields"; - -const DATABASE_DETAIL_OVERRIDES = { - "tunnel-enabled": () => ({ - title: t`Use an SSH-tunnel`, - description: getSshDescription(), - }), - "use-jvm-timezone": () => ({ - title: t`Use the Java Virtual Machine (JVM) timezone`, - description: t`We suggest you leave this off unless you plan on doing a lot of manual timezone casting with this data.`, - }), - "include-user-id-and-hash": () => ({ - title: t`Include User ID and query hash in queries`, - description: t`This can be useful for auditing and debugging, but prevents BigQuery from caching results and may increase your costs.`, - }), - "use-srv": () => ({ - title: t`Connect using DNS SRV`, - description: t`If you're connecting to an Atlas cluster, you might need to turn this on. Note that your provided host must be a fully qualified domain name.`, - }), - "client-id": (engine, details) => ({ - description: getClientIdDescription(engine, details), - }), - "auth-code": (engine, details) => ({ - description: ( - <div> - <div>{getAuthCodeLink(engine, details)}</div> - </div> - ), - }), - "service-account-json": (engine, details, id) => ({ - validate: value => { - // this field is only required if this is a new entry - if (id) { - return null; - } - - if (!value) { - return t`required`; - } - try { - JSON.parse(value); - } catch (e) { - return t`invalid JSON`; - } - return null; - }, - }), - "tunnel-private-key": () => ({ - title: t`SSH private key`, - placeholder: t`Paste the contents of your ssh private key here`, - type: "text", - }), - "tunnel-private-key-passphrase": () => ({ - title: t`Passphrase for the SSH private key`, - }), - "tunnel-auth-option": () => ({ - title: t`SSH authentication`, - options: [ - { name: t`SSH Key`, value: "ssh-key" }, - { name: t`Password`, value: "password" }, - ], - }), - "ssl-cert": () => ({ - title: t`Server SSL certificate chain`, - placeholder: t`Paste the contents of the server's SSL certificate chain here`, - type: "text", - }), - "ssl-key-options": engine => ({ - description: getSslKeyOptionsDescription(engine), - }), - "schedules.metadata_sync": () => ({ - name: "schedules.metadata_sync", - type: MetadataSyncScheduleWidget, - normalize: value => value, - }), - "schedules.cache_field_values": () => ({ - name: "schedules.cache_field_values", - type: CacheFieldValuesScheduleWidget, - normalize: value => value, - }), - auto_run_queries: () => ({ - name: "auto_run_queries", - initial: undefined, - }), - refingerprint: () => ({ - name: "refingerprint", - }), -}; - -function getEngineName(engine) { - const engineInfo = ENGINES[engine]; - return engineInfo != null ? engineInfo["driver-name"] : t`Database`; -} - -function getEngineInfo(engine, details, id) { - const engineInfo = (MetabaseSettings.get("engines") || {})[engine]; - switch (engine) { - // BigQuery has special logic to switch out forms depending on what style of authenication we use. - case "bigquery": - return getFieldsForBigQuery(details); - // Mongo has special logic to switch between a connection URI and broken out fields - case "mongo": - return getFieldsForMongo(details, engineInfo, id); - default: - return engineInfo; - } -} - -function shouldShowEngineProvidedField(field, details) { - const detailAndValueRequiredToShowField = field.visibleIf; - - if (detailAndValueRequiredToShowField) { - const pred = currentValue => { - const [detail, expectedDetailValue] = currentValue; - - if (Array.isArray(expectedDetailValue)) { - // if the expectedDetailValue is itself an array, then consider the condition satisfied if any of those values - // match the current detail value - return expectedDetailValue.includes(details[detail]); - } else { - return details[detail] === expectedDetailValue; - } - }; - - // check all entries in the visible-if map, and only show this field if all key/values are satisfied - // (i.e. boolean AND) - return Object.entries(detailAndValueRequiredToShowField).every(pred); - } - - return true; -} - -function getSshDescription() { - const link = ( - <ExternalLink href={MetabaseSettings.docsUrl("databases/ssh-tunnel")}> - {t`Learn more`} - </ExternalLink> - ); - - return jt`If a direct connection to your database isn't possible, you may want to use an SSH tunnel. ${link}.`; -} - -function getSslKeyOptionsDescription(engine) { - if (engine !== "postgres") { - return null; - } - - const link = ( - <ExternalLink - href={MetabaseSettings.docsUrl( - "databases/connections/postgresql", - "authenticate-client-certificate", - )} - > - {t`Learn more`} - </ExternalLink> - ); - - return jt`If you have a PEM SSL client key, you can convert that key to the PKCS-8/DER format using OpenSSL. ${link}.`; -} - -const AUTH_URL_PREFIXES = { - bigquery: - "https://accounts.google.com/o/oauth2/auth?redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=https://www.googleapis.com/auth/bigquery&client_id=", - bigquery_with_drive: - "https://accounts.google.com/o/oauth2/auth?redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=https://www.googleapis.com/auth/bigquery%20https://www.googleapis.com/auth/drive&client_id=", -}; - -const CREDENTIALS_URL_PREFIXES = { - bigquery: - "https://console.developers.google.com/apis/credentials/oauthclient?project=", -}; - -function concatTrimmed(a, b) { - return (a || "").trim() + (b || "").trim(); -} - -function getClientIdDescription(engine, details) { - if (CREDENTIALS_URL_PREFIXES[engine]) { - const credentialsURL = concatTrimmed( - CREDENTIALS_URL_PREFIXES[engine], - details["project-id"] || "", - ); - return ( - <span> - {jt`${( - <ExternalLink className="link" href={credentialsURL}> - {t`Click here`} - </ExternalLink> - )} to generate a Client ID and Client Secret for your project.`}{" "} - {t`Choose "Desktop App" as the application type. Name it whatever you'd like.`} - </span> - ); - } -} - -function getAuthCodeLink(engine, details) { - if (AUTH_URL_PREFIXES[engine] && details["client-id"]) { - const authCodeURL = concatTrimmed( - AUTH_URL_PREFIXES[engine], - details["client-id"], - ); - const googleDriveAuthCodeURL = concatTrimmed( - AUTH_URL_PREFIXES["bigquery_with_drive"], - details["client-id"], - ); - return ( - <span> - {jt`${( - <ExternalLink href={authCodeURL}>{t`Click here`}</ExternalLink> - )} to get an auth code.`} - {engine === "bigquery" && ( - <span> - {" "} - ({t`or`}{" "} - <ExternalLink href={googleDriveAuthCodeURL}> - {t`with Google Drive permissions`} - </ExternalLink> - ) - </span> - )} - </span> - ); - } -} - -function getDefaultValue(field) { - return "default" in field ? field.default : null; -} - -function normalizeFieldValue(value, field) { - if (value === "" || value == null) { - return getDefaultValue(field); - } - - if (typeof value === "string" && field.type !== "password") { - const trimmedValue = value.trim(); - return trimmedValue === "" ? getDefaultValue(field) : trimmedValue; - } - - return value; -} - -function getEngineFormFields(engine, details, id) { - const engineInfo = getEngineInfo(engine, details, id); - const engineFields = engineInfo ? engineInfo["details-fields"] : []; - const cachingField = getDatabaseCachingField(); - - // convert database details-fields to Form fields - return engineFields - .map(field => { - const overrides = DATABASE_DETAIL_OVERRIDES[field.name]; - - return { - name: `details.${field.name}`, - title: field["display-name"], - type: field.type, - description: field.description, - placeholder: field.placeholder || field.default, - options: field.options, - validate: value => (field.required && !value ? t`required` : null), - normalize: value => normalizeFieldValue(value, field), - horizontal: field.type === "boolean", - initial: field.default, - readOnly: field.readOnly || false, - helperText: field["helper-text"], - visibleIf: field["visible-if"], - treatBeforePosting: field["treat-before-posting"], - ...(overrides && overrides(engine, details, id)), - }; - }) - .concat(cachingField ? [cachingField] : []) - .filter(field => shouldShowEngineProvidedField(field, details)); -} - -const ENGINES = MetabaseSettings.get("engines", {}); -const ELEVATED_ENGINES = getElevatedEngines(); - -const ENGINE_OPTIONS = Object.entries(ENGINES) - .map(([engine, info]) => ({ - value: engine, - name: info["driver-name"], - official: info["official"] ?? true, // TODO remove default - index: ELEVATED_ENGINES.indexOf(engine), - })) - .sort((a, b) => a.name.localeCompare(b.name)); - -// use top level constant for engines so we only need to compute these maps once -const ENGINE_SUPERSEDES_MAPS = Object.keys(ENGINES).reduce( - (acc, engine) => { - const newEngine = ENGINES[engine]["superseded-by"]; - if (newEngine) { - acc.supersedes[newEngine] = engine; - acc.superseded_by[engine] = newEngine; - } - return acc; - }, - { supersedes: {}, superseded_by: {} }, -); - -/** - * Returns the options to show in the engines selection widget. An engine is available to be selected if either - * - it is not superseded by any other engine - * - it is the selected engine (i.e. someone is already using it) - * - it is superseded by some engine, which happens to be the currently selected one - * - * The idea behind this behavior is to only show someone a "legacy" driver if they have at least selected the one that - * will replace it first, at which point they can "fall back" on the legacy one if needed. - * - * @param currentEngine the current (selected engine) - * @returns the filtered engine options to be shown in the selection widget - */ -function getEngineOptions(currentEngine) { - return ENGINE_OPTIONS.filter(engine => { - const engineName = engine.value; - const newDriver = ENGINE_SUPERSEDES_MAPS["superseded_by"][engineName]; - return ( - typeof newDriver === "undefined" || - newDriver === currentEngine || - engineName === currentEngine - ); - }); -} - -function getDatabaseCachingField() { - const hasField = - PLUGIN_CACHING.databaseCacheTTLFormField && - MetabaseSettings.get("enable-query-caching"); - return hasField ? PLUGIN_CACHING.databaseCacheTTLFormField : null; -} - -const forms = { - details: { - fields: ({ id, engine, details = {} } = {}) => - [ - { - name: "engine", - title: t`Database type`, - type: "select", - options: getEngineOptions(engine), - placeholder: t`Select a database`, - isHosted: MetabaseSettings.isHosted(), - }, - { - name: "name", - title: t`Display name`, - placeholder: t`Our ${getEngineName(engine)}`, - validate: value => !value && t`required`, - hidden: !engine, - helperText: t`Choose what this data will be called in Metabase.`, - }, - ...(getEngineFormFields(engine, details, id) || []), - { name: "is_full_sync", type: "hidden" }, - { name: "is_on_demand", type: "hidden" }, - ].filter(Boolean), - normalize: function (database) { - if (!database.details["let-user-control-scheduling"]) { - // TODO Atte Keinänen 8/15/17: Implement engine-specific scheduling defaults - return { - ...database, - is_full_sync: true, - }; - } else { - return database; - } - }, - }, -}; - -forms.setup = { - ...forms.details, - fields: (...args) => - forms.details.fields(...args).map(field => ({ - ...field, - type: field.name === "engine" ? EngineWidget : field.type, - title: field.name === "engine" ? null : field.title, - hidden: field.hidden || ADVANCED_FIELDS.has(field.name), - })), -}; - -const ADVANCED_FIELDS = new Set([ - "auto_run_queries", - "details.let-user-control-scheduling", - "cache_ttl", -]); - -export default forms; -export const engineSupersedesMap = ENGINE_SUPERSEDES_MAPS; -export const allEngines = ENGINES; diff --git a/frontend/src/metabase/entities/databases/mongo-fields.js b/frontend/src/metabase/entities/databases/mongo-fields.js deleted file mode 100644 index 59436e5dce9aa9cb60ccaed5623e7b9b92a9598d..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/entities/databases/mongo-fields.js +++ /dev/null @@ -1,62 +0,0 @@ -/* eslint-disable react/prop-types */ -import React from "react"; -import { t } from "ttag"; - -import Link from "metabase/core/components/Link"; - -function MongoConnectionStringToggle({ field: { value, onChange } }) { - return ( - <div> - <Link className="link" onClick={() => onChange(!value)}> - {value === false - ? t`Paste a connection string` - : t`Fill out individual fields`} - </Link> - </div> - ); -} - -export default function getFieldsForMongo(details, defaults, id) { - const useConnectionString = - details["use-conn-uri"] == null || details["use-conn-uri"]; - - const manualFields = [ - "host", - "dbname", - "port", - "user", - "pass", - "authdb", - "additional-options", - "use-srv", - "ssl", - ]; - - const fields = defaults["details-fields"] - .filter( - field => - !( - field["name"] === "use-conn-uri" || - (useConnectionString && manualFields.includes(field["name"])) || - (!useConnectionString && field["name"] === "conn-uri") - ), - ) - .map(function (field) { - if (field["name"] === "conn-uri" && id) { - field.type = "password"; - } - return field; - }); - - return { - "details-fields": [ - { - name: "use-conn-uri", - type: MongoConnectionStringToggle, - hidden: true, - default: false, - }, - ...fields, - ], - }; -} diff --git a/frontend/src/metabase/store.js b/frontend/src/metabase/store.js index 6fc0c30276159ea35a39b11943d1c83cbbd908bc..132a727d67ac1caf6d98d66470cf825151a1d49d 100644 --- a/frontend/src/metabase/store.js +++ b/frontend/src/metabase/store.js @@ -1,5 +1,4 @@ import { combineReducers, applyMiddleware, createStore, compose } from "redux"; -import { reducer as form } from "redux-form"; import { routerReducer as routing, routerMiddleware } from "react-router-redux"; import promise from "redux-promise"; import { PLUGIN_REDUX_MIDDLEWARES } from "metabase/plugins"; @@ -29,7 +28,6 @@ const devToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION__ export function getStore(reducers, history, intialState, enhancer = a => a) { const reducer = combineReducers({ ...reducers, - form, routing, }); diff --git a/frontend/src/metabase/writeback/components/WritebackForm.styled.tsx b/frontend/src/metabase/writeback/components/WritebackForm.styled.tsx deleted file mode 100644 index b42b4dfeb6df2bdc0118e1b4398bda44135b5b4d..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/writeback/components/WritebackForm.styled.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import styled from "@emotion/styled"; - -import Radio from "metabase/core/components/Radio"; - -import Form from "metabase/containers/Form"; - -export const StyledForm = styled(Form)` - ${Radio.RadioGroupVariants.join(", ")} { - flex-wrap: wrap; - gap: 0.5rem 0; - } -`; diff --git a/frontend/src/metabase/writeback/containers/WritebackForm.tsx b/frontend/src/metabase/writeback/containers/WritebackForm.tsx index 7d4b2011c687da4297d6643bc133d533d6a93170..e9f62b66c357aac132e3af7533816cb2d18dc726 100644 --- a/frontend/src/metabase/writeback/containers/WritebackForm.tsx +++ b/frontend/src/metabase/writeback/containers/WritebackForm.tsx @@ -1,17 +1,5 @@ -import React, { useCallback, useMemo } from "react"; -import { t } from "ttag"; - -import validate from "metabase/lib/validate"; - -import { TYPE } from "metabase-lib/types/constants"; -import Field from "metabase-lib/metadata/Field"; import Table from "metabase-lib/metadata/Table"; -import { StyledForm } from "../components/WritebackForm.styled"; - -import { isEditableField } from "../utils"; -import CategoryFieldPicker from "./CategoryFieldPicker"; - export interface WritebackFormProps { table: Table; row?: unknown[]; @@ -23,127 +11,8 @@ export interface WritebackFormProps { isModal?: boolean; } -function getFieldTypeProps(field: Field) { - if (field.isFK()) { - return { - type: field.isNumeric() ? "integer" : "input", - }; - } - if (field.isNumeric()) { - return { type: "integer" }; - } - if (field.isBoolean()) { - return { type: "boolean" }; - } - if (field.isDate()) { - return { type: "date" }; - } - if (field.semantic_type === TYPE.Email) { - return { type: "email" }; - } - if ( - field.semantic_type === TYPE.Description || - field.semantic_type === TYPE.Comment - ) { - return { type: "text" }; - } - if (field.semantic_type === TYPE.Title) { - return { type: "input" }; - } - if (field.isCategory() && field.semantic_type !== TYPE.Name) { - return { - fieldInstance: field, - widget: CategoryFieldPicker, - }; - } - return { type: "input" }; -} - -function getFieldValidationProp(field: Field) { - let validator = validate as any; - - if (field.database_required) { - validator = validator.required(); - } - - return { - validate: validator, - }; -} - -function WritebackForm({ - table, - row, - type = row ? "update" : "insert", - mode, - onSubmit, - ...props -}: WritebackFormProps) { - const editableFields = useMemo(() => { - const fields = table.fields.filter(isEditableField); - if (mode === "bulk") { - // Ideally we need to filter out fields with 'unique' constraint - return fields.filter(field => !field.isPK()); - } - return fields; - }, [table, mode]); - - const form = useMemo(() => { - return { - fields: editableFields.map(field => { - const fieldIndex = table.fields.findIndex(f => f.id === field.id); - const initialValue = row ? row[fieldIndex] : undefined; - - let title = field.displayName(); - if (field.database_required) { - title += " (" + t`required` + ")"; - } - - return { - name: field.name, - title, - description: field.description, - initial: initialValue, - ...getFieldTypeProps(field), - ...getFieldValidationProp(field), - }; - }), - }; - }, [table, row, editableFields]); - - const handleSubmit = useCallback( - values => { - const isUpdate = type === "update"; - const changes = isUpdate ? {} : values; - - if (isUpdate) { - const fields = form.fields; - - // makes sure we only pass fields that were actually changed - Object.keys(values).forEach(fieldName => { - const field = fields.find(field => field.name === fieldName); - const hasChanged = !field || field.initial !== values[fieldName]; - if (hasChanged) { - changes[fieldName] = values[fieldName]; - } - }); - } - - return onSubmit?.(changes); - }, - [form, type, onSubmit], - ); - - const submitTitle = type === "update" ? t`Update` : t`Create`; - - return ( - <StyledForm - {...props} - form={form} - onSubmit={handleSubmit} - submitTitle={submitTitle} - /> - ); +function WritebackForm(props: WritebackFormProps) { + return null; } export default WritebackForm; diff --git a/frontend/test/__support__/ui.js b/frontend/test/__support__/ui.js index 80be3c3ddb68431cd5c264f46b4ee6528263d79c..28fa0e2b9fbc85844eb76820c0b3b9c940dace8f 100644 --- a/frontend/test/__support__/ui.js +++ b/frontend/test/__support__/ui.js @@ -5,7 +5,6 @@ import { merge } from "icepick"; import { createMemoryHistory } from "history"; import { Router, Route } from "react-router"; import { Provider } from "react-redux"; -import { reducer as form } from "redux-form"; import { ThemeProvider } from "@emotion/react"; import { DragDropContextProvider } from "react-dnd"; import HTML5Backend from "react-dnd-html5-backend"; @@ -63,7 +62,6 @@ export function renderWithProviders( const store = getStore( { - form, currentUser: () => getUser(currentUser), settings: withSettings ? () => createMockSettingsState() : undefined, embed: withEmbedSettings ? () => createMockEmbedState() : undefined, diff --git a/package.json b/package.json index 65b10f11bb2571afa21d5a7b66d143f449da43dc..0b8498b3caa11f80c35144aad554fef0af9d88ac 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,6 @@ "redux": "^3.5.2", "redux-actions": "^2.0.1", "redux-auth-wrapper": "^1.0.0", - "redux-form": "5", "redux-promise": "^0.5.0", "redux-router": "^2.1.2", "regenerator": "^0.14.1", @@ -187,7 +186,6 @@ "@types/react-virtualized": "^9.21.13", "@types/redux-actions": "^2.6.2", "@types/redux-auth-wrapper": "^1.0.7", - "@types/redux-form": "5.0.2", "@types/redux-logger": "^3.0.9", "@types/redux-promise": "^0.5.29", "@types/redux-router": "^1.0.45", diff --git a/yarn.lock b/yarn.lock index bdf6b2f8d5a513a6814f74e96d8fa7a441bd2d7a..7c95d852bd976fdc51052cd47a69ccd9bbd6b2bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5653,14 +5653,6 @@ "@types/react" "*" redux "^3.7.2" -"@types/redux-form@5.0.2": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@types/redux-form/-/redux-form-5.0.2.tgz#f803f42cc3bf7ec27de55b9df585c223ba3372ca" - integrity sha512-E2WfGuRXm8DGQZWQN/IXqdPqm30+PaHa//d3WbtYhQA8VFW162RCB0Vz5yFd+zQmCoguFxCIO1NjyFH20hIJMg== - dependencies: - "@types/react" "*" - redux "^3.6.0" - "@types/redux-logger@^3.0.9": version "3.0.9" resolved "https://registry.yarnpkg.com/@types/redux-logger/-/redux-logger-3.0.9.tgz#9193b3d51bb6ab98d25514ba7764e4f98a64d3ec" @@ -12317,7 +12309,7 @@ hoist-non-react-statics@1.2.0: resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" integrity sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs= -hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0: +hoist-non-react-statics@^2.5.0: version "2.5.5" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== @@ -13265,11 +13257,6 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-promise@^2.1.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" - integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== - is-regex@^1.0.4, is-regex@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" @@ -18079,13 +18066,6 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react-lazy-cache@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/react-lazy-cache/-/react-lazy-cache-3.0.1.tgz#0dc64d38df1767ef77678c5c94190064cb11b0cd" - integrity sha1-DcZNON8XZ+93Z4xclBkAZMsRsM0= - dependencies: - deep-equal "^1.0.1" - react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -18447,18 +18427,6 @@ redux-auth-wrapper@^1.0.0: lodash.isempty "4.4.0" prop-types "15.5.8" -redux-form@5: - version "5.4.0" - resolved "https://registry.yarnpkg.com/redux-form/-/redux-form-5.4.0.tgz#daac6b47ab20cbfb5ffdcee79f1115623a6c86a7" - integrity sha512-Ssftdf3Or6nwEBl1SqgPbZdxhmZC8AejHTTLnt1R6SfCqyoHgPnPyxu8NuewS2KvDN925nwRT9dGxeIYDgtAWQ== - dependencies: - deep-equal "^1.0.1" - hoist-non-react-statics "^2.3.1" - invariant "^2.0.0" - is-promise "^2.1.0" - prop-types "^15.5.8" - react-lazy-cache "^3.0.1" - redux-promise@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/redux-promise/-/redux-promise-0.5.3.tgz#e97e6c9d3bf376eacb79babe6d906da20112d6d8"