datajunction-ui 0.0.75 → 0.0.76
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/package.json +1 -1
- package/src/app/components/DashboardCard.jsx +93 -0
- package/src/app/components/NodeComponents.jsx +173 -0
- package/src/app/components/NodeListActions.jsx +8 -3
- package/src/app/components/__tests__/NodeComponents.test.jsx +262 -0
- package/src/app/hooks/__tests__/useWorkspaceData.test.js +533 -0
- package/src/app/hooks/useWorkspaceData.js +357 -0
- package/src/app/index.tsx +6 -0
- package/src/app/pages/MyWorkspacePage/ActiveBranchesSection.jsx +344 -0
- package/src/app/pages/MyWorkspacePage/CollectionsSection.jsx +188 -0
- package/src/app/pages/MyWorkspacePage/Loadable.jsx +6 -0
- package/src/app/pages/MyWorkspacePage/MaterializationsSection.jsx +190 -0
- package/src/app/pages/MyWorkspacePage/MyNodesSection.jsx +342 -0
- package/src/app/pages/MyWorkspacePage/MyWorkspacePage.css +632 -0
- package/src/app/pages/MyWorkspacePage/NeedsAttentionSection.jsx +185 -0
- package/src/app/pages/MyWorkspacePage/NodeList.jsx +46 -0
- package/src/app/pages/MyWorkspacePage/NotificationsSection.jsx +133 -0
- package/src/app/pages/MyWorkspacePage/TypeGroupGrid.jsx +209 -0
- package/src/app/pages/MyWorkspacePage/__tests__/ActiveBranchesSection.test.jsx +295 -0
- package/src/app/pages/MyWorkspacePage/__tests__/CollectionsSection.test.jsx +278 -0
- package/src/app/pages/MyWorkspacePage/__tests__/MaterializationsSection.test.jsx +238 -0
- package/src/app/pages/MyWorkspacePage/__tests__/MyNodesSection.test.jsx +389 -0
- package/src/app/pages/MyWorkspacePage/__tests__/MyWorkspacePage.test.jsx +347 -0
- package/src/app/pages/MyWorkspacePage/__tests__/NeedsAttentionSection.test.jsx +272 -0
- package/src/app/pages/MyWorkspacePage/__tests__/NodeList.test.jsx +162 -0
- package/src/app/pages/MyWorkspacePage/__tests__/NotificationsSection.test.jsx +204 -0
- package/src/app/pages/MyWorkspacePage/__tests__/TypeGroupGrid.test.jsx +556 -0
- package/src/app/pages/MyWorkspacePage/index.jsx +150 -0
- 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
|
+
});
|