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
@@ -1,34 +1,103 @@
1
- import { Component } from 'react';
1
+ import { Component, useContext, useEffect, useRef, useState } from 'react';
2
+ import ValidIcon from '../../icons/ValidIcon';
3
+ import InvalidIcon from '../../icons/InvalidIcon';
4
+ import DJClientContext from '../../providers/djclient';
5
+ import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
6
+ import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
7
+ import markdown from 'react-syntax-highlighter/dist/cjs/languages/hljs/markdown';
8
+ import * as React from 'react';
9
+ import { AlertMessage } from '../AddEditNodePage/AlertMessage';
10
+ import AlertIcon from '../../icons/AlertIcon';
11
+ import { labelize } from '../../../utils/form';
2
12
 
3
- export default class NodeStatus extends Component {
4
- render() {
5
- const { node } = this.props;
6
- return (
7
- <span className="status__valid status" style={{ alignContent: 'center' }}>
8
- {node?.status === 'valid' ? (
9
- <svg
10
- xmlns="http://www.w3.org/2000/svg"
11
- width="25"
12
- height="25"
13
- fill="currentColor"
14
- className="bi bi-check-circle-fill"
15
- viewBox="0 0 16 16"
16
- >
17
- <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z" />
18
- </svg>
19
- ) : (
20
- <svg
21
- xmlns="http://www.w3.org/2000/svg"
22
- width="16"
23
- height="16"
24
- fill="currentColor"
25
- className="bi bi-x-circle-fill"
26
- viewBox="0 0 16 16"
27
- >
28
- <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z" />
29
- </svg>
30
- )}
31
- </span>
13
+ SyntaxHighlighter.registerLanguage('markdown', markdown);
14
+
15
+ export default function NodeStatus({ node, revalidate = true }) {
16
+ const MAX_ERROR_LENGTH = 200;
17
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
18
+ const [validation, setValidation] = useState([]);
19
+
20
+ const [codeAnchor, setCodeAnchor] = useState(false);
21
+ const ref = useRef(null);
22
+
23
+ useEffect(() => {
24
+ if (revalidate) {
25
+ const fetchData = async () => {
26
+ setValidation(await djClient.revalidate(node.name));
27
+ };
28
+ fetchData().catch(console.error);
29
+ }
30
+ }, [djClient, node, revalidate]);
31
+
32
+ const displayValidation =
33
+ revalidate && validation?.errors?.length > 0 ? (
34
+ <>
35
+ <button
36
+ className="badge"
37
+ style={{
38
+ backgroundColor: '#b34b00',
39
+ fontSize: '15px',
40
+ outline: '0',
41
+ border: '0',
42
+ cursor: 'pointer',
43
+ }}
44
+ onClick={() => setCodeAnchor(!codeAnchor)}
45
+ >
46
+ ⚠ {validation?.errors?.length} error
47
+ {validation?.errors?.length > 1 ? 's' : ''}
48
+ </button>
49
+ <div
50
+ className="popover"
51
+ ref={ref}
52
+ style={{
53
+ display: codeAnchor === false ? 'none' : 'block',
54
+ border: 'none',
55
+ paddingTop: '0px !important',
56
+ marginTop: '0px',
57
+ backgroundColor: 'transparent',
58
+ }}
59
+ >
60
+ {validation?.errors?.map((error, idx) => (
61
+ <div className="validation_error">
62
+ <b
63
+ style={{
64
+ color: '#b34b00',
65
+ }}
66
+ >
67
+ {labelize(error.type.toLowerCase())}:
68
+ </b>{' '}
69
+ {error.message.length > MAX_ERROR_LENGTH
70
+ ? error.message.slice(0, MAX_ERROR_LENGTH - 1) + '...'
71
+ : error.message}
72
+ </div>
73
+ ))}
74
+ </div>
75
+ </>
76
+ ) : (
77
+ <></>
32
78
  );
33
- }
79
+
80
+ return (
81
+ <>
82
+ {revalidate && validation?.errors?.length > 0 ? (
83
+ displayValidation
84
+ ) : validation?.status === 'valid' ||
85
+ node?.status === 'valid' ||
86
+ node?.current?.status === 'VALID' ? (
87
+ <span
88
+ className="status__valid status"
89
+ style={{ alignContent: 'center' }}
90
+ >
91
+ <ValidIcon />
92
+ </span>
93
+ ) : (
94
+ <span
95
+ className="status__invalid status"
96
+ style={{ alignContent: 'center' }}
97
+ >
98
+ <InvalidIcon />
99
+ </span>
100
+ )}
101
+ </>
102
+ );
34
103
  }
@@ -0,0 +1,367 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import Select from 'react-select';
3
+ import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
4
+ import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
5
+ import { labelize } from '../../../utils/form';
6
+ import { Form, Formik } from 'formik';
7
+ import DimensionFilter from './DimensionFilter';
8
+ import QueryInfo from '../../components/QueryInfo';
9
+ import LoadingIcon from '../../icons/LoadingIcon';
10
+
11
+ export default function NodeValidateTab({ node, djClient }) {
12
+ const [query, setQuery] = useState('');
13
+ const [lookup, setLookup] = useState([]);
14
+ const [running, setRunning] = useState(false);
15
+
16
+ // These are a list of dimensions that are available for this node
17
+ const [dimensions, setDimensions] = useState([]);
18
+
19
+ // A separate structure used to store the selected dimensions to filter by and their values
20
+ const [selectedFilters, setSelectedFilters] = useState({});
21
+
22
+ // The set of dimensions and filters to pass to the /sql or /data endpoints for the node
23
+ const [selection, setSelection] = useState({
24
+ dimensions: [],
25
+ filters: [],
26
+ });
27
+
28
+ // Any query result info retrieved when a node query is run
29
+ const [queryInfo, setQueryInfo] = useState(null);
30
+
31
+ const initialValues = {};
32
+
33
+ const [state, setState] = useState({
34
+ selectedTab: 'results',
35
+ });
36
+
37
+ const switchTab = tabName => {
38
+ setState({ selectedTab: tabName });
39
+ };
40
+
41
+ useEffect(() => {
42
+ const fetchData = async () => {
43
+ if (node) {
44
+ // Find all the dimensions for this node
45
+ const dimensions = await djClient.nodeDimensions(node.name);
46
+
47
+ // Create a dimensions lookup object
48
+ const lookup = {};
49
+ dimensions.forEach(dimension => {
50
+ lookup[dimension.name] = dimension;
51
+ });
52
+ setLookup(lookup);
53
+
54
+ // Group the dimensions by dimension node
55
+ const grouped = Object.entries(
56
+ dimensions.reduce((group, dimension) => {
57
+ group[dimension.node_name + dimension.path] =
58
+ group[dimension.node_name + dimension.path] ?? [];
59
+ group[dimension.node_name + dimension.path].push(dimension);
60
+ return group;
61
+ }, {}),
62
+ );
63
+ setDimensions(grouped);
64
+
65
+ // Build the query for this node based on the user-provided dimensions and filters
66
+ const query = await djClient.sql(node.name, selection);
67
+ setQuery(query.sql);
68
+ }
69
+ };
70
+ fetchData().catch(console.error);
71
+ }, [djClient, node, selection]);
72
+
73
+ const dimensionsList = dimensions.flatMap(grouping => {
74
+ const dimensionsInGroup = grouping[1];
75
+ return dimensionsInGroup
76
+ .filter(dim => dim.properties.includes('primary_key') === true)
77
+ .map(dim => {
78
+ return {
79
+ value: dim.name,
80
+ label: (
81
+ <span>
82
+ {labelize(dim.name.split('.').slice(-1)[0])}{' '}
83
+ <small>{dim.name}</small>
84
+ </span>
85
+ ),
86
+ };
87
+ });
88
+ });
89
+
90
+ // Run the query and use SSE to stream the status of the query execution results
91
+ const runQuery = async (values, setStatus, setSubmitting) => {
92
+ setRunning(true);
93
+ const sse = await djClient.streamNodeData(node?.name, selection);
94
+ sse.onmessage = e => {
95
+ const messageData = JSON.parse(JSON.parse(e.data));
96
+ if (
97
+ messageData !== null &&
98
+ messageData?.state !== 'FINISHED' &&
99
+ messageData?.state !== 'CANCELED' &&
100
+ messageData?.state !== 'FAILED'
101
+ ) {
102
+ setRunning(false);
103
+ }
104
+ if (messageData.results && messageData.results?.length > 0) {
105
+ messageData.numRows = messageData.results?.length
106
+ ? messageData.results[0].rows.length
107
+ : [];
108
+ switchTab('results');
109
+ setRunning(false);
110
+ } else {
111
+ switchTab('info');
112
+ }
113
+ setQueryInfo(messageData);
114
+ };
115
+ sse.onerror = () => sse.close();
116
+ };
117
+
118
+ // Handle form submission (runs the query)
119
+ const handleSubmit = async (values, { setSubmitting, setStatus }) => {
120
+ await runQuery(values, setStatus, setSubmitting);
121
+ };
122
+
123
+ // Handle when filter values are updated. This is available for all nodes.
124
+ const handleAddFilters = event => {
125
+ const updatedFilters = selectedFilters;
126
+ if (event.dimension in updatedFilters) {
127
+ updatedFilters[event.dimension].operator = event.operator;
128
+ updatedFilters[event.dimension].values = event.values;
129
+ } else {
130
+ updatedFilters[event.dimension] = {
131
+ operator: event.operator,
132
+ values: event.values,
133
+ };
134
+ }
135
+ setSelectedFilters(updatedFilters);
136
+ const updatedDimensions = selection.dimensions.concat([event.dimension]);
137
+ setSelection({
138
+ filters: Object.entries(updatedFilters).map(obj =>
139
+ obj[1].values
140
+ ? `${obj[0]} IN (${obj[1].values
141
+ .map(val =>
142
+ ['int', 'bigint', 'float', 'double', 'long'].includes(
143
+ lookup[obj[0]].type,
144
+ )
145
+ ? val.value
146
+ : "'" + val.value + "'",
147
+ )
148
+ .join(', ')})`
149
+ : '',
150
+ ),
151
+ dimensions: updatedDimensions,
152
+ });
153
+ };
154
+
155
+ // Handle when one or more dimensions are selected from the dropdown
156
+ // Note that this is only available to metrics
157
+ const handleAddDimensions = event => {
158
+ const updatedDimensions = event.map(
159
+ selectedDimension => selectedDimension.value,
160
+ );
161
+ setSelection({
162
+ filters: selection.filters,
163
+ dimensions: updatedDimensions,
164
+ });
165
+ };
166
+
167
+ const filters = dimensions.map(grouping => {
168
+ const dimensionsInGroup = grouping[1];
169
+ const dimensionGroupOptions = dimensionsInGroup
170
+ .filter(dim => dim.properties.includes('primary_key') === true)
171
+ .map(dim => {
172
+ return {
173
+ value: dim.name,
174
+ label: labelize(dim.name.split('.').slice(-1)[0]),
175
+ metadata: dim,
176
+ };
177
+ });
178
+ return (
179
+ <>
180
+ <div className="dimensionsList">
181
+ {dimensionGroupOptions.map(dimension => {
182
+ return (
183
+ <DimensionFilter
184
+ dimension={dimension}
185
+ onChange={handleAddFilters}
186
+ />
187
+ );
188
+ })}
189
+ </div>
190
+ </>
191
+ );
192
+ });
193
+
194
+ return (
195
+ <Formik initialValues={initialValues} onSubmit={handleSubmit}>
196
+ {function Render({ isSubmitting, status, setFieldValue }) {
197
+ return (
198
+ <Form>
199
+ <div className={'queryrunner'}>
200
+ <div className="queryrunner-filters left">
201
+ {node?.type === 'metric' ? (
202
+ <>
203
+ <span>
204
+ <label>Group By</label>
205
+ </span>
206
+ <Select
207
+ name="dimensions"
208
+ options={dimensionsList}
209
+ isMulti
210
+ isClearable
211
+ onChange={handleAddDimensions}
212
+ />
213
+ <br />
214
+ </>
215
+ ) : null}
216
+ <span>
217
+ <label>Add Filters</label>
218
+ </span>
219
+ {filters}
220
+ </div>
221
+ <div className={'righttop'}>
222
+ <label>Generated Query</label>
223
+ <div
224
+ style={{
225
+ height: '200px',
226
+ width: '80%',
227
+ overflow: 'scroll',
228
+ borderRadius: '0',
229
+ border: '1px solid #ccc',
230
+ }}
231
+ className="queryrunner-query"
232
+ >
233
+ <SyntaxHighlighter
234
+ language="sql"
235
+ style={foundation}
236
+ wrapLines={true}
237
+ >
238
+ {query}
239
+ </SyntaxHighlighter>
240
+ </div>
241
+ <button
242
+ type="submit"
243
+ disabled={
244
+ running ||
245
+ (queryInfo !== null &&
246
+ queryInfo?.state !== 'FINISHED' &&
247
+ queryInfo?.state !== 'CANCELED' &&
248
+ queryInfo?.state !== 'FAILED')
249
+ }
250
+ className="button-3 execute-button"
251
+ style={{ marginTop: '1rem' }}
252
+ >
253
+ {isSubmitting || running === true ? <LoadingIcon /> : '► Run'}
254
+ </button>
255
+ </div>
256
+ <div
257
+ style={{
258
+ width: window.innerWidth * 0.8,
259
+ marginTop: '1rem',
260
+ marginLeft: '0.5rem',
261
+ height: 'calc(70% - 5.5px)',
262
+ }}
263
+ className={'rightbottom'}
264
+ >
265
+ <div
266
+ className={'align-items-center row'}
267
+ style={{
268
+ borderBottom: '1px solid #dddddd',
269
+ width: '86%',
270
+ }}
271
+ >
272
+ <div
273
+ className={
274
+ 'tab-item' +
275
+ (state.selectedTab === 'results' ? ' active' : '')
276
+ }
277
+ onClick={_ => switchTab('results')}
278
+ >
279
+ Results
280
+ </div>
281
+ <div
282
+ className={
283
+ 'tab-item' +
284
+ (state.selectedTab === 'info' ? ' active' : '')
285
+ }
286
+ aria-label={'QueryInfo'}
287
+ role={'button'}
288
+ onClick={_ => switchTab('info')}
289
+ >
290
+ Info
291
+ </div>
292
+ </div>
293
+ {state.selectedTab === 'info' ? (
294
+ <div>
295
+ {queryInfo && queryInfo.id ? (
296
+ <QueryInfo {...queryInfo} isList={true} />
297
+ ) : (
298
+ <></>
299
+ )}
300
+ </div>
301
+ ) : null}
302
+ {state.selectedTab === 'results' ? (
303
+ <div>
304
+ {queryInfo !== null && queryInfo.state !== 'FINISHED' ? (
305
+ <div style={{ padding: '2rem' }}>
306
+ The query has status {queryInfo.state}! Check the INFO
307
+ tab for more details.
308
+ </div>
309
+ ) : queryInfo !== null &&
310
+ queryInfo.results !== null &&
311
+ queryInfo.results.length === 0 ? (
312
+ <div style={{ padding: '2rem' }}>
313
+ The query finished but output no results.
314
+ </div>
315
+ ) : queryInfo !== null &&
316
+ queryInfo.results !== null &&
317
+ queryInfo.results.length > 0 ? (
318
+ <div
319
+ className="table-responsive"
320
+ style={{
321
+ gridGap: '0',
322
+ width: '86%',
323
+ padding: '0',
324
+ maxHeight: '50%',
325
+ }}
326
+ >
327
+ <table
328
+ style={{ marginTop: '0 !important' }}
329
+ className="table"
330
+ >
331
+ <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
332
+ <tr>
333
+ {queryInfo.results[0]?.columns.map(columnName => (
334
+ <th key={columnName.name}>
335
+ {columnName.column}
336
+ </th>
337
+ ))}
338
+ </tr>
339
+ </thead>
340
+ <tbody>
341
+ {queryInfo.results[0]?.rows
342
+ .slice(0, 100)
343
+ .map((rowData, index) => (
344
+ <tr key={`data-row:${index}`}>
345
+ {rowData.map(rowValue => (
346
+ <td key={rowValue}>{rowValue}</td>
347
+ ))}
348
+ </tr>
349
+ ))}
350
+ </tbody>
351
+ </table>
352
+ </div>
353
+ ) : (
354
+ <div style={{ padding: '2rem' }}>
355
+ Click "Run" to execute the query.
356
+ </div>
357
+ )}
358
+ </div>
359
+ ) : null}
360
+ </div>
361
+ </div>
362
+ </Form>
363
+ );
364
+ }}
365
+ </Formik>
366
+ );
367
+ }
@@ -0,0 +1,42 @@
1
+ import { useEffect, useState } from 'react';
2
+ import * as React from 'react';
3
+
4
+ export default function NodesWithDimension({ node, djClient }) {
5
+ const [availableNodes, setAvailableNodes] = useState([]);
6
+
7
+ useEffect(() => {
8
+ const fetchData = async () => {
9
+ const data = await djClient.nodesWithDimension(node.name);
10
+ setAvailableNodes(data);
11
+ };
12
+ fetchData().catch(console.error);
13
+ }, [djClient, node]);
14
+ return (
15
+ <div className="table-responsive">
16
+ <table className="card-inner-table table">
17
+ <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
18
+ <tr>
19
+ <th className="text-start">Name</th>
20
+ <th>Type</th>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ {availableNodes.map(node => (
25
+ <tr>
26
+ <td>
27
+ <a href={`/nodes/${node.name}`}>{node.display_name}</a>
28
+ </td>
29
+ <td>
30
+ <span
31
+ className={'node_type__' + node.type + ' badge node_type'}
32
+ >
33
+ {node.type}
34
+ </span>
35
+ </td>
36
+ </tr>
37
+ ))}
38
+ </tbody>
39
+ </table>
40
+ </div>
41
+ );
42
+ }
@@ -0,0 +1,36 @@
1
+ import DJClientContext from '../../providers/djclient';
2
+ import JupyterExportIcon from '../../icons/JupyterExportIcon';
3
+ import { useContext } from 'react';
4
+
5
+ export default function NotebookDownload({ node }) {
6
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
7
+
8
+ const downloadFile = async () => {
9
+ try {
10
+ const response = await djClient.notebookExportCube(node.name);
11
+ const notebook = await response.blob();
12
+ const url = window.URL.createObjectURL(new Blob([notebook]));
13
+
14
+ const link = document.createElement('a');
15
+ link.href = url;
16
+ link.setAttribute('download', 'notebook.ipynb');
17
+ document.body.appendChild(link);
18
+ link.click();
19
+ link.parentNode.removeChild(link);
20
+ } catch (error) {
21
+ console.error('Error downloading file: ', error);
22
+ }
23
+ };
24
+
25
+ return (
26
+ <>
27
+ <button
28
+ className="button-3"
29
+ onClick={downloadFile}
30
+ style={{ height: '2.5rem' }}
31
+ >
32
+ <JupyterExportIcon /> Export as Notebook
33
+ </button>
34
+ </>
35
+ );
36
+ }