datajunction-ui 0.0.21 → 0.0.23-rc.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datajunction-ui",
3
- "version": "0.0.21",
3
+ "version": "0.0.23-rc.0",
4
4
  "description": "DataJunction Metrics Platform UI",
5
5
  "module": "src/index.tsx",
6
6
  "repository": {
@@ -180,14 +180,15 @@
180
180
  }
181
181
  },
182
182
  "resolutions": {
183
- "@codemirror/state": "6.2.0",
184
- "@codemirror/view": "6.2.0",
185
- "@lezer/common": "^1.0.0"
183
+ "@lezer/common": "^1.2.0",
184
+ "glob": "^10.4.5"
186
185
  },
187
186
  "devDependencies": {
188
187
  "@babel/plugin-proposal-class-properties": "7.18.6",
189
188
  "@babel/plugin-proposal-private-property-in-object": "7.21.11",
190
189
  "@testing-library/user-event": "14.4.3",
190
+ "@types/glob": "^8.1.0",
191
+ "@types/minimatch": "^5.1.2",
191
192
  "eslint-config-prettier": "8.8.0",
192
193
  "eslint-plugin-prettier": "4.2.1",
193
194
  "eslint-plugin-react-hooks": "4.6.0",
@@ -1,5 +1,6 @@
1
1
  import { useContext, useEffect, useState } from 'react';
2
2
  import DJClientContext from '../providers/djclient';
3
+ import { useCurrentUser } from '../providers/UserProvider';
3
4
  import NotificationIcon from '../icons/NotificationIcon';
4
5
  import SettingsIcon from '../icons/SettingsIcon';
5
6
  import LoadingIcon from '../icons/LoadingIcon';
@@ -68,6 +69,7 @@ export default function NotificationBell({
68
69
  forceClose,
69
70
  }: NotificationBellProps) {
70
71
  const djClient = useContext(DJClientContext).DataJunctionAPI;
72
+ const { currentUser, loading: userLoading } = useCurrentUser();
71
73
  const [showDropdown, setShowDropdown] = useState(false);
72
74
 
73
75
  // Close when forceClose becomes true
@@ -82,14 +84,15 @@ export default function NotificationBell({
82
84
  const [loading, setLoading] = useState(false);
83
85
  const [unreadCount, setUnreadCount] = useState(0);
84
86
 
85
- // Fetch notifications on mount
87
+ // Fetch notifications when user data is available
86
88
  useEffect(() => {
89
+ if (userLoading) return;
90
+
87
91
  async function fetchNotifications() {
88
92
  setLoading(true);
89
93
  try {
90
- const current = await djClient.whoami();
91
94
  const history: HistoryEntry[] =
92
- (await djClient.getSubscribedHistory(10)) || [];
95
+ (await djClient.getSubscribedHistory(5)) || [];
93
96
 
94
97
  // Get unique entity names and fetch their info via GraphQL
95
98
  // (some may not be nodes, but GraphQL will just not return them)
@@ -101,7 +104,10 @@ export default function NotificationBell({
101
104
  const enriched = enrichWithNodeInfo(history, nodes);
102
105
  setNotifications(enriched);
103
106
  setUnreadCount(
104
- calculateUnreadCount(history, current?.last_viewed_notifications_at),
107
+ calculateUnreadCount(
108
+ history,
109
+ currentUser?.last_viewed_notifications_at,
110
+ ),
105
111
  );
106
112
  } catch (error) {
107
113
  console.error('Error fetching notifications:', error);
@@ -110,7 +116,7 @@ export default function NotificationBell({
110
116
  }
111
117
  }
112
118
  fetchNotifications();
113
- }, [djClient]);
119
+ }, [djClient, currentUser, userLoading]);
114
120
 
115
121
  // Close dropdown when clicking outside
116
122
  useEffect(() => {
@@ -1,5 +1,6 @@
1
1
  import { useContext, useEffect, useState } from 'react';
2
2
  import DJClientContext from '../providers/djclient';
3
+ import { useCurrentUser } from '../providers/UserProvider';
3
4
 
4
5
  interface User {
5
6
  id: number;
@@ -32,7 +33,7 @@ export default function UserMenu({
32
33
  forceClose,
33
34
  }: UserMenuProps) {
34
35
  const djClient = useContext(DJClientContext).DataJunctionAPI;
35
- const [currentUser, setCurrentUser] = useState<User | null>(null);
36
+ const { currentUser } = useCurrentUser();
36
37
  const [showDropdown, setShowDropdown] = useState(false);
37
38
 
38
39
  // Close when forceClose becomes true
@@ -42,15 +43,6 @@ export default function UserMenu({
42
43
  }
43
44
  }, [forceClose, showDropdown]);
44
45
 
45
- // Fetch current user on mount
46
- useEffect(() => {
47
- async function fetchUser() {
48
- const user = await djClient.whoami();
49
- setCurrentUser(user);
50
- }
51
- fetchUser();
52
- }, [djClient]);
53
-
54
46
  // Close dropdown when clicking outside
55
47
  useEffect(() => {
56
48
  const handleClickOutside = (event: MouseEvent) => {
@@ -79,7 +71,7 @@ export default function UserMenu({
79
71
  return (
80
72
  <div className="nav-dropdown user-menu-dropdown">
81
73
  <button className="avatar-button" onClick={handleToggle}>
82
- {getInitials(currentUser)}
74
+ {getInitials(currentUser as User | null)}
83
75
  </button>
84
76
  {showDropdown && (
85
77
  <div className="nav-dropdown-menu">
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3
3
  import NotificationBell from '../NotificationBell';
4
4
  import DJClientContext from '../../providers/djclient';
5
+ import { UserProvider } from '../../providers/UserProvider';
5
6
 
6
7
  describe('<NotificationBell />', () => {
7
8
  const mockNotifications = [
@@ -52,10 +53,12 @@ describe('<NotificationBell />', () => {
52
53
  ...overrides,
53
54
  });
54
55
 
55
- const renderWithContext = (mockDjClient: any) => {
56
+ const renderWithContext = (mockDjClient: any, props = {}) => {
56
57
  return render(
57
58
  <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
58
- <NotificationBell />
59
+ <UserProvider>
60
+ <NotificationBell {...props} />
61
+ </UserProvider>
59
62
  </DJClientContext.Provider>,
60
63
  );
61
64
  };
@@ -218,7 +221,9 @@ describe('<NotificationBell />', () => {
218
221
  <DJClientContext.Provider
219
222
  value={{ DataJunctionAPI: mockDjClient as any }}
220
223
  >
221
- <NotificationBell onDropdownToggle={onDropdownToggle} />
224
+ <UserProvider>
225
+ <NotificationBell onDropdownToggle={onDropdownToggle} />
226
+ </UserProvider>
222
227
  </DJClientContext.Provider>,
223
228
  );
224
229
 
@@ -239,7 +244,9 @@ describe('<NotificationBell />', () => {
239
244
  <DJClientContext.Provider
240
245
  value={{ DataJunctionAPI: mockDjClient as any }}
241
246
  >
242
- <NotificationBell forceClose={false} />
247
+ <UserProvider>
248
+ <NotificationBell forceClose={false} />
249
+ </UserProvider>
243
250
  </DJClientContext.Provider>,
244
251
  );
245
252
 
@@ -259,7 +266,9 @@ describe('<NotificationBell />', () => {
259
266
  <DJClientContext.Provider
260
267
  value={{ DataJunctionAPI: mockDjClient as any }}
261
268
  >
262
- <NotificationBell forceClose={true} />
269
+ <UserProvider>
270
+ <NotificationBell forceClose={true} />
271
+ </UserProvider>
263
272
  </DJClientContext.Provider>,
264
273
  );
265
274
 
@@ -275,7 +284,9 @@ describe('<NotificationBell />', () => {
275
284
  <DJClientContext.Provider
276
285
  value={{ DataJunctionAPI: mockDjClient as any }}
277
286
  >
278
- <NotificationBell onDropdownToggle={onDropdownToggle} />
287
+ <UserProvider>
288
+ <NotificationBell onDropdownToggle={onDropdownToggle} />
289
+ </UserProvider>
279
290
  </DJClientContext.Provider>,
280
291
  );
281
292
 
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3
3
  import UserMenu from '../UserMenu';
4
4
  import DJClientContext from '../../providers/djclient';
5
+ import { UserProvider } from '../../providers/UserProvider';
5
6
 
6
7
  describe('<UserMenu />', () => {
7
8
  const createMockDjClient = (overrides = {}) => ({
@@ -17,7 +18,9 @@ describe('<UserMenu />', () => {
17
18
  const renderWithContext = (mockDjClient: any, props = {}) => {
18
19
  return render(
19
20
  <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
20
- <UserMenu {...props} />
21
+ <UserProvider>
22
+ <UserMenu {...props} />
23
+ </UserProvider>
21
24
  </DJClientContext.Provider>,
22
25
  );
23
26
  };
@@ -164,7 +167,9 @@ describe('<UserMenu />', () => {
164
167
  <DJClientContext.Provider
165
168
  value={{ DataJunctionAPI: mockDjClient as any }}
166
169
  >
167
- <UserMenu forceClose={false} />
170
+ <UserProvider>
171
+ <UserMenu forceClose={false} />
172
+ </UserProvider>
168
173
  </DJClientContext.Provider>,
169
174
  );
170
175
 
@@ -184,7 +189,9 @@ describe('<UserMenu />', () => {
184
189
  <DJClientContext.Provider
185
190
  value={{ DataJunctionAPI: mockDjClient as any }}
186
191
  >
187
- <UserMenu forceClose={true} />
192
+ <UserProvider>
193
+ <UserMenu forceClose={true} />
194
+ </UserProvider>
188
195
  </DJClientContext.Provider>,
189
196
  );
190
197
 
package/src/app/index.tsx CHANGED
@@ -11,7 +11,7 @@ import { NamespacePage } from './pages/NamespacePage/Loadable';
11
11
  import { OverviewPage } from './pages/OverviewPage/Loadable';
12
12
  import { SettingsPage } from './pages/SettingsPage/Loadable';
13
13
  import { NotificationsPage } from './pages/NotificationsPage/Loadable';
14
- import { NodePage } from './pages/NodePage/Loadable';
14
+ import { NodePage } from './pages/NodePage';
15
15
  import RevisionDiff from './pages/NodePage/RevisionDiff';
16
16
  import { SQLBuilderPage } from './pages/SQLBuilderPage/Loadable';
17
17
  import { CubeBuilderPage } from './pages/CubeBuilderPage/Loadable';
@@ -21,8 +21,9 @@ import { AddEditTagPage } from './pages/AddEditTagPage/Loadable';
21
21
  import { NotFoundPage } from './pages/NotFoundPage/Loadable';
22
22
  import { LoginPage } from './pages/LoginPage';
23
23
  import { RegisterTablePage } from './pages/RegisterTablePage';
24
- import { Root } from './pages/Root/Loadable';
24
+ import { Root } from './pages/Root';
25
25
  import DJClientContext from './providers/djclient';
26
+ import { UserProvider } from './providers/UserProvider';
26
27
  import { DataJunctionAPI } from './services/DJService';
27
28
  import { CookiesProvider, useCookies } from 'react-cookie';
28
29
  import * as Constants from './constants';
@@ -44,101 +45,107 @@ export function App() {
44
45
  />
45
46
  </Helmet>
46
47
  <DJClientContext.Provider value={{ DataJunctionAPI }}>
47
- <Routes>
48
- <Route
49
- path="/"
50
- element={<Root />}
51
- children={
52
- <>
53
- <Route path="nodes" key="nodes">
54
- <Route path=":name" element={<NodePage />} />
55
- <Route
56
- path=":name/edit"
57
- key="edit"
58
- element={<AddEditNodePage />}
59
- />
60
- <Route
61
- path=":name/edit-cube"
62
- key="edit-cube"
63
- element={<CubeBuilderPage />}
64
- />
65
- <Route
66
- path=":name/revisions/:revision"
67
- element={<RevisionDiff />}
68
- />
69
- <Route path=":name/:tab" element={<NodePage />} />
70
- </Route>
48
+ <UserProvider>
49
+ <Routes>
50
+ <Route
51
+ path="/"
52
+ element={<Root />}
53
+ children={
54
+ <>
55
+ <Route path="nodes" key="nodes">
56
+ <Route path=":name" element={<NodePage />} />
57
+ <Route
58
+ path=":name/edit"
59
+ key="edit"
60
+ element={<AddEditNodePage />}
61
+ />
62
+ <Route
63
+ path=":name/edit-cube"
64
+ key="edit-cube"
65
+ element={<CubeBuilderPage />}
66
+ />
67
+ <Route
68
+ path=":name/revisions/:revision"
69
+ element={<RevisionDiff />}
70
+ />
71
+ <Route path=":name/:tab" element={<NodePage />} />
72
+ </Route>
71
73
 
72
- <Route path="/" element={<NamespacePage />} key="index" />
73
- <Route path="namespaces">
74
74
  <Route
75
- path=":namespace"
75
+ path="/"
76
76
  element={<NamespacePage />}
77
- key="namespaces"
77
+ key="index"
78
78
  />
79
- </Route>
80
- <Route
81
- path="create/tag"
82
- key="createtag"
83
- element={<AddEditTagPage />}
84
- ></Route>
85
- <Route
86
- path="create/source"
87
- key="register"
88
- element={<RegisterTablePage />}
89
- ></Route>
90
- <Route path="/create/cube">
79
+ <Route path="namespaces">
80
+ <Route
81
+ path=":namespace"
82
+ element={<NamespacePage />}
83
+ key="namespaces"
84
+ />
85
+ </Route>
86
+ <Route
87
+ path="create/tag"
88
+ key="createtag"
89
+ element={<AddEditTagPage />}
90
+ ></Route>
91
+ <Route
92
+ path="create/source"
93
+ key="register"
94
+ element={<RegisterTablePage />}
95
+ ></Route>
96
+ <Route path="/create/cube">
97
+ <Route
98
+ path=":initialNamespace"
99
+ key="create"
100
+ element={<CubeBuilderPage />}
101
+ />
102
+ <Route
103
+ path=""
104
+ key="create"
105
+ element={<CubeBuilderPage />}
106
+ />
107
+ </Route>
108
+ <Route path="create/:nodeType">
109
+ <Route
110
+ path=":initialNamespace"
111
+ key="create"
112
+ element={<AddEditNodePage />}
113
+ />
114
+ <Route
115
+ path=""
116
+ key="create"
117
+ element={<AddEditNodePage />}
118
+ />
119
+ </Route>
91
120
  <Route
92
- path=":initialNamespace"
93
- key="create"
94
- element={<CubeBuilderPage />}
121
+ path="sql"
122
+ key="sql"
123
+ element={<SQLBuilderPage />}
95
124
  />
125
+ <Route path="tags" key="tags">
126
+ <Route path=":name" element={<TagPage />} />
127
+ </Route>
96
128
  <Route
97
- path=""
98
- key="create"
99
- element={<CubeBuilderPage />}
129
+ path="overview"
130
+ key="overview"
131
+ element={<OverviewPage />}
100
132
  />
101
- </Route>
102
- <Route path="create/:nodeType">
103
133
  <Route
104
- path=":initialNamespace"
105
- key="create"
106
- element={<AddEditNodePage />}
134
+ path="settings"
135
+ key="settings"
136
+ element={<SettingsPage />}
107
137
  />
108
138
  <Route
109
- path=""
110
- key="create"
111
- element={<AddEditNodePage />}
139
+ path="notifications"
140
+ key="notifications"
141
+ element={<NotificationsPage />}
112
142
  />
113
- </Route>
114
- <Route
115
- path="sql"
116
- key="sql"
117
- element={<SQLBuilderPage />}
118
- />
119
- <Route path="tags" key="tags">
120
- <Route path=":name" element={<TagPage />} />
121
- </Route>
122
- <Route
123
- path="overview"
124
- key="overview"
125
- element={<OverviewPage />}
126
- />
127
- <Route
128
- path="settings"
129
- key="settings"
130
- element={<SettingsPage />}
131
- />
132
- <Route
133
- path="notifications"
134
- key="notifications"
135
- element={<NotificationsPage />}
136
- />
137
- </>
138
- }
139
- />
140
- <Route path="*" element={<NotFoundPage />} />
141
- </Routes>
143
+ </>
144
+ }
145
+ />
146
+ <Route path="*" element={<NotFoundPage />} />
147
+ </Routes>
148
+ </UserProvider>
142
149
  </DJClientContext.Provider>
143
150
  </>
144
151
  ) : (
@@ -4,13 +4,14 @@
4
4
  import { ErrorMessage } from 'formik';
5
5
  import { useContext, useEffect, useState } from 'react';
6
6
  import DJClientContext from '../../providers/djclient';
7
+ import { useCurrentUser } from '../../providers/UserProvider';
7
8
  import { FormikSelect } from './FormikSelect';
8
9
 
9
10
  export const OwnersField = ({ defaultValue }) => {
10
11
  const djClient = useContext(DJClientContext).DataJunctionAPI;
12
+ const { currentUser } = useCurrentUser();
11
13
 
12
14
  const [availableUsers, setAvailableUsers] = useState([]);
13
- const [currentUser, setCurrentUser] = useState(null);
14
15
 
15
16
  useEffect(() => {
16
17
  async function fetchData() {
@@ -23,8 +24,6 @@ export const OwnersField = ({ defaultValue }) => {
23
24
  };
24
25
  }),
25
26
  );
26
- const current = await djClient.whoami();
27
- setCurrentUser(current);
28
27
  }
29
28
  fetchData();
30
29
  }, [djClient]);
@@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom';
3
3
  import { useContext, useEffect, useState } from 'react';
4
4
  import NodeStatus from '../NodePage/NodeStatus';
5
5
  import DJClientContext from '../../providers/djclient';
6
+ import { useCurrentUser } from '../../providers/UserProvider';
6
7
  import Explorer from '../NamespacePage/Explorer';
7
8
  import AddNodeDropdown from '../../components/AddNodeDropdown';
8
9
  import NodeListActions from '../../components/NodeListActions';
@@ -24,6 +25,7 @@ export function NamespacePage() {
24
25
  const fields = ['name', 'displayName', 'type', 'status', 'mode', 'updatedAt'];
25
26
 
26
27
  const djClient = useContext(DJClientContext).DataJunctionAPI;
28
+ const { currentUser } = useCurrentUser();
27
29
  var { namespace } = useParams();
28
30
 
29
31
  const [state, setState] = useState({
@@ -31,7 +33,6 @@ export function NamespacePage() {
31
33
  nodes: [],
32
34
  });
33
35
  const [retrieved, setRetrieved] = useState(false);
34
- const [currentUser, setCurrentUser] = useState(null);
35
36
 
36
37
  const [filters, setFilters] = useState({
37
38
  tags: [],
@@ -105,9 +106,6 @@ export function NamespacePage() {
105
106
  const namespaces = await djClient.namespaces();
106
107
  const hierarchy = createNamespaceHierarchy(namespaces);
107
108
  setNamespaceHierarchy(hierarchy);
108
- const currentUser = await djClient.whoami();
109
- // setFilters({...filters, edited_by: currentUser?.username});
110
- setCurrentUser(currentUser);
111
109
  };
112
110
  fetchData().catch(console.error);
113
111
  }, [djClient, djClient.namespaces]);
@@ -1,11 +1,27 @@
1
+ import DJClientContext from '../../providers/djclient';
1
2
  import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
2
- import { useEffect, useRef, useState } from 'react';
3
+ import { useEffect, useRef, useState, useContext } from 'react';
3
4
  import { nightOwl } from 'react-syntax-highlighter/dist/esm/styles/hljs';
4
5
  import PythonIcon from '../../icons/PythonIcon';
6
+ import LoadingIcon from 'app/icons/LoadingIcon';
5
7
 
6
- export default function ClientCodePopover({ code }) {
8
+ export default function ClientCodePopover({ nodeName }) {
9
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
7
10
  const [showModal, setShowModal] = useState(false);
8
11
  const modalRef = useRef(null);
12
+ const [code, setCode] = useState(null);
13
+
14
+ useEffect(() => {
15
+ async function fetchCode() {
16
+ try {
17
+ const code = await djClient.clientCode(nodeName);
18
+ setCode(code);
19
+ } catch (err) {
20
+ console.log(err);
21
+ }
22
+ }
23
+ fetchCode();
24
+ }, [nodeName, djClient]);
9
25
 
10
26
  useEffect(() => {
11
27
  const handleClickOutside = event => {
@@ -83,9 +99,15 @@ export default function ClientCodePopover({ code }) {
83
99
  ×
84
100
  </button>
85
101
  <h2>Python Client Code</h2>
86
- <SyntaxHighlighter language="python" style={nightOwl}>
87
- {code}
88
- </SyntaxHighlighter>
102
+ {code ? (
103
+ <SyntaxHighlighter language="python" style={nightOwl}>
104
+ {code}
105
+ </SyntaxHighlighter>
106
+ ) : (
107
+ <>
108
+ <LoadingIcon />
109
+ </>
110
+ )}
89
111
  </div>
90
112
  </div>
91
113
  )}