From b8e9d9db095492a9cd0e396863d42128e4821b25 Mon Sep 17 00:00:00 2001 From: Jessica DeWitt <58329466+Opalevanescence@users.noreply.github.com> Date: Wed, 29 Jul 2020 23:27:42 -0500 Subject: [PATCH] E2e to cy/port/metrics (#13028) * Repro/universal search (#12957) * repro complete * Added issue # * prettier * Deleted change of table permissions * done porting metrics, except two qs * deleted 'metrics.e2e.spec.js' * edit to comment * loading problem * finished metrics --- .../metabase/reference/databases.e2e.spec.js | 249 ----------------- .../metabase/reference/metrics.e2e.spec.js | 143 ---------- .../admin/datamodel/metrics.cy.spec.js | 254 +++++++++++------- 3 files changed, 158 insertions(+), 488 deletions(-) delete mode 100644 frontend/test/metabase/reference/databases.e2e.spec.js delete mode 100644 frontend/test/metabase/reference/metrics.e2e.spec.js diff --git a/frontend/test/metabase/reference/databases.e2e.spec.js b/frontend/test/metabase/reference/databases.e2e.spec.js deleted file mode 100644 index 4a232f99466..00000000000 --- a/frontend/test/metabase/reference/databases.e2e.spec.js +++ /dev/null @@ -1,249 +0,0 @@ -import { useSharedAdminLogin, createTestStore } from "__support__/e2e"; -import { click, clickButton, setInputValue } from "__support__/enzyme"; - -import React from "react"; -import { mount } from "enzyme"; - -import { CardApi, MetabaseApi } from "metabase/services"; - -import { - FETCH_DATABASE_METADATA, - FETCH_REAL_DATABASES, -} from "metabase/redux/metadata"; - -import { END_LOADING } from "metabase/reference/reference"; - -import DatabaseListContainer from "metabase/reference/databases/DatabaseListContainer"; -import DatabaseDetailContainer from "metabase/reference/databases/DatabaseDetailContainer"; -import TableListContainer from "metabase/reference/databases/TableListContainer"; -import TableDetailContainer from "metabase/reference/databases/TableDetailContainer"; -import TableQuestionsContainer from "metabase/reference/databases/TableQuestionsContainer"; -import FieldListContainer from "metabase/reference/databases/FieldListContainer"; -import FieldDetailContainer from "metabase/reference/databases/FieldDetailContainer"; - -import DatabaseList from "metabase/reference/databases/DatabaseList"; -import List from "metabase/components/List"; -import ListItem from "metabase/components/ListItem"; -import ReferenceHeader from "metabase/reference/components/ReferenceHeader"; -import AdminAwareEmptyState from "metabase/components/AdminAwareEmptyState"; -import UsefulQuestions from "metabase/reference/components/UsefulQuestions"; -import Detail from "metabase/reference/components/Detail"; -import QueryButton from "metabase/components/QueryButton"; -import { INITIALIZE_QB, QUERY_COMPLETED } from "metabase/query_builder/actions"; -import { getQuestion } from "metabase/query_builder/selectors"; -import { delay } from "metabase/lib/promise"; - -describe("The Reference Section", () => { - // Test data - const cardDef = { - name: "A card", - display: "scalar", - dataset_query: { - database: 1, - type: "query", - query: { "source-table": 1, aggregation: [["count"]] }, - }, - visualization_settings: {}, - }; - - // Scaffolding - beforeAll(async () => { - useSharedAdminLogin(); - }); - - describe("The Data Reference for the Sample Database", async () => { - // database list - it("should see databases", async () => { - const store = await createTestStore(); - store.pushPath("/reference/databases/"); - const container = mount( - store.connectContainer(<DatabaseListContainer />), - ); - await store.waitForActions([FETCH_REAL_DATABASES, END_LOADING]); - - expect(container.find(ReferenceHeader).length).toBe(1); - expect(container.find(DatabaseList).length).toBe(1); - expect(container.find(AdminAwareEmptyState).length).toBe(0); - - expect(container.find(List).length).toBe(1); - expect(container.find(ListItem).length).toBeGreaterThanOrEqual(1); - }); - - // database list - it("should not see saved questions in the database list", async () => { - const card = await CardApi.create(cardDef); - const store = await createTestStore(); - store.pushPath("/reference/databases/"); - const container = mount( - store.connectContainer(<DatabaseListContainer />), - ); - await store.waitForActions([FETCH_REAL_DATABASES, END_LOADING]); - - expect(container.find(ReferenceHeader).length).toBe(1); - expect(container.find(DatabaseList).length).toBe(1); - expect(container.find(AdminAwareEmptyState).length).toBe(0); - - expect(container.find(List).length).toBe(1); - expect(container.find(ListItem).length).toBe(1); - - expect(card.name).toBe(cardDef.name); - - await CardApi.delete({ cardId: card.id }); - }); - - // database detail - it("should see a the detail view for the sample database", async () => { - const store = await createTestStore(); - store.pushPath("/reference/databases/1"); - mount(store.connectContainer(<DatabaseDetailContainer />)); - await store.waitForActions([FETCH_DATABASE_METADATA, END_LOADING]); - }); - - // database update - it("should update the sample database", async () => { - // create a new db by cloning #1 - const d1 = await MetabaseApi.db_get({ dbId: 1 }); - const { id } = await MetabaseApi.db_create(d1); - - // go to that db's reference page - const store = await createTestStore(); - store.pushPath(`/reference/databases/${id}`); - const app = mount(store.connectContainer(<DatabaseDetailContainer />)); - await store.waitForActions([FETCH_DATABASE_METADATA, END_LOADING]); - - // switch to edit view - const editButton = app.find(".Button"); - - clickButton(editButton); - - // update "caveats" and save - const textarea = app - .find(Detail) - .at(2) - .find("textarea"); - setInputValue(textarea, "v important thing"); - - const doneButton = app.find(".Button--primary"); - - clickButton(doneButton); - await store.waitForActions(END_LOADING); - // unfortunately this is required? - await delay(200); - - // check that the field was updated - const savedText = app - .find(Detail) - .at(2) - .find("span") - .at(1) - .text(); - - expect(savedText).toBe("v important thing"); - - // clean up - await MetabaseApi.db_delete({ dbId: id }); - }); - - // table list - it("should see the 4 tables in the sample database", async () => { - const store = await createTestStore(); - store.pushPath("/reference/databases/1/tables"); - mount(store.connectContainer(<TableListContainer />)); - await store.waitForActions([FETCH_DATABASE_METADATA, END_LOADING]); - }); - // table detail - - it("should see the Orders table", async () => { - const store = await createTestStore(); - store.pushPath("/reference/databases/1/tables/1"); - mount(store.connectContainer(<TableDetailContainer />)); - await store.waitForActions([FETCH_DATABASE_METADATA, END_LOADING]); - }); - - it("should see the Reviews table", async () => { - const store = await createTestStore(); - store.pushPath("/reference/databases/1/tables/2"); - mount(store.connectContainer(<TableDetailContainer />)); - await store.waitForActions([FETCH_DATABASE_METADATA, END_LOADING]); - }); - it("should see the Products table", async () => { - const store = await createTestStore(); - store.pushPath("/reference/databases/1/tables/3"); - mount(store.connectContainer(<TableDetailContainer />)); - await store.waitForActions([FETCH_DATABASE_METADATA, END_LOADING]); - }); - it("should see the People table", async () => { - const store = await createTestStore(); - store.pushPath("/reference/databases/1/tables/4"); - mount(store.connectContainer(<TableDetailContainer />)); - await store.waitForActions([FETCH_DATABASE_METADATA, END_LOADING]); - }); - // field list - it("should see the fields for the orders table", async () => { - const store = await createTestStore(); - store.pushPath("/reference/databases/1/tables/1/fields"); - mount(store.connectContainer(<FieldListContainer />)); - await store.waitForActions([FETCH_DATABASE_METADATA, END_LOADING]); - }); - it("should see the questions for the orders tables", async () => { - const store = await createTestStore(); - store.pushPath("/reference/databases/1/tables/1/questions"); - mount(store.connectContainer(<TableQuestionsContainer />)); - await store.waitForActions([FETCH_DATABASE_METADATA, END_LOADING]); - - const card = await CardApi.create(cardDef); - - expect(card.name).toBe(cardDef.name); - - await CardApi.delete({ cardId: card.id }); - }); - - // field detail - - it("should see the orders created_at timestamp field", async () => { - const store = await createTestStore(); - store.pushPath("/reference/databases/1/tables/1/fields/1"); - mount(store.connectContainer(<FieldDetailContainer />)); - await store.waitForActions([FETCH_DATABASE_METADATA, END_LOADING]); - }); - - it("should let you open a potentially useful question for created_at field without errors", async () => { - const store = await createTestStore(); - store.pushPath("/reference/databases/1/tables/1/fields/1"); - - const app = mount(store.getAppContainer()); - - await store.waitForActions([FETCH_DATABASE_METADATA, END_LOADING]); - const fieldDetails = app.find(FieldDetailContainer); - expect(fieldDetails.length).toBe(1); - - const usefulQuestionLink = fieldDetails - .find(UsefulQuestions) - .find(QueryButton) - .first() - .find("a"); - expect(usefulQuestionLink.text()).toBe( - "Number of Orders grouped by Created At", - ); - click(usefulQuestionLink); - - await store.waitForActions([INITIALIZE_QB, QUERY_COMPLETED]); - - const qbQuery = getQuestion(store.getState()).query(); - - // the granularity/subdimension should be applied correctly to the breakout - expect(JSON.stringify(qbQuery.breakouts())).toEqual( - JSON.stringify([ - ["datetime-field", ["field-id", 1], "month"], // depends on the date range - ]), - ); - }); - - it("should see the orders id field", async () => { - const store = await createTestStore(); - store.pushPath("/reference/databases/1/tables/1/fields/25"); - mount(store.connectContainer(<FieldDetailContainer />)); - await store.waitForActions([FETCH_DATABASE_METADATA, END_LOADING]); - }); - }); -}); diff --git a/frontend/test/metabase/reference/metrics.e2e.spec.js b/frontend/test/metabase/reference/metrics.e2e.spec.js deleted file mode 100644 index a6b8a89865a..00000000000 --- a/frontend/test/metabase/reference/metrics.e2e.spec.js +++ /dev/null @@ -1,143 +0,0 @@ -import { useSharedAdminLogin, createTestStore } from "__support__/e2e"; - -import React from "react"; -import { mount } from "enzyme"; -import { assocIn } from "icepick"; - -import { CardApi, MetricApi } from "metabase/services"; - -import { - FETCH_METRICS, - FETCH_METRIC_TABLE, - FETCH_METRIC_REVISIONS, -} from "metabase/redux/metadata"; - -import { FETCH_GUIDE } from "metabase/reference/reference"; - -import MetricListContainer from "metabase/reference/metrics/MetricListContainer"; -import MetricDetailContainer from "metabase/reference/metrics/MetricDetailContainer"; -import MetricQuestionsContainer from "metabase/reference/metrics/MetricQuestionsContainer"; -import MetricRevisionsContainer from "metabase/reference/metrics/MetricRevisionsContainer"; - -// NOTE: database/table_id/source-table are hard-coded, this might be a problem at some point - -describe("The Reference Section", () => { - // Test data - const metricDef = { - name: "A Metric", - description: "I did it!", - table_id: 1, - show_in_getting_started: true, - definition: { aggregation: [["count"]] }, - }; - - const anotherMetricDef = { - name: "Another Metric", - description: "I did it again!", - table_id: 1, - show_in_getting_started: true, - definition: { aggregation: [["count"]] }, - }; - - const metricCardDef = { - name: "A card", - display: "scalar", - dataset_query: { - database: 1, - type: "query", - query: { "source-table": 1, aggregation: [["metric", 1]] }, - }, - visualization_settings: {}, - }; - - // Scaffolding - beforeAll(async () => { - useSharedAdminLogin(); - }); - - describe("The Metrics section of the Data Reference", async () => { - describe("Empty State", async () => { - it("Should show no metrics in the list", async () => { - const store = await createTestStore(); - store.pushPath("/reference/metrics"); - mount(store.connectContainer(<MetricListContainer />)); - await store.waitForActions([FETCH_METRICS]); - }); - }); - - describe("With Metrics State", async () => { - const metricIds = []; - - beforeAll(async () => { - // Create some metrics to have something to look at - const metric = await MetricApi.create(metricDef); - const metric2 = await MetricApi.create(anotherMetricDef); - - metricIds.push(metric.id); - metricIds.push(metric2.id); - }); - - afterAll(async () => { - // Delete the guide we created - // remove the metrics we created - // This is a bit messy as technically these are just archived - for (const id of metricIds) { - await MetricApi.delete({ metricId: id, revision_message: "Please" }); - } - }); - // metrics list - it("Should show no metrics in the list", async () => { - const store = await createTestStore(); - store.pushPath("/reference/metrics"); - mount(store.connectContainer(<MetricListContainer />)); - await store.waitForActions([FETCH_METRICS]); - }); - // metric detail - it("Should show the metric detail view for a specific id", async () => { - const store = await createTestStore(); - store.pushPath("/reference/metrics/" + metricIds[0]); - mount(store.connectContainer(<MetricDetailContainer />)); - await store.waitForActions([FETCH_METRIC_TABLE, FETCH_GUIDE]); - }); - // metrics questions - it("Should show no questions based on a new metric", async () => { - const store = await createTestStore(); - store.pushPath("/reference/metrics/" + metricIds[0] + "/questions"); - mount(store.connectContainer(<MetricQuestionsContainer />)); - await store.waitForActions([FETCH_METRICS, FETCH_METRIC_TABLE]); - }); - // metrics revisions - it("Should show revisions", async () => { - const store = await createTestStore(); - store.pushPath("/reference/metrics/" + metricIds[0] + "/revisions"); - mount(store.connectContainer(<MetricRevisionsContainer />)); - await store.waitForActions([FETCH_METRICS, FETCH_METRIC_REVISIONS]); - }); - - it("Should see a newly asked question in its questions list", async () => { - let card; - try { - const cardDef = assocIn( - metricCardDef, - ["dataset_query", "query", "aggregation", 0, 1], - metricIds[0], - ); - card = await CardApi.create(cardDef); - expect(card.name).toBe(metricCardDef.name); - - // see that there is a new question on the metric's questions page - const store = await createTestStore(); - - store.pushPath("/reference/metrics/" + metricIds[0] + "/questions"); - mount(store.connectContainer(<MetricQuestionsContainer />)); - await store.waitForActions([FETCH_METRICS, FETCH_METRIC_TABLE]); - } finally { - if (card) { - // even if the code above results in an exception, try to delete the question - await CardApi.delete({ cardId: card.id }); - } - } - }); - }); - }); -}); diff --git a/frontend/test/metabase/scenarios/admin/datamodel/metrics.cy.spec.js b/frontend/test/metabase/scenarios/admin/datamodel/metrics.cy.spec.js index 47fc9772a18..69ef7020c0d 100644 --- a/frontend/test/metabase/scenarios/admin/datamodel/metrics.cy.spec.js +++ b/frontend/test/metabase/scenarios/admin/datamodel/metrics.cy.spec.js @@ -7,103 +7,165 @@ describe("scenarios > admin > datamodel > metrics", () => { cy.viewport(1400, 860); }); - it("should create a metric", () => { - cy.visit("/admin"); - cy.contains("Data Model").click(); - cy.contains("Metrics").click(); - cy.contains("New metric").click(); - cy.contains("Select a table").click(); - popover() - .contains("Orders") - .click({ force: true }); // this shouldn't be needed, but there were issues with reordering as loads happeend - - cy.url().should("match", /metric\/create$/); - cy.contains("Create Your Metric"); - - // filter to orders with total under 100 - cy.contains("Add filters").click(); - cy.contains("Total").click(); - cy.contains("Equal to").click(); - cy.contains("Less than").click(); - cy.get('[placeholder="Enter a number"]').type("100"); - popover() - .contains("Add filter") - .click(); - - // - cy.contains("Result: 12765"); - - // fill in name/description - cy.get('[name="name"]').type("orders <100"); - cy.get('[name="description"]').type( - "Count of orders with a total under $100.", - ); - - // saving bounces you back and you see new metric in the list - cy.contains("Save changes").click(); - cy.url().should("match", /datamodel\/metrics$/); - cy.contains("orders <100"); - cy.contains("Count, Filtered by Total"); + describe("with no metrics", () => { + it("should show no metrics in the list", () => { + cy.visit("/admin/datamodel/metrics"); + cy.findByText( + "Create metrics to add them to the Summarize dropdown in the query builder", + ); + }); + + it("should show how to create metrics", () => { + cy.visit("/reference/metrics"); + cy.findByText( + "Metrics are the official numbers that your team cares about", + ); + cy.findByText("Learn how to create metrics"); + }); }); - it("should update that metric", () => { - cy.visit("/admin"); - cy.contains("Data Model").click(); - cy.contains("Metrics").click(); - - cy.contains("orders <100") - .parent() - .parent() - .find(".Icon-ellipsis") - .click(); - cy.contains("Edit Metric").click(); - - // update the filter from "< 100" to "> 10" - cy.url().should("match", /metric\/1$/); - cy.contains("Edit Your Metric"); - cy.contains(/Total\s+is less than/).click(); - popover() - .contains("Less than") - .click(); - popover() - .contains("Greater than") - .click(); - popover() - .find("input") - .type("{SelectAll}10"); - popover() - .contains("Update filter") - .click(); - - // confirm that the preview updated - cy.contains("Result: 18703"); - - // update name and description, set a revision note, and save the update - cy.get('[name="name"]') - .clear() - .type("orders >10"); - cy.get('[name="description"]') - .clear() - .type("Count of orders with a total over $10."); - cy.get('[name="revision_message"]').type("time for a change"); - cy.contains("Save changes").click(); - - // get redirected to previous page and see the new metric name - cy.url().should("match", /datamodel\/metrics$/); - cy.contains("orders >10"); - - // clean up - cy.contains("orders >10") - .parent() - .parent() - .find(".Icon-ellipsis") - .click(); - cy.contains("Retire Metric").click(); - modal() - .find("textarea") - .type("delete it"); - modal() - .contains("button", "Retire") - .click(); + describe("with metrics", () => { + before(() => { + // CREATE METRIC + signInAsAdmin(); + cy.visit("/admin"); + cy.contains("Data Model").click(); + cy.contains("Metrics").click(); + cy.contains("New metric").click(); + cy.contains("Select a table").click(); + popover() + .contains("Orders") + .click({ force: true }); // this shouldn't be needed, but there were issues with reordering as loads happeend + + cy.url().should("match", /metric\/create$/); + cy.contains("Create Your Metric"); + + // filter to orders with total under 100 + cy.contains("Add filters").click(); + cy.contains("Total").click(); + cy.contains("Equal to").click(); + cy.contains("Less than").click(); + cy.get('[placeholder="Enter a number"]').type("100"); + popover() + .contains("Add filter") + .click(); + + // + cy.contains("Result: 12765"); + + // fill in name/description + cy.get('[name="name"]').type("orders <100"); + cy.get('[name="description"]').type( + "Count of orders with a total under $100.", + ); + + // saving bounces you back and you see new metric in the list + cy.contains("Save changes").click(); + cy.url().should("match", /datamodel\/metrics$/); + cy.contains("orders <100"); + cy.contains("Count, Filtered by Total"); + }); + + it("should show no questions based on a new metric", () => { + cy.visit("/reference/metrics/1/questions"); + cy.findAllByText("Questions about orders <100"); + cy.findByText("Loading..."); + cy.findByText("Loading...").should("not.exist"); + cy.findByText( + "Questions about this metric will appear here as they're added", + ); + }); + + it("should see a newly asked question in its questions list", () => { + // Ask a new qustion + cy.visit("/reference/metrics/1/questions"); + cy.get(".full") + .find(".Button") + .click(); + cy.findByText("Filter").click(); + cy.findByText("Total").click(); + cy.findByText("Equal to").click(); + cy.findByText("Greater than").click(); + cy.findByPlaceholderText("Enter a number").type("50"); + cy.findByText("Add filter").click(); + cy.findByText("Save").click(); + cy.findAllByText("Save") + .last() + .click(); + cy.findByText("Not now").click(); + + // Check the list + cy.visit("/reference/metrics/1/questions"); + cy.findByText("Our analysis").should("not.exist"); + cy.findAllByText("Questions about orders <100"); + cy.findByText("Orders, orders <100, Filtered by Total"); + }); + + it("should show the metric detail view for a specific id", () => { + cy.visit("/admin/datamodel/metric/1"); + cy.findByText("Edit Your Metric"); + cy.findByText("Preview"); + }); + + it("should update that metric", () => { + cy.visit("/admin"); + cy.contains("Data Model").click(); + cy.contains("Metrics").click(); + + cy.contains("orders <100") + .parent() + .parent() + .find(".Icon-ellipsis") + .click(); + cy.contains("Edit Metric").click(); + + // update the filter from "< 100" to "> 10" + cy.url().should("match", /metric\/1$/); + cy.contains("Edit Your Metric"); + cy.contains(/Total\s+is less than/).click(); + popover() + .contains("Less than") + .click(); + popover() + .contains("Greater than") + .click(); + popover() + .find("input") + .type("{SelectAll}10"); + popover() + .contains("Update filter") + .click(); + + // confirm that the preview updated + cy.contains("Result: 18703"); + + // update name and description, set a revision note, and save the update + cy.get('[name="name"]') + .clear() + .type("orders >10"); + cy.get('[name="description"]') + .clear() + .type("Count of orders with a total over $10."); + cy.get('[name="revision_message"]').type("time for a change"); + cy.contains("Save changes").click(); + + // get redirected to previous page and see the new metric name + cy.url().should("match", /datamodel\/metrics$/); + cy.contains("orders >10"); + + // clean up + cy.contains("orders >10") + .parent() + .parent() + .find(".Icon-ellipsis") + .click(); + cy.contains("Retire Metric").click(); + modal() + .find("textarea") + .type("delete it"); + modal() + .contains("button", "Retire") + .click(); + }); }); }); -- GitLab