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,47 @@
1
+ import React from 'react';
2
+ import { DJNodeDimensions } from './DJNodeDimensions';
3
+ import { DJNodeColumns } from './DJNodeColumns';
4
+
5
+ export default function Collapse({ collapsed, text, data }) {
6
+ const [isCollapsed, setIsCollapsed] = React.useState(collapsed);
7
+
8
+ const limit = 5;
9
+ return (
10
+ <>
11
+ <div className="collapse">
12
+ {data.type === 'metric' ? (
13
+ <button
14
+ className="collapse-button"
15
+ onClick={() => setIsCollapsed(!isCollapsed)}
16
+ >
17
+ {isCollapsed ? '\u25B6 Show' : '\u25BC Hide'} {text}
18
+ </button>
19
+ ) : (
20
+ ''
21
+ )}
22
+ <div
23
+ className={`collapse-content ${
24
+ isCollapsed && data.type === 'metric' ? 'collapsed' : 'expanded'
25
+ }`}
26
+ aria-expanded={isCollapsed}
27
+ >
28
+ {data.type !== 'metric'
29
+ ? isCollapsed
30
+ ? DJNodeColumns({ data: data, limit: limit })
31
+ : DJNodeColumns({ data: data, limit: 100 })
32
+ : DJNodeDimensions(data)}
33
+ </div>
34
+ {data.type !== 'metric' && data.column_names.length > limit ? (
35
+ <button
36
+ className="collapse-button"
37
+ onClick={() => setIsCollapsed(!isCollapsed)}
38
+ >
39
+ {isCollapsed ? '\u25B6 More' : '\u25BC Less'} {text}
40
+ </button>
41
+ ) : (
42
+ ''
43
+ )}
44
+ </div>
45
+ </>
46
+ );
47
+ }
@@ -1,81 +1,58 @@
1
- import React, { memo } from 'react';
1
+ import { memo } from 'react';
2
2
  import { Handle, Position } from 'reactflow';
3
-
4
- function renderBasedOnDJNodeType(param) {
5
- switch (param) {
6
- case 'source':
7
- return { backgroundColor: '#7EB46150', color: '#7EB461' };
8
- case 'transform':
9
- return { backgroundColor: '#6DAAA750', color: '#6DAAA7' };
10
- case 'dimension':
11
- return { backgroundColor: '#CF7D2950', color: '#CF7D29' };
12
- case 'metric':
13
- return { backgroundColor: '#A27E8650', color: '#A27E86' };
14
- case 'cube':
15
- return { backgroundColor: '#C2180750', color: '#C21807' };
16
- default:
17
- return {};
18
- }
19
- }
3
+ import Collapse from './Collapse';
20
4
 
21
5
  function capitalize(string) {
22
6
  return string.charAt(0).toUpperCase() + string.slice(1);
23
7
  }
24
8
 
25
- const Collapse = ({ collapsed, text, children }) => {
26
- const [isCollapsed, setIsCollapsed] = React.useState(collapsed);
27
-
28
- return (
29
- <>
30
- <div className="collapse">
31
- <button
32
- className="collapse-button"
33
- onClick={() => setIsCollapsed(!isCollapsed)}
34
- >
35
- {isCollapsed ? '\u25B6 Show' : '\u25BC Hide'} {text}
36
- </button>
37
- <div
38
- className={`collapse-content ${
39
- isCollapsed ? 'collapsed' : 'expanded'
40
- }`}
41
- aria-expanded={isCollapsed}
42
- >
43
- {children}
44
- </div>
45
- </div>
46
- </>
47
- );
48
- };
49
-
50
9
  export function DJNode({ id, data }) {
51
- const columnsRenderer = data =>
52
- data.column_names.map(col => (
53
- <tr>
54
- <td>
55
- {data.primary_key.includes(col.name) ? (
56
- <b>{col.name} (PK)</b>
57
- ) : (
58
- <>{col.name}</>
59
- )}
60
- </td>
61
- <td style={{ textAlign: 'right' }}>{col.type}</td>
62
- </tr>
63
- ));
64
- // const dimensionsRenderer = data =>
65
- // data.dimensions.map(dim => (
66
- // <tr>
67
- // <td>{dim}</td>
68
- // </tr>
69
- // ));
10
+ const handleWrapperStyle = {
11
+ display: 'flex',
12
+ position: 'absolute',
13
+ height: '100%',
14
+ flexDirection: 'column',
15
+ top: '50%',
16
+ justifyContent: 'space-between',
17
+ };
18
+ const handleWrapperStyleRight = { ...handleWrapperStyle, ...{ right: 0 } };
70
19
 
20
+ const handleStyle = {
21
+ width: '12px',
22
+ height: '12px',
23
+ borderRadius: '12px',
24
+ background: 'transparent',
25
+ border: '4px solid transparent',
26
+ cursor: 'pointer',
27
+ position: 'absolute',
28
+ top: '0px',
29
+ left: 0,
30
+ };
31
+ const handleStyleLeft = percentage => {
32
+ return {
33
+ ...handleStyle,
34
+ ...{
35
+ transform: 'translate(-' + percentage + '%, -50%)',
36
+ },
37
+ };
38
+ };
39
+ const highlightNodeClass =
40
+ data.is_current === true ? ' dj-node_highlight' : '';
71
41
  return (
72
42
  <>
73
- <Handle
74
- type="target"
75
- position={Position.Left}
76
- style={{ backgroundColor: '#ccc' }}
77
- />
78
- <div className="dj-node__full" style={renderBasedOnDJNodeType(data.type)}>
43
+ <div
44
+ className={'dj-node__full node_type__' + data.type + highlightNodeClass}
45
+ key={data.name}
46
+ style={{ width: '450px' }}
47
+ >
48
+ <div style={handleWrapperStyle}>
49
+ <Handle
50
+ type="target"
51
+ id={data.name}
52
+ position={Position.Left}
53
+ style={handleStyleLeft(100)}
54
+ />
55
+ </div>
79
56
  <div className="dj-node__header">
80
57
  <div className="serif">
81
58
  {data.name
@@ -85,25 +62,26 @@ export function DJNode({ id, data }) {
85
62
  </div>
86
63
  </div>
87
64
  <div className="dj-node__body">
88
- <b>{capitalize(data.type)}</b>:{' '}
89
- {data.type === 'source' ? data.table : data.display_name}
65
+ <b>{capitalize(data.type)}</b>
66
+ <br />{' '}
67
+ <a href={`/nodes/${data.name}`}>
68
+ {data.type === 'source' ? data.table : data.display_name}
69
+ </a>
90
70
  <Collapse
91
- collapsed={true}
71
+ collapsed={data.is_current && data.type != 'metric' ? false : true}
92
72
  text={data.type !== 'metric' ? 'columns' : 'dimensions'}
93
- >
94
- <div className="dj-node__metadata">
95
- {
96
- data.type !== 'metric' ? columnsRenderer(data) : '' // dimensionsRenderer(data)
97
- }
98
- </div>
99
- </Collapse>
73
+ data={data}
74
+ />
75
+ </div>
76
+ <div style={handleWrapperStyleRight}>
77
+ <Handle
78
+ type="source"
79
+ id={data.name}
80
+ position={Position.Right}
81
+ style={handleStyleLeft(90)}
82
+ />
100
83
  </div>
101
84
  </div>
102
- <Handle
103
- type="source"
104
- position={Position.Right}
105
- style={{ backgroundColor: '#ccc' }}
106
- />
107
85
  </>
108
86
  );
109
87
  }
@@ -0,0 +1,75 @@
1
+ import { Handle } from 'reactflow';
2
+ import React from 'react';
3
+
4
+ export function DJNodeColumns({ data, limit }) {
5
+ const handleWrapperStyle = {
6
+ display: 'flex',
7
+ position: 'absolute',
8
+ height: '100%',
9
+ flexDirection: 'column',
10
+ top: '50%',
11
+ justifyContent: 'space-between',
12
+ };
13
+ const handleWrapperStyleRight = { ...handleWrapperStyle, ...{ right: 0 } };
14
+
15
+ const handleStyle = {
16
+ width: '12px',
17
+ height: '12px',
18
+ borderRadius: '12px',
19
+ background: 'transparent',
20
+ border: '4px solid transparent',
21
+ cursor: 'pointer',
22
+ position: 'absolute',
23
+ top: '0px',
24
+ left: 0,
25
+ };
26
+ const handleStyleLeft = percentage => {
27
+ return {
28
+ ...handleStyle,
29
+ ...{
30
+ transform: 'translate(-' + percentage + '%, -50%)',
31
+ },
32
+ };
33
+ };
34
+ return data.column_names.slice(0, limit).map(col => (
35
+ <div
36
+ className={
37
+ 'custom-node-subheader node_type__' +
38
+ data.type +
39
+ (col.order <= 0 ? ' custom-node-emphasis' : '')
40
+ }
41
+ key={`${data.name}.${col.name}`}
42
+ >
43
+ <div style={handleWrapperStyle}>
44
+ <Handle
45
+ type="target"
46
+ position="left"
47
+ id={data.name + '.' + col.name}
48
+ style={handleStyleLeft(100)}
49
+ />
50
+ </div>
51
+ <div
52
+ className="custom-node-port"
53
+ id={data.name + '.' + col.name}
54
+ key={'i-' + data.name + '.' + col.name}
55
+ >
56
+ {data.primary_key.includes(col.name) ? (
57
+ <b>{col.name} (PK)</b>
58
+ ) : (
59
+ <>{col.name}</>
60
+ )}
61
+ <span style={{ marginLeft: '0.25rem' }} className={'badge'}>
62
+ {col.type}
63
+ </span>
64
+ </div>
65
+ <div style={handleWrapperStyleRight}>
66
+ <Handle
67
+ type="source"
68
+ position="right"
69
+ id={data.name + '.' + col.name}
70
+ style={handleStyle}
71
+ />
72
+ </div>
73
+ </div>
74
+ ));
75
+ }
@@ -0,0 +1,75 @@
1
+ import { useContext, useEffect, useState } from 'react';
2
+ import DJClientContext from '../../providers/djclient';
3
+
4
+ export function DJNodeDimensions(data) {
5
+ const [dimensions, setDimensions] = useState([]);
6
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
7
+ useEffect(() => {
8
+ if (data.type === 'metric') {
9
+ async function getDimensions() {
10
+ try {
11
+ const metricData = await djClient.metric(data.name);
12
+ setDimensions(metricData.dimensions);
13
+ } catch (err) {
14
+ console.log(err);
15
+ }
16
+ }
17
+ getDimensions();
18
+ }
19
+ }, [data, djClient]);
20
+ const dimensionsToObject = dimensions => {
21
+ return dimensions.map(dim => {
22
+ const [attribute, ...nodeName] = dim.name.split('.').reverse();
23
+ return {
24
+ dimension: nodeName.reverse().join('.'),
25
+ path: dim.path,
26
+ column: attribute,
27
+ };
28
+ });
29
+ };
30
+ const groupedDimensions = dims =>
31
+ dims.reduce((acc, current) => {
32
+ const dimKey = current.dimension + ' via ' + current.path.slice(-1);
33
+ acc[dimKey] = acc[dimKey] || {
34
+ dimension: current.dimension,
35
+ path: current.path.slice(-1),
36
+ columns: [],
37
+ };
38
+ acc[dimKey].columns.push(current.column);
39
+ return acc;
40
+ }, {});
41
+ const dimensionsRenderer = grouped =>
42
+ Object.entries(grouped).map(([dimKey, dimValue]) => {
43
+ if (Array.isArray(dimValue.columns)) {
44
+ const attributes = dimValue.columns.map(col => {
45
+ return (
46
+ <span className={'badge white_badge'} key={`attr-${col}`}>
47
+ {col}
48
+ </span>
49
+ );
50
+ });
51
+ return (
52
+ <div
53
+ className={'custom-node-subheader node_type__' + data.type}
54
+ key={`dim-${dimValue.path}-${dimValue.dimension}`}
55
+ >
56
+ <div className="custom-node-port">
57
+ <a href={`/nodes/${dimValue.dimension}`}>{dimValue.dimension}</a>{' '}
58
+ <div className={'badge node_type__metric text-black'}>
59
+ {dimValue.path}
60
+ </div>
61
+ </div>
62
+ <div className={'dimension_attributes'}>{attributes}</div>
63
+ </div>
64
+ );
65
+ }
66
+ return <></>;
67
+ });
68
+ return (
69
+ <>
70
+ {dimensions.length <= 0
71
+ ? ''
72
+ : dimensionsRenderer(groupedDimensions(dimensionsToObject(dimensions)))}
73
+ </>
74
+ );
75
+ }
@@ -0,0 +1,106 @@
1
+ import React, { useCallback, useEffect, useMemo } from 'react';
2
+ import ReactFlow, {
3
+ addEdge,
4
+ MiniMap,
5
+ Controls,
6
+ Background,
7
+ useNodesState,
8
+ useEdgesState,
9
+ } from 'reactflow';
10
+
11
+ import '../../../styles/dag.css';
12
+ import 'reactflow/dist/style.css';
13
+ import DJNode from '../../components/djgraph/DJNode';
14
+ import dagre from 'dagre';
15
+
16
+ const getLayoutedElements = (
17
+ nodes,
18
+ edges,
19
+ direction = 'LR',
20
+ nodeWidth = 600,
21
+ ) => {
22
+ const dagreGraph = new dagre.graphlib.Graph();
23
+ dagreGraph.setDefaultEdgeLabel(() => ({}));
24
+
25
+ const isHorizontal = direction === 'TB';
26
+ dagreGraph.setGraph({
27
+ rankdir: direction,
28
+ nodesep: 40,
29
+ ranksep: 10,
30
+ ranker: 'longest-path',
31
+ });
32
+ const nodeHeightTracker = {};
33
+
34
+ nodes.forEach(node => {
35
+ const minColumnsLength = node.data.column_names.filter(
36
+ col => col.order > 0,
37
+ ).length;
38
+ nodeHeightTracker[node.id] = Math.min(minColumnsLength, 5) * 40 + 250;
39
+ dagreGraph.setNode(node.id, {
40
+ width: nodeWidth,
41
+ height: nodeHeightTracker[node.id],
42
+ });
43
+ });
44
+
45
+ edges.forEach(edge => {
46
+ dagreGraph.setEdge(edge.source, edge.target);
47
+ });
48
+
49
+ dagre.layout(dagreGraph);
50
+
51
+ nodes.forEach(node => {
52
+ const nodeWithPosition = dagreGraph.node(node.id);
53
+ node.targetPosition = isHorizontal ? 'left' : 'top';
54
+ node.sourcePosition = isHorizontal ? 'right' : 'bottom';
55
+ node.position = {
56
+ x: nodeWithPosition.x - nodeWidth / 2,
57
+ y: nodeWithPosition.y - nodeHeightTracker[node.id] / 3,
58
+ };
59
+ node.width = nodeWidth;
60
+ node.height = nodeHeightTracker[node.id];
61
+ return node;
62
+ });
63
+
64
+ return { nodes: nodes, edges: edges };
65
+ };
66
+
67
+ const LayoutFlow = (djNode, saveGraph) => {
68
+ const nodeTypes = useMemo(() => ({ DJNode: DJNode }), []);
69
+
70
+ // These are used internally by ReactFlow (to update the nodes on the ReactFlow pane)
71
+ const [nodes, setNodes, onNodesChange] = useNodesState([]);
72
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
73
+
74
+ const minimapStyle = {
75
+ height: 100,
76
+ width: 150,
77
+ };
78
+
79
+ useEffect(() => {
80
+ saveGraph(getLayoutedElements, setNodes, setEdges).catch(console.error);
81
+ }, [djNode]);
82
+
83
+ const onConnect = useCallback(
84
+ params => setEdges(eds => addEdge(params, eds)),
85
+ [setEdges],
86
+ );
87
+ return (
88
+ <div style={{ height: '800px' }}>
89
+ <ReactFlow
90
+ nodes={nodes}
91
+ edges={edges}
92
+ nodeTypes={nodeTypes}
93
+ onNodesChange={onNodesChange}
94
+ onEdgesChange={onEdgesChange}
95
+ onConnect={onConnect}
96
+ snapToGrid={true}
97
+ fitView
98
+ >
99
+ <MiniMap style={minimapStyle} zoomable pannable />
100
+ <Controls />
101
+ <Background color="#aaa" gap={16} />
102
+ </ReactFlow>
103
+ </div>
104
+ );
105
+ };
106
+ export default LayoutFlow;
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+ import { render, fireEvent, screen, waitFor } from '@testing-library/react';
3
+ import Collapse from '../Collapse';
4
+ import { mocks } from '../../../../mocks/mockNodes';
5
+ import { ReactFlowProvider } from 'reactflow';
6
+
7
+ jest.mock('../DJNodeDimensions', () => ({
8
+ DJNodeDimensions: jest.fn(data => <div>DJNodeDimensions content</div>),
9
+ }));
10
+
11
+ describe('<Collapse />', () => {
12
+ const defaultProps = {
13
+ collapsed: true,
14
+ text: 'Dimensions',
15
+ data: mocks.mockMetricNode,
16
+ };
17
+
18
+ it('renders without crashing', () => {
19
+ render(<Collapse {...defaultProps} />);
20
+ });
21
+
22
+ it('renders toggle for metric type', () => {
23
+ const { getByText } = render(
24
+ <Collapse {...defaultProps} data={mocks.mockMetricNode} />,
25
+ );
26
+ const button = screen.getByText('▶ Show Dimensions');
27
+ fireEvent.click(button);
28
+ expect(getByText('▼ Hide Dimensions')).toBeInTheDocument();
29
+ });
30
+
31
+ it('toggles More/Less button text for non-metric type on click', () => {
32
+ defaultProps.text = 'Columns';
33
+ const { getByText } = render(
34
+ <ReactFlowProvider>
35
+ <Collapse
36
+ {...defaultProps}
37
+ data={{
38
+ type: 'transform',
39
+ column_names: Array(11).fill(idx => {
40
+ return `column-${idx}`;
41
+ }),
42
+ primary_key: [],
43
+ }}
44
+ />
45
+ </ReactFlowProvider>,
46
+ );
47
+ const button = getByText('▶ More Columns');
48
+ fireEvent.click(button);
49
+ expect(getByText('▼ Less Columns')).toBeInTheDocument();
50
+ });
51
+ });
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { DJNodeColumns } from '../DJNodeColumns';
4
+ import { ReactFlowProvider } from 'reactflow';
5
+
6
+ describe('<DJNodeColumns />', () => {
7
+ const defaultProps = {
8
+ data: {
9
+ name: 'TestName',
10
+ type: 'metric',
11
+ column_names: [
12
+ { name: 'col1', type: 'int' },
13
+ { name: 'col2', type: 'string' },
14
+ { name: 'col3', type: 'float' },
15
+ ],
16
+ primary_key: ['col1'],
17
+ },
18
+ limit: 10,
19
+ };
20
+
21
+ const domTestingLib = require('@testing-library/dom');
22
+ const { queryHelpers } = domTestingLib;
23
+
24
+ const queryByAttribute = attribute =>
25
+ queryHelpers.queryAllByAttribute.bind(null, attribute);
26
+
27
+ function getByAttribute(container, id, attribute, ...rest) {
28
+ const result = queryByAttribute(attribute)(container, id, ...rest);
29
+ return result[0];
30
+ }
31
+
32
+ function DJNodeColumnsWithProvider(props) {
33
+ return (
34
+ <ReactFlowProvider>
35
+ <DJNodeColumns {...props} />
36
+ </ReactFlowProvider>
37
+ );
38
+ }
39
+
40
+ it('renders without crashing', () => {
41
+ render(<DJNodeColumnsWithProvider {...defaultProps} />);
42
+ });
43
+
44
+ it('renders columns correctly', () => {
45
+ const { container } = render(
46
+ <DJNodeColumnsWithProvider {...defaultProps} />,
47
+ );
48
+
49
+ // renders column names
50
+ defaultProps.data.column_names.forEach(col => {
51
+ expect(screen.getByText(col.name, { exact: false })).toBeInTheDocument();
52
+ });
53
+
54
+ // appends (PK) to primary key column name
55
+ expect(screen.getByText('col1 (PK)')).toBeInTheDocument();
56
+
57
+ // renders column type badge correctly
58
+ defaultProps.data.column_names.forEach(col => {
59
+ expect(screen.getByText(col.type)).toBeInTheDocument();
60
+ });
61
+
62
+ // renders handles correctly
63
+ defaultProps.data.column_names.forEach(col => {
64
+ expect(
65
+ getByAttribute(
66
+ container,
67
+ defaultProps.data.name + '.' + col.name,
68
+ 'data-handleid',
69
+ ),
70
+ ).toBeInTheDocument();
71
+ });
72
+ });
73
+
74
+ it('renders limited columns based on the limit prop', () => {
75
+ const limitedProps = {
76
+ ...defaultProps,
77
+ limit: 2,
78
+ };
79
+
80
+ render(<DJNodeColumnsWithProvider {...limitedProps} />);
81
+ expect(screen.queryByText('col3')).not.toBeInTheDocument();
82
+ });
83
+ });