Skip to content

Commit

Permalink
SDK launch in core app (metabase#48107)
Browse files Browse the repository at this point in the history
Co-authored-by: Oisin Coveney <oisin@metabase.com>
Co-authored-by: Mahatthana (Kelvin) Nomsawadi <me@bboykelvin.dev>
Co-authored-by: bryan <bryan.maass@gmail.com>
Co-authored-by: Nicolò Pretto <info@npretto.com>
  • Loading branch information
4 people authored Oct 8, 2024
1 parent f03e6d8 commit 537ba41
Show file tree
Hide file tree
Showing 131 changed files with 4,919 additions and 1,518 deletions.
5 changes: 4 additions & 1 deletion e2e/snapshot-creators/default.cy.snap.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ describe("snapshots", () => {

function updateSettings() {
updateSetting("enable-public-sharing", true);
updateSetting("enable-embedding", true).then(() => {
// interactive is not enabled in the snapshots as it requires a premium feature
// updateSetting("enable-embedding-interactive", true);
updateSetting("enable-embedding-sdk", true);
updateSetting("enable-embedding-static", true).then(() => {
updateSetting("embedding-secret-key", METABASE_SECRET_KEY);
});

Expand Down
Empty file.
12 changes: 12 additions & 0 deletions e2e/support/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
verifyDownloadTasks,
} from "./commands/downloads/downloadUtils";
import * as dbTasks from "./db_tasks";
import { signJwt } from "./helpers/e2e-jwt-tasks";

const createBundler = require("@bahmutov/cypress-esbuild-preprocessor"); // This function is called when a project is opened or re-opened (e.g. due to the project's config changing)
const {
Expand All @@ -27,6 +28,8 @@ const targetVersion = process.env["CROSS_VERSION_TARGET"];

const feHealthcheckEnabled = process.env["CYPRESS_FE_HEALTHCHECK"] === "true";

const isEmbeddingSdk = process.env.CYPRESS_IS_EMBEDDING_SDK === "true";

// docs say that tsconfig paths should handle aliases, but they don't
const assetsResolverPlugin = {
name: "assetsResolver",
Expand Down Expand Up @@ -104,6 +107,7 @@ const defaultConfig = {
...dbTasks,
...verifyDownloadTasks,
removeDirectory,
signJwt,
});

// this is an official workaround to keep recordings of the failed specs only
Expand Down Expand Up @@ -169,6 +173,14 @@ const defaultConfig = {

const mainConfig = {
...defaultConfig,
...(isEmbeddingSdk
? {
chromeWebSecurity: true,
hosts: {
"my-site.local": "127.0.0.1",
},
}
: {}),
projectId: "ywjy9z",
numTestsKeptInMemory: process.env["CI"] ? 1 : 50,
reporter: "cypress-multi-reporters",
Expand Down
2 changes: 1 addition & 1 deletion e2e/support/helpers/e2e-embedding-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export function openStaticEmbeddingModal({
cy.findByRole("button", { name: "Save" }).click();
}

cy.findByTestId("sharing-pane-static-embed-button").click();
cy.findByText("Static embedding").click();

if (acceptTerms) {
cy.findByTestId("accept-legalese-terms-button").click();
Expand Down
11 changes: 11 additions & 0 deletions e2e/support/helpers/e2e-jwt-tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import jwt from "jsonwebtoken";

export function signJwt({
payload,
secret,
}: {
payload: Record<string, string | number>;
secret: string;
}): string {
return jwt.sign(payload, secret);
}
199 changes: 199 additions & 0 deletions e2e/test/scenarios/embedding-sdk/static-dashboard-cors.cy.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import { USERS } from "e2e/support/cypress_data";
import {
ORDERS_DASHBOARD_DASHCARD_ID,
ORDERS_QUESTION_ID,
} from "e2e/support/cypress_sample_instance_data";
import {
getTextCardDetails,
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 STORYBOOK_ID = "embeddingsdk-cypressstaticdashboardwithcors--default";
describeSDK("scenarios > embedding-sdk > static-dashboard", () => {
beforeEach(() => {
restore();
cy.signInAsAdmin();
setTokenFeatures("all");
setupJwt();

const textCard = getTextCardDetails({ col: 16, text: "Text text card" });
const questionCard = {
id: ORDERS_DASHBOARD_DASHCARD_ID,
card_id: ORDERS_QUESTION_ID,
row: 0,
col: 0,
size_x: 16,
size_y: 8,
visualization_settings: {
"card.title": "Test question card",
},
};

cy.createDashboard(
{
name: "Embedding Sdk Test Dashboard",
dashcards: [questionCard, textCard],
},
{ wrapId: true },
);

cy.intercept("GET", "/api/dashboard/*").as("getDashboard");
cy.intercept("GET", "/api/user/current").as("getUser");
cy.intercept("POST", "/api/dashboard/*/dashcard/*/card/*/query").as(
"dashcardQuery",
);

cy.task("signJwt", {
payload: {
email: USERS.normal.email,
exp: Math.round(Date.now() / 1000) + 10 * 60, // 10 minute expiration
},
secret: JWT_SHARED_SECRET,
}).then(jwtToken => {
const ssoUrl = new URL("/auth/sso", Cypress.config().baseUrl);
ssoUrl.searchParams.set("jwt", jwtToken);
ssoUrl.searchParams.set("token", "true");
cy.request(ssoUrl.toString()).then(({ body }) => {
cy.wrap(body).as("metabaseSsoResponse");
});
});
cy.get("@metabaseSsoResponse").then(ssoResponse => {
cy.intercept("GET", "/sso/metabase", ssoResponse);
});
});

it("should not render dashboard when embedding SDK is not enabled", () => {
cy.request("PUT", "/api/setting", {
"enable-embedding-sdk": false,
});
cy.signOut();

cy.get("@dashboardId").then(dashboardId => {
visitFullAppEmbeddingUrl({
url: EMBEDDING_SDK_STORY_HOST,
qs: { id: STORYBOOK_ID, viewMode: "story" },
onBeforeLoad: window => {
window.METABASE_INSTANCE_URL = Cypress.config().baseUrl;
window.DASHBOARD_ID = dashboardId;
},
});
});

cy.get("#metabase-sdk-root").within(() => {
cy.findByText("Error").should("be.visible");
cy.findByText(
"Could not authenticate: invalid JWT URI or JWT provider did not return a valid JWT token",
).should("be.visible");
});
});

it("should show dashboard content", () => {
cy.request("PUT", "/api/setting", {
"enable-embedding-sdk": true,
});
cy.signOut();
cy.get("@dashboardId").then(dashboardId => {
visitFullAppEmbeddingUrl({
url: EMBEDDING_SDK_STORY_HOST,
qs: { id: STORYBOOK_ID, viewMode: "story" },
onBeforeLoad: window => {
window.METABASE_INSTANCE_URL = Cypress.config().baseUrl;
window.DASHBOARD_ID = dashboardId;
},
});
});

cy.wait("@getUser").then(({ response }) => {
expect(response?.statusCode).to.equal(200);
});

cy.wait("@getDashboard").then(({ response }) => {
expect(response?.statusCode).to.equal(200);
});

cy.get("#metabase-sdk-root")
.should("be.visible")
.within(() => {
cy.findByText("Embedding Sdk Test Dashboard").should("be.visible"); // dashboard title

cy.findByText("Text text card").should("be.visible"); // text card content

cy.wait("@dashcardQuery");
cy.findByText("Test question card").should("be.visible"); // question card content
});
});

it("should not render the SDK on non localhost sites when embedding SDK origins is not set", () => {
cy.request("PUT", "/api/setting", {
"enable-embedding-sdk": true,
});
cy.signOut();
cy.get("@dashboardId").then(dashboardId => {
visitFullAppEmbeddingUrl({
url: "http://my-site.local:6006/iframe.html",
qs: {
id: STORYBOOK_ID,
viewMode: "story",
},
onBeforeLoad: window => {
window.METABASE_INSTANCE_URL = Cypress.config().baseUrl;
window.DASHBOARD_ID = dashboardId;
},
});
});

cy.get("#metabase-sdk-root").within(() => {
cy.findByText("Error").should("be.visible");
cy.findByText(
"Could not authenticate: invalid JWT URI or JWT provider did not return a valid JWT token",
).should("be.visible");
});
});

it("should show dashboard content", () => {
cy.request("PUT", "/api/setting", {
"enable-embedding-sdk": true,
"embedding-app-origins-sdk": "my-site.local:6006",
});
cy.signOut();
cy.get("@dashboardId").then(dashboardId => {
visitFullAppEmbeddingUrl({
url: "http://my-site.local:6006/iframe.html",
qs: { id: STORYBOOK_ID, viewMode: "story" },
onBeforeLoad: window => {
window.METABASE_INSTANCE_URL = Cypress.config().baseUrl;
window.DASHBOARD_ID = dashboardId;
},
});
});

cy.wait("@getUser").then(({ response }) => {
expect(response?.statusCode).to.equal(200);
});

cy.wait("@getDashboard").then(({ response }) => {
expect(response?.statusCode).to.equal(200);
});

cy.get("#metabase-sdk-root")
.should("be.visible")
.within(() => {
cy.findByText("Embedding Sdk Test Dashboard").should("be.visible"); // dashboard title

cy.findByText("Text text card").should("be.visible"); // text card content

cy.wait("@dashcardQuery");
cy.findByText("Test question card").should("be.visible"); // question card content
});
});
});
3 changes: 3 additions & 0 deletions e2e/test/scenarios/embedding-sdk/static-dashboard.cy.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ describeSDK("scenarios > embedding-sdk > static-dashboard", () => {
cy.signInAsAdmin();
setTokenFeatures("all");
setupJwt();
cy.request("PUT", "/api/setting", {
"enable-embedding-sdk": true,
});

const textCard = getTextCardDetails({ col: 16, text: "Text text card" });
const questionCard = {
Expand Down
38 changes: 21 additions & 17 deletions e2e/test/scenarios/embedding/embedding-smoketests.cy.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,25 +59,14 @@ describe("scenarios > embedding > smoke tests", { tags: "@OSS" }, () => {
});

cy.location("pathname").should("eq", embeddingPage);
cy.findByTestId("enable-embedding-setting").within(() => {
cy.findByText(embeddingDescription);

cy.findByLabelText("Enable Embedding").click({ force: true });
});
// The URL should stay the same
cy.location("pathname").should("eq", embeddingPage);

cy.findByTestId("enable-embedding-setting").within(() => {
cy.findByRole("checkbox").should("be.checked");
});

mainPage().findByText(embeddingDescription).should("be.visible");
cy.log(
"With the embedding enabled, we should now see two new sections on the main page",
);
cy.log("The first section: 'Static embedding'");
cy.findByTestId("-static-embedding-setting").within(() => {
cy.findByRole("article", { name: "Static embedding" }).within(() => {
// FE unit tests are making sure this section doesn't exist when a valid token is provided,
// so we don't have to do it here usign a conditional logic
// so we don't have to do it here using conditional logic
assertLinkMatchesUrl("upgrade to a paid plan", upgradeUrl);

cy.findByRole("link", { name: "Manage" })
Expand All @@ -89,7 +78,15 @@ describe("scenarios > embedding > smoke tests", { tags: "@OSS" }, () => {
});

cy.log("Standalone embeds page");
// TODO: Remove this when the actual BE is implemented, this flag still controls the static embedding
// I've tried to change this but it failed like 500 BE tests.
cy.request("PUT", "/api/setting/enable-embedding-static", {
value: true,
});
mainPage().within(() => {
cy.findByLabelText("Enable Static embedding")
.click({ force: true })
.should("be.checked");
cy.findByTestId("embedding-secret-key-setting").within(() => {
cy.findByText("Embedding secret key");
cy.findByText(
Expand All @@ -99,7 +96,7 @@ describe("scenarios > embedding > smoke tests", { tags: "@OSS" }, () => {
cy.button("Regenerate key");
});

cy.findByTestId("-embedded-resources-setting").within(() => {
cy.findByTestId("embedded-resources").within(() => {
cy.findByText("Embedded Dashboards");
cy.findByText("No dashboards have been embedded yet.");

Expand All @@ -112,7 +109,7 @@ describe("scenarios > embedding > smoke tests", { tags: "@OSS" }, () => {
cy.location("pathname").should("eq", embeddingPage);

cy.log("The second section: 'Interactive embedding'");
cy.findByTestId("-interactive-embedding-setting").within(() => {
cy.findByRole("article", { name: "Interactive embedding" }).within(() => {
cy.findByText("Interactive embedding");

cy.findByRole("link", { name: "Learn More" })
Expand Down Expand Up @@ -142,6 +139,9 @@ describe("scenarios > embedding > smoke tests", { tags: "@OSS" }, () => {
};
["question", "dashboard"].forEach(object => {
it(`should be able to publish/embed and then unpublish a ${object} without filters`, () => {
cy.request("PUT", "/api/setting/enable-embedding-static", {
value: true,
});
const embeddableObject = object === "question" ? "card" : "dashboard";
const objectName =
object === "question" ? "Orders" : "Orders in a dashboard";
Expand Down Expand Up @@ -248,6 +248,10 @@ describe("scenarios > embedding > smoke tests", { tags: "@OSS" }, () => {
});

it("should regenerate embedding token and invalidate previous embed url", () => {
cy.request("PUT", "/api/setting/enable-embedding-static", {
value: true,
});

cy.request("PUT", `/api/card/${ORDERS_QUESTION_ID}`, {
enable_embedding: true,
});
Expand Down Expand Up @@ -311,7 +315,7 @@ describe("scenarios > embedding > smoke tests", { tags: "@OSS" }, () => {
});

function resetEmbedding() {
updateSetting("enable-embedding", false);
updateSetting("enable-embedding-static", false);
updateSetting("embedding-secret-key", null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ describe("issue 35954", () => {

visitDashboard(id);
openSharingMenu("Embed");
modal().findByText("Static embed").click();
modal().findByText("Static embedding").click();

cy.findByTestId("embedding-preview").within(() => {
cy.intercept("GET", "api/preview_embed/dashboard/**").as(
Expand Down
Loading

0 comments on commit 537ba41

Please sign in to comment.