virool-pivot
Version:
A web-based exploratory visualization UI for Druid.io
172 lines (146 loc) • 5.26 kB
text/typescript
import { Class, Instance, isInstanceOf } from 'immutable-class';
import { Timezone, Duration, minute } from 'chronoshift';
import { $, r, Expression, ExpressionJS, LiteralExpression, RefExpression, Set, SetJS, ChainExpression, NotAction, OverlapAction, InAction, Range, TimeRange, Datum } from 'plywood';
// Basically these represent
// expression.in(selection) .not()?
export interface FilterClauseValue {
expression: Expression;
selection?: Expression;
exclude?: boolean;
}
export interface FilterClauseJS {
expression: ExpressionJS;
selection?: ExpressionJS;
exclude?: boolean;
}
function isLiteral(ex: Expression): boolean {
if (ex instanceof LiteralExpression) return TimeRange.isTimeRange(ex.value) || Set.isSet(ex.value);
return false;
}
function isRelative(ex: Expression): boolean {
if (ex instanceof ChainExpression) {
if (ex.type !== 'TIME_RANGE') return false;
var expression = ex.expression;
if (expression instanceof RefExpression) {
return expression.name === FilterClause.NOW_REF_NAME || expression.name === FilterClause.MAX_TIME_REF_NAME;
}
}
return false;
}
var check: Class<FilterClauseValue, FilterClauseJS>;
export class FilterClause implements Instance<FilterClauseValue, FilterClauseJS> {
static isFilterClause(candidate: any): candidate is FilterClause {
return isInstanceOf(candidate, FilterClause);
}
static NOW_REF_NAME = 'n';
static MAX_TIME_REF_NAME = 'm';
static evaluate(selection: Expression, now: Date, maxTime: Date, timezone: Timezone): TimeRange {
if (!selection) return null;
var maxTimeMinuteTop = minute.shift(minute.floor(maxTime, timezone), timezone, 1);
var datum: Datum = {};
datum[FilterClause.NOW_REF_NAME] = now;
datum[FilterClause.MAX_TIME_REF_NAME] = maxTimeMinuteTop;
return selection.defineEnvironment({ timezone }).getFn()(datum, {});
}
static fromExpression(ex: Expression): FilterClause {
var exclude = false;
if (ex.lastAction() instanceof NotAction) {
ex = ex.popAction();
exclude = true;
}
var lastAction = ex.lastAction();
var dimExpression = ex.popAction();
if (lastAction instanceof InAction || lastAction instanceof OverlapAction) {
var selection = lastAction.expression;
return new FilterClause({
expression: dimExpression,
selection,
exclude
});
}
throw new Error(`invalid expression ${ex.toString()}`);
}
static fromJS(parameters: FilterClauseJS): FilterClause {
var value: FilterClauseValue = {
expression: Expression.fromJS(parameters.expression),
selection: Expression.fromJS(parameters.selection),
exclude: Boolean(parameters.exclude)
};
return new FilterClause(value);
}
public expression: Expression;
public selection: Expression;
public exclude: boolean;
public relative: boolean;
constructor(parameters: FilterClauseValue) {
this.expression = parameters.expression;
var selection = parameters.selection;
if (isRelative(selection)) {
this.relative = true;
} else if (isLiteral(selection)) {
this.relative = false;
} else {
throw new Error(`invalid expression ${selection.toString()}`);
}
this.selection = selection;
this.exclude = parameters.exclude || false;
}
public valueOf(): FilterClauseValue {
return {
expression: this.expression,
selection: this.selection,
exclude: this.exclude
};
}
public toJS(): FilterClauseJS {
var js: FilterClauseJS = {
expression: this.expression.toJS(),
selection: this.selection.toJS()
};
if (this.exclude) js.exclude = true;
return js;
}
public toJSON(): FilterClauseJS {
return this.toJS();
}
public toString(): string {
return `[FilterClause: ${this.expression.toString()}]`;
}
public equals(other: FilterClause): boolean {
return FilterClause.isFilterClause(other) &&
this.expression.equals(other.expression) &&
this.selection.equals(other.selection) &&
this.exclude === other.exclude;
}
public toExpression(): ChainExpression {
const { expression, selection } = this;
var ex: ChainExpression = null;
var selectionType = selection.type;
if (selectionType === 'TIME_RANGE' || selectionType === 'SET/TIME_RANGE' || selectionType === 'NUMBER_RANGE' || selectionType === 'SET/NUMBER_RANGE') {
ex = expression.in(selection);
} else {
ex = expression.overlap(selection);
}
if (this.exclude) ex = ex.not();
return ex;
}
public getLiteralSet(): Set {
if (this.relative) return null;
var v = this.selection.getLiteralValue();
return TimeRange.isTimeRange(v) ? Set.fromJS([v]) : v;
}
public getExtent(): Range<any> {
var mySet = this.getLiteralSet();
return mySet ? mySet.extent() : null;
}
public changeSelection(selection: Expression) {
var value = this.valueOf();
value.selection = selection;
return new FilterClause(value);
}
public evaluate(now: Date, maxTime: Date, timezone: Timezone): FilterClause {
if (!this.relative) return this;
return this.changeSelection(r(FilterClause.evaluate(this.selection, now, maxTime, timezone)));
}
}
check = FilterClause;