Skip to content
Snippets Groups Projects
notebook-data-source.cy.spec.ts 15.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • // 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 {
      ORDERS_COUNT_QUESTION_ID,
      ORDERS_MODEL_ID,
    
    import type { StructuredQuestionDetails } from "e2e/support/helpers";
    
      entityPickerModal,
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
      entityPickerModalItem,
      entityPickerModalLevel,
      entityPickerModalTab,
    
      openQuestionActions,
      openReviewsTable,
      popover,
    
    import { checkNotNull } from "metabase/lib/types";
    
    
    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",
            );
    
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
            entityPickerModal().within(() => {
              cy.log("Should not have Recents tab");
              cy.findAllByRole("tab").should("have.length", 0);
    
              entityPickerModalLevel(0).should("not.exist");
              entityPickerModalLevel(1).should("not.exist");
              entityPickerModalLevel(2)
                .get("[data-index]")
                .should("have.length", 8);
              assertDataPickerEntityNotSelected(2, "Accounts");
              assertDataPickerEntityNotSelected(2, "Analytic Events");
              assertDataPickerEntityNotSelected(2, "Feedback");
              assertDataPickerEntityNotSelected(2, "Invoices");
              assertDataPickerEntityNotSelected(2, "Orders");
              assertDataPickerEntityNotSelected(2, "People");
              assertDataPickerEntityNotSelected(2, "Products");
              assertDataPickerEntityNotSelected(2, "Reviews");
    
            });
          },
        );
    
        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",
          );
    
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
          entityPickerModal().within(() => {
            cy.log("Should not have Recents tab");
            cy.findAllByRole("tab").should("have.length", 0);
    
            entityPickerModalLevel(0).should("not.exist");
            entityPickerModalLevel(1).should("not.exist");
            entityPickerModalLevel(2).get("[data-index]").should("have.length", 8);
            assertDataPickerEntityNotSelected(2, "Accounts");
            assertDataPickerEntityNotSelected(2, "Analytic Events");
            assertDataPickerEntityNotSelected(2, "Feedback");
            assertDataPickerEntityNotSelected(2, "Invoices");
            assertDataPickerEntityNotSelected(2, "Orders");
            assertDataPickerEntityNotSelected(2, "People");
            assertDataPickerEntityNotSelected(2, "Products");
            assertDataPickerEntityNotSelected(2, "Reviews");
    
          });
        });
    
        it("should not show saved questions if only models exist (metabase#25142)", () => {
    
            name: "GUI Model",
            query: { "source-table": REVIEWS_ID, limit: 1 },
            display: "table",
            type: "model",
          });
    
          startNewQuestion();
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
          entityPickerModal().within(() => {
            cy.findAllByRole("tab").should("have.length", 2);
            entityPickerModalTab("Recents").should("not.exist");
            entityPickerModalTab("Models").and(
              "have.attr",
              "aria-selected",
              "true",
            );
            entityPickerModalTab("Tables").should("exist");
            entityPickerModalTab("Saved questions").should("not.exist");
    
          });
        });
    
        it("should not show models if only saved questions exist", () => {
    
            name: "GUI Question",
            query: { "source-table": REVIEWS_ID, limit: 1 },
            display: "table",
          });
    
          startNewQuestion();
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
          entityPickerModal().within(() => {
            cy.findAllByRole("tab").should("have.length", 2);
            entityPickerModalTab("Recents").should("not.exist");
            entityPickerModalTab("Models").should("not.exist");
            entityPickerModalTab("Tables").and(
              "have.attr",
              "aria-selected",
              "true",
            );
            entityPickerModalTab("Saved questions").should("exist");
    
          });
        });
      });
    
      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();
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
          entityPickerModal().within(() => {
            entityPickerModalTab("Recents").should("exist");
            entityPickerModalTab("Tables").and(
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
            // should not show databases step if there's only 1 database
            entityPickerModalLevel(0).should("not.exist");
            // should not show schema step if there's only 1 schema
            entityPickerModalLevel(1).should("not.exist");
            assertDataPickerEntitySelected(2, "Reviews");
    
          });
        });
    
        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();
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
          entityPickerModal().within(() => {
            entityPickerModalTab("Recents").should("exist");
            entityPickerModalTab("Tables").and(
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
            // should not show databases step if there's only 1 database
            entityPickerModalLevel(0).should("not.exist");
            // should not show schema step if there's only 1 schema
            entityPickerModalLevel(1).should("not.exist");
            assertDataPickerEntitySelected(2, "Orders");
    
          });
        });
    
        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();
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
            entityPickerModal().within(() => {
              entityPickerModalTab("Recents").should("not.exist");
              entityPickerModalTab("Tables").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();
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
            entityPickerModal().within(() => {
              assertDataPickerEntitySelected(0, dbName);
              assertDataPickerEntitySelected(1, schemaName);
              assertDataPickerEntitySelected(2, tableName);
    
              entityPickerModalTab("Recents").click();
              cy.findByTestId("result-item")
                .should("exist")
                .and("contain.text", tableName)
                .and("have.attr", "aria-selected", "true");
    
            });
          },
        );
    
        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();
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
          entityPickerModal().within(() => {
            entityPickerModalTab("Tables").should(
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
            // should not show databases step if there's only 1 database
            entityPickerModalLevel(0).should("not.exist");
            // should not show schema step if there's only 1 schema
            entityPickerModalLevel(1).should("not.exist");
            assertDataPickerEntitySelected(2, "Orders");
    
          });
        });
      });
    
      describe("saved entity as a source (aka the virtual table)", () => {
    
        const modelDetails: StructuredQuestionDetails = {
    
          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)", () => {
    
          createQuestion(modelDetails, { visitQuestion: true });
    
          openNotebook();
          cy.findByTestId("data-step-cell")
            .should("have.text", modelDetails.name)
            .click();
    
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
          entityPickerModal().within(() => {
            entityPickerModalTab("Models").should(
              "have.attr",
              "aria-selected",
              "true",
            );
            assertDataPickerEntitySelected(0, "Our analytics");
            assertDataPickerEntitySelected(1, "First collection");
            assertDataPickerEntitySelected(2, "Second collection");
            assertDataPickerEntitySelected(3, checkNotNull(modelDetails.name));
    
            entityPickerModalTab("Recents").click();
            cy.findByTestId("result-item")
              .should("exist")
              .and("contain.text", checkNotNull(modelDetails.name))
    
              .and("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();
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
          entityPickerModal().within(() => {
            entityPickerModalTab("Models").should(
              "have.attr",
              "aria-selected",
              "true",
            );
            assertDataPickerEntitySelected(0, "Our analytics");
            assertDataPickerEntitySelected(1, "Orders Model");
    
            entityPickerModalTab("Recents").click();
            cy.findByTestId("result-item")
              .should("exist")
              .and("contain.text", "Orders Model")
              .and("have.attr", "aria-selected", "true");
    
            cy.button("Close").click();
          });
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
          entityPickerModal().within(() => {
            entityPickerModalTab("Models").should(
              "have.attr",
              "aria-selected",
              "true",
            );
            assertDataPickerEntitySelected(0, "Our analytics");
            assertDataPickerEntitySelected(1, "First collection");
            assertDataPickerEntitySelected(2, "Orders Model");
    
            entityPickerModalTab("Recents").click();
            cy.findByTestId("result-item")
              .should("exist")
              .and("contain.text", "Orders Model")
              .and("have.attr", "aria-selected", "true");
          });
    
        });
    
        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}` },
          };
    
    
          createQuestion(nestedQuestionDetails, {
    
            wrapId: true,
            idAlias: "nestedQuestionId",
          });
    
          visitQuestion("@nestedQuestionId");
          openNotebook();
    
          openDataSelector();
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
          entityPickerModal().within(() => {
            entityPickerModalTab("Saved questions").should(
              "have.attr",
              "aria-selected",
              "true",
            );
            assertDataPickerEntitySelected(0, "Our analytics");
            assertDataPickerEntitySelected(1, sourceQuestionName);
    
            entityPickerModalTab("Recents").click();
            cy.findAllByTestId("result-item").should("have.length", 1);
            cy.findByTestId("result-item")
              .should("exist")
              .and("contain.text", "Nested Question")
              .and("not.have.attr", "aria-selected", "true");
    
            cy.button("Close").click();
          });
    
    
          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();
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
          entityPickerModal().within(() => {
            entityPickerModalTab("Saved questions").should(
              "have.attr",
              "aria-selected",
              "true",
            );
            assertDataPickerEntitySelected(0, "Our analytics");
            assertDataPickerEntitySelected(1, "First collection");
            assertDataPickerEntitySelected(2, sourceQuestionName);
    
            entityPickerModalTab("Recents").click();
            cy.findAllByTestId("result-item")
              .contains(nestedQuestionDetails.name)
              .parents("button")
              .and("not.have.attr", "aria-selected", "true");
            cy.findAllByTestId("result-item")
              .contains(sourceQuestionName)
              .parents("button")
              .and("have.attr", "aria-selected", "true");
          });
    
    describeOSS("scenarios > notebook > data source", { tags: "@OSS" }, () => {
    
      beforeEach(() => {
        restore("setup");
        cy.signInAsAdmin();
      });
    
      it("should not show saved questions if only models exist (metabase#25142)", () => {
        createQuestion({
          name: "GUI Model",
          query: { "source-table": REVIEWS_ID, limit: 1 },
          display: "table",
          type: "model",
        });
    
        startNewQuestion();
    
        entityPickerModal().within(() => {
          cy.findAllByRole("tab").should("have.length", 2);
          entityPickerModalTab("Recents").should("not.exist");
          entityPickerModalTab("Models").and("have.attr", "aria-selected", "true");
          entityPickerModalTab("Tables").should("exist");
          entityPickerModalTab("Saved questions").should("not.exist");
    
      cy.intercept("GET", "/api/collection/tree**").as("updateCollectionTree");
    
    
      openQuestionActions();
      popover().findByTextEnsureVisible("Move").click();
    
    
      entityPickerModal().within(() => {
        cy.findByText(collection).click();
    
        cy.button("Move").click();
        cy.wait("@updateCollectionTree");
      });
    }
    
    function openDataSelector() {
      cy.findByTestId("data-step-cell").click();
    }
    
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
    function assertDataPickerEntitySelected(level: number, name: string) {
      entityPickerModalItem(level, name).should("have.attr", "data-active", "true");
    
    Kamil Mielnik's avatar
    Kamil Mielnik committed
    function assertDataPickerEntityNotSelected(level: number, name: string) {
      entityPickerModalItem(level, name).should("not.have.attr", "data-active");