@object-ui/data-objectstack 3.1.4 → 3.3.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/dist/index.cjs +24 -17
- package/dist/index.js +24 -17
- package/package.json +6 -6
- package/src/aggregate.test.ts +277 -0
- package/src/errors.ts +2 -2
- package/src/index.ts +31 -7
package/dist/index.cjs
CHANGED
|
@@ -213,9 +213,9 @@ var ObjectStackError = class extends Error {
|
|
|
213
213
|
*/
|
|
214
214
|
constructor(message, code, statusCode, details) {
|
|
215
215
|
super(message);
|
|
216
|
-
this
|
|
217
|
-
this
|
|
218
|
-
this
|
|
216
|
+
__publicField(this, "code", code);
|
|
217
|
+
__publicField(this, "statusCode", statusCode);
|
|
218
|
+
__publicField(this, "details", details);
|
|
219
219
|
this.name = "ObjectStackError";
|
|
220
220
|
if (Error.captureStackTrace) {
|
|
221
221
|
Error.captureStackTrace(this, this.constructor);
|
|
@@ -269,9 +269,9 @@ var BulkOperationError = class extends ObjectStackError {
|
|
|
269
269
|
...details
|
|
270
270
|
}
|
|
271
271
|
);
|
|
272
|
-
this
|
|
273
|
-
this
|
|
274
|
-
this
|
|
272
|
+
__publicField(this, "successCount", successCount);
|
|
273
|
+
__publicField(this, "failureCount", failureCount);
|
|
274
|
+
__publicField(this, "errors", errors);
|
|
275
275
|
this.name = "BulkOperationError";
|
|
276
276
|
}
|
|
277
277
|
/**
|
|
@@ -298,7 +298,7 @@ var ConnectionError = class extends ObjectStackError {
|
|
|
298
298
|
statusCode || 503,
|
|
299
299
|
{ url, ...details }
|
|
300
300
|
);
|
|
301
|
-
this
|
|
301
|
+
__publicField(this, "url", url);
|
|
302
302
|
this.name = "ConnectionError";
|
|
303
303
|
}
|
|
304
304
|
};
|
|
@@ -333,8 +333,8 @@ var ValidationError = class extends ObjectStackError {
|
|
|
333
333
|
...details
|
|
334
334
|
}
|
|
335
335
|
);
|
|
336
|
-
this
|
|
337
|
-
this
|
|
336
|
+
__publicField(this, "field", field);
|
|
337
|
+
__publicField(this, "validationErrors", validationErrors);
|
|
338
338
|
this.name = "ValidationError";
|
|
339
339
|
}
|
|
340
340
|
/**
|
|
@@ -391,7 +391,7 @@ function isErrorType(error, errorClass) {
|
|
|
391
391
|
// src/cloud.ts
|
|
392
392
|
var CloudOperations = class {
|
|
393
393
|
constructor(getClient) {
|
|
394
|
-
this
|
|
394
|
+
__publicField(this, "getClient", getClient);
|
|
395
395
|
}
|
|
396
396
|
/**
|
|
397
397
|
* Deploy an application to the cloud.
|
|
@@ -1318,19 +1318,26 @@ var ObjectStackAdapter = class {
|
|
|
1318
1318
|
async aggregate(resource, params) {
|
|
1319
1319
|
await this.connect();
|
|
1320
1320
|
try {
|
|
1321
|
+
const measureName = params.function === "count" ? "count" : `${params.field}_${params.function}`;
|
|
1321
1322
|
const payload = {
|
|
1322
|
-
|
|
1323
|
-
measures: [
|
|
1324
|
-
dimensions
|
|
1323
|
+
cube: resource,
|
|
1324
|
+
measures: [measureName],
|
|
1325
|
+
// When groupBy is '_all' no dimensions are needed (single-bucket).
|
|
1326
|
+
dimensions: params.groupBy && params.groupBy !== "_all" ? [params.groupBy] : []
|
|
1325
1327
|
};
|
|
1326
1328
|
if (params.filter) {
|
|
1327
1329
|
payload.filters = params.filter;
|
|
1328
1330
|
}
|
|
1329
1331
|
const data = await this.client.analytics.query(payload);
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1332
|
+
const rawRows = Array.isArray(data) ? data : data?.rows && Array.isArray(data.rows) ? data.rows : data?.data && Array.isArray(data.data) ? data.data : data?.data?.rows && Array.isArray(data.data.rows) ? data.data.rows : data?.results && Array.isArray(data.results) ? data.results : [];
|
|
1333
|
+
return rawRows.map((row) => {
|
|
1334
|
+
const mapped = { ...row };
|
|
1335
|
+
if (measureName !== params.field && measureName in mapped) {
|
|
1336
|
+
mapped[params.field] = mapped[measureName];
|
|
1337
|
+
delete mapped[measureName];
|
|
1338
|
+
}
|
|
1339
|
+
return mapped;
|
|
1340
|
+
});
|
|
1334
1341
|
} catch {
|
|
1335
1342
|
const result = await this.find(resource);
|
|
1336
1343
|
const records = result.data || [];
|
package/dist/index.js
CHANGED
|
@@ -173,9 +173,9 @@ var ObjectStackError = class extends Error {
|
|
|
173
173
|
*/
|
|
174
174
|
constructor(message, code, statusCode, details) {
|
|
175
175
|
super(message);
|
|
176
|
-
this
|
|
177
|
-
this
|
|
178
|
-
this
|
|
176
|
+
__publicField(this, "code", code);
|
|
177
|
+
__publicField(this, "statusCode", statusCode);
|
|
178
|
+
__publicField(this, "details", details);
|
|
179
179
|
this.name = "ObjectStackError";
|
|
180
180
|
if (Error.captureStackTrace) {
|
|
181
181
|
Error.captureStackTrace(this, this.constructor);
|
|
@@ -229,9 +229,9 @@ var BulkOperationError = class extends ObjectStackError {
|
|
|
229
229
|
...details
|
|
230
230
|
}
|
|
231
231
|
);
|
|
232
|
-
this
|
|
233
|
-
this
|
|
234
|
-
this
|
|
232
|
+
__publicField(this, "successCount", successCount);
|
|
233
|
+
__publicField(this, "failureCount", failureCount);
|
|
234
|
+
__publicField(this, "errors", errors);
|
|
235
235
|
this.name = "BulkOperationError";
|
|
236
236
|
}
|
|
237
237
|
/**
|
|
@@ -258,7 +258,7 @@ var ConnectionError = class extends ObjectStackError {
|
|
|
258
258
|
statusCode || 503,
|
|
259
259
|
{ url, ...details }
|
|
260
260
|
);
|
|
261
|
-
this
|
|
261
|
+
__publicField(this, "url", url);
|
|
262
262
|
this.name = "ConnectionError";
|
|
263
263
|
}
|
|
264
264
|
};
|
|
@@ -293,8 +293,8 @@ var ValidationError = class extends ObjectStackError {
|
|
|
293
293
|
...details
|
|
294
294
|
}
|
|
295
295
|
);
|
|
296
|
-
this
|
|
297
|
-
this
|
|
296
|
+
__publicField(this, "field", field);
|
|
297
|
+
__publicField(this, "validationErrors", validationErrors);
|
|
298
298
|
this.name = "ValidationError";
|
|
299
299
|
}
|
|
300
300
|
/**
|
|
@@ -351,7 +351,7 @@ function isErrorType(error, errorClass) {
|
|
|
351
351
|
// src/cloud.ts
|
|
352
352
|
var CloudOperations = class {
|
|
353
353
|
constructor(getClient) {
|
|
354
|
-
this
|
|
354
|
+
__publicField(this, "getClient", getClient);
|
|
355
355
|
}
|
|
356
356
|
/**
|
|
357
357
|
* Deploy an application to the cloud.
|
|
@@ -1278,19 +1278,26 @@ var ObjectStackAdapter = class {
|
|
|
1278
1278
|
async aggregate(resource, params) {
|
|
1279
1279
|
await this.connect();
|
|
1280
1280
|
try {
|
|
1281
|
+
const measureName = params.function === "count" ? "count" : `${params.field}_${params.function}`;
|
|
1281
1282
|
const payload = {
|
|
1282
|
-
|
|
1283
|
-
measures: [
|
|
1284
|
-
dimensions
|
|
1283
|
+
cube: resource,
|
|
1284
|
+
measures: [measureName],
|
|
1285
|
+
// When groupBy is '_all' no dimensions are needed (single-bucket).
|
|
1286
|
+
dimensions: params.groupBy && params.groupBy !== "_all" ? [params.groupBy] : []
|
|
1285
1287
|
};
|
|
1286
1288
|
if (params.filter) {
|
|
1287
1289
|
payload.filters = params.filter;
|
|
1288
1290
|
}
|
|
1289
1291
|
const data = await this.client.analytics.query(payload);
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1292
|
+
const rawRows = Array.isArray(data) ? data : data?.rows && Array.isArray(data.rows) ? data.rows : data?.data && Array.isArray(data.data) ? data.data : data?.data?.rows && Array.isArray(data.data.rows) ? data.data.rows : data?.results && Array.isArray(data.results) ? data.results : [];
|
|
1293
|
+
return rawRows.map((row) => {
|
|
1294
|
+
const mapped = { ...row };
|
|
1295
|
+
if (measureName !== params.field && measureName in mapped) {
|
|
1296
|
+
mapped[params.field] = mapped[measureName];
|
|
1297
|
+
delete mapped[measureName];
|
|
1298
|
+
}
|
|
1299
|
+
return mapped;
|
|
1300
|
+
});
|
|
1294
1301
|
} catch {
|
|
1295
1302
|
const result = await this.find(resource);
|
|
1296
1303
|
const records = result.data || [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/data-objectstack",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "ObjectStack Data Adapter for Object UI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@objectstack/client": "^
|
|
24
|
-
"@object-ui/core": "3.
|
|
25
|
-
"@object-ui/types": "3.
|
|
23
|
+
"@objectstack/client": "^4.0.3",
|
|
24
|
+
"@object-ui/core": "3.3.0",
|
|
25
|
+
"@object-ui/types": "3.3.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"tsup": "^8.5.1",
|
|
29
|
-
"typescript": "^
|
|
30
|
-
"vitest": "^4.1.
|
|
29
|
+
"typescript": "^6.0.2",
|
|
30
|
+
"vitest": "^4.1.4"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
10
|
+
import { ObjectStackAdapter } from './index';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Tests for ObjectStackAdapter.aggregate() — verifies that the analytics
|
|
14
|
+
* query payload uses the correct string-based measure/dimension format
|
|
15
|
+
* expected by the backend analytics service (MemoryAnalyticsService).
|
|
16
|
+
*
|
|
17
|
+
* See: https://github.com/objectstack-ai/objectui/issues (measures format bug)
|
|
18
|
+
*/
|
|
19
|
+
describe('ObjectStackAdapter aggregate()', () => {
|
|
20
|
+
let adapter: ObjectStackAdapter;
|
|
21
|
+
let mockAnalyticsQuery: ReturnType<typeof vi.fn>;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
mockAnalyticsQuery = vi.fn().mockResolvedValue({ data: [] });
|
|
25
|
+
|
|
26
|
+
adapter = new ObjectStackAdapter({
|
|
27
|
+
baseUrl: 'http://localhost:3000',
|
|
28
|
+
autoReconnect: false,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Inject mock client and mark as connected to bypass connect()
|
|
32
|
+
(adapter as any).client = {
|
|
33
|
+
data: {
|
|
34
|
+
find: vi.fn().mockResolvedValue({ records: [], total: 0 }),
|
|
35
|
+
},
|
|
36
|
+
analytics: {
|
|
37
|
+
query: mockAnalyticsQuery,
|
|
38
|
+
},
|
|
39
|
+
connect: vi.fn().mockResolvedValue(undefined),
|
|
40
|
+
discover: vi.fn().mockResolvedValue({ status: 'ok' }),
|
|
41
|
+
};
|
|
42
|
+
(adapter as any).connected = true;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should send measures as string array with field_function format for sum', async () => {
|
|
46
|
+
mockAnalyticsQuery.mockResolvedValue({ data: [] });
|
|
47
|
+
|
|
48
|
+
await adapter.aggregate('opportunity', {
|
|
49
|
+
field: 'amount',
|
|
50
|
+
function: 'sum',
|
|
51
|
+
groupBy: 'stage',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(mockAnalyticsQuery).toHaveBeenCalledWith({
|
|
55
|
+
cube: 'opportunity',
|
|
56
|
+
measures: ['amount_sum'],
|
|
57
|
+
dimensions: ['stage'],
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should send measures as ["count"] for count aggregation', async () => {
|
|
62
|
+
mockAnalyticsQuery.mockResolvedValue({ data: [] });
|
|
63
|
+
|
|
64
|
+
await adapter.aggregate('opportunity', {
|
|
65
|
+
field: 'amount',
|
|
66
|
+
function: 'count',
|
|
67
|
+
groupBy: 'stage',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(mockAnalyticsQuery).toHaveBeenCalledWith({
|
|
71
|
+
cube: 'opportunity',
|
|
72
|
+
measures: ['count'],
|
|
73
|
+
dimensions: ['stage'],
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should send measures as string for avg aggregation', async () => {
|
|
78
|
+
mockAnalyticsQuery.mockResolvedValue({ data: [] });
|
|
79
|
+
|
|
80
|
+
await adapter.aggregate('opportunity', {
|
|
81
|
+
field: 'amount',
|
|
82
|
+
function: 'avg',
|
|
83
|
+
groupBy: 'stage',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(mockAnalyticsQuery).toHaveBeenCalledWith({
|
|
87
|
+
cube: 'opportunity',
|
|
88
|
+
measures: ['amount_avg'],
|
|
89
|
+
dimensions: ['stage'],
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should send empty dimensions when groupBy is _all', async () => {
|
|
94
|
+
mockAnalyticsQuery.mockResolvedValue({ data: [] });
|
|
95
|
+
|
|
96
|
+
await adapter.aggregate('opportunity', {
|
|
97
|
+
field: 'amount',
|
|
98
|
+
function: 'sum',
|
|
99
|
+
groupBy: '_all',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(mockAnalyticsQuery).toHaveBeenCalledWith({
|
|
103
|
+
cube: 'opportunity',
|
|
104
|
+
measures: ['amount_sum'],
|
|
105
|
+
dimensions: [],
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should include filters in payload when provided', async () => {
|
|
110
|
+
const filter = [{ member: 'stage', operator: 'equals', values: ['Closed Won'] }];
|
|
111
|
+
mockAnalyticsQuery.mockResolvedValue({ data: [] });
|
|
112
|
+
|
|
113
|
+
await adapter.aggregate('opportunity', {
|
|
114
|
+
field: 'amount',
|
|
115
|
+
function: 'sum',
|
|
116
|
+
groupBy: 'stage',
|
|
117
|
+
filter,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(mockAnalyticsQuery).toHaveBeenCalledWith({
|
|
121
|
+
cube: 'opportunity',
|
|
122
|
+
measures: ['amount_sum'],
|
|
123
|
+
dimensions: ['stage'],
|
|
124
|
+
filters: filter,
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should map measure key back to field name in response', async () => {
|
|
129
|
+
mockAnalyticsQuery.mockResolvedValue({
|
|
130
|
+
data: [
|
|
131
|
+
{ stage: 'Prospect', amount_sum: 300 },
|
|
132
|
+
{ stage: 'Closed Won', amount_sum: 500 },
|
|
133
|
+
],
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const result = await adapter.aggregate('opportunity', {
|
|
137
|
+
field: 'amount',
|
|
138
|
+
function: 'sum',
|
|
139
|
+
groupBy: 'stage',
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(result).toEqual([
|
|
143
|
+
{ stage: 'Prospect', amount: 300 },
|
|
144
|
+
{ stage: 'Closed Won', amount: 500 },
|
|
145
|
+
]);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should map count measure back to field name in response', async () => {
|
|
149
|
+
mockAnalyticsQuery.mockResolvedValue({
|
|
150
|
+
data: [
|
|
151
|
+
{ stage: 'Prospect', count: 5 },
|
|
152
|
+
{ stage: 'Closed Won', count: 3 },
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const result = await adapter.aggregate('opportunity', {
|
|
157
|
+
field: 'amount',
|
|
158
|
+
function: 'count',
|
|
159
|
+
groupBy: 'stage',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(result).toEqual([
|
|
163
|
+
{ stage: 'Prospect', amount: 5 },
|
|
164
|
+
{ stage: 'Closed Won', amount: 3 },
|
|
165
|
+
]);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should handle direct array response from analytics', async () => {
|
|
169
|
+
mockAnalyticsQuery.mockResolvedValue([
|
|
170
|
+
{ stage: 'Prospect', amount_sum: 300 },
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
const result = await adapter.aggregate('opportunity', {
|
|
174
|
+
field: 'amount',
|
|
175
|
+
function: 'sum',
|
|
176
|
+
groupBy: 'stage',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
expect(result).toEqual([
|
|
180
|
+
{ stage: 'Prospect', amount: 300 },
|
|
181
|
+
]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should handle results wrapper in response', async () => {
|
|
185
|
+
mockAnalyticsQuery.mockResolvedValue({
|
|
186
|
+
results: [
|
|
187
|
+
{ stage: 'Prospect', amount_avg: 150 },
|
|
188
|
+
],
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const result = await adapter.aggregate('opportunity', {
|
|
192
|
+
field: 'amount',
|
|
193
|
+
function: 'avg',
|
|
194
|
+
groupBy: 'stage',
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(result).toEqual([
|
|
198
|
+
{ stage: 'Prospect', amount: 150 },
|
|
199
|
+
]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should extract rows from { rows: [...] } envelope (SDK unwraps outer success/data)', async () => {
|
|
203
|
+
mockAnalyticsQuery.mockResolvedValue({
|
|
204
|
+
rows: [
|
|
205
|
+
{ stage: 'closed_won', expected_revenue_sum: 225000 },
|
|
206
|
+
{ stage: 'negotiation', expected_revenue_sum: 36000 },
|
|
207
|
+
],
|
|
208
|
+
fields: [
|
|
209
|
+
{ name: 'stage', type: 'string' },
|
|
210
|
+
{ name: 'expected_revenue_sum', type: 'number' },
|
|
211
|
+
],
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const result = await adapter.aggregate('opportunity', {
|
|
215
|
+
field: 'expected_revenue',
|
|
216
|
+
function: 'sum',
|
|
217
|
+
groupBy: 'stage',
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
expect(result).toEqual([
|
|
221
|
+
{ stage: 'closed_won', expected_revenue: 225000 },
|
|
222
|
+
{ stage: 'negotiation', expected_revenue: 36000 },
|
|
223
|
+
]);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should extract rows from { data: { rows: [...] } } envelope (SDK does not unwrap)', async () => {
|
|
227
|
+
mockAnalyticsQuery.mockResolvedValue({
|
|
228
|
+
success: true,
|
|
229
|
+
data: {
|
|
230
|
+
rows: [
|
|
231
|
+
{ stage: 'closed_won', expected_revenue_sum: 225000 },
|
|
232
|
+
{ stage: 'negotiation', expected_revenue_sum: 36000 },
|
|
233
|
+
],
|
|
234
|
+
fields: [
|
|
235
|
+
{ name: 'stage', type: 'string' },
|
|
236
|
+
{ name: 'expected_revenue_sum', type: 'number' },
|
|
237
|
+
],
|
|
238
|
+
sql: '-- MongoDB Aggregation Pipeline',
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const result = await adapter.aggregate('opportunity', {
|
|
243
|
+
field: 'expected_revenue',
|
|
244
|
+
function: 'sum',
|
|
245
|
+
groupBy: 'stage',
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
expect(result).toEqual([
|
|
249
|
+
{ stage: 'closed_won', expected_revenue: 225000 },
|
|
250
|
+
{ stage: 'negotiation', expected_revenue: 36000 },
|
|
251
|
+
]);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should fall back to client-side aggregation when analytics endpoint fails', async () => {
|
|
255
|
+
mockAnalyticsQuery.mockRejectedValue(new Error('Analytics not available'));
|
|
256
|
+
|
|
257
|
+
// Mock find() to return records for client-side aggregation
|
|
258
|
+
(adapter as any).client.data.find = vi.fn().mockResolvedValue({
|
|
259
|
+
records: [
|
|
260
|
+
{ stage: 'Prospect', amount: 100 },
|
|
261
|
+
{ stage: 'Prospect', amount: 200 },
|
|
262
|
+
{ stage: 'Closed Won', amount: 500 },
|
|
263
|
+
],
|
|
264
|
+
total: 3,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const result = await adapter.aggregate('opportunity', {
|
|
268
|
+
field: 'amount',
|
|
269
|
+
function: 'sum',
|
|
270
|
+
groupBy: 'stage',
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
expect(result).toHaveLength(2);
|
|
274
|
+
expect(result.find((r: any) => r.stage === 'Prospect')?.amount).toBe(300);
|
|
275
|
+
expect(result.find((r: any) => r.stage === 'Closed Won')?.amount).toBe(500);
|
|
276
|
+
});
|
|
277
|
+
});
|
package/src/errors.ts
CHANGED
|
@@ -28,8 +28,8 @@ export class ObjectStackError extends Error {
|
|
|
28
28
|
this.name = 'ObjectStackError';
|
|
29
29
|
|
|
30
30
|
// Maintains proper stack trace for where error was thrown (only in V8)
|
|
31
|
-
if (Error.captureStackTrace) {
|
|
32
|
-
Error.captureStackTrace(this, this.constructor);
|
|
31
|
+
if ((Error as any).captureStackTrace) {
|
|
32
|
+
(Error as any).captureStackTrace(this, this.constructor);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
package/src/index.ts
CHANGED
|
@@ -829,20 +829,44 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
829
829
|
await this.connect();
|
|
830
830
|
|
|
831
831
|
try {
|
|
832
|
+
// Build measure name in the format expected by the backend analytics
|
|
833
|
+
// service (memory-analytics / cube). For 'count' the measure key is
|
|
834
|
+
// simply 'count'; for other aggregation functions it follows the
|
|
835
|
+
// convention `${field}_${function}` (e.g. 'amount_sum').
|
|
836
|
+
const measureName = params.function === 'count'
|
|
837
|
+
? 'count'
|
|
838
|
+
: `${params.field}_${params.function}`;
|
|
839
|
+
|
|
832
840
|
const payload: Record<string, unknown> = {
|
|
833
|
-
|
|
834
|
-
measures: [
|
|
835
|
-
dimensions
|
|
841
|
+
cube: resource,
|
|
842
|
+
measures: [measureName],
|
|
843
|
+
// When groupBy is '_all' no dimensions are needed (single-bucket).
|
|
844
|
+
dimensions: params.groupBy && params.groupBy !== '_all' ? [params.groupBy] : [],
|
|
836
845
|
};
|
|
837
846
|
if (params.filter) {
|
|
838
847
|
payload.filters = params.filter;
|
|
839
848
|
}
|
|
840
849
|
|
|
841
850
|
const data = await this.client.analytics.query(payload);
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
851
|
+
const rawRows: any[] = Array.isArray(data) ? data
|
|
852
|
+
: data?.rows && Array.isArray(data.rows) ? data.rows
|
|
853
|
+
: data?.data && Array.isArray(data.data) ? data.data
|
|
854
|
+
: data?.data?.rows && Array.isArray(data.data.rows) ? data.data.rows
|
|
855
|
+
: data?.results && Array.isArray(data.results) ? data.results
|
|
856
|
+
: [];
|
|
857
|
+
|
|
858
|
+
// Map measure keys back to the original field name so that consumers
|
|
859
|
+
// (ObjectChart, DashboardRenderer, etc.) can access values by field name.
|
|
860
|
+
// This includes count → field (e.g. 'count' → 'amount') to match the
|
|
861
|
+
// output format of aggregateClientSide() which always uses params.field.
|
|
862
|
+
return rawRows.map((row: any) => {
|
|
863
|
+
const mapped = { ...row };
|
|
864
|
+
if (measureName !== params.field && measureName in mapped) {
|
|
865
|
+
mapped[params.field] = mapped[measureName];
|
|
866
|
+
delete mapped[measureName];
|
|
867
|
+
}
|
|
868
|
+
return mapped;
|
|
869
|
+
});
|
|
846
870
|
} catch {
|
|
847
871
|
// If the analytics endpoint is not available, fall back to
|
|
848
872
|
// find() + client-side aggregation
|