diff --git a/frontend/src/metabase-types/store/mocks/setup.ts b/frontend/src/metabase-types/store/mocks/setup.ts index c0f28e1e8809d0fa530a98e072f402484bb495b2..08284538d1baeb17f55f1967211456dc50c8a07e 100644 --- a/frontend/src/metabase-types/store/mocks/setup.ts +++ b/frontend/src/metabase-types/store/mocks/setup.ts @@ -41,6 +41,9 @@ export const createMockDatabaseInfo = ( schedules: {}, auto_run_queries: false, refingerprint: false, + is_sample: false, + is_full_sync: false, + is_on_demand: false, ...opts, }); diff --git a/frontend/src/metabase-types/store/setup.ts b/frontend/src/metabase-types/store/setup.ts index 45858c5dab77db629477556f0593d6446d4165e1..e28a057403feea3bccce5c3f55ba06b2bb083ca2 100644 --- a/frontend/src/metabase-types/store/setup.ts +++ b/frontend/src/metabase-types/store/setup.ts @@ -27,6 +27,9 @@ export interface DatabaseInfo { schedules: DatabaseSchedules; auto_run_queries: boolean; refingerprint: boolean; + is_sample: boolean; + is_full_sync: boolean; + is_on_demand: boolean; } export interface SubscribeInfo { diff --git a/frontend/src/metabase/components/SchedulePicker/SchedulePicker.tsx b/frontend/src/metabase/components/SchedulePicker/SchedulePicker.tsx index 47db0d86cc130358432e269768bf3c0210e6c0c2..60d9c693bd05f1f3c75414bc61a807514a1c3639 100644 --- a/frontend/src/metabase/components/SchedulePicker/SchedulePicker.tsx +++ b/frontend/src/metabase/components/SchedulePicker/SchedulePicker.tsx @@ -66,7 +66,7 @@ type ScheduleChangeProp = { name: ScheduleProperty; value: unknown }; export interface SchedulePickerProps { schedule: ScheduleSettings; scheduleOptions: ScheduleType[]; - timezone: string; + timezone?: string; textBeforeInterval?: string; textBeforeSendTime?: string; minutesOnHourPicker?: boolean; diff --git a/frontend/src/metabase/containers/SchedulePicker.tsx b/frontend/src/metabase/containers/SchedulePicker.tsx index 0ba5a083e3197d148b7352e1f12391f43ac18f4e..7c24658f3833ec56c241aa6e60c8b4627acbf207 100644 --- a/frontend/src/metabase/containers/SchedulePicker.tsx +++ b/frontend/src/metabase/containers/SchedulePicker.tsx @@ -6,7 +6,7 @@ import SchedulePicker, { } from "metabase/components/SchedulePicker"; type StateProps = { - timezone: string; + timezone?: string; }; function mapStateToProps(state: State): StateProps { diff --git a/frontend/src/metabase/core/components/FormSelect/FormSelect.tsx b/frontend/src/metabase/core/components/FormSelect/FormSelect.tsx index 0dd14524e9c402cc7af8d2f3358783c8b4d92260..919319699b7c07d5f294aceaa35424089ae0a900 100644 --- a/frontend/src/metabase/core/components/FormSelect/FormSelect.tsx +++ b/frontend/src/metabase/core/components/FormSelect/FormSelect.tsx @@ -24,21 +24,21 @@ const FormSelect = forwardRef(function FormSelect< className, title, description, - onChange: onChangeProp, + onChange, ...props }: FormSelectProps<TValue, TOption>, ref: Ref<HTMLDivElement>, ) { const id = useUniqueId(); - const [{ value, onChange, onBlur }, { error, touched }] = useField(name); + const [{ value, onBlur }, { error, touched }, { setValue }] = useField(name); const buttonProps = useMemo(() => ({ id, onBlur }), [id, onBlur]); const handleChange = useCallback( (event: SelectChangeEvent<TValue>) => { - onChange(event); - onChangeProp?.(event); + setValue(event.target.value); + onChange?.(event); }, - [onChange, onChangeProp], + [setValue, onChange], ); return ( diff --git a/frontend/src/metabase/core/components/FormToggle/FormToggle.tsx b/frontend/src/metabase/core/components/FormToggle/FormToggle.tsx index 807fc5ff55cc251638ec651b1ae9ff0329406197..9ea59d5ad513093f1abd5a1ff734e87ec58f6d7d 100644 --- a/frontend/src/metabase/core/components/FormToggle/FormToggle.tsx +++ b/frontend/src/metabase/core/components/FormToggle/FormToggle.tsx @@ -1,23 +1,38 @@ -import React, { forwardRef, ReactNode, Ref } from "react"; +import React, { forwardRef, ReactNode, Ref, useCallback } from "react"; import { useField } from "formik"; import { useUniqueId } from "metabase/hooks/use-unique-id"; import Toggle, { ToggleProps } from "metabase/core/components/Toggle"; import FormField from "metabase/core/components/FormField"; -export interface FormToggleProps - extends Omit<ToggleProps, "value" | "onChange" | "onBlur"> { +export interface FormToggleProps extends Omit<ToggleProps, "value" | "onBlur"> { name: string; title?: string; description?: ReactNode; } const FormToggle = forwardRef(function FormToggle( - { name, className, style, title, description, ...props }: FormToggleProps, + { + name, + className, + style, + title, + description, + onChange, + ...props + }: FormToggleProps, ref: Ref<HTMLDivElement>, ) { const id = useUniqueId(); const [{ value, onBlur }, { error, touched }, { setValue }] = useField(name); + const handleChange = useCallback( + (value: boolean) => { + setValue(value); + onChange?.(value); + }, + [setValue, onChange], + ); + return ( <FormField ref={ref} @@ -34,7 +49,7 @@ const FormToggle = forwardRef(function FormToggle( id={id} name={name} value={value ?? false} - onChange={setValue} + onChange={handleChange} onBlur={onBlur} /> </FormField> diff --git a/frontend/src/metabase/databases/components/DatabaseCacheScheduleField/DatabaseCacheScheduleField.styled.tsx b/frontend/src/metabase/databases/components/DatabaseCacheScheduleField/DatabaseCacheScheduleField.styled.tsx new file mode 100644 index 0000000000000000000000000000000000000000..12e4090153da37e0728a2863915858a373461dee --- /dev/null +++ b/frontend/src/metabase/databases/components/DatabaseCacheScheduleField/DatabaseCacheScheduleField.styled.tsx @@ -0,0 +1,74 @@ +import styled from "@emotion/styled"; +import { color } from "metabase/lib/colors"; + +export const ScheduleOptionList = styled.div` + border: 1px solid ${color("border")}; + box-shadow: 0 2px 2px ${color("shadow")}; +`; + +interface ScheduleOptionRootProps { + isSelected: boolean; +} + +export const ScheduleOptionRoot = styled.div<ScheduleOptionRootProps>` + display: flex; + cursor: ${props => !props.isSelected && "pointer"}; + padding: 1.5rem 1rem; + border-bottom: 1px solid ${color("border")}; + + &:last-child { + border-bottom: none; + } +`; + +interface ScheduleOptionIndicatorProps { + isSelected: boolean; +} + +export const ScheduleOptionIndicator = styled.div<ScheduleOptionIndicatorProps>` + display: flex; + justify-content: center; + align-items: center; + width: 1.125rem; + height: 1.125rem; + border: 0.125rem solid + ${props => (props.isSelected ? color("brand") : color("text-light"))}; + border-radius: 50%; +`; + +interface ScheduleOptionIndicatorBackgroundProps { + isSelected: boolean; +} + +export const ScheduleOptionIndicatorBackground = styled.div<ScheduleOptionIndicatorBackgroundProps>` + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + background-color: ${props => props.isSelected && color("brand")}; +`; + +export const ScheduleOptionBody = styled.div` + margin-left: 1rem; +`; + +interface ScheduleOptionTitleProps { + isSelected: boolean; +} + +export const ScheduleOptionTitle = styled.div<ScheduleOptionTitleProps>` + color: ${props => (props.isSelected ? color("brand") : color("text-medium"))}; + font-size: 1rem; + font-weight: bold; + line-height: 1.25rem; +`; + +export const ScheduleOptionContent = styled.div` + margin-top: 1rem; +`; + +export const ScheduleOptionText = styled.div` + color: ${color("text-medium")}; + font-size: 1rem; + line-height: 1.5rem; + max-width: 38.75rem; +`; diff --git a/frontend/src/metabase/databases/components/DatabaseCacheScheduleField/DatabaseCacheScheduleField.tsx b/frontend/src/metabase/databases/components/DatabaseCacheScheduleField/DatabaseCacheScheduleField.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9f963d2863687ee41dddb0622c1165d7ad1dec4d --- /dev/null +++ b/frontend/src/metabase/databases/components/DatabaseCacheScheduleField/DatabaseCacheScheduleField.tsx @@ -0,0 +1,138 @@ +import React, { ReactNode, useCallback } from "react"; +import { useField, useFormikContext } from "formik"; +import { t } from "ttag"; +import FormField from "metabase/core/components/FormField"; +import SchedulePicker from "metabase/components/SchedulePicker"; +import { ScheduleSettings, ScheduleType } from "metabase-types/api"; +import { DatabaseValues } from "../../types"; +import { + ScheduleOptionList, + ScheduleOptionBody, + ScheduleOptionContent, + ScheduleOptionIndicator, + ScheduleOptionIndicatorBackground, + ScheduleOptionRoot, + ScheduleOptionTitle, + ScheduleOptionText, +} from "./DatabaseCacheScheduleField.styled"; + +const DEFAULT_SCHEDULE: ScheduleSettings = { + schedule_day: "mon", + schedule_frame: null, + schedule_hour: 0, + schedule_type: "daily", +}; + +const SCHEDULE_OPTIONS: ScheduleType[] = ["daily", "weekly", "monthly"]; + +export interface DatabaseCacheScheduleFieldProps { + name: string; + title?: string; + description?: ReactNode; +} + +const DatabaseCacheScheduleField = ({ + name, + title, + description, +}: DatabaseCacheScheduleFieldProps): JSX.Element => { + const { values, setValues } = useFormikContext<DatabaseValues>(); + const [{ value }, , { setValue }] = useField(name); + + const handleScheduleChange = useCallback( + (value: ScheduleSettings) => { + setValue(value); + }, + [setValue], + ); + + const handleFullSyncSelect = useCallback(() => { + setValues(values => ({ + ...values, + is_full_sync: true, + is_on_demand: false, + })); + }, [setValues]); + + const handleOnDemandSyncSelect = useCallback(() => { + setValues(values => ({ + ...values, + schedules: {}, + is_full_sync: false, + is_on_demand: true, + })); + }, [setValues]); + + const handleNoneSyncSelect = useCallback(() => { + setValues(values => ({ + ...values, + schedules: {}, + is_full_sync: false, + is_on_demand: false, + })); + }, [setValues]); + + return ( + <FormField title={title} description={description}> + <ScheduleOptionList> + <ScheduleOption + title={t`Regularly, on a schedule`} + isSelected={values.is_full_sync} + onSelect={handleFullSyncSelect} + > + <SchedulePicker + schedule={value ?? DEFAULT_SCHEDULE} + scheduleOptions={SCHEDULE_OPTIONS} + onScheduleChange={handleScheduleChange} + /> + </ScheduleOption> + <ScheduleOption + title={t`Only when adding a new filter widget`} + isSelected={!values.is_full_sync && values.is_on_demand} + onSelect={handleOnDemandSyncSelect} + > + <ScheduleOptionText> + {t`When a user adds a new filter to a dashboard or a SQL question, Metabase will scan the field(s) mapped to that filter in order to show the list of selectable values.`} + </ScheduleOptionText> + </ScheduleOption> + <ScheduleOption + title={t`Never, I'll do this manually if I need to`} + isSelected={!values.is_full_sync && !values.is_on_demand} + onSelect={handleNoneSyncSelect} + /> + </ScheduleOptionList> + </FormField> + ); +}; + +interface ScheduleOptionProps { + title: string; + isSelected: boolean; + children?: ReactNode; + onSelect: () => void; +} + +const ScheduleOption = ({ + title, + isSelected, + children, + onSelect, +}: ScheduleOptionProps): JSX.Element => { + return ( + <ScheduleOptionRoot isSelected={isSelected} onClick={onSelect}> + <ScheduleOptionIndicator isSelected={isSelected}> + <ScheduleOptionIndicatorBackground isSelected={isSelected} /> + </ScheduleOptionIndicator> + <ScheduleOptionBody> + <ScheduleOptionTitle isSelected={isSelected}> + {title} + </ScheduleOptionTitle> + {children && isSelected && ( + <ScheduleOptionContent>{children}</ScheduleOptionContent> + )} + </ScheduleOptionBody> + </ScheduleOptionRoot> + ); +}; + +export default DatabaseCacheScheduleField; diff --git a/frontend/src/metabase/databases/components/DatabaseCacheScheduleField/index.ts b/frontend/src/metabase/databases/components/DatabaseCacheScheduleField/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..62bfa5f0d7f2ba226c2b06503b2642a5e1987374 --- /dev/null +++ b/frontend/src/metabase/databases/components/DatabaseCacheScheduleField/index.ts @@ -0,0 +1 @@ +export { default } from "./DatabaseCacheScheduleField"; diff --git a/frontend/src/metabase/databases/components/DatabaseEngineField/DatabaseEngineField.tsx b/frontend/src/metabase/databases/components/DatabaseEngineField/DatabaseEngineField.tsx index 8350db138cd5a46ecedfd9b1d15858c114e77aa8..5e302ca05085881bcbef225016ff5d24c41e1549 100644 --- a/frontend/src/metabase/databases/components/DatabaseEngineField/DatabaseEngineField.tsx +++ b/frontend/src/metabase/databases/components/DatabaseEngineField/DatabaseEngineField.tsx @@ -1,5 +1,7 @@ -import React, { useMemo, useRef } from "react"; +import React, { useMemo } from "react"; +import { useFormikContext } from "formik"; import { Engine } from "metabase-types/api"; +import { DatabaseValues } from "../../types"; import { getEngineOptions } from "../../utils/engine"; import DatabaseEngineSelect from "./DatabaseEngineSelect"; import DatabaseEngineWidget from "./DatabaseEngineWidget"; @@ -19,7 +21,7 @@ const DatabaseEngineField = ({ isAdvanced, onChange, }: DatabaseEngineFieldProps): JSX.Element => { - const { current: isNew } = useRef(engineKey == null); + const { values } = useFormikContext<DatabaseValues>(); const options = useMemo(() => { return getEngineOptions(engines, engineKey); @@ -28,7 +30,7 @@ const DatabaseEngineField = ({ return isAdvanced ? ( <DatabaseEngineSelect options={options} - disabled={!isNew} + disabled={values.is_sample} onChange={onChange} /> ) : ( diff --git a/frontend/src/metabase/databases/components/DatabaseForm/DatabaseForm.stories.tsx b/frontend/src/metabase/databases/components/DatabaseForm/DatabaseForm.stories.tsx index 852ee4fb795aa1806c6089b011a1dbbf52e6d25d..018c265ff5ba54f499cc636a2d23f8a0bc40ab5a 100644 --- a/frontend/src/metabase/databases/components/DatabaseForm/DatabaseForm.stories.tsx +++ b/frontend/src/metabase/databases/components/DatabaseForm/DatabaseForm.stories.tsx @@ -15,12 +15,79 @@ const Template: ComponentStory<typeof DatabaseForm> = args => { export const Default = Template.bind({}); Default.args = { engines: { - presto: createMockEngine({ - "driver-name": "Presto (Deprecated Driver)", - "superseded-by": "presto-jdbc", - }), - "presto-jdbc": createMockEngine({ - "driver-name": "Presto", - }), + h2: { + source: { + type: "official", + contact: null, + }, + "details-fields": [ + { + name: "db", + "display-name": "Connection String", + "helper-text": + "The local path relative to where Metabase is running from. Your string should not include the .mv.db extension.", + placeholder: "file:/Users/camsaul/bird_sightings/toucans", + required: true, + }, + { + name: "advanced-options", + type: "section", + default: false, + }, + { + name: "auto_run_queries", + type: "boolean", + default: true, + "display-name": "Rerun queries for simple explorations", + description: + "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": "Choose when syncs and scans happen", + description: + "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": "Database syncing", + description: + "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": { + "advanced-options": true, + "let-user-control-scheduling": true, + }, + }, + { + name: "schedules.cache_field_values", + "display-name": "Scanning for Filter Values", + description: + "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. When should Metabase automatically scan and cache field values?", + "visible-if": { + "advanced-options": true, + "let-user-control-scheduling": true, + }, + }, + { + name: "refingerprint", + type: "boolean", + "display-name": "Periodically refingerprint tables", + description: + "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, + }, + }, + ], + "driver-name": "H2", + "superseded-by": null, + }, }, }; diff --git a/frontend/src/metabase/databases/components/DatabaseForm/DatabaseForm.tsx b/frontend/src/metabase/databases/components/DatabaseForm/DatabaseForm.tsx index 2f27e43cacb759439460cba32d29443cdc14607a..3013bb1e69f9d59999a5e7a2c2c381c970049188 100644 --- a/frontend/src/metabase/databases/components/DatabaseForm/DatabaseForm.tsx +++ b/frontend/src/metabase/databases/components/DatabaseForm/DatabaseForm.tsx @@ -9,6 +9,7 @@ import FormSubmitButton from "metabase/core/components/FormSubmitButton"; import FormErrorMessage from "metabase/core/components/FormErrorMessage"; import { Engine } from "metabase-types/api"; import { DatabaseValues } from "../../types"; +import { getDefaultEngineKey } from "../../utils/engine"; import { getValidationSchema, getVisibleFields } from "../../utils/schema"; import DatabaseEngineField from "../DatabaseEngineField"; import DatabaseNameField from "../DatabaseNameField"; @@ -35,7 +36,9 @@ const DatabaseForm = ({ onCancel, onEngineChange, }: DatabaseFormProps): JSX.Element => { - const [engineKey, setEngineKey] = useState(initialData?.engine); + const [engineKey, setEngineKey] = useState(() => + getInitialEngineKey(engines, initialData, isAdvanced), + ); const engine = engineKey ? engines[engineKey] : undefined; const validationSchema = useMemo(() => { @@ -110,13 +113,11 @@ const DatabaseFormBody = ({ isAdvanced={isAdvanced} onChange={onEngineChange} /> - {!isAdvanced && ( - <DatabaseEngineWarning - engineKey={engineKey} - engines={engines} - onChange={onEngineChange} - /> - )} + <DatabaseEngineWarning + engineKey={engineKey} + engines={engines} + onChange={onEngineChange} + /> {engine && <DatabaseNameField engine={engine} />} {fields.map(field => ( <DatabaseDetailField key={field.name} field={field} /> @@ -136,11 +137,16 @@ const DatabaseFormFooter = ({ onCancel, }: DatabaseFormFooterProps) => { const { values, dirty } = useFormikContext<DatabaseValues>(); + const isNew = values.id == null; if (isAdvanced) { return ( <div> - <FormSubmitButton title={t`Save`} disabled={!dirty} primary /> + <FormSubmitButton + title={isNew ? t`Save` : t`Save changes`} + disabled={!dirty} + primary + /> <FormErrorMessage /> </div> ); @@ -163,4 +169,16 @@ const DatabaseFormFooter = ({ } }; +const getInitialEngineKey = ( + engines: Record<string, Engine>, + values?: DatabaseValues, + isAdvanced?: boolean, +) => { + if (values?.engine) { + return values.engine; + } else if (isAdvanced) { + return getDefaultEngineKey(engines); + } +}; + export default DatabaseForm; diff --git a/frontend/src/metabase/databases/components/DatabaseScheduleToggleField/DatabaseScheduleToggleField.tsx b/frontend/src/metabase/databases/components/DatabaseScheduleToggleField/DatabaseScheduleToggleField.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2d0e010aabe663504f062b8fbb211d8bd6d2657d --- /dev/null +++ b/frontend/src/metabase/databases/components/DatabaseScheduleToggleField/DatabaseScheduleToggleField.tsx @@ -0,0 +1,43 @@ +import React, { ReactNode, useCallback } from "react"; +import { useFormikContext } from "formik"; +import FormToggle from "metabase/core/components/FormToggle"; +import { DatabaseValues } from "../../types"; + +export interface DatabaseScheduleToggleFieldProps { + name: string; + title?: string; + description?: ReactNode; +} + +const DatabaseScheduleToggleField = ({ + name, + title, + description, +}: DatabaseScheduleToggleFieldProps): JSX.Element => { + const { setValues } = useFormikContext<DatabaseValues>(); + + const handleChange = useCallback( + (value: boolean) => { + if (!value) { + setValues(values => ({ + ...values, + schedules: {}, + is_full_sync: true, + is_on_demand: false, + })); + } + }, + [setValues], + ); + + return ( + <FormToggle + name={name} + title={title} + description={description} + onChange={handleChange} + /> + ); +}; + +export default DatabaseScheduleToggleField; diff --git a/frontend/src/metabase/databases/components/DatabaseScheduleToggleField/index.ts b/frontend/src/metabase/databases/components/DatabaseScheduleToggleField/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..0862e6b41c79c05275ea0acec0889d4b60d38643 --- /dev/null +++ b/frontend/src/metabase/databases/components/DatabaseScheduleToggleField/index.ts @@ -0,0 +1 @@ +export { default } from "./DatabaseScheduleToggleField"; diff --git a/frontend/src/metabase/databases/components/DatabaseSyncScheduleField/DatabaseSyncScheduleField.tsx b/frontend/src/metabase/databases/components/DatabaseSyncScheduleField/DatabaseSyncScheduleField.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a009409f0750bb40b4b00fbedb33d694db296dc4 --- /dev/null +++ b/frontend/src/metabase/databases/components/DatabaseSyncScheduleField/DatabaseSyncScheduleField.tsx @@ -0,0 +1,50 @@ +import React, { ReactNode, useCallback } from "react"; +import { useField } from "formik"; +import { t } from "ttag"; +import SchedulePicker from "metabase/components/SchedulePicker"; +import { ScheduleSettings, ScheduleType } from "metabase-types/api"; +import FormField from "metabase/core/components/FormField"; + +const DEFAULT_SCHEDULE: ScheduleSettings = { + schedule_day: "mon", + schedule_frame: null, + schedule_hour: 0, + schedule_type: "daily", +}; + +const SCHEDULE_OPTIONS: ScheduleType[] = ["hourly", "daily"]; + +export interface DatabaseSyncScheduleFieldProps { + name: string; + title?: string; + description?: ReactNode; +} + +const DatabaseSyncScheduleField = ({ + name, + title, + description, +}: DatabaseSyncScheduleFieldProps): JSX.Element => { + const [{ value }, , { setValue }] = useField(name); + + const handleScheduleChange = useCallback( + (value: ScheduleSettings) => { + setValue(value); + }, + [setValue], + ); + + return ( + <FormField title={title} description={description}> + <SchedulePicker + schedule={value ?? DEFAULT_SCHEDULE} + scheduleOptions={SCHEDULE_OPTIONS} + textBeforeInterval={t`Scan`} + minutesOnHourPicker + onScheduleChange={handleScheduleChange} + /> + </FormField> + ); +}; + +export default DatabaseSyncScheduleField; diff --git a/frontend/src/metabase/databases/components/DatabaseSyncScheduleField/index.ts b/frontend/src/metabase/databases/components/DatabaseSyncScheduleField/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..8439442836dddb1555ed9a1b0e1c47f97eaafbc6 --- /dev/null +++ b/frontend/src/metabase/databases/components/DatabaseSyncScheduleField/index.ts @@ -0,0 +1 @@ +export { default } from "./DatabaseSyncScheduleField"; diff --git a/frontend/src/metabase/databases/constants.tsx b/frontend/src/metabase/databases/constants.tsx index 3f7ca3ecaae74064eb618c08cf149bda3a3c93d0..a06df0c091bb176910a00569ec75b22e770acac4 100644 --- a/frontend/src/metabase/databases/constants.tsx +++ b/frontend/src/metabase/databases/constants.tsx @@ -1,10 +1,13 @@ import React from "react"; import { t } from "ttag"; import DatabaseAuthCodeDescription from "./components/DatabaseAuthCodeDescription"; +import DatabaseCacheScheduleField from "./components/DatabaseCacheScheduleField"; import DatabaseClientIdDescription from "./components/DatabaseClientIdDescription"; import DatabaseConnectionSectionField from "./components/DatabaseConnectionSectionField"; +import DatabaseScheduleToggleField from "./components/DatabaseScheduleToggleField"; import DatabaseSshDescription from "./components/DatabaseSshDescription"; import DatabaseSslKeyDescription from "./components/DatabaseSslKeyDescription"; +import DatabaseSyncScheduleField from "./components/DatabaseSyncScheduleField"; import { EngineFieldOverride } from "./types"; export const ELEVATED_ENGINES = [ @@ -92,11 +95,16 @@ export const FIELD_OVERRIDES: Record<string, EngineFieldOverride> = { "use-conn-uri": { type: DatabaseConnectionSectionField, }, + "let-user-control-scheduling": { + type: DatabaseScheduleToggleField, + }, "schedules.metadata_sync": { name: "schedules.metadata_sync", + type: DatabaseSyncScheduleField, }, "schedules.cache_field_values": { name: "schedules.cache_field_values", + type: DatabaseCacheScheduleField, }, auto_run_queries: { name: "auto_run_queries", diff --git a/frontend/src/metabase/databases/containers/DatabaseForm/DatabaseForm.tsx b/frontend/src/metabase/databases/containers/DatabaseForm/DatabaseForm.tsx index eaab029de550b8e25989be2f5707d08d1d8eefd9..988b47982a66acda587b9ab1c7b64268192fd9c3 100644 --- a/frontend/src/metabase/databases/containers/DatabaseForm/DatabaseForm.tsx +++ b/frontend/src/metabase/databases/containers/DatabaseForm/DatabaseForm.tsx @@ -3,8 +3,9 @@ import { getSetting } from "metabase/selectors/settings"; import { State } from "metabase-types/store"; import DatabaseForm, { DatabaseFormProps } from "../../components/DatabaseForm"; -type DatabaseFormOwnProps = Omit<DatabaseFormProps, "engines" | "isHosted">; -type DatabaseFormStateProps = Pick<DatabaseFormProps, "engines" | "isHosted">; +type DatabaseFormStateKeys = "engines" | "isHosted"; +type DatabaseFormOwnProps = Omit<DatabaseFormProps, DatabaseFormStateKeys>; +type DatabaseFormStateProps = Pick<DatabaseFormProps, DatabaseFormStateKeys>; const mapStateToProps = (state: State) => ({ engines: getSetting(state, "engines"), diff --git a/frontend/src/metabase/databases/types.ts b/frontend/src/metabase/databases/types.ts index e764f02ac61c45e142bf1be421b016f6f62103ac..c2a49942ffae6e5a4f0663307c76b666b553a368 100644 --- a/frontend/src/metabase/databases/types.ts +++ b/frontend/src/metabase/databases/types.ts @@ -1,17 +1,22 @@ import { ComponentType, ReactNode } from "react"; import { + DatabaseId, DatabaseSchedules, EngineFieldOption, EngineFieldType, } from "metabase-types/api"; export interface DatabaseValues { + id?: DatabaseId; name: string; engine: string | undefined; details: Record<string, unknown>; schedules: DatabaseSchedules; auto_run_queries: boolean; refingerprint: boolean; + is_sample: boolean; + is_full_sync: boolean; + is_on_demand: boolean; } export interface EngineOption { @@ -21,8 +26,8 @@ export interface EngineOption { } export interface EngineFieldOverride { - type?: EngineFieldType | ComponentType<EngineFieldProps>; name?: string; + type?: EngineFieldType | ComponentType<EngineFieldProps>; title?: string; description?: ReactNode; placeholder?: unknown; diff --git a/frontend/src/metabase/databases/utils/engine.ts b/frontend/src/metabase/databases/utils/engine.ts index 39ba32f61e254a75f89726357c66d6ec440ef327..af754e4b4c187494d63e18c755c2bc7944780efc 100644 --- a/frontend/src/metabase/databases/utils/engine.ts +++ b/frontend/src/metabase/databases/utils/engine.ts @@ -30,3 +30,7 @@ export const getEngineLogo = (engine: string): string | undefined => { const logo = ENGINE_LOGO[engine]; return logo ? `app/assets/img/drivers/${logo}` : undefined; }; + +export const getDefaultEngineKey = (engines: Record<string, Engine>) => { + return engines["postgres"] ? "postgres" : Object.keys(engines)[0]; +}; diff --git a/frontend/src/metabase/databases/utils/schema.ts b/frontend/src/metabase/databases/utils/schema.ts index f31f7d3168460ca46466ee1ab9c9cb820ebcfae7..d3c28f8706f93cc2cad1fcac887b865e19f2df7f 100644 --- a/frontend/src/metabase/databases/utils/schema.ts +++ b/frontend/src/metabase/databases/utils/schema.ts @@ -23,6 +23,9 @@ export const getValidationSchema = ( }), auto_run_queries: Yup.boolean().default(true), refingerprint: Yup.boolean().default(false), + is_sample: Yup.boolean().default(false), + is_full_sync: Yup.boolean().default(true), + is_on_demand: Yup.boolean().default(false), }); };