From e5e7337ef20958cba0632bd205fb0ed7fde892f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 02:15:26 +0000 Subject: [PATCH 1/2] Initial plan From 70cb62b859bcb1e1f293d245d77aba14e0d368ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 02:18:08 +0000 Subject: [PATCH 2/2] fix(data-objectstack): handle rows and data.rows response envelopes in aggregate() Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/e3d0d80a-c390-42e4-bd21-38d21ba233a6 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- CHANGELOG.md | 2 +- .../data-objectstack/src/aggregate.test.ts | 52 +++++++++++++++++++ packages/data-objectstack/src/index.ts | 2 + 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d583da1e2..62ddc6b5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- **Analytics aggregate measures format** (`@object-ui/data-objectstack`): Fixed `aggregate()` method to send `measures` as string array (`['amount_sum']`, `['count']`) instead of object array (`[{ field, function }]`). The backend `MemoryAnalyticsService.resolveMeasure()` expects strings and calls `.split('.')`, causing `TypeError: t.split is not a function` when receiving objects. Also fixed `dimensions` to send an empty array when `groupBy` is `'_all'` (single-bucket aggregation), and added response mapping to rename measure keys (e.g. `amount_sum`) back to the original field name (`amount`) for consumer compatibility. +- **Analytics aggregate measures format** (`@object-ui/data-objectstack`): Fixed `aggregate()` method to send `measures` as string array (`['amount_sum']`, `['count']`) instead of object array (`[{ field, function }]`). The backend `MemoryAnalyticsService.resolveMeasure()` expects strings and calls `.split('.')`, causing `TypeError: t.split is not a function` when receiving objects. Also fixed `dimensions` to send an empty array when `groupBy` is `'_all'` (single-bucket aggregation), and added response mapping to rename measure keys (e.g. `amount_sum`) back to the original field name (`amount`) for consumer compatibility. Additionally fixed chart rendering blank issue: the `rawRows` extraction now handles the `{ rows: [...] }` envelope (when the SDK unwraps the outer `{ success, data }` wrapper) and the `{ data: { rows: [...] } }` envelope (when the SDK returns the full response), matching the actual shape returned by the analytics API (`/api/v1/analytics/query`). - **Fields SSR build** (`@object-ui/fields`): Added `@object-ui/i18n` to Vite `external` in `vite.config.ts` and converted to regex-based externalization pattern (consistent with `@object-ui/components`) to prevent `react-i18next` CJS code from being bundled. Fixes `"dynamic usage of require is not supported"` error during Next.js SSR prerendering of `/docs/components/basic/text`. - **Console build** (`@object-ui/console`): Added missing `@object-ui/plugin-chatbot` devDependency that caused `TS2307: Cannot find module '@object-ui/plugin-chatbot'` during build. - **Site SSR build** (`@object-ui/site`): Added `@object-ui/i18n` to `transpilePackages` in `next.config.mjs` to fix "dynamic usage of require is not supported" error when prerendering the tooltip docs page. The i18n package is a transitive dependency of `@object-ui/react` and its `react-i18next` dependency requires transpilation for Turbopack SSR compatibility. diff --git a/packages/data-objectstack/src/aggregate.test.ts b/packages/data-objectstack/src/aggregate.test.ts index b1a481299..95d4ec485 100644 --- a/packages/data-objectstack/src/aggregate.test.ts +++ b/packages/data-objectstack/src/aggregate.test.ts @@ -199,6 +199,58 @@ describe('ObjectStackAdapter aggregate()', () => { ]); }); + it('should extract rows from { rows: [...] } envelope (SDK unwraps outer success/data)', async () => { + mockAnalyticsQuery.mockResolvedValue({ + rows: [ + { stage: 'closed_won', expected_revenue_sum: 225000 }, + { stage: 'negotiation', expected_revenue_sum: 36000 }, + ], + fields: [ + { name: 'stage', type: 'string' }, + { name: 'expected_revenue_sum', type: 'number' }, + ], + }); + + const result = await adapter.aggregate('opportunity', { + field: 'expected_revenue', + function: 'sum', + groupBy: 'stage', + }); + + expect(result).toEqual([ + { stage: 'closed_won', expected_revenue: 225000 }, + { stage: 'negotiation', expected_revenue: 36000 }, + ]); + }); + + it('should extract rows from { data: { rows: [...] } } envelope (SDK does not unwrap)', async () => { + mockAnalyticsQuery.mockResolvedValue({ + success: true, + data: { + rows: [ + { stage: 'closed_won', expected_revenue_sum: 225000 }, + { stage: 'negotiation', expected_revenue_sum: 36000 }, + ], + fields: [ + { name: 'stage', type: 'string' }, + { name: 'expected_revenue_sum', type: 'number' }, + ], + sql: '-- MongoDB Aggregation Pipeline', + }, + }); + + const result = await adapter.aggregate('opportunity', { + field: 'expected_revenue', + function: 'sum', + groupBy: 'stage', + }); + + expect(result).toEqual([ + { stage: 'closed_won', expected_revenue: 225000 }, + { stage: 'negotiation', expected_revenue: 36000 }, + ]); + }); + it('should fall back to client-side aggregation when analytics endpoint fails', async () => { mockAnalyticsQuery.mockRejectedValue(new Error('Analytics not available')); diff --git a/packages/data-objectstack/src/index.ts b/packages/data-objectstack/src/index.ts index 71794de9e..e3a83136f 100644 --- a/packages/data-objectstack/src/index.ts +++ b/packages/data-objectstack/src/index.ts @@ -849,7 +849,9 @@ export class ObjectStackAdapter implements DataSource { const data = await this.client.analytics.query(payload); const rawRows: any[] = 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 : [];