diff --git a/enterprise/frontend/src/metabase-enterprise/caching/components/StrategyEditorForDatabases.unit.spec.tsx b/enterprise/frontend/src/metabase-enterprise/caching/components/StrategyEditorForDatabases.unit.spec.tsx index e846565d3870cc02f3e3bee00a4c61187b6f8eee..a4c559e76fa9ee6a7d3c79cd8ab9fd3a7147982e 100644 --- a/enterprise/frontend/src/metabase-enterprise/caching/components/StrategyEditorForDatabases.unit.spec.tsx +++ b/enterprise/frontend/src/metabase-enterprise/caching/components/StrategyEditorForDatabases.unit.spec.tsx @@ -73,7 +73,7 @@ describe("StrategyEditorForDatabases", () => { expect(await getSaveButton()).toBeInTheDocument(); await act(async () => { - await changeInput(/minimum query duration/i, 60000, 70000); + await changeInput(/minimum query duration/i, 1, 5); await changeInput(/multiplier/i, 10, 3); }); @@ -136,7 +136,7 @@ describe("StrategyEditorForDatabases", () => { expect((await screen.findAllByRole("spinbutton")).length).toBe(2); await act(async () => { - await changeInput(/minimum query duration/i, 60000, 70000); + await changeInput(/minimum query duration/i, 1, 5); await changeInput(/multiplier/i, 10, 3); }); diff --git a/frontend/src/metabase-types/api/performance.ts b/frontend/src/metabase-types/api/performance.ts index 3a4730c9c9c4f48a96f8af45af00492cd48d13af..ffd7fab62c5aa8b24b5f0947b2ba177a277db187 100644 --- a/frontend/src/metabase-types/api/performance.ts +++ b/frontend/src/metabase-types/api/performance.ts @@ -17,6 +17,7 @@ export interface TTLStrategy extends StrategyBase { type: "ttl"; multiplier: number; min_duration_ms: number; + min_duration_seconds?: number; } export interface DoNotCacheStrategy extends StrategyBase { diff --git a/frontend/src/metabase/admin/performance/components/StrategyEditorForDatabases.tsx b/frontend/src/metabase/admin/performance/components/StrategyEditorForDatabases.tsx index 1a3b5d2b39328f610ebfbe25315bf4b93520bada..4c115e34068ad281df4878d2f8a77fbc18c0093c 100644 --- a/frontend/src/metabase/admin/performance/components/StrategyEditorForDatabases.tsx +++ b/frontend/src/metabase/admin/performance/components/StrategyEditorForDatabases.tsx @@ -17,7 +17,12 @@ import { useCacheConfigs } from "../hooks/useCacheConfigs"; import { useConfirmOnRouteLeave } from "../hooks/useConfirmOnRouteLeave"; import { useVerticallyOverflows } from "../hooks/useVerticallyOverflows"; import type { UpdateTargetId } from "../strategies"; -import { getFieldsForStrategyType, rootId, Strategies } from "../strategies"; +import { + getFieldsForStrategyType, + rootId, + Strategies, + translateConfigToAPI, +} from "../strategies"; import { Panel, TabWrapper } from "./StrategyEditorForDatabases.styled"; import { StrategyForm } from "./StrategyForm"; @@ -137,7 +142,8 @@ const StrategyEditorForDatabases_Base = ({ strategy: validatedStrategy, }; - await CacheConfigApi.update(newConfig); + const translatedConfig = translateConfigToAPI(newConfig); + await CacheConfigApi.update(translatedConfig); setConfigs([...otherConfigs, newConfig]); } }, diff --git a/frontend/src/metabase/admin/performance/components/StrategyEditorForDatabases.unit.spec.tsx b/frontend/src/metabase/admin/performance/components/StrategyEditorForDatabases.unit.spec.tsx index 136ec297d2221ee4fc1de1d32c2c86bf14ca7870..674b24c3bdd6f74693b54284b81c5e5eedbe7f19 100644 --- a/frontend/src/metabase/admin/performance/components/StrategyEditorForDatabases.unit.spec.tsx +++ b/frontend/src/metabase/admin/performance/components/StrategyEditorForDatabases.unit.spec.tsx @@ -19,7 +19,7 @@ describe("StrategyEditorForDatabases", () => { expect(await getSaveButton()).toBeInTheDocument(); await act(async () => { - await changeInput(/minimum query duration/i, 60000, 70000); + await changeInput(/minimum query duration/i, 1, 5); await changeInput(/multiplier/i, 10, 3); }); diff --git a/frontend/src/metabase/admin/performance/components/StrategyForm.tsx b/frontend/src/metabase/admin/performance/components/StrategyForm.tsx index 2d83fb556cb438813969164862c511a3afeee4da..d8982a84ac46ada566f9f19925ac5fa1e1b1cf8f 100644 --- a/frontend/src/metabase/admin/performance/components/StrategyForm.tsx +++ b/frontend/src/metabase/admin/performance/components/StrategyForm.tsx @@ -104,9 +104,12 @@ const StrategyFormBody = ({ <> <Field title={t`Minimum query duration`} - subtitle={t`Metabase will cache all saved questions with an average query execution time greater than this many milliseconds.`} + subtitle={t`Metabase will cache all saved questions with an average query execution time greater than this many seconds.`} > - <PositiveNumberInput strategyType="ttl" name="min_duration_ms" /> + <PositiveNumberInput + strategyType="ttl" + name="min_duration_seconds" + /> </Field> <Field title={t`Cache time-to-live (TTL) multiplier`} diff --git a/frontend/src/metabase/admin/performance/hooks/useCacheConfigs.tsx b/frontend/src/metabase/admin/performance/hooks/useCacheConfigs.tsx index d1d29e08b87ab62c59229b05cec5a24c2bb0c643..fd0cdade7d253eef9932d1e7d1f5f680da35282a 100644 --- a/frontend/src/metabase/admin/performance/hooks/useCacheConfigs.tsx +++ b/frontend/src/metabase/admin/performance/hooks/useCacheConfigs.tsx @@ -5,7 +5,7 @@ import { useDatabaseListQuery } from "metabase/common/hooks"; import { CacheConfigApi } from "metabase/services"; import type { CacheConfigAPIResponse, Config } from "metabase-types/api"; -import { rootId } from "../strategies"; +import { rootId, translateConfigFromAPI } from "../strategies"; import { useRecentlyTrue } from "./useRecentlyTrue"; @@ -28,7 +28,9 @@ export const useCacheConfigs = ({ })) as CacheConfigAPIResponse ).data : []; - const configs = [...rootConfigsFromAPI, ...dbConfigsFromAPI]; + const configs = [...rootConfigsFromAPI, ...dbConfigsFromAPI].map(config => + translateConfigFromAPI(config), + ); return configs; }, []); diff --git a/frontend/src/metabase/admin/performance/strategies.ts b/frontend/src/metabase/admin/performance/strategies.ts index 7e4307c549b06d324276608a770f9db8ad78b046..2bb3edf3beee453c73d1f1411b19b13ec7de5537 100644 --- a/frontend/src/metabase/admin/performance/strategies.ts +++ b/frontend/src/metabase/admin/performance/strategies.ts @@ -3,7 +3,7 @@ import type { AnySchema } from "yup"; import * as Yup from "yup"; import type { SchemaObjectDescription } from "yup/lib/schema"; -import type { Strategy, StrategyType } from "metabase-types/api"; +import type { Config, Strategy, StrategyType } from "metabase-types/api"; import { DurationUnit } from "metabase-types/api"; export type UpdateTargetId = ( @@ -22,8 +22,8 @@ export const rootId = 0; const durationUnits = new Set(Object.values(DurationUnit).map(String)); const positiveInteger = Yup.number() - .positive(t`The minimum query duration must be a positive number.`) - .integer(t`The minimum query duration must be an integer.`); + .positive(t`Enter a positive number.`) + .integer(t`Enter an integer.`); export const inheritStrategyValidationSchema = Yup.object({ type: Yup.string().equals(["inherit"]), @@ -33,9 +33,13 @@ export const doNotCacheStrategyValidationSchema = Yup.object({ type: Yup.string().equals(["nocache"]), }); +export const defaultMinDurationMs = 1000; export const ttlStrategyValidationSchema = Yup.object({ type: Yup.string().equals(["ttl"]), - min_duration_ms: positiveInteger.default(60000), + min_duration_ms: positiveInteger.default(defaultMinDurationMs), + min_duration_seconds: positiveInteger.default( + Math.ceil(defaultMinDurationMs / 1000), + ), multiplier: positiveInteger.default(10), }); @@ -126,3 +130,29 @@ export const getFieldsForStrategyType = (strategyType: StrategyType) => { const fields = Object.keys(fieldRecord); return fields; }; + +export const translateConfig = ( + config: Config, + direction: "fromAPI" | "toAPI", +): Config => { + const translated: Config = { ...config }; + if (translated.strategy.type === "ttl") { + if (direction === "fromAPI") { + translated.strategy.min_duration_seconds = Math.ceil( + translated.strategy.min_duration_ms / 1000, + ); + } else { + translated.strategy.min_duration_ms = + translated.strategy.min_duration_seconds === undefined + ? defaultMinDurationMs + : translated.strategy.min_duration_seconds * 1000; + delete translated.strategy.min_duration_seconds; + } + } + return translated; +}; + +export const translateConfigFromAPI = (config: Config): Config => + translateConfig(config, "fromAPI"); +export const translateConfigToAPI = (config: Config): Config => + translateConfig(config, "toAPI");