Files
metabase/e2e/test/scenarios/admin-2/settings.cy.spec.js

1161 lines
37 KiB
JavaScript

const { H } = cy;
import {
SAMPLE_DB_ID,
SAMPLE_DB_SCHEMA_ID,
WEBMAIL_CONFIG,
} from "e2e/support/cypress_data";
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
import { ORDERS_QUESTION_ID } from "e2e/support/cypress_sample_instance_data";
const { ORDERS, ORDERS_ID } = SAMPLE_DATABASE;
const { SMTP_PORT, WEB_PORT } = WEBMAIL_CONFIG;
describe("scenarios > admin > settings", () => {
beforeEach(() => {
H.resetSnowplow();
H.restore();
cy.signInAsAdmin();
});
it(
"should prompt admin to migrate to a hosted instance",
{ tags: "@OSS" },
() => {
cy.visit("/admin/settings/cloud");
cy.findByTestId("upsell-big-card").findByText(
/Migrate to Metabase Cloud/,
);
cy.findByTestId("upsell-big-card")
.findAllByRole("link", { name: "Learn more" })
.click();
// link opens in new tab
H.expectUnstructuredSnowplowEvent({
event: "upsell_viewed",
promoted_feature: "cloud",
});
H.expectNoBadSnowplowEvents();
},
);
it("should surface an error when validation for any field fails (metabase#4506)", () => {
const BASE_URL = Cypress.config().baseUrl;
const DOMAIN_AND_PORT = BASE_URL.replace("http://", "");
cy.intercept("PUT", "/api/setting/site-url").as("url");
cy.visit("/admin/settings/general");
// Needed to strip down the protocol from URL to accomodate our UI (<select> PORT | <input> DOMAIN_AND_PORT)
cy.findByDisplayValue(DOMAIN_AND_PORT) // findByDisplayValue comes from @testing-library/cypress
.click()
.type("foo", { delay: 100 })
.blur();
cy.wait("@url").should(({ response }) => {
expect(response.statusCode).to.eq(500);
// Switching to regex match for assertions - the test was flaky because of the "typing" issue
// i.e. it sometimes doesn't type the whole string "foo", but only "oo".
// We only care that the `cause` is starting with "Invalid site URL"
expect(response.body.cause).to.match(/^Invalid site URL/);
});
// NOTE: This test is not concerned with HOW we style the error message - only that there is one.
// If we update UI in the future (for example: we show an error within a popup/modal), the test in current form could fail.
cy.log("Making sure we display an error message in UI");
// Same reasoning for regex as above
H.undoToast().contains(/^Invalid site URL/);
});
it("should save a setting", () => {
cy.intercept("PUT", "**/admin-email").as("saveSettings");
cy.visit("/admin/settings/general");
// aliases don't last past refreshes, so create a function to grab the input
// rather than aliasing it with .as()
const emailInput = () =>
cy
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
.contains(/Email Address for Help Requests/i)
.parent()
.parent()
.find("input");
// extremely ugly hack because nothing else worked
// for some reason, Cypress failed to clear this field quite often disrupting our CI
emailInput()
.click()
.clear()
.type("abc", { delay: 50 })
.clear()
.click()
.type("other.email@metabase.test")
.blur();
cy.wait("@saveSettings");
cy.visit("/admin/settings/general");
// after we refreshed, the field should still be "other.email"
emailInput().should("have.value", "other.email@metabase.test");
});
it("should check for working https before enabling a redirect", () => {
cy.visit("/admin/settings/general");
cy.intercept("GET", "**/api/health", "ok").as("httpsCheck");
cy.findByTestId("admin-layout-content").within(() => {
cy.contains(/Site Url/i);
cy.contains(/Redirect to HTTPS/i).should("not.exist");
// switch site url to use https
cy.findByTestId("site-url-setting")
.findByRole("textbox", { name: "input-prefix" })
.click();
});
H.popover().contains("https://").click();
cy.wait("@httpsCheck");
cy.findByTestId("admin-layout-content")
.contains("Redirect to HTTPS")
.parent()
.parent()
.contains("Disabled");
H.restore(); // avoid leaving https site url
});
it("should correctly apply the globalized date formats (metabase#11394) and update the formatting", () => {
cy.intercept("PUT", "**/custom-formatting").as("saveFormatting");
cy.request("PUT", `/api/field/${ORDERS.CREATED_AT}`, {
semantic_type: null,
});
cy.visit("/admin/settings/localization");
cy.findByTestId("date_style-formatting-setting")
.findByDisplayValue("January 31, 2018")
.click({ force: true });
H.popover().findByText("2018/1/31").click({ force: true });
cy.wait("@saveFormatting");
cy.findByTestId("date_style-formatting-setting").findByDisplayValue(
"2018/1/31",
);
cy.findByTestId("custom-formatting-setting")
.findByText("17:24 (24-hour clock)")
.click();
cy.wait("@saveFormatting");
cy.findByDisplayValue("HH:mm").should("be.checked");
H.openOrdersTable({ limit: 2 });
cy.findByTextEnsureVisible("Created At");
cy.get("[data-testid=cell-data]")
.should("contain", "Created At")
.and("contain", "2028/2/11, 21:40");
// Go back to the settings and reset the time formatting
cy.visit("/admin/settings/localization");
cy.findByTestId("custom-formatting-setting")
.findByText("5:24 PM (12-hour clock)")
.click();
cy.wait("@saveFormatting");
cy.findByDisplayValue("h:mm A").should("be.checked");
H.openOrdersTable({ limit: 2 });
cy.findByTextEnsureVisible("Created At");
cy.get("[data-testid=cell-data]").and("contain", "2028/2/11, 9:40 PM");
});
it("should show where to display the unit of currency (metabase#table-metadata-missing-38021 and update the formatting", () => {
// Set the semantic type of total to currency
cy.request("PUT", `/api/field/${ORDERS.TOTAL}`, {
semantic_type: "type/Currency",
});
H.DataModel.visit({
databaseId: SAMPLE_DB_ID,
schemaId: SAMPLE_DB_SCHEMA_ID,
tableId: ORDERS_ID,
fieldId: ORDERS.TOTAL,
});
H.DataModel.FieldSection.get().within(() => {
// Assert that this option now exists
cy.findByText("Where to display the unit of currency")
.scrollIntoView()
.should("be.visible");
cy.findByText("In every table cell").click();
});
// Open the orders table
H.openOrdersTable({ limit: 2 });
H.tableInteractiveBody().within(() => {
// Items in the total column should have a leading dollar sign
cy.findByText("$39.72");
cy.findByText("$117.03");
});
});
it("should search for and select a new timezone", () => {
cy.intercept("PUT", "**/report-timezone").as("reportTimezone");
cy.visit("/admin/settings/localization");
cy.findByRole("textbox", { name: /timezone/i })
.as("timezoneSelect")
.clear()
.type("Centr");
H.selectDropdown().findByText("US/Central").click();
cy.wait("@reportTimezone");
cy.get("@timezoneSelect").should("have.value", "US/Central");
});
it("'General' admin settings should handle setup via `MB_SITE_URL` environment variable (metabase#14900)", () => {
// 1. Get the array of ALL available settings
cy.request("GET", "/api/setting").then(({ body }) => {
// 2. Create a stubbed version of that array by passing modified "site-url" settings
const STUBBED_BODY = body.map((setting) => {
if (setting.key === "site-url") {
const STUBBED_SITE_URL = Object.assign({}, setting, {
is_env_setting: true,
value: null,
});
return STUBBED_SITE_URL;
}
return setting;
});
// 3. Stub the whole response
cy.intercept("GET", "/api/setting", (req) => {
req.reply({ body: STUBBED_BODY });
}).as("appSettings");
});
cy.visit("/admin/settings/general");
cy.wait("@appSettings");
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("We're a little lost...").should("not.exist");
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText(/Site name/i);
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText(/Site URL/i);
});
describe(" > slack settings", () => {
it("should present the form and display errors", () => {
cy.visit("/admin/settings/slack");
cy.findByRole("main").findByText("Create a Slack app and connect to it.");
cy.findByLabelText(/Slack Bot User OAuth Token/i).type("xoxb");
cy.button("Connect").click();
cy.findByRole("main").findByText(/invalid token/i);
});
});
});
describe("scenarios > admin > settings (OSS)", { tags: "@OSS" }, () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
});
it("should show the store link when running Metabase OSS", () => {
cy.visit("/admin/settings/general");
cy.findByLabelText("Navigation bar").findByLabelText("store icon");
});
});
describe("scenarios > admin > settings (EE)", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
H.activateToken("pro-self-hosted");
});
it("should hide the store link when running Metabase EE", () => {
cy.visit("/admin/settings/license");
cy.findByTestId("admin-layout-content")
.findByLabelText("store icon")
.should("not.exist");
});
});
describe("Cloud settings section", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
});
it("should be visible when running Metabase Cloud", () => {
// Setting to none will give us an instance where token-features.hosting is set to true
// Allowing us to pretend that we are a hosted instance (seems backwards though haha)
H.activateToken("pro-cloud");
cy.visit("/admin/settings");
cy.findByTestId("admin-layout-sidebar").findByText(/Cloud/i).click();
cy.findByTestId("admin-layout-content")
.findByText("Go to the Metabase Store")
.should("have.attr", "href", "https://test-store.metabase.com/");
});
it("should prompt us to migrate to cloud if we are not hosted", () => {
H.activateToken("pro-self-hosted");
cy.visit("/admin/settings/cloud");
cy.findAllByTestId("settings-sidebar-link")
.filter(":contains(Cloud)")
.should("have.descendants", ".Icon-gem")
.click();
cy.findByRole("heading", { name: "Migrate to Metabase Cloud" }).should(
"exist",
);
cy.button("Try for free").should("exist");
});
});
describe("scenarios > admin > settings > email settings", () => {
describe("self-hosted instance", () => {
beforeEach(() => {
cy.intercept("PUT", "api/email").as("smtpSaved");
H.restore();
cy.signInAsAdmin();
H.resetSnowplow();
H.enableTracking();
});
it("should be able to save and clear email settings", () => {
cy.visit("/admin/settings/email");
cy.findByTestId("cloud-smtp-connection-card").should("not.exist");
cy.findByTestId("self-hosted-smtp-connection-card")
.button("Configure")
.click();
H.expectUnstructuredSnowplowEvent({
event: "custom_smtp_setup_clicked",
event_detail: "self-hosted",
});
H.modal().within(() => {
// SMTP connection setup
cy.findByLabelText(/SMTP Host/i)
.type("localhost")
.blur();
cy.findByLabelText(/SMTP Port/i)
.type(SMTP_PORT)
.blur();
cy.findByLabelText(/SMTP Username/i)
.type("admin")
.blur();
cy.findByLabelText(/SMTP Password/i)
.type("admin")
.blur();
cy.button("Save changes").click();
});
cy.wait("@smtpSaved");
H.expectUnstructuredSnowplowEvent({
event: "custom_smtp_setup_success",
event_detail: "self-hosted",
});
// should show as active now
cy.findByTestId("self-hosted-smtp-connection-card")
.findByText("Active")
.should("be.visible");
// button should be different
cy.findByTestId("self-hosted-smtp-connection-card")
.button("Edit configuration")
.should("be.visible");
// Non SMTP-settings should save automatically
cy.findByLabelText("From Address")
.clear()
.type("mailer@metabase.test")
.blur();
cy.findByLabelText("From Name").type("Sender Name").blur();
cy.findByLabelText("Reply-To Address")
.type("reply-to@metabase.test")
.blur();
// Refresh page to confirm changes persist
cy.reload();
// validate additional settings
cy.findByDisplayValue("mailer@metabase.test");
cy.findByDisplayValue("Sender Name");
cy.findByDisplayValue("reply-to@metabase.test");
// validate SMTP connection settings
cy.findByTestId("self-hosted-smtp-connection-card")
.findByText("Edit configuration")
.click();
cy.findByDisplayValue("localhost");
cy.findByDisplayValue(SMTP_PORT);
cy.findAllByDisplayValue("admin");
// should not offer to save email changes when there aren't any (metabase#14749)
cy.button("Save changes").should("be.disabled");
// should be able to clear email settings
H.modal().button("Clear").click();
cy.reload();
cy.findByTestId("self-hosted-smtp-connection-card")
.findByText("Configure")
.click();
H.modal().within(() => {
cy.findByLabelText("SMTP Host").should("have.value", "");
cy.findByLabelText("SMTP Port").should("have.value", "");
cy.findByLabelText("SMTP Username").should("have.value", "");
cy.findByLabelText("SMTP Password").should("have.value", "");
});
H.expectNoBadSnowplowEvents();
});
it("should show an error if test email fails", () => {
// Reuse Email setup without relying on the previous test
cy.request("PUT", "/api/setting", {
"email-from-address": "admin@metabase.test",
"email-from-name": "Metabase Admin",
"email-reply-to": ["reply-to@metabase.test"],
"email-smtp-host": "localhost",
"email-smtp-password": null,
"email-smtp-port": "1234",
"email-smtp-security": "none",
"email-smtp-username": null,
});
cy.visit("/admin/settings/email");
cy.findByTestId("admin-layout-content").within(() => {
cy.button("Send test email").click();
cy.findByText(
"Couldn't connect to host, port: localhost, 1234; timeout -1",
);
});
});
it(
"should send a test email for a valid SMTP configuration",
{ tags: "@external" },
() => {
H.setupSMTP();
cy.visit("/admin/settings/email");
cy.button("Send test email").click();
H.undoToast().findByText("Email sent!").should("be.visible");
cy.request("GET", `http://localhost:${WEB_PORT}/email`).then(
({ body }) => {
const emailBody = body[0].text;
expect(emailBody).to.include("Your Metabase emails are working");
},
);
},
);
});
describe("starter instance", () => {
beforeEach(() => {
cy.intercept("GET", "/api/session/properties", (req) => {
req.continue((res) => {
// in an actual cloud starter instance this gets configured via env vars
res.body["email-configured?"] = true;
return res.body;
});
});
H.restore();
cy.signInAsAdmin();
H.activateToken("starter");
});
it("should not allow custom SMTP configuration", () => {
cy.visit("/admin/settings/email");
cy.findByTestId("self-hosted-smtp-connection-card").should("not.exist");
cy.findByTestId("cloud-smtp-connection-card").should("not.exist");
cy.button("Send test email").should("not.exist");
cy.findByTestId("admin-layout-content").within(() => {
cy.findByText("Whitelabel email notifications").should("be.visible");
cy.findByLabelText("From Address").should("be.disabled");
cy.findByText(
"Please set up a custom SMTP server to change this (Pro only)",
).should("be.visible");
});
});
});
describe("Pro-cloud instance", () => {
beforeEach(() => {
cy.intercept("DELETE", "api/ee/email/override").as("smtpCleared");
cy.intercept("PUT", "api/ee/email/override").as("smtpSaved");
H.restore();
cy.signInAsAdmin();
H.activateToken("pro-cloud");
// A real pro-cloud instance configures email via env vars; set the SMTP host so
// email-configured? is true (avoids a session/properties intercept that flakes on reload).
H.updateSetting("email-smtp-host", "smtp.example.test");
H.resetSnowplow();
H.enableTracking();
});
it("should be able to save and clear email settings", () => {
cy.visit("/admin/settings/email");
cy.findByTestId("self-hosted-smtp-connection-card").should("not.exist");
cy.findByTestId("cloud-smtp-connection-card")
.button("Set up a custom SMTP server")
.click();
H.expectUnstructuredSnowplowEvent({
event: "custom_smtp_setup_clicked",
event_detail: "cloud",
});
H.modal().within(() => {
cy.findByLabelText(/SMTP Host/i)
.type("localhost")
.blur();
cy.findByText(/465/i).click();
cy.findByText(/SSL/i).click();
cy.findByDisplayValue(/465/i).should("have.attr", "checked");
cy.findByDisplayValue(/SSL/i).should("have.attr", "checked");
cy.findByLabelText(/SMTP Username/i)
.type("admin")
.blur();
cy.findByLabelText(/SMTP Password/i)
.type("admin")
.blur();
cy.button("Save changes").click();
});
cy.wait("@smtpSaved");
H.expectUnstructuredSnowplowEvent({
event: "custom_smtp_setup_success",
event_detail: "cloud",
});
cy.findByTestId("cloud-smtp-connection-card").within(() => {
// Button text should change
cy.button("Edit settings");
// Custom server should be auto-enabled
cy.findByLabelText("Custom SMTP Server").should("be.checked");
});
cy.findByLabelText("From Address")
.clear()
.type("mailer@metabase.test")
.blur();
cy.findByLabelText("From Name").type("Sender Name").blur();
cy.findByLabelText("Reply-To Address")
.type("reply-to@metabase.test")
.blur();
// Refresh page to confirm changes persist
cy.reload();
// validate additional settings
cy.findByDisplayValue("mailer@metabase.test");
cy.findByDisplayValue("Sender Name");
cy.findByDisplayValue("reply-to@metabase.test");
// validate SMTP connection settings
cy.findByTestId("cloud-smtp-connection-card")
.findByText("Edit settings")
.click();
H.modal().within(() => {
cy.findByDisplayValue("localhost");
cy.findAllByDisplayValue("admin");
cy.button("Save changes").should("be.disabled");
cy.button("Clear").click();
cy.wait("@smtpCleared");
cy.findByLabelText("SMTP Host").should("have.value", "");
cy.findByDisplayValue(/465/i).should("have.attr", "checked");
cy.findByDisplayValue(/SSL/i).should("have.attr", "checked");
cy.findByLabelText("SMTP Username").should("have.value", "");
cy.findByLabelText("SMTP Password").should("have.value", "");
cy.findByRole("button", { name: "Close" }).click();
});
// Button text should revert back
cy.findByTestId("cloud-smtp-connection-card").findByText(
"Set up a custom SMTP server",
);
H.expectNoBadSnowplowEvents();
});
});
});
describe("scenarios > admin > license and billing", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
});
describe("store info", () => {
it("should show the user a link to the store for an unlincensed enterprise instance", () => {
cy.visit("/admin/settings/license");
cy.findByTestId("license-and-billing-content")
.findByText("Go to the Metabase Store")
.should("have.prop", "tagName", "A");
});
});
});
describe("scenarios > admin > localization", () => {
function setFirstWeekDayTo(day) {
H.updateSetting("start-of-week", day.toLowerCase());
}
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
setFirstWeekDayTo("monday");
});
it("should correctly apply start of the week to a bar chart (metabase#13516)", () => {
// programatically create and save a question based on Orders table
// filter: created before June 1st, 2025
// summarize: Count by CreatedAt: Week
H.createQuestion(
{
name: "Orders created before June 1st 2025",
query: {
"source-table": ORDERS_ID,
aggregation: [["count"]],
breakout: [["field", ORDERS.CREATED_AT, { "temporal-unit": "week" }]],
filter: ["<", ["field", ORDERS.CREATED_AT, null], "2025-06-01"],
},
display: "bar",
},
{ visitQuestion: true },
);
cy.log("Assert the dates on the X axis");
// it's hard and tricky to invoke hover in Cypress, especially in our graphs
// that's why we have to assert on the x-axis, instead of a popover that shows on a dot hover
// April 28 is Monday in year 2025. Expect this to break when we shift years in the Sample Database.
H.echartsContainer().find("text").should("contain", "April 28, 2025");
});
it("should display days on X-axis correctly when grouped by 'Day of the Week' (metabase#13604)", () => {
H.createQuestion({
name: "13604",
query: {
"source-table": ORDERS_ID,
aggregation: [["count"]],
breakout: [
["field", ORDERS.CREATED_AT, { "temporal-unit": "day-of-week" }],
],
filter: [
"between",
["field", ORDERS.CREATED_AT, null],
"2026-03-02", // Monday
"2026-03-03", // Tuesday
],
},
display: "bar",
visualization_settings: {
"graph.dimensions": ["CREATED_AT"],
"graph.metrics": ["count"],
"graph.x_axis.scale": "ordinal",
},
});
cy.visit("/collection/root");
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("13604").click();
cy.log("Reported failing on v0.37.0.2 and labeled as `.Regression`");
H.echartsContainer()
.get("text")
.contains(/sunday/i)
.should("not.exist");
H.echartsContainer()
.get("text")
.contains(/monday/i);
H.echartsContainer()
.get("text")
.contains(/tuesday/i);
});
// HANDLE WITH CARE!
// This test is extremely tricky and fragile because it needs to test for the "past X weeks" to check if week starts on Sunday or Monday.
// As the time goes by we're risking that past X weeks don't yield any result when applied to the sample database.
// For that reason I've chosen the past 220 weeks (mid-October 2022). This should give us 3+ years to run this test without updates.
// TODO:
// - Keep an eye on this test in CI and update the week range as needed.
it("should respect start of the week in SQL questions with filters (metabase#14294)", () => {
H.createNativeQuestion(
{
name: "14294",
native: {
query:
"select ID, CREATED_AT, dayname(CREATED_AT) as CREATED_AT_DAY\nfrom ORDERS \n[[where {{date_range}}]]\norder by CREATED_AT",
"template-tags": {
date_range: {
id: "93961154-c3d5-7c93-7b59-f4e494fda499",
name: "date_range",
"display-name": "Date range",
type: "dimension",
dimension: ["field", ORDERS.CREATED_AT, null],
"widget-type": "date/all-options",
default: "past220weeks",
required: true,
},
},
},
},
{ visitQuestion: true },
);
H.tableInteractive().as("resultTable");
cy.get("@resultTable").within(() => {
// The third cell in the first row (CREATED_AT_DAY)
cy.get("[data-testid=cell-data]").eq(2).should("not.contain", "Sunday");
});
});
it("should use currency settings for number columns with style set to currency (metabase#10787)", () => {
cy.visit("/admin/settings/localization");
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Unit of currency");
cy.findByDisplayValue("US Dollar").click();
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Euro").click();
H.undoToast().findByText("Changes saved").should("be.visible");
H.visitQuestionAdhoc({
display: "scalar",
dataset_query: {
type: "native",
native: {
query: "SELECT 10 as A",
"template-tags": {},
},
database: SAMPLE_DB_ID,
},
visualization_settings: {
column_settings: {
'["name","A"]': {
number_style: "currency",
},
},
},
});
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("€10.00");
});
it("should use fix up clj unit testsdate and time styling settings in the date filter widget (metabase#9151, metabase#12472)", () => {
cy.intercept("POST", "/api/dataset").as("dataset");
cy.intercept("PUT", "/api/setting/custom-formatting").as(
"updateFormatting",
);
cy.visit("/admin/settings/localization");
cy.findByTestId("custom-formatting-setting").within(() => {
// update the date style setting to YYYY/MM/DD
cy.findByDisplayValue("January 31, 2018").click();
});
H.popover().findByText("2018/1/31").click();
cy.wait("@updateFormatting");
cy.findByTestId("custom-formatting-setting").within(() => {
cy.findByTestId("date_style-formatting-setting").findByDisplayValue(
"2018/1/31",
);
// update the time style setting to 24 hour
cy.findByText("17:24 (24-hour clock)").click();
cy.wait("@updateFormatting");
cy.findByDisplayValue("HH:mm").should("be.checked");
});
H.visitQuestion(ORDERS_QUESTION_ID);
// create a date filter and set it to the 'On' view to see a specific date
H.tableHeaderClick("Created At");
H.popover().within(() => {
cy.findByText("Filter by this column").click();
cy.findByText("Fixed date range…").click();
cy.findByText("On").click();
// ensure the date picker is ready
cy.findByTextEnsureVisible("Add time");
cy.findByTextEnsureVisible("Add filter");
// update the date input in the widget
cy.findByLabelText("Date").clear().type("2027/5/15").blur();
// add a time to the date
cy.findByText("Add time").click();
cy.findByLabelText("Time").clear().type("19:56");
// apply the date filter
cy.button("Add filter").click();
});
cy.wait("@dataset");
cy.findByTestId("loading-indicator").should("not.exist");
// verify that the correct row is displayed
H.tableInteractive().within(() => {
cy.findByText("2027/5/15, 19:56");
cy.findByText("127.52");
});
});
});
describe("scenarios > admin > settings > map settings", () => {
beforeEach(() => {
H.restore();
cy.intercept("GET", "/api/geojson*").as("getGeoJson");
cy.signInAsAdmin();
});
it("should be able to load and save a custom map", () => {
cy.visit("/admin/settings/maps");
cy.button("Add a map").click();
cy.findByPlaceholderText("e.g. United Kingdom, Brazil, Mars").type(
"Test Map",
);
cy.findByPlaceholderText(
"Like https://my-mb-server.com/maps/my-map.json",
).type(
"https://raw.githubusercontent.com/metabase/metabase/master/resources/frontend_client/app/assets/geojson/world.json",
);
cy.button("Load").click();
cy.wait("@getGeoJson");
cy.findByTestId("map-region-key-select").click();
H.selectDropdown().contains("NAME").click();
cy.findByTestId("map-region-name-select").click();
H.selectDropdown().contains("NAME").click();
cy.button("Add map").click();
cy.findByTestId("admin-layout-content").within(() => {
cy.contains("NAME").should("not.exist");
cy.contains("Test Map");
});
});
it("should be able to load a custom map even if a name has not been added yet (#14635)", () => {
cy.intercept("GET", "/api/geojson*").as("load");
cy.visit("/admin/settings/maps");
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Add a map").click();
cy.findByPlaceholderText(
"Like https://my-mb-server.com/maps/my-map.json",
).type(
"https://raw.githubusercontent.com/metabase/metabase/master/resources/frontend_client/app/assets/geojson/world.json",
);
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Load").click();
cy.wait("@load").then((interception) => {
expect(interception.response.statusCode).to.eq(200);
});
});
});
// Ensure the webhook tester docker container is running
// docker run -p 127.0.0.1:9080:8080/tcp tarampampam/webhook-tester:1.1.0 serve --create-session 00000000-0000-0000-0000-000000000000
describe("notifications", { tags: "@external" }, () => {
beforeEach(() => {
H.resetWebhookTester();
H.restore();
cy.signInAsAdmin();
cy.request({
failOnStatusCode: false,
url: `${H.WEBHOOK_TEST_HOST}/api/session/${H.WEBHOOK_TEST_SESSION_ID}/requests`,
method: "DELETE",
}).then((response) => {
cy.log("Deleted requests.");
});
});
describe("Auth", () => {
const COMMON_FIELDS = [
{
label: "Webhook URL",
value: H.WEBHOOK_TEST_URL,
},
{
label: "Give it a name",
value: "Awesome Hook",
},
{
label: "Description",
value: "The best hook ever",
},
];
// 3 Auth methods that add to the request. Unfortunately the webhook tester docker image doesn't support
// query params at the moment. https://github.com/tarampampam/webhook-tester/issues/389
const AUTH_METHODS = [
{
display: "Basic",
name: "Basic",
populateFields: () => {
cy.findByLabelText("Username").type("test@metabase.com");
cy.findByLabelText("Password").type("password");
},
validate: () => {
cy.findByText("Authorization").should("exist");
cy.findByText("Basic dGVzdEBtZXRhYmFzZS5jb206cGFzc3dvcmQ=").should(
"exist",
);
},
},
{
display: "Bearer",
name: "Bearer",
populateFields: () => {
cy.findByLabelText("Bearer token").type("my-secret-token");
},
validate: () => {
cy.findByText("Authorization").should("exist");
cy.findByText("Bearer my-secret-token").should("exist");
},
},
{
display: "API Key - Header",
name: "API Key",
populateFields: () => {
cy.findByLabelText("Key").type("Mb_foo");
cy.findByLabelText("Value").type("mb-bar");
},
validate: () => {
cy.findByText("Mb_foo").should("exist");
cy.findByText("mb-bar").should("exist");
},
},
];
AUTH_METHODS.forEach((auth) => {
it(`${auth.display} Auth`, () => {
cy.visit("/admin/settings/webhooks");
cy.findByRole("heading", { name: "Add a webhook" }).click();
H.modal().within(() => {
COMMON_FIELDS.forEach((field) => {
cy.findByLabelText(field.label).type(field.value);
});
cy.findByRole("radio", { name: auth.name }).click({ force: true });
auth.populateFields();
cy.button("Send a test").click();
cy.button("Success").should("exist");
cy.button("Create destination").click();
});
cy.findByRole("heading", { name: "Awesome Hook" }).should("exist");
cy.visit(H.WEBHOOK_TEST_DASHBOARD);
cy.findByRole("heading", { name: /Requests 1/ }).should("exist");
auth.validate();
});
});
});
it("Should allow you to create and edit Notifications", () => {
cy.visit("/admin/settings/webhooks");
cy.findByRole("heading", { name: "Add a webhook" }).click();
H.modal().within(() => {
cy.findByRole("heading", { name: "New webhook destination" }).should(
"exist",
);
cy.findByLabelText("Give it a name").type("Awesome Hook");
cy.findByLabelText("Description").type("The best hook ever");
cy.log("should show error responses when testing");
cy.findByLabelText("Webhook URL").clear().type(H.WEBHOOK_TEST_HOST);
cy.button("Send a test").click();
cy.findByText("Test response").should("exist");
cy.findByTestId("notification-test-response").should(
"contain.text",
"request-status",
);
cy.findByTestId("notification-test-response").should(
"contain.text",
"request-body",
);
cy.findByLabelText("Webhook URL").clear().type(H.WEBHOOK_TEST_URL);
cy.button("Send a test").click();
cy.findByText("Test response").should("not.exist");
cy.button("Create destination").click();
});
cy.findByRole("button", { name: /Add another/ }).should("exist");
cy.findByRole("heading", { name: "Awesome Hook" }).click();
H.modal().within(() => {
cy.findByRole("heading", { name: "Edit this webhook" }).should("exist");
cy.findByLabelText("Give it a name").clear().type("Updated Hook");
cy.button("Save changes").click();
});
cy.findByRole("heading", { name: "Updated Hook" }).click();
H.modal()
.button(/Delete this destination/)
.click();
cy.findByRole("heading", { name: "Add a webhook" }).should("exist");
});
});
describe("admin > settings > updates", () => {
// we're mocking this so it can be stable for tests
const versionInfo = {
latest: {
version: "v1.56.4",
released: "2022-10-14",
rollout: 60,
highlights: ["New latest feature", "Another new feature"],
},
beta: {
version: "v1.56.75.3",
released: "2022-10-15",
rollout: 70,
highlights: ["New beta feature", "Another new feature"],
},
nightly: {
version: "v1.56.75.2",
released: "2022-10-16",
rollout: 80,
highlights: ["New nightly feature", "Another new feature"],
},
older: [
{
version: "v1.56.1",
released: "2022-10-10",
rollout: 100,
highlights: ["Some old feature", "Another old feature"],
},
],
};
const currentVersion = "v1.55.2";
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
cy.intercept("GET", "/api/session/properties", (req) => {
req.continue((res) => {
res.body.version.tag = currentVersion;
return res.body;
});
});
cy.intercept("GET", "/api/setting/version-info", (req) => {
req.reply(versionInfo);
});
cy.visit("/admin/settings/updates");
});
const assertIframeLoaded = (testId) => {
cy.findByTestId(testId).should("be.visible");
cy.findByTestId(testId).should(($iframe) => {
const body = $iframe.contents().find("body");
expect(body).to.exist;
});
};
it("should show the updates page", () => {
cy.findByTestId("check-for-updates-setting")
.findByText("Check for updates")
.should("be.visible");
cy.findByTestId("settings-updates").within(() => {
cy.findByText(
"Metabase 1.56.4 is available. You're running 1.55.2.",
).should("be.visible");
cy.findByText("Changelog").should("be.visible").click();
assertIframeLoaded("changelog-iframe");
cy.findByText("What's new").should("be.visible").click();
assertIframeLoaded("releases-iframe");
});
});
});
describe("admin > settings > nav", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
cy.visit("/admin/settings");
});
it("should navigate properly", () => {
// sadly we can't test this in unit tests with the current state of react-router and redux 😭
cy.log("clicking sidebar nav links should navigate there");
cy.url().should("include", "/admin/settings/general");
cy.findByTestId("admin-layout-sidebar").findByText(/email/i).click();
cy.findByTestId("admin-layout-content").findByText(/email/i);
cy.url().should("include", "/admin/settings/email");
cy.log(
"clicking folders should expand and collapse them, but not navigate",
);
cy.findByTestId("admin-layout-sidebar")
.findByText(/api keys/i)
.should("not.be.visible");
cy.findByTestId("admin-layout-sidebar")
.findByText(/authentication/i)
.click();
cy.findByTestId("admin-layout-sidebar")
.findByText(/api keys/i)
.should("be.visible");
// still on email page
cy.findByTestId("admin-layout-content").findByText(/email/i);
cy.url().should("include", "/admin/settings/email");
// navigate to sub-item
cy.findByTestId("admin-layout-sidebar")
.findByText(/api keys/i)
.click();
cy.findByTestId("admin-layout-content").findByText(/No API keys yet/i);
cy.url().should("include", "/admin/settings/authentication/api-keys");
});
});