UNPKG

virool-pivot

Version:

A web-based exploratory visualization UI for Druid.io

336 lines (285 loc) 10.7 kB
import { List } from 'immutable'; import { Class, Instance, isInstanceOf } from 'immutable-class'; import { Timezone, Duration } from 'chronoshift'; import { $, r, Expression, LiteralExpression, ExpressionJS, InAction, Set, Range, TimeRange } from 'plywood'; import { immutableListsEqual } from '../../utils/general/general'; import { Dimension } from '../dimension/dimension'; import { FilterClause, FilterClauseJS } from '../filter-clause/filter-clause'; function withholdClause(clauses: List<FilterClause>, clause: FilterClause, allowIndex: number): List<FilterClause> { return <List<FilterClause>>clauses.filter((c, i) => { return i === allowIndex || !c.equals(clause); }); } function swapClause(clauses: List<FilterClause>, clause: FilterClause, other: FilterClause, allowIndex: number): List<FilterClause> { return <List<FilterClause>>clauses.map((c, i) => { return (i === allowIndex || !c.equals(clause)) ? c : other; }); } function dateToFileString(date: Date): string { return date.toISOString() .replace('T', '_') .replace('Z', '') .replace('.000', ''); } export type FilterValue = List<FilterClause>; export type FilterJS = ExpressionJS | string; var check: Class<FilterValue, FilterJS>; export class Filter implements Instance<FilterValue, FilterJS> { static EMPTY: Filter; static isFilter(candidate: any): candidate is Filter { return isInstanceOf(candidate, Filter); } static fromClause(clause: FilterClause): Filter { if (!clause) throw new Error('must have clause'); return new Filter(List([clause])); } static fromJS(parameters: FilterJS): Filter { var expression = Expression.fromJSLoose(parameters); var clauses: FilterClause[] = null; if (expression.equals(Expression.TRUE)) { clauses = []; } else { clauses = (expression.getExpressionPattern('and') || [expression]).map(c => FilterClause.fromExpression(c)); } return new Filter(<List<FilterClause>>List(clauses)); } public clauses: List<FilterClause>; constructor(parameters: FilterValue) { this.clauses = parameters; } public valueOf(): FilterValue { return this.clauses; } public toJS(): FilterJS { return this.toExpression().toJS(); } public toJSON(): FilterJS { return this.toJS(); } public toString() { return this.clauses.map(clause => clause.toString()).join(' and '); } public equals(other: Filter): boolean { return Filter.isFilter(other) && immutableListsEqual(this.clauses, other.clauses); } public replaceByIndex(index: number, replace: FilterClause): Filter { var { clauses } = this; if (clauses.size === index) return this.insertByIndex(index, replace); var replacedClause = clauses.get(index); clauses = <List<FilterClause>>clauses.map((c, i) => i === index ? replace : c); clauses = swapClause(clauses, replace, replacedClause, index); return new Filter(clauses); } public insertByIndex(index: number, insert: FilterClause): Filter { var { clauses } = this; clauses = <List<FilterClause>>clauses.splice(index, 0, insert); clauses = withholdClause(clauses, insert, index); return new Filter(clauses); } public empty(): boolean { return this.clauses.size === 0; } public single(): boolean { return this.clauses.size === 1; } public length(): number { return this.clauses.size; } public toExpression(): Expression { var clauses = this.clauses.toArray().map(clause => { return clause.toExpression(); }); switch (clauses.length) { case 0: return Expression.TRUE; case 1: return clauses[0]; default: return Expression.and(clauses); } } public isEmpty(): boolean { return this.clauses.isEmpty(); } public isRelative(): boolean { return this.clauses.some(clause => clause.relative); } public getSpecificFilter(now: Date, maxTime: Date, timezone: Timezone): Filter { if (!this.isRelative()) return this; return new Filter(this.clauses.map(c => c.evaluate(now, maxTime, timezone)) as List<FilterClause>); } private indexOfClause(attribute: Expression): number { return this.clauses.findIndex(clause => clause.expression.equals(attribute)); } public clauseForExpression(attribute: Expression): FilterClause { return this.clauses.find(clause => clause.expression.equals(attribute)); } public filteredOn(attribute: Expression): boolean { return this.indexOfClause(attribute) !== -1; } public filteredOnValue(attribute: Expression, value: any): boolean { var clauses = this.clauses; var index = this.indexOfClause(attribute); if (index === -1) return false; return clauses.get(index).getLiteralSet().contains(value); } public addValue(attribute: Expression, value: any): Filter { var clauses = this.clauses; var index = this.indexOfClause(attribute); if (index === -1) { return new Filter(<List<FilterClause>>clauses.concat(new FilterClause({ expression: attribute, selection: r(Set.fromJS([value])) }))); } else { var clause = clauses.get(index); var newSet = clause.getLiteralSet().add(value); return new Filter(<List<FilterClause>>clauses.splice(index, 1, clause.changeSelection(r(newSet)))); } } public remove(attribute: Expression): Filter { var clauses = this.clauses; var index = this.indexOfClause(attribute); if (index === -1) return this; return new Filter(clauses.delete(index)); } public removeValue(attribute: Expression, value: any): Filter { var clauses = this.clauses; var index = this.indexOfClause(attribute); if (index === -1) return this; var clause = clauses.get(index); var newSet = clause.getLiteralSet().remove(value); if (newSet.empty()) { return new Filter(clauses.delete(index)); } else { clauses = <List<FilterClause>>clauses.splice(index, 1, clause.changeSelection(r(newSet))); return new Filter(clauses); } } public toggleValue(attribute: Expression, value: any): Filter { return this.filteredOnValue(attribute, value) ? this.removeValue(attribute, value) : this.addValue(attribute, value); } public getSelection(attribute: Expression): Expression { var clauses = this.clauses; var index = this.indexOfClause(attribute); if (index === -1) return null; return clauses.get(index).selection; } public setSelection(attribute: Expression, selection: Expression): Filter { var clauses = this.clauses; var index = this.indexOfClause(attribute); var newClause = new FilterClause({ expression: attribute, selection }); if (index === -1) { clauses = <List<FilterClause>>clauses.push(newClause); } else { clauses = <List<FilterClause>>clauses.splice(index, 1, newClause); } return new Filter(clauses); } public getExtent(attribute: Expression): Range<any> { var clauses = this.clauses; var index = this.indexOfClause(attribute); if (index === -1) return null; return clauses.get(index).getExtent(); } public getFileString(timeAttribute: Expression) { var nonTimeClauseSize = this.clauses.size; const timeRange = this.getExtent(timeAttribute); // ToDo: revisit this const nonTimeFilters = ((nonTimeClauseSize: number) => { return nonTimeClauseSize === 0 ? "" : `_filters-${nonTimeClauseSize}`; }); if (timeRange) { var { start, end } = timeRange; nonTimeClauseSize--; return `${dateToFileString(start)}_${dateToFileString(end)}${nonTimeFilters(nonTimeClauseSize)}`; } return nonTimeFilters(nonTimeClauseSize); } public getLiteralSet(attribute: Expression): Set { var clauses = this.clauses; var index = this.indexOfClause(attribute); if (index === -1) return null; return clauses.get(index).getLiteralSet(); } public setClause(expression: FilterClause): Filter { var expressionAttribute = expression.expression; var added = false; var newOperands = <List<FilterClause>>this.clauses.map((clause) => { if (clause.expression.equals(expressionAttribute)) { added = true; return expression; } else { return clause; } }); if (!added) { newOperands = newOperands.push(expression); } return new Filter(newOperands); } public applyDelta(delta: Filter): Filter { var newFilter: Filter = this; var deltaClauses = delta.clauses; deltaClauses.forEach((deltaClause) => { newFilter = newFilter.setClause(deltaClause); }); return newFilter; } public getSingleClauseSet(): Set { var clauses = this.clauses; if (clauses.size !== 1) return null; return clauses.get(0).getLiteralSet(); } public constrainToDimensions(dimensions: List<Dimension>, timeAttribute: Expression, oldTimeAttribute: Expression = null): Filter { var hasChanged = false; var clauses: FilterClause[] = []; this.clauses.forEach((clause) => { var clauseExpression = clause.expression; if (Dimension.getDimensionByExpression(dimensions, clauseExpression)) { clauses.push(clause); } else { hasChanged = true; // Special handling for time filter if (timeAttribute && oldTimeAttribute && oldTimeAttribute.equals(clauseExpression)) { clauses.push(new FilterClause({ expression: timeAttribute, selection: clause.selection })); } } }); return hasChanged ? new Filter(List(clauses)) : this; } public getDifferentAttributes(other: Filter): Expression[] { var diff: Expression[] = []; this.clauses.forEach((clause) => { var clauseExpression = clause.expression; var otherClause = other.clauseForExpression(clauseExpression); if (!clause.equals(otherClause)) { diff.push(clauseExpression); } }); return diff; } public overQuery(duration: Duration, timezone: Timezone, timeAttribute: Expression): Filter { if (!timeAttribute) return this; return new Filter(<List<FilterClause>>this.clauses.map((clause) => { if (clause.expression.equals(timeAttribute)) { var timeRange: TimeRange = clause.getExtent() as TimeRange; var newTimeRange = new TimeRange({ start: duration.shift(timeRange.start, timezone, -1), end: duration.shift(timeRange.end, timezone, 1) }); return clause.changeSelection(r(newTimeRange)); } else { return clause; } })); } } check = Filter; Filter.EMPTY = new Filter(<List<FilterClause>>List());