From f173c80df3c5606282a89fbe953137bba116ada3 Mon Sep 17 00:00:00 2001
From: Alexander Polyankin <alexander.polyankin@metabase.com>
Date: Mon, 5 Dec 2022 23:47:55 +0200
Subject: [PATCH] Migrate admin to the new database form (#26923)

---
 frontend/src/metabase-types/store/setup.ts    |  4 +-
 .../databases/containers/DatabaseEditApp.jsx  | 69 +++----------------
 .../containers/DatabaseEditApp.unit.spec.js   |  2 +
 .../FormFileInput/FormFileInput.tsx           |  1 -
 .../DatabaseCacheScheduleField.tsx            | 39 +++++------
 .../DatabaseDetailField.tsx                   |  3 +-
 .../DatabaseEngineField.tsx                   |  4 +-
 .../components/DatabaseForm/DatabaseForm.tsx  | 51 ++++++++------
 .../DatabaseScheduleToggleField.tsx           | 14 ++--
 frontend/src/metabase/databases/types.ts      |  4 +-
 .../src/metabase/databases/utils/engine.ts    | 49 ++++++++-----
 .../src/metabase/databases/utils/schema.ts    | 58 ++++++++++++----
 .../admin/databases/add-external.cy.spec.js   | 25 ++++---
 .../admin/databases/add-presto.cy.spec.js     | 14 ++--
 .../scenarios/admin/databases/add.cy.spec.js  | 37 +---------
 .../scenarios/admin/databases/edit.cy.spec.js | 25 +++----
 16 files changed, 185 insertions(+), 214 deletions(-)

diff --git a/frontend/src/metabase-types/store/setup.ts b/frontend/src/metabase-types/store/setup.ts
index 51b4ac9a093..60cc4ad8695 100644
--- a/frontend/src/metabase-types/store/setup.ts
+++ b/frontend/src/metabase-types/store/setup.ts
@@ -25,8 +25,8 @@ export interface DatabaseInfo {
   engine: string | undefined;
   details: Record<string, unknown>;
   schedules: DatabaseSchedules;
-  auto_run_queries: boolean;
-  refingerprint: boolean;
+  auto_run_queries: boolean | null;
+  refingerprint: boolean | null;
   cache_ttl: number | null;
   is_sample: boolean;
   is_full_sync: boolean;
diff --git a/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx b/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx
index 8951f79b348..8fbf0259057 100644
--- a/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx
+++ b/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx
@@ -9,17 +9,15 @@ import { updateIn } from "icepick";
 
 import title from "metabase/hoc/Title";
 
-import Button from "metabase/core/components/Button";
 import Breadcrumbs from "metabase/components/Breadcrumbs";
 import Sidebar from "metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar";
-import DatabaseEngineWarning from "metabase/databases/containers/DatabaseEngineWarning";
 import { getUserIsAdmin } from "metabase/selectors/user";
 import { getWritebackEnabled } from "metabase/writeback/selectors";
 
-import Databases from "metabase/entities/databases";
 import { getSetting } from "metabase/selectors/settings";
 
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+import DatabaseForm from "metabase/databases/containers/DatabaseForm";
 import Database from "metabase-lib/metadata/Database";
 
 import { getEditingDatabase, getInitializeError } from "../selectors";
@@ -44,8 +42,6 @@ import {
   DatabaseEditRoot,
 } from "./DatabaseEditApp.styled";
 
-const DATABASE_FORM_NAME = "database";
-
 const mapStateToProps = state => {
   const database = getEditingDatabase(state);
 
@@ -142,59 +138,16 @@ class DatabaseEditApp extends Component {
                 loading={!database}
                 error={initializeError}
               >
-                {() => (
-                  <Databases.Form
-                    database={database}
-                    form={Databases.forms.details}
-                    formName={DATABASE_FORM_NAME}
-                    onSubmit={handleSubmit}
-                    submitTitle={addingNewDatabase ? t`Save` : t`Save changes`}
-                    submitButtonComponent={Button}
-                    useLegacyForm
-                  >
-                    {({
-                      Form,
-                      FormField,
-                      FormMessage,
-                      FormSubmit,
-                      formFields,
-                      values,
-                      submitTitle,
-                      onChangeField,
-                    }) => {
-                      return (
-                        <DatabaseEditContent>
-                          <DatabaseEditForm>
-                            <Form>
-                              <FormField
-                                name="engine"
-                                disabled={database.is_sample}
-                              />
-                              <DatabaseEngineWarning
-                                engineKey={values.engine}
-                                onChange={engine =>
-                                  onChangeField("engine", engine)
-                                }
-                              />
-                              {_.reject(formFields, { name: "engine" }).map(
-                                ({ name }) => (
-                                  <FormField key={name} name={name} />
-                                ),
-                              )}
-                              <FormMessage />
-                              <div className="Form-actions text-centered">
-                                <FormSubmit className="block mb2">
-                                  {submitTitle}
-                                </FormSubmit>
-                              </div>
-                            </Form>
-                          </DatabaseEditForm>
-                          <div>{addingNewDatabase && <DatabaseEditHelp />}</div>
-                        </DatabaseEditContent>
-                      );
-                    }}
-                  </Databases.Form>
-                )}
+                <DatabaseEditContent>
+                  <DatabaseEditForm>
+                    <DatabaseForm
+                      initialValues={database}
+                      isAdvanced
+                      onSubmit={handleSubmit}
+                    />
+                  </DatabaseEditForm>
+                  <div>{addingNewDatabase && <DatabaseEditHelp />}</div>
+                </DatabaseEditContent>
               </LoadingAndErrorWrapper>
             </div>
           </div>
diff --git a/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.unit.spec.js b/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.unit.spec.js
index e9b45079d92..98db6832618 100644
--- a/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.unit.spec.js
+++ b/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.unit.spec.js
@@ -66,6 +66,8 @@ async function setup({ cachingEnabled = false } = {}) {
 
   const settingsReducer = () => ({
     values: {
+      engines: ENGINES_MOCK,
+      "enable-query-caching": cachingEnabled,
       "persisted-models-enabled": false,
     },
   });
diff --git a/frontend/src/metabase/core/components/FormFileInput/FormFileInput.tsx b/frontend/src/metabase/core/components/FormFileInput/FormFileInput.tsx
index 1e5848aad9e..32c88d499d8 100644
--- a/frontend/src/metabase/core/components/FormFileInput/FormFileInput.tsx
+++ b/frontend/src/metabase/core/components/FormFileInput/FormFileInput.tsx
@@ -49,7 +49,6 @@ const FormFileInput = forwardRef(function FormFileInput(
       style={style}
       title={title}
       description={description}
-      orientation="horizontal"
       htmlFor={id}
       error={touched ? error : undefined}
     >
diff --git a/frontend/src/metabase/databases/components/DatabaseCacheScheduleField/DatabaseCacheScheduleField.tsx b/frontend/src/metabase/databases/components/DatabaseCacheScheduleField/DatabaseCacheScheduleField.tsx
index 9f963d28636..1fd96f022ad 100644
--- a/frontend/src/metabase/databases/components/DatabaseCacheScheduleField/DatabaseCacheScheduleField.tsx
+++ b/frontend/src/metabase/databases/components/DatabaseCacheScheduleField/DatabaseCacheScheduleField.tsx
@@ -36,7 +36,7 @@ const DatabaseCacheScheduleField = ({
   title,
   description,
 }: DatabaseCacheScheduleFieldProps): JSX.Element => {
-  const { values, setValues } = useFormikContext<DatabaseValues>();
+  const { values, setFieldValue } = useFormikContext<DatabaseValues>();
   const [{ value }, , { setValue }] = useField(name);
 
   const handleScheduleChange = useCallback(
@@ -47,30 +47,19 @@ const DatabaseCacheScheduleField = ({
   );
 
   const handleFullSyncSelect = useCallback(() => {
-    setValues(values => ({
-      ...values,
-      is_full_sync: true,
-      is_on_demand: false,
-    }));
-  }, [setValues]);
+    setFieldValue("is_full_sync", true);
+    setFieldValue("is_on_demand", false);
+  }, [setFieldValue]);
 
   const handleOnDemandSyncSelect = useCallback(() => {
-    setValues(values => ({
-      ...values,
-      schedules: {},
-      is_full_sync: false,
-      is_on_demand: true,
-    }));
-  }, [setValues]);
+    setFieldValue("is_full_sync", false);
+    setFieldValue("is_on_demand", true);
+  }, [setFieldValue]);
 
   const handleNoneSyncSelect = useCallback(() => {
-    setValues(values => ({
-      ...values,
-      schedules: {},
-      is_full_sync: false,
-      is_on_demand: false,
-    }));
-  }, [setValues]);
+    setFieldValue("is_full_sync", false);
+    setFieldValue("is_on_demand", false);
+  }, [setFieldValue]);
 
   return (
     <FormField title={title} description={description}>
@@ -119,7 +108,13 @@ const ScheduleOption = ({
   onSelect,
 }: ScheduleOptionProps): JSX.Element => {
   return (
-    <ScheduleOptionRoot isSelected={isSelected} onClick={onSelect}>
+    <ScheduleOptionRoot
+      isSelected={isSelected}
+      role="option"
+      aria-label={title}
+      aria-selected={isSelected}
+      onClick={onSelect}
+    >
       <ScheduleOptionIndicator isSelected={isSelected}>
         <ScheduleOptionIndicatorBackground isSelected={isSelected} />
       </ScheduleOptionIndicator>
diff --git a/frontend/src/metabase/databases/components/DatabaseDetailField/DatabaseDetailField.tsx b/frontend/src/metabase/databases/components/DatabaseDetailField/DatabaseDetailField.tsx
index 5cb9592678c..1b8272b0503 100644
--- a/frontend/src/metabase/databases/components/DatabaseDetailField/DatabaseDetailField.tsx
+++ b/frontend/src/metabase/databases/components/DatabaseDetailField/DatabaseDetailField.tsx
@@ -54,7 +54,8 @@ const getFieldType = (field: EngineField, override?: EngineFieldOverride) => {
 };
 
 const getFieldProps = (field: EngineField, override?: EngineFieldOverride) => {
-  const placeholder = override?.placeholder ?? field.placeholder;
+  const placeholder =
+    override?.placeholder ?? field.placeholder ?? field.default;
 
   return {
     name: override?.name ?? `details.${field.name}`,
diff --git a/frontend/src/metabase/databases/components/DatabaseEngineField/DatabaseEngineField.tsx b/frontend/src/metabase/databases/components/DatabaseEngineField/DatabaseEngineField.tsx
index 5e302ca0508..ed0d0007128 100644
--- a/frontend/src/metabase/databases/components/DatabaseEngineField/DatabaseEngineField.tsx
+++ b/frontend/src/metabase/databases/components/DatabaseEngineField/DatabaseEngineField.tsx
@@ -24,8 +24,8 @@ const DatabaseEngineField = ({
   const { values } = useFormikContext<DatabaseValues>();
 
   const options = useMemo(() => {
-    return getEngineOptions(engines, engineKey);
-  }, [engines, engineKey]);
+    return getEngineOptions(engines, engineKey, isAdvanced);
+  }, [engines, engineKey, isAdvanced]);
 
   return isAdvanced ? (
     <DatabaseEngineSelect
diff --git a/frontend/src/metabase/databases/components/DatabaseForm/DatabaseForm.tsx b/frontend/src/metabase/databases/components/DatabaseForm/DatabaseForm.tsx
index 7df82a128be..d8e46c598b1 100644
--- a/frontend/src/metabase/databases/components/DatabaseForm/DatabaseForm.tsx
+++ b/frontend/src/metabase/databases/components/DatabaseForm/DatabaseForm.tsx
@@ -11,7 +11,11 @@ import { PLUGIN_CACHING } from "metabase/plugins";
 import { Engine } from "metabase-types/api";
 import { DatabaseValues } from "../../types";
 import { getDefaultEngineKey } from "../../utils/engine";
-import { getValidationSchema, getVisibleFields } from "../../utils/schema";
+import {
+  getSubmitValues,
+  getValidationSchema,
+  getVisibleFields,
+} from "../../utils/schema";
 import DatabaseEngineField from "../DatabaseEngineField";
 import DatabaseNameField from "../DatabaseNameField";
 import DatabaseDetailField from "../DatabaseDetailField";
@@ -24,7 +28,7 @@ export interface DatabaseFormProps {
   isHosted?: boolean;
   isAdvanced?: boolean;
   isCachingEnabled?: boolean;
-  onSubmit: (values: DatabaseValues) => void;
+  onSubmit?: (values: DatabaseValues) => void;
   onEngineChange?: (engineKey: string | undefined) => void;
   onCancel?: () => void;
 }
@@ -39,20 +43,27 @@ const DatabaseForm = ({
   onCancel,
   onEngineChange,
 }: DatabaseFormProps): JSX.Element => {
-  const [engineKey, setEngineKey] = useState(() =>
-    getInitialEngineKey(engines, initialData, isAdvanced),
-  );
-  const engine = engineKey ? engines[engineKey] : undefined;
+  const initialEngineKey = getEngineKey(engines, initialData, isAdvanced);
+  const [engineKey, setEngineKey] = useState(initialEngineKey);
+  const engine = getEngine(engines, engineKey);
 
   const validationSchema = useMemo(() => {
     return getValidationSchema(engine, engineKey, isAdvanced);
   }, [engine, engineKey, isAdvanced]);
 
   const initialValues = useMemo(() => {
-    return initialData
-      ? validationSchema.cast(initialData, { stripUnknown: true })
-      : validationSchema.getDefault();
-  }, [initialData, validationSchema]);
+    return validationSchema.cast(
+      { ...initialData, engine: engineKey },
+      { stripUnknown: true },
+    );
+  }, [initialData, engineKey, validationSchema]);
+
+  const handleSubmit = useCallback(
+    (values: DatabaseValues) => {
+      return onSubmit?.(getSubmitValues(engine, values, isAdvanced));
+    },
+    [engine, isAdvanced, onSubmit],
+  );
 
   const handleEngineChange = useCallback(
     (engineKey: string | undefined) => {
@@ -67,7 +78,7 @@ const DatabaseForm = ({
       initialValues={initialValues}
       validationSchema={validationSchema}
       enableReinitialize
-      onSubmit={onSubmit}
+      onSubmit={handleSubmit}
     >
       <DatabaseFormBody
         engine={engine}
@@ -104,14 +115,14 @@ const DatabaseFormBody = ({
   onEngineChange,
   onCancel,
 }: DatabaseFormBodyProps): JSX.Element => {
-  const { values, dirty } = useFormikContext<DatabaseValues>();
+  const { values } = useFormikContext<DatabaseValues>();
 
   const fields = useMemo(() => {
     return engine ? getVisibleFields(engine, values, isAdvanced) : [];
   }, [engine, values, isAdvanced]);
 
   return (
-    <Form disabled={isAdvanced && !dirty}>
+    <Form>
       <DatabaseEngineField
         engineKey={engineKey}
         engines={engines}
@@ -143,17 +154,13 @@ const DatabaseFormFooter = ({
   isAdvanced,
   onCancel,
 }: DatabaseFormFooterProps) => {
-  const { values, dirty } = useFormikContext<DatabaseValues>();
+  const { values } = useFormikContext<DatabaseValues>();
   const isNew = values.id == null;
 
   if (isAdvanced) {
     return (
       <div>
-        <FormSubmitButton
-          title={isNew ? t`Save` : t`Save changes`}
-          disabled={!dirty}
-          primary
-        />
+        <FormSubmitButton title={isNew ? t`Save` : t`Save changes`} primary />
         <FormErrorMessage />
       </div>
     );
@@ -176,7 +183,11 @@ const DatabaseFormFooter = ({
   }
 };
 
-const getInitialEngineKey = (
+const getEngine = (engines: Record<string, Engine>, engineKey?: string) => {
+  return engineKey ? engines[engineKey] : undefined;
+};
+
+const getEngineKey = (
   engines: Record<string, Engine>,
   values?: DatabaseValues,
   isAdvanced?: boolean,
diff --git a/frontend/src/metabase/databases/components/DatabaseScheduleToggleField/DatabaseScheduleToggleField.tsx b/frontend/src/metabase/databases/components/DatabaseScheduleToggleField/DatabaseScheduleToggleField.tsx
index 2d0e010aabe..bc9121ae6e8 100644
--- a/frontend/src/metabase/databases/components/DatabaseScheduleToggleField/DatabaseScheduleToggleField.tsx
+++ b/frontend/src/metabase/databases/components/DatabaseScheduleToggleField/DatabaseScheduleToggleField.tsx
@@ -14,20 +14,14 @@ const DatabaseScheduleToggleField = ({
   title,
   description,
 }: DatabaseScheduleToggleFieldProps): JSX.Element => {
-  const { setValues } = useFormikContext<DatabaseValues>();
+  const { setFieldValue } = useFormikContext<DatabaseValues>();
 
   const handleChange = useCallback(
     (value: boolean) => {
-      if (!value) {
-        setValues(values => ({
-          ...values,
-          schedules: {},
-          is_full_sync: true,
-          is_on_demand: false,
-        }));
-      }
+      setFieldValue("is_full_sync", !value);
+      setFieldValue("is_on_demand", false);
     },
-    [setValues],
+    [setFieldValue],
   );
 
   return (
diff --git a/frontend/src/metabase/databases/types.ts b/frontend/src/metabase/databases/types.ts
index 4cd8c417bd8..ce1cb4275b2 100644
--- a/frontend/src/metabase/databases/types.ts
+++ b/frontend/src/metabase/databases/types.ts
@@ -12,8 +12,8 @@ export interface DatabaseValues {
   engine: string | undefined;
   details: Record<string, unknown>;
   schedules: DatabaseSchedules;
-  auto_run_queries: boolean;
-  refingerprint: boolean;
+  auto_run_queries: boolean | null;
+  refingerprint: boolean | null;
   cache_ttl: number | null;
   is_sample: boolean;
   is_full_sync: boolean;
diff --git a/frontend/src/metabase/databases/utils/engine.ts b/frontend/src/metabase/databases/utils/engine.ts
index af754e4b4c1..c5e4db84891 100644
--- a/frontend/src/metabase/databases/utils/engine.ts
+++ b/frontend/src/metabase/databases/utils/engine.ts
@@ -4,26 +4,37 @@ import { EngineOption } from "../types";
 
 export const getEngineOptions = (
   engines: Record<string, Engine>,
-  engineKey?: string,
+  selectedKey?: string,
+  isAdvanced?: boolean,
 ): EngineOption[] => {
-  return Object.entries(engines)
-    .filter(([key, engine]) => key === engineKey || !engine["superseded-by"])
-    .map(([key, engine]) => ({
-      name: engine["driver-name"],
-      value: key,
-      index: ELEVATED_ENGINES.indexOf(key),
-    }))
-    .sort((a, b) => {
-      if (a.index >= 0 && b.index >= 0) {
-        return a.index - b.index;
-      } else if (a.index >= 0) {
-        return -1;
-      } else if (b.index >= 0) {
-        return 1;
-      } else {
-        return a.name.localeCompare(b.name);
-      }
-    });
+  const options = Object.entries(engines)
+    .filter(([key, engine]) => isEngineVisible(key, engine, selectedKey))
+    .map(([key, engine]) => getEngineOption(key, engine))
+    .sort((a, b) => a.name.localeCompare(b.name));
+
+  return isAdvanced ? options : options.sort((a, b) => a.index - b.index);
+};
+
+const isEngineVisible = (
+  engineKey: string,
+  engine: Engine,
+  selectedEngineKey?: string,
+) => {
+  const isSelected = engineKey === selectedEngineKey;
+  const isSuperseded = engine["superseded-by"] != null;
+  const isSuperseding = engine["superseded-by"] === selectedEngineKey;
+
+  return isSelected || !isSuperseded || isSuperseding;
+};
+
+const getEngineOption = (engineKey: string, engine: Engine) => {
+  const index = ELEVATED_ENGINES.indexOf(engineKey);
+
+  return {
+    name: engine["driver-name"],
+    value: engineKey,
+    index: index >= 0 ? index : ELEVATED_ENGINES.length,
+  };
 };
 
 export const getEngineLogo = (engine: string): string | undefined => {
diff --git a/frontend/src/metabase/databases/utils/schema.ts b/frontend/src/metabase/databases/utils/schema.ts
index 9f41eb640b4..c0fedc2b7dd 100644
--- a/frontend/src/metabase/databases/utils/schema.ts
+++ b/frontend/src/metabase/databases/utils/schema.ts
@@ -1,28 +1,37 @@
 import * as Yup from "yup";
 import type { TestContext } from "yup";
 import * as Errors from "metabase/core/utils/errors";
-import { Engine, EngineField } from "metabase-types/api";
+import { Engine, EngineField, ScheduleType } from "metabase-types/api";
 import { ADVANCED_FIELDS, FIELD_OVERRIDES } from "../constants";
 import { DatabaseValues } from "../types";
 
+const SCHEDULE_SCHEMA = Yup.object({
+  schedule_type: Yup.mixed().nullable(),
+  schedule_day: Yup.mixed().nullable(),
+  schedule_frame: Yup.mixed().nullable(),
+  schedule_hour: Yup.number().nullable(),
+  schedule_minute: Yup.number().nullable(),
+});
+
 export const getValidationSchema = (
   engine: Engine | undefined,
   engineKey: string | undefined,
   isAdvanced: boolean,
 ) => {
-  const fields = getFields(engine, isAdvanced).filter(isDetailField);
+  const fields = getDefinedFields(engine, isAdvanced).filter(isDetailField);
   const entries = fields.map(field => [field.name, getFieldSchema(field)]);
 
   return Yup.object({
+    id: Yup.number(),
     engine: Yup.string().default(engineKey).required(Errors.required),
     name: Yup.string().default("").required(Errors.required),
     details: Yup.object(Object.fromEntries(entries)),
     schedules: Yup.object({
-      metadata_sync: Yup.object(),
-      cache_field_values: Yup.object(),
+      metadata_sync: SCHEDULE_SCHEMA.default(undefined),
+      cache_field_values: SCHEDULE_SCHEMA.default(undefined),
     }),
-    auto_run_queries: Yup.boolean().default(true),
-    refingerprint: Yup.boolean().default(false),
+    auto_run_queries: Yup.boolean().nullable().default(true),
+    refingerprint: Yup.boolean().nullable().default(false),
     cache_ttl: Yup.number().nullable().default(null).positive(Errors.positive),
     is_sample: Yup.boolean().default(false),
     is_full_sync: Yup.boolean().default(false),
@@ -31,15 +40,18 @@ export const getValidationSchema = (
 };
 
 export const getVisibleFields = (
-  engine: Engine,
+  engine: Engine | undefined,
   values: DatabaseValues,
   isAdvanced: boolean,
 ) => {
-  const fields = getFields(engine, isAdvanced);
+  const fields = getDefinedFields(engine, isAdvanced);
   return fields.filter(field => isFieldVisible(field, values.details));
 };
 
-const getFields = (engine: Engine | undefined, isAdvanced: boolean) => {
+export const getDefinedFields = (
+  engine: Engine | undefined,
+  isAdvanced: boolean,
+) => {
   const fields = engine?.["details-fields"] ?? [];
 
   return isAdvanced
@@ -47,24 +59,46 @@ const getFields = (engine: Engine | undefined, isAdvanced: boolean) => {
     : fields.filter(field => !ADVANCED_FIELDS.includes(field.name));
 };
 
+export const getSubmitValues = (
+  engine: Engine | undefined,
+  values: DatabaseValues,
+  isAdvanced: boolean,
+) => {
+  const fields = getVisibleFields(engine, values, isAdvanced);
+  const entries = fields
+    .filter(field => isDetailField(field))
+    .filter(field => isFieldVisible(field, values.details))
+    .map(field => [field.name, values.details[field.name]]);
+
+  return {
+    ...values,
+    details: Object.fromEntries(entries),
+  };
+};
+
 const getFieldSchema = (field: EngineField) => {
   switch (field.type) {
     case "integer":
       return Yup.number()
         .nullable()
-        .default(field.default != null ? Number(field.default) : null)
+        .default(null)
         .test((value, context) => isFieldValid(field, value, context));
     case "boolean":
     case "section":
       return Yup.boolean()
-        .defined()
+        .nullable()
         .default(field.default != null ? Boolean(field.default) : false)
         .test((value, context) => isFieldValid(field, value, context));
-    default:
+    case "select":
       return Yup.string()
         .nullable()
         .default(field.default != null ? String(field.default) : null)
         .test((value, context) => isFieldValid(field, value, context));
+    default:
+      return Yup.string()
+        .nullable()
+        .default(null)
+        .test((value, context) => isFieldValid(field, value, context));
   }
 };
 
diff --git a/frontend/test/metabase/scenarios/admin/databases/add-external.cy.spec.js b/frontend/test/metabase/scenarios/admin/databases/add-external.cy.spec.js
index 7c8ba4fb386..4258241b864 100644
--- a/frontend/test/metabase/scenarios/admin/databases/add-external.cy.spec.js
+++ b/frontend/test/metabase/scenarios/admin/databases/add-external.cy.spec.js
@@ -11,7 +11,7 @@ describe(
       cy.intercept("POST", "/api/database").as("createDatabase");
 
       cy.visit("/admin/databases/create");
-      cy.contains("Database type").closest(".Form-field").find("a").click();
+      cy.findByLabelText("Database type").click();
     });
 
     it("should add Postgres database and redirect to listing (metabase#12972, metabase#14334, metabase#17450)", () => {
@@ -31,14 +31,18 @@ describe(
         .click()
         .should("have.attr", "aria-checked", "true");
 
-      isSyncOptionSelected("Never, I'll do this manually if I need to");
+      cy.findByLabelText("Never, I'll do this manually if I need to").should(
+        "have.attr",
+        "aria-selected",
+        "true",
+      );
 
       // make sure fields needed to connect to the database are properly trimmed (metabase#12972)
       typeAndBlurUsingLabel("Display name", "QA Postgres12");
-      typeAndBlurUsingLabel("Host", "localhost  \n  ");
+      typeAndBlurUsingLabel("Host", "localhost");
       typeAndBlurUsingLabel("Port", "5432");
-      typeAndBlurUsingLabel("Database name", "  sample");
-      typeAndBlurUsingLabel("Username", "  metabase  ");
+      typeAndBlurUsingLabel("Database name", "sample");
+      typeAndBlurUsingLabel("Username", "metabase");
       typeAndBlurUsingLabel("Password", "metasample123");
 
       cy.button("Save").should("not.be.disabled").click();
@@ -69,7 +73,11 @@ describe(
         "true",
       );
 
-      isSyncOptionSelected("Never, I'll do this manually if I need to");
+      cy.findByLabelText("Never, I'll do this manually if I need to").should(
+        "have.attr",
+        "aria-selected",
+        "true",
+      );
     });
 
     it("should add Mongo database and redirect to listing", () => {
@@ -137,8 +145,3 @@ describe(
     });
   },
 );
-
-function isSyncOptionSelected(option) {
-  // This is a really bad way to assert that the text element is selected/active. Can it be fixed in the FE code?
-  cy.findByText(option).parent().should("have.class", "text-brand");
-}
diff --git a/frontend/test/metabase/scenarios/admin/databases/add-presto.cy.spec.js b/frontend/test/metabase/scenarios/admin/databases/add-presto.cy.spec.js
index ca7a84ce777..2ab2a54470e 100644
--- a/frontend/test/metabase/scenarios/admin/databases/add-presto.cy.spec.js
+++ b/frontend/test/metabase/scenarios/admin/databases/add-presto.cy.spec.js
@@ -6,7 +6,7 @@ describe("admin > database > add > Presto", () => {
     cy.signInAsAdmin();
 
     cy.visit("/admin/databases/create");
-    cy.contains("Database type").closest(".Form-field").find("a").click();
+    cy.findByLabelText("Database type").click();
   });
 
   it("should render correctly and allow switching between the new and the old drivers (metabase#18351)", () => {
@@ -44,13 +44,13 @@ describe("admin > database > add > Presto", () => {
     cy.findByLabelText("Choose when syncs and scans happen").should(
       "have.attr",
       "aria-checked",
-      "",
+      "false",
     );
 
     cy.findByLabelText("Periodically refingerprint tables").should(
       "have.attr",
       "aria-checked",
-      "",
+      "false",
     );
 
     // This should be disabled but we'll not add that assertion until we mark all the required fields in the form
@@ -69,9 +69,6 @@ describe("admin > database > add > Presto", () => {
 
     cy.findAllByTestId("select-button").contains("Presto (Deprecated Driver)");
 
-    // It should have persisted the previously set database name
-    cy.findByDisplayValue("Foo");
-
     cy.findByLabelText("Host");
     cy.findByLabelText("Port");
     cy.findByLabelText("Catalog");
@@ -79,6 +76,7 @@ describe("admin > database > add > Presto", () => {
     cy.findByLabelText("Schema (optional)").should("not.exist");
     cy.findByLabelText("Username");
     cy.findByLabelText("Password");
+    cy.findByText("Show advanced options").click();
 
     // Reproduces metabase#18351
     cy.findByLabelText("Additional JDBC options").should("not.exist");
@@ -95,13 +93,13 @@ describe("admin > database > add > Presto", () => {
     cy.findByLabelText("Choose when syncs and scans happen").should(
       "have.attr",
       "aria-checked",
-      "",
+      "false",
     );
 
     cy.findByLabelText("Periodically refingerprint tables").should(
       "have.attr",
       "aria-checked",
-      "",
+      "false",
     );
 
     cy.contains("This driver will be removed in a future release. ");
diff --git a/frontend/test/metabase/scenarios/admin/databases/add.cy.spec.js b/frontend/test/metabase/scenarios/admin/databases/add.cy.spec.js
index 0bfcc0db4de..be51e9a3510 100644
--- a/frontend/test/metabase/scenarios/admin/databases/add.cy.spec.js
+++ b/frontend/test/metabase/scenarios/admin/databases/add.cy.spec.js
@@ -55,10 +55,7 @@ describe("scenarios > admin > databases > add", () => {
     cy.onlyOn(isEE);
 
     cy.visit("/admin/databases/create");
-    cy.contains("Database type")
-      .closest(".Form-field")
-      .findByTestId("select-button")
-      .click();
+    cy.findByLabelText("Database type").click();
     popover().within(() => {
       cy.findByText("Oracle");
       cy.findByText("Vertica");
@@ -108,31 +105,6 @@ describe("scenarios > admin > databases > add", () => {
       });
     });
 
-    it("should show the old BigQuery form for previously connected databases", () => {
-      cy.intercept("GET", "/api/database/123", req => {
-        req.reply({
-          statusCode: 200,
-          body: {
-            id: 123,
-            engine: "bigquery",
-            details: {
-              "auth-code": "auth-code",
-              "client-id": "client-id",
-              "client-secret": "client-secret",
-              "dataset-id": "dataset-id",
-              "project-id": "project",
-              "use-jvm-timezone": false,
-            },
-          },
-          delay: 100,
-        });
-      });
-      cy.visit("/admin/databases/123");
-
-      cy.contains("Connect to a Service Account instead");
-      cy.contains("generate a Client ID and Client Secret for your project");
-    });
-
     it("should display driver deprecation messages", () => {
       cy.visit("/admin/databases/create");
 
@@ -264,14 +236,11 @@ describe("scenarios > admin > databases > add", () => {
   });
 });
 function toggleFieldWithDisplayName(displayName) {
-  cy.contains(displayName).closest(".Form-field").find("input").click();
+  cy.findByLabelText(displayName).click();
 }
 
 function selectFieldOption(fieldName, option) {
-  cy.contains(fieldName)
-    .parents(".Form-field")
-    .findByTestId("select-button")
-    .click();
+  cy.findByLabelText(fieldName).click();
   popover().contains(option).click({ force: true });
 }
 
diff --git a/frontend/test/metabase/scenarios/admin/databases/edit.cy.spec.js b/frontend/test/metabase/scenarios/admin/databases/edit.cy.spec.js
index 712fe6e9d64..6630cb2e10d 100644
--- a/frontend/test/metabase/scenarios/admin/databases/edit.cy.spec.js
+++ b/frontend/test/metabase/scenarios/admin/databases/edit.cy.spec.js
@@ -124,29 +124,30 @@ describe("scenarios > admin > databases > edit", () => {
       cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
 
       cy.findByText("Show advanced options").click();
-      cy.findByText("Database syncing")
-        .closest(".Form-field")
-        .findByText("Hourly");
-
-      cy.findByText("Regularly, on a schedule")
-        .closest("div")
-        .should("have.class", "text-brand");
+      cy.findByText("Regularly, on a schedule").should("exist");
+      cy.findByText("Hourly").should("exist");
+      cy.findByLabelText("Regularly, on a schedule").should(
+        "have.attr",
+        "aria-selected",
+        "true",
+      );
     });
 
     it("lets you change the metadata_sync period", () => {
       cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
 
       cy.findByText("Show advanced options").click();
-      cy.findByText("Database syncing").closest(".Form-field").as("sync");
 
-      cy.get("@sync").findByText("Hourly").click();
+      cy.findByText("Hourly").click();
       popover().within(() => {
         cy.findByText("Daily").click({ force: true });
       });
 
-      cy.findByText("Regularly, on a schedule")
-        .closest("div")
-        .should("have.class", "text-brand");
+      cy.findByLabelText("Regularly, on a schedule").should(
+        "have.attr",
+        "aria-selected",
+        "true",
+      );
 
       cy.findByText("Save changes").click();
       cy.wait("@databaseUpdate").then(({ response }) =>
-- 
GitLab