client/cypress/integration/query/parameter_spec.js (504 lines of code) (raw):
import { dragParam } from "../../support/parameters";
function openAndSearchAntdDropdown(testId, paramOption) {
cy.getByTestId(testId)
.find(".ant-select-selection-search-input")
.type(paramOption, { force: true });
}
describe("Parameter", () => {
const expectDirtyStateChange = edit => {
cy.getByTestId("ParameterName-test-parameter")
.find(".parameter-input")
.should($el => {
assert.isUndefined($el.data("dirty"));
});
edit();
cy.getByTestId("ParameterName-test-parameter")
.find(".parameter-input")
.should($el => {
assert.isTrue($el.data("dirty"));
});
};
beforeEach(() => {
cy.login();
});
describe("Text Parameter", () => {
beforeEach(() => {
const queryData = {
name: "Text Parameter",
query: "SELECT '{{test-parameter}}' AS parameter",
options: {
parameters: [{ name: "test-parameter", title: "Test Parameter", type: "text" }],
},
};
cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}`));
});
it("updates the results after clicking Apply", () => {
cy.getByTestId("ParameterName-test-parameter")
.find("input")
.type("Redash");
cy.getByTestId("ParameterApplyButton").click();
cy.getByTestId("TableVisualization").should("contain", "Redash");
});
it("sets dirty state when edited", () => {
expectDirtyStateChange(() => {
cy.getByTestId("ParameterName-test-parameter")
.find("input")
.type("Redash");
});
});
});
describe("Number Parameter", () => {
beforeEach(() => {
const queryData = {
name: "Number Parameter",
query: "SELECT '{{test-parameter}}' AS parameter",
options: {
parameters: [{ name: "test-parameter", title: "Test Parameter", type: "number" }],
},
};
cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}`));
});
it("updates the results after clicking Apply", () => {
cy.getByTestId("ParameterName-test-parameter")
.find("input")
.type("{selectall}42");
cy.getByTestId("ParameterApplyButton").click();
cy.getByTestId("TableVisualization").should("contain", 42);
cy.getByTestId("ParameterName-test-parameter")
.find("input")
.type("{selectall}31415");
cy.getByTestId("ParameterApplyButton").click();
cy.getByTestId("TableVisualization").should("contain", 31415);
});
it("sets dirty state when edited", () => {
expectDirtyStateChange(() => {
cy.getByTestId("ParameterName-test-parameter")
.find("input")
.type("{selectall}42");
});
});
});
describe("Dropdown Parameter", () => {
beforeEach(() => {
const queryData = {
name: "Dropdown Parameter",
query: "SELECT '{{test-parameter}}' AS parameter",
options: {
parameters: [
{ name: "test-parameter", title: "Test Parameter", type: "enum", enumOptions: "value1\nvalue2\nvalue3" },
],
},
};
cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}/source`));
});
it("updates the results after selecting a value", () => {
openAndSearchAntdDropdown("ParameterName-test-parameter", "value2"); // asserts option filter prop
// only the filtered option should be on the DOM
cy.get(".ant-select-item-option")
.should("have.length", 1)
.and("contain", "value2")
.click();
cy.getByTestId("ParameterApplyButton").click();
// ensure that query is being executed
cy.getByTestId("QueryExecutionStatus").should("exist");
cy.getByTestId("TableVisualization").should("contain", "value2");
});
it("supports multi-selection", () => {
cy.clickThrough(`
ParameterSettings-test-parameter
AllowMultipleValuesCheckbox
QuotationSelect
DoubleQuotationMarkOption
SaveParameterSettings
`);
cy.getByTestId("ParameterName-test-parameter")
.find(".ant-select-selection-search")
.click();
// select all unselected options
cy.get(".ant-select-item-option").each($option => {
if (!$option.hasClass("ant-select-item-option-selected")) {
cy.wrap($option).click();
}
});
cy.getByTestId("QueryEditor").click(); // just to close the select menu
cy.getByTestId("ParameterApplyButton").click();
cy.getByTestId("TableVisualization").should("contain", '"value1","value2","value3"');
});
it("sets dirty state when edited", () => {
expectDirtyStateChange(() => {
cy.getByTestId("ParameterName-test-parameter")
.find(".ant-select")
.click();
cy.contains(".ant-select-item-option", "value2").click();
});
});
});
describe("Query Based Dropdown Parameter", () => {
context("based on a query with no results", () => {
beforeEach(() => {
const dropdownQueryData = {
name: "Dropdown Query",
query: "",
};
cy.createQuery(dropdownQueryData, true).then(dropdownQuery => {
const queryData = {
name: "Query Based Dropdown Parameter",
query: "SELECT '{{test-parameter}}' AS parameter",
options: {
parameters: [
{ name: "test-parameter", title: "Test Parameter", type: "query", queryId: dropdownQuery.id },
],
},
};
cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}/source`));
});
});
it("should show a 'No options available' message when you click", () => {
cy.getByTestId("ParameterName-test-parameter")
.find(".ant-select:not(.ant-select-disabled) .ant-select-selector")
.click();
cy.contains(".ant-select-item-empty", "No options available");
});
});
context("based on a query with 3 results", () => {
beforeEach(() => {
const dropdownQueryData = {
name: "Dropdown Query",
query: `SELECT 'value1' AS name, 1 AS value UNION ALL
SELECT 'value2' AS name, 2 AS value UNION ALL
SELECT 'value3' AS name, 3 AS value`,
};
cy.createQuery(dropdownQueryData, true).then(dropdownQuery => {
const queryData = {
name: "Query Based Dropdown Parameter",
query: "SELECT '{{test-parameter}}' AS parameter",
options: {
parameters: [
{ name: "test-parameter", title: "Test Parameter", type: "query", queryId: dropdownQuery.id },
],
},
};
cy.visit(`/queries/${dropdownQuery.id}`);
cy.getByTestId("ExecuteButton").click();
cy.getByTestId("TableVisualization")
.should("contain", "value1")
.and("contain", "value2")
.and("contain", "value3");
cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}/source`));
});
});
it("updates the results after selecting a value", () => {
openAndSearchAntdDropdown("ParameterName-test-parameter", "value2"); // asserts option filter prop
// only the filtered option should be on the DOM
cy.get(".ant-select-item-option")
.should("have.length", 1)
.and("contain", "value2")
.click();
cy.getByTestId("ParameterApplyButton").click();
// ensure that query is being executed
cy.getByTestId("QueryExecutionStatus").should("exist");
cy.getByTestId("TableVisualization").should("contain", "2");
});
it("supports multi-selection", () => {
cy.clickThrough(`
ParameterSettings-test-parameter
AllowMultipleValuesCheckbox
QuotationSelect
DoubleQuotationMarkOption
SaveParameterSettings
`);
cy.getByTestId("ParameterName-test-parameter")
.find(".ant-select")
.click();
// make sure all options are unselected and select all
cy.get(".ant-select-item-option").each($option => {
expect($option).not.to.have.class("ant-select-dropdown-menu-item-selected");
cy.wrap($option).click();
});
cy.getByTestId("QueryEditor").click(); // just to close the select menu
cy.getByTestId("ParameterApplyButton").click();
cy.getByTestId("TableVisualization").should("contain", '"1","2","3"');
});
});
});
const selectCalendarDate = date => {
cy.getByTestId("ParameterName-test-parameter")
.find("input")
.click();
cy.get(".ant-picker-panel")
.contains(".ant-picker-cell-inner", date)
.click();
};
describe("Date Parameter", () => {
beforeEach(() => {
const queryData = {
name: "Date Parameter",
query: "SELECT '{{test-parameter}}' AS parameter",
options: {
parameters: [{ name: "test-parameter", title: "Test Parameter", type: "date", value: null }],
},
};
const now = new Date();
now.setDate(1);
cy.wrap(now.getTime()).as("now");
cy.clock(now.getTime(), ["Date"]);
cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}`));
});
afterEach(() => {
cy.clock().then(clock => clock.restore());
});
it("updates the results after selecting a date", function() {
selectCalendarDate("15");
cy.getByTestId("ParameterApplyButton").click();
cy.getByTestId("TableVisualization").should("contain", Cypress.moment(this.now).format("15/MM/YY"));
});
it("allows picking a dynamic date", function() {
cy.getByTestId("DynamicButton").click();
cy.getByTestId("DynamicButtonMenu")
.contains("Today/Now")
.click();
cy.getByTestId("ParameterApplyButton").click();
cy.getByTestId("TableVisualization").should("contain", Cypress.moment(this.now).format("DD/MM/YY"));
});
it("sets dirty state when edited", () => {
expectDirtyStateChange(() => selectCalendarDate("15"));
});
});
describe("Date and Time Parameter", () => {
beforeEach(() => {
const queryData = {
name: "Date and Time Parameter",
query: "SELECT '{{test-parameter}}' AS parameter",
options: {
parameters: [{ name: "test-parameter", title: "Test Parameter", type: "datetime-local", value: null }],
},
};
const now = new Date();
now.setDate(1);
cy.wrap(now.getTime()).as("now");
cy.clock(now.getTime(), ["Date"]);
cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}`));
});
afterEach(() => {
cy.clock().then(clock => clock.restore());
});
it("updates the results after selecting a date and clicking in ok", function() {
cy.getByTestId("ParameterName-test-parameter")
.find("input")
.as("Input")
.click();
selectCalendarDate("15");
cy.get(".ant-picker-ok button").click();
cy.getByTestId("ParameterApplyButton").click();
cy.getByTestId("TableVisualization").should("contain", Cypress.moment(this.now).format("YYYY-MM-15 HH:mm"));
});
it("shows the current datetime after clicking in Now", function() {
cy.getByTestId("ParameterName-test-parameter")
.find("input")
.as("Input")
.click();
cy.get(".ant-picker-panel")
.contains("Now")
.click();
cy.getByTestId("ParameterApplyButton").click();
cy.getByTestId("TableVisualization").should("contain", Cypress.moment(this.now).format("YYYY-MM-DD HH:mm"));
});
it("allows picking a dynamic date", function() {
cy.getByTestId("DynamicButton").click();
cy.getByTestId("DynamicButtonMenu")
.contains("Today/Now")
.click();
cy.getByTestId("ParameterApplyButton").click();
cy.getByTestId("TableVisualization").should("contain", Cypress.moment(this.now).format("YYYY-MM-DD HH:mm"));
});
it("sets dirty state when edited", () => {
expectDirtyStateChange(() => {
cy.getByTestId("ParameterName-test-parameter")
.find("input")
.click();
cy.get(".ant-picker-panel")
.contains("Now")
.click();
});
});
});
describe("Date Range Parameter", () => {
const selectCalendarDateRange = (startDate, endDate) => {
cy.getByTestId("ParameterName-test-parameter")
.find("input")
.first()
.click();
cy.get(".ant-picker-panel")
.contains(".ant-picker-cell-inner", startDate)
.click();
cy.get(".ant-picker-panel")
.contains(".ant-picker-cell-inner", endDate)
.click();
};
beforeEach(() => {
const queryData = {
name: "Date Range Parameter",
query: "SELECT '{{test-parameter.start}} - {{test-parameter.end}}' AS parameter",
options: {
parameters: [{ name: "test-parameter", title: "Test Parameter", type: "date-range" }],
},
};
const now = new Date();
now.setDate(1);
cy.wrap(now.getTime()).as("now");
cy.clock(now.getTime(), ["Date"]);
cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}/source`));
});
afterEach(() => {
cy.clock().then(clock => clock.restore());
});
it("updates the results after selecting a date range", function() {
selectCalendarDateRange("15", "20");
cy.getByTestId("ParameterApplyButton").click();
const now = Cypress.moment(this.now);
cy.getByTestId("TableVisualization").should(
"contain",
now.format("YYYY-MM-15") + " - " + now.format("YYYY-MM-20")
);
});
it("allows picking a dynamic date range", function() {
cy.getByTestId("DynamicButton").click();
cy.getByTestId("DynamicButtonMenu")
.contains("Last month")
.click();
cy.getByTestId("ParameterApplyButton").click();
const lastMonth = Cypress.moment(this.now).subtract(1, "month");
cy.getByTestId("TableVisualization").should(
"contain",
lastMonth.startOf("month").format("YYYY-MM-DD") + " - " + lastMonth.endOf("month").format("YYYY-MM-DD")
);
});
it("sets dirty state when edited", () => {
expectDirtyStateChange(() => selectCalendarDateRange("15", "20"));
});
});
describe("Apply Changes", () => {
const expectAppliedChanges = apply => {
cy.getByTestId("ParameterName-test-parameter-1")
.find("input")
.as("Input")
.type("Redash");
cy.getByTestId("ParameterName-test-parameter-2")
.find("input")
.type("Redash");
cy.location("search").should("not.contain", "Redash");
cy.server();
cy.route("POST", "**/api/queries/*/results").as("Results");
apply(cy.get("@Input"));
cy.location("search").should("contain", "Redash");
cy.wait("@Results");
};
beforeEach(() => {
const queryData = {
name: "Testing Apply Button",
query: "SELECT '{{test-parameter-1}} {{ test-parameter-2 }}'",
options: {
parameters: [
{ name: "test-parameter-1", title: "Test Parameter 1", type: "text" },
{ name: "test-parameter-2", title: "Test Parameter 2", type: "text" },
],
},
};
cy.server();
cy.route("GET", "**/api/data_sources/*/schema").as("Schema");
cy.createQuery(queryData, false)
.then(({ id }) => cy.visit(`/queries/${id}/source`))
.then(() => cy.wait("@Schema"));
});
it("shows and hides according to parameter dirty state", () => {
cy.getByTestId("ParameterApplyButton").should("not.be", "visible");
cy.getByTestId("ParameterName-test-parameter-1")
.find("input")
.as("Param")
.type("Redash");
cy.getByTestId("ParameterApplyButton").should("be.visible");
cy.get("@Param").clear();
cy.getByTestId("ParameterApplyButton").should("not.be", "visible");
});
it("updates dirty counter", () => {
cy.getByTestId("ParameterName-test-parameter-1")
.find("input")
.type("Redash");
cy.getByTestId("ParameterApplyButton")
.find(".ant-badge-count p.current")
.should("contain", "1");
cy.getByTestId("ParameterName-test-parameter-2")
.find("input")
.type("Redash");
cy.getByTestId("ParameterApplyButton")
.find(".ant-badge-count p.current")
.should("contain", "2");
});
it('applies changes from "Apply Changes" button', () => {
expectAppliedChanges(() => {
cy.getByTestId("ParameterApplyButton").click();
});
});
it('applies changes from "alt+enter" keyboard shortcut', () => {
expectAppliedChanges(input => {
input.type("{alt}{enter}");
});
});
it('disables "Execute" button', () => {
cy.getByTestId("ParameterName-test-parameter-1")
.find("input")
.as("Input")
.type("Redash");
cy.getByTestId("ExecuteButton").should("be.disabled");
cy.get("@Input").clear();
cy.getByTestId("ExecuteButton").should("be.enabled");
});
});
describe("Draggable", () => {
beforeEach(() => {
const queryData = {
name: "Draggable",
query: "SELECT '{{param1}}', '{{param2}}', '{{param3}}', '{{param4}}' AS parameter",
options: {
parameters: [
{ name: "param1", title: "Parameter 1", type: "text" },
{ name: "param2", title: "Parameter 2", type: "text" },
{ name: "param3", title: "Parameter 3", type: "text" },
{ name: "param4", title: "Parameter 4", type: "text" },
],
},
};
cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}/source`));
cy.get(".parameter-block")
.first()
.invoke("width")
.as("paramWidth");
cy.get("body").type("{alt}D"); // hide schema browser
});
it("is possible to rearrange parameters", function() {
cy.server();
cy.route("POST", "**/api/queries/*").as("QuerySave");
dragParam("param1", this.paramWidth, 1);
cy.wait("@QuerySave");
dragParam("param4", -this.paramWidth, 1);
cy.wait("@QuerySave");
cy.reload();
const expectedOrder = ["Parameter 2", "Parameter 1", "Parameter 4", "Parameter 3"];
cy.get(".parameter-container label").each(($label, index) => expect($label).to.have.text(expectedOrder[index]));
});
});
describe("Parameter Settings", () => {
beforeEach(() => {
const queryData = {
name: "Draggable",
query: "SELECT '{{parameter}}' AS parameter",
options: {
parameters: [{ name: "parameter", title: "Parameter", type: "text" }],
},
};
cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}/source`));
cy.getByTestId("ParameterSettings-parameter").click();
});
it("changes the parameter title", () => {
cy.getByTestId("ParameterTitleInput").type("{selectall}New Parameter Name");
cy.getByTestId("SaveParameterSettings").click();
cy.contains("Query saved");
cy.reload();
cy.getByTestId("ParameterName-parameter").contains("label", "New Parameter Name");
});
});
});