UNPKG

turnilo

Version:

Business intelligence, data exploration and visualization web application for Druid, formerly known as Swiv and Pivot

498 lines 23.1 kB
"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