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,357 @@
|
|
|
1
|
+
import { useContext, useEffect, useState } from 'react';
|
|
2
|
+
import DJClientContext from '../providers/djclient';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook to fetch the current user
|
|
6
|
+
*/
|
|
7
|
+
export function useCurrentUser() {
|
|
8
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
9
|
+
const [data, setData] = useState(null);
|
|
10
|
+
const [loading, setLoading] = useState(true);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const fetchUser = async () => {
|
|
15
|
+
try {
|
|
16
|
+
const user = await djClient.whoami();
|
|
17
|
+
setData(user);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
console.error('Error fetching user:', err);
|
|
20
|
+
setError(err);
|
|
21
|
+
}
|
|
22
|
+
setLoading(false);
|
|
23
|
+
};
|
|
24
|
+
fetchUser();
|
|
25
|
+
}, [djClient]);
|
|
26
|
+
|
|
27
|
+
return { data, loading, error };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Hook to fetch workspace owned nodes
|
|
32
|
+
*/
|
|
33
|
+
export function useWorkspaceOwnedNodes(username, limit = 5000) {
|
|
34
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
35
|
+
const [data, setData] = useState([]);
|
|
36
|
+
const [loading, setLoading] = useState(true);
|
|
37
|
+
const [error, setError] = useState(null);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!username) {
|
|
41
|
+
setLoading(false);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const fetchData = async () => {
|
|
46
|
+
try {
|
|
47
|
+
const result = await djClient.getWorkspaceOwnedNodes(username, limit);
|
|
48
|
+
setData(
|
|
49
|
+
result?.data?.findNodesPaginated?.edges?.map(e => e.node) || [],
|
|
50
|
+
);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error('Error fetching owned nodes:', err);
|
|
53
|
+
setError(err);
|
|
54
|
+
}
|
|
55
|
+
setLoading(false);
|
|
56
|
+
};
|
|
57
|
+
fetchData();
|
|
58
|
+
}, [djClient, username, limit]);
|
|
59
|
+
|
|
60
|
+
return { data, loading, error };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Hook to fetch workspace recently edited nodes
|
|
65
|
+
*/
|
|
66
|
+
export function useWorkspaceRecentlyEdited(username, limit = 5000) {
|
|
67
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
68
|
+
const [data, setData] = useState([]);
|
|
69
|
+
const [loading, setLoading] = useState(true);
|
|
70
|
+
const [error, setError] = useState(null);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!username) {
|
|
74
|
+
setLoading(false);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const fetchData = async () => {
|
|
79
|
+
try {
|
|
80
|
+
const result = await djClient.getWorkspaceRecentlyEdited(
|
|
81
|
+
username,
|
|
82
|
+
limit,
|
|
83
|
+
);
|
|
84
|
+
setData(
|
|
85
|
+
result?.data?.findNodesPaginated?.edges?.map(e => e.node) || [],
|
|
86
|
+
);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error('Error fetching recently edited nodes:', err);
|
|
89
|
+
setError(err);
|
|
90
|
+
}
|
|
91
|
+
setLoading(false);
|
|
92
|
+
};
|
|
93
|
+
fetchData();
|
|
94
|
+
}, [djClient, username, limit]);
|
|
95
|
+
|
|
96
|
+
return { data, loading, error };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Hook to fetch watched nodes (notification subscriptions)
|
|
101
|
+
*/
|
|
102
|
+
export function useWorkspaceWatchedNodes(username) {
|
|
103
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
104
|
+
const [data, setData] = useState([]);
|
|
105
|
+
const [loading, setLoading] = useState(true);
|
|
106
|
+
const [error, setError] = useState(null);
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!username) {
|
|
110
|
+
setLoading(false);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const fetchData = async () => {
|
|
115
|
+
try {
|
|
116
|
+
const subscriptions = await djClient.getNotificationPreferences({
|
|
117
|
+
entity_type: 'node',
|
|
118
|
+
});
|
|
119
|
+
const watchedNodeNames = (subscriptions || []).map(s => s.entity_name);
|
|
120
|
+
|
|
121
|
+
if (watchedNodeNames.length > 0) {
|
|
122
|
+
const nodes = await djClient.getNodesByNames(watchedNodeNames);
|
|
123
|
+
setData(nodes || []);
|
|
124
|
+
} else {
|
|
125
|
+
setData([]);
|
|
126
|
+
}
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.error('Error fetching watched nodes:', err);
|
|
129
|
+
setError(err);
|
|
130
|
+
}
|
|
131
|
+
setLoading(false);
|
|
132
|
+
};
|
|
133
|
+
fetchData();
|
|
134
|
+
}, [djClient, username]);
|
|
135
|
+
|
|
136
|
+
return { data, loading, error };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Hook to fetch workspace collections
|
|
141
|
+
*/
|
|
142
|
+
export function useWorkspaceCollections(username) {
|
|
143
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
144
|
+
const [data, setData] = useState([]);
|
|
145
|
+
const [loading, setLoading] = useState(true);
|
|
146
|
+
const [error, setError] = useState(null);
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (!username) {
|
|
150
|
+
setLoading(false);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const fetchData = async () => {
|
|
155
|
+
try {
|
|
156
|
+
const result = await djClient.getWorkspaceCollections(username);
|
|
157
|
+
setData(result?.data?.listCollections || []);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.error('Error fetching collections:', err);
|
|
160
|
+
setError(err);
|
|
161
|
+
}
|
|
162
|
+
setLoading(false);
|
|
163
|
+
};
|
|
164
|
+
fetchData();
|
|
165
|
+
}, [djClient, username]);
|
|
166
|
+
|
|
167
|
+
return { data, loading, error };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Hook to fetch workspace notifications
|
|
172
|
+
*/
|
|
173
|
+
export function useWorkspaceNotifications(username, limit = 50) {
|
|
174
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
175
|
+
const [data, setData] = useState([]);
|
|
176
|
+
const [loading, setLoading] = useState(true);
|
|
177
|
+
const [error, setError] = useState(null);
|
|
178
|
+
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
if (!username) {
|
|
181
|
+
setLoading(false);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const fetchData = async () => {
|
|
186
|
+
try {
|
|
187
|
+
const history = await djClient.getSubscribedHistory(limit);
|
|
188
|
+
|
|
189
|
+
// Enrich with node info
|
|
190
|
+
const nodeNames = Array.from(
|
|
191
|
+
new Set((history || []).map(h => h.entity_name)),
|
|
192
|
+
);
|
|
193
|
+
let nodeInfoMap = {};
|
|
194
|
+
if (nodeNames.length > 0) {
|
|
195
|
+
const nodes = await djClient.getNodesByNames(nodeNames);
|
|
196
|
+
nodeInfoMap = Object.fromEntries((nodes || []).map(n => [n.name, n]));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const enriched = (history || []).map(entry => ({
|
|
200
|
+
...entry,
|
|
201
|
+
node_type: nodeInfoMap[entry.entity_name]?.type,
|
|
202
|
+
display_name: nodeInfoMap[entry.entity_name]?.current?.displayName,
|
|
203
|
+
}));
|
|
204
|
+
|
|
205
|
+
setData(enriched);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
console.error('Error fetching notifications:', err);
|
|
208
|
+
setError(err);
|
|
209
|
+
}
|
|
210
|
+
setLoading(false);
|
|
211
|
+
};
|
|
212
|
+
fetchData();
|
|
213
|
+
}, [djClient, username, limit]);
|
|
214
|
+
|
|
215
|
+
return { data, loading, error };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Hook to fetch workspace materializations
|
|
220
|
+
*/
|
|
221
|
+
export function useWorkspaceMaterializations(username, limit = 5000) {
|
|
222
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
223
|
+
const [data, setData] = useState([]);
|
|
224
|
+
const [loading, setLoading] = useState(true);
|
|
225
|
+
const [error, setError] = useState(null);
|
|
226
|
+
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
if (!username) {
|
|
229
|
+
setLoading(false);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const fetchData = async () => {
|
|
234
|
+
try {
|
|
235
|
+
const result = await djClient.getWorkspaceMaterializations(
|
|
236
|
+
username,
|
|
237
|
+
limit,
|
|
238
|
+
);
|
|
239
|
+
setData(
|
|
240
|
+
result?.data?.findNodesPaginated?.edges?.map(e => e.node) || [],
|
|
241
|
+
);
|
|
242
|
+
} catch (err) {
|
|
243
|
+
console.error('Error fetching materializations:', err);
|
|
244
|
+
setError(err);
|
|
245
|
+
}
|
|
246
|
+
setLoading(false);
|
|
247
|
+
};
|
|
248
|
+
fetchData();
|
|
249
|
+
}, [djClient, username, limit]);
|
|
250
|
+
|
|
251
|
+
return { data, loading, error };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Hook to fetch all "Needs Attention" items
|
|
256
|
+
* Returns an object with all actionable item categories
|
|
257
|
+
*/
|
|
258
|
+
export function useWorkspaceNeedsAttention(username) {
|
|
259
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
260
|
+
const [data, setData] = useState({
|
|
261
|
+
nodesMissingDescription: [],
|
|
262
|
+
invalidNodes: [],
|
|
263
|
+
staleDrafts: [],
|
|
264
|
+
orphanedDimensions: [],
|
|
265
|
+
});
|
|
266
|
+
const [loading, setLoading] = useState(true);
|
|
267
|
+
const [error, setError] = useState(null);
|
|
268
|
+
|
|
269
|
+
useEffect(() => {
|
|
270
|
+
if (!username) {
|
|
271
|
+
setLoading(false);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const fetchData = async () => {
|
|
276
|
+
try {
|
|
277
|
+
const [missingDescData, invalidData, draftData, orphanedData] =
|
|
278
|
+
await Promise.all([
|
|
279
|
+
djClient.getWorkspaceNodesMissingDescription(username, 5000),
|
|
280
|
+
djClient.getWorkspaceInvalidNodes(username, 5000),
|
|
281
|
+
djClient.getWorkspaceDraftNodes(username, 5000),
|
|
282
|
+
djClient.getWorkspaceOrphanedDimensions(username, 5000),
|
|
283
|
+
]);
|
|
284
|
+
|
|
285
|
+
const nodesMissingDescription =
|
|
286
|
+
missingDescData?.data?.findNodesPaginated?.edges?.map(e => e.node) ||
|
|
287
|
+
[];
|
|
288
|
+
const invalidNodes =
|
|
289
|
+
invalidData?.data?.findNodesPaginated?.edges?.map(e => e.node) || [];
|
|
290
|
+
const orphanedDimensions =
|
|
291
|
+
orphanedData?.data?.findNodesPaginated?.edges?.map(e => e.node) || [];
|
|
292
|
+
|
|
293
|
+
// Filter drafts older than 7 days
|
|
294
|
+
const sevenDaysAgo = new Date();
|
|
295
|
+
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
|
296
|
+
const allDrafts =
|
|
297
|
+
draftData?.data?.findNodesPaginated?.edges?.map(e => e.node) || [];
|
|
298
|
+
const staleDrafts = allDrafts.filter(node => {
|
|
299
|
+
const updatedAt = new Date(node.current?.updatedAt);
|
|
300
|
+
return updatedAt < sevenDaysAgo;
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
setData({
|
|
304
|
+
nodesMissingDescription,
|
|
305
|
+
invalidNodes,
|
|
306
|
+
staleDrafts,
|
|
307
|
+
orphanedDimensions,
|
|
308
|
+
});
|
|
309
|
+
} catch (err) {
|
|
310
|
+
console.error('Error fetching needs attention items:', err);
|
|
311
|
+
setError(err);
|
|
312
|
+
}
|
|
313
|
+
setLoading(false);
|
|
314
|
+
};
|
|
315
|
+
fetchData();
|
|
316
|
+
}, [djClient, username]);
|
|
317
|
+
|
|
318
|
+
return { data, loading, error };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Hook to check if personal namespace exists
|
|
323
|
+
*/
|
|
324
|
+
export function usePersonalNamespace(username) {
|
|
325
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
326
|
+
const [exists, setExists] = useState(null);
|
|
327
|
+
const [loading, setLoading] = useState(true);
|
|
328
|
+
const [error, setError] = useState(null);
|
|
329
|
+
|
|
330
|
+
useEffect(() => {
|
|
331
|
+
if (!username) {
|
|
332
|
+
setLoading(false);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const checkNamespace = async () => {
|
|
337
|
+
try {
|
|
338
|
+
// Extract username without domain for namespace
|
|
339
|
+
const usernameForNamespace = username.split('@')[0];
|
|
340
|
+
const personalNamespace = `users.${usernameForNamespace}`;
|
|
341
|
+
const namespaces = await djClient.namespaces();
|
|
342
|
+
const namespaceExists = namespaces.some(
|
|
343
|
+
ns => ns.namespace === personalNamespace,
|
|
344
|
+
);
|
|
345
|
+
setExists(namespaceExists);
|
|
346
|
+
} catch (err) {
|
|
347
|
+
console.error('Error checking namespace:', err);
|
|
348
|
+
setError(err);
|
|
349
|
+
setExists(false);
|
|
350
|
+
}
|
|
351
|
+
setLoading(false);
|
|
352
|
+
};
|
|
353
|
+
checkNamespace();
|
|
354
|
+
}, [djClient, username]);
|
|
355
|
+
|
|
356
|
+
return { exists, loading, error };
|
|
357
|
+
}
|
package/src/app/index.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { Helmet } from 'react-helmet-async';
|
|
|
8
8
|
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
|
9
9
|
|
|
10
10
|
import { NamespacePage } from './pages/NamespacePage/Loadable';
|
|
11
|
+
import { MyWorkspacePage } from './pages/MyWorkspacePage/Loadable';
|
|
11
12
|
import { OverviewPage } from './pages/OverviewPage/Loadable';
|
|
12
13
|
import { SettingsPage } from './pages/SettingsPage/Loadable';
|
|
13
14
|
import { NotificationsPage } from './pages/NotificationsPage/Loadable';
|
|
@@ -136,6 +137,11 @@ export function App() {
|
|
|
136
137
|
key="overview"
|
|
137
138
|
element={<OverviewPage />}
|
|
138
139
|
/>
|
|
140
|
+
<Route
|
|
141
|
+
path="workspace"
|
|
142
|
+
key="workspace"
|
|
143
|
+
element={<MyWorkspacePage />}
|
|
144
|
+
/>
|
|
139
145
|
<Route
|
|
140
146
|
path="settings"
|
|
141
147
|
key="settings"
|