datajunction-ui 0.0.75 → 0.0.77

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 (29) hide show
  1. package/package.json +1 -1
  2. package/src/app/components/DashboardCard.jsx +93 -0
  3. package/src/app/components/NodeComponents.jsx +173 -0
  4. package/src/app/components/NodeListActions.jsx +8 -3
  5. package/src/app/components/__tests__/NodeComponents.test.jsx +262 -0
  6. package/src/app/hooks/__tests__/useWorkspaceData.test.js +533 -0
  7. package/src/app/hooks/useWorkspaceData.js +357 -0
  8. package/src/app/index.tsx +6 -0
  9. package/src/app/pages/MyWorkspacePage/ActiveBranchesSection.jsx +344 -0
  10. package/src/app/pages/MyWorkspacePage/CollectionsSection.jsx +188 -0
  11. package/src/app/pages/MyWorkspacePage/Loadable.jsx +6 -0
  12. package/src/app/pages/MyWorkspacePage/MaterializationsSection.jsx +190 -0
  13. package/src/app/pages/MyWorkspacePage/MyNodesSection.jsx +342 -0
  14. package/src/app/pages/MyWorkspacePage/MyWorkspacePage.css +632 -0
  15. package/src/app/pages/MyWorkspacePage/NeedsAttentionSection.jsx +185 -0
  16. package/src/app/pages/MyWorkspacePage/NodeList.jsx +46 -0
  17. package/src/app/pages/MyWorkspacePage/NotificationsSection.jsx +133 -0
  18. package/src/app/pages/MyWorkspacePage/TypeGroupGrid.jsx +209 -0
  19. package/src/app/pages/MyWorkspacePage/__tests__/ActiveBranchesSection.test.jsx +295 -0
  20. package/src/app/pages/MyWorkspacePage/__tests__/CollectionsSection.test.jsx +278 -0
  21. package/src/app/pages/MyWorkspacePage/__tests__/MaterializationsSection.test.jsx +238 -0
  22. package/src/app/pages/MyWorkspacePage/__tests__/MyNodesSection.test.jsx +389 -0
  23. package/src/app/pages/MyWorkspacePage/__tests__/MyWorkspacePage.test.jsx +347 -0
  24. package/src/app/pages/MyWorkspacePage/__tests__/NeedsAttentionSection.test.jsx +272 -0
  25. package/src/app/pages/MyWorkspacePage/__tests__/NodeList.test.jsx +162 -0
  26. package/src/app/pages/MyWorkspacePage/__tests__/NotificationsSection.test.jsx +204 -0
  27. package/src/app/pages/MyWorkspacePage/__tests__/TypeGroupGrid.test.jsx +556 -0
  28. package/src/app/pages/MyWorkspacePage/index.jsx +150 -0
  29. package/src/app/services/DJService.js +323 -2
@@ -0,0 +1,162 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ import { NodeList } from '../NodeList';
5
+
6
+ jest.mock('../MyWorkspacePage.css', () => ({}));
7
+ jest.mock('../../../components/NodeListActions', () => {
8
+ return function MockNodeListActions({ nodeName }) {
9
+ return <div data-testid="node-actions">{nodeName}</div>;
10
+ };
11
+ });
12
+
13
+ describe('<NodeList />', () => {
14
+ const mockNodes = [
15
+ {
16
+ name: 'default.test_metric',
17
+ type: 'METRIC',
18
+ current: {
19
+ displayName: 'Test Metric',
20
+ updatedAt: '2024-01-01T12:00:00Z',
21
+ },
22
+ },
23
+ {
24
+ name: 'default.test_dimension',
25
+ type: 'DIMENSION',
26
+ current: {
27
+ displayName: 'Test Dimension',
28
+ updatedAt: '2024-01-02T15:30:00Z',
29
+ },
30
+ },
31
+ ];
32
+
33
+ it('should render list of nodes', () => {
34
+ render(
35
+ <MemoryRouter>
36
+ <NodeList nodes={mockNodes} showUpdatedAt={false} />
37
+ </MemoryRouter>,
38
+ );
39
+
40
+ // Node names appear twice (in NodeDisplay and as full name)
41
+ expect(
42
+ screen.getAllByText('default.test_metric').length,
43
+ ).toBeGreaterThanOrEqual(1);
44
+ expect(
45
+ screen.getAllByText('default.test_dimension').length,
46
+ ).toBeGreaterThanOrEqual(1);
47
+ });
48
+
49
+ it('should render node actions for each node', () => {
50
+ render(
51
+ <MemoryRouter>
52
+ <NodeList nodes={mockNodes} showUpdatedAt={false} />
53
+ </MemoryRouter>,
54
+ );
55
+
56
+ const actions = screen.getAllByTestId('node-actions');
57
+ expect(actions).toHaveLength(2);
58
+ expect(actions[0]).toHaveTextContent('default.test_metric');
59
+ expect(actions[1]).toHaveTextContent('default.test_dimension');
60
+ });
61
+
62
+ it('should show updated timestamp when showUpdatedAt is true', () => {
63
+ render(
64
+ <MemoryRouter>
65
+ <NodeList nodes={mockNodes} showUpdatedAt={true} />
66
+ </MemoryRouter>,
67
+ );
68
+
69
+ // Should show formatted date
70
+ expect(screen.getByText(/Jan 1/)).toBeInTheDocument();
71
+ expect(screen.getByText(/Jan 2/)).toBeInTheDocument();
72
+ });
73
+
74
+ it('should not show updated timestamp when showUpdatedAt is false', () => {
75
+ const { container } = render(
76
+ <MemoryRouter>
77
+ <NodeList nodes={mockNodes} showUpdatedAt={false} />
78
+ </MemoryRouter>,
79
+ );
80
+
81
+ // Should not show formatted date
82
+ expect(
83
+ container.querySelector('.node-list-item-updated'),
84
+ ).not.toBeInTheDocument();
85
+ });
86
+
87
+ it('should handle nodes without updatedAt gracefully', () => {
88
+ const nodesWithoutDate = [
89
+ {
90
+ name: 'default.no_date_metric',
91
+ type: 'METRIC',
92
+ current: {
93
+ displayName: 'No Date Metric',
94
+ },
95
+ },
96
+ ];
97
+
98
+ render(
99
+ <MemoryRouter>
100
+ <NodeList nodes={nodesWithoutDate} showUpdatedAt={true} />
101
+ </MemoryRouter>,
102
+ );
103
+
104
+ // Should render the node name (appears twice - in display name area and as full name)
105
+ const nodeNames = screen.getAllByText('default.no_date_metric');
106
+ expect(nodeNames.length).toBeGreaterThanOrEqual(1);
107
+ // Should not crash when no updatedAt
108
+ });
109
+
110
+ it('should render empty list when no nodes', () => {
111
+ const { container } = render(
112
+ <MemoryRouter>
113
+ <NodeList nodes={[]} showUpdatedAt={false} />
114
+ </MemoryRouter>,
115
+ );
116
+
117
+ // Should render the container but with no items
118
+ expect(container.querySelector('.node-list')).toBeInTheDocument();
119
+ expect(screen.queryByTestId('node-actions')).not.toBeInTheDocument();
120
+ });
121
+
122
+ it('should format datetime correctly', () => {
123
+ const nodeWithDate = [
124
+ {
125
+ name: 'default.dated_metric',
126
+ type: 'METRIC',
127
+ current: {
128
+ displayName: 'Dated Metric',
129
+ updatedAt: '2024-03-15T14:30:00Z',
130
+ },
131
+ },
132
+ ];
133
+
134
+ const { container } = render(
135
+ <MemoryRouter>
136
+ <NodeList nodes={nodeWithDate} showUpdatedAt={true} />
137
+ </MemoryRouter>,
138
+ );
139
+
140
+ // Should show month, day, and time in the updated timestamp
141
+ const updatedSpan = container.querySelector('.node-list-item-updated');
142
+ expect(updatedSpan).toBeInTheDocument();
143
+ expect(updatedSpan.textContent).toMatch(/Mar 15/);
144
+ });
145
+
146
+ it('should apply correct CSS classes', () => {
147
+ const { container } = render(
148
+ <MemoryRouter>
149
+ <NodeList nodes={mockNodes} showUpdatedAt={false} />
150
+ </MemoryRouter>,
151
+ );
152
+
153
+ expect(container.querySelector('.node-list')).toBeInTheDocument();
154
+ expect(container.querySelector('.node-list-item')).toBeInTheDocument();
155
+ expect(
156
+ container.querySelector('.node-list-item-content'),
157
+ ).toBeInTheDocument();
158
+ expect(
159
+ container.querySelector('.node-list-item-actions'),
160
+ ).toBeInTheDocument();
161
+ });
162
+ });
@@ -0,0 +1,204 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ import { NotificationsSection } from '../NotificationsSection';
5
+
6
+ jest.mock('../MyWorkspacePage.css', () => ({}));
7
+
8
+ describe('<NotificationsSection />', () => {
9
+ const mockNotifications = [
10
+ {
11
+ entity_name: 'default.test_metric',
12
+ entity_type: 'node',
13
+ activity_type: 'update',
14
+ created_at: '2024-01-01T00:00:00Z',
15
+ user: 'test.user@example.com',
16
+ node_type: 'metric',
17
+ display_name: 'Test Metric',
18
+ details: { version: 'v1.0' },
19
+ },
20
+ {
21
+ entity_name: 'default.test_metric',
22
+ entity_type: 'node',
23
+ activity_type: 'create',
24
+ created_at: '2024-01-02T00:00:00Z',
25
+ user: 'other.user@example.com',
26
+ node_type: 'metric',
27
+ display_name: 'Test Metric',
28
+ },
29
+ ];
30
+
31
+ it('should render loading state', () => {
32
+ render(
33
+ <MemoryRouter>
34
+ <NotificationsSection
35
+ notifications={[]}
36
+ username="test.user@example.com"
37
+ loading={true}
38
+ />
39
+ </MemoryRouter>,
40
+ );
41
+
42
+ // Should not show content when loading
43
+ expect(screen.queryByText('Test Metric')).not.toBeInTheDocument();
44
+ });
45
+
46
+ it('should render empty state when no notifications', () => {
47
+ render(
48
+ <MemoryRouter>
49
+ <NotificationsSection
50
+ notifications={[]}
51
+ username="test.user@example.com"
52
+ loading={false}
53
+ />
54
+ </MemoryRouter>,
55
+ );
56
+
57
+ expect(screen.getByText('No notifications yet.')).toBeInTheDocument();
58
+ expect(
59
+ screen.getByText('Watch nodes to get notified of changes.'),
60
+ ).toBeInTheDocument();
61
+ });
62
+
63
+ it('should render notifications list', () => {
64
+ render(
65
+ <MemoryRouter>
66
+ <NotificationsSection
67
+ notifications={mockNotifications}
68
+ username="test.user@example.com"
69
+ loading={false}
70
+ />
71
+ </MemoryRouter>,
72
+ );
73
+
74
+ expect(screen.getByText('Test Metric')).toBeInTheDocument();
75
+ });
76
+
77
+ it('should group notifications by node', () => {
78
+ render(
79
+ <MemoryRouter>
80
+ <NotificationsSection
81
+ notifications={mockNotifications}
82
+ username="test.user@example.com"
83
+ loading={false}
84
+ />
85
+ </MemoryRouter>,
86
+ );
87
+
88
+ // Both notifications are for the same node, should show "2 updates"
89
+ // Since current user is one of them, should show "you + 1 other"
90
+ expect(screen.getByText(/you \+ 1 other/)).toBeInTheDocument();
91
+ expect(screen.getByText(/2 updates/)).toBeInTheDocument();
92
+ });
93
+
94
+ it('should show "you" for current user', () => {
95
+ const singleNotification = [
96
+ {
97
+ entity_name: 'default.test_metric',
98
+ entity_type: 'node',
99
+ activity_type: 'update',
100
+ created_at: '2024-01-01T00:00:00Z',
101
+ user: 'test.user@example.com',
102
+ node_type: 'metric',
103
+ display_name: 'Test Metric',
104
+ },
105
+ ];
106
+
107
+ render(
108
+ <MemoryRouter>
109
+ <NotificationsSection
110
+ notifications={singleNotification}
111
+ username="test.user@example.com"
112
+ loading={false}
113
+ />
114
+ </MemoryRouter>,
115
+ );
116
+
117
+ expect(screen.getByText(/by you/)).toBeInTheDocument();
118
+ });
119
+
120
+ it('should link to revision when version is available', () => {
121
+ const notificationWithVersion = [
122
+ {
123
+ entity_name: 'default.test_metric',
124
+ entity_type: 'node',
125
+ activity_type: 'update',
126
+ created_at: '2024-01-01T00:00:00Z',
127
+ user: 'test.user@example.com',
128
+ node_type: 'metric',
129
+ display_name: 'Test Metric',
130
+ details: { version: 'v2.0' },
131
+ },
132
+ ];
133
+
134
+ render(
135
+ <MemoryRouter>
136
+ <NotificationsSection
137
+ notifications={notificationWithVersion}
138
+ username="test.user@example.com"
139
+ loading={false}
140
+ />
141
+ </MemoryRouter>,
142
+ );
143
+
144
+ const link = screen.getByText('Test Metric').closest('a');
145
+ expect(link).toHaveAttribute(
146
+ 'href',
147
+ '/nodes/default.test_metric/revisions/v2.0',
148
+ );
149
+ });
150
+
151
+ it('should link to history when version is not available', () => {
152
+ const notificationWithoutVersion = [
153
+ {
154
+ entity_name: 'default.test_metric',
155
+ entity_type: 'node',
156
+ activity_type: 'update',
157
+ created_at: '2024-01-01T00:00:00Z',
158
+ user: 'test.user@example.com',
159
+ node_type: 'metric',
160
+ display_name: 'Test Metric',
161
+ },
162
+ ];
163
+
164
+ render(
165
+ <MemoryRouter>
166
+ <NotificationsSection
167
+ notifications={notificationWithoutVersion}
168
+ username="test.user@example.com"
169
+ loading={false}
170
+ />
171
+ </MemoryRouter>,
172
+ );
173
+
174
+ const link = screen.getByText('Test Metric').closest('a');
175
+ expect(link).toHaveAttribute('href', '/nodes/default.test_metric/history');
176
+ });
177
+
178
+ it('should limit notifications to 15', () => {
179
+ const manyNotifications = Array.from({ length: 20 }, (_, i) => ({
180
+ entity_name: `default.metric_${i}`,
181
+ entity_type: 'node',
182
+ activity_type: 'update',
183
+ created_at: '2024-01-01T00:00:00Z',
184
+ user: 'test.user@example.com',
185
+ node_type: 'metric',
186
+ display_name: `Metric ${i}`,
187
+ }));
188
+
189
+ render(
190
+ <MemoryRouter>
191
+ <NotificationsSection
192
+ notifications={manyNotifications}
193
+ username="test.user@example.com"
194
+ loading={false}
195
+ />
196
+ </MemoryRouter>,
197
+ );
198
+
199
+ // Should only render 15 notifications
200
+ expect(screen.getByText('Metric 0')).toBeInTheDocument();
201
+ expect(screen.getByText('Metric 14')).toBeInTheDocument();
202
+ expect(screen.queryByText('Metric 15')).not.toBeInTheDocument();
203
+ });
204
+ });