UNPKG

@finos/legend-data-cube

Version:
264 lines 9.92 kB
/** * Copyright (c) 2020-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { customList, guaranteeNonNullable, guaranteeType, hashArray, SerializationFactory, usingModelSchema, } from '@finos/legend-shared'; import { createModelSchema, deserialize, list, primitive, serialize, } from 'serializr'; export const DIMENSIONAL_L0_COLUMN = 'ALL'; var DimensionalNodeHashType; (function (DimensionalNodeHashType) { DimensionalNodeHashType["GROUP_BY_NODE"] = "groupByNode"; DimensionalNodeHashType["DIMENSIONAL_NODE"] = "dimensionalNode"; DimensionalNodeHashType["DIMENSIONAL_TREE"] = "dimensionalTree"; })(DimensionalNodeHashType || (DimensionalNodeHashType = {})); export class DataCubeDimensionalGroupByNode { column; filter; dimension; constructor(column, filter, dimension) { this.column = column; this.filter = filter; this.dimension = dimension; } get hashCode() { return hashArray([ DimensionalNodeHashType.GROUP_BY_NODE, this.column, this.filter, this.dimension, ]); } static serialization = new SerializationFactory(createModelSchema(DataCubeDimensionalGroupByNode, { column: primitive(), filter: primitive(), dimension: primitive(), })); } export class DataCubeDimensionalNode { column; dimension; childNodes; groupByNodes; constructor(column, dimension, childNodes = [], groupByNodes = []) { this.column = column; this.dimension = dimension; this.childNodes = childNodes; this.groupByNodes = groupByNodes; } get hashCode() { return hashArray([ DimensionalNodeHashType.DIMENSIONAL_NODE, this.column, hashArray(this.groupByNodes), ]); } static serialization = new SerializationFactory(createModelSchema(DataCubeDimensionalNode, { column: primitive(), dimension: primitive(), childNodes: customList(serializeDataCubeDimensionalNode, deSerializeDataCubeDimensionalNode), groupByNodes: list(usingModelSchema(DataCubeDimensionalGroupByNode.serialization.schema)), })); } const dataCubeDimensionalNodeModelSchema = createModelSchema(DataCubeDimensionalNode, { column: primitive(), dimension: primitive(), childNodes: customList(serializeDataCubeDimensionalNode, deSerializeDataCubeDimensionalNode), groupByNodes: list(usingModelSchema(DataCubeDimensionalGroupByNode.serialization.schema)), }); function serializeDataCubeDimensionalNode(protocol) { return serialize(dataCubeDimensionalNodeModelSchema, protocol); } function deSerializeDataCubeDimensionalNode(plainObject) { return deserialize(dataCubeDimensionalNodeModelSchema, plainObject); } export class DataCubeDimensionalTree { rootNodes; constructor(rootNodes) { this.rootNodes = rootNodes; } // TODO: improve hashing get hashCode() { return hashArray([ DimensionalNodeHashType.DIMENSIONAL_TREE, hashArray(this.rootNodes), ]); } static serialization = new SerializationFactory(createModelSchema(DataCubeDimensionalTree, { rootNodes: list(usingModelSchema(DataCubeDimensionalNode.serialization.schema)), })); } export function cloneDimensionalTree(tree) { let clone = new DataCubeDimensionalTree([]); clone = JSON.parse(JSON.stringify(tree)); return clone; } export class DataCubeDimensionalMetadata { column; level; groupByNodes; constructor(column, level, groupByNodes = []) { this.column = column; this.level = level; this.groupByNodes = groupByNodes; } } export function generateDimensionalPaths(tree) { const paths = []; const collectAllDescendants = (node) => { const result = []; const stack = [node]; while (stack.length) { const current = stack.pop(); if (current) { result.push(current); stack.push(...current.childNodes); } } return result; }; const backtrack = (index, path) => { if (index === tree.rootNodes.length) { paths.push([...path]); return; } const options = collectAllDescendants(guaranteeNonNullable(tree.rootNodes[index])); for (const option of options) { path.push(option); backtrack(index + 1, path); path.pop(); } }; backtrack(0, []); return paths; } export function hydrateDataCubeDimensionalTree(configuration, event, tree) { if (!event && !tree) { return new DataCubeDimensionalTree(configuration.dimensions.dimensions.map((d) => new DataCubeDimensionalNode(DIMENSIONAL_L0_COLUMN, d.name))); } const safeTree = guaranteeNonNullable(tree); const safeEvent = guaranteeNonNullable(event); const metadata = guaranteeType(safeEvent.data.metadata, (Map)); const clickedDimension = guaranteeNonNullable(safeEvent.column.getColId()); const dimensionMetadata = metadata.get(clickedDimension); if (!dimensionMetadata) { return safeTree; // No metadata for clicked column -> return tree untouched } const safeMetadata = guaranteeType(dimensionMetadata, DataCubeDimensionalMetadata); const { column: clickedColumn, groupByNodes } = safeMetadata; const dimension = guaranteeNonNullable(configuration.dimensions.dimensions.find((d) => d.name === clickedDimension)); const nextColumn = clickedColumn !== DIMENSIONAL_L0_COLUMN ? dimension.columns[dimension.columns.indexOf(clickedColumn) + 1] : dimension.columns[0]; if (!nextColumn) { return safeTree; } // No further drilldown possible const newNode = new DataCubeDimensionalNode(nextColumn, clickedDimension); newNode.groupByNodes.push(...groupByNodes); for (const rootNode of safeTree.rootNodes) { if (rootNode.dimension !== clickedDimension) { continue; } const targetNode = findNodeByColumnOrGroup(rootNode, clickedColumn, groupByNodes); if (targetNode) { if (clickedColumn !== DIMENSIONAL_L0_COLUMN) { newNode.groupByNodes.push(new DataCubeDimensionalGroupByNode(clickedColumn, safeEvent.value, clickedDimension)); } const alreadyExists = targetNode.childNodes.some((child) => child.column === newNode.column && child.hashCode === newNode.hashCode); if (!alreadyExists) { targetNode.childNodes.push(newNode); } } break; // done } return safeTree; } export function removeSubtreeNode(tree, targetDimension, targetColumn, targetGroupByNodes) { // Iterate only the roots of the matching dimension for (const root of tree.rootNodes) { if (root.dimension !== targetDimension) { continue; } const found = findNodeWithParent(root, targetColumn, targetGroupByNodes, null); if (!found) { continue; } const { parent, index } = found; if (parent) { // remove from its parent’s childNodes parent.childNodes.splice(index, 1); } else { // remove a root‐level node // TODO: do nothing } return true; // removed successfully } return false; // no matching node found } function findNodeWithParent(current, targetColumn, targetGroupByNodes, parent = null) { if (current.column === targetColumn && findNodeByColumnOrGroup(current, targetColumn, targetGroupByNodes)) { if (parent) { const idx = parent.childNodes.findIndex((c) => c === current); return { parent, index: idx }; } else { // matched a root node return { parent: null, index: -1 }; } } for (const child of current.childNodes) { const found = findNodeWithParent(child, targetColumn, targetGroupByNodes, current); if (found) { return found; } } return null; } function findNodeByColumnOrGroup(node, targetColumn, targetGroupByNodes) { if (node.column === targetColumn && hashArray(node.groupByNodes) === hashArray(targetGroupByNodes)) { return node; } for (const child of node.childNodes) { const match = findNodeByColumnOrGroup(child, targetColumn, targetGroupByNodes); if (match) { return match; } } return undefined; } function pathSignature(path) { return path .map((node) => { const base = `${node.dimension}:${node.column}`; if (node.groupByNodes.length > 0) { const filters = node.groupByNodes .map((g) => `${g.dimension}:${g.column}:${g.filter}`) .join(','); return `${base}->[${filters}]`; } else { return `${base}->[NO_FILTER]`; } }) .join('|'); } //TODO: we can add oldPath signature directly in the snapshot export function findExtraPaths(oldPaths, newPaths) { const oldSignatures = new Set(oldPaths.map(pathSignature)); return newPaths.filter((path) => !oldSignatures.has(pathSignature(path))); } //# sourceMappingURL=DataCubeGridDimensionalTree.js.map