From df158d8896d46b817d23884297146a875d45f97d Mon Sep 17 00:00:00 2001 From: Denis Berezin <denis.berezin@metabase.com> Date: Thu, 24 Oct 2024 14:20:38 +0300 Subject: [PATCH] fix(sdk): remove runtime error on aggregated question drill (#49064) --- .../interactive-question.cy.spec.ts | 101 ++++++++++++++++++ .../InteractiveQuestion.stories.tsx | 33 ++++++ .../hooks/private/use-load-question.ts | 1 + .../run-question-on-navigate.ts | 3 + 4 files changed, 138 insertions(+) create mode 100644 e2e/test/scenarios/embedding-sdk/interactive-question.cy.spec.ts create mode 100644 enterprise/frontend/src/embedding-sdk/components/public/InteractiveQuestion/InteractiveQuestion.stories.tsx diff --git a/e2e/test/scenarios/embedding-sdk/interactive-question.cy.spec.ts b/e2e/test/scenarios/embedding-sdk/interactive-question.cy.spec.ts new file mode 100644 index 00000000000..21590930a2d --- /dev/null +++ b/e2e/test/scenarios/embedding-sdk/interactive-question.cy.spec.ts @@ -0,0 +1,101 @@ +import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database"; +import { + createQuestion, + popover, + restore, + setTokenFeatures, + visitFullAppEmbeddingUrl, +} from "e2e/support/helpers"; +import { + EMBEDDING_SDK_STORY_HOST, + describeSDK, +} from "e2e/support/helpers/e2e-embedding-sdk-helpers"; +import { + JWT_SHARED_SECRET, + setupJwt, +} from "e2e/support/helpers/e2e-jwt-helpers"; + +const { ORDERS, ORDERS_ID } = SAMPLE_DATABASE; + +describeSDK("scenarios > embedding-sdk > interactive-question", () => { + beforeEach(() => { + restore(); + cy.signInAsAdmin(); + setTokenFeatures("all"); + setupJwt(); + cy.request("PUT", "/api/setting", { + "enable-embedding-sdk": true, + }); + + createQuestion( + { + name: "47563", + query: { + "source-table": ORDERS_ID, + aggregation: [["max", ["field", ORDERS.QUANTITY, null]]], + breakout: [["field", ORDERS.PRODUCT_ID, null]], + limit: 2, + }, + }, + { wrapId: true }, + ); + + cy.signOut(); + + cy.intercept("GET", "/api/card/*").as("getCard"); + cy.intercept("GET", "/api/user/current").as("getUser"); + cy.intercept("POST", "/api/card/*/query").as("cardQuery"); + + cy.get("@questionId").then(questionId => { + visitFullAppEmbeddingUrl({ + url: EMBEDDING_SDK_STORY_HOST, + qs: { + id: "embeddingsdk-interactivequestion--default", + viewMode: "story", + }, + onBeforeLoad: (window: any) => { + window.JWT_SHARED_SECRET = JWT_SHARED_SECRET; + window.METABASE_INSTANCE_URL = Cypress.config().baseUrl; + window.QUESTION_ID = questionId; + }, + }); + }); + + cy.wait("@getUser").then(({ response }) => { + expect(response?.statusCode).to.equal(200); + }); + + cy.wait("@getCard").then(({ response }) => { + expect(response?.statusCode).to.equal(200); + }); + }); + + it("should show question content", () => { + cy.get("#metabase-sdk-root") + .should("be.visible") + .within(() => { + cy.findByText("Product ID").should("be.visible"); + cy.findByText("Max of Quantity").should("be.visible"); + }); + }); + + it("should not fail on aggregated question drill", () => { + cy.wait("@cardQuery").then(({ response }) => { + expect(response?.statusCode).to.equal(202); + }); + + cy.findAllByTestId("cell-data").last().click(); + + cy.on("uncaught:exception", error => { + expect( + error.message.includes( + "Error converting :aggregation reference: no aggregation at index 0", + ), + ).to.be.false; + }); + + popover().findByText("See these Orders").click(); + + cy.icon("warning").should("not.exist"); + }); +}); diff --git a/enterprise/frontend/src/embedding-sdk/components/public/InteractiveQuestion/InteractiveQuestion.stories.tsx b/enterprise/frontend/src/embedding-sdk/components/public/InteractiveQuestion/InteractiveQuestion.stories.tsx new file mode 100644 index 00000000000..a9c05f1ea0e --- /dev/null +++ b/enterprise/frontend/src/embedding-sdk/components/public/InteractiveQuestion/InteractiveQuestion.stories.tsx @@ -0,0 +1,33 @@ +import type { StoryFn } from "@storybook/react"; +import type { ComponentProps } from "react"; + +import { CommonSdkStoryWrapper } from "embedding-sdk/test/CommonSdkStoryWrapper"; + +import { InteractiveQuestion } from "./InteractiveQuestion"; + +const QUESTION_ID = (window as any).QUESTION_ID || 12; + +type InteractiveQuestionComponentProps = ComponentProps< + typeof InteractiveQuestion +>; + +export default { + title: "EmbeddingSDK/InteractiveQuestion", + component: InteractiveQuestion, + parameters: { + layout: "fullscreen", + }, + decorators: [CommonSdkStoryWrapper], +}; + +const Template: StoryFn<InteractiveQuestionComponentProps> = args => { + return <InteractiveQuestion {...args} />; +}; + +export const Default = { + render: Template, + + args: { + questionId: QUESTION_ID, + }, +}; diff --git a/enterprise/frontend/src/embedding-sdk/hooks/private/use-load-question.ts b/enterprise/frontend/src/embedding-sdk/hooks/private/use-load-question.ts index 13cfbd2fa60..14a788379c7 100644 --- a/enterprise/frontend/src/embedding-sdk/hooks/private/use-load-question.ts +++ b/enterprise/frontend/src/embedding-sdk/hooks/private/use-load-question.ts @@ -131,6 +131,7 @@ export function useLoadQuestion({ originalQuestion, cancelDeferred: deferred(), onQuestionChange: question => setQuestionState({ question }), + onClearQueryResults: () => setQuestionState({ queryResults: [null] }), }), ); diff --git a/enterprise/frontend/src/embedding-sdk/lib/interactive-question/run-question-on-navigate.ts b/enterprise/frontend/src/embedding-sdk/lib/interactive-question/run-question-on-navigate.ts index fc4e4cd35a6..8661e9bb940 100644 --- a/enterprise/frontend/src/embedding-sdk/lib/interactive-question/run-question-on-navigate.ts +++ b/enterprise/frontend/src/embedding-sdk/lib/interactive-question/run-question-on-navigate.ts @@ -14,6 +14,7 @@ import type { Dispatch, GetState } from "metabase-types/store"; interface RunQuestionOnNavigateParams extends NavigateToNewCardParams { originalQuestion?: Question; onQuestionChange: (question: Question) => void; + onClearQueryResults: () => void; } export const runQuestionOnNavigateSdk = @@ -28,6 +29,7 @@ export const runQuestionOnNavigateSdk = originalQuestion, cancelDeferred, onQuestionChange, + onClearQueryResults, } = params; // Do not reload questions with breakouts when clicking on a legend item @@ -40,6 +42,7 @@ export const runQuestionOnNavigateSdk = nextCard = await loadCard(nextCard.id, { dispatch, getState }); } else { nextCard = getCardAfterVisualizationClick(nextCard, previousCard); + onClearQueryResults(); } // Optimistic update the UI before we re-fetch the query metadata. -- GitLab