Skip to content
Snippets Groups Projects
Unverified Commit 75e77f87 authored by Anton Kulyk's avatar Anton Kulyk Committed by GitHub
Browse files

Update model caching DB control (#23592)

* Expose `site-uuid` setting

* Add `site-uuid` to settings type

* Add utility generating cache schema name

* Update model caching control

* Fix copy
parent aa96149b
No related branches found
No related tags found
No related merge requests found
import styled from "@emotion/styled";
import Icon from "metabase/components/Icon";
import { color } from "metabase/lib/colors";
export const ControlContainer = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 1rem;
`;
export const HoverableIcon = styled(Icon)`
cursor: pointer;
&:hover {
color: ${color("brand")};
}
`;
export const PopoverContent = styled.div`
padding: 1.5rem;
`;
export const FeatureTitle = styled.h4`
color: ${color("text-dark")};
font-weight: 700;
`;
export const FeatureDescriptionText = styled.p`
color: ${color("text-medium")};
font-weight: 400;
`;
import React from "react";
import { t, jt } from "ttag";
import ExternalLink from "metabase/core/components/ExternalLink";
import ActionButton from "metabase/components/ActionButton";
import TippyPopover from "metabase/components/Popover/TippyPopover";
import { getModelCacheSchemaName } from "metabase/lib/data-modeling/utils";
import MetabaseSettings from "metabase/lib/settings";
import {
ControlContainer,
HoverableIcon,
PopoverContent,
FeatureTitle,
FeatureDescriptionText,
} from "./ModelCachingControl.styled";
interface Props {
databaseId: number;
isEnabled: boolean;
onToggle: (isEnabled: boolean) => Promise<void>;
}
function FeatureDescription({ schemaName }: { schemaName: string }) {
const docsLink = (
<ExternalLink
key="model-caching-link"
href={MetabaseSettings.docsUrl("users-guide/models")}
>{t`Learn more.`}</ExternalLink>
);
return (
<PopoverContent>
<FeatureTitle>{t`Cache models`}</FeatureTitle>
<FeatureDescriptionText>{jt`We'll create tables with model data and refresh them on a schedule you define. To enable it, you need to grant this connection credential read and write permissions on the "${schemaName}" schema or grant create schema permissions. ${docsLink}`}</FeatureDescriptionText>
</PopoverContent>
);
}
function ModelCachingControl({ databaseId, isEnabled, onToggle }: Props) {
const normalText = isEnabled
? t`Turn model caching off`
: t`Turn model caching on`;
const cacheSchemaName = getModelCacheSchemaName(databaseId);
return (
<ControlContainer>
<ActionButton
className="Button"
normalText={normalText}
failedText={t`Failed`}
successText={t`Done`}
actionFn={() => onToggle(!isEnabled)}
/>
<TippyPopover
placement="right-end"
content={<FeatureDescription schemaName={cacheSchemaName} />}
>
<HoverableIcon name="info" />
</TippyPopover>
</ControlContainer>
);
}
export default ModelCachingControl;
export { default } from "./ModelCachingControl";
......@@ -8,6 +8,8 @@ import ActionButton from "metabase/components/ActionButton";
import ModalWithTrigger from "metabase/components/ModalWithTrigger";
import ConfirmContent from "metabase/components/ConfirmContent";
import Button from "metabase/core/components/Button";
import ModelCachingControl from "./ModelCachingControl";
import { SidebarRoot } from "./Sidebar.styled";
const propTypes = {
......@@ -69,25 +71,17 @@ const DatabaseEditAppSidebar = ({
</li>
{isModelPersistenceEnabled && database.supportsPersistence() && (
<li className="mt2">
{database.isPersisted() ? (
<ActionButton
actionFn={() => unpersistDatabase(database.id)}
className="Button"
normalText={t`Disable model persistence`}
activeText={t`Disabling…`}
failedText={t`Failed`}
successText={t`Done`}
/>
) : (
<ActionButton
actionFn={() => persistDatabase(database.id)}
className="Button"
normalText={t`Enable model persistence`}
activeText={t`Enabling…`}
failedText={t`Failed`}
successText={t`Done`}
/>
)}
<ModelCachingControl
databaseId={database.id}
isEnabled={database.isPersisted()}
onToggle={isEnabled => {
if (isEnabled) {
return persistDatabase(database.id);
} else {
return unpersistDatabase(database.id);
}
}}
/>
</li>
)}
</ol>
......
......@@ -4,6 +4,7 @@ import Database from "metabase-lib/lib/metadata/Database";
import { isStructured } from "metabase/lib/query";
import { getQuestionVirtualTableId } from "metabase/lib/saved-questions";
import MetabaseSettings from "metabase/lib/settings";
import { ModelCacheRefreshStatus } from "metabase-types/api";
import { TemplateTag } from "metabase-types/types/Query";
......@@ -75,3 +76,10 @@ export function checkCanRefreshModelCache(
) {
return refreshInfo.state === "persisted" || refreshInfo.state === "error";
}
export function getModelCacheSchemaName(databaseId: number) {
const siteUUID = MetabaseSettings.get("site-uuid") as string;
const uuidParts = siteUUID.split("-");
const firstLetters = uuidParts.map(part => part.charAt(0)).join("");
return `metabase_cache_${firstLetters}_${databaseId}`;
}
import MetabaseSettings from "metabase/lib/settings";
import Question from "metabase-lib/lib/Question";
import Database from "metabase-lib/lib/metadata/Database";
......@@ -19,6 +21,7 @@ import {
isAdHocModelQuestion,
isAdHocModelQuestionCard,
checkCanRefreshModelCache,
getModelCacheSchemaName,
} from "./utils";
type NativeQuestionFactoryOpts = {
......@@ -275,4 +278,24 @@ describe("data model utils", () => {
});
});
});
describe("getModelCacheSchemaName", () => {
const DB_ID = 9;
beforeEach(() => {
const defaultGet = MetabaseSettings.get;
jest.spyOn(MetabaseSettings, "get").mockImplementation(key => {
if (key === "site-uuid") {
return "143dd8ce-e116-4c7f-8d6d-32e99eaefbbc";
}
return defaultGet(key);
});
});
it("generates correct schema name", () => {
expect(getModelCacheSchemaName(DB_ID)).toBe(
`metabase_cache_1e483_${DB_ID}`,
);
});
});
});
......@@ -85,6 +85,7 @@ export type SettingName =
| "search-typeahead-enabled"
| "setup-token"
| "site-url"
| "site-uuid"
| "types"
| "version-info-last-checked"
| "version-info"
......
......@@ -85,7 +85,7 @@
;; Don't i18n this docstring because it's not user-facing! :)
"Unique identifier used for this instance of Metabase. This is set once and only once the first time it is fetched via
its magic getter. Nice!"
:visibility :internal
:visibility :authenticated
:setter :none
;; magic getter will either fetch value from DB, or if no value exists, set the value to a random UUID.
:type ::uuid-nonce)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment