Skip to content
Snippets Groups Projects
Unverified Commit b8e9d9db authored by Jessica DeWitt's avatar Jessica DeWitt Committed by GitHub
Browse files

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
parent da0021cd
No related branches found
No related tags found
No related merge requests found
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]);
});
});
});
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 });
}
}
});
});
});
});
......@@ -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();
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment