datajunction-ui 0.0.1-rc.9 → 0.0.2-0.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 (244) hide show
  1. package/.env +2 -0
  2. package/.prettierignore +3 -1
  3. package/Makefile +9 -0
  4. package/cleanup-deps.sh +70 -0
  5. package/dj-logo.svg +10 -0
  6. package/package.json +53 -14
  7. package/public/favicon.ico +0 -0
  8. package/public/index.html +1 -1
  9. package/runit.sh +30 -0
  10. package/runit2.sh +30 -0
  11. package/src/__tests__/reportWebVitals.test.jsx +44 -0
  12. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +5 -109
  13. package/src/app/components/AddNodeDropdown.jsx +44 -0
  14. package/src/app/components/ListGroupItem.jsx +9 -1
  15. package/src/app/components/NamespaceHeader.jsx +4 -13
  16. package/src/app/components/NodeListActions.jsx +69 -0
  17. package/src/app/components/NodeMaterializationDelete.jsx +90 -0
  18. package/src/app/components/NotificationBell.tsx +223 -0
  19. package/src/app/components/QueryInfo.jsx +172 -0
  20. package/src/app/components/Search.jsx +94 -0
  21. package/src/app/components/Tab.jsx +8 -1
  22. package/src/app/components/ToggleSwitch.jsx +20 -0
  23. package/src/app/components/UserMenu.tsx +100 -0
  24. package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
  25. package/src/app/components/__tests__/NodeMaterializationDelete.test.jsx +263 -0
  26. package/src/app/components/__tests__/NotificationBell.test.tsx +302 -0
  27. package/src/app/components/__tests__/QueryInfo.test.jsx +183 -0
  28. package/src/app/components/__tests__/Search.test.jsx +307 -0
  29. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  30. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  31. package/src/app/components/__tests__/UserMenu.test.tsx +241 -0
  32. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +8 -3
  33. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
  34. package/src/app/components/djgraph/Collapse.jsx +47 -0
  35. package/src/app/components/djgraph/DJNode.jsx +61 -83
  36. package/src/app/components/djgraph/DJNodeColumns.jsx +75 -0
  37. package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
  38. package/src/app/components/djgraph/LayoutFlow.jsx +106 -0
  39. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  40. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  41. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  42. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +84 -40
  43. package/src/app/components/forms/Action.jsx +8 -0
  44. package/src/app/components/forms/NodeNameField.jsx +64 -0
  45. package/src/app/components/search.css +17 -0
  46. package/src/app/constants.js +2 -0
  47. package/src/app/icons/AddItemIcon.jsx +16 -0
  48. package/src/app/icons/AlertIcon.jsx +33 -0
  49. package/src/app/icons/CollapsedIcon.jsx +15 -0
  50. package/src/app/icons/CommitIcon.jsx +45 -0
  51. package/src/app/icons/DJLogo.jsx +36 -0
  52. package/src/app/icons/DeleteIcon.jsx +21 -0
  53. package/src/app/icons/DiffIcon.jsx +63 -0
  54. package/src/app/icons/EditIcon.jsx +18 -0
  55. package/src/app/icons/ExpandedIcon.jsx +15 -0
  56. package/src/app/icons/EyeIcon.jsx +20 -0
  57. package/src/app/icons/FilterIcon.jsx +7 -0
  58. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  59. package/src/app/icons/InvalidIcon.jsx +16 -0
  60. package/src/app/icons/JupyterExportIcon.jsx +25 -0
  61. package/src/app/icons/LoadingIcon.jsx +14 -0
  62. package/src/app/icons/NodeIcon.jsx +49 -0
  63. package/src/app/icons/NotificationIcon.jsx +27 -0
  64. package/src/app/icons/PythonIcon.jsx +14 -0
  65. package/src/app/icons/SettingsIcon.jsx +28 -0
  66. package/src/app/icons/TableIcon.jsx +14 -0
  67. package/src/app/icons/ValidIcon.jsx +16 -0
  68. package/src/app/icons/WrenchIcon.jsx +36 -0
  69. package/src/app/index.tsx +130 -37
  70. package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -0
  71. package/src/app/pages/AddEditNodePage/ColumnMetadata.jsx +61 -0
  72. package/src/app/pages/AddEditNodePage/ColumnsMetadataInput.jsx +72 -0
  73. package/src/app/pages/AddEditNodePage/ColumnsSelect.jsx +84 -0
  74. package/src/app/pages/AddEditNodePage/CustomMetadataField.jsx +144 -0
  75. package/src/app/pages/AddEditNodePage/DescriptionField.jsx +17 -0
  76. package/src/app/pages/AddEditNodePage/DisplayNameField.jsx +16 -0
  77. package/src/app/pages/AddEditNodePage/ExperimentationExtension.jsx +338 -0
  78. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +64 -0
  79. package/src/app/pages/AddEditNodePage/FullNameField.jsx +38 -0
  80. package/src/app/pages/AddEditNodePage/Loadable.jsx +20 -0
  81. package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +75 -0
  82. package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +71 -0
  83. package/src/app/pages/AddEditNodePage/NamespaceField.jsx +40 -0
  84. package/src/app/pages/AddEditNodePage/NodeModeField.jsx +14 -0
  85. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +94 -0
  86. package/src/app/pages/AddEditNodePage/OwnersField.jsx +54 -0
  87. package/src/app/pages/AddEditNodePage/RequiredDimensionsSelect.jsx +54 -0
  88. package/src/app/pages/AddEditNodePage/TagsField.jsx +47 -0
  89. package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +49 -0
  90. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +110 -0
  91. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +291 -0
  92. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
  93. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
  94. package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
  95. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
  96. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
  97. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
  98. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +224 -0
  99. package/src/app/pages/AddEditNodePage/index.jsx +545 -0
  100. package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
  101. package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
  102. package/src/app/pages/AddEditTagPage/index.jsx +132 -0
  103. package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +152 -0
  104. package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
  105. package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +75 -0
  106. package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +373 -0
  107. package/src/app/pages/CubeBuilderPage/index.jsx +291 -0
  108. package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
  109. package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
  110. package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
  111. package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
  112. package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
  113. package/src/app/pages/LoginPage/index.jsx +17 -0
  114. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
  115. package/src/app/pages/NamespacePage/Explorer.jsx +232 -0
  116. package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
  117. package/src/app/pages/NamespacePage/NodeModeSelect.jsx +27 -0
  118. package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +30 -0
  119. package/src/app/pages/NamespacePage/TagSelect.jsx +44 -0
  120. package/src/app/pages/NamespacePage/UserSelect.jsx +47 -0
  121. package/src/app/pages/NamespacePage/__tests__/AddNamespacePopover.test.jsx +283 -0
  122. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +331 -0
  123. package/src/app/pages/NamespacePage/index.jsx +356 -42
  124. package/src/app/pages/NodePage/AddBackfillPopover.jsx +165 -0
  125. package/src/app/pages/NodePage/AddComplexDimensionLinkPopover.jsx +367 -0
  126. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +222 -0
  127. package/src/app/pages/NodePage/AvailabilityStateBlock.jsx +67 -0
  128. package/src/app/pages/NodePage/ClientCodePopover.jsx +94 -0
  129. package/src/app/pages/NodePage/DimensionFilter.jsx +86 -0
  130. package/src/app/pages/NodePage/EditColumnDescriptionPopover.jsx +116 -0
  131. package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
  132. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +164 -0
  133. package/src/app/pages/NodePage/ManageDimensionLinksDialog.jsx +526 -0
  134. package/src/app/pages/NodePage/MaterializationConfigField.jsx +60 -0
  135. package/src/app/pages/NodePage/NodeColumnTab.jsx +421 -30
  136. package/src/app/pages/NodePage/NodeDependenciesTab.jsx +153 -0
  137. package/src/app/pages/NodePage/NodeGraphTab.jsx +119 -148
  138. package/src/app/pages/NodePage/NodeHistory.jsx +236 -0
  139. package/src/app/pages/NodePage/NodeInfoTab.jsx +346 -49
  140. package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
  141. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +585 -0
  142. package/src/app/pages/NodePage/NodeRevisionMaterializationTab.jsx +58 -0
  143. package/src/app/pages/NodePage/NodeStatus.jsx +100 -31
  144. package/src/app/pages/NodePage/NodeValidateTab.jsx +367 -0
  145. package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
  146. package/src/app/pages/NodePage/NotebookDownload.jsx +36 -0
  147. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +151 -0
  148. package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
  149. package/src/app/pages/NodePage/RevisionDiff.jsx +209 -0
  150. package/src/app/pages/NodePage/WatchNodeButton.jsx +226 -0
  151. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +56 -0
  152. package/src/app/pages/NodePage/__tests__/AddComplexDimensionLinkPopover.test.jsx +459 -0
  153. package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +87 -0
  154. package/src/app/pages/NodePage/__tests__/DimensionFilter.test.jsx +74 -0
  155. package/src/app/pages/NodePage/__tests__/EditColumnDescriptionPopover.test.jsx +149 -0
  156. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +144 -0
  157. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +132 -0
  158. package/src/app/pages/NodePage/__tests__/ManageDimensionLinksDialog.test.jsx +390 -0
  159. package/src/app/pages/NodePage/__tests__/NodeColumnTab.test.jsx +166 -0
  160. package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +151 -0
  161. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +595 -0
  162. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +58 -0
  163. package/src/app/pages/NodePage/__tests__/NodeMaterializationTab.test.jsx +190 -0
  164. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +882 -0
  165. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  166. package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
  167. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +19 -0
  168. package/src/app/pages/NodePage/index.jsx +190 -44
  169. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  170. package/src/app/pages/NotificationsPage/Loadable.jsx +6 -0
  171. package/src/app/pages/NotificationsPage/__tests__/index.test.jsx +287 -0
  172. package/src/app/pages/NotificationsPage/index.jsx +136 -0
  173. package/src/app/pages/OverviewPage/ByStatusPanel.jsx +69 -0
  174. package/src/app/pages/OverviewPage/DimensionNodeUsagePanel.jsx +48 -0
  175. package/src/app/pages/OverviewPage/GovernanceWarningsPanel.jsx +107 -0
  176. package/src/app/pages/OverviewPage/Loadable.jsx +16 -0
  177. package/src/app/pages/OverviewPage/NodesByTypePanel.jsx +63 -0
  178. package/src/app/pages/OverviewPage/OverviewPanel.jsx +94 -0
  179. package/src/app/pages/OverviewPage/TrendsPanel.jsx +66 -0
  180. package/src/app/pages/OverviewPage/__tests__/ByStatusPanel.test.jsx +36 -0
  181. package/src/app/pages/OverviewPage/__tests__/DimensionNodeUsagePanel.test.jsx +76 -0
  182. package/src/app/pages/OverviewPage/__tests__/GovernanceWarningsPanel.test.jsx +77 -0
  183. package/src/app/pages/OverviewPage/__tests__/NodesByTypePanel.test.jsx +86 -0
  184. package/src/app/pages/OverviewPage/__tests__/OverviewPanel.test.jsx +78 -0
  185. package/src/app/pages/OverviewPage/__tests__/TrendsPanel.test.jsx +120 -0
  186. package/src/app/pages/OverviewPage/__tests__/index.test.jsx +54 -0
  187. package/src/app/pages/OverviewPage/index.jsx +22 -0
  188. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  189. package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +112 -0
  190. package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +38 -0
  191. package/src/app/pages/RegisterTablePage/index.jsx +142 -0
  192. package/src/app/pages/Root/__tests__/index.test.jsx +44 -0
  193. package/src/app/pages/Root/index.tsx +92 -10
  194. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  195. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  196. package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
  197. package/src/app/pages/SettingsPage/CreateServiceAccountModal.jsx +152 -0
  198. package/src/app/pages/SettingsPage/Loadable.jsx +16 -0
  199. package/src/app/pages/SettingsPage/NotificationSubscriptionsSection.jsx +189 -0
  200. package/src/app/pages/SettingsPage/ProfileSection.jsx +41 -0
  201. package/src/app/pages/SettingsPage/ServiceAccountsSection.jsx +95 -0
  202. package/src/app/pages/SettingsPage/__tests__/CreateServiceAccountModal.test.jsx +318 -0
  203. package/src/app/pages/SettingsPage/__tests__/NotificationSubscriptionsSection.test.jsx +233 -0
  204. package/src/app/pages/SettingsPage/__tests__/ProfileSection.test.jsx +65 -0
  205. package/src/app/pages/SettingsPage/__tests__/ServiceAccountsSection.test.jsx +150 -0
  206. package/src/app/pages/SettingsPage/__tests__/index.test.jsx +184 -0
  207. package/src/app/pages/SettingsPage/index.jsx +148 -0
  208. package/src/app/pages/TagPage/Loadable.jsx +16 -0
  209. package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
  210. package/src/app/pages/TagPage/index.jsx +79 -0
  211. package/src/app/services/DJService.js +1444 -21
  212. package/src/app/services/__tests__/DJService.test.jsx +2118 -0
  213. package/src/app/utils/__tests__/date.test.js +198 -0
  214. package/src/app/utils/date.js +65 -0
  215. package/src/index.tsx +1 -0
  216. package/src/mocks/mockNodes.jsx +1477 -0
  217. package/src/setupTests.ts +31 -1
  218. package/src/styles/dag.css +117 -5
  219. package/src/styles/index.css +1028 -31
  220. package/src/styles/loading.css +34 -0
  221. package/src/styles/login.css +81 -0
  222. package/src/styles/nav-bar.css +274 -0
  223. package/src/styles/node-creation.scss +276 -0
  224. package/src/styles/node-list.css +4 -0
  225. package/src/styles/overview.css +72 -0
  226. package/src/styles/settings.css +787 -0
  227. package/src/styles/sorted-table.css +15 -0
  228. package/src/styles/styles.scss +44 -0
  229. package/src/styles/styles.scss.d.ts +9 -0
  230. package/src/utils/form.jsx +23 -0
  231. package/webpack.config.js +17 -6
  232. package/.babelrc +0 -4
  233. package/.env.local +0 -4
  234. package/.env.production +0 -1
  235. package/.github/pull_request_template.md +0 -11
  236. package/.github/workflows/ci.yml +0 -33
  237. package/.vscode/extensions.json +0 -7
  238. package/.vscode/launch.json +0 -15
  239. package/.vscode/settings.json +0 -25
  240. package/Dockerfile +0 -7
  241. package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
  242. package/src/app/pages/ListNamespacesPage/index.jsx +0 -62
  243. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -45
  244. package/src/app/pages/NamespacePage/__tests__/index.test.tsx +0 -14
@@ -0,0 +1,69 @@
1
+ import DJClientContext from '../providers/djclient';
2
+ import * as React from 'react';
3
+ import DeleteIcon from '../icons/DeleteIcon';
4
+ import EditIcon from '../icons/EditIcon';
5
+ import { Form, Formik } from 'formik';
6
+ import { useContext } from 'react';
7
+ import { displayMessageAfterSubmit } from '../../utils/form';
8
+
9
+ export default function NodeListActions({ nodeName }) {
10
+ const [editButton, setEditButton] = React.useState(<EditIcon />);
11
+ const [deleteButton, setDeleteButton] = React.useState(<DeleteIcon />);
12
+
13
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
14
+ const deleteNode = async (values, { setStatus }) => {
15
+ if (
16
+ !window.confirm('Deleting node ' + values.nodeName + '. Are you sure?')
17
+ ) {
18
+ return;
19
+ }
20
+ const { status, json } = await djClient.deactivate(values.nodeName);
21
+ if (status === 200 || status === 201 || status === 204) {
22
+ setStatus({
23
+ success: <>Successfully deleted node {values.nodeName}</>,
24
+ });
25
+ setEditButton(''); // hide the Edit button
26
+ setDeleteButton(''); // hide the Delete button
27
+ } else {
28
+ setStatus({
29
+ failure: `${json.message}`,
30
+ });
31
+ }
32
+ };
33
+
34
+ const initialValues = {
35
+ nodeName: nodeName,
36
+ };
37
+
38
+ return (
39
+ <div>
40
+ <a href={`/nodes/${nodeName}/edit`} style={{ marginLeft: '0.5rem' }}>
41
+ {editButton}
42
+ </a>
43
+ <Formik initialValues={initialValues} onSubmit={deleteNode}>
44
+ {function Render({ status, setFieldValue }) {
45
+ return (
46
+ <Form className="deleteNode">
47
+ {displayMessageAfterSubmit(status)}
48
+ {
49
+ <>
50
+ <button
51
+ type="submit"
52
+ style={{
53
+ marginLeft: 0,
54
+ all: 'unset',
55
+ color: '#005c72',
56
+ cursor: 'pointer',
57
+ }}
58
+ >
59
+ {deleteButton}
60
+ </button>
61
+ </>
62
+ }
63
+ </Form>
64
+ );
65
+ }}
66
+ </Formik>
67
+ </div>
68
+ );
69
+ }
@@ -0,0 +1,90 @@
1
+ import DJClientContext from '../providers/djclient';
2
+ import * as React from 'react';
3
+ import DeleteIcon from '../icons/DeleteIcon';
4
+ import { Form, Formik } from 'formik';
5
+ import { useContext } from 'react';
6
+ import { displayMessageAfterSubmit } from '../../utils/form';
7
+
8
+ export default function NodeMaterializationDelete({
9
+ nodeName,
10
+ materializationName,
11
+ nodeVersion = null,
12
+ }) {
13
+ const [deleteButton, setDeleteButton] = React.useState(<DeleteIcon />);
14
+
15
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
16
+ const deleteNode = async (values, { setStatus }) => {
17
+ if (
18
+ !window.confirm(
19
+ 'Deleting materialization job ' +
20
+ values.materializationName +
21
+ ' for node version ' +
22
+ values.nodeVersion +
23
+ '. Are you sure?',
24
+ )
25
+ ) {
26
+ return;
27
+ }
28
+ const { status, json } = await djClient.deleteMaterialization(
29
+ values.nodeName,
30
+ values.materializationName,
31
+ values.nodeVersion,
32
+ );
33
+ if (status === 200 || status === 201 || status === 204) {
34
+ window.location.reload();
35
+ setStatus({
36
+ success: (
37
+ <>
38
+ Successfully deleted materialization job:{' '}
39
+ {values.materializationName}
40
+ </>
41
+ ),
42
+ });
43
+ setDeleteButton(''); // hide the Delete button
44
+ } else {
45
+ setStatus({
46
+ failure: `${json.message}`,
47
+ });
48
+ }
49
+ };
50
+
51
+ const initialValues = {
52
+ nodeName: nodeName,
53
+ materializationName: materializationName,
54
+ nodeVersion: nodeVersion,
55
+ };
56
+
57
+ return (
58
+ <div>
59
+ <Formik
60
+ key={`${nodeName}-${materializationName}-${nodeVersion}`}
61
+ initialValues={initialValues}
62
+ enableReinitialize={true}
63
+ onSubmit={deleteNode}
64
+ >
65
+ {function Render({ status, setFieldValue }) {
66
+ return (
67
+ <Form className="deleteNode">
68
+ {displayMessageAfterSubmit(status)}
69
+ {
70
+ <>
71
+ <button
72
+ type="submit"
73
+ style={{
74
+ marginLeft: 0,
75
+ all: 'unset',
76
+ color: '#005c72',
77
+ cursor: 'pointer',
78
+ }}
79
+ >
80
+ {deleteButton}
81
+ </button>
82
+ </>
83
+ }
84
+ </Form>
85
+ );
86
+ }}
87
+ </Formik>
88
+ </div>
89
+ );
90
+ }
@@ -0,0 +1,223 @@
1
+ import { useContext, useEffect, useState } from 'react';
2
+ import DJClientContext from '../providers/djclient';
3
+ import NotificationIcon from '../icons/NotificationIcon';
4
+ import SettingsIcon from '../icons/SettingsIcon';
5
+ import LoadingIcon from '../icons/LoadingIcon';
6
+ import { formatRelativeTime } from '../utils/date';
7
+
8
+ interface HistoryEntry {
9
+ id: number;
10
+ entity_type: string;
11
+ entity_name: string;
12
+ node: string;
13
+ activity_type: string;
14
+ user: string;
15
+ created_at: string;
16
+ details?: {
17
+ version?: string;
18
+ [key: string]: any;
19
+ };
20
+ }
21
+
22
+ interface EnrichedHistoryEntry extends HistoryEntry {
23
+ node_type?: string;
24
+ display_name?: string;
25
+ }
26
+
27
+ interface NodeInfo {
28
+ name: string;
29
+ type: string;
30
+ current?: {
31
+ displayName?: string;
32
+ };
33
+ }
34
+
35
+ // Calculate unread count based on last_viewed_notifications_at
36
+ const calculateUnreadCount = (
37
+ notifs: HistoryEntry[],
38
+ lastViewed: string | null | undefined,
39
+ ): number => {
40
+ if (!lastViewed) return notifs.length;
41
+ const lastViewedDate = new Date(lastViewed);
42
+ return notifs.filter(n => new Date(n.created_at) > lastViewedDate).length;
43
+ };
44
+
45
+ // Enrich history entries with node info from GraphQL
46
+ const enrichWithNodeInfo = (
47
+ entries: HistoryEntry[],
48
+ nodes: NodeInfo[],
49
+ ): EnrichedHistoryEntry[] => {
50
+ const nodeMap = new Map(nodes.map(n => [n.name, n]));
51
+ return entries.map(entry => {
52
+ const node = nodeMap.get(entry.entity_name);
53
+ return {
54
+ ...entry,
55
+ node_type: node?.type,
56
+ display_name: node?.current?.displayName,
57
+ };
58
+ });
59
+ };
60
+
61
+ interface NotificationBellProps {
62
+ onDropdownToggle?: (isOpen: boolean) => void;
63
+ forceClose?: boolean;
64
+ }
65
+
66
+ export default function NotificationBell({
67
+ onDropdownToggle,
68
+ forceClose,
69
+ }: NotificationBellProps) {
70
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
71
+ const [showDropdown, setShowDropdown] = useState(false);
72
+
73
+ // Close when forceClose becomes true
74
+ useEffect(() => {
75
+ if (forceClose && showDropdown) {
76
+ setShowDropdown(false);
77
+ }
78
+ }, [forceClose, showDropdown]);
79
+ const [notifications, setNotifications] = useState<EnrichedHistoryEntry[]>(
80
+ [],
81
+ );
82
+ const [loading, setLoading] = useState(false);
83
+ const [unreadCount, setUnreadCount] = useState(0);
84
+
85
+ // Fetch notifications on mount
86
+ useEffect(() => {
87
+ async function fetchNotifications() {
88
+ setLoading(true);
89
+ try {
90
+ const current = await djClient.whoami();
91
+ const history: HistoryEntry[] =
92
+ (await djClient.getSubscribedHistory(10)) || [];
93
+
94
+ // Get unique entity names and fetch their info via GraphQL
95
+ // (some may not be nodes, but GraphQL will just not return them)
96
+ const nodeNames = Array.from(new Set(history.map(h => h.entity_name)));
97
+ const nodes: NodeInfo[] = nodeNames.length
98
+ ? await djClient.getNodesByNames(nodeNames)
99
+ : [];
100
+
101
+ const enriched = enrichWithNodeInfo(history, nodes);
102
+ setNotifications(enriched);
103
+ setUnreadCount(
104
+ calculateUnreadCount(history, current?.last_viewed_notifications_at),
105
+ );
106
+ } catch (error) {
107
+ console.error('Error fetching notifications:', error);
108
+ } finally {
109
+ setLoading(false);
110
+ }
111
+ }
112
+ fetchNotifications();
113
+ }, [djClient]);
114
+
115
+ // Close dropdown when clicking outside
116
+ useEffect(() => {
117
+ const handleClickOutside = (event: MouseEvent) => {
118
+ const target = event.target as HTMLElement;
119
+ if (!target.closest('.notification-bell-dropdown')) {
120
+ setShowDropdown(false);
121
+ onDropdownToggle?.(false);
122
+ }
123
+ };
124
+ document.addEventListener('click', handleClickOutside);
125
+ return () => document.removeEventListener('click', handleClickOutside);
126
+ }, [onDropdownToggle]);
127
+
128
+ const handleToggle = (e: React.MouseEvent) => {
129
+ e.stopPropagation();
130
+ const willOpen = !showDropdown;
131
+
132
+ // Mark as read when opening
133
+ if (willOpen && unreadCount > 0) {
134
+ djClient.markNotificationsRead();
135
+ setUnreadCount(0);
136
+ }
137
+
138
+ setShowDropdown(willOpen);
139
+ onDropdownToggle?.(willOpen);
140
+ };
141
+
142
+ return (
143
+ <div className="nav-dropdown notification-bell-dropdown">
144
+ <button className="nav-icon-button" onClick={handleToggle}>
145
+ <NotificationIcon />
146
+ {unreadCount > 0 && (
147
+ <span className="notification-badge">{unreadCount}</span>
148
+ )}
149
+ </button>
150
+ {showDropdown && (
151
+ <div className="nav-dropdown-menu notifications-menu">
152
+ <div className="dropdown-header">
153
+ <span className="header-left">
154
+ <NotificationIcon /> Updates
155
+ </span>
156
+ <a
157
+ href="/settings#notifications"
158
+ className="header-settings"
159
+ title="Manage subscriptions"
160
+ >
161
+ <SettingsIcon />
162
+ </a>
163
+ </div>
164
+ <div className="notifications-list">
165
+ {loading ? (
166
+ <div className="dropdown-item">
167
+ <LoadingIcon centered={false} />
168
+ </div>
169
+ ) : notifications.length === 0 ? (
170
+ <div className="dropdown-item text-muted">
171
+ No updates on watched nodes
172
+ </div>
173
+ ) : (
174
+ notifications.slice(0, 5).map(entry => {
175
+ const version = entry.details?.version;
176
+ const href = version
177
+ ? `/nodes/${entry.entity_name}/revisions/${version}`
178
+ : `/nodes/${entry.entity_name}/history`;
179
+ return (
180
+ <a key={entry.id} className="notification-item" href={href}>
181
+ <span className="notification-node">
182
+ <span className="notification-title">
183
+ {entry.display_name || entry.entity_name}
184
+ {version && (
185
+ <span className="badge version">{version}</span>
186
+ )}
187
+ </span>
188
+ {entry.display_name && (
189
+ <span className="notification-entity">
190
+ {entry.entity_name}
191
+ </span>
192
+ )}
193
+ </span>
194
+ <span className="notification-meta">
195
+ {entry.node_type && (
196
+ <span
197
+ className={`node_type__${entry.node_type} badge node_type`}
198
+ >
199
+ {entry.node_type.toUpperCase()}
200
+ </span>
201
+ )}
202
+ {entry.activity_type}d by{' '}
203
+ <span style={{ color: '#333' }}>{entry.user}</span> ·{' '}
204
+ {formatRelativeTime(entry.created_at)}
205
+ </span>
206
+ </a>
207
+ );
208
+ })
209
+ )}
210
+ </div>
211
+ {notifications.length > 0 && (
212
+ <>
213
+ <hr className="dropdown-divider" />
214
+ <a className="dropdown-item view-all" href="/notifications">
215
+ View all
216
+ </a>
217
+ </>
218
+ )}
219
+ </div>
220
+ )}
221
+ </div>
222
+ );
223
+ }
@@ -0,0 +1,172 @@
1
+ import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
2
+ import { solarizedDark } from 'react-syntax-highlighter/src/styles/hljs';
3
+ import React from 'react';
4
+
5
+ export default function QueryInfo({
6
+ id,
7
+ state,
8
+ engine_name,
9
+ engine_version,
10
+ errors,
11
+ links,
12
+ output_table,
13
+ scheduled,
14
+ started,
15
+ finished,
16
+ numRows,
17
+ isList = false,
18
+ }) {
19
+ return isList === false ? (
20
+ <div className="table-responsive">
21
+ <table className="card-inner-table table">
22
+ <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
23
+ <tr>
24
+ <th>Query ID</th>
25
+ <th>Engine</th>
26
+ <th>State</th>
27
+ <th>Scheduled</th>
28
+ <th>Started</th>
29
+ <th>Errors</th>
30
+ <th>Links</th>
31
+ <th>Output Table</th>
32
+ <th>Number of Rows</th>
33
+ </tr>
34
+ </thead>
35
+ <tbody>
36
+ <tr>
37
+ <td>
38
+ <span className="rounded-pill badge bg-secondary-soft">{id}</span>
39
+ </td>
40
+ <td>
41
+ <span className="rounded-pill badge bg-secondary-soft">
42
+ {engine_name}
43
+ {' - '}
44
+ {engine_version}
45
+ </span>
46
+ </td>
47
+ <td>{state}</td>
48
+ <td>{scheduled}</td>
49
+ <td>{started}</td>
50
+ <td>
51
+ {errors?.length ? (
52
+ errors.map((e, idx) => (
53
+ <p key={`error-${idx}`}>
54
+ <span className="rounded-pill badge bg-secondary-error">
55
+ {e}
56
+ </span>
57
+ </p>
58
+ ))
59
+ ) : (
60
+ <></>
61
+ )}
62
+ </td>
63
+ <td>
64
+ {links?.length ? (
65
+ links.map((link, idx) => (
66
+ <p key={idx}>
67
+ <a href={link} target="_blank" rel="noreferrer">
68
+ {link}
69
+ </a>
70
+ </p>
71
+ ))
72
+ ) : (
73
+ <></>
74
+ )}
75
+ </td>
76
+ <td>{output_table}</td>
77
+ <td>{numRows}</td>
78
+ </tr>
79
+ </tbody>
80
+ </table>
81
+ </div>
82
+ ) : (
83
+ <div className="rightbottom">
84
+ <ul style={{ padding: '20px' }}>
85
+ <li className={'query-info'}>
86
+ <label>Query ID</label>{' '}
87
+ <span className="tag_value rounded-pill badge">
88
+ {links?.length ? (
89
+ <a
90
+ href={links[links.length - 1]}
91
+ target={'_blank'}
92
+ rel="noreferrer"
93
+ >
94
+ {id}
95
+ </a>
96
+ ) : (
97
+ id
98
+ )}
99
+ </span>
100
+ </li>
101
+ <li className={'query-info'}>
102
+ <label>State</label>
103
+ <span className="tag_value rounded-pill badge">{state}</span>
104
+ </li>
105
+ <li className={'query-info'}>
106
+ <label>Engine</label>{' '}
107
+ <span className="tag_value rounded-pill badge">
108
+ {engine_name}
109
+ {' - '}
110
+ {engine_version}
111
+ </span>
112
+ </li>
113
+ <li className={'query-info'}>
114
+ <label>Scheduled</label> {scheduled}
115
+ </li>
116
+ <li className={'query-info'}>
117
+ <label>Started</label> {started}
118
+ </li>
119
+ <li className={'query-info'}>
120
+ <label>Finished</label> {finished}
121
+ </li>
122
+ <li className={'query-info'}>
123
+ <label>Logs</label>{' '}
124
+ {errors?.length ? (
125
+ errors.map(error => (
126
+ <div
127
+ style={{
128
+ height: '800px',
129
+ width: '80%',
130
+ overflow: 'scroll',
131
+ borderRadius: '0',
132
+ border: '1px solid #ccc',
133
+ }}
134
+ className="queryrunner-query"
135
+ >
136
+ <SyntaxHighlighter
137
+ language="javascript"
138
+ style={solarizedDark}
139
+ wrapLines={true}
140
+ >
141
+ {error}
142
+ </SyntaxHighlighter>
143
+ </div>
144
+ ))
145
+ ) : (
146
+ <></>
147
+ )}
148
+ </li>
149
+ <li className={'query-info'}>
150
+ <label>Links:</label>{' '}
151
+ {links?.length ? (
152
+ links.map((link, idx) => (
153
+ <p key={idx}>
154
+ <a href={link} target="_blank" rel="noreferrer">
155
+ {link}
156
+ </a>
157
+ </p>
158
+ ))
159
+ ) : (
160
+ <></>
161
+ )}
162
+ </li>
163
+ <li className={'query-info'}>
164
+ <label>Output Table:</label> {output_table}
165
+ </li>
166
+ <li className={'query-info'}>
167
+ <label>Rows:</label> {numRows}
168
+ </li>
169
+ </ul>
170
+ </div>
171
+ );
172
+ }
@@ -0,0 +1,94 @@
1
+ import { useState, useEffect, useContext } from 'react';
2
+ import DJClientContext from '../providers/djclient';
3
+ import Fuse from 'fuse.js';
4
+
5
+ import './search.css';
6
+
7
+ export default function Search() {
8
+ const [fuse, setFuse] = useState();
9
+ const [searchValue, setSearchValue] = useState('');
10
+ const [searchResults, setSearchResults] = useState([]);
11
+
12
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
13
+
14
+ const truncate = str => {
15
+ if (str === null) {
16
+ return '';
17
+ }
18
+ return str.length > 100 ? str.substring(0, 90) + '...' : str;
19
+ };
20
+
21
+ useEffect(() => {
22
+ const fetchNodes = async () => {
23
+ try {
24
+ const [data, tags] = await Promise.all([
25
+ djClient.nodeDetails(),
26
+ djClient.listTags(),
27
+ ]);
28
+ const allEntities = data.concat(
29
+ (tags || []).map(tag => {
30
+ tag.type = 'tag';
31
+ return tag;
32
+ }),
33
+ );
34
+ const fuse = new Fuse(allEntities || [], {
35
+ keys: [
36
+ 'name', // will be assigned a `weight` of 1
37
+ { name: 'description', weight: 2 },
38
+ { name: 'display_name', weight: 3 },
39
+ { name: 'type', weight: 4 },
40
+ { name: 'tag_type', weight: 5 },
41
+ ],
42
+ });
43
+ setFuse(fuse);
44
+ } catch (error) {
45
+ console.error('Error fetching nodes or tags:', error);
46
+ }
47
+ };
48
+ fetchNodes();
49
+ }, []);
50
+
51
+ const handleChange = e => {
52
+ setSearchValue(e.target.value);
53
+ if (fuse) {
54
+ setSearchResults(fuse.search(e.target.value).map(result => result.item));
55
+ }
56
+ };
57
+
58
+ return (
59
+ <div>
60
+ <form
61
+ className="search-box"
62
+ onSubmit={e => {
63
+ e.preventDefault();
64
+ }}
65
+ >
66
+ <input
67
+ type="text"
68
+ placeholder="Search"
69
+ name="search"
70
+ value={searchValue}
71
+ onChange={handleChange}
72
+ />
73
+ </form>
74
+ <div className="search-results">
75
+ {searchResults.slice(0, 20).map(item => {
76
+ const itemUrl =
77
+ item.type !== 'tag' ? `/nodes/${item.name}` : `/tags/${item.name}`;
78
+ return (
79
+ <a href={itemUrl}>
80
+ <div key={item.name} className="search-result-item">
81
+ <span className={`node_type__${item.type} badge node_type`}>
82
+ {item.type}
83
+ </span>
84
+ {item.display_name} (<b>{item.name}</b>){' '}
85
+ {item.description ? '- ' : ' '}
86
+ {truncate(item.description || '')}
87
+ </div>
88
+ </a>
89
+ );
90
+ })}
91
+ </div>
92
+ </div>
93
+ );
94
+ }
@@ -7,7 +7,14 @@ export default class Tab extends Component {
7
7
  <div className={selectedTab === id ? 'col active' : 'col'}>
8
8
  <div className="header-tabs nav-overflow nav nav-tabs">
9
9
  <div className="nav-item">
10
- <button id={id} className="nav-link" tabIndex="0" onClick={onClick}>
10
+ <button
11
+ id={id}
12
+ className="nav-link"
13
+ tabIndex="0"
14
+ onClick={onClick}
15
+ aria-label={this.props.name}
16
+ aria-hidden="false"
17
+ >
11
18
  {this.props.name}
12
19
  </button>
13
20
  </div>
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+
3
+ const ToggleSwitch = ({ checked, onChange, toggleName }) => (
4
+ <>
5
+ <input
6
+ id="show-compiled-sql-toggle"
7
+ role="checkbox"
8
+ aria-label="ToggleSwitch"
9
+ aria-hidden="false"
10
+ type="checkbox"
11
+ className="checkbox"
12
+ checked={checked}
13
+ onChange={e => onChange(e.target.checked)}
14
+ />
15
+ <label htmlFor="show-compiled-sql-toggle" className="switch"></label>{' '}
16
+ {toggleName}
17
+ </>
18
+ );
19
+
20
+ export default ToggleSwitch;