tm1npm 1.5.3 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/services/ApplicationService.d.ts +19 -3
- package/lib/services/ApplicationService.d.ts.map +1 -1
- package/lib/services/ApplicationService.js +232 -6
- package/lib/services/ElementService.d.ts +67 -1
- package/lib/services/ElementService.d.ts.map +1 -1
- package/lib/services/ElementService.js +214 -0
- package/lib/services/HierarchyService.d.ts +26 -0
- package/lib/services/HierarchyService.d.ts.map +1 -1
- package/lib/services/HierarchyService.js +306 -0
- package/lib/services/ProcessService.d.ts +22 -9
- package/lib/services/ProcessService.d.ts.map +1 -1
- package/lib/services/ProcessService.js +94 -98
- package/lib/services/RestService.d.ts.map +1 -1
- package/lib/services/RestService.js +11 -2
- package/lib/services/SubsetService.d.ts +2 -0
- package/lib/services/SubsetService.d.ts.map +1 -1
- package/lib/services/SubsetService.js +33 -0
- package/lib/services/TM1Service.d.ts +2 -0
- package/lib/services/TM1Service.d.ts.map +1 -1
- package/lib/services/TM1Service.js +2 -0
- package/lib/services/index.d.ts +1 -1
- package/lib/services/index.d.ts.map +1 -1
- package/lib/tests/100PercentParityCheck.test.js +23 -6
- package/lib/tests/applicationService.issue38.test.d.ts +5 -0
- package/lib/tests/applicationService.issue38.test.d.ts.map +1 -0
- package/lib/tests/applicationService.issue38.test.js +237 -0
- package/lib/tests/bugfix28.test.js +12 -4
- package/lib/tests/elementService.issue37.test.d.ts +5 -0
- package/lib/tests/elementService.issue37.test.d.ts.map +1 -0
- package/lib/tests/elementService.issue37.test.js +413 -0
- package/lib/tests/elementService.issue38.test.d.ts +5 -0
- package/lib/tests/elementService.issue38.test.d.ts.map +1 -0
- package/lib/tests/elementService.issue38.test.js +79 -0
- package/lib/tests/hierarchyService.issue38.test.d.ts +5 -0
- package/lib/tests/hierarchyService.issue38.test.d.ts.map +1 -0
- package/lib/tests/hierarchyService.issue38.test.js +460 -0
- package/lib/tests/processService.comprehensive.test.js +7 -7
- package/lib/tests/processService.test.js +220 -0
- package/lib/tests/subsetService.issue38.test.d.ts +5 -0
- package/lib/tests/subsetService.issue38.test.d.ts.map +1 -0
- package/lib/tests/subsetService.issue38.test.js +113 -0
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/services/ApplicationService.ts +278 -6
- package/src/services/ElementService.ts +322 -1
- package/src/services/HierarchyService.ts +419 -1
- package/src/services/ProcessService.ts +124 -111
- package/src/services/RestService.ts +15 -3
- package/src/services/SubsetService.ts +48 -0
- package/src/services/TM1Service.ts +3 -0
- package/src/services/index.ts +1 -1
- package/src/tests/100PercentParityCheck.test.ts +29 -8
- package/src/tests/applicationService.issue38.test.ts +293 -0
- package/src/tests/bugfix28.test.ts +12 -4
- package/src/tests/elementService.issue37.test.ts +571 -0
- package/src/tests/elementService.issue38.test.ts +103 -0
- package/src/tests/hierarchyService.issue38.test.ts +599 -0
- package/src/tests/processService.comprehensive.test.ts +7 -7
- package/src/tests/processService.test.ts +282 -2
- package/src/tests/subsetService.issue38.test.ts +182 -0
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
2
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
12
|
exports.ElementService = exports.MDXDrillMethod = void 0;
|
|
4
13
|
const ObjectService_1 = require("./ObjectService");
|
|
5
14
|
const Element_1 = require("../objects/Element");
|
|
6
15
|
const ElementAttribute_1 = require("../objects/ElementAttribute");
|
|
7
16
|
const Process_1 = require("../objects/Process");
|
|
17
|
+
const ProcessService_1 = require("./ProcessService");
|
|
18
|
+
const HierarchyService_1 = require("./HierarchyService");
|
|
19
|
+
const CellService_1 = require("./CellService");
|
|
8
20
|
const Utils_1 = require("../utils/Utils");
|
|
9
21
|
var MDXDrillMethod;
|
|
10
22
|
(function (MDXDrillMethod) {
|
|
@@ -225,6 +237,14 @@ class ElementService extends ObjectService_1.ObjectService {
|
|
|
225
237
|
const body = elements.map(e => e.bodyAsDict);
|
|
226
238
|
return await this.rest.post(url, JSON.stringify(body));
|
|
227
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Add multiple element attributes in bulk
|
|
242
|
+
*/
|
|
243
|
+
async addElementAttributes(dimensionName, hierarchyName, elementAttributes) {
|
|
244
|
+
const url = (0, Utils_1.formatUrl)("/Dimensions('{}')/Hierarchies('{}')/ElementAttributes", dimensionName, hierarchyName);
|
|
245
|
+
const body = elementAttributes.map(ea => ea.bodyAsDict);
|
|
246
|
+
return await this.rest.post(url, JSON.stringify(body));
|
|
247
|
+
}
|
|
228
248
|
/**
|
|
229
249
|
* Delete multiple elements in bulk
|
|
230
250
|
*/
|
|
@@ -716,6 +736,200 @@ class ElementService extends ObjectService_1.ObjectService {
|
|
|
716
736
|
}
|
|
717
737
|
return elements.map(name => `[${dimensionName}].[${hierarchy}].[${name}]`);
|
|
718
738
|
}
|
|
739
|
+
async getNumberOfConsolidatedElements(dimensionName, hierarchyName) {
|
|
740
|
+
return this._getElementCountWithFilter(dimensionName, hierarchyName, `Type eq ${Element_1.ElementType.CONSOLIDATED}`);
|
|
741
|
+
}
|
|
742
|
+
async getNumberOfLeafElements(dimensionName, hierarchyName) {
|
|
743
|
+
return this._getElementCountWithFilter(dimensionName, hierarchyName, `Type ne ${Element_1.ElementType.CONSOLIDATED}`);
|
|
744
|
+
}
|
|
745
|
+
async getNumberOfNumericElements(dimensionName, hierarchyName) {
|
|
746
|
+
return this._getElementCountWithFilter(dimensionName, hierarchyName, `Type eq ${Element_1.ElementType.NUMERIC}`);
|
|
747
|
+
}
|
|
748
|
+
async getNumberOfStringElements(dimensionName, hierarchyName) {
|
|
749
|
+
return this._getElementCountWithFilter(dimensionName, hierarchyName, `Type eq ${Element_1.ElementType.STRING}`);
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Get element types from all hierarchies in a single API call
|
|
753
|
+
*/
|
|
754
|
+
async getElementTypesFromAllHierarchies(dimensionName, skipConsolidations = false) {
|
|
755
|
+
let url = (0, Utils_1.formatUrl)("/Dimensions('{}')?$expand=Hierarchies($select=Elements;$expand=Elements($select=Name,Type", dimensionName);
|
|
756
|
+
url += skipConsolidations ? ";$filter=Type ne 3))" : "))";
|
|
757
|
+
const response = await this.rest.get(url);
|
|
758
|
+
const result = new Utils_1.CaseAndSpaceInsensitiveDict();
|
|
759
|
+
for (const hierarchy of response.data.Hierarchies) {
|
|
760
|
+
for (const element of hierarchy.Elements) {
|
|
761
|
+
result.set(element.Name, element.Type);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return result;
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Check if the element attributes cube exists for a dimension
|
|
768
|
+
*/
|
|
769
|
+
async attributeCubeExists(dimensionName) {
|
|
770
|
+
const url = (0, Utils_1.formatUrl)("/Cubes('{}')", Element_1.Element.ELEMENT_ATTRIBUTES_PREFIX + dimensionName);
|
|
771
|
+
return this._exists(url);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Get parent mapping for all elements in a hierarchy
|
|
775
|
+
*/
|
|
776
|
+
async getParentsOfAllElements(dimensionName, hierarchyName) {
|
|
777
|
+
const url = (0, Utils_1.formatUrl)("/Dimensions('{}')/Hierarchies('{}')/Elements?$select=Name&$expand=Parents($select=Name)", dimensionName, hierarchyName);
|
|
778
|
+
const response = await this.rest.get(url);
|
|
779
|
+
const result = {};
|
|
780
|
+
for (const child of response.data.value) {
|
|
781
|
+
result[child.Name] = (child.Parents || []).map((p) => p.Name);
|
|
782
|
+
}
|
|
783
|
+
return result;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Get the canonical/principal name of an element (resolves aliases)
|
|
787
|
+
*/
|
|
788
|
+
async getElementPrincipalName(dimensionName, hierarchyName, elementName) {
|
|
789
|
+
const element = await this.get(dimensionName, hierarchyName, elementName);
|
|
790
|
+
return element.name;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Check if one element is a direct parent of another
|
|
794
|
+
*
|
|
795
|
+
* Unlike the related function in TM1 (ELISPAR or ElementIsParent), this function will return false
|
|
796
|
+
* if an invalid element is passed. An invalid dimension or hierarchy will cause the underlying
|
|
797
|
+
* REST call to throw (the error propagates from the TM1 server).
|
|
798
|
+
*/
|
|
799
|
+
async elementIsParent(dimensionName, hierarchyName, parentName, elementName) {
|
|
800
|
+
const mdx = this._buildDrillIntersectionMdx(dimensionName, hierarchyName, parentName, elementName, 'TM1DrillDownMember', false);
|
|
801
|
+
const cardinality = await this._getMdxSetCardinality(mdx);
|
|
802
|
+
return cardinality > 0;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Check if one element is an ancestor of another
|
|
806
|
+
*
|
|
807
|
+
* Unlike the related function in TM1 (ELISANC or ElementIsAncestor), this function will return false
|
|
808
|
+
* if an invalid element is passed; but will raise an exception if an invalid dimension or hierarchy is passed.
|
|
809
|
+
*
|
|
810
|
+
* For method you can pass three values:
|
|
811
|
+
* - 'TI' performs best, but requires admin permissions
|
|
812
|
+
* - 'TM1DrillDownMember' performs well when element is a leaf
|
|
813
|
+
* - 'Descendants' performs well when ancestorName and elementName are Consolidations
|
|
814
|
+
*
|
|
815
|
+
* If no method is passed, defaults to 'TI' for admin users, 'TM1DrillDownMember' otherwise.
|
|
816
|
+
* Note: isAdmin is determined from RestService state; if not set, defaults to 'TM1DrillDownMember'.
|
|
817
|
+
*/
|
|
818
|
+
async elementIsAncestor(dimensionName, hierarchyName, ancestorName, elementName, method) {
|
|
819
|
+
if (!method) {
|
|
820
|
+
method = this.isAdmin ? 'TI' : 'TM1DrillDownMember';
|
|
821
|
+
}
|
|
822
|
+
if (method.toUpperCase() === 'TI') {
|
|
823
|
+
if (await this._elementIsAncestorTi(dimensionName, hierarchyName, elementName, ancestorName)) {
|
|
824
|
+
return true;
|
|
825
|
+
}
|
|
826
|
+
if (await this.hierarchyExists(dimensionName, hierarchyName)) {
|
|
827
|
+
return false;
|
|
828
|
+
}
|
|
829
|
+
throw new Error(`Hierarchy: '${hierarchyName}' does not exist in dimension: '${dimensionName}'`);
|
|
830
|
+
}
|
|
831
|
+
if (method.toUpperCase() === 'DESCENDANTS' || method.toUpperCase() === 'TM1DRILLDOWNMEMBER') {
|
|
832
|
+
if (!await this.exists(dimensionName, hierarchyName, elementName)) {
|
|
833
|
+
if (!await this.hierarchyExists(dimensionName, hierarchyName)) {
|
|
834
|
+
throw new Error(`Hierarchy '${hierarchyName}' does not exist in dimension '${dimensionName}'`);
|
|
835
|
+
}
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
const mdx = this._buildDrillIntersectionMdx(dimensionName, hierarchyName, ancestorName, elementName, method, true);
|
|
840
|
+
const cardinality = await this._getMdxSetCardinality(mdx);
|
|
841
|
+
return cardinality > 0;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Remove a single edge from a hierarchy
|
|
845
|
+
*/
|
|
846
|
+
async removeEdge(dimensionName, hierarchyName, parentName, componentName) {
|
|
847
|
+
const url = (0, Utils_1.formatUrl)("/Dimensions('{}')/Hierarchies('{}')/Elements('{}')/Edges(ParentName='{}',ComponentName='{}')", dimensionName, hierarchyName, parentName, parentName, componentName);
|
|
848
|
+
return this.rest.delete(url);
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Check if a hierarchy exists in a dimension (convenience delegation to HierarchyService)
|
|
852
|
+
*/
|
|
853
|
+
async hierarchyExists(dimensionName, hierarchyName) {
|
|
854
|
+
const hierarchyService = new HierarchyService_1.HierarchyService(this.rest);
|
|
855
|
+
return hierarchyService.exists(dimensionName, hierarchyName);
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Get all element names and alias values for leaf elements as a case-and-space-insensitive set
|
|
859
|
+
*/
|
|
860
|
+
async getAllLeafElementIdentifiers(dimensionName, hierarchyName) {
|
|
861
|
+
const mdxElements = `{ Tm1FilterByLevel ( { Tm1SubsetAll ([${dimensionName}].[${hierarchyName}]) } , 0 ) }`;
|
|
862
|
+
const aliasAttributes = await this.getAliasElementAttributes(dimensionName, hierarchyName);
|
|
863
|
+
if (aliasAttributes.length === 0) {
|
|
864
|
+
const result = await this.executeSetMdx(mdxElements, undefined, ['Name'], null, null);
|
|
865
|
+
const identifiers = new Utils_1.CaseAndSpaceInsensitiveSet();
|
|
866
|
+
for (const record of result) {
|
|
867
|
+
identifiers.add(record[0].Name);
|
|
868
|
+
}
|
|
869
|
+
return identifiers;
|
|
870
|
+
}
|
|
871
|
+
const attrMdx = aliasAttributes.map(a => `[}ElementAttributes_${dimensionName}].[}ElementAttributes_${dimensionName}].[${a}]`).join(',');
|
|
872
|
+
const mdx = `SELECT ${mdxElements} ON ROWS, {${attrMdx}} ON COLUMNS FROM [}ElementAttributes_${dimensionName}]`;
|
|
873
|
+
return this._retrieveMdxRowsAndCellValuesAsStringSet(mdx);
|
|
874
|
+
}
|
|
875
|
+
async _getElementCountWithFilter(dimensionName, hierarchyName, filter) {
|
|
876
|
+
const baseUrl = (0, Utils_1.formatUrl)("/Dimensions('{}')/Hierarchies('{}')/Elements/$count", dimensionName, hierarchyName);
|
|
877
|
+
const url = `${baseUrl}?$filter=${filter}`;
|
|
878
|
+
const response = await this.rest.get(url);
|
|
879
|
+
return parseInt(response.data) || 0;
|
|
880
|
+
}
|
|
881
|
+
_buildDrillIntersectionMdx(dimensionName, hierarchyName, firstElementName, secondElementName, mdxMethod, recursive) {
|
|
882
|
+
const first = `[${dimensionName}].[${hierarchyName}].[${firstElementName}]`;
|
|
883
|
+
const second = `[${dimensionName}].[${hierarchyName}].[${secondElementName}]`;
|
|
884
|
+
let drillSet;
|
|
885
|
+
if (mdxMethod.toUpperCase() === 'TM1DRILLDOWNMEMBER') {
|
|
886
|
+
drillSet = recursive
|
|
887
|
+
? `{TM1DRILLDOWNMEMBER({${first}}, ALL, RECURSIVE)}`
|
|
888
|
+
: `{TM1DRILLDOWNMEMBER({${first}}, ALL)}`;
|
|
889
|
+
}
|
|
890
|
+
else if (mdxMethod.toUpperCase() === 'DESCENDANTS') {
|
|
891
|
+
drillSet = `{DESCENDANTS(${first}, ${second}.Level, SELF)}`;
|
|
892
|
+
}
|
|
893
|
+
else {
|
|
894
|
+
throw new Error("Invalid MDX Drill Method. Options: 'TM1DrillDownMember' or 'Descendants'");
|
|
895
|
+
}
|
|
896
|
+
return `INTERSECT(${drillSet}, {${second}})`;
|
|
897
|
+
}
|
|
898
|
+
async _getMdxSetCardinality(mdx) {
|
|
899
|
+
const url = '/ExecuteMDXSetExpression?$select=Cardinality';
|
|
900
|
+
const payload = { MDX: mdx };
|
|
901
|
+
const response = await this.rest.post(url, JSON.stringify(payload));
|
|
902
|
+
return response.data.Cardinality || 0;
|
|
903
|
+
}
|
|
904
|
+
async _elementIsAncestorTi(dimensionName, hierarchyName, elementName, ancestorName) {
|
|
905
|
+
const processService = new ProcessService_1.ProcessService(this.rest);
|
|
906
|
+
const code = `ElementIsAncestor('${(0, Utils_1.escapeODataValue)(dimensionName)}', '${(0, Utils_1.escapeODataValue)(hierarchyName)}', '${(0, Utils_1.escapeODataValue)(ancestorName)}', '${(0, Utils_1.escapeODataValue)(elementName)}')=1`;
|
|
907
|
+
return processService.evaluateBooleanTiExpression(code);
|
|
908
|
+
}
|
|
909
|
+
async _retrieveMdxRowsAndCellValuesAsStringSet(mdx) {
|
|
910
|
+
const cellService = new CellService_1.CellService(this.rest);
|
|
911
|
+
const { rows, values } = await cellService.executeMdxRowsAndValues(mdx);
|
|
912
|
+
const result = new Utils_1.CaseAndSpaceInsensitiveSet();
|
|
913
|
+
for (const row of rows) {
|
|
914
|
+
for (const name of row) {
|
|
915
|
+
if (name) {
|
|
916
|
+
result.add(name);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
for (const value of values) {
|
|
921
|
+
if (value && typeof value === 'string' && value.trim() !== '') {
|
|
922
|
+
result.add(value);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return result;
|
|
926
|
+
}
|
|
719
927
|
}
|
|
720
928
|
exports.ElementService = ElementService;
|
|
929
|
+
__decorate([
|
|
930
|
+
Utils_1.requireDataAdmin,
|
|
931
|
+
__metadata("design:type", Function),
|
|
932
|
+
__metadata("design:paramtypes", [String, String, String, String]),
|
|
933
|
+
__metadata("design:returntype", Promise)
|
|
934
|
+
], ElementService.prototype, "_elementIsAncestorTi", null);
|
|
721
935
|
//# sourceMappingURL=ElementService.js.map
|
|
@@ -2,16 +2,42 @@ import { AxiosResponse } from 'axios';
|
|
|
2
2
|
import { RestService } from './RestService';
|
|
3
3
|
import { Hierarchy } from '../objects/Hierarchy';
|
|
4
4
|
import { ElementAttribute } from '../objects/ElementAttribute';
|
|
5
|
+
import { Element } from '../objects/Element';
|
|
5
6
|
import { ObjectService } from './ObjectService';
|
|
7
|
+
import { DataFrame } from '../utils/DataFrame';
|
|
6
8
|
export declare class HierarchyService extends ObjectService {
|
|
9
|
+
private elementService?;
|
|
7
10
|
constructor(rest: RestService);
|
|
11
|
+
private getElementService;
|
|
8
12
|
create(hierarchy: Hierarchy): Promise<AxiosResponse>;
|
|
9
13
|
get(dimensionName: string, hierarchyName?: string): Promise<Hierarchy>;
|
|
10
14
|
update(hierarchy: Hierarchy, keepExistingAttributes?: boolean): Promise<AxiosResponse[]>;
|
|
15
|
+
updateOrCreate(hierarchy: Hierarchy): Promise<AxiosResponse | AxiosResponse[]>;
|
|
11
16
|
delete(dimensionName: string, hierarchyName: string): Promise<AxiosResponse>;
|
|
12
17
|
exists(dimensionName: string, hierarchyName: string): Promise<boolean>;
|
|
13
18
|
getAllNames(dimensionName: string): Promise<string[]>;
|
|
14
19
|
getAll(dimensionName: string): Promise<Hierarchy[]>;
|
|
20
|
+
getHierarchySummary(dimensionName: string, hierarchyName?: string): Promise<Record<string, number>>;
|
|
21
|
+
getDefaultMember(dimensionName: string, hierarchyName?: string): Promise<string | null>;
|
|
22
|
+
updateDefaultMember(dimensionName: string, hierarchyName?: string, memberName?: string): Promise<AxiosResponse | void>;
|
|
23
|
+
private _updateDefaultMemberViaApi;
|
|
24
|
+
private _updateDefaultMemberViaPropsCube;
|
|
25
|
+
removeEdgesUnderConsolidation(dimensionName: string, hierarchyName: string, consolidationElement: string): Promise<AxiosResponse[]>;
|
|
26
|
+
addEdges(dimensionName: string, hierarchyName: string | undefined, edges: {
|
|
27
|
+
[parent: string]: {
|
|
28
|
+
[child: string]: number;
|
|
29
|
+
};
|
|
30
|
+
}): Promise<AxiosResponse>;
|
|
31
|
+
addElements(dimensionName: string, hierarchyName: string, elements: Element[]): Promise<AxiosResponse>;
|
|
32
|
+
addElementAttributes(dimensionName: string, hierarchyName: string, elementAttributes: ElementAttribute[]): Promise<AxiosResponse>;
|
|
33
|
+
updateOrCreateHierarchyFromDataframe(dimensionName: string, hierarchyName: string, df: DataFrame, options?: {
|
|
34
|
+
elementColumn?: string;
|
|
35
|
+
verifyUniqueElements?: boolean;
|
|
36
|
+
elementTypeColumn?: string;
|
|
37
|
+
unwindAll?: boolean;
|
|
38
|
+
unwindConsolidations?: string[];
|
|
39
|
+
deleteOrphanedConsolidations?: boolean;
|
|
40
|
+
}): Promise<void>;
|
|
15
41
|
updateElementAttributes(hierarchy: Hierarchy, keepExisting?: boolean): Promise<void>;
|
|
16
42
|
getElementAttributes(dimensionName: string, hierarchyName: string): Promise<ElementAttribute[]>;
|
|
17
43
|
elementAttributeExists(dimensionName: string, hierarchyName: string, attributeName: string): Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HierarchyService.d.ts","sourceRoot":"","sources":["../../src/services/HierarchyService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"HierarchyService.d.ts","sourceRoot":"","sources":["../../src/services/HierarchyService.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,13 +3,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.HierarchyService = void 0;
|
|
4
4
|
const Hierarchy_1 = require("../objects/Hierarchy");
|
|
5
5
|
const ElementAttribute_1 = require("../objects/ElementAttribute");
|
|
6
|
+
const Element_1 = require("../objects/Element");
|
|
7
|
+
const Dimension_1 = require("../objects/Dimension");
|
|
6
8
|
const TM1Exception_1 = require("../exceptions/TM1Exception");
|
|
7
9
|
const ObjectService_1 = require("./ObjectService");
|
|
10
|
+
const ElementService_1 = require("./ElementService");
|
|
11
|
+
const CellService_1 = require("./CellService");
|
|
12
|
+
const DimensionService_1 = require("./DimensionService");
|
|
8
13
|
const Utils_1 = require("../utils/Utils");
|
|
9
14
|
class HierarchyService extends ObjectService_1.ObjectService {
|
|
10
15
|
constructor(rest) {
|
|
11
16
|
super(rest);
|
|
12
17
|
}
|
|
18
|
+
getElementService() {
|
|
19
|
+
if (!this.elementService) {
|
|
20
|
+
this.elementService = new ElementService_1.ElementService(this.rest);
|
|
21
|
+
}
|
|
22
|
+
return this.elementService;
|
|
23
|
+
}
|
|
13
24
|
async create(hierarchy) {
|
|
14
25
|
const url = this.formatUrl("/Dimensions('{}')/Hierarchies", hierarchy.dimensionName);
|
|
15
26
|
const response = await this.rest.post(url, hierarchy.body);
|
|
@@ -30,6 +41,12 @@ class HierarchyService extends ObjectService_1.ObjectService {
|
|
|
30
41
|
await this.updateElementAttributes(hierarchy, keepExistingAttributes);
|
|
31
42
|
return responses;
|
|
32
43
|
}
|
|
44
|
+
async updateOrCreate(hierarchy) {
|
|
45
|
+
if (await this.exists(hierarchy.dimensionName, hierarchy.name)) {
|
|
46
|
+
return await this.update(hierarchy);
|
|
47
|
+
}
|
|
48
|
+
return await this.create(hierarchy);
|
|
49
|
+
}
|
|
33
50
|
async delete(dimensionName, hierarchyName) {
|
|
34
51
|
const url = this.formatUrl("/Dimensions('{}')/Hierarchies('{}')", dimensionName, hierarchyName);
|
|
35
52
|
return await this.rest.delete(url);
|
|
@@ -58,6 +75,295 @@ class HierarchyService extends ObjectService_1.ObjectService {
|
|
|
58
75
|
const response = await this.rest.get(url);
|
|
59
76
|
return response.data.value.map((h) => Hierarchy_1.Hierarchy.fromDict(h, dimensionName));
|
|
60
77
|
}
|
|
78
|
+
async getHierarchySummary(dimensionName, hierarchyName) {
|
|
79
|
+
var _a, _b, _c, _d, _e;
|
|
80
|
+
const hierarchy = hierarchyName || dimensionName;
|
|
81
|
+
const url = this.formatUrl("/Dimensions('{}')/Hierarchies('{}')" +
|
|
82
|
+
"?$expand=Edges/$count,Elements/$count,ElementAttributes/$count,Members/$count,Levels/$count" +
|
|
83
|
+
"&$select=Cardinality", dimensionName, hierarchy);
|
|
84
|
+
const response = await this.rest.get(url);
|
|
85
|
+
const data = response.data;
|
|
86
|
+
return {
|
|
87
|
+
Elements: (_a = data['Elements@odata.count']) !== null && _a !== void 0 ? _a : 0,
|
|
88
|
+
Edges: (_b = data['Edges@odata.count']) !== null && _b !== void 0 ? _b : 0,
|
|
89
|
+
ElementAttributes: (_c = data['ElementAttributes@odata.count']) !== null && _c !== void 0 ? _c : 0,
|
|
90
|
+
Members: (_d = data['Members@odata.count']) !== null && _d !== void 0 ? _d : 0,
|
|
91
|
+
Levels: (_e = data['Levels@odata.count']) !== null && _e !== void 0 ? _e : 0
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async getDefaultMember(dimensionName, hierarchyName) {
|
|
95
|
+
var _a;
|
|
96
|
+
const hierarchy = hierarchyName || dimensionName;
|
|
97
|
+
const url = this.formatUrl("/Dimensions('{}')/Hierarchies('{}')/DefaultMember", dimensionName, hierarchy);
|
|
98
|
+
try {
|
|
99
|
+
const response = await this.rest.get(url);
|
|
100
|
+
return ((_a = response.data) === null || _a === void 0 ? void 0 : _a.Name) || null;
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (error instanceof TM1Exception_1.TM1RestException && error.statusCode === 404) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async updateDefaultMember(dimensionName, hierarchyName, memberName = '') {
|
|
110
|
+
const hierarchy = hierarchyName || dimensionName;
|
|
111
|
+
// Default to API approach (v12+) when version is unknown
|
|
112
|
+
if (!this.version || (0, Utils_1.verifyVersion)(this.version, '12.0.0')) {
|
|
113
|
+
return this._updateDefaultMemberViaApi(dimensionName, hierarchy, memberName);
|
|
114
|
+
}
|
|
115
|
+
return this._updateDefaultMemberViaPropsCube(dimensionName, hierarchy, memberName);
|
|
116
|
+
}
|
|
117
|
+
async _updateDefaultMemberViaApi(dimensionName, hierarchyName, memberName) {
|
|
118
|
+
const url = this.formatUrl("/Dimensions('{}')/Hierarchies('{}')", dimensionName, hierarchyName);
|
|
119
|
+
const body = {};
|
|
120
|
+
if (memberName) {
|
|
121
|
+
body['DefaultMember@odata.bind'] = (0, Utils_1.formatUrl)("Dimensions('{}')/Hierarchies('{}')/Elements('{}')", dimensionName, hierarchyName, memberName);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
body['DefaultMember@odata.bind'] = null;
|
|
125
|
+
}
|
|
126
|
+
return await this.rest.patch(url, JSON.stringify(body));
|
|
127
|
+
}
|
|
128
|
+
async _updateDefaultMemberViaPropsCube(dimensionName, hierarchyName, memberName) {
|
|
129
|
+
const cellService = new CellService_1.CellService(this.rest);
|
|
130
|
+
const value = memberName || '';
|
|
131
|
+
await cellService.writeValue('}HierarchyProperties', [dimensionName, hierarchyName, 'MEMBER_DEFAULT'], value);
|
|
132
|
+
}
|
|
133
|
+
async removeEdgesUnderConsolidation(dimensionName, hierarchyName, consolidationElement) {
|
|
134
|
+
const hierarchy = await this.get(dimensionName, hierarchyName);
|
|
135
|
+
const descendants = hierarchy.getDescendants(consolidationElement, true);
|
|
136
|
+
const membersUnderConsolidation = new Utils_1.CaseAndSpaceInsensitiveSet(descendants);
|
|
137
|
+
membersUnderConsolidation.add(consolidationElement);
|
|
138
|
+
const edgesToRemove = [];
|
|
139
|
+
for (const [parent, children] of hierarchy.edges) {
|
|
140
|
+
if (membersUnderConsolidation.has(parent)) {
|
|
141
|
+
for (const child of children.keys()) {
|
|
142
|
+
edgesToRemove.push([parent, child]);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const [parent, child] of edgesToRemove) {
|
|
147
|
+
hierarchy.removeEdge(parent, child);
|
|
148
|
+
}
|
|
149
|
+
return await this.update(hierarchy);
|
|
150
|
+
}
|
|
151
|
+
async addEdges(dimensionName, hierarchyName, edges) {
|
|
152
|
+
return this.getElementService().addEdges(dimensionName, hierarchyName || dimensionName, edges);
|
|
153
|
+
}
|
|
154
|
+
async addElements(dimensionName, hierarchyName, elements) {
|
|
155
|
+
return this.getElementService().addElements(dimensionName, hierarchyName, elements);
|
|
156
|
+
}
|
|
157
|
+
async addElementAttributes(dimensionName, hierarchyName, elementAttributes) {
|
|
158
|
+
return this.getElementService().addElementAttributes(dimensionName, hierarchyName, elementAttributes);
|
|
159
|
+
}
|
|
160
|
+
async updateOrCreateHierarchyFromDataframe(dimensionName, hierarchyName, df, options = {}) {
|
|
161
|
+
const { elementColumn: elemCol, verifyUniqueElements = false, elementTypeColumn = 'ElementType', unwindAll = false, unwindConsolidations, deleteOrphanedConsolidations = false } = options;
|
|
162
|
+
const elementColumn = elemCol || df.columns[0];
|
|
163
|
+
const rows = df.toJson();
|
|
164
|
+
const numericSuffix = (col) => parseInt(col.replace(/\D/g, ''), 10);
|
|
165
|
+
const levelColumns = df.columns
|
|
166
|
+
.filter(c => /^Level\d+$/i.test(c))
|
|
167
|
+
.sort((a, b) => numericSuffix(a) - numericSuffix(b));
|
|
168
|
+
const weightColumns = df.columns
|
|
169
|
+
.filter(c => /^Weight\d+$/i.test(c))
|
|
170
|
+
.sort((a, b) => numericSuffix(a) - numericSuffix(b));
|
|
171
|
+
const reservedColumns = new Set([
|
|
172
|
+
elementColumn.toLowerCase(),
|
|
173
|
+
elementTypeColumn.toLowerCase(),
|
|
174
|
+
...levelColumns.map(c => c.toLowerCase()),
|
|
175
|
+
...weightColumns.map(c => c.toLowerCase())
|
|
176
|
+
]);
|
|
177
|
+
const attributeColumns = df.columns.filter(c => !reservedColumns.has(c.toLowerCase()));
|
|
178
|
+
if (verifyUniqueElements) {
|
|
179
|
+
const seen = new Utils_1.CaseAndSpaceInsensitiveSet();
|
|
180
|
+
for (const row of rows) {
|
|
181
|
+
const name = String(row[elementColumn]);
|
|
182
|
+
if (seen.has(name)) {
|
|
183
|
+
throw new Error(`Duplicate element found: '${name}'`);
|
|
184
|
+
}
|
|
185
|
+
seen.add(name);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Parse DataFrame rows into elements, edges, and attribute values
|
|
189
|
+
const elementsToAdd = [];
|
|
190
|
+
const edgesToAdd = {};
|
|
191
|
+
const attributeValues = new Map();
|
|
192
|
+
for (const row of rows) {
|
|
193
|
+
const elementName = String(row[elementColumn]);
|
|
194
|
+
const elementTypeStr = df.columns.includes(elementTypeColumn)
|
|
195
|
+
? String(row[elementTypeColumn] || 'Numeric')
|
|
196
|
+
: 'Numeric';
|
|
197
|
+
// Element constructor handles string→ElementType conversion
|
|
198
|
+
elementsToAdd.push(new Element_1.Element(elementName, elementTypeStr));
|
|
199
|
+
for (let i = 0; i < levelColumns.length; i++) {
|
|
200
|
+
const parentName = row[levelColumns[i]];
|
|
201
|
+
if (parentName && String(parentName).trim()) {
|
|
202
|
+
const weight = weightColumns[i] ? Number(row[weightColumns[i]] || 1) : 1;
|
|
203
|
+
const parentStr = String(parentName);
|
|
204
|
+
if (!edgesToAdd[parentStr]) {
|
|
205
|
+
edgesToAdd[parentStr] = {};
|
|
206
|
+
}
|
|
207
|
+
edgesToAdd[parentStr][elementName] = weight;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (attributeColumns.length > 0) {
|
|
211
|
+
const attrs = new Map();
|
|
212
|
+
for (const col of attributeColumns) {
|
|
213
|
+
if (row[col] !== undefined && row[col] !== null && row[col] !== '') {
|
|
214
|
+
attrs.set(col, row[col]);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (attrs.size > 0) {
|
|
218
|
+
attributeValues.set(elementName, attrs);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Get or create the dimension/hierarchy
|
|
223
|
+
const dimensionService = new DimensionService_1.DimensionService(this.rest);
|
|
224
|
+
const hierarchyExists = await this.exists(dimensionName, hierarchyName).catch(() => false);
|
|
225
|
+
let hierarchy;
|
|
226
|
+
if (hierarchyExists) {
|
|
227
|
+
hierarchy = await this.get(dimensionName, hierarchyName);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
const newHierarchy = new Hierarchy_1.Hierarchy(hierarchyName, dimensionName);
|
|
231
|
+
try {
|
|
232
|
+
const dim = new Dimension_1.Dimension(dimensionName, [newHierarchy]);
|
|
233
|
+
await dimensionService.create(dim);
|
|
234
|
+
}
|
|
235
|
+
catch (e) {
|
|
236
|
+
const isAlreadyExists = (e instanceof TM1Exception_1.TM1RestException && e.statusCode === 409)
|
|
237
|
+
|| (e.message && e.message.includes('already exists'));
|
|
238
|
+
if (isAlreadyExists) {
|
|
239
|
+
// Dimension exists but hierarchy doesn't — create just the hierarchy
|
|
240
|
+
await this.create(newHierarchy);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
throw e;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
hierarchy = await this.get(dimensionName, hierarchyName);
|
|
247
|
+
}
|
|
248
|
+
// Unwind edges if requested
|
|
249
|
+
if (unwindAll) {
|
|
250
|
+
await this.removeAllEdges(dimensionName, hierarchyName);
|
|
251
|
+
}
|
|
252
|
+
else if (unwindConsolidations && unwindConsolidations.length > 0) {
|
|
253
|
+
for (const consolidation of unwindConsolidations) {
|
|
254
|
+
try {
|
|
255
|
+
await this.removeEdgesUnderConsolidation(dimensionName, hierarchyName, consolidation);
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
258
|
+
if (!(e instanceof TM1Exception_1.TM1RestException && e.statusCode === 404)) {
|
|
259
|
+
throw e;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Re-fetch hierarchy only if edges were modified
|
|
265
|
+
if (unwindAll || (unwindConsolidations && unwindConsolidations.length > 0)) {
|
|
266
|
+
hierarchy = await this.get(dimensionName, hierarchyName);
|
|
267
|
+
}
|
|
268
|
+
// Add new elements not already in hierarchy
|
|
269
|
+
const existingElementNames = new Utils_1.CaseAndSpaceInsensitiveSet(hierarchy.elements.map(e => e.name));
|
|
270
|
+
const newElements = elementsToAdd.filter(e => !existingElementNames.has(e.name));
|
|
271
|
+
// Ensure parent elements from edges exist as Consolidated type
|
|
272
|
+
const newElementNames = new Utils_1.CaseAndSpaceInsensitiveSet(newElements.map(e => e.name));
|
|
273
|
+
const parentElementsToAdd = [];
|
|
274
|
+
for (const parentName of Object.keys(edgesToAdd)) {
|
|
275
|
+
if (!existingElementNames.has(parentName) && !newElementNames.has(parentName)) {
|
|
276
|
+
parentElementsToAdd.push(new Element_1.Element(parentName, Element_1.ElementType.CONSOLIDATED));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const allNewElements = [...newElements, ...parentElementsToAdd];
|
|
280
|
+
if (allNewElements.length > 0) {
|
|
281
|
+
await this.addElements(dimensionName, hierarchyName, allNewElements);
|
|
282
|
+
}
|
|
283
|
+
// Create missing element attributes and write values
|
|
284
|
+
if (attributeColumns.length > 0) {
|
|
285
|
+
const existingAttrs = await this.getElementAttributes(dimensionName, hierarchyName);
|
|
286
|
+
const existingAttrNames = new Utils_1.CaseAndSpaceInsensitiveSet(existingAttrs.map(a => a.name));
|
|
287
|
+
const newAttrs = [];
|
|
288
|
+
for (const col of attributeColumns) {
|
|
289
|
+
if (!existingAttrNames.has(col)) {
|
|
290
|
+
const values = rows.map(r => r[col]).filter(v => v !== undefined && v !== null && v !== '');
|
|
291
|
+
const isNumeric = values.length > 0 && values.every(v => !isNaN(Number(v)));
|
|
292
|
+
newAttrs.push(new ElementAttribute_1.ElementAttribute(col, isNumeric ? 'Numeric' : 'String'));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (newAttrs.length > 0) {
|
|
296
|
+
await this.addElementAttributes(dimensionName, hierarchyName, newAttrs);
|
|
297
|
+
}
|
|
298
|
+
// Write attribute values in parallel batches
|
|
299
|
+
if (attributeValues.size > 0) {
|
|
300
|
+
const cellService = new CellService_1.CellService(this.rest);
|
|
301
|
+
const cubeName = `}ElementAttributes_${dimensionName}`;
|
|
302
|
+
const writePromises = [];
|
|
303
|
+
for (const [elementName, attrs] of attributeValues) {
|
|
304
|
+
for (const [attrName, value] of attrs) {
|
|
305
|
+
writePromises.push(cellService.writeValue(cubeName, [elementName, attrName], value)
|
|
306
|
+
.catch((e) => {
|
|
307
|
+
// Expected: element may not exist in control dimension
|
|
308
|
+
if (!(e instanceof TM1Exception_1.TM1RestException && e.statusCode === 404)) {
|
|
309
|
+
throw e;
|
|
310
|
+
}
|
|
311
|
+
}));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
await Promise.all(writePromises);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Add edges
|
|
318
|
+
if (Object.keys(edgesToAdd).length > 0) {
|
|
319
|
+
try {
|
|
320
|
+
await this.addEdges(dimensionName, hierarchyName, edgesToAdd);
|
|
321
|
+
}
|
|
322
|
+
catch (bulkError) {
|
|
323
|
+
// Only fall back on client errors (400, 422); re-throw server/network errors
|
|
324
|
+
if (!(bulkError instanceof TM1Exception_1.TM1RestException) ||
|
|
325
|
+
(bulkError.statusCode !== 400 && bulkError.statusCode !== 422)) {
|
|
326
|
+
throw bulkError;
|
|
327
|
+
}
|
|
328
|
+
// Bulk failed due to validation; fall back to individual edge adds
|
|
329
|
+
const edgePromises = [];
|
|
330
|
+
for (const [parent, children] of Object.entries(edgesToAdd)) {
|
|
331
|
+
for (const [child, weight] of Object.entries(children)) {
|
|
332
|
+
edgePromises.push(this.addEdges(dimensionName, hierarchyName, { [parent]: { [child]: weight } })
|
|
333
|
+
.catch((e) => {
|
|
334
|
+
if (e instanceof TM1Exception_1.TM1RestException && e.statusCode === 409) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
throw e;
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
await Promise.all(edgePromises);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Delete orphaned consolidations (consolidated elements with no children)
|
|
345
|
+
if (deleteOrphanedConsolidations) {
|
|
346
|
+
const updatedHierarchy = await this.get(dimensionName, hierarchyName);
|
|
347
|
+
const parentNames = new Utils_1.CaseAndSpaceInsensitiveSet();
|
|
348
|
+
for (const [parent] of updatedHierarchy.edges) {
|
|
349
|
+
parentNames.add(parent);
|
|
350
|
+
}
|
|
351
|
+
const elemService = this.getElementService();
|
|
352
|
+
const deletePromises = [];
|
|
353
|
+
for (const element of updatedHierarchy.elements) {
|
|
354
|
+
if (element.elementType === Element_1.ElementType.CONSOLIDATED && !parentNames.has(element.name)) {
|
|
355
|
+
deletePromises.push(elemService.delete(dimensionName, hierarchyName, element.name)
|
|
356
|
+
.catch((e) => {
|
|
357
|
+
// Expected: element may have been deleted already (404)
|
|
358
|
+
if (!(e instanceof TM1Exception_1.TM1RestException && e.statusCode === 404)) {
|
|
359
|
+
throw e;
|
|
360
|
+
}
|
|
361
|
+
}));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
await Promise.all(deletePromises);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
61
367
|
async updateElementAttributes(hierarchy, keepExisting = false) {
|
|
62
368
|
const existingAttributes = await this.getElementAttributes(hierarchy.dimensionName, hierarchy.name);
|
|
63
369
|
const existingByName = new Utils_1.CaseAndSpaceInsensitiveDict();
|