Skip to content
Snippets Groups Projects
Unverified Commit 9b0e65f7 authored by metabase-bot[bot]'s avatar metabase-bot[bot] Committed by GitHub
Browse files

[CI] Optimize `admin > databases` E2E tests (#29418) (#29831)


* Rework repro for 16382

* Merge multiple "connection settings" tests together

* Merge multiple "scheduling settings" tests together

* Merge tiny test with the main body

* Remove redundant tests

* Rework the sample database action sidebar

* Handle database exceptions in one spec

* Move stray database test to exceptions spec

* Move Postgres SSL test to external

* Group together Google service account JSON tests

* Custom caching

* Move remaining pieces of add to external

* Rename spec to `add-new-database`

* Move repro for 20471 to exceptions

* Add segments and metrics to the database we want to delete

* Add `visitDatabase` helper

Co-authored-by: default avatarNemanja Glumac <31325167+nemanjaglumac@users.noreply.github.com>
parent b92a1cdb
No related branches found
No related tags found
No related merge requests found
import { restore } from "e2e/support/helpers";
import { WRITABLE_DB_ID, WRITABLE_DB_CONFIG } from "e2e/support/cypress_data";
import { visitDatabase } from "./helpers/e2e-database-helpers";
describe(
"admin > database > external databases > enable actions",
{ tags: ["@external", "@actions"] },
......@@ -10,7 +12,7 @@ describe(
restore(`${dialect}-writable`);
cy.signInAsAdmin();
cy.request(`/api/database/${WRITABLE_DB_ID}`).then(({ body }) => {
visitDatabase(WRITABLE_DB_ID).then(({ response: { body } }) => {
expect(body.name).to.include("Writable");
expect(body.name.toLowerCase()).to.include(dialect);
......@@ -20,7 +22,6 @@ describe(
expect(body.settings["database-enable-actions"]).to.eq(true);
});
cy.visit(`/admin/databases/${WRITABLE_DB_ID}`);
cy.get("#model-actions-toggle").should(
"have.attr",
"aria-checked",
......
import { restore, typeAndBlurUsingLabel } from "e2e/support/helpers";
import {
restore,
popover,
typeAndBlurUsingLabel,
isEE,
} from "e2e/support/helpers";
import {
QA_MONGO_PORT,
QA_MYSQL_PORT,
QA_POSTGRES_PORT,
} from "e2e/support/cypress_data";
describe(
"admin > database > add > external databases",
{ tags: "@external" },
() => {
beforeEach(() => {
restore();
cy.signInAsAdmin();
describe("admin > database > add", () => {
beforeEach(() => {
restore();
cy.signInAsAdmin();
cy.intercept("POST", "/api/database").as("createDatabase");
cy.intercept("POST", "/api/database").as("createDatabase");
cy.visit("/admin/databases/create");
cy.findByLabelText("Database type").click();
cy.visit("/admin/databases/create");
// should display a setup help card
cy.findByText("Need help connecting?");
cy.findByLabelText("Database type").click();
});
it("should add a new database", () => {
popover().within(() => {
if (isEE) {
// EE should ship with Oracle and Vertica as options
cy.findByText("Oracle");
// cy.findByText("Vertica");
}
cy.findByText("H2").click();
});
typeAndBlurUsingLabel("Display name", "Test");
typeAndBlurUsingLabel("Connection String", "invalid");
// should surface an error if the connection string is invalid
cy.button("Save").click();
cy.wait("@createDatabase");
cy.findByText(": check your connection string");
cy.findByText("Implicitly relative file paths are not allowed.");
// should be able to recover from an error and add database with the correct connection string
cy.findByDisplayValue("invalid")
.clear()
.type(
"zip:./target/uberjar/metabase.jar!/sample-database.db;USER=GUEST;PASSWORD=guest",
{ delay: 0 },
);
cy.button("Save", { timeout: 10000 }).click();
cy.wait("@createDatabase");
});
describe("external databases", { tags: "@external" }, () => {
it("should add Postgres database and redirect to listing (metabase#12972, metabase#14334, metabase#17450)", () => {
cy.contains("PostgreSQL").click({ force: true });
......@@ -50,6 +86,53 @@ describe(
typeAndBlurUsingLabel("Username", "metabase");
typeAndBlurUsingLabel("Password", "metasample123");
const confirmSSLFields = (visible, hidden) => {
visible.forEach(field => cy.findByText(field));
hidden.forEach(field => cy.findByText(field).should("not.exist"));
};
const ssl = "Use a secure connection (SSL)",
sslMode = "SSL Mode",
useClientCert = "Authenticate client certificate?",
clientPemCert = "SSL Client Certificate (PEM)",
clientPkcsCert = "SSL Client Key (PKCS-8/DER)",
sslRootCert = "SSL Root Certificate (PEM)";
// initially, all SSL sub-properties should be hidden
confirmSSLFields(
[ssl],
[sslMode, useClientCert, clientPemCert, clientPkcsCert, sslRootCert],
);
toggleFieldWithDisplayName(ssl);
// when ssl is enabled, the mode and "enable client cert" options should be shown
confirmSSLFields(
[ssl, sslMode, useClientCert],
[clientPemCert, clientPkcsCert, sslRootCert],
);
toggleFieldWithDisplayName(useClientCert);
// when the "enable client cert" option is enabled, its sub-properties should be shown
confirmSSLFields(
[ssl, sslMode, useClientCert, clientPemCert, clientPkcsCert],
[sslRootCert],
);
selectFieldOption(sslMode, "verify-ca");
// when the ssl mode is set to "verify-ca", then the root cert option should be shown
confirmSSLFields(
[
ssl,
sslMode,
useClientCert,
clientPemCert,
clientPkcsCert,
sslRootCert,
],
[],
);
toggleFieldWithDisplayName(ssl);
cy.button("Save").should("not.be.disabled").click();
cy.wait("@createDatabase").then(({ request }) => {
......@@ -123,7 +206,9 @@ describe(
cy.findByLabelText("Port").should("not.exist");
cy.findByLabelText("Paste your connection string").type(
connectionString,
{ delay: 0 },
{
delay: 0,
},
);
cy.findByText("Save").should("not.be.disabled").click();
......@@ -176,5 +261,78 @@ describe(
cy.findByText("Done!");
});
});
},
);
});
describe("Google service account JSON upload", () => {
const serviceAccountJSON = '{"foo": 123}';
it("should work for BigQuery", () => {
cy.visit("/admin/databases/create");
chooseDatabase("BigQuery");
typeAndBlurUsingLabel("Display name", "BQ");
selectFieldOption("Datasets", "Only these...");
cy.findByPlaceholderText("E.x. public,auth*").type("some-dataset");
mockUploadServiceAccountJSON(serviceAccountJSON);
mockSuccessfulDatabaseSave().then(({ request: { body } }) => {
expect(body.details["service-account-json"]).to.equal(
serviceAccountJSON,
);
});
});
it("should work for Google Analytics", () => {
cy.visit("/admin/databases/create");
chooseDatabase("Google Analytics");
typeAndBlurUsingLabel("Display name", "GA");
typeAndBlurUsingLabel("Google Analytics Account ID", " 9 ");
mockUploadServiceAccountJSON(serviceAccountJSON);
mockSuccessfulDatabaseSave().then(({ request: { body } }) => {
expect(body.details["service-account-json"]).to.equal(
serviceAccountJSON,
);
});
});
});
});
function toggleFieldWithDisplayName(displayName) {
cy.findByLabelText(displayName).click();
}
function selectFieldOption(fieldName, option) {
cy.findByLabelText(fieldName).click();
popover().contains(option).click({ force: true });
}
function chooseDatabase(database) {
selectFieldOption("Database type", database);
}
function mockUploadServiceAccountJSON(fileContents) {
// create blob to act as selected file
cy.get("input[type=file]")
.then(async input => {
const blob = await Cypress.Blob.binaryStringToBlob(fileContents);
const file = new File([blob], "service-account.json");
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
input[0].files = dataTransfer.files;
return input;
})
.trigger("change", { force: true })
.trigger("blur", { force: true });
}
function mockSuccessfulDatabaseSave() {
cy.intercept("POST", "/api/database", req => {
req.reply({ statusCode: 200, body: { id: 42 }, delay: 100 });
}).as("createDatabase");
cy.button("Save").click();
return cy.wait("@createDatabase");
}
import {
restore,
popover,
describeEE,
mockSessionProperty,
isEE,
typeAndBlurUsingLabel,
} from "e2e/support/helpers";
describe("scenarios > admin > databases > add", () => {
beforeEach(() => {
restore();
cy.signInAsAdmin();
});
it("should show validation error if you enter invalid db connection info", () => {
cy.intercept("POST", "/api/database").as("createDatabase");
// should display a setup help card
cy.visit("/admin/databases/create");
cy.findByText("Need help connecting?");
chooseDatabase("H2");
typeAndBlurUsingLabel("Display name", "Test");
typeAndBlurUsingLabel("Connection String", "invalid");
cy.button("Save").click();
cy.wait("@createDatabase");
cy.findByText(": check your connection string");
cy.findByText("Implicitly relative file paths are not allowed.");
});
it("should show error correctly on server error", () => {
cy.intercept("POST", "/api/database", req => {
req.reply({
statusCode: 400,
body: "DATABASE CONNECTION ERROR",
delay: 1000,
});
}).as("createDatabase");
cy.visit("/admin/databases/create");
typeAndBlurUsingLabel("Display name", "Test");
typeAndBlurUsingLabel("Database name", "db");
typeAndBlurUsingLabel("Username", "admin");
cy.button("Save").click();
cy.wait("@createDatabase");
cy.findByText("DATABASE CONNECTION ERROR").should("exist");
});
it("EE should ship with Oracle and Vertica as options", () => {
cy.onlyOn(isEE);
cy.visit("/admin/databases/create");
cy.findByLabelText("Database type").click();
popover().within(() => {
cy.findByText("Oracle");
cy.findByText("Vertica");
});
});
describe("BigQuery", () => {
it("should let you upload the service account json from a file", () => {
cy.visit("/admin/databases/create");
chooseDatabase("BigQuery");
// enter text
typeAndBlurUsingLabel("Display name", "bq db");
// typeAndBlurUsingLabel("Dataset ID", "some-dataset");
selectFieldOption("Datasets", "Only these...");
cy.findByPlaceholderText("E.x. public,auth*").type("some-dataset");
// create blob to act as selected file
cy.get("input[type=file]")
.then(async input => {
const blob = await Cypress.Blob.binaryStringToBlob('{"foo": 123}');
const file = new File([blob], "service-account.json");
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
input[0].files = dataTransfer.files;
return input;
})
.trigger("change", { force: true })
.trigger("blur", { force: true });
cy.intercept("POST", "/api/database", req => {
req.reply({
statusCode: 200,
body: { id: 123 },
delay: 100,
});
}).as("createDatabase");
// submit form and check that the file's body is included
cy.button("Save").click();
cy.wait("@createDatabase").should(xhr => {
expect(xhr.request.body.details["service-account-json"]).to.equal(
'{"foo": 123}',
);
});
});
});
describe("Google Analytics ", () => {
it("should let you upload the service account json from a file", () => {
cy.visit("/admin/databases/create");
chooseDatabase("Google Analytics");
typeAndBlurUsingLabel("Display name", "google analytics");
typeAndBlurUsingLabel("Google Analytics Account ID", " 999 ");
// create blob to act as selected file
cy.get("input[type=file]")
.then(async input => {
const blob = await Cypress.Blob.binaryStringToBlob('{"foo": 123}');
const file = new File([blob], "service-account.json");
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
input[0].files = dataTransfer.files;
return input;
})
.trigger("change", { force: true })
.trigger("blur", { force: true });
cy.intercept("POST", "/api/database", req => {
req.reply({ statusCode: 200, body: { id: 123 }, delay: 100 });
}).as("createDatabase");
// submit form and check that the file's body is included
cy.button("Save").click();
cy.wait("@createDatabase").should(xhr => {
expect(xhr.request.body.details["service-account-json"]).to.equal(
'{"foo": 123}',
);
});
});
});
describeEE("caching", () => {
beforeEach(() => {
mockSessionProperty("enable-query-caching", true);
cy.intercept("POST", "/api/database", { id: 42 }).as("createDatabase");
cy.visit("/admin/databases/create");
typeAndBlurUsingLabel("Display name", "Test");
typeAndBlurUsingLabel("Host", "localhost");
typeAndBlurUsingLabel("Database name", "db");
typeAndBlurUsingLabel("Username", "admin");
cy.findByText("Show advanced options").click();
});
it("sets cache ttl to null by default", () => {
cy.button("Save").click();
cy.wait("@createDatabase").then(({ request }) => {
expect(request.body.cache_ttl).to.equal(null);
});
});
it("allows to set cache ttl", () => {
cy.findByText("Use instance default (TTL)").click();
popover().findByText("Custom").click();
cy.findByDisplayValue("24").clear().type("48").blur();
cy.button("Save").click();
cy.wait("@createDatabase").then(({ request }) => {
expect(request.body.cache_ttl).to.equal(48);
});
});
});
it("should show the various Postgres SSL options correctly", () => {
const confirmSSLFields = (visible, hidden) => {
visible.forEach(field => cy.findByText(field));
hidden.forEach(field => cy.findByText(field).should("not.exist"));
};
const ssl = "Use a secure connection (SSL)",
sslMode = "SSL Mode",
useClientCert = "Authenticate client certificate?",
clientPemCert = "SSL Client Certificate (PEM)",
clientPkcsCert = "SSL Client Key (PKCS-8/DER)",
sslRootCert = "SSL Root Certificate (PEM)";
cy.visit("/admin/databases/create");
chooseDatabase("PostgreSQL");
// initially, all SSL sub-properties should be hidden
confirmSSLFields(
[ssl],
[sslMode, useClientCert, clientPemCert, clientPkcsCert, sslRootCert],
);
toggleFieldWithDisplayName(ssl);
// when ssl is enabled, the mode and "enable client cert" options should be shown
confirmSSLFields(
[ssl, sslMode, useClientCert],
[clientPemCert, clientPkcsCert, sslRootCert],
);
toggleFieldWithDisplayName(useClientCert);
// when the "enable client cert" option is enabled, its sub-properties should be shown
confirmSSLFields(
[ssl, sslMode, useClientCert, clientPemCert, clientPkcsCert],
[sslRootCert],
);
selectFieldOption(sslMode, "verify-ca");
// when the ssl mode is set to "verify-ca", then the root cert option should be shown
confirmSSLFields(
[ssl, sslMode, useClientCert, clientPemCert, clientPkcsCert, sslRootCert],
[],
);
});
});
function toggleFieldWithDisplayName(displayName) {
cy.findByLabelText(displayName).click();
}
function selectFieldOption(fieldName, option) {
cy.findByLabelText(fieldName).click();
popover().contains(option).click({ force: true });
}
function chooseDatabase(database) {
selectFieldOption("Database type", database);
}
import { restore, typeAndBlurUsingLabel, isEE } from "e2e/support/helpers";
describe("scenarios > admin > databases > exceptions", () => {
beforeEach(() => {
restore();
cy.signInAsAdmin();
});
it("should handle malformed (null) database details (metabase#25715)", () => {
cy.intercept("GET", "/api/database/1", req => {
req.reply(res => {
res.body.details = null;
});
}).as("loadDatabase");
cy.visit("/admin/databases/1");
cy.wait("@loadDatabase");
// It is unclear how this issue will be handled,
// but at the very least it shouldn't render the blank page.
cy.get("nav").should("contain", "Metabase Admin");
// The response still contains the database name,
// so there's no reason we can't display it.
cy.contains(/Sample Database/i);
// This seems like a reasonable CTA if the database is beyond repair.
cy.button("Remove this database").should("not.be.disabled");
});
it("should show error upon a bad request", () => {
cy.intercept("POST", "/api/database", req => {
req.reply({
statusCode: 400,
body: "DATABASE CONNECTION ERROR",
});
}).as("createDatabase");
cy.visit("/admin/databases/create");
typeAndBlurUsingLabel("Display name", "Test");
typeAndBlurUsingLabel("Database name", "db");
typeAndBlurUsingLabel("Username", "admin");
cy.button("Save").click();
cy.wait("@createDatabase");
cy.findByText("DATABASE CONNECTION ERROR").should("exist");
});
it("should handle non-existing databases (metabase#11037)", () => {
cy.intercept("GET", "/api/database/999").as("loadDatabase");
cy.visit("/admin/databases/999");
cy.wait("@loadDatabase").then(({ response }) => {
expect(response.statusCode).to.eq(404);
});
cy.findByText("Not found.");
cy.findByRole("table").should("not.exist");
});
it("should handle a failure to `GET` the list of all databases (metabase#20471)", () => {
const errorMessage = "Lorem ipsum dolor sit amet, consectetur adip";
cy.intercept(
{
method: "GET",
pathname: "/api/database",
query: isEE
? {
exclude_uneditable_details: "true",
}
: null,
},
req => {
req.reply({
statusCode: 500,
body: { message: errorMessage },
});
},
).as("failedGet");
cy.visit("/admin/databases");
cy.wait("@failedGet");
cy.findByRole("heading", { name: "Something's gone wrong" });
cy.findByText(
"We've run into an error. You can try refreshing the page, or just go back.",
);
cy.findByText(errorMessage).should("not.be.visible");
cy.findByText("Show error details").click();
cy.findByText(errorMessage).should("be.visible");
});
});
import { restore, popover, modal, describeEE } from "e2e/support/helpers";
import { SAMPLE_DB_ID } from "e2e/support/cypress_data";
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
import { visitDatabase } from "./helpers/e2e-database-helpers";
const { ORDERS_ID, ORDERS } = SAMPLE_DATABASE;
describe("scenarios > admin > databases > sample database", () => {
beforeEach(() => {
restore();
cy.signInAsAdmin();
cy.intercept("PUT", "/api/database/*").as("databaseUpdate");
});
it("database settings", () => {
visitDatabase(SAMPLE_DB_ID);
// should not display a setup help card
cy.findByText("Need help connecting?").should("not.exist");
cy.log(
"should not be possible to change database type for the Sample Database (metabase#16382)",
);
cy.findByLabelText("Database type")
.should("have.text", "H2")
.and("be.disabled");
cy.log("should correctly display connection settings");
cy.findByLabelText("Display name").should("have.value", "Sample Database");
cy.findByLabelText("Connection String")
.should("have.attr", "value")
.and("contain", "sample-database.db");
cy.log("should be possible to modify the connection settings");
cy.findByText("Show advanced options").click();
// `auto_run_queries` toggle should be ON by default
cy.findByLabelText("Rerun queries for simple explorations")
.should("have.attr", "aria-checked", "true")
.click();
// Reported failing in v0.36.4
cy.log(
"should respect the settings for automatic query running (metabase#13187)",
);
cy.findByLabelText("Rerun queries for simple explorations").should(
"have.attr",
"aria-checked",
"false",
);
cy.log("change the metadata_sync period");
cy.findByLabelText("Choose when syncs and scans happen").click();
cy.findByText("Hourly").click();
popover().within(() => {
cy.findByText("Daily").click({ force: true });
});
// "lets you change the cache_field_values period"
cy.findByLabelText("Never, I'll do this manually if I need to").should(
"have.attr",
"aria-selected",
"true",
);
cy.findByLabelText("Regularly, on a schedule")
.click()
.within(() => {
cy.findByText("Daily").click();
});
popover().findByText("Weekly").click();
cy.button("Save changes").click();
cy.wait("@databaseUpdate").then(({ response: { body } }) => {
expect(body.details["let-user-control-scheduling"]).to.equal(true);
expect(body.schedules.metadata_sync.schedule_type).to.equal("daily");
expect(body.schedules.cache_field_values.schedule_type).to.equal(
"weekly",
);
});
cy.button("Success");
// "lets you change the cache_field_values to 'Only when adding a new filter widget'"
cy.findByLabelText("Only when adding a new filter widget").click();
cy.button("Save changes", { timeout: 10000 }).click();
cy.wait("@databaseUpdate").then(({ response: { body } }) => {
expect(body.is_full_sync).to.equal(false);
expect(body.is_on_demand).to.equal(true);
});
// and back to never
cy.findByLabelText("Never, I'll do this manually if I need to").click();
cy.button("Save changes", { timeout: 10000 }).click();
cy.wait("@databaseUpdate").then(({ response: { body } }) => {
expect(body.is_full_sync).to.equal(false);
expect(body.is_on_demand).to.equal(false);
});
});
it("database actions sidebar", () => {
cy.intercept("POST", `/api/database/${SAMPLE_DB_ID}/sync_schema`).as(
"sync_schema",
);
cy.intercept("POST", `/api/database/${SAMPLE_DB_ID}/rescan_values`).as(
"rescan_values",
);
cy.intercept("POST", `/api/database/${SAMPLE_DB_ID}/discard_values`).as(
"discard_values",
);
cy.intercept("GET", `/api/database/${SAMPLE_DB_ID}/usage_info`).as(
`usage_info`,
);
cy.intercept("DELETE", `/api/database/${SAMPLE_DB_ID}`).as("delete");
// model
cy.request("PUT", "/api/card/1", { dataset: true });
// Create a segment through API
cy.request("POST", "/api/segment", {
name: "Small orders",
description: "All orders with a total under $100.",
table_id: ORDERS_ID,
definition: {
"source-table": ORDERS_ID,
aggregation: [["count"]],
filter: ["<", ["field", ORDERS.TOTAL, null], 100],
},
});
// metric
cy.request("POST", "/api/metric", {
name: "Revenue",
description: "Sum of orders subtotal",
table_id: ORDERS_ID,
definition: {
"source-table": ORDERS_ID,
aggregation: [["sum", ["field", ORDERS.SUBTOTAL, null]]],
},
});
visitDatabase(SAMPLE_DB_ID);
// lets you trigger the manual database schema sync
cy.button("Sync database schema now").click();
cy.wait("@sync_schema");
cy.findByText("Sync triggered!");
// lets you trigger the manual rescan of field values
cy.findByText("Re-scan field values now").click();
cy.wait("@rescan_values");
cy.findByText("Scan triggered!");
// lets you discard saved field values
cy.findByText("Danger Zone")
.parent()
.as("danger")
.within(() => {
cy.button("Discard saved field values").click();
});
modal().within(() => {
cy.findByRole("heading").should(
"have.text",
"Discard saved field values",
);
cy.findByText("Are you sure you want to do this?");
cy.button("Yes").click();
});
cy.wait("@discard_values");
// lets you remove the Sample Database
cy.get("@danger").within(() => {
cy.button("Remove this database").click();
cy.wait("@usage_info");
});
modal().within(() => {
cy.button("Delete this content and the DB connection")
.as("deleteButton")
.should("be.disabled");
cy.findByLabelText(/Delete [0-9]* saved questions?/)
.should("not.be.checked")
.click()
.should("be.checked");
cy.findByLabelText(/Delete [0-9]* models?/)
.should("not.be.checked")
.click()
.should("be.checked");
cy.findByLabelText(/Delete [0-9]* metrics?/)
.should("not.be.checked")
.click()
.should("be.checked");
cy.findByLabelText(/Delete [0-9]* segments?/)
.should("not.be.checked")
.click()
.should("be.checked");
cy.findByText(
"This will delete every saved question, model, metric, and segment you’ve made that uses this data, and can’t be undone!",
);
cy.get("@deleteButton").should("be.disabled");
cy.findByPlaceholderText("Are you completely sure?")
.type("Sample Database")
.blur();
cy.intercept("GET", "/api/database").as("fetchDatabases");
cy.get("@deleteButton").should("be.enabled").click();
cy.wait(["@delete", "@fetchDatabases"]);
});
cy.location("pathname").should("eq", "/admin/databases/"); // FIXME why the trailing slash?
cy.intercept("POST", "/api/database/sample_database").as("sample_database");
cy.contains("Bring the sample database back", {
timeout: 10000,
}).click();
cy.wait("@sample_database");
cy.findAllByRole("cell").contains("Sample Database").click();
const newSampleDatabaseId = SAMPLE_DB_ID + 1;
cy.location("pathname").should(
"eq",
`/admin/databases/${newSampleDatabaseId}`,
);
});
describeEE("custom caching", () => {
it("should set custom cache ttl", () => {
cy.request("PUT", "api/setting/enable-query-caching", { value: true });
visitDatabase(SAMPLE_DB_ID).then(({ response: { body } }) => {
expect(body.cache_ttl).to.be.null;
});
cy.findByText("Show advanced options").click();
setCustomCacheTTLValue("48");
cy.button("Save changes").click();
cy.wait("@databaseUpdate").then(({ request, response }) => {
expect(request.body.cache_ttl).to.equal(48);
expect(response.body.cache_ttl).to.equal(48);
});
function setCustomCacheTTLValue(value) {
cy.findAllByTestId("select-button")
.contains("Use instance default (TTL)")
.click();
popover().findByText("Custom").click();
cy.findByDisplayValue("24").clear().type(value).blur();
}
});
});
});
import {
restore,
popover,
modal,
describeEE,
mockSessionProperty,
} from "e2e/support/helpers";
import { SAMPLE_DB_ID } from "e2e/support/cypress_data";
describe("scenarios > admin > databases > edit", () => {
beforeEach(() => {
restore();
cy.signInAsAdmin();
cy.intercept("PUT", "/api/database/*").as("databaseUpdate");
});
describe("Database type", () => {
it("should be disabled for the Sample Dataset (metabase#16382)", () => {
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.findByText("H2").parentsUntil("a").should("be.disabled");
});
});
describe("Connection settings", () => {
it("shows the connection settings for sample database correctly", () => {
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.findByLabelText("Display name").should(
"have.value",
"Sample Database",
);
cy.findByLabelText("Connection String").should($input =>
expect($input[0].value).to.match(/sample-database\.db/),
);
});
it("lets you modify the connection settings", () => {
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.findByText("Show advanced options").click();
cy.findByLabelText("Choose when syncs and scans happen").click();
cy.findByText("Save changes").click();
cy.wait("@databaseUpdate").then(({ response }) =>
expect(response.body.details["let-user-control-scheduling"]).to.equal(
true,
),
);
cy.findByText("Success");
});
it("`auto_run_queries` toggle should be ON by default for `SAMPLE_DATABASE`", () => {
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.findByText("Show advanced options").click();
cy.findByLabelText("Rerun queries for simple explorations").should(
"have.attr",
"aria-checked",
"true",
);
});
it("should respect the settings for automatic query running (metabase#13187)", () => {
cy.log("Turn off `auto run queries`");
cy.request("PUT", `/api/database/${SAMPLE_DB_ID}`, {
auto_run_queries: false,
});
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.log("Reported failing on v0.36.4");
cy.findByText("Show advanced options").click();
cy.findByLabelText("Rerun queries for simple explorations").should(
"have.attr",
"aria-checked",
"false",
);
});
describeEE("caching", () => {
beforeEach(() => {
mockSessionProperty("enable-query-caching", true);
});
it("allows to manage cache ttl", () => {
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.findByText("Show advanced options").click();
cy.findByText("Use instance default (TTL)").click();
popover().findByText("Custom").click();
cy.findByDisplayValue("24").clear().type("32").blur();
cy.button("Save changes").click();
cy.wait("@databaseUpdate").then(({ request, response }) => {
expect(request.body.cache_ttl).to.equal(32);
expect(response.body.cache_ttl).to.equal(32);
});
cy.findByTextEnsureVisible("Custom").click();
popover().findByText("Use instance default (TTL)").click();
// We need to wait until "Success" button state is gone first
cy.button("Save changes", { timeout: 10000 }).click();
cy.wait("@databaseUpdate").then(({ request }) => {
expect(request.body.cache_ttl).to.equal(null);
});
});
});
});
describe("Scheduling settings", () => {
beforeEach(() => {
// Turn on scheduling without relying on the previous test(s)
cy.request("PUT", `/api/database/${SAMPLE_DB_ID}`, {
details: {
"let-user-control-scheduling": true,
},
engine: "h2",
});
});
it("shows the initial scheduling settings correctly", () => {
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.findByText("Show advanced options").click();
cy.findByText("Regularly, on a schedule").should("exist");
cy.findByText("Hourly").should("exist");
cy.findByLabelText("Regularly, on a schedule").should(
"have.attr",
"aria-selected",
"true",
);
});
it("lets you change the metadata_sync period", () => {
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.findByText("Show advanced options").click();
cy.findByText("Hourly").click();
popover().within(() => {
cy.findByText("Daily").click({ force: true });
});
cy.findByLabelText("Regularly, on a schedule").should(
"have.attr",
"aria-selected",
"true",
);
cy.findByText("Save changes").click();
cy.wait("@databaseUpdate").then(({ response }) =>
expect(response.body.schedules.metadata_sync.schedule_type).to.equal(
"daily",
),
);
});
it("lets you change the cache_field_values perid", () => {
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.findByText("Show advanced options").click();
cy.findByText("Regularly, on a schedule")
.parent()
.parent()
.within(() => {
cy.findByText("Daily").click();
});
popover().within(() => {
cy.findByText("Weekly").click({ force: true });
});
cy.findByText("Save changes").click();
cy.wait("@databaseUpdate").then(({ response }) => {
expect(
response.body.schedules.cache_field_values.schedule_type,
).to.equal("weekly");
});
});
it("lets you change the cache_field_values to 'Only when adding a new filter widget'", () => {
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.findByText("Show advanced options").click();
cy.findByText("Only when adding a new filter widget").click();
cy.findByText("Save changes").click();
cy.wait("@databaseUpdate").then(({ response }) => {
expect(response.body.is_full_sync).to.equal(false);
expect(response.body.is_on_demand).to.equal(true);
});
});
it("lets you change the cache_field_values to Never", () => {
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.findByText("Show advanced options").click();
cy.findByText("Never, I'll do this manually if I need to").click();
cy.findByText("Save changes").click();
cy.wait("@databaseUpdate").then(({ response }) => {
expect(response.body.is_full_sync).to.equal(false);
expect(response.body.is_on_demand).to.equal(false);
});
});
});
describe("Actions sidebar", () => {
it("lets you trigger the manual database schema sync", () => {
cy.intercept("POST", `/api/database/${SAMPLE_DB_ID}/sync_schema`).as(
"sync_schema",
);
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.findByText("Sync database schema now").click();
cy.wait("@sync_schema");
cy.findByText("Sync triggered!");
});
it("lets you trigger the manual rescan of field values", () => {
cy.intercept("POST", `/api/database/${SAMPLE_DB_ID}/rescan_values`).as(
"rescan_values",
);
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.findByText("Re-scan field values now").click();
cy.wait("@rescan_values");
cy.findByText("Scan triggered!");
});
it("lets you discard saved field values", () => {
cy.intercept("POST", `/api/database/${SAMPLE_DB_ID}/discard_values`).as(
"discard_values",
);
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.findByText("Discard saved field values").click();
cy.findByText("Yes").click();
cy.wait("@discard_values");
});
it("lets you remove the Sample Database", () => {
cy.intercept("DELETE", `/api/database/${SAMPLE_DB_ID}`).as("delete");
cy.intercept("GET", `/api/database/${SAMPLE_DB_ID}/usage_info`).as(
`usage_info`,
);
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.findByText("Remove this database").click();
cy.wait("@usage_info");
modal().within(() => {
cy.findByLabelText(/Delete [0-9]* saved questions/).click();
cy.findByPlaceholderText("Are you completely sure?").type(
"Sample Database",
);
cy.get(".Button.Button--danger").click();
});
cy.wait("@delete");
cy.url().should("match", /\/admin\/databases\/$/);
});
it("should not display a setup help card", () => {
cy.intercept("GET", `/api/database/${SAMPLE_DB_ID}`).as("loadDatabase");
cy.visit(`/admin/databases/${SAMPLE_DB_ID}`);
cy.wait("@loadDatabase");
cy.findByText("Need help connecting?").should("not.exist");
});
});
});
/**
* Visit a database and immediately wait for the related request.
* You can assert on the response of `GET /api/database/:id`.
* @param {number} id - Id of the database we're about to visit.
*
* @example
* visitDatabase(3)
*
* @example
* visitDatabase(42).then(({response: { body }})=> {
* expect(body.id).to.equal(42);
* })
*/
export function visitDatabase(id) {
cy.intercept("GET", `/api/database/${id}`).as("loadDatabase");
cy.visit(`/admin/databases/${id}`);
return cy.wait("@loadDatabase");
}
import { restore, describeEE, isOSS } from "e2e/support/helpers";
import { SAMPLE_DB_ID } from "e2e/support/cypress_data";
describe("scenarios > admin > databases > list", () => {
beforeEach(() => {
restore();
cy.signInAsAdmin();
});
describe("OSS", { tags: "@OSS" }, () => {
it("should not display error messages upon a failed `GET` (metabase#20471)", () => {
cy.onlyOn(isOSS);
const errorMessage = "Lorem ipsum dolor sit amet, consectetur adip";
cy.intercept(
{
method: "GET",
pathname: "/api/database",
},
req => {
req.reply({
statusCode: 500,
body: { message: errorMessage },
});
},
).as("failedGet");
cy.visit("/admin/databases");
cy.wait("@failedGet");
// Not sure how exactly is this going the be fixed, but we should't show the full error message on the page in any case
cy.findByText(errorMessage).should("not.be.visible");
});
});
describeEE("EE", () => {
it("should not display error messages upon a failed `GET` (metabase#20471)", () => {
const errorMessage = "Lorem ipsum dolor sit amet, consectetur adip";
cy.intercept(
{
method: "GET",
pathname: "/api/database",
query: {
exclude_uneditable_details: "true",
},
},
req => {
req.reply({
statusCode: 500,
body: { message: errorMessage },
});
},
).as("failedGet");
cy.visit("/admin/databases");
cy.wait("@failedGet");
// Not sure how exactly is this going the be fixed, but we should't show the full error message on the page in any case
cy.findByText(errorMessage).should("not.be.visible");
});
});
it("should let you see databases in list view", () => {
cy.visit("/admin/databases");
cy.findByText("Sample Database");
cy.findByText("H2");
});
it("should not let you see saved questions in the database list", () => {
cy.visit("/admin/databases");
cy.get("tr").should("have.length", 2);
});
it("should let you view a database's detail view", () => {
cy.visit("/admin/databases");
cy.contains("Sample Database").click();
cy.url().should("match", /\/admin\/databases\/\d+$/);
});
it("should let you add a database", () => {
cy.visit("/admin/databases");
cy.contains("Add database").click();
cy.url().should("match", /\/admin\/databases\/create$/);
// *** code here should be more thorough
});
it("should let you access edit page a database", () => {
cy.visit("/admin/databases");
cy.contains("Sample Database").click();
cy.location("pathname").should("eq", `/admin/databases/${SAMPLE_DB_ID}`);
});
it("should let you bring back the sample database", () => {
cy.intercept("POST", "/api/database/sample_database").as("sample_database");
cy.request("DELETE", `/api/database/${SAMPLE_DB_ID}`).as("delete");
cy.visit("/admin/databases");
cy.contains("Bring the sample database back").click();
cy.wait("@sample_database");
cy.contains("Sample Database").click();
cy.url().should("match", /\/admin\/databases\/\d+$/);
});
it("should handle malformed (null) database details (metabase#25715)", () => {
cy.intercept("GET", "/api/database/1", req => {
req.reply(res => {
res.body.details = null;
});
}).as("loadDatabase");
cy.visit("/admin/databases/1");
cy.wait("@loadDatabase");
// It is unclear how this issue will be handled,
// but at the very least it shouldn't render the blank page.
cy.get("nav").should("contain", "Metabase Admin");
// The response still contains the database name,
// so there's no reason we can't display it.
cy.contains(/Sample Database/i);
// This seems like a reasonable CTA if the database is beyond repair.
cy.button("Remove this database");
});
});
import { restore } from "e2e/support/helpers";
describe("scenarios > admin > spinner", () => {
beforeEach(() => {
restore();
cy.signInAsAdmin();
});
describe("API request", () => {
it("should return correct DB", () => {
cy.visit("/admin/databases/1");
cy.findByText("Sample Database");
cy.findByText("Add Database").should("not.exist");
});
it("should not spin forever if it returns an error (metabase#11037)", () => {
cy.visit("/admin/databases/999");
cy.findAllByText("Databases").should("have.length", 2);
cy.findByText("Loading...").should("not.exist");
cy.findByText("Not found.");
});
});
});
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