@finos/legend-data-cube
Version:
264 lines • 9.92 kB
JavaScript
/**
* 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