@object-ui/plugin-dashboard 3.3.0 → 3.3.1

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/CHANGELOG.md +10 -0
  2. package/README.md +21 -1
  3. package/dist/index.js +869 -787
  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/MetricWidget.d.ts +4 -1
  8. package/dist/packages/plugin-dashboard/src/MetricWidget.d.ts.map +1 -1
  9. package/dist/packages/plugin-dashboard/src/ObjectMetricWidget.d.ts +2 -0
  10. package/dist/packages/plugin-dashboard/src/ObjectMetricWidget.d.ts.map +1 -1
  11. package/dist/packages/plugin-dashboard/src/index.d.ts +1 -1
  12. package/package.json +40 -7
  13. package/.turbo/turbo-build.log +0 -41
  14. package/src/DashboardConfigPanel.stories.tsx +0 -164
  15. package/src/DashboardConfigPanel.tsx +0 -158
  16. package/src/DashboardGridLayout.tsx +0 -367
  17. package/src/DashboardRenderer.stories.tsx +0 -173
  18. package/src/DashboardRenderer.tsx +0 -479
  19. package/src/DashboardWithConfig.tsx +0 -211
  20. package/src/MetricCard.tsx +0 -102
  21. package/src/MetricWidget.tsx +0 -96
  22. package/src/ObjectDataTable.tsx +0 -226
  23. package/src/ObjectMetricWidget.tsx +0 -159
  24. package/src/ObjectPivotTable.tsx +0 -160
  25. package/src/PivotTable.tsx +0 -262
  26. package/src/WidgetConfigPanel.tsx +0 -540
  27. package/src/__tests__/DashboardConfigPanel.test.tsx +0 -206
  28. package/src/__tests__/DashboardGridLayout.test.tsx +0 -199
  29. package/src/__tests__/DashboardRenderer.autoRefresh.test.tsx +0 -124
  30. package/src/__tests__/DashboardRenderer.designMode.test.tsx +0 -386
  31. package/src/__tests__/DashboardRenderer.header.test.tsx +0 -114
  32. package/src/__tests__/DashboardRenderer.mobile.test.tsx +0 -214
  33. package/src/__tests__/DashboardRenderer.widgetData.test.tsx +0 -1411
  34. package/src/__tests__/DashboardWithConfig.test.tsx +0 -276
  35. package/src/__tests__/MetricCard.test.tsx +0 -107
  36. package/src/__tests__/ObjectDataTable.test.tsx +0 -211
  37. package/src/__tests__/ObjectMetricWidget.test.tsx +0 -196
  38. package/src/__tests__/ObjectPivotTable.test.tsx +0 -192
  39. package/src/__tests__/PivotTable.test.tsx +0 -162
  40. package/src/__tests__/WidgetConfigPanel.test.tsx +0 -492
  41. package/src/__tests__/ensureWidgetIds.test.tsx +0 -103
  42. package/src/index.tsx +0 -236
  43. package/src/utils.ts +0 -17
  44. package/tsconfig.json +0 -19
  45. package/vite.config.ts +0 -64
  46. package/vitest.config.ts +0 -9
  47. 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
- });