Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🤖 backported "Improve Clear cache button text and question refresh button tooltip" #47321

Open
wants to merge 2 commits into
base: release-x.50.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
wip
  • Loading branch information
rafpaf committed Aug 27, 2024
commit 1754b704cfc2c3c69da9ceda5276ccf8fb108c94
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 20,7 @@ export const interceptRoutes = () => {
cy.intercept("POST", "/api/cache/invalidate?include=overrides&database=*").as(
"invalidateCacheForSampleDatabase",
);
cy.intercept("POST", "/api/cache/invalidate?*").as("invalidateCache");
};

/** Cypress log messages sometimes occur out of order so it is helpful to log to the console as well. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 156,7 @@ describeEE("EE", () => {
saveCacheStrategyForm({ strategyType: "duration", model: "database" });

cy.log("Now there's a 'Clear cache' button. Click it");
cy.button(/Clear cache/).click();
cy.button(/Clear cache for this database/).click();

cy.log('Confirm via the "Clear cache" dialog');
cy.findByRole("dialog")
Expand Down
24 changes: 24 additions & 0 deletions e2e/test/scenarios/dashboard/dashboard.cy.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 1289,30 @@ describeEE("scenarios > dashboard > caching", () => {
cy.findByLabelText(/Caching policy/).should("contain", "Adaptive");
});
});

it("can click 'Clear cache' for a dashboard", () => {
interceptPerformanceRoutes();
visitDashboard(ORDERS_DASHBOARD_ID);

openSidebarCacheStrategyForm();

rightSidebar().within(() => {
cy.findByRole("heading", { name: /Caching settings/ }).should(
"be.visible",
);
cy.findByRole("button", {
name: /Clear cache for this dashboard/,
}).click();
});

modal().within(() => {
cy.findByRole("button", { name: /Clear cache/ }).click();
});
cy.wait("@invalidateCache");
rightSidebar().within(() => {
cy.findByText("Cache cleared").should("be.visible");
});
});
});

describe("scenarios > dashboard > permissions", () => {
Expand Down
25 changes: 25 additions & 0 deletions e2e/test/scenarios/question/caching.cy.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 1,7 @@
import { ORDERS_QUESTION_ID } from "e2e/support/cypress_sample_instance_data";
import {
describeEE,
modal,
restore,
rightSidebar,
setTokenFeatures,
Expand Down Expand Up @@ -54,4 55,28 @@ describeEE("scenarios > question > caching", () => {
cy.findByLabelText(/Caching policy/).should("contain", "Adaptive");
});
});

it("can click 'Clear cache' for a question", () => {
interceptPerformanceRoutes();
visitQuestion(ORDERS_QUESTION_ID);

openSidebarCacheStrategyForm();

rightSidebar().within(() => {
cy.findByRole("heading", { name: /Caching settings/ }).should(
"be.visible",
);
cy.findByRole("button", {
name: /Clear cache for this question/,
}).click();
});
modal().within(() => {
cy.findByRole("button", { name: /Clear cache/ }).click();
});
cy.wait("@invalidateCache");

rightSidebar().within(() => {
cy.findByText("Cache cleared").should("be.visible");
});
});
});
Original file line number Diff line number Diff line change
@@ -1,10 1,11 @@
import { useFormikContext } from "formik";
import { useCallback } from "react";
import { useCallback, useMemo } from "react";
import { c, t } from "ttag";

import { IconInButton } from "metabase/admin/performance/components/StrategyForm.styled";
import { useInvalidateTarget } from "metabase/admin/performance/hooks/useInvalidateTarget";
import { useIsFormPending } from "metabase/admin/performance/hooks/useIsFormPending";
import type { ModelWithClearableCache } from "metabase/admin/performance/types";
import { Form, FormProvider } from "metabase/forms";
import { useConfirmation } from "metabase/hooks/use-confirmation";
import { color } from "metabase/lib/colors";
Expand All @@ -13,6 14,7 @@ import { Group, Icon, Loader, Text } from "metabase/ui";

import { StyledInvalidateNowButton } from "./InvalidateNowButton.styled";

/** Button that clears the cache of a particular object (the "target") */
export const InvalidateNowButton = ({
targetId,
targetModel,
Expand All @@ -21,12 23,21 @@ export const InvalidateNowButton = ({
const invalidateTarget = useInvalidateTarget(targetId, targetModel);
return (
<FormProvider initialValues={{}} onSubmit={invalidateTarget}>
<InvalidateNowFormBody targetName={targetName} />
<InvalidateNowFormBody
targetModel={targetModel}
targetName={targetName}
/>
</FormProvider>
);
};

const InvalidateNowFormBody = ({ targetName }: { targetName?: string }) => {
const InvalidateNowFormBody = ({
targetName,
targetModel,
}: {
targetName?: string;
targetModel: ModelWithClearableCache;
}) => {
const { show: askConfirmation, modalContent: confirmationModal } =
useConfirmation();
const { submitForm } = useFormikContext();
Expand All @@ -45,6 56,19 @@ const InvalidateNowFormBody = ({ targetName }: { targetName?: string }) => {
[askConfirmation, targetName, submitForm],
);

const buttonText = useMemo(() => {
switch (targetModel) {
case "dashboard":
return t`Clear cache for this dashboard`;
case "question":
return t`Clear cache for this question`;
case "database":
return t`Clear cache for this database`;
default:
return t`Clear cache`;
}
}, [targetModel]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ts-pattern isn't available on v50 :-)


return (
<>
<Form>
Expand All @@ -57,8 81,8 @@ const InvalidateNowFormBody = ({ targetName }: { targetName?: string }) => {
disabled={wasFormRecentlyPending}
label={
<Group spacing="sm">
<Icon color={color("danger")} name="trash" />
<Text>{t`Clear cache`}</Text>
<Icon color="var(--mb-color-danger)" name="trash" />
<Text>{buttonText}</Text>
</Group>
}
activeLabel={
Expand Down
8 changes: 0 additions & 8 deletions frontend/src/metabase-lib/v1/Question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 226,6 @@ class Question {
return this.setCard(assoc(this.card(), "display", display));
}

cacheTTL(): number | null {
return this._card?.cache_ttl;
}

setCacheTTL(cache) {
return this.setCard(assoc(this.card(), "cache_ttl", cache));
}

type(): CardType {
return this._card?.type ?? "question";
}
Expand Down
14 changes: 13 additions & 1 deletion frontend/src/metabase-types/api/dataset.ts
Original file line number Diff line number Diff line change
@@ -1,4 1,4 @@
import type { LocalFieldReference } from "metabase-types/api";
import type { CacheStrategy, LocalFieldReference } from "metabase-types/api";

import type { Card } from "./card";
import type { DatabaseId } from "./database";
Expand Down Expand Up @@ -66,6 66,12 @@ export interface DatasetData {

export type JsonQuery = DatasetQuery & {
parameters?: unknown[];
"cache-strategy"?: CacheStrategy & {
/** An ISO 8601 date */
"invalidated-at"?: string;
/** In milliseconds */
"avg-execution-ms"?: number;
};
};

export interface Dataset {
Expand All @@ -81,6 87,12 @@ export interface Dataset {
};
context?: string;
status?: string;
/** In milliseconds */
average_execution_time?: number;
/** A date in ISO 8601 format */
cached?: string;
/** A date in ISO 8601 format */
started_at?: string;
}

export interface EmbedDatasetData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 41,7 @@ import { DurationUnit } from "metabase-types/api";
import { strategyValidationSchema } from "../constants/complex";
import { rootId } from "../constants/simple";
import { useIsFormPending } from "../hooks/useIsFormPending";
import { isModelWithClearableCache } from "../types";
import {
cronToScheduleSettings,
getLabelString,
Expand Down Expand Up @@ -279,10 280,6 @@ const FormButtons = ({
}: FormButtonsProps) => {
const { dirty } = useFormikContext<Strategy>();

if (targetId === rootId) {
shouldAllowInvalidation = false;
}

const { isFormPending, wasFormRecentlyPending } = useIsFormPending();

const isSavingPossible = dirty || isFormPending || wasFormRecentlyPending;
Expand All @@ -300,7 297,12 @@ const FormButtons = ({
);
}

if (shouldAllowInvalidation && targetId && targetName) {
if (
shouldAllowInvalidation &&
isModelWithClearableCache(targetModel) &&
targetId &&
targetName
) {
return (
<FormButtonsGroup isInSidebar={isInSidebar}>
<PLUGIN_CACHING.InvalidateNowButton
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/metabase/admin/performance/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 22,12 @@ export enum PerformanceTabId {
Databases = "databases",
Models = "models",
}

export type ModelWithClearableCache = Exclude<CacheableModel, "root">;

/** The default policy's cache cannot be cleared. But objects of other kinds,
* such as dashboards, databases, and questions, can have their cache cleared
* (if they have a cache) */
export const isModelWithClearableCache = (
model: CacheableModel,
): model is ModelWithClearableCache => model !== "root";
4 changes: 3 additions & 1 deletion frontend/src/metabase/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 11,7 @@ import type { AnySchema } from "yup";

import noResultsSource from "assets/img/no_results.svg";
import { strategies } from "metabase/admin/performance/constants/complex";
import type { ModelWithClearableCache } from "metabase/admin/performance/types";
import { UNABLE_TO_CHANGE_ADMIN_PERMISSIONS } from "metabase/admin/permissions/constants/messages";
import {
type DataPermission,
Expand Down Expand Up @@ -347,7 348,8 @@ export const PLUGIN_MODERATION = {

export type InvalidateNowButtonProps = {
targetId: number;
targetModel: CacheableModel;
/** The type of object that the target is */
targetModel: ModelWithClearableCache;
targetName: string;
};

Expand Down
Loading