tm1npm 2.0.0 → 2.1.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 (47) hide show
  1. package/lib/objects/Axis.d.ts +1 -0
  2. package/lib/objects/Axis.d.ts.map +1 -1
  3. package/lib/objects/Axis.js +3 -0
  4. package/lib/objects/Chore.d.ts +2 -2
  5. package/lib/objects/Chore.d.ts.map +1 -1
  6. package/lib/objects/Chore.js +7 -13
  7. package/lib/objects/Cube.d.ts.map +1 -1
  8. package/lib/objects/Cube.js +2 -1
  9. package/lib/objects/Hierarchy.js +10 -10
  10. package/lib/objects/MDXView.d.ts +2 -0
  11. package/lib/objects/MDXView.d.ts.map +1 -1
  12. package/lib/objects/MDXView.js +30 -9
  13. package/lib/objects/NativeView.d.ts +5 -5
  14. package/lib/objects/NativeView.d.ts.map +1 -1
  15. package/lib/objects/NativeView.js +17 -34
  16. package/lib/objects/Process.d.ts +8 -3
  17. package/lib/objects/Process.d.ts.map +1 -1
  18. package/lib/objects/Process.js +143 -33
  19. package/lib/objects/Subset.d.ts.map +1 -1
  20. package/lib/objects/Subset.js +10 -3
  21. package/lib/objects/User.d.ts +5 -5
  22. package/lib/objects/User.d.ts.map +1 -1
  23. package/lib/objects/User.js +14 -23
  24. package/lib/tests/debuggerService.test.js +3 -1
  25. package/lib/tests/objectModelParity.test.js +362 -11
  26. package/lib/tests/objects.improved.test.js +28 -5
  27. package/lib/tests/user.issue61.test.d.ts +2 -0
  28. package/lib/tests/user.issue61.test.d.ts.map +1 -0
  29. package/lib/tests/user.issue61.test.js +180 -0
  30. package/lib/utils/Utils.d.ts +6 -1
  31. package/lib/utils/Utils.d.ts.map +1 -1
  32. package/lib/utils/Utils.js +56 -7
  33. package/package.json +1 -1
  34. package/src/objects/Axis.ts +4 -0
  35. package/src/objects/Chore.ts +7 -14
  36. package/src/objects/Cube.ts +2 -1
  37. package/src/objects/Hierarchy.ts +11 -11
  38. package/src/objects/MDXView.ts +29 -9
  39. package/src/objects/NativeView.ts +26 -42
  40. package/src/objects/Process.ts +182 -66
  41. package/src/objects/Subset.ts +17 -3
  42. package/src/objects/User.ts +17 -23
  43. package/src/tests/debuggerService.test.ts +3 -1
  44. package/src/tests/objectModelParity.test.ts +456 -11
  45. package/src/tests/objects.improved.test.ts +41 -9
  46. package/src/tests/user.issue61.test.ts +206 -0
  47. package/src/utils/Utils.ts +60 -7
@@ -163,86 +163,70 @@ export class NativeView extends View {
163
163
  this._rows.push(row);
164
164
  }
165
165
 
166
- public removeTitle(dimensionName: string): boolean {
167
- const index = this._titles.findIndex(t =>
168
- caseAndSpaceInsensitiveEquals(t.dimensionName, dimensionName));
169
- if (index !== -1) {
170
- this._titles.splice(index, 1);
171
- return true;
172
- }
173
- return false;
166
+ public removeTitle(dimensionName: string): void {
167
+ this._titles = this._titles.filter(
168
+ t => !caseAndSpaceInsensitiveEquals(t.dimensionName, dimensionName));
174
169
  }
175
170
 
176
- public removeColumn(dimensionName: string): boolean {
177
- const index = this._columns.findIndex(c =>
178
- caseAndSpaceInsensitiveEquals(c.subset.dimensionName, dimensionName));
179
- if (index !== -1) {
180
- this._columns.splice(index, 1);
181
- return true;
182
- }
183
- return false;
171
+ public removeColumn(dimensionName: string): void {
172
+ this._columns = this._columns.filter(
173
+ c => !caseAndSpaceInsensitiveEquals(c.dimensionName, dimensionName));
184
174
  }
185
175
 
186
- public removeRow(dimensionName: string): boolean {
187
- const index = this._rows.findIndex(r =>
188
- caseAndSpaceInsensitiveEquals(r.subset.dimensionName, dimensionName));
189
- if (index !== -1) {
190
- this._rows.splice(index, 1);
191
- return true;
192
- }
193
- return false;
176
+ public removeRow(dimensionName: string): void {
177
+ this._rows = this._rows.filter(
178
+ r => !caseAndSpaceInsensitiveEquals(r.dimensionName, dimensionName));
194
179
  }
195
180
 
196
181
  public substituteTitle(dimension: string, element: string): void {
197
- /** Substitute the title element for a given dimension
198
- *
199
- * :param dimension: str, name of dimension
200
- * :param element: str, name of element
201
- */
202
182
  for (const title of this._titles) {
203
183
  if (caseAndSpaceInsensitiveEquals(title.dimensionName, dimension)) {
184
+ title.subset = new AnonymousSubset(dimension, dimension, undefined, [element]);
204
185
  title.selected = element;
205
186
  return;
206
187
  }
207
188
  }
208
- throw new Error(`No title with dimension: '${dimension}'`);
189
+ throw new Error(`Dimension '${dimension}' not found in titles`);
209
190
  }
210
191
 
211
- public static fromJSON(viewAsJson: string, cubeName: string): NativeView {
192
+ public static fromJSON(viewAsJson: string, cubeName?: string): NativeView {
212
193
  const viewAsDict = JSON.parse(viewAsJson);
213
194
  return NativeView.fromDict(viewAsDict, cubeName);
214
195
  }
215
196
 
216
- public static fromDict(viewAsDict: any, cubeName: string): NativeView {
197
+ public static fromDict(viewAsDict: any, cubeName?: string): NativeView {
198
+ let resolvedCube = cubeName;
199
+ if (!resolvedCube) {
200
+ const ctx: string = viewAsDict["@odata.context"];
201
+ resolvedCube = ctx.substring(20, ctx.indexOf("')/"));
202
+ }
203
+
217
204
  const view = new NativeView(
218
- cubeName,
205
+ resolvedCube,
219
206
  viewAsDict.Name,
220
207
  viewAsDict.SuppressEmptyColumns || false,
221
208
  viewAsDict.SuppressEmptyRows || false,
222
209
  viewAsDict.FormatString || "0.#########"
223
210
  );
224
211
 
225
- // Parse titles
226
212
  if (viewAsDict.Titles) {
227
213
  for (const titleDict of viewAsDict.Titles) {
228
- const title = ViewTitleSelection.fromDict(titleDict);
229
- view.addTitle(title);
214
+ if (!('Selected' in titleDict) && !('Selected@odata.bind' in titleDict)) {
215
+ throw new Error("View Title dict must contain 'Selected' or 'Selected@odata.bind' as key");
216
+ }
217
+ view.addTitle(ViewTitleSelection.fromDict(titleDict));
230
218
  }
231
219
  }
232
220
 
233
- // Parse columns
234
221
  if (viewAsDict.Columns) {
235
222
  for (const columnDict of viewAsDict.Columns) {
236
- const column = ViewAxisSelection.fromDict(columnDict);
237
- view.addColumn(column);
223
+ view.addColumn(ViewAxisSelection.fromDict(columnDict));
238
224
  }
239
225
  }
240
226
 
241
- // Parse rows
242
227
  if (viewAsDict.Rows) {
243
228
  for (const rowDict of viewAsDict.Rows) {
244
- const row = ViewAxisSelection.fromDict(rowDict);
245
- view.addRow(row);
229
+ view.addRow(ViewAxisSelection.fromDict(rowDict));
246
230
  }
247
231
  }
248
232
 
@@ -15,7 +15,7 @@ export class Process extends TM1Object {
15
15
  public static readonly MAX_STATEMENTS_POST_11_8_015 = 100_000;
16
16
 
17
17
  private _name: string;
18
- private _hasSecurityAccess: boolean;
18
+ private _hasSecurityAccess: boolean | undefined;
19
19
  private _uiData: string;
20
20
  private _parameters: any[];
21
21
  private _variables: any[];
@@ -28,7 +28,7 @@ export class Process extends TM1Object {
28
28
  private _datasourceAsciiDecimalSeparator: string;
29
29
  private _datasourceAsciiDelimiterChar: string;
30
30
  private _datasourceAsciiDelimiterType: string;
31
- private _datasourceAsciiHeaderRecords: number;
31
+ private _datasourceAsciiHeaderRecords: number | string;
32
32
  private _datasourceAsciiQuoteCharacter: string;
33
33
  private _datasourceAsciiThousandSeparator: string;
34
34
  private _datasourceDataSourceNameForClient: string;
@@ -36,7 +36,7 @@ export class Process extends TM1Object {
36
36
  private _datasourcePassword: string;
37
37
  private _datasourceUserName: string;
38
38
  private _datasourceQuery: string;
39
- private _datasourceUsesUnicode: boolean;
39
+ private _datasourceUsesUnicode: boolean | string;
40
40
  private _datasourceView: string;
41
41
  private _datasourceSubset: string;
42
42
  private _datasourceJsonRootPointer: string;
@@ -60,7 +60,7 @@ export class Process extends TM1Object {
60
60
 
61
61
  constructor(
62
62
  name: string,
63
- hasSecurityAccess: boolean = false,
63
+ hasSecurityAccess: boolean | undefined = false,
64
64
  uiData: string = "CubeAction=1511\fDataAction=1503\fCubeLogChanges=0\f",
65
65
  parameters?: Iterable<any>,
66
66
  variables?: Iterable<any>,
@@ -73,7 +73,7 @@ export class Process extends TM1Object {
73
73
  datasourceAsciiDecimalSeparator: string = '.',
74
74
  datasourceAsciiDelimiterChar: string = ';',
75
75
  datasourceAsciiDelimiterType: string = 'Character',
76
- datasourceAsciiHeaderRecords: number = 1,
76
+ datasourceAsciiHeaderRecords: number | string = 1,
77
77
  datasourceAsciiQuoteCharacter: string = '',
78
78
  datasourceAsciiThousandSeparator: string = ',',
79
79
  datasourceDataSourceNameForClient: string = '',
@@ -81,7 +81,7 @@ export class Process extends TM1Object {
81
81
  datasourcePassword: string = '',
82
82
  datasourceUserName: string = '',
83
83
  datasourceQuery: string = '',
84
- datasourceUsesUnicode: boolean = true,
84
+ datasourceUsesUnicode: boolean | string = true,
85
85
  datasourceView: string = '',
86
86
  datasourceSubset: string = '',
87
87
  datasourceJsonRootPointer: string = '',
@@ -123,11 +123,18 @@ export class Process extends TM1Object {
123
123
  this._uiData = uiData;
124
124
  this._parameters = parameters ? Array.from(parameters) : [];
125
125
  this._variables = variables ? Array.from(variables) : [];
126
- this._variablesUiData = variablesUiData ? Array.from(variablesUiData) : [];
127
- this._prologProcedure = prologProcedure;
128
- this._metadataProcedure = metadataProcedure;
129
- this._dataProcedure = dataProcedure;
130
- this._epilogProcedure = epilogProcedure;
126
+ if (variablesUiData) {
127
+ // Handle encoding issue in variable_ui_data for async requests
128
+ this._variablesUiData = Array.from(variablesUiData).map((entry: any) =>
129
+ typeof entry === 'string' ? entry.replace(/€/g, '\f') : entry
130
+ );
131
+ } else {
132
+ this._variablesUiData = [];
133
+ }
134
+ this._prologProcedure = Process.addGeneratedStringToCode(prologProcedure);
135
+ this._metadataProcedure = Process.addGeneratedStringToCode(metadataProcedure);
136
+ this._dataProcedure = Process.addGeneratedStringToCode(dataProcedure);
137
+ this._epilogProcedure = Process.addGeneratedStringToCode(epilogProcedure);
131
138
  this._datasourceType = datasourceType;
132
139
  this._datasourceAsciiDecimalSeparator = datasourceAsciiDecimalSeparator;
133
140
  this._datasourceAsciiDelimiterChar = datasourceAsciiDelimiterChar;
@@ -155,11 +162,11 @@ export class Process extends TM1Object {
155
162
  this._name = value;
156
163
  }
157
164
 
158
- public get hasSecurityAccess(): boolean {
165
+ public get hasSecurityAccess(): boolean | undefined {
159
166
  return this._hasSecurityAccess;
160
167
  }
161
168
 
162
- public set hasSecurityAccess(value: boolean) {
169
+ public set hasSecurityAccess(value: boolean | undefined) {
163
170
  this._hasSecurityAccess = value;
164
171
  }
165
172
 
@@ -241,37 +248,110 @@ export class Process extends TM1Object {
241
248
  * :param process_as_dict: process as dict
242
249
  * :return: process, an instance of this class
243
250
  */
251
+ const ds = processAsDict['DataSource'] ?? {};
244
252
  return new Process(
245
- processAsDict.Name,
246
- processAsDict.HasSecurityAccess || false,
247
- processAsDict.UIData || "CubeAction=1511\fDataAction=1503\fCubeLogChanges=0\f",
248
- processAsDict.Parameters || [],
249
- processAsDict.Variables || [],
250
- processAsDict.VariablesUIData || [],
251
- processAsDict.PrologProcedure || '',
252
- processAsDict.MetadataProcedure || '',
253
- processAsDict.DataProcedure || '',
254
- processAsDict.EpilogProcedure || '',
255
- processAsDict.DataSource?.Type || 'None',
256
- processAsDict.DataSource?.AsciiDecimalSeparator || '.',
257
- processAsDict.DataSource?.AsciiDelimiterChar || ';',
258
- processAsDict.DataSource?.AsciiDelimiterType || 'Character',
259
- processAsDict.DataSource?.AsciiHeaderRecords || 1,
260
- processAsDict.DataSource?.AsciiQuoteCharacter || '',
261
- processAsDict.DataSource?.AsciiThousandSeparator || ',',
262
- processAsDict.DataSource?.DataSourceNameForClient || '',
263
- processAsDict.DataSource?.DataSourceNameForServer || '',
264
- processAsDict.DataSource?.Password || '',
265
- processAsDict.DataSource?.UserName || '',
266
- processAsDict.DataSource?.Query || '',
267
- processAsDict.DataSource?.UsesUnicode !== false,
268
- processAsDict.DataSource?.View || '',
269
- processAsDict.DataSource?.Subset || '',
270
- processAsDict.DataSource?.JsonRootPointer || '',
271
- processAsDict.DataSource?.JsonVariableMapping || ''
253
+ processAsDict['Name'],
254
+ processAsDict['HasSecurityAccess'],
255
+ processAsDict['UIData'] ?? '',
256
+ processAsDict['Parameters'],
257
+ processAsDict['Variables'],
258
+ processAsDict['VariablesUIData'] ?? '',
259
+ processAsDict['PrologProcedure'],
260
+ processAsDict['MetadataProcedure'],
261
+ processAsDict['DataProcedure'],
262
+ processAsDict['EpilogProcedure'],
263
+ ds['Type'] ?? '',
264
+ ds['asciiDecimalSeparator'] ?? '',
265
+ ds['asciiDelimiterChar'] ?? '',
266
+ ds['asciiDelimiterType'] ?? '',
267
+ ds['asciiHeaderRecords'] ?? '',
268
+ ds['asciiQuoteCharacter'] ?? '',
269
+ ds['asciiThousandSeparator'] ?? '',
270
+ ds['dataSourceNameForClient'] ?? '',
271
+ ds['dataSourceNameForServer'] ?? '',
272
+ ds['password'] ?? '',
273
+ ds['userName'] ?? '',
274
+ ds['query'] ?? '',
275
+ ds['usesUnicode'] ?? '',
276
+ ds['view'] ?? '',
277
+ ds['subset'] ?? '',
278
+ ds['jsonRootPointer'] ?? '',
279
+ ds['jsonVariableMapping'] ?? ''
272
280
  );
273
281
  }
274
282
 
283
+ public addVariable(name: string, variableType: string): void {
284
+ // variable consists of actual variable and UI-Information ('ignore','other', etc.)
285
+ // 1. handle Variable info
286
+ const variable = {
287
+ Name: name,
288
+ Type: variableType,
289
+ Position: this._variables.length + 1,
290
+ StartByte: 0,
291
+ EndByte: 0,
292
+ };
293
+ this._variables.push(variable);
294
+ // 2. handle UI info
295
+ const varType = variableType === 'Numeric' ? 33 : 32;
296
+ // '\f' !
297
+ const variableUiData = 'VarType=' + varType + '\f' + 'ColType=' + 827 + '\f';
298
+ /*
299
+ * mapping VariableUIData:
300
+ * VarType 33 -> Numeric
301
+ * VarType 32 -> String
302
+ * ColType 827 -> Other
303
+ */
304
+ this._variablesUiData.push(variableUiData);
305
+ }
306
+
307
+ public removeVariable(name: string): void {
308
+ for (const variable of this._variables.slice()) {
309
+ if (variable['Name'] === name) {
310
+ const vuid = this._variablesUiData[this._variables.indexOf(variable)];
311
+ const vuidIdx = this._variablesUiData.indexOf(vuid);
312
+ if (vuidIdx !== -1) {
313
+ this._variablesUiData.splice(vuidIdx, 1);
314
+ }
315
+ const varIdx = this._variables.indexOf(variable);
316
+ if (varIdx !== -1) {
317
+ this._variables.splice(varIdx, 1);
318
+ }
319
+ }
320
+ }
321
+ }
322
+
323
+ public addParameter(
324
+ name: string,
325
+ prompt: string,
326
+ value: string | number,
327
+ parameterType?: string
328
+ ): void {
329
+ if (!parameterType) {
330
+ parameterType = typeof value === 'string' ? 'String' : 'Numeric';
331
+ }
332
+ const parameter = { Name: name, Prompt: prompt, Value: value, Type: parameterType };
333
+ this._parameters.push(parameter);
334
+ }
335
+
336
+ public removeParameter(name: string): void {
337
+ for (const parameter of this._parameters.slice()) {
338
+ if (parameter['Name'] === name) {
339
+ const idx = this._parameters.indexOf(parameter);
340
+ if (idx !== -1) {
341
+ this._parameters.splice(idx, 1);
342
+ }
343
+ }
344
+ }
345
+ }
346
+
347
+ public dropParameterTypes(): void {
348
+ for (let p = 0; p < this._parameters.length; p++) {
349
+ if ('Type' in this._parameters[p]) {
350
+ delete this._parameters[p]['Type'];
351
+ }
352
+ }
353
+ }
354
+
275
355
  public get body(): string {
276
356
  return JSON.stringify(this.constructBody());
277
357
  }
@@ -281,40 +361,76 @@ export class Process extends TM1Object {
281
361
  }
282
362
 
283
363
  private constructBody(): any {
284
- const body: any = {
364
+ // general parameters key order matches tm1py _construct_body_as_dict
365
+ const bodyAsDict: any = {
285
366
  Name: this._name,
367
+ PrologProcedure: this._prologProcedure,
368
+ MetadataProcedure: this._metadataProcedure,
369
+ DataProcedure: this._dataProcedure,
370
+ EpilogProcedure: this._epilogProcedure,
286
371
  HasSecurityAccess: this._hasSecurityAccess,
287
372
  UIData: this._uiData,
373
+ DataSource: {},
288
374
  Parameters: this._parameters,
289
375
  Variables: this._variables,
290
376
  VariablesUIData: this._variablesUiData,
291
- PrologProcedure: this._prologProcedure,
292
- MetadataProcedure: this._metadataProcedure,
293
- DataProcedure: this._dataProcedure,
294
- EpilogProcedure: this._epilogProcedure
295
377
  };
296
378
 
297
- // Add DataSource information
298
- body.DataSource = {
299
- Type: this._datasourceType,
300
- AsciiDecimalSeparator: this._datasourceAsciiDecimalSeparator,
301
- AsciiDelimiterChar: this._datasourceAsciiDelimiterChar,
302
- AsciiDelimiterType: this._datasourceAsciiDelimiterType,
303
- AsciiHeaderRecords: this._datasourceAsciiHeaderRecords,
304
- AsciiQuoteCharacter: this._datasourceAsciiQuoteCharacter,
305
- AsciiThousandSeparator: this._datasourceAsciiThousandSeparator,
306
- DataSourceNameForClient: this._datasourceDataSourceNameForClient,
307
- DataSourceNameForServer: this._datasourceDataSourceNameForServer,
308
- Password: this._datasourcePassword,
309
- UserName: this._datasourceUserName,
310
- Query: this._datasourceQuery,
311
- UsesUnicode: this._datasourceUsesUnicode,
312
- View: this._datasourceView,
313
- Subset: this._datasourceSubset,
314
- JsonRootPointer: this._datasourceJsonRootPointer,
315
- JsonVariableMapping: this._datasourceJsonVariableMapping
316
- };
379
+ // specific parameters (depending on datasource type)
380
+ if (this._datasourceType === 'ASCII') {
381
+ bodyAsDict['DataSource'] = {
382
+ Type: this._datasourceType,
383
+ asciiDecimalSeparator: this._datasourceAsciiDecimalSeparator,
384
+ asciiDelimiterChar: this._datasourceAsciiDelimiterChar,
385
+ asciiDelimiterType: this._datasourceAsciiDelimiterType,
386
+ asciiHeaderRecords: this._datasourceAsciiHeaderRecords,
387
+ asciiQuoteCharacter: this._datasourceAsciiQuoteCharacter,
388
+ asciiThousandSeparator: this._datasourceAsciiThousandSeparator,
389
+ dataSourceNameForClient: this._datasourceDataSourceNameForClient,
390
+ dataSourceNameForServer: this._datasourceDataSourceNameForServer,
391
+ };
392
+ if (this._datasourceAsciiDelimiterType === 'FixedWidth') {
393
+ delete bodyAsDict['DataSource']['asciiDelimiterChar'];
394
+ }
395
+ } else if (this._datasourceType === 'None') {
396
+ bodyAsDict['DataSource'] = { Type: 'None' };
397
+ } else if (this._datasourceType === 'ODBC') {
398
+ bodyAsDict['DataSource'] = {
399
+ Type: this._datasourceType,
400
+ dataSourceNameForClient: this._datasourceDataSourceNameForClient,
401
+ dataSourceNameForServer: this._datasourceDataSourceNameForServer,
402
+ userName: this._datasourceUserName,
403
+ password: this._datasourcePassword,
404
+ query: this._datasourceQuery,
405
+ usesUnicode: this._datasourceUsesUnicode,
406
+ };
407
+ } else if (this._datasourceType === 'TM1CubeView') {
408
+ bodyAsDict['DataSource'] = {
409
+ Type: this._datasourceType,
410
+ // Note: tm1py uses _datasource_data_source_name_for_server for BOTH client and server
411
+ dataSourceNameForClient: this._datasourceDataSourceNameForServer,
412
+ dataSourceNameForServer: this._datasourceDataSourceNameForServer,
413
+ view: this._datasourceView,
414
+ };
415
+ } else if (this._datasourceType === 'TM1DimensionSubset') {
416
+ bodyAsDict['DataSource'] = {
417
+ Type: this._datasourceType,
418
+ // Note: tm1py uses _datasource_data_source_name_for_server for BOTH client and server
419
+ dataSourceNameForClient: this._datasourceDataSourceNameForServer,
420
+ dataSourceNameForServer: this._datasourceDataSourceNameForServer,
421
+ subset: this._datasourceSubset,
422
+ };
423
+ } else if (this._datasourceType === 'JSON') {
424
+ bodyAsDict['DataSource'] = {
425
+ Type: this._datasourceType,
426
+ // Note: tm1py uses _datasource_data_source_name_for_server for BOTH client and server
427
+ dataSourceNameForClient: this._datasourceDataSourceNameForServer,
428
+ dataSourceNameForServer: this._datasourceDataSourceNameForServer,
429
+ jsonRootPointer: this._datasourceJsonRootPointer,
430
+ jsonVariableMapping: this._datasourceJsonVariableMapping,
431
+ };
432
+ }
317
433
 
318
- return body;
434
+ return bodyAsDict;
319
435
  }
320
436
  }
@@ -240,14 +240,28 @@ export class AnonymousSubset extends Subset {
240
240
  } else if ("Hierarchy@odata.bind" in subsetAsDict) {
241
241
  const hierarchyOdata = subsetAsDict["Hierarchy@odata.bind"];
242
242
 
243
- dimensionName = readObjectNameFromUrl(hierarchyOdata);
244
- hierarchyName = readObjectNameFromUrl(hierarchyOdata);
243
+ // tm1py parity (TM1py/Objects/Subset.py): two read_object_name_from_url
244
+ // calls with these exact regex patterns. Both return match.group(1) (the
245
+ // first capture group), so both names resolve to the dimension. This
246
+ // mirrors tm1py's behavior — including the latent bug where a non-default
247
+ // hierarchy name in the URL is dropped. Do NOT "fix" without tm1py.
248
+ const dimResult = readObjectNameFromUrl(
249
+ hierarchyOdata,
250
+ /^Dimensions\('(.*?)'\)\/Hierarchies\('(.+?)'\)/
251
+ );
252
+ const hierResult = readObjectNameFromUrl(
253
+ hierarchyOdata,
254
+ /^Dimensions\('(.+?)'\)\/Hierarchies\('(.*?)'\)/
255
+ );
245
256
 
246
- if (!dimensionName || !hierarchyName) {
257
+ if (!dimResult || !hierResult) {
247
258
  throw new Error(
248
259
  `Unexpected value for 'Hierarchy@odata.bind' property in subset dict: '${hierarchyOdata}'`
249
260
  );
250
261
  }
262
+
263
+ dimensionName = dimResult;
264
+ hierarchyName = hierResult;
251
265
  } else {
252
266
  throw new Error("Subset dict must contain 'Hierarchy' or 'Hierarchy@odata.bind' as key");
253
267
  }
@@ -1,14 +1,18 @@
1
1
  import { TM1Object } from './TM1Object';
2
- import { CaseAndSpaceInsensitiveSet, formatUrl } from '../utils/Utils';
2
+ import { CaseAndSpaceInsensitiveSet, formatUrl, lowerAndDropSpaces } from '../utils/Utils';
3
3
 
4
4
  export enum UserType {
5
- User = 0,
6
- SecurityAdmin = 1,
7
- DataAdmin = 2,
8
- Admin = 3,
9
- OperationsAdmin = 4
5
+ User = 'User',
6
+ SecurityAdmin = 'SecurityAdmin',
7
+ DataAdmin = 'DataAdmin',
8
+ Admin = 'Admin',
9
+ OperationsAdmin = 'OperationsAdmin'
10
10
  }
11
11
 
12
+ const USER_TYPE_LOOKUP = new Map<string, UserType>(
13
+ Object.entries(UserType).map(([k, v]) => [k.toLowerCase(), v as UserType])
14
+ );
15
+
12
16
  export class User extends TM1Object {
13
17
  /** Abstraction of a TM1 User
14
18
  *
@@ -70,25 +74,15 @@ export class User extends TM1Object {
70
74
  }
71
75
 
72
76
  public set userType(value: UserType | string) {
73
- if (typeof value === 'string') {
74
- // Parse string to UserType enum
75
- const lowerValue = value.replace(/\s+/g, '').toLowerCase();
76
- for (const [key, enumValue] of Object.entries(UserType)) {
77
- if (key.toLowerCase() === lowerValue) {
78
- this._userType = enumValue as UserType;
79
- break;
80
- }
81
- }
82
- if (this._userType === undefined) {
83
- throw new Error(`Invalid element type=${value}`);
84
- }
85
- } else {
86
- this._userType = value;
77
+ const resolved = USER_TYPE_LOOKUP.get(lowerAndDropSpaces(value));
78
+ if (resolved === undefined) {
79
+ throw new Error(`Invalid user type=${value}`);
87
80
  }
81
+ this._userType = resolved;
88
82
 
89
83
  // update groups as well, since TM1 doesn't react to change in user_type property
90
84
  if (this._userType !== UserType.User) {
91
- this.addGroup(this._userType.toString());
85
+ this.addGroup(this._userType);
92
86
  }
93
87
  }
94
88
 
@@ -109,7 +103,7 @@ export class User extends TM1Object {
109
103
  }
110
104
 
111
105
  public get isAdmin(): boolean {
112
- return this._groups.has("ADMIN");
106
+ return this._groups.has("Admin");
113
107
  }
114
108
 
115
109
  public get isDataAdmin(): boolean {
@@ -165,7 +159,7 @@ export class User extends TM1Object {
165
159
  userAsDict.Groups.map((group: any) => group.Name),
166
160
  userAsDict.FriendlyName,
167
161
  undefined, // password not included in dict
168
- userAsDict.Type,
162
+ userAsDict.Type ?? undefined,
169
163
  userAsDict.Enabled
170
164
  );
171
165
  }
@@ -370,7 +370,9 @@ describe('DebuggerService and Enhanced Process Debugging (Issue #13)', () => {
370
370
  expect(plan.hasVariables).toBe(true);
371
371
  expect(plan.variableCount).toBe(1);
372
372
  expect(plan.procedures.hasPrologProcedure).toBe(true);
373
- expect(plan.estimatedComplexity).toBe('Medium');
373
+ // addGeneratedStringToCode prepends ~73 chars to each procedure tab,
374
+ // so actual total exceeds 2000 → 'High'
375
+ expect(plan.estimatedComplexity).toBe('High');
374
376
  });
375
377
  });
376
378
  });