@object-ui/plugin-dashboard 3.3.0 → 3.3.2

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 (48) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +21 -1
  3. package/dist/index.js +876 -797
  4. package/dist/index.umd.cjs +4 -4
  5. package/dist/packages/plugin-dashboard/src/DashboardRenderer.d.ts +5 -0
  6. package/dist/packages/plugin-dashboard/src/DashboardRenderer.d.ts.map +1 -1
  7. package/dist/packages/plugin-dashboard/src/MetricCard.d.ts.map +1 -1
  8. package/dist/packages/plugin-dashboard/src/MetricWidget.d.ts +4 -1
  9. package/dist/packages/plugin-dashboard/src/MetricWidget.d.ts.map +1 -1
  10. package/dist/packages/plugin-dashboard/src/ObjectMetricWidget.d.ts +2 -0
  11. package/dist/packages/plugin-dashboard/src/ObjectMetricWidget.d.ts.map +1 -1
  12. package/dist/packages/plugin-dashboard/src/index.d.ts +1 -1
  13. package/package.json +40 -7
  14. package/.turbo/turbo-build.log +0 -41
  15. package/src/DashboardConfigPanel.stories.tsx +0 -164
  16. package/src/DashboardConfigPanel.tsx +0 -158
  17. package/src/DashboardGridLayout.tsx +0 -367
  18. package/src/DashboardRenderer.stories.tsx +0 -173
  19. package/src/DashboardRenderer.tsx +0 -479
  20. package/src/DashboardWithConfig.tsx +0 -211
  21. package/src/MetricCard.tsx +0 -102
  22. package/src/MetricWidget.tsx +0 -96
  23. package/src/ObjectDataTable.tsx +0 -226
  24. package/src/ObjectMetricWidget.tsx +0 -159
  25. package/src/ObjectPivotTable.tsx +0 -160
  26. package/src/PivotTable.tsx +0 -262
  27. package/src/WidgetConfigPanel.tsx +0 -540
  28. package/src/__tests__/DashboardConfigPanel.test.tsx +0 -206
  29. package/src/__tests__/DashboardGridLayout.test.tsx +0 -199
  30. package/src/__tests__/DashboardRenderer.autoRefresh.test.tsx +0 -124
  31. package/src/__tests__/DashboardRenderer.designMode.test.tsx +0 -386
  32. package/src/__tests__/DashboardRenderer.header.test.tsx +0 -114
  33. package/src/__tests__/DashboardRenderer.mobile.test.tsx +0 -214
  34. package/src/__tests__/DashboardRenderer.widgetData.test.tsx +0 -1411
  35. package/src/__tests__/DashboardWithConfig.test.tsx +0 -276
  36. package/src/__tests__/MetricCard.test.tsx +0 -107
  37. package/src/__tests__/ObjectDataTable.test.tsx +0 -211
  38. package/src/__tests__/ObjectMetricWidget.test.tsx +0 -196
  39. package/src/__tests__/ObjectPivotTable.test.tsx +0 -192
  40. package/src/__tests__/PivotTable.test.tsx +0 -162
  41. package/src/__tests__/WidgetConfigPanel.test.tsx +0 -492
  42. package/src/__tests__/ensureWidgetIds.test.tsx +0 -103
  43. package/src/index.tsx +0 -236
  44. package/src/utils.ts +0 -17
  45. package/tsconfig.json +0 -19
  46. package/vite.config.ts +0 -64
  47. package/vitest.config.ts +0 -9
  48. package/vitest.setup.tsx +0 -18
@@ -1,196 +0,0 @@
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, vi, beforeEach, afterEach } from 'vitest';
10
- import { render, screen, waitFor } from '@testing-library/react';
11
- import React from 'react';
12
- import '@testing-library/jest-dom';
13
- import { ObjectMetricWidget } from '../ObjectMetricWidget';
14
- import { SchemaRendererProvider } from '@object-ui/react';
15
-
16
- // Suppress console.error from expected fetch errors
17
- const originalConsoleError = console.error;
18
- beforeEach(() => {
19
- console.error = vi.fn();
20
- });
21
- afterEach(() => {
22
- console.error = originalConsoleError;
23
- });
24
-
25
- describe('ObjectMetricWidget', () => {
26
- const baseProps = {
27
- objectName: 'opportunity',
28
- label: 'Total Revenue',
29
- fallbackValue: '$652,000',
30
- icon: 'DollarSign',
31
- };
32
-
33
- it('should show fallback value when no dataSource is available', () => {
34
- render(<ObjectMetricWidget {...baseProps} />);
35
-
36
- expect(screen.getByText('Total Revenue')).toBeInTheDocument();
37
- expect(screen.getByText('$652,000')).toBeInTheDocument();
38
- });
39
-
40
- it('should show loading state while fetching', async () => {
41
- const dataSource = {
42
- find: vi.fn(() => new Promise(() => {})), // Never resolves
43
- };
44
-
45
- const { container } = render(
46
- <SchemaRendererProvider dataSource={dataSource}>
47
- <ObjectMetricWidget {...baseProps} />
48
- </SchemaRendererProvider>,
49
- );
50
-
51
- await waitFor(() => {
52
- const loadingEl = container.querySelector('[data-testid="metric-loading"]');
53
- expect(loadingEl).toBeTruthy();
54
- });
55
- });
56
-
57
- it('should show error state when data fetch fails', async () => {
58
- const dataSource = {
59
- find: vi.fn().mockRejectedValue(new Error('Cube name is required')),
60
- };
61
-
62
- const { container } = render(
63
- <SchemaRendererProvider dataSource={dataSource}>
64
- <ObjectMetricWidget {...baseProps} />
65
- </SchemaRendererProvider>,
66
- );
67
-
68
- await waitFor(() => {
69
- const errorEl = container.querySelector('[data-testid="metric-error"]');
70
- expect(errorEl).toBeTruthy();
71
- });
72
-
73
- expect(screen.getByText('Cube name is required')).toBeInTheDocument();
74
- });
75
-
76
- it('should show error state when aggregate fails', async () => {
77
- const dataSource = {
78
- aggregate: vi.fn().mockRejectedValue(new Error('Connection refused')),
79
- find: vi.fn(),
80
- };
81
-
82
- const propsWithAggregate = {
83
- ...baseProps,
84
- aggregate: { field: 'amount', function: 'sum', groupBy: 'stage' },
85
- };
86
-
87
- const { container } = render(
88
- <SchemaRendererProvider dataSource={dataSource}>
89
- <ObjectMetricWidget {...propsWithAggregate} />
90
- </SchemaRendererProvider>,
91
- );
92
-
93
- await waitFor(() => {
94
- const errorEl = container.querySelector('[data-testid="metric-error"]');
95
- expect(errorEl).toBeTruthy();
96
- });
97
-
98
- expect(screen.getByText('Connection refused')).toBeInTheDocument();
99
- });
100
-
101
- it('should display fetched value from find()', async () => {
102
- const dataSource = {
103
- find: vi.fn().mockResolvedValue({
104
- data: [
105
- { name: 'A', amount: 100 },
106
- { name: 'B', amount: 200 },
107
- { name: 'C', amount: 300 },
108
- ],
109
- }),
110
- };
111
-
112
- render(
113
- <SchemaRendererProvider dataSource={dataSource}>
114
- <ObjectMetricWidget {...baseProps} />
115
- </SchemaRendererProvider>,
116
- );
117
-
118
- // When no aggregate config, it falls back to counting records
119
- await waitFor(() => {
120
- expect(screen.getByText('3')).toBeInTheDocument();
121
- });
122
- });
123
-
124
- it('should display aggregated value from aggregate()', async () => {
125
- const dataSource = {
126
- aggregate: vi.fn().mockResolvedValue([
127
- { stage: 'All', amount: 652000 },
128
- ]),
129
- find: vi.fn(),
130
- };
131
-
132
- const propsWithAggregate = {
133
- ...baseProps,
134
- aggregate: { field: 'amount', function: 'sum', groupBy: '_all' },
135
- };
136
-
137
- render(
138
- <SchemaRendererProvider dataSource={dataSource}>
139
- <ObjectMetricWidget {...propsWithAggregate} />
140
- </SchemaRendererProvider>,
141
- );
142
-
143
- await waitFor(() => {
144
- expect(screen.getByText('652000')).toBeInTheDocument();
145
- });
146
-
147
- // Should have called aggregate, not find
148
- expect(dataSource.aggregate).toHaveBeenCalledWith('opportunity', {
149
- field: 'amount',
150
- function: 'sum',
151
- groupBy: '_all',
152
- filter: undefined,
153
- });
154
- });
155
-
156
- it('should render label even in error state', async () => {
157
- const dataSource = {
158
- find: vi.fn().mockRejectedValue(new Error('Server error')),
159
- };
160
-
161
- render(
162
- <SchemaRendererProvider dataSource={dataSource}>
163
- <ObjectMetricWidget {...baseProps} />
164
- </SchemaRendererProvider>,
165
- );
166
-
167
- await waitFor(() => {
168
- expect(screen.getByText('Server error')).toBeInTheDocument();
169
- });
170
-
171
- // Label should still be visible
172
- expect(screen.getByText('Total Revenue')).toBeInTheDocument();
173
- });
174
-
175
- it('should use prop dataSource over context dataSource', async () => {
176
- const contextDs = {
177
- find: vi.fn().mockResolvedValue({ data: [{ a: 1 }] }),
178
- };
179
- const propDs = {
180
- find: vi.fn().mockResolvedValue({ data: [{ a: 1 }, { a: 2 }] }),
181
- };
182
-
183
- render(
184
- <SchemaRendererProvider dataSource={contextDs}>
185
- <ObjectMetricWidget {...baseProps} dataSource={propDs} />
186
- </SchemaRendererProvider>,
187
- );
188
-
189
- await waitFor(() => {
190
- expect(screen.getByText('2')).toBeInTheDocument();
191
- });
192
-
193
- expect(propDs.find).toHaveBeenCalled();
194
- expect(contextDs.find).not.toHaveBeenCalled();
195
- });
196
- });
@@ -1,192 +0,0 @@
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, vi, beforeEach } from 'vitest';
10
- import { render, screen, waitFor } from '@testing-library/react';
11
- import React from 'react';
12
- import { ObjectPivotTable } from '../ObjectPivotTable';
13
- import { SchemaRendererProvider } from '@object-ui/react';
14
-
15
- describe('ObjectPivotTable', () => {
16
- const baseSchema = {
17
- type: 'pivot' as const,
18
- rowField: 'region',
19
- columnField: 'quarter',
20
- valueField: 'revenue',
21
- aggregation: 'sum' as const,
22
- };
23
-
24
- it('should render PivotTable with static data', () => {
25
- const schema = {
26
- ...baseSchema,
27
- data: [
28
- { region: 'North', quarter: 'Q1', revenue: 100 },
29
- { region: 'South', quarter: 'Q1', revenue: 200 },
30
- ],
31
- };
32
-
33
- render(<ObjectPivotTable schema={schema} />);
34
- expect(screen.getByText('North')).toBeDefined();
35
- expect(screen.getByText('South')).toBeDefined();
36
- });
37
-
38
- it('should show loading skeleton when fetching data', async () => {
39
- // A dataSource that resolves slowly
40
- const dataSource = {
41
- find: vi.fn(() => new Promise(() => {})), // Never resolves
42
- };
43
-
44
- const schema = {
45
- ...baseSchema,
46
- objectName: 'sales',
47
- };
48
-
49
- const { container } = render(
50
- <SchemaRendererProvider dataSource={dataSource}>
51
- <ObjectPivotTable schema={schema} />
52
- </SchemaRendererProvider>,
53
- );
54
-
55
- // Should show loading skeleton
56
- await waitFor(() => {
57
- const loadingEl = container.querySelector('[data-testid="pivot-loading"]');
58
- expect(loadingEl).toBeDefined();
59
- });
60
- });
61
-
62
- it('should show error state on fetch failure', async () => {
63
- const dataSource = {
64
- find: vi.fn().mockRejectedValue(new Error('Network error')),
65
- };
66
-
67
- const schema = {
68
- ...baseSchema,
69
- objectName: 'sales',
70
- };
71
-
72
- const { container } = render(
73
- <SchemaRendererProvider dataSource={dataSource}>
74
- <ObjectPivotTable schema={schema} />
75
- </SchemaRendererProvider>,
76
- );
77
-
78
- await waitFor(() => {
79
- const errorEl = container.querySelector('[data-testid="pivot-error"]');
80
- expect(errorEl).not.toBeNull();
81
- });
82
-
83
- expect(screen.getByText('Network error')).toBeDefined();
84
- });
85
-
86
- it('should render fetched data in PivotTable', async () => {
87
- const dataSource = {
88
- find: vi.fn().mockResolvedValue({
89
- records: [
90
- { region: 'East', quarter: 'Q1', revenue: 500 },
91
- { region: 'West', quarter: 'Q2', revenue: 300 },
92
- ],
93
- }),
94
- };
95
-
96
- const schema = {
97
- ...baseSchema,
98
- objectName: 'sales',
99
- };
100
-
101
- render(
102
- <SchemaRendererProvider dataSource={dataSource}>
103
- <ObjectPivotTable schema={schema} />
104
- </SchemaRendererProvider>,
105
- );
106
-
107
- await waitFor(() => {
108
- expect(screen.getByText('East')).toBeDefined();
109
- expect(screen.getByText('West')).toBeDefined();
110
- });
111
-
112
- expect(dataSource.find).toHaveBeenCalledWith('sales', { $filter: undefined });
113
- });
114
-
115
- it('should show empty state when no data returned', async () => {
116
- const dataSource = {
117
- find: vi.fn().mockResolvedValue({ records: [] }),
118
- };
119
-
120
- const schema = {
121
- ...baseSchema,
122
- objectName: 'sales',
123
- };
124
-
125
- const { container } = render(
126
- <SchemaRendererProvider dataSource={dataSource}>
127
- <ObjectPivotTable schema={schema} />
128
- </SchemaRendererProvider>,
129
- );
130
-
131
- await waitFor(() => {
132
- const emptyState = container.querySelector('[data-testid="pivot-empty-state"]');
133
- expect(emptyState).toBeDefined();
134
- });
135
- });
136
-
137
- it('should prefer static data over fetched data', () => {
138
- const dataSource = {
139
- find: vi.fn(),
140
- };
141
-
142
- const schema = {
143
- ...baseSchema,
144
- objectName: 'sales',
145
- data: [
146
- { region: 'Static', quarter: 'Q1', revenue: 999 },
147
- ],
148
- };
149
-
150
- render(
151
- <SchemaRendererProvider dataSource={dataSource}>
152
- <ObjectPivotTable schema={schema} />
153
- </SchemaRendererProvider>,
154
- );
155
-
156
- expect(screen.getByText('Static')).toBeDefined();
157
- expect(dataSource.find).not.toHaveBeenCalled();
158
- });
159
-
160
- it('should show no-data-source message when objectName is set but no dataSource available', () => {
161
- const schema = {
162
- ...baseSchema,
163
- objectName: 'sales',
164
- };
165
-
166
- render(<ObjectPivotTable schema={schema} />);
167
- expect(screen.getByText(/No data source available/)).toBeDefined();
168
- });
169
-
170
- it('should render title in all states', async () => {
171
- const dataSource = {
172
- find: vi.fn().mockRejectedValue(new Error('fail')),
173
- };
174
-
175
- const schema = {
176
- ...baseSchema,
177
- objectName: 'sales',
178
- title: 'Revenue Pivot',
179
- };
180
-
181
- render(
182
- <SchemaRendererProvider dataSource={dataSource}>
183
- <ObjectPivotTable schema={schema} />
184
- </SchemaRendererProvider>,
185
- );
186
-
187
- // Title shows even in error state
188
- await waitFor(() => {
189
- expect(screen.getByText('Revenue Pivot')).toBeDefined();
190
- });
191
- });
192
- });
@@ -1,162 +0,0 @@
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 } from 'vitest';
10
- import { render, screen } from '@testing-library/react';
11
- import '@testing-library/jest-dom';
12
- import { PivotTable } from '../PivotTable';
13
- import type { PivotTableSchema } from '@object-ui/types';
14
-
15
- const SAMPLE_DATA = [
16
- { owner: 'Alice', stage: 'Discovery', amount: 1000 },
17
- { owner: 'Alice', stage: 'Proposal', amount: 2000 },
18
- { owner: 'Alice', stage: 'Discovery', amount: 500 },
19
- { owner: 'Bob', stage: 'Discovery', amount: 3000 },
20
- { owner: 'Bob', stage: 'Closed', amount: 5000 },
21
- { owner: 'Carol', stage: 'Proposal', amount: 4000 },
22
- ];
23
-
24
- function makeSchema(overrides?: Partial<PivotTableSchema>): PivotTableSchema {
25
- return {
26
- type: 'pivot',
27
- rowField: 'owner',
28
- columnField: 'stage',
29
- valueField: 'amount',
30
- data: SAMPLE_DATA,
31
- ...overrides,
32
- };
33
- }
34
-
35
- describe('PivotTable', () => {
36
- it('should render row and column headers', () => {
37
- render(<PivotTable schema={makeSchema()} />);
38
-
39
- // Row headers
40
- expect(screen.getByText('Alice')).toBeInTheDocument();
41
- expect(screen.getByText('Bob')).toBeInTheDocument();
42
- expect(screen.getByText('Carol')).toBeInTheDocument();
43
-
44
- // Column headers
45
- expect(screen.getByText('Discovery')).toBeInTheDocument();
46
- expect(screen.getByText('Proposal')).toBeInTheDocument();
47
- expect(screen.getByText('Closed')).toBeInTheDocument();
48
- });
49
-
50
- it('should aggregate values with sum by default', () => {
51
- render(<PivotTable schema={makeSchema()} />);
52
-
53
- // Alice + Discovery = 1000 + 500 = 1500
54
- expect(screen.getByText('1500')).toBeInTheDocument();
55
- // Alice + Proposal = 2000
56
- expect(screen.getByText('2000')).toBeInTheDocument();
57
- // Bob + Discovery = 3000
58
- expect(screen.getByText('3000')).toBeInTheDocument();
59
- // Bob + Closed = 5000
60
- expect(screen.getByText('5000')).toBeInTheDocument();
61
- // Carol + Proposal = 4000
62
- expect(screen.getByText('4000')).toBeInTheDocument();
63
- });
64
-
65
- it('should support count aggregation', () => {
66
- render(<PivotTable schema={makeSchema({ aggregation: 'count' })} />);
67
-
68
- // Alice + Discovery = 2 items
69
- expect(screen.getByText('2')).toBeInTheDocument();
70
- });
71
-
72
- it('should support avg aggregation', () => {
73
- render(<PivotTable schema={makeSchema({ aggregation: 'avg' })} />);
74
-
75
- // Alice + Discovery = avg(1000, 500) = 750
76
- expect(screen.getByText('750')).toBeInTheDocument();
77
- });
78
-
79
- it('should render title when provided', () => {
80
- render(<PivotTable schema={makeSchema({ title: 'Revenue Pivot' })} />);
81
-
82
- expect(screen.getByText('Revenue Pivot')).toBeInTheDocument();
83
- });
84
-
85
- it('should show row totals when showRowTotals is true', () => {
86
- render(<PivotTable schema={makeSchema({ showRowTotals: true })} />);
87
-
88
- // Header "Total" column
89
- const totalHeaders = screen.getAllByText('Total');
90
- expect(totalHeaders.length).toBeGreaterThanOrEqual(1);
91
-
92
- // Alice total = 1000 + 2000 + 500 = 3500
93
- expect(screen.getByText('3500')).toBeInTheDocument();
94
- // Bob total = 3000 + 5000 = 8000
95
- expect(screen.getByText('8000')).toBeInTheDocument();
96
- });
97
-
98
- it('should show column totals when showColumnTotals is true', () => {
99
- render(<PivotTable schema={makeSchema({ showColumnTotals: true })} />);
100
-
101
- // Footer row with "Total" label
102
- const totalCells = screen.getAllByText('Total');
103
- expect(totalCells.length).toBeGreaterThanOrEqual(1);
104
-
105
- // Discovery total = 1000 + 500 + 3000 = 4500
106
- expect(screen.getByText('4500')).toBeInTheDocument();
107
- // Proposal total = 2000 + 4000 = 6000
108
- expect(screen.getByText('6000')).toBeInTheDocument();
109
- });
110
-
111
- it('should show grand total when both row and column totals are enabled', () => {
112
- render(<PivotTable schema={makeSchema({ showRowTotals: true, showColumnTotals: true })} />);
113
-
114
- // Grand total = 1000 + 2000 + 500 + 3000 + 5000 + 4000 = 15500
115
- expect(screen.getByText('15500')).toBeInTheDocument();
116
- });
117
-
118
- it('should apply format string', () => {
119
- render(<PivotTable schema={makeSchema({ format: '$,.0f' })} />);
120
-
121
- // Alice + Discovery = $1,500
122
- expect(screen.getByText('$1,500')).toBeInTheDocument();
123
- // Bob + Closed = $5,000
124
- expect(screen.getByText('$5,000')).toBeInTheDocument();
125
- });
126
-
127
- it('should handle empty data gracefully', () => {
128
- const { container } = render(<PivotTable schema={makeSchema({ data: [] })} />);
129
-
130
- // Should render a friendly empty state instead of an empty table
131
- const emptyState = container.querySelector('[data-testid="pivot-empty-state"]');
132
- expect(emptyState).toBeInTheDocument();
133
- expect(screen.getByText('No data available')).toBeInTheDocument();
134
- });
135
-
136
- it('should handle missing values in data as 0', () => {
137
- const data = [
138
- { owner: 'Alice', stage: 'A', amount: 10 },
139
- { owner: 'Bob', stage: 'B', amount: 20 },
140
- ];
141
- render(<PivotTable schema={makeSchema({ data })} />);
142
-
143
- // Alice × B = 0, Bob × A = 0 should appear
144
- const zeroCells = screen.getAllByText('0');
145
- expect(zeroCells.length).toBe(2);
146
- });
147
-
148
- it('should show title in empty state', () => {
149
- render(<PivotTable schema={makeSchema({ data: [], title: 'Empty Pivot' })} />);
150
-
151
- expect(screen.getByText('Empty Pivot')).toBeInTheDocument();
152
- expect(screen.getByText('No data available')).toBeInTheDocument();
153
- });
154
-
155
- it('should treat non-array data (e.g. provider config) as empty', () => {
156
- const schema = makeSchema({ data: { provider: 'object', object: 'sales' } as any });
157
- const { container } = render(<PivotTable schema={schema} />);
158
-
159
- const emptyState = container.querySelector('[data-testid="pivot-empty-state"]');
160
- expect(emptyState).toBeInTheDocument();
161
- });
162
- });