tm1npm 1.6.0 → 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.
- package/CHANGELOG.md +89 -0
- package/lib/services/ApplicationService.d.ts.map +1 -1
- package/lib/services/AsyncOperationService.d.ts +8 -1
- package/lib/services/AsyncOperationService.d.ts.map +1 -1
- package/lib/services/AsyncOperationService.js +69 -26
- package/lib/services/FileService.d.ts.map +1 -1
- package/lib/services/ProcessService.d.ts +18 -13
- package/lib/services/ProcessService.d.ts.map +1 -1
- package/lib/services/ProcessService.js +28 -17
- package/lib/services/RestService.d.ts +213 -25
- package/lib/services/RestService.d.ts.map +1 -1
- package/lib/services/RestService.js +840 -271
- package/lib/services/TM1Service.d.ts +42 -1
- package/lib/services/TM1Service.d.ts.map +1 -1
- package/lib/services/TM1Service.js +94 -4
- package/lib/tests/asyncOperationService.test.js +51 -45
- package/lib/tests/processService.comprehensive.test.js +2 -2
- package/lib/tests/processService.test.js +20 -6
- package/lib/tests/restService.test.d.ts +0 -4
- package/lib/tests/restService.test.d.ts.map +1 -1
- package/lib/tests/restService.test.js +1558 -143
- package/lib/tests/tm1Service.test.js +80 -8
- package/package.json +1 -1
- package/src/services/ApplicationService.ts +4 -4
- package/src/services/AsyncOperationService.ts +76 -29
- package/src/services/FileService.ts +3 -3
- package/src/services/ProcessService.ts +67 -37
- package/src/services/RestService.ts +1020 -278
- package/src/services/TM1Service.ts +124 -6
- package/src/tests/asyncOperationService.test.ts +52 -48
- package/src/tests/processService.comprehensive.test.ts +3 -3
- package/src/tests/processService.test.ts +21 -9
- package/src/tests/restService.test.ts +1844 -139
- package/src/tests/tm1Service.test.ts +95 -11
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
const TM1Service_1 = require("../services/TM1Service");
|
|
9
9
|
const RestService_1 = require("../services/RestService");
|
|
10
|
+
const User_1 = require("../objects/User");
|
|
10
11
|
// Mock all service dependencies
|
|
11
12
|
jest.mock('../services/RestService');
|
|
12
13
|
jest.mock('../services/DimensionService');
|
|
@@ -22,6 +23,19 @@ jest.mock('../services/FileService');
|
|
|
22
23
|
jest.mock('../services/SessionService');
|
|
23
24
|
jest.mock('../services/ServerService');
|
|
24
25
|
jest.mock('../services/MonitoringService');
|
|
26
|
+
jest.mock('../services/AnnotationService');
|
|
27
|
+
jest.mock('../services/ChoreService');
|
|
28
|
+
jest.mock('../services/GitService');
|
|
29
|
+
jest.mock('../services/ApplicationService');
|
|
30
|
+
jest.mock('../services/SandboxService');
|
|
31
|
+
jest.mock('../services/JobService');
|
|
32
|
+
jest.mock('../services/UserService');
|
|
33
|
+
jest.mock('../services/ThreadService');
|
|
34
|
+
jest.mock('../services/TransactionLogService');
|
|
35
|
+
jest.mock('../services/MessageLogService');
|
|
36
|
+
jest.mock('../services/ConfigurationService');
|
|
37
|
+
jest.mock('../services/AuditLogService');
|
|
38
|
+
jest.mock('../services/LoggerService');
|
|
25
39
|
describe('TM1Service', () => {
|
|
26
40
|
let tm1Service;
|
|
27
41
|
let mockRestService;
|
|
@@ -56,6 +70,8 @@ describe('TM1Service', () => {
|
|
|
56
70
|
setSandbox: jest.fn(),
|
|
57
71
|
getSandbox: jest.fn().mockReturnValue('test-sandbox'),
|
|
58
72
|
isLoggedIn: jest.fn().mockReturnValue(true),
|
|
73
|
+
getVersion: jest.fn().mockResolvedValue('12.0.0'),
|
|
74
|
+
version: undefined,
|
|
59
75
|
};
|
|
60
76
|
// Mock RestService constructor
|
|
61
77
|
RestService_1.RestService.mockImplementation(() => mockRestService);
|
|
@@ -75,6 +91,7 @@ describe('TM1Service', () => {
|
|
|
75
91
|
expect(tm1Service.security).toBeDefined();
|
|
76
92
|
expect(tm1Service.files).toBeDefined();
|
|
77
93
|
expect(tm1Service.sessions).toBeDefined();
|
|
94
|
+
expect(tm1Service.applications).toBeDefined();
|
|
78
95
|
});
|
|
79
96
|
test('should create RestService with provided config', () => {
|
|
80
97
|
expect(RestService_1.RestService).toHaveBeenCalledWith(mockConfig);
|
|
@@ -123,14 +140,16 @@ describe('TM1Service', () => {
|
|
|
123
140
|
});
|
|
124
141
|
});
|
|
125
142
|
describe('User and Authentication', () => {
|
|
126
|
-
test('should get current user with whoami', async () => {
|
|
127
|
-
|
|
143
|
+
test('should get current user as User object with whoami', async () => {
|
|
144
|
+
const expectedUser = new User_1.User('test-user', ['ADMIN'], 'Test User', undefined, User_1.UserType.Admin, true);
|
|
128
145
|
const mockSecurityService = {
|
|
129
|
-
getCurrentUser: jest.fn().mockResolvedValue(
|
|
146
|
+
getCurrentUser: jest.fn().mockResolvedValue(expectedUser)
|
|
130
147
|
};
|
|
131
148
|
tm1Service.security = mockSecurityService;
|
|
132
149
|
const result = await tm1Service.whoami();
|
|
133
|
-
expect(result).
|
|
150
|
+
expect(result).toBeInstanceOf(User_1.User);
|
|
151
|
+
expect(result.name).toBe('test-user');
|
|
152
|
+
expect(result).toBe(expectedUser);
|
|
134
153
|
expect(mockSecurityService.getCurrentUser).toHaveBeenCalledTimes(1);
|
|
135
154
|
});
|
|
136
155
|
test('should check if user is logged in', () => {
|
|
@@ -157,11 +176,11 @@ describe('TM1Service', () => {
|
|
|
157
176
|
expect(result).toEqual({ metadata: 'test-metadata' });
|
|
158
177
|
expect(mockRestService.get).toHaveBeenCalledWith('/$metadata');
|
|
159
178
|
});
|
|
160
|
-
test('should get TM1 version', async () => {
|
|
161
|
-
mockRestService.
|
|
179
|
+
test('should get TM1 version via cached RestService.getVersion', async () => {
|
|
180
|
+
mockRestService.getVersion.mockResolvedValueOnce('12.0.0');
|
|
162
181
|
const result = await tm1Service.getVersion();
|
|
163
182
|
expect(result).toBe('12.0.0');
|
|
164
|
-
expect(mockRestService.
|
|
183
|
+
expect(mockRestService.getVersion).toHaveBeenCalledTimes(1);
|
|
165
184
|
});
|
|
166
185
|
test('should handle metadata retrieval errors', async () => {
|
|
167
186
|
const metadataError = new Error('Metadata not available');
|
|
@@ -170,7 +189,7 @@ describe('TM1Service', () => {
|
|
|
170
189
|
});
|
|
171
190
|
test('should handle version retrieval errors', async () => {
|
|
172
191
|
const versionError = new Error('Version not available');
|
|
173
|
-
mockRestService.
|
|
192
|
+
mockRestService.getVersion.mockRejectedValueOnce(versionError);
|
|
174
193
|
await expect(tm1Service.getVersion()).rejects.toThrow('Version not available');
|
|
175
194
|
});
|
|
176
195
|
});
|
|
@@ -286,5 +305,58 @@ describe('TM1Service', () => {
|
|
|
286
305
|
expect(monitoring1).toBe(monitoring2);
|
|
287
306
|
});
|
|
288
307
|
});
|
|
308
|
+
describe('Lazy Services (Issue #82)', () => {
|
|
309
|
+
const lazyServiceNames = [
|
|
310
|
+
'annotations',
|
|
311
|
+
'chores',
|
|
312
|
+
'git',
|
|
313
|
+
'sandboxes',
|
|
314
|
+
'jobs',
|
|
315
|
+
'users',
|
|
316
|
+
'threads',
|
|
317
|
+
'transactionLogs',
|
|
318
|
+
'messageLogs',
|
|
319
|
+
'configuration',
|
|
320
|
+
'auditLogs',
|
|
321
|
+
'loggers',
|
|
322
|
+
];
|
|
323
|
+
test.each(lazyServiceNames)('should lazy-initialize %s service', (serviceName) => {
|
|
324
|
+
const instance = tm1Service[serviceName];
|
|
325
|
+
expect(instance).toBeDefined();
|
|
326
|
+
});
|
|
327
|
+
test.each(lazyServiceNames)('should cache %s service instance across accesses', (serviceName) => {
|
|
328
|
+
const first = tm1Service[serviceName];
|
|
329
|
+
const second = tm1Service[serviceName];
|
|
330
|
+
expect(second).toBe(first);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
describe('Version Getter (Issue #82)', () => {
|
|
334
|
+
test('should expose cached version via sync getter', () => {
|
|
335
|
+
Object.defineProperty(mockRestService, 'version', {
|
|
336
|
+
get: () => '11.8.0',
|
|
337
|
+
configurable: true,
|
|
338
|
+
});
|
|
339
|
+
expect(tm1Service.version).toBe('11.8.0');
|
|
340
|
+
});
|
|
341
|
+
test('should return undefined when version has not been fetched yet', () => {
|
|
342
|
+
Object.defineProperty(mockRestService, 'version', {
|
|
343
|
+
get: () => undefined,
|
|
344
|
+
configurable: true,
|
|
345
|
+
});
|
|
346
|
+
expect(tm1Service.version).toBeUndefined();
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
describe('reConnect (Issue #82)', () => {
|
|
350
|
+
test('should call connect without disconnecting (tm1py parity)', async () => {
|
|
351
|
+
await tm1Service.reConnect();
|
|
352
|
+
expect(mockRestService.connect).toHaveBeenCalledTimes(1);
|
|
353
|
+
expect(mockRestService.disconnect).not.toHaveBeenCalled();
|
|
354
|
+
});
|
|
355
|
+
test('should propagate errors from connect', async () => {
|
|
356
|
+
mockRestService.connect.mockRejectedValueOnce(new Error('Connect failed'));
|
|
357
|
+
await expect(tm1Service.reConnect()).rejects.toThrow('Connect failed');
|
|
358
|
+
expect(mockRestService.connect).toHaveBeenCalledTimes(1);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
289
361
|
});
|
|
290
362
|
//# sourceMappingURL=tm1Service.test.js.map
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AxiosResponse } from 'axios';
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
3
|
import { RestService } from './RestService';
|
|
4
4
|
import { ObjectService } from './ObjectService';
|
|
@@ -152,7 +152,7 @@ export class ApplicationService extends ObjectService {
|
|
|
152
152
|
requestName
|
|
153
153
|
);
|
|
154
154
|
|
|
155
|
-
const arrayBufferResponse = await this.rest.get(contentUrl, { responseType: 'arraybuffer' }
|
|
155
|
+
const arrayBufferResponse = await this.rest.get(contentUrl, { responseType: 'arraybuffer' });
|
|
156
156
|
const metadataResponse = await this.rest.get(metadataUrl);
|
|
157
157
|
|
|
158
158
|
const buffer = Buffer.from(arrayBufferResponse.data);
|
|
@@ -183,7 +183,7 @@ export class ApplicationService extends ObjectService {
|
|
|
183
183
|
);
|
|
184
184
|
await this.rest.put(contentUrl, application.content, {
|
|
185
185
|
headers: this.binaryHttpHeader
|
|
186
|
-
}
|
|
186
|
+
});
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
return response;
|
|
@@ -204,7 +204,7 @@ export class ApplicationService extends ObjectService {
|
|
|
204
204
|
);
|
|
205
205
|
return await this.rest.post(url, application.content, {
|
|
206
206
|
headers: this.binaryHttpHeader
|
|
207
|
-
}
|
|
207
|
+
});
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
const url = formatUrl(
|
|
@@ -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
|
-
//
|
|
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("/
|
|
119
|
-
const response = await this.rest.get(url);
|
|
120
|
-
const serverStatus = this.
|
|
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
|
|
127
|
-
operation.result = response.data
|
|
128
|
-
} else if (serverStatus === OperationStatus.FAILED
|
|
129
|
-
operation.error = response
|
|
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
|
-
//
|
|
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
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import {
|
|
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:
|
|
230
|
+
const config: RequestOptions = {
|
|
231
231
|
headers: this.binaryHttpHeader
|
|
232
232
|
};
|
|
233
233
|
return await this.rest.put(url, content, config);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AxiosResponse } from 'axios';
|
|
2
2
|
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
-
import { RestService } from './RestService';
|
|
3
|
+
import { RequestOptions, RestService } from './RestService';
|
|
4
4
|
import { ObjectService } from './ObjectService';
|
|
5
5
|
import { Process } from '../objects/Process';
|
|
6
6
|
import { ProcessDebugBreakpoint, BreakPointType, HitMode } from '../objects/ProcessDebugBreakpoint';
|
|
@@ -13,6 +13,26 @@ export interface CompileSyntaxError {
|
|
|
13
13
|
Message: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
interface ProcessExecuteBody {
|
|
17
|
+
Parameters?: Array<{ Name: string; Value: unknown }>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ProcessExecuteSummary {
|
|
21
|
+
ProcessExecuteStatusCode: string;
|
|
22
|
+
ErrorLogFile?: { Filename?: string } | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ValidationError {
|
|
26
|
+
line: number;
|
|
27
|
+
message: string;
|
|
28
|
+
severity: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ValidationResult {
|
|
32
|
+
isValid: boolean;
|
|
33
|
+
errors: ValidationError[];
|
|
34
|
+
}
|
|
35
|
+
|
|
16
36
|
export class ProcessService extends ObjectService {
|
|
17
37
|
/** Service to handle Object Updates for TI Processes
|
|
18
38
|
*
|
|
@@ -79,7 +99,7 @@ export class ProcessService extends ObjectService {
|
|
|
79
99
|
|
|
80
100
|
const response = await this.rest.get(url);
|
|
81
101
|
const responseAsDict = response.data;
|
|
82
|
-
return responseAsDict.value.map((p:
|
|
102
|
+
return responseAsDict.value.map((p: Record<string, unknown>) => Process.fromDict(p));
|
|
83
103
|
}
|
|
84
104
|
|
|
85
105
|
public async getAllNames(skipControlProcesses: boolean = false): Promise<string[]> {
|
|
@@ -93,7 +113,7 @@ export class ProcessService extends ObjectService {
|
|
|
93
113
|
const url = "/Processes?$select=Name" + (skipControlProcesses ? modelProcessFilter : "");
|
|
94
114
|
|
|
95
115
|
const response = await this.rest.get(url);
|
|
96
|
-
const processes = response.data.value.map((process:
|
|
116
|
+
const processes = response.data.value.map((process: { Name: string }) => process.Name);
|
|
97
117
|
return processes;
|
|
98
118
|
}
|
|
99
119
|
|
|
@@ -174,8 +194,8 @@ export class ProcessService extends ObjectService {
|
|
|
174
194
|
* :return: response
|
|
175
195
|
*/
|
|
176
196
|
const url = formatUrl("/Processes('{}')/tm1.Execute", processName);
|
|
177
|
-
|
|
178
|
-
const body:
|
|
197
|
+
|
|
198
|
+
const body: ProcessExecuteBody = {};
|
|
179
199
|
if (parameters && Object.keys(parameters).length > 0) {
|
|
180
200
|
body.Parameters = Object.entries(parameters).map(([name, value]) => ({
|
|
181
201
|
Name: name,
|
|
@@ -199,8 +219,8 @@ export class ProcessService extends ObjectService {
|
|
|
199
219
|
* :return: response including execution details
|
|
200
220
|
*/
|
|
201
221
|
const url = formatUrl("/Processes('{}')/tm1.ExecuteWithReturn?$expand=*", processName);
|
|
202
|
-
|
|
203
|
-
const body:
|
|
222
|
+
|
|
223
|
+
const body: ProcessExecuteBody = {};
|
|
204
224
|
if (parameters && Object.keys(parameters).length > 0) {
|
|
205
225
|
body.Parameters = Object.entries(parameters).map(([name, value]) => ({
|
|
206
226
|
Name: name,
|
|
@@ -208,9 +228,9 @@ export class ProcessService extends ObjectService {
|
|
|
208
228
|
}));
|
|
209
229
|
}
|
|
210
230
|
|
|
211
|
-
const config:
|
|
231
|
+
const config: RequestOptions = {};
|
|
212
232
|
if (timeout) {
|
|
213
|
-
config.timeout = timeout
|
|
233
|
+
config.timeout = timeout;
|
|
214
234
|
}
|
|
215
235
|
|
|
216
236
|
return await this.rest.post(url, JSON.stringify(body), config);
|
|
@@ -247,12 +267,20 @@ export class ProcessService extends ObjectService {
|
|
|
247
267
|
public async pollExecuteWithReturn(asyncId: string): Promise<[boolean, string, string | null] | null> {
|
|
248
268
|
try {
|
|
249
269
|
const response = await this.rest.retrieve_async_response(asyncId);
|
|
270
|
+
// tm1py returns None while the async op is still in-flight (status 202).
|
|
271
|
+
if (response.status !== 200 && response.status !== 201) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
250
274
|
// TODO: tm1py handles TM1 < v11 binary-wrapped responses via
|
|
251
275
|
// build_response_from_binary_response. Add support if needed.
|
|
252
|
-
return this._executeWithReturnParseResponse(response);
|
|
253
|
-
} catch (error
|
|
254
|
-
//
|
|
255
|
-
|
|
276
|
+
return this._executeWithReturnParseResponse(response.data);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
// 404 means the async resource hasn't materialized yet — return null
|
|
279
|
+
// so the caller can retry. 202 means still running. Both are
|
|
280
|
+
// retryable, unlike AsyncOperationService which treats 404 as
|
|
281
|
+
// terminal FAILED for locally-tracked operations.
|
|
282
|
+
const err = error as { status?: number; response?: { status?: number } };
|
|
283
|
+
const status = err?.status ?? err?.response?.status;
|
|
256
284
|
if (status === 202 || status === 404) {
|
|
257
285
|
return null;
|
|
258
286
|
}
|
|
@@ -260,7 +288,7 @@ export class ProcessService extends ObjectService {
|
|
|
260
288
|
}
|
|
261
289
|
}
|
|
262
290
|
|
|
263
|
-
private _executeWithReturnParseResponse(executionSummary:
|
|
291
|
+
private _executeWithReturnParseResponse(executionSummary: ProcessExecuteSummary): [boolean, string, string | null] {
|
|
264
292
|
const success = executionSummary.ProcessExecuteStatusCode === 'CompletedSuccessfully';
|
|
265
293
|
const status = executionSummary.ProcessExecuteStatusCode;
|
|
266
294
|
const errorLogFile = executionSummary.ErrorLogFile?.Filename ?? null;
|
|
@@ -300,7 +328,7 @@ export class ProcessService extends ObjectService {
|
|
|
300
328
|
*/
|
|
301
329
|
const url = formatUrl("/ProcessDebugContexts('{}')/Breakpoints", debugId);
|
|
302
330
|
const response = await this.rest.get(url);
|
|
303
|
-
return response.data.value.map((bp:
|
|
331
|
+
return response.data.value.map((bp: Record<string, unknown>) => ProcessDebugBreakpoint.fromDict(bp));
|
|
304
332
|
}
|
|
305
333
|
|
|
306
334
|
public async debugAddBreakpoint(
|
|
@@ -532,7 +560,7 @@ export class ProcessService extends ObjectService {
|
|
|
532
560
|
}
|
|
533
561
|
|
|
534
562
|
const response = await this.rest.get(url);
|
|
535
|
-
return response.data.value.map((log:
|
|
563
|
+
return response.data.value.map((log: { Filename: string }) => log.Filename);
|
|
536
564
|
}
|
|
537
565
|
|
|
538
566
|
public async getErrorLogFilenames(
|
|
@@ -619,7 +647,7 @@ export class ProcessService extends ObjectService {
|
|
|
619
647
|
}
|
|
620
648
|
|
|
621
649
|
const response = await this.rest.get(url);
|
|
622
|
-
return response.data.value.map((process:
|
|
650
|
+
return response.data.value.map((process: { Name: string }) => process.Name);
|
|
623
651
|
}
|
|
624
652
|
|
|
625
653
|
public async updateOrCreate(process: Process): Promise<AxiosResponse> {
|
|
@@ -673,7 +701,7 @@ export class ProcessService extends ObjectService {
|
|
|
673
701
|
processName
|
|
674
702
|
);
|
|
675
703
|
|
|
676
|
-
const body:
|
|
704
|
+
const body: ProcessExecuteBody = {};
|
|
677
705
|
if (parameters && Object.keys(parameters).length > 0) {
|
|
678
706
|
body.Parameters = Object.entries(parameters).map(([name, value]) => ({
|
|
679
707
|
Name: name,
|
|
@@ -681,12 +709,14 @@ export class ProcessService extends ObjectService {
|
|
|
681
709
|
}));
|
|
682
710
|
}
|
|
683
711
|
|
|
684
|
-
const config:
|
|
712
|
+
const config: RequestOptions = {};
|
|
685
713
|
if (timeout) {
|
|
686
|
-
config.timeout = timeout
|
|
714
|
+
config.timeout = timeout;
|
|
687
715
|
}
|
|
688
716
|
|
|
689
|
-
|
|
717
|
+
// rest.post returns AxiosResponse | string (string only when caller
|
|
718
|
+
// opts into returnAsyncId). debugProcess never does, so narrow.
|
|
719
|
+
const response = (await this.rest.post(url, JSON.stringify(body), config)) as AxiosResponse;
|
|
690
720
|
return response.data;
|
|
691
721
|
}
|
|
692
722
|
|
|
@@ -885,8 +915,8 @@ export class ProcessService extends ObjectService {
|
|
|
885
915
|
* @example
|
|
886
916
|
* ```typescript
|
|
887
917
|
* const deps = await processService.analyzeProcessDependencies('ImportData');
|
|
888
|
-
*
|
|
889
|
-
*
|
|
918
|
+
* // deps.cubes => array of cube names referenced in the process
|
|
919
|
+
* // deps.dimensions => array of dimension names referenced in the process
|
|
890
920
|
* ```
|
|
891
921
|
*/
|
|
892
922
|
public async analyzeProcessDependencies(processName: string): Promise<any> {
|
|
@@ -954,17 +984,17 @@ export class ProcessService extends ObjectService {
|
|
|
954
984
|
* Validate process syntax without executing it
|
|
955
985
|
*
|
|
956
986
|
* @param processName - Name of the process
|
|
957
|
-
* @returns Promise<
|
|
987
|
+
* @returns Promise<ValidationResult> - Validation result
|
|
958
988
|
*
|
|
959
989
|
* @example
|
|
960
990
|
* ```typescript
|
|
961
991
|
* const result = await processService.validateProcessSyntax('MyProcess');
|
|
962
992
|
* if (!result.isValid) {
|
|
963
|
-
*
|
|
993
|
+
* // result.errors contains ValidationError[] entries
|
|
964
994
|
* }
|
|
965
995
|
* ```
|
|
966
996
|
*/
|
|
967
|
-
public async validateProcessSyntax(processName: string): Promise<
|
|
997
|
+
public async validateProcessSyntax(processName: string): Promise<ValidationResult> {
|
|
968
998
|
try {
|
|
969
999
|
const response = await this.compile(processName);
|
|
970
1000
|
if (response.status === 200) {
|
|
@@ -974,8 +1004,9 @@ export class ProcessService extends ObjectService {
|
|
|
974
1004
|
isValid: false,
|
|
975
1005
|
errors: [{ line: 0, message: response.statusText || 'Compilation failed', severity: 'Error' }]
|
|
976
1006
|
};
|
|
977
|
-
} catch (error
|
|
978
|
-
const
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
const err = error as { response?: { data?: { error?: { message?: string } } }; message?: string };
|
|
1009
|
+
const errorMessage = err.response?.data?.error?.message || err.message || 'Validation failed';
|
|
979
1010
|
return {
|
|
980
1011
|
isValid: false,
|
|
981
1012
|
errors: [{ line: 0, message: errorMessage, severity: 'Error' }]
|
|
@@ -992,7 +1023,7 @@ export class ProcessService extends ObjectService {
|
|
|
992
1023
|
* @example
|
|
993
1024
|
* ```typescript
|
|
994
1025
|
* const plan = await processService.getProcessExecutionPlan('ImportData');
|
|
995
|
-
*
|
|
1026
|
+
* // plan.estimatedTime => estimated execution time (ms)
|
|
996
1027
|
* ```
|
|
997
1028
|
*/
|
|
998
1029
|
public async getProcessExecutionPlan(processName: string): Promise<any> {
|
|
@@ -1072,19 +1103,20 @@ export class ProcessService extends ObjectService {
|
|
|
1072
1103
|
|
|
1073
1104
|
// Execute process asynchronously
|
|
1074
1105
|
this.executeWithReturn(processName, parameters)
|
|
1075
|
-
.then((result:
|
|
1106
|
+
.then((result: unknown) => {
|
|
1076
1107
|
asyncOps.updateOperationStatus(
|
|
1077
1108
|
operationId,
|
|
1078
1109
|
OperationStatus.COMPLETED,
|
|
1079
1110
|
result
|
|
1080
1111
|
);
|
|
1081
1112
|
})
|
|
1082
|
-
.catch((error:
|
|
1113
|
+
.catch((error: unknown) => {
|
|
1114
|
+
const message = (error as { message?: string })?.message ?? String(error);
|
|
1083
1115
|
asyncOps.updateOperationStatus(
|
|
1084
1116
|
operationId,
|
|
1085
1117
|
OperationStatus.FAILED,
|
|
1086
1118
|
undefined,
|
|
1087
|
-
|
|
1119
|
+
message
|
|
1088
1120
|
);
|
|
1089
1121
|
});
|
|
1090
1122
|
|
|
@@ -1100,9 +1132,7 @@ export class ProcessService extends ObjectService {
|
|
|
1100
1132
|
* @example
|
|
1101
1133
|
* ```typescript
|
|
1102
1134
|
* const status = await processService.pollProcessExecution(operationId);
|
|
1103
|
-
* if (status === OperationStatus.COMPLETED)
|
|
1104
|
-
* console.log('Process completed!');
|
|
1105
|
-
* }
|
|
1135
|
+
* // if (status === OperationStatus.COMPLETED) handle completion
|
|
1106
1136
|
* ```
|
|
1107
1137
|
*/
|
|
1108
1138
|
public async pollProcessExecution(operationId: string): Promise<OperationStatus> {
|
|
@@ -1123,7 +1153,7 @@ export class ProcessService extends ObjectService {
|
|
|
1123
1153
|
* @example
|
|
1124
1154
|
* ```typescript
|
|
1125
1155
|
* await processService.cancelProcessExecution(operationId);
|
|
1126
|
-
*
|
|
1156
|
+
* // cancellation is acknowledged once the promise resolves
|
|
1127
1157
|
* ```
|
|
1128
1158
|
*/
|
|
1129
1159
|
public async cancelProcessExecution(operationId: string): Promise<void> {
|
|
@@ -1134,4 +1164,4 @@ export class ProcessService extends ObjectService {
|
|
|
1134
1164
|
|
|
1135
1165
|
await asyncOps.cancelAsyncOperation(operationId);
|
|
1136
1166
|
}
|
|
1137
|
-
}
|
|
1167
|
+
}
|