-
Nemanja Glumac authored
* Add the initial test for the empty app db * Add reproduction for #25142 * Add test that covers only saved questions * Cover scenarios with a table as a source * Add tests that cover nested questions and models * Add test for simple model's source table * Remove intercepted schema routes * Improve assertion * Fix the test failing on enterprise instance * Add E2E reproduction for #40223 * Remove superfluous code block * Avoid casting
Nemanja Glumac authored* Add the initial test for the empty app db * Add reproduction for #25142 * Add test that covers only saved questions * Cover scenarios with a table as a source * Add tests that cover nested questions and models * Add test for simple model's source table * Remove intercepted schema routes * Improve assertion * Fix the test failing on enterprise instance * Add E2E reproduction for #40223 * Remove superfluous code block * Avoid casting
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
notebook-data-source.cy.spec.ts 10.64 KiB
// TypeScript doesn't recognize `onlyOn` on the `cy` object.
// Hence, we have to import it as a standalone helper.
import { onlyOn } from "@cypress/skip-test";
import { WRITABLE_DB_ID } from "e2e/support/cypress_data";
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
import {
SECOND_COLLECTION_ID,
ORDERS_COUNT_QUESTION_ID,
ORDERS_MODEL_ID,
} from "e2e/support/cypress_sample_instance_data";
import {
popover,
restore,
startNewQuestion,
openReviewsTable,
openNotebook,
visitQuestion,
resetTestTable,
resyncDatabase,
visualize,
saveQuestion,
visitModel,
openQuestionActions,
isOSS,
isEE,
} from "e2e/support/helpers";
const { REVIEWS_ID } = SAMPLE_DATABASE;
describe("scenarios > notebook > data source", () => {
describe("empty app db", () => {
beforeEach(() => {
restore("setup");
cy.signInAsAdmin();
});
it(
"should display tables from the only existing database by default",
{ tags: "@OSS" },
() => {
onlyOn(isOSS);
cy.visit("/");
cy.findByTestId("app-bar").findByText("New").click();
popover().findByTextEnsureVisible("Question").click();
cy.findByTestId("data-step-cell").should(
"have.text",
"Pick your starting data",
);
popover().within(() => {
cy.findByTestId("source-database").should(
"have.text",
"Sample Database",
);
cy.findAllByRole("option")
.should("have.length", 8)
.each(table => {
cy.wrap(table).should("have.attr", "aria-selected", "false");
});
});
},
);
it.skip("should display tables from the only existing database by default on an enterprise instance without token activation (metabase#40223)", () => {
onlyOn(isEE);
cy.visit("/");
cy.findByTestId("app-bar").findByText("New").click();
popover().findByTextEnsureVisible("Question").click();
cy.findByTestId("data-step-cell").should(
"have.text",
"Pick your starting data",
);
popover().within(() => {
cy.findByTestId("source-database").should(
"have.text",
"Sample Database",
);
cy.findAllByRole("option")
.should("have.length", 8)
.each(table => {
cy.wrap(table).should("have.attr", "aria-selected", "false");
});
});
});
it("should not show saved questions if only models exist (metabase#25142)", () => {
cy.createQuestion({
name: "GUI Model",
query: { "source-table": REVIEWS_ID, limit: 1 },
display: "table",
type: "model",
});
startNewQuestion();
popover().within(() => {
cy.findByPlaceholderText("Search for some data…");
cy.findAllByTestId("data-bucket-list-item")
.as("sources")
.should("have.length", 2);
cy.get("@sources")
.first()
.should("contain", "Models")
.and("have.attr", "aria-selected", "false");
cy.get("@sources")
.last()
.should("contain", "Raw Data")
.and("have.attr", "aria-selected", "false");
});
});
// There is a huge discrepancy between how we render this popover vs the one for models
// That's the reason this test is a bit vague. Will be reported as a separate issue
// and covered in a separate reproduction.
it("should not show models if only saved questions exist", () => {
cy.createQuestion({
name: "GUI Question",
query: { "source-table": REVIEWS_ID, limit: 1 },
display: "table",
});
startNewQuestion();
popover().within(() => {
cy.get(".List-section-title")
.should("have.length", 2)
.and("contain", "Saved Questions")
.and("not.contain", "Models");
});
});
});
describe("table as a source", () => {
beforeEach(() => {
restore();
cy.signInAsAdmin();
});
it("should correctly display the source data for ad-hoc questions", () => {
openReviewsTable();
openNotebook();
cy.findByTestId("data-step-cell").should("have.text", "Reviews").click();
popover().within(() => {
cy.findByTestId("source-database").should(
"have.text",
"Sample Database",
);
cy.findByLabelText("Reviews").should(
"have.attr",
"aria-selected",
"true",
);
});
});
it("should correctly display the source data for a simple saved question", () => {
visitQuestion(ORDERS_COUNT_QUESTION_ID);
openNotebook();
cy.findByTestId("data-step-cell").should("have.text", "Orders").click();
popover().within(() => {
cy.findByTestId("source-database").should(
"have.text",
"Sample Database",
);
cy.findByLabelText("Orders").should(
"have.attr",
"aria-selected",
"true",
);
});
});
it(
"should correctly display a table from a multi-schema database (metabase#39807)",
{ tags: "@external" },
() => {
const dialect = "postgres";
const TEST_TABLE = "multi_schema";
const dbName = "Writable Postgres12";
const schemaName = "Wild";
const tableName = "Animals";
resetTestTable({ type: dialect, table: TEST_TABLE });
restore(`${dialect}-writable`);
cy.signInAsAdmin();
resyncDatabase({
dbId: WRITABLE_DB_ID,
});
startNewQuestion();
popover().within(() => {
cy.findByText("Raw Data").click();
cy.findByText(dbName).click();
cy.findByText(schemaName).click();
cy.findByText(tableName).click();
});
visualize();
saveQuestion("Beasts");
openNotebook();
cy.findByTestId("data-step-cell").should("contain", tableName).click();
popover().within(() => {
cy.findByTestId("source-database").should("have.text", dbName);
cy.findByTestId("source-schema").should("have.text", schemaName);
});
},
);
it("should correctly display a table as the model's source when editing simple model's query", () => {
cy.visit(`/model/${ORDERS_MODEL_ID}/query`);
cy.findByTestId("data-step-cell").should("have.text", "Orders").click();
popover().within(() => {
cy.findByTestId("source-database").should(
"have.text",
"Sample Database",
);
cy.findByLabelText("Orders").should(
"have.attr",
"aria-selected",
"true",
);
});
});
});
describe("saved entity as a source (aka the virtual table)", () => {
const modelDetails: Parameters<typeof cy.createQuestion>[0] = {
name: "GUI Model",
query: { "source-table": REVIEWS_ID, limit: 1 },
display: "table",
type: "model",
collection_id: SECOND_COLLECTION_ID,
};
beforeEach(() => {
restore();
cy.signInAsAdmin();
});
it("data selector should properly show a model as the source (metabase#39699)", () => {
cy.createQuestion(modelDetails, { visitQuestion: true });
openNotebook();
cy.findByTestId("data-step-cell")
.should("have.text", modelDetails.name)
.click();
cy.findByTestId("saved-entity-back-navigation").should(
"have.text",
"Models",
);
cy.findByTestId("saved-entity-collection-tree").within(() => {
cy.findByLabelText("Our analytics")
.should("have.attr", "aria-expanded", "false")
.and("have.attr", "aria-selected", "false");
cy.findByLabelText("First collection")
.should("have.attr", "aria-expanded", "true")
.and("have.attr", "aria-selected", "false");
cy.findByLabelText("Second collection")
.should("have.attr", "aria-expanded", "false")
.and("have.attr", "aria-selected", "true");
});
cy.findByTestId("select-list")
.findByLabelText(modelDetails.name)
.should("have.attr", "aria-selected", "true");
});
it("moving the model to another collection should immediately be reflected in the data selector (metabase#39812-1)", () => {
visitModel(ORDERS_MODEL_ID);
openNotebook();
openDataSelector();
assertSourceCollection("Our analytics");
assertDataSource("Orders Model");
moveToCollection("First collection");
openDataSelector();
assertSourceCollection("First collection");
assertDataSource("Orders Model");
});
it("moving the source question should immediately reflect in the data selector for the nested question that depends on it (metabase#39812-2)", () => {
const SOURCE_QUESTION_ID = ORDERS_COUNT_QUESTION_ID;
// Rename the source question to make assertions more explicit
const sourceQuestionName = "Source Question";
cy.request("PUT", `/api/card/${ORDERS_COUNT_QUESTION_ID}`, {
name: sourceQuestionName,
});
const nestedQuestionDetails = {
name: "Nested Question",
query: { "source-table": `card__${SOURCE_QUESTION_ID}` },
};
cy.createQuestion(nestedQuestionDetails, {
wrapId: true,
idAlias: "nestedQuestionId",
});
visitQuestion("@nestedQuestionId");
openNotebook();
openDataSelector();
assertSourceCollection("Our analytics");
assertDataSource(sourceQuestionName);
cy.log("Move the source question to another collection");
visitQuestion(SOURCE_QUESTION_ID);
openNotebook();
moveToCollection("First collection");
cy.log("Make sure the source change is reflected in a nested question");
visitQuestion("@nestedQuestionId");
openNotebook();
openDataSelector();
assertSourceCollection("First collection");
assertDataSource(sourceQuestionName);
});
});
});
function moveToCollection(collection: string) {
openQuestionActions();
popover().findByTextEnsureVisible("Move").click();
cy.findByRole("dialog").within(() => {
cy.intercept("GET", "/api/collection/tree**").as("updateCollectionTree");
cy.findAllByTestId("item-picker-item")
.filter(`:contains(${collection})`)
.click();
cy.button("Move").click();
cy.wait("@updateCollectionTree");
});
}
function openDataSelector() {
cy.findByTestId("data-step-cell").click();
}
function assertItemSelected(item: string) {
cy.findByLabelText(item).should("have.attr", "aria-selected", "true");
}
function assertSourceCollection(collection: string) {
return assertItemSelected(collection);
}
function assertDataSource(questionOrModel: string) {
return assertItemSelected(questionOrModel);
}