datajunction-ui 0.0.1-rc.9 → 0.0.2-3.dev1

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 (238) hide show
  1. package/.env +2 -0
  2. package/.prettierignore +3 -1
  3. package/Makefile +9 -0
  4. package/dj-logo.svg +10 -0
  5. package/package.json +53 -14
  6. package/public/favicon.ico +0 -0
  7. package/public/index.html +1 -1
  8. package/src/__tests__/reportWebVitals.test.jsx +44 -0
  9. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +5 -109
  10. package/src/app/components/AddNodeDropdown.jsx +44 -0
  11. package/src/app/components/ListGroupItem.jsx +9 -1
  12. package/src/app/components/NamespaceHeader.jsx +4 -13
  13. package/src/app/components/NodeListActions.jsx +69 -0
  14. package/src/app/components/NodeMaterializationDelete.jsx +90 -0
  15. package/src/app/components/NotificationBell.tsx +229 -0
  16. package/src/app/components/QueryInfo.jsx +172 -0
  17. package/src/app/components/Search.jsx +94 -0
  18. package/src/app/components/Tab.jsx +8 -1
  19. package/src/app/components/ToggleSwitch.jsx +20 -0
  20. package/src/app/components/UserMenu.tsx +92 -0
  21. package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
  22. package/src/app/components/__tests__/NodeMaterializationDelete.test.jsx +263 -0
  23. package/src/app/components/__tests__/NotificationBell.test.tsx +313 -0
  24. package/src/app/components/__tests__/QueryInfo.test.jsx +183 -0
  25. package/src/app/components/__tests__/Search.test.jsx +307 -0
  26. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  27. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  28. package/src/app/components/__tests__/UserMenu.test.tsx +248 -0
  29. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +8 -3
  30. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
  31. package/src/app/components/djgraph/Collapse.jsx +47 -0
  32. package/src/app/components/djgraph/DJNode.jsx +61 -83
  33. package/src/app/components/djgraph/DJNodeColumns.jsx +75 -0
  34. package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
  35. package/src/app/components/djgraph/LayoutFlow.jsx +106 -0
  36. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  37. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  38. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  39. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +84 -40
  40. package/src/app/components/forms/Action.jsx +8 -0
  41. package/src/app/components/forms/NodeNameField.jsx +64 -0
  42. package/src/app/components/search.css +17 -0
  43. package/src/app/constants.js +2 -0
  44. package/src/app/icons/AddItemIcon.jsx +16 -0
  45. package/src/app/icons/AlertIcon.jsx +33 -0
  46. package/src/app/icons/CollapsedIcon.jsx +15 -0
  47. package/src/app/icons/CommitIcon.jsx +45 -0
  48. package/src/app/icons/DJLogo.jsx +36 -0
  49. package/src/app/icons/DeleteIcon.jsx +21 -0
  50. package/src/app/icons/DiffIcon.jsx +63 -0
  51. package/src/app/icons/EditIcon.jsx +18 -0
  52. package/src/app/icons/ExpandedIcon.jsx +15 -0
  53. package/src/app/icons/EyeIcon.jsx +20 -0
  54. package/src/app/icons/FilterIcon.jsx +7 -0
  55. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  56. package/src/app/icons/InvalidIcon.jsx +16 -0
  57. package/src/app/icons/JupyterExportIcon.jsx +25 -0
  58. package/src/app/icons/LoadingIcon.jsx +14 -0
  59. package/src/app/icons/NodeIcon.jsx +49 -0
  60. package/src/app/icons/NotificationIcon.jsx +27 -0
  61. package/src/app/icons/PythonIcon.jsx +14 -0
  62. package/src/app/icons/SettingsIcon.jsx +28 -0
  63. package/src/app/icons/TableIcon.jsx +14 -0
  64. package/src/app/icons/ValidIcon.jsx +16 -0
  65. package/src/app/index.tsx +138 -38
  66. package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -0
  67. package/src/app/pages/AddEditNodePage/ColumnsSelect.jsx +84 -0
  68. package/src/app/pages/AddEditNodePage/CustomMetadataField.jsx +144 -0
  69. package/src/app/pages/AddEditNodePage/DescriptionField.jsx +17 -0
  70. package/src/app/pages/AddEditNodePage/DisplayNameField.jsx +16 -0
  71. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +64 -0
  72. package/src/app/pages/AddEditNodePage/FullNameField.jsx +38 -0
  73. package/src/app/pages/AddEditNodePage/Loadable.jsx +20 -0
  74. package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +75 -0
  75. package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +71 -0
  76. package/src/app/pages/AddEditNodePage/NamespaceField.jsx +40 -0
  77. package/src/app/pages/AddEditNodePage/NodeModeField.jsx +14 -0
  78. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +94 -0
  79. package/src/app/pages/AddEditNodePage/OwnersField.jsx +53 -0
  80. package/src/app/pages/AddEditNodePage/RequiredDimensionsSelect.jsx +54 -0
  81. package/src/app/pages/AddEditNodePage/TagsField.jsx +47 -0
  82. package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +49 -0
  83. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +110 -0
  84. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +291 -0
  85. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
  86. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
  87. package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
  88. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
  89. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
  90. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
  91. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +224 -0
  92. package/src/app/pages/AddEditNodePage/index.jsx +545 -0
  93. package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
  94. package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
  95. package/src/app/pages/AddEditTagPage/index.jsx +132 -0
  96. package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +152 -0
  97. package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
  98. package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +75 -0
  99. package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +373 -0
  100. package/src/app/pages/CubeBuilderPage/index.jsx +291 -0
  101. package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
  102. package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
  103. package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
  104. package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
  105. package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
  106. package/src/app/pages/LoginPage/index.jsx +17 -0
  107. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
  108. package/src/app/pages/NamespacePage/Explorer.jsx +232 -0
  109. package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
  110. package/src/app/pages/NamespacePage/NodeModeSelect.jsx +27 -0
  111. package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +30 -0
  112. package/src/app/pages/NamespacePage/TagSelect.jsx +44 -0
  113. package/src/app/pages/NamespacePage/UserSelect.jsx +47 -0
  114. package/src/app/pages/NamespacePage/__tests__/AddNamespacePopover.test.jsx +283 -0
  115. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +331 -0
  116. package/src/app/pages/NamespacePage/index.jsx +354 -42
  117. package/src/app/pages/NodePage/AddBackfillPopover.jsx +165 -0
  118. package/src/app/pages/NodePage/AddComplexDimensionLinkPopover.jsx +367 -0
  119. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +222 -0
  120. package/src/app/pages/NodePage/AvailabilityStateBlock.jsx +67 -0
  121. package/src/app/pages/NodePage/ClientCodePopover.jsx +116 -0
  122. package/src/app/pages/NodePage/DimensionFilter.jsx +86 -0
  123. package/src/app/pages/NodePage/EditColumnDescriptionPopover.jsx +116 -0
  124. package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
  125. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +164 -0
  126. package/src/app/pages/NodePage/ManageDimensionLinksDialog.jsx +526 -0
  127. package/src/app/pages/NodePage/MaterializationConfigField.jsx +60 -0
  128. package/src/app/pages/NodePage/NodeColumnTab.jsx +421 -30
  129. package/src/app/pages/NodePage/NodeDependenciesTab.jsx +155 -0
  130. package/src/app/pages/NodePage/NodeGraphTab.jsx +119 -148
  131. package/src/app/pages/NodePage/NodeHistory.jsx +236 -0
  132. package/src/app/pages/NodePage/NodeInfoTab.jsx +404 -49
  133. package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
  134. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +585 -0
  135. package/src/app/pages/NodePage/NodeRevisionMaterializationTab.jsx +58 -0
  136. package/src/app/pages/NodePage/NodeStatus.jsx +100 -31
  137. package/src/app/pages/NodePage/NodeValidateTab.jsx +367 -0
  138. package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
  139. package/src/app/pages/NodePage/NotebookDownload.jsx +36 -0
  140. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +151 -0
  141. package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
  142. package/src/app/pages/NodePage/RevisionDiff.jsx +209 -0
  143. package/src/app/pages/NodePage/WatchNodeButton.jsx +226 -0
  144. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +56 -0
  145. package/src/app/pages/NodePage/__tests__/AddComplexDimensionLinkPopover.test.jsx +459 -0
  146. package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +87 -0
  147. package/src/app/pages/NodePage/__tests__/DimensionFilter.test.jsx +74 -0
  148. package/src/app/pages/NodePage/__tests__/EditColumnDescriptionPopover.test.jsx +149 -0
  149. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +144 -0
  150. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +132 -0
  151. package/src/app/pages/NodePage/__tests__/ManageDimensionLinksDialog.test.jsx +390 -0
  152. package/src/app/pages/NodePage/__tests__/NodeColumnTab.test.jsx +166 -0
  153. package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +157 -0
  154. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +595 -0
  155. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +58 -0
  156. package/src/app/pages/NodePage/__tests__/NodeMaterializationTab.test.jsx +190 -0
  157. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +892 -0
  158. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  159. package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
  160. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +19 -0
  161. package/src/app/pages/NodePage/index.jsx +186 -45
  162. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  163. package/src/app/pages/NotificationsPage/Loadable.jsx +6 -0
  164. package/src/app/pages/NotificationsPage/__tests__/index.test.jsx +287 -0
  165. package/src/app/pages/NotificationsPage/index.jsx +136 -0
  166. package/src/app/pages/OverviewPage/ByStatusPanel.jsx +69 -0
  167. package/src/app/pages/OverviewPage/DimensionNodeUsagePanel.jsx +48 -0
  168. package/src/app/pages/OverviewPage/GovernanceWarningsPanel.jsx +107 -0
  169. package/src/app/pages/OverviewPage/Loadable.jsx +16 -0
  170. package/src/app/pages/OverviewPage/NodesByTypePanel.jsx +63 -0
  171. package/src/app/pages/OverviewPage/OverviewPanel.jsx +94 -0
  172. package/src/app/pages/OverviewPage/TrendsPanel.jsx +66 -0
  173. package/src/app/pages/OverviewPage/__tests__/ByStatusPanel.test.jsx +36 -0
  174. package/src/app/pages/OverviewPage/__tests__/DimensionNodeUsagePanel.test.jsx +76 -0
  175. package/src/app/pages/OverviewPage/__tests__/GovernanceWarningsPanel.test.jsx +77 -0
  176. package/src/app/pages/OverviewPage/__tests__/NodesByTypePanel.test.jsx +86 -0
  177. package/src/app/pages/OverviewPage/__tests__/OverviewPanel.test.jsx +78 -0
  178. package/src/app/pages/OverviewPage/__tests__/TrendsPanel.test.jsx +120 -0
  179. package/src/app/pages/OverviewPage/__tests__/index.test.jsx +54 -0
  180. package/src/app/pages/OverviewPage/index.jsx +22 -0
  181. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  182. package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +112 -0
  183. package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +38 -0
  184. package/src/app/pages/RegisterTablePage/index.jsx +142 -0
  185. package/src/app/pages/Root/__tests__/index.test.jsx +44 -0
  186. package/src/app/pages/Root/index.tsx +92 -10
  187. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  188. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  189. package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
  190. package/src/app/pages/SettingsPage/CreateServiceAccountModal.jsx +152 -0
  191. package/src/app/pages/SettingsPage/Loadable.jsx +16 -0
  192. package/src/app/pages/SettingsPage/NotificationSubscriptionsSection.jsx +189 -0
  193. package/src/app/pages/SettingsPage/ProfileSection.jsx +41 -0
  194. package/src/app/pages/SettingsPage/ServiceAccountsSection.jsx +95 -0
  195. package/src/app/pages/SettingsPage/__tests__/CreateServiceAccountModal.test.jsx +318 -0
  196. package/src/app/pages/SettingsPage/__tests__/NotificationSubscriptionsSection.test.jsx +233 -0
  197. package/src/app/pages/SettingsPage/__tests__/ProfileSection.test.jsx +65 -0
  198. package/src/app/pages/SettingsPage/__tests__/ServiceAccountsSection.test.jsx +150 -0
  199. package/src/app/pages/SettingsPage/__tests__/index.test.jsx +187 -0
  200. package/src/app/pages/SettingsPage/index.jsx +148 -0
  201. package/src/app/pages/TagPage/Loadable.jsx +16 -0
  202. package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
  203. package/src/app/pages/TagPage/index.jsx +79 -0
  204. package/src/app/providers/UserProvider.tsx +78 -0
  205. package/src/app/services/DJService.js +1487 -21
  206. package/src/app/services/__tests__/DJService.test.jsx +2194 -0
  207. package/src/app/utils/__tests__/date.test.js +198 -0
  208. package/src/app/utils/date.js +65 -0
  209. package/src/index.tsx +1 -0
  210. package/src/mocks/mockNodes.jsx +1477 -0
  211. package/src/setupTests.ts +31 -1
  212. package/src/styles/dag.css +117 -5
  213. package/src/styles/index.css +1028 -31
  214. package/src/styles/loading.css +34 -0
  215. package/src/styles/login.css +81 -0
  216. package/src/styles/nav-bar.css +274 -0
  217. package/src/styles/node-creation.scss +276 -0
  218. package/src/styles/node-list.css +4 -0
  219. package/src/styles/overview.css +72 -0
  220. package/src/styles/settings.css +787 -0
  221. package/src/styles/sorted-table.css +15 -0
  222. package/src/styles/styles.scss +44 -0
  223. package/src/styles/styles.scss.d.ts +9 -0
  224. package/src/utils/form.jsx +23 -0
  225. package/webpack.config.js +20 -7
  226. package/.babelrc +0 -4
  227. package/.env.local +0 -4
  228. package/.env.production +0 -1
  229. package/.github/pull_request_template.md +0 -11
  230. package/.github/workflows/ci.yml +0 -33
  231. package/.vscode/extensions.json +0 -7
  232. package/.vscode/launch.json +0 -15
  233. package/.vscode/settings.json +0 -25
  234. package/Dockerfile +0 -7
  235. package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
  236. package/src/app/pages/ListNamespacesPage/index.jsx +0 -62
  237. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -45
  238. package/src/app/pages/NamespacePage/__tests__/index.test.tsx +0 -14
package/src/app/index.tsx CHANGED
@@ -8,50 +8,150 @@ 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 { NodePage } from './pages/NodePage/Loadable';
11
+ import { OverviewPage } from './pages/OverviewPage/Loadable';
12
+ import { SettingsPage } from './pages/SettingsPage/Loadable';
13
+ import { NotificationsPage } from './pages/NotificationsPage/Loadable';
14
+ import { NodePage } from './pages/NodePage';
15
+ import RevisionDiff from './pages/NodePage/RevisionDiff';
16
+ import { SQLBuilderPage } from './pages/SQLBuilderPage/Loadable';
17
+ import { CubeBuilderPage } from './pages/CubeBuilderPage/Loadable';
18
+ import { TagPage } from './pages/TagPage/Loadable';
19
+ import { AddEditNodePage } from './pages/AddEditNodePage/Loadable';
20
+ import { AddEditTagPage } from './pages/AddEditTagPage/Loadable';
12
21
  import { NotFoundPage } from './pages/NotFoundPage/Loadable';
13
- import { Root } from './pages/Root/Loadable';
14
- import { ListNamespacesPage } from './pages/ListNamespacesPage';
22
+ import { LoginPage } from './pages/LoginPage';
23
+ import { RegisterTablePage } from './pages/RegisterTablePage';
24
+ import { Root } from './pages/Root';
15
25
  import DJClientContext from './providers/djclient';
16
- import {DataJunctionAPI} from "./services/DJService";
26
+ import { UserProvider } from './providers/UserProvider';
27
+ import { DataJunctionAPI } from './services/DJService';
28
+ import { CookiesProvider, useCookies } from 'react-cookie';
29
+ import * as Constants from './constants';
17
30
 
18
31
  export function App() {
32
+ const [cookies] = useCookies([Constants.LOGGED_IN_FLAG_COOKIE]);
19
33
  return (
20
- <BrowserRouter>
21
- <Helmet
22
- titleTemplate="DataJunction: %s"
23
- defaultTitle="DataJunction: A Metrics Platform"
24
- >
25
- <meta
26
- name="description"
27
- content="DataJunction serves as a semantic layer to help manage metrics"
28
- />
29
- </Helmet>
30
- <DJClientContext.Provider value={{ DataJunctionAPI }}>
31
- <Routes>
32
- <Route
33
- path="/"
34
- element={<Root />}
35
- children={
36
- <>
37
- <Route path="nodes" key="nodes">
38
- <Route path=":name" element={<NodePage />} />
39
- </Route>
40
-
41
- <Route path="/" element={<ListNamespacesPage />} key="index" />
42
- <Route path="namespaces">
34
+ <CookiesProvider>
35
+ <BrowserRouter>
36
+ {cookies.__djlif || process.env.REACT_DISABLE_AUTH === 'true' ? (
37
+ <>
38
+ <Helmet
39
+ titleTemplate="DataJunction: %s"
40
+ defaultTitle="DataJunction: A Metrics Platform"
41
+ >
42
+ <meta
43
+ name="description"
44
+ content="DataJunction serves as a semantic layer to help manage metrics"
45
+ />
46
+ </Helmet>
47
+ <DJClientContext.Provider value={{ DataJunctionAPI }}>
48
+ <UserProvider>
49
+ <Routes>
43
50
  <Route
44
- path=":namespace"
45
- element={<NamespacePage />}
46
- key="namespaces"
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>
73
+
74
+ <Route
75
+ path="/"
76
+ element={<NamespacePage />}
77
+ key="index"
78
+ />
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>
120
+ <Route
121
+ path="sql"
122
+ key="sql"
123
+ element={<SQLBuilderPage />}
124
+ />
125
+ <Route path="tags" key="tags">
126
+ <Route path=":name" element={<TagPage />} />
127
+ </Route>
128
+ <Route
129
+ path="overview"
130
+ key="overview"
131
+ element={<OverviewPage />}
132
+ />
133
+ <Route
134
+ path="settings"
135
+ key="settings"
136
+ element={<SettingsPage />}
137
+ />
138
+ <Route
139
+ path="notifications"
140
+ key="notifications"
141
+ element={<NotificationsPage />}
142
+ />
143
+ </>
144
+ }
47
145
  />
48
- </Route>
49
- </>
50
- }
51
- />
52
- <Route path="*" element={<NotFoundPage />} />
53
- </Routes>
54
- </DJClientContext.Provider>
55
- </BrowserRouter>
146
+ <Route path="*" element={<NotFoundPage />} />
147
+ </Routes>
148
+ </UserProvider>
149
+ </DJClientContext.Provider>
150
+ </>
151
+ ) : (
152
+ <LoginPage />
153
+ )}
154
+ </BrowserRouter>
155
+ </CookiesProvider>
56
156
  );
57
157
  }
@@ -0,0 +1,10 @@
1
+ import AlertIcon from '../../icons/AlertIcon';
2
+
3
+ export const AlertMessage = ({ message }) => {
4
+ return (
5
+ <div className="message alert">
6
+ <AlertIcon />
7
+ {message}
8
+ </div>
9
+ );
10
+ };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Component for selecting node columns based on the current form state
3
+ */
4
+ import { ErrorMessage, useFormikContext } from 'formik';
5
+ import { useContext, useMemo, useState, useEffect } from 'react';
6
+ import DJClientContext from '../../providers/djclient';
7
+ import { FormikSelect } from './FormikSelect';
8
+
9
+ export const ColumnsSelect = ({
10
+ defaultValue,
11
+ fieldName,
12
+ label,
13
+ labelStyle = {},
14
+ isMulti = false,
15
+ isClearable = true,
16
+ }) => {
17
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
18
+
19
+ // Used to pull out current form values for node validation
20
+ const { values } = useFormikContext();
21
+
22
+ // The available columns, determined from validating the node query
23
+ const [availableColumns, setAvailableColumns] = useState([]);
24
+ const selectableOptions = useMemo(() => {
25
+ if (availableColumns && availableColumns.length > 0) {
26
+ return availableColumns;
27
+ }
28
+ }, [availableColumns]);
29
+
30
+ // Fetch columns by validating the latest node query
31
+ const fetchColumns = async () => {
32
+ try {
33
+ const { status, json } = await djClient.validateNode(
34
+ values.type,
35
+ values.name,
36
+ values.display_name,
37
+ values.description,
38
+ values.query,
39
+ );
40
+ if (json?.columns) {
41
+ setAvailableColumns(
42
+ json.columns.map(col => ({ value: col.name, label: col.name })),
43
+ );
44
+ }
45
+ } catch (error) {
46
+ console.error('Error fetching columns:', error);
47
+ }
48
+ };
49
+
50
+ useEffect(() => {
51
+ fetchColumns();
52
+ }, [values.type, values.name, values.query]);
53
+
54
+ return (
55
+ <div className="CubeCreationInput">
56
+ <ErrorMessage name={fieldName} component="span" />
57
+ <label htmlFor={fieldName} style={labelStyle}>
58
+ {label}
59
+ </label>
60
+ <span data-testid={`select-${fieldName}`}>
61
+ <FormikSelect
62
+ className={isMulti ? 'MultiSelectInput' : 'SelectInput'}
63
+ defaultValue={
64
+ isMulti
65
+ ? defaultValue.map(val => {
66
+ return {
67
+ value: val,
68
+ label: val,
69
+ };
70
+ })
71
+ : defaultValue
72
+ ? { value: defaultValue, label: defaultValue }
73
+ : null
74
+ }
75
+ selectOptions={selectableOptions}
76
+ formikFieldName={fieldName}
77
+ onFocus={event => fetchColumns(event)}
78
+ isMulti={isMulti}
79
+ isClearable={isClearable}
80
+ />
81
+ </span>
82
+ </div>
83
+ );
84
+ };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Custom metadata field component for nodes
3
+ */
4
+ import { ErrorMessage, Field, useFormikContext } from 'formik';
5
+ import CodeMirror from '@uiw/react-codemirror';
6
+ import { langs } from '@uiw/codemirror-extensions-langs';
7
+ import { useState, useEffect } from 'react';
8
+
9
+ export const CustomMetadataField = ({ value }) => {
10
+ const formik = useFormikContext();
11
+ const jsonExt = langs.json();
12
+ const [hasError, setHasError] = useState(false);
13
+
14
+ useEffect(() => {
15
+ if (!value || value === '') {
16
+ setHasError(false);
17
+ return;
18
+ }
19
+
20
+ const stringValue =
21
+ typeof value === 'string' ? value : JSON.stringify(value, null, 2);
22
+
23
+ try {
24
+ JSON.parse(stringValue);
25
+ setHasError(false);
26
+ } catch (err) {
27
+ setHasError(true);
28
+ }
29
+ }, [value]);
30
+
31
+ const formatValue = value => {
32
+ if (value === null || value === undefined) {
33
+ return '';
34
+ }
35
+ if (typeof value === 'string') {
36
+ return value;
37
+ }
38
+ return JSON.stringify(value, null, 2);
39
+ };
40
+
41
+ const updateFormik = val => {
42
+ formik.setFieldValue('custom_metadata', val);
43
+ formik.setFieldTouched('custom_metadata', true);
44
+
45
+ if (!val || val.trim() === '') {
46
+ setHasError(false);
47
+ } else {
48
+ try {
49
+ JSON.parse(val);
50
+ setHasError(false);
51
+ } catch (err) {
52
+ setHasError(true);
53
+ }
54
+ }
55
+ };
56
+
57
+ return (
58
+ <div className="QueryInput NodeCreationInput">
59
+ <details>
60
+ <summary style={{ cursor: 'pointer' }}>
61
+ <label
62
+ style={{
63
+ paddingLeft: '3px',
64
+ display: 'inline-block',
65
+ pointerEvents: 'none',
66
+ }}
67
+ >
68
+ Custom Metadata (JSON)
69
+ </label>
70
+ </summary>
71
+ <ErrorMessage name="custom_metadata" component="span" />
72
+ <Field
73
+ type="textarea"
74
+ style={{ display: 'none' }}
75
+ as="textarea"
76
+ name="custom_metadata"
77
+ id="CustomMetadata"
78
+ validate={value => {
79
+ if (!value || value.trim() === '') {
80
+ return undefined;
81
+ }
82
+ try {
83
+ const parsed = JSON.parse(value);
84
+
85
+ if (
86
+ typeof parsed === 'object' &&
87
+ parsed !== null &&
88
+ !Array.isArray(parsed)
89
+ ) {
90
+ const keys = Object.keys(parsed);
91
+ const originalKeyMatches = value.match(/"([^"]+)"\s*:/g);
92
+ if (
93
+ originalKeyMatches &&
94
+ originalKeyMatches.length > keys.length
95
+ ) {
96
+ return 'Duplicate keys detected';
97
+ }
98
+ }
99
+
100
+ return undefined;
101
+ } catch (err) {
102
+ return 'Invalid JSON format';
103
+ }
104
+ }}
105
+ />
106
+ <div
107
+ role="button"
108
+ tabIndex={0}
109
+ className={`relative flex ${
110
+ hasError ? 'bg-red-900/20' : 'bg-[#282a36]'
111
+ }`}
112
+ style={{
113
+ border: hasError ? '2px solid #ef4444' : 'none',
114
+ borderRadius: '4px',
115
+ boxShadow: hasError ? '0 0 0 1px rgba(239, 68, 68, 0.3)' : 'none',
116
+ }}
117
+ >
118
+ <CodeMirror
119
+ id={'custom_metadata'}
120
+ name={'custom_metadata'}
121
+ extensions={[jsonExt]}
122
+ value={formatValue(value)}
123
+ placeholder={'{\n "key": "value"\n}'}
124
+ options={{
125
+ theme: 'default',
126
+ lineNumbers: true,
127
+ }}
128
+ width="100%"
129
+ height="200px"
130
+ style={{
131
+ margin: '0 0 23px 0',
132
+ flex: 1,
133
+ fontSize: '150%',
134
+ textAlign: 'left',
135
+ }}
136
+ onChange={(value, viewUpdate) => {
137
+ updateFormik(value);
138
+ }}
139
+ />
140
+ </div>
141
+ </details>
142
+ </div>
143
+ );
144
+ };
@@ -0,0 +1,17 @@
1
+ import { ErrorMessage, Field } from 'formik';
2
+
3
+ export const DescriptionField = () => {
4
+ return (
5
+ <div className="DescriptionInput NodeCreationInput">
6
+ <ErrorMessage name="description" component="span" />
7
+ <label htmlFor="Description">Description</label>
8
+ <Field
9
+ type="textarea"
10
+ as="textarea"
11
+ name="description"
12
+ id="Description"
13
+ placeholder="Describe your node"
14
+ />
15
+ </div>
16
+ );
17
+ };
@@ -0,0 +1,16 @@
1
+ import { ErrorMessage, Field } from 'formik';
2
+
3
+ export const DisplayNameField = () => {
4
+ return (
5
+ <div className="DisplayNameInput NodeCreationInput">
6
+ <ErrorMessage name="display_name" component="span" />
7
+ <label htmlFor="displayName">Display Name *</label>
8
+ <Field
9
+ type="text"
10
+ name="display_name"
11
+ id="displayName"
12
+ placeholder="Human readable display name"
13
+ />
14
+ </div>
15
+ );
16
+ };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * A React Select component for use in Formik forms.
3
+ */
4
+ import { useField } from 'formik';
5
+ import Select from 'react-select';
6
+
7
+ export const FormikSelect = ({
8
+ selectOptions,
9
+ formikFieldName,
10
+ placeholder,
11
+ defaultValue,
12
+ style,
13
+ className = 'SelectInput',
14
+ isMulti = false,
15
+ isClearable = false,
16
+ onFocus = event => {},
17
+ onChange: customOnChange,
18
+ menuPortalTarget,
19
+ styles,
20
+ ...rest
21
+ }) => {
22
+ // eslint-disable-next-line no-unused-vars
23
+ const [field, _, helpers] = useField(formikFieldName);
24
+ const { setValue } = helpers;
25
+
26
+ // handles both multi-select and single-select cases
27
+ const getValue = options => {
28
+ if (options) {
29
+ return isMulti ? options.map(option => option.value) : options.value;
30
+ } else {
31
+ return isMulti ? [] : '';
32
+ }
33
+ };
34
+
35
+ const handleChange = selected => {
36
+ setValue(getValue(selected));
37
+ if (customOnChange) {
38
+ customOnChange(selected);
39
+ }
40
+ };
41
+
42
+ return (
43
+ <Select
44
+ className={className}
45
+ defaultValue={defaultValue}
46
+ options={selectOptions}
47
+ name={field.name}
48
+ placeholder={placeholder}
49
+ onBlur={field.onBlur}
50
+ onChange={handleChange}
51
+ styles={styles || style}
52
+ isMulti={isMulti}
53
+ isClearable={isClearable}
54
+ onFocus={event => onFocus(event)}
55
+ id={field.name}
56
+ menuPortalTarget={menuPortalTarget}
57
+ {...rest}
58
+ />
59
+ );
60
+ };
61
+
62
+ FormikSelect.defaultProps = {
63
+ placeholder: '',
64
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * A field for the full node name, which is generated based on the node's input
3
+ * namespace and display name.
4
+ */
5
+ import { useField, useFormikContext } from 'formik';
6
+ import { useEffect } from 'react';
7
+
8
+ export const FullNameField = props => {
9
+ const { values, setFieldValue } = useFormikContext();
10
+ const [field, meta] = useField(props);
11
+
12
+ useEffect(() => {
13
+ // Set the value of the node's full name based on its namespace and display name
14
+ if (values.namespace || values.display_name) {
15
+ setFieldValue(
16
+ props.name,
17
+ `${values.namespace}.${values.display_name
18
+ .toLowerCase()
19
+ .replace(/ /g, '_')
20
+ .replace(/[^a-zA-Z0-9_]/g, '')}` || '',
21
+ );
22
+ }
23
+ }, [setFieldValue, props.name, values]);
24
+
25
+ return (
26
+ <>
27
+ <input
28
+ {...props}
29
+ {...field}
30
+ className="FullNameField"
31
+ disabled="disabled"
32
+ id="FullName"
33
+ value={values.name || ''}
34
+ />
35
+ {!!meta.touched && !!meta.error && <div>{meta.error}</div>}
36
+ </>
37
+ );
38
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Asynchronously loads the component for the Node page
3
+ */
4
+
5
+ import * as React from 'react';
6
+ import { lazyLoad } from '../../../utils/loadable';
7
+
8
+ export const LazyAddEditNodePage = props => {
9
+ return lazyLoad(
10
+ () => import('./index'),
11
+ module => module.AddEditNodePage,
12
+ {
13
+ fallback: <div></div>,
14
+ },
15
+ )(props);
16
+ };
17
+
18
+ export const AddEditNodePage = props => {
19
+ return <LazyAddEditNodePage {...props} />;
20
+ };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Metric unit select component
3
+ */
4
+ import { ErrorMessage, Field } from 'formik';
5
+ import { useContext, useEffect, useState } from 'react';
6
+ import DJClientContext from '../../providers/djclient';
7
+ import { labelize } from '../../../utils/form';
8
+
9
+ export const MetricMetadataFields = () => {
10
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
11
+
12
+ // Metric metadata
13
+ const [metricUnits, setMetricUnits] = useState([]);
14
+ const [metricDirections, setMetricDirections] = useState([]);
15
+
16
+ // Get metric metadata values
17
+ useEffect(() => {
18
+ const fetchData = async () => {
19
+ const metadata = await djClient.listMetricMetadata();
20
+ setMetricDirections(metadata.directions);
21
+ setMetricUnits(metadata.units);
22
+ };
23
+ fetchData().catch(console.error);
24
+ }, [djClient]);
25
+
26
+ return (
27
+ <div
28
+ style={{
29
+ borderRadius: '8px',
30
+ padding: '10px 10px 20px 10px',
31
+ margin: '32px 0',
32
+ background: '#f9f9f9',
33
+ width: 'max-content',
34
+ display: 'flex',
35
+ }}
36
+ >
37
+ <div style={{ margin: '15px 25px' }}>
38
+ <ErrorMessage name="metric_direction" component="span" />
39
+ <label htmlFor="MetricDirection">Metric Direction</label>
40
+ <Field as="select" name="metric_direction" id="MetricDirection">
41
+ <option value=""></option>
42
+ {metricDirections.map(direction => (
43
+ <option value={direction} key={direction}>
44
+ {labelize(direction)}
45
+ </option>
46
+ ))}
47
+ </Field>
48
+ </div>
49
+ <div style={{ margin: '15px 25px' }}>
50
+ <ErrorMessage name="metric_unit" component="span" />
51
+ <label htmlFor="MetricUnit">Metric Unit</label>
52
+ <Field as="select" name="metric_unit" id="MetricUnit">
53
+ <option value=""></option>
54
+ {metricUnits.map(unit => (
55
+ <option value={unit.name} key={unit.name}>
56
+ {unit.label}
57
+ </option>
58
+ ))}
59
+ </Field>
60
+ </div>
61
+ <div style={{ margin: '15px 25px' }}>
62
+ <ErrorMessage name="significant_digits" component="span" />
63
+ <label htmlFor="SignificantDigits">Significant Digits</label>
64
+ <Field as="select" name="significant_digits" id="SignificantDigits">
65
+ <option value=""></option>
66
+ {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(val => (
67
+ <option value={val} key={val}>
68
+ {val}
69
+ </option>
70
+ ))}
71
+ </Field>
72
+ </div>
73
+ </div>
74
+ );
75
+ };