Skip to content
Snippets Groups Projects
Commit 7aece734 authored by Ariya Hidayat's avatar Ariya Hidayat
Browse files

Custom expression: new function ISNULL() and ISEMPTY() (#14866)

These new functions are to support MBQL's is-null and is-empty.
parent 5c154335
No related branches found
No related tags found
No related merge requests found
......@@ -68,6 +68,8 @@ This would return rows where `Created At` is between January 1, 2020 and March 3
| Exp | `exp(column)` | Returns Euler's number, e, raised to the power of the supplied number. | `exp([Interest Months])` |
| Floor | `floor(column)` | Rounds a decimal number down. | `floor([Price])` |
| Interval | `interval(column, number, text)` | Checks a date column's values to see if they're within the relative range. | `interval([Created At], -1, "month")` |
| IsEmpty | `isempty(column)` | Returns true if the column is empty. | `isempty([Discount])` |
| IsNull | `isnull(column)` | Returns true if the column is null. | `isnull([Tax])` |
| Left trim | `ltrim(text)` | Removes leading whitespace from a string of text. | `ltrim([Comment])` |
| Length | `length(text)` | Returns the number of characters in text. | `length([Comment])` |
| Log | `log(column)` | Returns the base 10 log of the number. | `log([Value])` |
......
......@@ -224,6 +224,16 @@ export const MBQL_CLAUSES = {
type: "boolean",
args: ["expression", "number", "string"],
},
"is-null": {
displayName: `isnull`,
type: "boolean",
args: ["expression"],
},
"is-empty": {
displayName: `isempty`,
type: "boolean",
args: ["expression"],
},
// other expression functions
coalesce: {
displayName: `coalesce`,
......@@ -380,6 +390,8 @@ export const EXPRESSION_FUNCTIONS = new Set([
"starts-with",
"between",
"time-interval",
"is-null",
"is-empty",
// other
"coalesce",
]);
......
......@@ -461,6 +461,30 @@ const helperTextStrings = [
},
],
},
{
name: "is-null",
structure: "isnull(" + t`column` + ")",
description: t`Checks if a column is null`,
example: "isnull([" + t`Discount` + '")',
args: [
{
name: t`column`,
description: t`The column to check.`,
},
],
},
{
name: "is-empty",
structure: "isempty(" + t`column` + ")",
description: t`Checks if a column is empty`,
example: "isempty([" + t`Name` + '")',
args: [
{
name: t`column`,
description: t`The column to check.`,
},
],
},
{
name: "coalesce",
structure: "coalesce(" + t`value1` + ", " + t`value2` + ", …)",
......
......@@ -64,6 +64,8 @@ const FILTER_FUNCTIONS = [
{ text: "contains(", type: "functions" },
{ text: "endsWith(", type: "functions" },
{ text: "interval(", type: "functions" },
{ text: "isempty(", type: "functions" },
{ text: "isnull(", type: "functions" },
{ text: "startsWith(", type: "functions" },
];
const EXPRESSION_OPERATORS = [
......
......@@ -113,6 +113,8 @@ describe("type-checker", () => {
expect(filter("5 <= Q").segments).toEqual([]);
expect(filter("Between([BIG],3,7)").segments).toEqual([]);
expect(filter("Contains([GI],'Joe')").segments).toEqual([]);
expect(filter("IsEmpty([Discount])").segments).toEqual([]);
expect(filter("IsNull([Tax])").segments).toEqual([]);
});
it("should resolve dimensions correctly", () => {
......@@ -124,6 +126,8 @@ describe("type-checker", () => {
expect(filter("5 <= Q").dimensions).toEqual(["Q"]);
expect(filter("Between([BIG],3,7)").dimensions).toEqual(["BIG"]);
expect(filter("Contains([GI],'Joe')").dimensions).toEqual(["GI"]);
expect(filter("IsEmpty([Discount])").dimensions).toEqual(["Discount"]);
expect(filter("IsNull([Tax])").dimensions).toEqual(["Tax"]);
});
it("should resolve dimensions and segments correctly", () => {
......
......@@ -3,6 +3,7 @@ import {
restore,
openOrdersTable,
openProductsTable,
openReviewsTable,
popover,
visitQuestionAdhoc,
} from "__support__/cypress";
......@@ -779,4 +780,83 @@ describe("scenarios > question > filter", () => {
cy.wait("@cardQuery");
cy.findByText("Rye").should("not.exist");
});
it("should filter using IsNull() and IsEmpty()", () => {
openReviewsTable({ mode: "notebook" });
cy.findByText("Filter").click();
cy.findByText("Custom Expression").click();
cy.get("[contenteditable='true']")
.click()
.clear()
.type("NOT IsNull([Rating])", { delay: 50 });
cy.findAllByRole("button")
.contains("Done")
.should("not.be.disabled")
.click();
cy.get(".QueryBuilder .Icon-add").click();
cy.findByText("Custom Expression").click();
cy.get("[contenteditable='true']")
.click()
.clear()
.type("NOT IsEmpty([Reviewer])", { delay: 50 });
cy.findAllByRole("button")
.contains("Done")
.should("not.be.disabled")
.click();
// check that filter is applied and rows displayed
cy.findByText("Visualize").click();
cy.contains("Showing 1,112 rows");
});
it("should convert 'is empty' on a text column to a custom expression using IsEmpty()", () => {
openReviewsTable();
cy.contains("Reviewer").click();
cy.findByText("Filter by this column").click();
cy.findByText("Is").click();
cy.findByText("Is empty").click();
cy.findByText("Update filter").click();
// filter out everything
cy.contains("Showing 0 rows");
// change the corresponding custom expression
cy.findByText("Reviewer is empty").click();
cy.get(".Icon-chevronleft").click();
cy.findByText("Custom Expression").click();
cy.get("[contenteditable='true']").contains("isempty([Reviewer])");
cy.get("[contenteditable='true']")
.click()
.clear()
.type("NOT IsEmpty([Reviewer])", { delay: 50 });
cy.findByText("Done").click();
cy.contains("Showing 1,112 rows");
});
it("should convert 'is empty' on a numeric column to a custom expression using IsNull()", () => {
openReviewsTable();
cy.contains("Rating").click();
cy.findByText("Filter by this column").click();
cy.findByText("Equal to").click();
cy.findByText("Is empty").click();
cy.findByText("Update filter").click();
// filter out everything
cy.contains("Showing 0 rows");
// change the corresponding custom expression
cy.findByText("Rating is empty").click();
cy.get(".Icon-chevronleft").click();
cy.findByText("Custom Expression").click();
cy.get("[contenteditable='true']").contains("isnull([Rating])");
cy.get("[contenteditable='true']")
.click()
.clear()
.type("NOT IsNull([Rating])", { delay: 50 });
cy.findByText("Done").click();
cy.contains("Showing 1,112 rows");
});
});
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