turnilo
Version:
Business intelligence, data exploration and visualization web application for Druid, formerly known as Swiv and Pivot
498 lines • 23.1 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const chronoshift_1 = require("chronoshift");
const immutable_1 = require("immutable");
const app_settings_1 = require("../../../client/deserializers/app-settings");
const data_cube_1 = require("../../../client/deserializers/data-cube");
const functional_1 = require("../../utils/functional/functional");
const nullable_equals_1 = __importDefault(require("../../utils/immutable-utils/nullable-equals"));
const visualization_independent_evaluator_1 = require("../../utils/rules/visualization-independent-evaluator");
const time_1 = require("../../utils/time/time");
const visualization_manifests_1 = require("../../visualization-manifests");
const data_cube_2 = require("../data-cube/data-cube");
const date_range_1 = require("../date-range/date-range");
const dimensions_1 = require("../dimension/dimensions");
const filter_clause_1 = require("../filter-clause/filter-clause");
const measures_1 = require("../measure/measures");
const concrete_series_1 = require("../series/concrete-series");
const create_concrete_series_1 = __importDefault(require("../series/create-concrete-series"));
const sort_on_1 = require("../sort-on/sort-on");
const sort_1 = require("../sort/sort");
const splits_1 = require("../splits/splits");
const time_shift_1 = require("../time-shift/time-shift");
const time_shift_env_1 = require("../time-shift/time-shift-env");
const visualization_manifest_1 = require("../visualization-manifest/visualization-manifest");
function constrainDimensions(dimensions, dataCube) {
return dimensions.filter(dimensionName => Boolean(dimensions_1.findDimensionByName(dataCube.dimensions, dimensionName)));
}
var VisStrategy;
(function (VisStrategy) {
VisStrategy[VisStrategy["FairGame"] = 0] = "FairGame";
VisStrategy[VisStrategy["UnfairGame"] = 1] = "UnfairGame";
VisStrategy[VisStrategy["KeepAlways"] = 2] = "KeepAlways";
})(VisStrategy = exports.VisStrategy || (exports.VisStrategy = {}));
const defaultEssence = {
appSettings: null,
dataCube: null,
visualization: null,
visualizationSettings: null,
timezone: chronoshift_1.Timezone.UTC,
filter: null,
splits: null,
series: null,
pinnedDimensions: immutable_1.OrderedSet([]),
pinnedSort: null,
timeShift: time_shift_1.TimeShift.empty(),
visResolve: null
};
function resolveVisualization({ visualization, dataCube, splits, series, appSettings }) {
let visResolve;
if (!visualization) {
const visAndResolve = Essence.getBestVisualization(appSettings, dataCube, splits, series, null);
visualization = visAndResolve.visualization;
}
const ruleVariables = { appSettings, dataCube, series, splits, isSelectedVisualization: true };
visResolve = visualization.evaluateRules(ruleVariables);
if (visResolve.isAutomatic()) {
const adjustment = visResolve.adjustment;
splits = adjustment.splits;
visResolve = visualization.evaluateRules({ ...ruleVariables, splits });
if (!visResolve.isReady()) {
throw new Error(visualization.title + " must be ready after automatic adjustment");
}
}
if (visResolve.isReady()) {
visResolve = visualization_independent_evaluator_1.visualizationIndependentEvaluator({ dataCube, series });
}
return { visualization, splits, visResolve };
}
class Essence extends immutable_1.Record(defaultEssence) {
static getBestVisualization(appSettings, dataCube, splits, series, currentVisualization) {
const visAndResolves = visualization_manifests_1.MANIFESTS.map(visualization => {
const isSelectedVisualization = visualization === currentVisualization;
const ruleVariables = { appSettings, dataCube, splits, series, isSelectedVisualization };
return {
visualization,
resolve: visualization.evaluateRules(ruleVariables)
};
});
return visAndResolves.sort((vr1, vr2) => visualization_manifest_1.Resolve.compare(vr1.resolve, vr2.resolve))[0];
}
static fromDataCube(dataCube, appSettings) {
const essence = new Essence({
appSettings,
dataCube,
visualization: null,
visualizationSettings: null,
timezone: dataCube.defaultTimezone,
filter: data_cube_2.getDefaultFilter(dataCube),
timeShift: time_shift_1.TimeShift.empty(),
splits: data_cube_2.getDefaultSplits(dataCube),
series: data_cube_2.getDefaultSeries(dataCube),
pinnedDimensions: immutable_1.OrderedSet(dataCube.defaultPinnedDimensions),
pinnedSort: dataCube.defaultSortMeasure
});
return essence.updateSplitsWithFilter();
}
static defaultSortReference(series, dataCube) {
const seriesRefs = immutable_1.Set(series.series.map(series => series.key()));
const defaultSort = dataCube.defaultSortMeasure;
if (seriesRefs.has(defaultSort))
return defaultSort;
return seriesRefs.first();
}
static timeFilter(filter, dataCube) {
const timeFilter = filter.getClauseForDimension(data_cube_2.getTimeDimension(dataCube));
if (!filter_clause_1.isTimeFilter(timeFilter))
throw new Error(`Unknown time filter: ${timeFilter}`);
return timeFilter;
}
constructor(parameters) {
const { filter, dataCube, timezone, timeShift, series, pinnedDimensions, pinnedSort } = parameters;
if (!dataCube)
throw new Error("Essence must have a dataCube");
const { visResolve, visualization, splits } = resolveVisualization(parameters);
const constrainedSeries = series && series.constrainToMeasures(dataCube.measures);
const isPinnedSortValid = series && constrainedSeries.hasMeasureSeries(pinnedSort);
const constrainedPinnedSort = isPinnedSortValid ? pinnedSort : Essence.defaultSortReference(constrainedSeries, dataCube);
const constrainedFilter = filter.constrainToDimensions(dataCube.dimensions);
const validTimezone = timezone || chronoshift_1.Timezone.UTC;
const timeFilter = Essence.timeFilter(filter, dataCube);
const constrainedTimeShift = timeShift.constrainToFilter(timeFilter, validTimezone);
super({
...parameters,
dataCube,
visualization,
timezone: validTimezone,
timeShift: constrainedTimeShift,
splits: splits && splits.constrainToDimensionsAndSeries(dataCube.dimensions, constrainedSeries),
filter: constrainedFilter,
series: constrainedSeries,
pinnedDimensions: constrainDimensions(pinnedDimensions, dataCube),
pinnedSort: constrainedPinnedSort,
visResolve
});
}
toString() {
return "[Essence]";
}
toJS() {
return {
appSettings: app_settings_1.serialize(this.appSettings),
visualization: this.visualization,
visualizationSettings: this.visualizationSettings,
dataCube: data_cube_1.serialize(this.dataCube),
timezone: this.timezone.toJS(),
filter: this.filter && this.filter.toJS(),
splits: this.splits && this.splits.toJS(),
series: this.series.toJS(),
timeShift: this.timeShift.toJS(),
pinnedSort: this.pinnedSort,
pinnedDimensions: this.pinnedDimensions.toJS(),
visResolve: this.visResolve
};
}
getTimeDimension() {
return data_cube_2.getTimeDimension(this.dataCube);
}
evaluateSelection(filter, timekeeper) {
if (filter instanceof filter_clause_1.FixedTimeFilterClause)
return filter;
const { timezone, dataCube } = this;
return filter.evaluate(timekeeper.now(), data_cube_2.getMaxTime(dataCube, timekeeper), timezone);
}
combineWithPrevious(filter) {
const timeDimension = this.getTimeDimension();
const timeFilter = filter.getClauseForDimension(timeDimension);
if (!timeFilter || !(timeFilter instanceof filter_clause_1.FixedTimeFilterClause)) {
throw new Error("Can't combine current time filter with previous period without time filter");
}
return filter.setClause(this.combinePeriods(timeFilter));
}
getTimeShiftEnv(timekeeper) {
const timeDimension = this.getTimeDimension();
if (!this.hasComparison()) {
return { type: time_shift_env_1.TimeShiftEnvType.CURRENT };
}
const currentFilter = filter_clause_1.toExpression(this.currentTimeFilter(timekeeper), timeDimension);
const previousFilter = filter_clause_1.toExpression(this.previousTimeFilter(timekeeper), timeDimension);
return {
type: time_shift_env_1.TimeShiftEnvType.WITH_PREVIOUS,
shift: this.timeShift.valueOf(),
currentFilter,
previousFilter
};
}
constrainTimeShift() {
const { timeShift, timezone } = this;
return this.set("timeShift", timeShift.constrainToFilter(this.timeFilter(), timezone));
}
getEffectiveFilter(timekeeper, { combineWithPrevious = false, unfilterDimension = null } = {}) {
const { dataCube, timezone } = this;
let filter = this.filter;
if (unfilterDimension)
filter = filter.removeClause(unfilterDimension.name);
filter = filter.getSpecificFilter(timekeeper.now(), data_cube_2.getMaxTime(dataCube, timekeeper), timezone);
if (combineWithPrevious) {
filter = this.combineWithPrevious(filter);
}
return filter;
}
hasComparison() {
return !this.timeShift.isEmpty();
}
combinePeriods(timeFilter) {
const { timezone, timeShift } = this;
const duration = timeShift.valueOf();
return timeFilter.update("values", values => values.flatMap(({ start, end }) => [
new date_range_1.DateRange({ start, end }),
new date_range_1.DateRange({ start: duration.shift(start, timezone, -1), end: duration.shift(end, timezone, -1) })
]));
}
timeFilter() {
const { filter, dataCube } = this;
return Essence.timeFilter(filter, dataCube);
}
fixedTimeFilter(timekeeper) {
const { dataCube, timezone } = this;
const timeFilter = this.timeFilter();
if (timeFilter instanceof filter_clause_1.FixedTimeFilterClause)
return timeFilter;
return timeFilter.evaluate(timekeeper.now(), data_cube_2.getMaxTime(dataCube, timekeeper), timezone);
}
currentTimeFilter(timekeeper) {
return this.fixedTimeFilter(timekeeper);
}
shiftToPrevious(timeFilter) {
const { timezone, timeShift } = this;
const duration = timeShift.valueOf();
return timeFilter.update("values", values => values.map(({ start, end }) => new date_range_1.DateRange({
start: duration.shift(start, timezone, -1),
end: duration.shift(end, timezone, -1)
})));
}
previousTimeFilter(timekeeper) {
const timeFilter = this.fixedTimeFilter(timekeeper);
return this.shiftToPrevious(timeFilter);
}
getTimeClause() {
const timeDimension = this.getTimeDimension();
return this.filter.getClauseForDimension(timeDimension);
}
concreteSeriesFromSeries(series) {
const { reference } = series;
const { dataCube } = this;
const measure = measures_1.findMeasureByName(dataCube.measures, reference);
return create_concrete_series_1.default(series, measure, dataCube.measures);
}
findConcreteSeries(key) {
const series = this.series.series.find(series => series.key() === key);
if (!series)
return null;
return this.concreteSeriesFromSeries(series);
}
getConcreteSeries() {
return this.series.series.map(series => this.concreteSeriesFromSeries(series));
}
differentDataCube(other) {
return this.dataCube !== other.dataCube;
}
differentSplits(other) {
return !this.splits.equals(other.splits);
}
differentTimeShift(other) {
return !this.timeShift.equals(other.timeShift);
}
differentSeries(other) {
return !this.series.equals(other.series);
}
differentSettings(other) {
return !nullable_equals_1.default(this.visualizationSettings, other.visualizationSettings);
}
differentEffectiveFilter(other, myTimekeeper, otherTimekeeper, unfilterDimension = null) {
const myEffectiveFilter = this.getEffectiveFilter(myTimekeeper, { unfilterDimension });
const otherEffectiveFilter = other.getEffectiveFilter(otherTimekeeper, { unfilterDimension });
return !myEffectiveFilter.equals(otherEffectiveFilter);
}
getCommonSort() {
return this.splits.getCommonSort();
}
changeComparisonShift(timeShift) {
return this
.set("timeShift", timeShift)
.constrainTimeShift()
.updateSorts();
}
updateDataCube(newDataCube) {
const { dataCube } = this;
if (dataCube.name === newDataCube.name)
return this;
function setDataCube(essence) {
return essence.set("dataCube", newDataCube);
}
function constrainProps(essence) {
const seriesValidInNewCube = essence.series.constrainToMeasures(newDataCube.measures);
const newSeriesList = !seriesValidInNewCube.isEmpty()
? seriesValidInNewCube
: data_cube_2.getDefaultSeries(newDataCube);
return essence
.update("filter", filter => filter.constrainToDimensions(newDataCube.dimensions))
.set("series", newSeriesList)
.update("splits", splits => splits.constrainToDimensionsAndSeries(newDataCube.dimensions, newSeriesList))
.update("pinnedDimensions", pinned => constrainDimensions(pinned, newDataCube))
.update("pinnedSort", sort => !measures_1.hasMeasureWithName(newDataCube.measures, sort) ? newDataCube.defaultSortMeasure : sort);
}
function adjustVisualization(essence) {
const { dataCube, visualization, splits, series, appSettings } = essence;
const { visualization: newVis } = Essence.getBestVisualization(appSettings, dataCube, splits, series, visualization);
if (newVis === visualization)
return essence;
return essence.changeVisualization(newVis, newVis.visualizationSettings.defaults);
}
return functional_1.thread(this, setDataCube, constrainProps, adjustVisualization, (essence) => essence.resolveVisualizationAndUpdate());
}
changeFilter(filter) {
const { filter: oldFilter } = this;
return this
.set("filter", filter)
.constrainTimeShift()
.update("splits", splits => {
const differentClauses = filter.clauses.filter(clause => {
const otherClause = oldFilter.clauseForReference(clause.reference);
return !clause.equals(otherClause);
});
return splits.removeBucketingFrom(immutable_1.Set(differentClauses.map(clause => clause.reference)));
})
.updateSplitsWithFilter();
}
changeTimezone(newTimezone) {
const { timezone } = this;
if (timezone === newTimezone)
return this;
return this.set("timezone", newTimezone);
}
convertToSpecificFilter(timekeeper) {
const { dataCube, filter, timezone } = this;
if (!filter.isRelative())
return this;
return this.changeFilter(filter.getSpecificFilter(timekeeper.now(), data_cube_2.getMaxTime(dataCube, timekeeper), timezone));
}
defaultSplitSort(split) {
const { dataCube, series } = this;
const dimension = dimensions_1.findDimensionByName(dataCube.dimensions, split.reference);
const { sortStrategy, name, kind } = dimension;
if (sortStrategy === "self" || sortStrategy === name) {
return new sort_1.DimensionSort({ reference: name, direction: sort_1.SortDirection.ascending });
}
if (sortStrategy && series.hasMeasureSeries(sortStrategy)) {
return new sort_1.SeriesSort({ reference: sortStrategy, direction: sort_1.SortDirection.descending });
}
if (kind === "time") {
return new sort_1.DimensionSort({ reference: name, direction: sort_1.SortDirection.ascending });
}
return new sort_1.SeriesSort({ reference: this.defaultSort(), direction: sort_1.SortDirection.descending });
}
setSortOnSplits(splits) {
return splits.update("splits", list => list.map(split => {
return sort_1.isSortEmpty(split.sort) ? split.set("sort", this.defaultSplitSort(split)) : split;
}));
}
changeSplits(splits, strategy) {
const { splits: oldSplits, appSettings, dataCube, visualization, visResolve, filter, series } = this;
const newSplits = this.setSortOnSplits(splits).updateWithFilter(filter, dataCube.dimensions);
function adjustStrategy(strategy) {
if (visResolve.isManual()) {
return VisStrategy.KeepAlways;
}
if (oldSplits.length() > 0 && newSplits.length() !== 0) {
return VisStrategy.UnfairGame;
}
return strategy;
}
function adjustVisualization(essence) {
if (adjustStrategy(strategy) !== VisStrategy.FairGame)
return essence;
const { visualization: newVis } = Essence.getBestVisualization(appSettings, dataCube, newSplits, series, visualization);
if (newVis === visualization)
return essence;
return essence.changeVisualization(newVis, newVis.visualizationSettings.defaults);
}
return functional_1.thread(this, (essence) => essence.set("splits", newSplits), adjustVisualization, (essence) => essence.resolveVisualizationAndUpdate());
}
changeSplit(splitCombine, strategy) {
return this.changeSplits(splits_1.Splits.fromSplit(splitCombine), strategy);
}
addSplit(split, strategy) {
return this.changeSplits(this.splits.addSplit(split), strategy);
}
removeSplit(split, strategy) {
return this.changeSplits(this.splits.removeSplit(split), strategy);
}
addSeries(series) {
return this.changeSeriesList(this.series.addSeries(series));
}
removeSeries(series) {
return this.changeSeriesList(this.series.removeSeries(series));
}
changeSeriesList(series) {
return this
.set("series", series)
.updateSorts()
.resolveVisualizationAndUpdate();
}
defaultSort() {
return Essence.defaultSortReference(this.series, this.dataCube);
}
updateSorts() {
const seriesRefs = immutable_1.Set(this.series.series.map(series => series.reference));
return this
.update("pinnedSort", sort => {
if (seriesRefs.has(sort))
return sort;
return this.defaultSort();
})
.update("splits", splits => splits.update("splits", splits => splits.map((split) => {
const { sort } = split;
const { type, reference } = sort;
switch (type) {
case sort_1.SortType.DIMENSION:
return split;
case sort_1.SortType.SERIES: {
const measureSort = sort;
if (!seriesRefs.has(reference)) {
const measureSortRef = this.defaultSort();
if (measureSortRef) {
return split.changeSort(new sort_1.SeriesSort({
reference: measureSortRef
}));
}
return split.changeSort(new sort_1.DimensionSort({
reference: split.reference
}));
}
if (measureSort.period !== concrete_series_1.SeriesDerivation.CURRENT && !this.hasComparison()) {
return split.update("sort", (sort) => sort.set("period", concrete_series_1.SeriesDerivation.CURRENT));
}
return split;
}
}
})));
}
updateSplitsWithFilter() {
const { filter, dataCube: { dimensions }, splits } = this;
const newSplits = splits.updateWithFilter(filter, dimensions);
if (splits === newSplits)
return this;
return this.set("splits", newSplits).resolveVisualizationAndUpdate();
}
changeVisualization(visualization, settings = visualization.visualizationSettings.defaults) {
return this
.set("visualization", visualization)
.set("visualizationSettings", settings)
.resolveVisualizationAndUpdate();
}
resolveVisualizationAndUpdate() {
const { visualization, splits, dataCube, series, appSettings } = this;
const result = resolveVisualization({ appSettings, dataCube, visualization, splits, series });
return this
.set("visResolve", result.visResolve)
.set("visualization", result.visualization)
.set("splits", result.splits);
}
pin({ name }) {
return this.update("pinnedDimensions", pinned => pinned.add(name));
}
unpin({ name }) {
return this.update("pinnedDimensions", pinned => pinned.remove(name));
}
changePinnedSortSeries(series) {
return this.set("pinnedSort", series.plywoodKey());
}
seriesSortOns(withTimeShift) {
const series = this.getConcreteSeries();
const addPrevious = withTimeShift && this.hasComparison();
if (!addPrevious)
return series.map(series => new sort_on_1.SeriesSortOn(series));
return series.flatMap(series => {
return [
new sort_on_1.SeriesSortOn(series),
new sort_on_1.SeriesSortOn(series, concrete_series_1.SeriesDerivation.PREVIOUS),
new sort_on_1.SeriesSortOn(series, concrete_series_1.SeriesDerivation.DELTA)
];
});
}
getPinnedSortSeries() {
return this.findConcreteSeries(this.pinnedSort);
}
description(timekeeper) {
const timeFilter = this.currentTimeFilter(timekeeper);
const { start, end } = timeFilter.values.first();
const timezone = this.timezone;
return `${this.dataCube.name}_${time_1.formatUrlSafeDateTime(start, timezone)}_${time_1.formatUrlSafeDateTime(end, timezone)}`;
}
}
exports.Essence = Essence;
//# sourceMappingURL=essence.js.map