Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invalid filters #375

Merged
merged 15 commits into from
Jan 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,10 @@
"render_warning.html/render warning should show above size limit.": "6b6f46fd4075987564eee7df17652d3d",
"render_warning.html/dismissing render warning should trigger render.": "1338b4720cc3374d42fe4c75a9e3dc8b",
"render_warning.html/selecting 'do not show again' should stop render warnings.": "1338b4720cc3374d42fe4c75a9e3dc8b",
"render_warning.html/underlying data updates should not trigger rerender if warning is visible.": "e606e604c4ade6671319f8d049ee5a63"
"render_warning.html/underlying data updates should not trigger rerender if warning is visible.": "e606e604c4ade6671319f8d049ee5a63",
"bar.html/highlights invalid filter.": "55b58961efb8ad7acc4d6613d4a5027e",
"scatter.html/highlights invalid filter.": "07814ed1500d1810d934ca8ac9f07486",
"line.html/highlights invalid filter.": "1dfcefa55fb5bad1804b1fbb708204de",
"treemap.html/highlights invalid filter.": "5b36e9d8a49d330118c2d6079262d268",
"heatmap.html/highlights invalid filter.": "1d1971aee71f389067e7f3c732d26a5f"
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
"superstore.html/pivots by a column.": "523c39ab27ca610b5ed179cbe742ec5b",
"superstore.html/collapses to depth smaller than viewport": "bdd25c1c40781478bd040d5816b887a6",
"regressions.html/saving a computed column does not interrupt update rendering": "da13afb4284b9c3da21ed57c6ba69301",
"empty.html/empty grids do not explode": "423ca653bbcbc21a28029c149a37b8ec"
"empty.html/empty grids do not explode": "423ca653bbcbc21a28029c149a37b8ec",
"superstore.html/highlights invalid filter.": "e244cca8fc2278cb2477d0a46ab5331f"
}
1 change: 1 addition & 0 deletions packages/perspective-viewer/src/html/row.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<select id="column_aggregate" class="string"></select>
<select id="filter_operator"></select>
<input id="filter_operand" placeholder="Value" />
<span id="row_exclamation" hidden>&#x26A0;</span>
<span id='row_close'>&#x2715;</span>
</div>
</div>
Expand Down
24 changes: 23 additions & 1 deletion packages/perspective-viewer/src/js/viewer/perspective_element.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,34 @@ export class PerspectiveElement extends StateElement {
}
}

async _validate_filters() {
const filters = [];
for (const node of this._get_view_filter_nodes()) {
const operandNode = node.shadowRoot.getElementById("filter_operand");
const exclamation = node.shadowRoot.getElementById("row_exclamation");
const {operator, operand} = JSON.parse(node.getAttribute("filter"));
const filter = [node.getAttribute("name"), operator, operand];
if (await this._table.is_valid_filter(filter)) {
filters.push(filter);
node.title = "";
operandNode.style.borderColor = "";
exclamation.hidden = true;
} else {
node.title = "Invalid Filter";
operandNode.style.borderColor = "red";
exclamation.hidden = false;
}
}

return filters;
}

async _new_view(ignore_size_check = false) {
if (!this._table) return;
this._check_responsive_layout();
const row_pivots = this._get_view_row_pivots();
const column_pivots = this._get_view_column_pivots();
const filters = this._get_view_filters();
const filters = await this._validate_filters();
const aggregates = this._get_view_aggregates();
if (aggregates.length === 0) return;
const sort = this._get_view_sorts();
Expand Down
4 changes: 4 additions & 0 deletions packages/perspective-viewer/src/js/viewer/state_element.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export class StateElement extends HTMLElement {
});
}

_get_view_filter_nodes() {
return this._get_view_dom_columns("#filters perspective-row");
}

_get_view_filters() {
return this._get_view_dom_columns("#filters perspective-row", col => {
let {operator, operand} = JSON.parse(col.getAttribute("filter"));
Expand Down
2 changes: 1 addition & 1 deletion packages/perspective-viewer/src/less/column_labels.less
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
*/

.titled {
.titled {
margin-top: 23px;
}

Expand Down
4 changes: 4 additions & 0 deletions packages/perspective-viewer/src/less/row.less
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@
padding: var(--sort_order-padding, 0);
}

#row_exclamation {
color: red;
}

#row_close {
color: #999;
font-family: Arial;
Expand Down
6 changes: 6 additions & 0 deletions packages/perspective-viewer/test/js/simple_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ exports.default = function() {
await page.evaluate(element => element.setAttribute("filters", '[["Sales", ">", 500]]'), viewer);
});

test.capture("highlights invalid filter.", async page => {
const viewer = await page.$("perspective-viewer");
await page.shadow_click("perspective-viewer", "#config_button");
await page.evaluate(element => element.setAttribute("filters", '[["Sales", "==", null]]'), viewer);
});

test.capture("sorts by an alpha column.", async page => {
const viewer = await page.$("perspective-viewer");
await page.shadow_click("perspective-viewer", "#config_button");
Expand Down
3 changes: 2 additions & 1 deletion packages/perspective-viewer/test/results/results.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@
"superstore.html/doesn't leak views when setting filters.": "0516bfcff10dd15dca9c7d2e4923ab10",
"superstore.html/adds computed column via attribute": "96016d19e8c94accc24f0d613bdbbab1",
"superstore.html/saving without parameters should fail as button is disabled.": "a26ce038cea02a8e3e53b7e0b74a204d",
"superstore.html/saving a duplicate column should fail with error message.": "9d74a59806ad8ec4ab1bcef7c3c94b68"
"superstore.html/saving a duplicate column should fail with error message.": "9d74a59806ad8ec4ab1bcef7c3c94b68",
"superstore.html/highlights invalid filter.": "3eadf1175e231c1fbcc2bb9ef10ca6ef"
}
2 changes: 2 additions & 0 deletions packages/perspective/src/js/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ table.prototype.column_metadata = async_queue("column_metadata", "table_method")

table.prototype.computed_schema = async_queue("computed_schema", "table_method");

table.prototype.is_valid_filter = async_queue("is_valid_filter", "table_method");

table.prototype.size = async_queue("size", "table_method");

table.prototype.columns = async_queue("columns", "table_method");
Expand Down
30 changes: 25 additions & 5 deletions packages/perspective/src/js/perspective.js
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,28 @@ export default function(Module) {
return this._computed_schema();
};

table.prototype._is_date_filter = function(schema) {
return key => schema[key] === "datetime" || schema[key] === "date";
};

table.prototype._is_valid_filter = function(filter) {
const schema = this._schema();
const isDateFilter = this._is_date_filter(schema);
const value = isDateFilter(filter[0]) ? new DateParser().parse(filter[2]) : filter[2];
return typeof value !== "undefined" && value !== null && value !== "";
};

/**
* Determines whether a given filter is valid.
*
* @param {Array<string>} [filter] A filter configuration array to test
*
* @returns {boolean} Whether the filter is valid
*/
table.prototype.is_valid_filter = function(filter) {
return this._is_valid_filter(filter);
};

/**
* Create a new {@link view} from this table with a specified
* configuration.
Expand Down Expand Up @@ -971,12 +993,10 @@ export default function(Module) {

if (config.filter) {
let schema = this._schema();
let isDateFilter = key => schema[key] === "datetime" || schema[key] === "date";
let isDateFilter = this._is_date_filter(schema);
let isValidFilter = this._is_valid_filter;
filters = config.filter
.filter(filter => {
const value = isDateFilter(filter[0]) ? new DateParser().parse(filter[2]) : filter[2];
return typeof value !== "undefined" && value !== null;
})
.filter(filter => isValidFilter(filter))
.map(filter => {
if (isDateFilter(filter[0])) {
return [filter[0], _string_to_filter_op[filter[1]], new DateParser().parse(filter[2])];
Expand Down
87 changes: 84 additions & 3 deletions packages/perspective/test/js/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ module.exports = perspective => {
filter: [["x", ">", 1], ["x", "<", 4]]
});
let json = await view.to_json();
expect(rdata.slice(1, 3)).toEqual(json);
expect(json).toEqual(rdata.slice(1, 3));
view.delete();
table.delete();
});
Expand All @@ -222,7 +222,7 @@ module.exports = perspective => {
filter: [["y", "contains", "a"], ["y", "contains", "b"]]
});
let json = await view.to_json();
expect(rdata.slice(0, 2)).toEqual(json);
expect(json).toEqual(rdata.slice(0, 2));
view.delete();
table.delete();
});
Expand Down Expand Up @@ -266,7 +266,7 @@ module.exports = perspective => {
table.delete();
});

it("x == null", async function() {
it("x > null", async function() {
var table = perspective.table({x: "float", y: "integer"});
const dataSet = [{x: 3.5, y: 1}, {x: 2.5, y: 1}, {x: null, y: 1}, {x: null, y: 1}, {x: 4.5, y: 2}, {x: null, y: 2}];
table.update(dataSet);
Expand All @@ -279,6 +279,87 @@ module.exports = perspective => {
view.delete();
table.delete();
});

it('x == ""', async function() {
var table = perspective.table({x: "float", y: "integer"});
const dataSet = [{x: 3.5, y: 1}, {x: 2.5, y: 1}, {x: null, y: 1}, {x: null, y: 1}, {x: 4.5, y: 2}, {x: null, y: 2}];
table.update(dataSet);
var view = table.view({
filter: [["x", "==", ""]]
});
var answer = dataSet;
let result = await view.to_json();
expect(answer).toEqual(result);
view.delete();
table.delete();
});
});

describe("is_valid_filter", function() {
it("x == 2", async function() {
var table = perspective.table(data);
let isValid = await table.is_valid_filter(["x", "==", 2]);
expect(isValid).toBeTruthy();
table.delete();
});
it("x < null", async function() {
var table = perspective.table(data);
let isValid = await table.is_valid_filter(["x", "<", null]);
expect(isValid).toBeFalsy();
table.delete();
});
it("x > undefined", async function() {
var table = perspective.table(data);
let isValid = await table.is_valid_filter(["x", ">", undefined]);
expect(isValid).toBeFalsy();
table.delete();
});
it('x == ""', async function() {
var table = perspective.table(data);
let isValid = await table.is_valid_filter(["x", "==", ""]);
expect(isValid).toBeFalsy();
table.delete();
});
it("valid date", async function() {
const schema = {
x: "string",
y: "date"
};
var table = perspective.table(schema);
let isValid = await table.is_valid_filter(["y", "==", "01-01-1970"]);
expect(isValid).toBeTruthy();
table.delete();
});
it("invalid date", async function() {
const schema = {
x: "string",
y: "date"
};
var table = perspective.table(schema);
let isValid = await table.is_valid_filter(["y", "<", "1234"]);
expect(isValid).toBeFalsy();
table.delete();
});
it("valid datetime", async function() {
const schema = {
x: "string",
y: "datetime"
};
var table = perspective.table(schema);
let isValid = await table.is_valid_filter(["y", "==", "11:11:11.111"]);
expect(isValid).toBeTruthy();
table.delete();
});
it("invalid datetime", async function() {
const schema = {
x: "string",
y: "datetime"
};
var table = perspective.table(schema);
let isValid = await table.is_valid_filter(["y", ">", "11:11:11:111"]);
expect(isValid).toBeFalsy();
table.delete();
});
});
});
};