tm1npm 1.5.3 → 2.0.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/lib/index.d.ts +1 -1
  3. package/lib/index.d.ts.map +1 -1
  4. package/lib/services/ApplicationService.d.ts +19 -3
  5. package/lib/services/ApplicationService.d.ts.map +1 -1
  6. package/lib/services/ApplicationService.js +232 -6
  7. package/lib/services/AsyncOperationService.d.ts +8 -1
  8. package/lib/services/AsyncOperationService.d.ts.map +1 -1
  9. package/lib/services/AsyncOperationService.js +69 -26
  10. package/lib/services/ElementService.d.ts +67 -1
  11. package/lib/services/ElementService.d.ts.map +1 -1
  12. package/lib/services/ElementService.js +214 -0
  13. package/lib/services/FileService.d.ts.map +1 -1
  14. package/lib/services/HierarchyService.d.ts +26 -0
  15. package/lib/services/HierarchyService.d.ts.map +1 -1
  16. package/lib/services/HierarchyService.js +306 -0
  17. package/lib/services/ProcessService.d.ts +40 -22
  18. package/lib/services/ProcessService.d.ts.map +1 -1
  19. package/lib/services/ProcessService.js +118 -111
  20. package/lib/services/RestService.d.ts +213 -25
  21. package/lib/services/RestService.d.ts.map +1 -1
  22. package/lib/services/RestService.js +841 -263
  23. package/lib/services/SubsetService.d.ts +2 -0
  24. package/lib/services/SubsetService.d.ts.map +1 -1
  25. package/lib/services/SubsetService.js +33 -0
  26. package/lib/services/TM1Service.d.ts +44 -1
  27. package/lib/services/TM1Service.d.ts.map +1 -1
  28. package/lib/services/TM1Service.js +96 -4
  29. package/lib/services/index.d.ts +1 -1
  30. package/lib/services/index.d.ts.map +1 -1
  31. package/lib/tests/100PercentParityCheck.test.js +23 -6
  32. package/lib/tests/applicationService.issue38.test.d.ts +5 -0
  33. package/lib/tests/applicationService.issue38.test.d.ts.map +1 -0
  34. package/lib/tests/applicationService.issue38.test.js +237 -0
  35. package/lib/tests/asyncOperationService.test.js +51 -45
  36. package/lib/tests/bugfix28.test.js +12 -4
  37. package/lib/tests/elementService.issue37.test.d.ts +5 -0
  38. package/lib/tests/elementService.issue37.test.d.ts.map +1 -0
  39. package/lib/tests/elementService.issue37.test.js +413 -0
  40. package/lib/tests/elementService.issue38.test.d.ts +5 -0
  41. package/lib/tests/elementService.issue38.test.d.ts.map +1 -0
  42. package/lib/tests/elementService.issue38.test.js +79 -0
  43. package/lib/tests/hierarchyService.issue38.test.d.ts +5 -0
  44. package/lib/tests/hierarchyService.issue38.test.d.ts.map +1 -0
  45. package/lib/tests/hierarchyService.issue38.test.js +460 -0
  46. package/lib/tests/processService.comprehensive.test.js +9 -9
  47. package/lib/tests/processService.test.js +234 -0
  48. package/lib/tests/restService.test.d.ts +0 -4
  49. package/lib/tests/restService.test.d.ts.map +1 -1
  50. package/lib/tests/restService.test.js +1558 -143
  51. package/lib/tests/subsetService.issue38.test.d.ts +5 -0
  52. package/lib/tests/subsetService.issue38.test.d.ts.map +1 -0
  53. package/lib/tests/subsetService.issue38.test.js +113 -0
  54. package/lib/tests/tm1Service.test.js +80 -8
  55. package/package.json +1 -1
  56. package/src/index.ts +1 -1
  57. package/src/services/ApplicationService.ts +282 -10
  58. package/src/services/AsyncOperationService.ts +76 -29
  59. package/src/services/ElementService.ts +322 -1
  60. package/src/services/FileService.ts +3 -3
  61. package/src/services/HierarchyService.ts +419 -1
  62. package/src/services/ProcessService.ts +185 -142
  63. package/src/services/RestService.ts +1021 -267
  64. package/src/services/SubsetService.ts +48 -0
  65. package/src/services/TM1Service.ts +127 -6
  66. package/src/services/index.ts +1 -1
  67. package/src/tests/100PercentParityCheck.test.ts +29 -8
  68. package/src/tests/applicationService.issue38.test.ts +293 -0
  69. package/src/tests/asyncOperationService.test.ts +52 -48
  70. package/src/tests/bugfix28.test.ts +12 -4
  71. package/src/tests/elementService.issue37.test.ts +571 -0
  72. package/src/tests/elementService.issue38.test.ts +103 -0
  73. package/src/tests/hierarchyService.issue38.test.ts +599 -0
  74. package/src/tests/processService.comprehensive.test.ts +10 -10
  75. package/src/tests/processService.test.ts +295 -3
  76. package/src/tests/restService.test.ts +1844 -139
  77. package/src/tests/subsetService.issue38.test.ts +182 -0
  78. package/src/tests/tm1Service.test.ts +95 -11
@@ -52,6 +52,12 @@ export interface AsyncOperation {
52
52
  result?: any;
53
53
  parameters?: Record<string, any>;
54
54
  metadata?: Record<string, any>;
55
+ /**
56
+ * When true, the operation is tracked with a client-side UUID and the
57
+ * TM1 server has no record of it. `getAsyncOperationStatus` returns the
58
+ * cached status instead of polling `/_async('{id}')`.
59
+ */
60
+ trackedLocally?: boolean;
55
61
  }
56
62
 
57
63
  /**
@@ -113,30 +119,81 @@ export class AsyncOperationService {
113
119
  return operation.status;
114
120
  }
115
121
 
116
- // Poll TM1 server for updated status
122
+ // Locally tracked operations hold a client-side UUID; the TM1 server
123
+ // would return 404 for them. Rely on the in-memory cache, which is
124
+ // populated by background .then()/.catch() callbacks in helpers like
125
+ // ProcessService.executeWithReturnAsync.
126
+ if (operation.trackedLocally) {
127
+ return operation.status;
128
+ }
129
+
130
+ // Poll TM1 server for updated status. /_async('{id}') returns 202 while
131
+ // the op is pending, and 200/201 with the final operation payload once done.
132
+ // TM1 v12 may encode embedded failures in the `asyncresult` response header.
117
133
  try {
118
- const url = formatUrl("/AsyncOperations('{}')", operationId);
119
- const response = await this.rest.get(url);
120
- const serverStatus = this.mapServerStatus(response.data.Status);
134
+ const url = formatUrl("/_async('{}')", operationId);
135
+ const response = await this.rest.get(url, { asyncRequestsMode: false });
136
+ const serverStatus = this.deriveStatusFromResponse(response);
121
137
 
122
- // Update operation status
123
138
  operation.status = serverStatus;
124
139
  if (this.isTerminalStatus(serverStatus)) {
125
140
  operation.endTime = new Date();
126
- if (serverStatus === OperationStatus.COMPLETED && response.data.Result) {
127
- operation.result = response.data.Result;
128
- } else if (serverStatus === OperationStatus.FAILED && response.data.Error) {
129
- operation.error = response.data.Error;
141
+ if (serverStatus === OperationStatus.COMPLETED) {
142
+ operation.result = response.data;
143
+ } else if (serverStatus === OperationStatus.FAILED) {
144
+ operation.error = this.extractErrorFromResponse(response);
130
145
  }
131
146
  }
132
147
 
133
148
  return serverStatus;
134
- } catch (error) {
135
- // If server doesn't support AsyncOperations endpoint, return cached status
149
+ } catch (error: any) {
150
+ // HTTP 4xx/5xx surfaces as a thrown TM1RestException treat as a terminal FAILED
151
+ // so callers stop polling. Network errors (no status) leave cached status intact.
152
+ // Note: 404 is treated as FAILED here (operation never materialized). This differs
153
+ // from ProcessService.pollExecuteWithReturn which treats 404 as "not ready yet"
154
+ // because process async IDs can take a moment to register on the server.
155
+ const status = error?.status ?? error?.response?.status;
156
+ if (typeof status === 'number' && status >= 400) {
157
+ operation.status = OperationStatus.FAILED;
158
+ operation.endTime = new Date();
159
+ operation.error = error?.message ?? String(error);
160
+ return OperationStatus.FAILED;
161
+ }
136
162
  return operation.status;
137
163
  }
138
164
  }
139
165
 
166
+ private deriveStatusFromResponse(response: any): OperationStatus {
167
+ if (response.status === 202) {
168
+ return OperationStatus.RUNNING;
169
+ }
170
+ const asyncResult = response.headers?.['asyncresult'];
171
+ if (typeof asyncResult === 'string') {
172
+ const embedded = parseInt(asyncResult.trim().split(/\s+/)[0], 10);
173
+ if (!Number.isNaN(embedded) && (embedded < 200 || embedded >= 300)) {
174
+ return OperationStatus.FAILED;
175
+ }
176
+ }
177
+ if (response.status === 200 || response.status === 201) {
178
+ return OperationStatus.COMPLETED;
179
+ }
180
+ return OperationStatus.PENDING;
181
+ }
182
+
183
+ private extractErrorFromResponse(response: any): string {
184
+ const asyncResult = response.headers?.['asyncresult'];
185
+ if (typeof asyncResult === 'string') {
186
+ return asyncResult;
187
+ }
188
+ if (typeof response.data === 'string') {
189
+ return response.data;
190
+ }
191
+ if (response.data?.error?.message) {
192
+ return response.data.error.message;
193
+ }
194
+ return JSON.stringify(response.data);
195
+ }
196
+
140
197
  /**
141
198
  * List all active async operations
142
199
  *
@@ -178,11 +235,10 @@ export class AsyncOperationService {
178
235
  this.stopPolling(operationId);
179
236
 
180
237
  try {
181
- // Try to cancel on server if supported
182
- const url = formatUrl("/AsyncOperations('{}')/Cancel", operationId);
183
- await this.rest.post(url, {});
238
+ const url = formatUrl("/_async('{}')", operationId);
239
+ await this.rest.delete(url, { asyncRequestsMode: false });
184
240
  } catch (error) {
185
- // If server doesn't support cancellation, just mark as cancelled locally
241
+ console.warn(`Failed to cancel async operation ${operationId} on server:`, error);
186
242
  }
187
243
 
188
244
  // Update operation status
@@ -243,6 +299,9 @@ export class AsyncOperationService {
243
299
  public async createAsyncOperation(definition: AsyncOperationDefinition): Promise<string> {
244
300
  const operationId = this.generateOperationId();
245
301
 
302
+ // generateOperationId produces a client-side UUID; the server has no
303
+ // record of it, so polling /_async('{id}') would 404. Mark as locally
304
+ // tracked so getAsyncOperationStatus returns the cached status instead.
246
305
  const operation: AsyncOperation = {
247
306
  id: operationId,
248
307
  type: definition.type,
@@ -250,7 +309,8 @@ export class AsyncOperationService {
250
309
  status: OperationStatus.PENDING,
251
310
  startTime: new Date(),
252
311
  parameters: definition.parameters,
253
- metadata: definition.metadata
312
+ metadata: definition.metadata,
313
+ trackedLocally: true
254
314
  };
255
315
 
256
316
  this.operations.set(operationId, operation);
@@ -442,19 +502,6 @@ export class AsyncOperationService {
442
502
  status === OperationStatus.TIMEOUT;
443
503
  }
444
504
 
445
- private mapServerStatus(serverStatus: string): OperationStatus {
446
- const statusMap: Record<string, OperationStatus> = {
447
- 'Pending': OperationStatus.PENDING,
448
- 'Running': OperationStatus.RUNNING,
449
- 'CompletedSuccessfully': OperationStatus.COMPLETED,
450
- 'CompletedWithErrors': OperationStatus.FAILED,
451
- 'Cancelled': OperationStatus.CANCELLED,
452
- 'Timeout': OperationStatus.TIMEOUT
453
- };
454
-
455
- return statusMap[serverStatus] || OperationStatus.PENDING;
456
- }
457
-
458
505
  private stopPolling(operationId: string): void {
459
506
  const intervalId = this.pollingIntervals.get(operationId);
460
507
  if (intervalId) {
@@ -4,9 +4,15 @@ import { ObjectService } from './ObjectService';
4
4
  import { Element, ElementType } from '../objects/Element';
5
5
  import { ElementAttribute } from '../objects/ElementAttribute';
6
6
  import { Process } from '../objects/Process';
7
+ import { ProcessService } from './ProcessService';
8
+ import { HierarchyService } from './HierarchyService';
9
+ import { CellService } from './CellService';
7
10
  import {
8
11
  formatUrl,
9
- CaseAndSpaceInsensitiveDict
12
+ escapeODataValue,
13
+ requireDataAdmin,
14
+ CaseAndSpaceInsensitiveDict,
15
+ CaseAndSpaceInsensitiveSet
10
16
  } from '../utils/Utils';
11
17
 
12
18
  export interface ElementsDataFrameOptions {
@@ -408,6 +414,21 @@ export class ElementService extends ObjectService {
408
414
  return await this.rest.post(url, JSON.stringify(body));
409
415
  }
410
416
 
417
+ /**
418
+ * Add multiple element attributes in bulk
419
+ */
420
+ public async addElementAttributes(
421
+ dimensionName: string,
422
+ hierarchyName: string,
423
+ elementAttributes: ElementAttribute[]
424
+ ): Promise<AxiosResponse> {
425
+ const url = formatUrl(
426
+ "/Dimensions('{}')/Hierarchies('{}')/ElementAttributes",
427
+ dimensionName, hierarchyName);
428
+ const body = elementAttributes.map(ea => ea.bodyAsDict);
429
+ return await this.rest.post(url, JSON.stringify(body));
430
+ }
431
+
411
432
  /**
412
433
  * Delete multiple elements in bulk
413
434
  */
@@ -1204,4 +1225,304 @@ export class ElementService extends ObjectService {
1204
1225
 
1205
1226
  return elements.map(name => `[${dimensionName}].[${hierarchy}].[${name}]`);
1206
1227
  }
1228
+
1229
+ public async getNumberOfConsolidatedElements(
1230
+ dimensionName: string,
1231
+ hierarchyName: string
1232
+ ): Promise<number> {
1233
+ return this._getElementCountWithFilter(dimensionName, hierarchyName, `Type eq ${ElementType.CONSOLIDATED}`);
1234
+ }
1235
+
1236
+ public async getNumberOfLeafElements(
1237
+ dimensionName: string,
1238
+ hierarchyName: string
1239
+ ): Promise<number> {
1240
+ return this._getElementCountWithFilter(dimensionName, hierarchyName, `Type ne ${ElementType.CONSOLIDATED}`);
1241
+ }
1242
+
1243
+ public async getNumberOfNumericElements(
1244
+ dimensionName: string,
1245
+ hierarchyName: string
1246
+ ): Promise<number> {
1247
+ return this._getElementCountWithFilter(dimensionName, hierarchyName, `Type eq ${ElementType.NUMERIC}`);
1248
+ }
1249
+
1250
+ public async getNumberOfStringElements(
1251
+ dimensionName: string,
1252
+ hierarchyName: string
1253
+ ): Promise<number> {
1254
+ return this._getElementCountWithFilter(dimensionName, hierarchyName, `Type eq ${ElementType.STRING}`);
1255
+ }
1256
+
1257
+ /**
1258
+ * Get element types from all hierarchies in a single API call
1259
+ */
1260
+ public async getElementTypesFromAllHierarchies(
1261
+ dimensionName: string,
1262
+ skipConsolidations: boolean = false
1263
+ ): Promise<CaseAndSpaceInsensitiveDict<string>> {
1264
+ let url = formatUrl(
1265
+ "/Dimensions('{}')?$expand=Hierarchies($select=Elements;$expand=Elements($select=Name,Type",
1266
+ dimensionName
1267
+ );
1268
+ url += skipConsolidations ? ";$filter=Type ne 3))" : "))";
1269
+
1270
+ const response = await this.rest.get(url);
1271
+ const result = new CaseAndSpaceInsensitiveDict<string>();
1272
+ for (const hierarchy of response.data.Hierarchies) {
1273
+ for (const element of hierarchy.Elements) {
1274
+ result.set(element.Name, element.Type);
1275
+ }
1276
+ }
1277
+ return result;
1278
+ }
1279
+
1280
+ /**
1281
+ * Check if the element attributes cube exists for a dimension
1282
+ */
1283
+ public async attributeCubeExists(dimensionName: string): Promise<boolean> {
1284
+ const url = formatUrl("/Cubes('{}')", Element.ELEMENT_ATTRIBUTES_PREFIX + dimensionName);
1285
+ return this._exists(url);
1286
+ }
1287
+
1288
+ /**
1289
+ * Get parent mapping for all elements in a hierarchy
1290
+ */
1291
+ public async getParentsOfAllElements(
1292
+ dimensionName: string,
1293
+ hierarchyName: string
1294
+ ): Promise<{ [elementName: string]: string[] }> {
1295
+ const url = formatUrl(
1296
+ "/Dimensions('{}')/Hierarchies('{}')/Elements?$select=Name&$expand=Parents($select=Name)",
1297
+ dimensionName, hierarchyName
1298
+ );
1299
+ const response = await this.rest.get(url);
1300
+ const result: { [elementName: string]: string[] } = {};
1301
+ for (const child of response.data.value) {
1302
+ result[child.Name] = (child.Parents || []).map((p: any) => p.Name);
1303
+ }
1304
+ return result;
1305
+ }
1306
+
1307
+ /**
1308
+ * Get the canonical/principal name of an element (resolves aliases)
1309
+ */
1310
+ public async getElementPrincipalName(
1311
+ dimensionName: string,
1312
+ hierarchyName: string,
1313
+ elementName: string
1314
+ ): Promise<string> {
1315
+ const element = await this.get(dimensionName, hierarchyName, elementName);
1316
+ return element.name;
1317
+ }
1318
+
1319
+ /**
1320
+ * Check if one element is a direct parent of another
1321
+ *
1322
+ * Unlike the related function in TM1 (ELISPAR or ElementIsParent), this function will return false
1323
+ * if an invalid element is passed. An invalid dimension or hierarchy will cause the underlying
1324
+ * REST call to throw (the error propagates from the TM1 server).
1325
+ */
1326
+ public async elementIsParent(
1327
+ dimensionName: string,
1328
+ hierarchyName: string,
1329
+ parentName: string,
1330
+ elementName: string
1331
+ ): Promise<boolean> {
1332
+ const mdx = this._buildDrillIntersectionMdx(
1333
+ dimensionName, hierarchyName,
1334
+ parentName, elementName,
1335
+ 'TM1DrillDownMember', false
1336
+ );
1337
+ const cardinality = await this._getMdxSetCardinality(mdx);
1338
+ return cardinality > 0;
1339
+ }
1340
+
1341
+ /**
1342
+ * Check if one element is an ancestor of another
1343
+ *
1344
+ * Unlike the related function in TM1 (ELISANC or ElementIsAncestor), this function will return false
1345
+ * if an invalid element is passed; but will raise an exception if an invalid dimension or hierarchy is passed.
1346
+ *
1347
+ * For method you can pass three values:
1348
+ * - 'TI' performs best, but requires admin permissions
1349
+ * - 'TM1DrillDownMember' performs well when element is a leaf
1350
+ * - 'Descendants' performs well when ancestorName and elementName are Consolidations
1351
+ *
1352
+ * If no method is passed, defaults to 'TI' for admin users, 'TM1DrillDownMember' otherwise.
1353
+ * Note: isAdmin is determined from RestService state; if not set, defaults to 'TM1DrillDownMember'.
1354
+ */
1355
+ public async elementIsAncestor(
1356
+ dimensionName: string,
1357
+ hierarchyName: string,
1358
+ ancestorName: string,
1359
+ elementName: string,
1360
+ method?: string
1361
+ ): Promise<boolean> {
1362
+ if (!method) {
1363
+ method = this.isAdmin ? 'TI' : 'TM1DrillDownMember';
1364
+ }
1365
+
1366
+ if (method.toUpperCase() === 'TI') {
1367
+ if (await this._elementIsAncestorTi(dimensionName, hierarchyName, elementName, ancestorName)) {
1368
+ return true;
1369
+ }
1370
+ if (await this.hierarchyExists(dimensionName, hierarchyName)) {
1371
+ return false;
1372
+ }
1373
+ throw new Error(`Hierarchy: '${hierarchyName}' does not exist in dimension: '${dimensionName}'`);
1374
+ }
1375
+
1376
+ if (method.toUpperCase() === 'DESCENDANTS' || method.toUpperCase() === 'TM1DRILLDOWNMEMBER') {
1377
+ if (!await this.exists(dimensionName, hierarchyName, elementName)) {
1378
+ if (!await this.hierarchyExists(dimensionName, hierarchyName)) {
1379
+ throw new Error(`Hierarchy '${hierarchyName}' does not exist in dimension '${dimensionName}'`);
1380
+ }
1381
+ return false;
1382
+ }
1383
+ }
1384
+
1385
+ const mdx = this._buildDrillIntersectionMdx(
1386
+ dimensionName, hierarchyName,
1387
+ ancestorName, elementName,
1388
+ method, true
1389
+ );
1390
+ const cardinality = await this._getMdxSetCardinality(mdx);
1391
+ return cardinality > 0;
1392
+ }
1393
+
1394
+ /**
1395
+ * Remove a single edge from a hierarchy
1396
+ */
1397
+ public async removeEdge(
1398
+ dimensionName: string,
1399
+ hierarchyName: string,
1400
+ parentName: string,
1401
+ componentName: string
1402
+ ): Promise<AxiosResponse> {
1403
+ const url = formatUrl(
1404
+ "/Dimensions('{}')/Hierarchies('{}')/Elements('{}')/Edges(ParentName='{}',ComponentName='{}')",
1405
+ dimensionName, hierarchyName, parentName, parentName, componentName
1406
+ );
1407
+ return this.rest.delete(url);
1408
+ }
1409
+
1410
+ /**
1411
+ * Check if a hierarchy exists in a dimension (convenience delegation to HierarchyService)
1412
+ */
1413
+ public async hierarchyExists(
1414
+ dimensionName: string,
1415
+ hierarchyName: string
1416
+ ): Promise<boolean> {
1417
+ const hierarchyService = new HierarchyService(this.rest);
1418
+ return hierarchyService.exists(dimensionName, hierarchyName);
1419
+ }
1420
+
1421
+ /**
1422
+ * Get all element names and alias values for leaf elements as a case-and-space-insensitive set
1423
+ */
1424
+ public async getAllLeafElementIdentifiers(
1425
+ dimensionName: string,
1426
+ hierarchyName: string
1427
+ ): Promise<CaseAndSpaceInsensitiveSet> {
1428
+ const mdxElements = `{ Tm1FilterByLevel ( { Tm1SubsetAll ([${dimensionName}].[${hierarchyName}]) } , 0 ) }`;
1429
+
1430
+ const aliasAttributes = await this.getAliasElementAttributes(dimensionName, hierarchyName);
1431
+
1432
+ if (aliasAttributes.length === 0) {
1433
+ const result = await this.executeSetMdx(mdxElements, undefined, ['Name'], null, null);
1434
+ const identifiers = new CaseAndSpaceInsensitiveSet();
1435
+ for (const record of result) {
1436
+ identifiers.add(record[0].Name);
1437
+ }
1438
+ return identifiers;
1439
+ }
1440
+
1441
+ const attrMdx = aliasAttributes.map(a =>
1442
+ `[}ElementAttributes_${dimensionName}].[}ElementAttributes_${dimensionName}].[${a}]`
1443
+ ).join(',');
1444
+ const mdx = `SELECT ${mdxElements} ON ROWS, {${attrMdx}} ON COLUMNS FROM [}ElementAttributes_${dimensionName}]`;
1445
+
1446
+ return this._retrieveMdxRowsAndCellValuesAsStringSet(mdx);
1447
+ }
1448
+
1449
+ private async _getElementCountWithFilter(
1450
+ dimensionName: string,
1451
+ hierarchyName: string,
1452
+ filter: string
1453
+ ): Promise<number> {
1454
+ const baseUrl = formatUrl(
1455
+ "/Dimensions('{}')/Hierarchies('{}')/Elements/$count",
1456
+ dimensionName, hierarchyName
1457
+ );
1458
+ const url = `${baseUrl}?$filter=${filter}`;
1459
+ const response = await this.rest.get(url);
1460
+ return parseInt(response.data) || 0;
1461
+ }
1462
+
1463
+ private _buildDrillIntersectionMdx(
1464
+ dimensionName: string,
1465
+ hierarchyName: string,
1466
+ firstElementName: string,
1467
+ secondElementName: string,
1468
+ mdxMethod: string,
1469
+ recursive: boolean
1470
+ ): string {
1471
+ const first = `[${dimensionName}].[${hierarchyName}].[${firstElementName}]`;
1472
+ const second = `[${dimensionName}].[${hierarchyName}].[${secondElementName}]`;
1473
+
1474
+ let drillSet: string;
1475
+ if (mdxMethod.toUpperCase() === 'TM1DRILLDOWNMEMBER') {
1476
+ drillSet = recursive
1477
+ ? `{TM1DRILLDOWNMEMBER({${first}}, ALL, RECURSIVE)}`
1478
+ : `{TM1DRILLDOWNMEMBER({${first}}, ALL)}`;
1479
+ } else if (mdxMethod.toUpperCase() === 'DESCENDANTS') {
1480
+ drillSet = `{DESCENDANTS(${first}, ${second}.Level, SELF)}`;
1481
+ } else {
1482
+ throw new Error("Invalid MDX Drill Method. Options: 'TM1DrillDownMember' or 'Descendants'");
1483
+ }
1484
+
1485
+ return `INTERSECT(${drillSet}, {${second}})`;
1486
+ }
1487
+
1488
+ private async _getMdxSetCardinality(mdx: string): Promise<number> {
1489
+ const url = '/ExecuteMDXSetExpression?$select=Cardinality';
1490
+ const payload = { MDX: mdx };
1491
+ const response = await this.rest.post(url, JSON.stringify(payload));
1492
+ return response.data.Cardinality || 0;
1493
+ }
1494
+
1495
+ @requireDataAdmin
1496
+ private async _elementIsAncestorTi(
1497
+ dimensionName: string,
1498
+ hierarchyName: string,
1499
+ elementName: string,
1500
+ ancestorName: string
1501
+ ): Promise<boolean> {
1502
+ const processService = new ProcessService(this.rest);
1503
+ const code = `ElementIsAncestor('${escapeODataValue(dimensionName)}', '${escapeODataValue(hierarchyName)}', '${escapeODataValue(ancestorName)}', '${escapeODataValue(elementName)}')=1`;
1504
+ return processService.evaluateBooleanTiExpression(code);
1505
+ }
1506
+
1507
+ private async _retrieveMdxRowsAndCellValuesAsStringSet(mdx: string): Promise<CaseAndSpaceInsensitiveSet> {
1508
+ const cellService = new CellService(this.rest);
1509
+ const { rows, values } = await cellService.executeMdxRowsAndValues(mdx);
1510
+ const result = new CaseAndSpaceInsensitiveSet();
1511
+
1512
+ for (const row of rows) {
1513
+ for (const name of row) {
1514
+ if (name) {
1515
+ result.add(name);
1516
+ }
1517
+ }
1518
+ }
1519
+
1520
+ for (const value of values) {
1521
+ if (value && typeof value === 'string' && value.trim() !== '') {
1522
+ result.add(value);
1523
+ }
1524
+ }
1525
+
1526
+ return result;
1527
+ }
1207
1528
  }
@@ -1,6 +1,6 @@
1
1
  import path from 'path';
2
- import { AxiosRequestConfig, AxiosResponse } from 'axios';
3
- import { RestService } from './RestService';
2
+ import { AxiosResponse } from 'axios';
3
+ import { RequestOptions, RestService } from './RestService';
4
4
  import { ObjectService } from './ObjectService';
5
5
  import { verifyVersion } from '../utils/Utils';
6
6
 
@@ -227,7 +227,7 @@ export class FileService extends ObjectService {
227
227
  }
228
228
 
229
229
  private async uploadFileContentWithoutMpu(url: string, content: Buffer): Promise<AxiosResponse> {
230
- const config: AxiosRequestConfig = {
230
+ const config: RequestOptions = {
231
231
  headers: this.binaryHttpHeader
232
232
  };
233
233
  return await this.rest.put(url, content, config);