datajunction-ui 0.0.1-a1

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 (154) hide show
  1. package/.babel-plugin-macrosrc.js +5 -0
  2. package/.env +3 -0
  3. package/.eslintrc.js +20 -0
  4. package/.gitattributes +201 -0
  5. package/.husky/pre-commit +6 -0
  6. package/.nvmrc +1 -0
  7. package/.prettierignore +6 -0
  8. package/.prettierrc +9 -0
  9. package/.stylelintrc +7 -0
  10. package/LICENSE +22 -0
  11. package/Makefile +3 -0
  12. package/README.md +10 -0
  13. package/dj-logo.svg +10 -0
  14. package/internals/testing/loadable.mock.tsx +6 -0
  15. package/package.json +189 -0
  16. package/public/favicon.ico +0 -0
  17. package/public/index.html +26 -0
  18. package/public/manifest.json +15 -0
  19. package/public/robots.txt +3 -0
  20. package/src/__tests__/reportWebVitals.test.jsx +44 -0
  21. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +9 -0
  22. package/src/app/__tests__/index.test.tsx +14 -0
  23. package/src/app/components/DeleteNode.jsx +55 -0
  24. package/src/app/components/ListGroupItem.jsx +24 -0
  25. package/src/app/components/NamespaceHeader.jsx +31 -0
  26. package/src/app/components/QueryInfo.jsx +77 -0
  27. package/src/app/components/Tab.jsx +25 -0
  28. package/src/app/components/ToggleSwitch.jsx +20 -0
  29. package/src/app/components/__tests__/DeleteNode.test.jsx +53 -0
  30. package/src/app/components/__tests__/ListGroupItem.test.tsx +16 -0
  31. package/src/app/components/__tests__/NamespaceHeader.test.jsx +14 -0
  32. package/src/app/components/__tests__/QueryInfo.test.jsx +55 -0
  33. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  34. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  35. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +29 -0
  36. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +47 -0
  37. package/src/app/components/djgraph/Collapse.jsx +46 -0
  38. package/src/app/components/djgraph/DJNode.jsx +89 -0
  39. package/src/app/components/djgraph/DJNodeColumns.jsx +71 -0
  40. package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
  41. package/src/app/components/djgraph/LayoutFlow.jsx +104 -0
  42. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  43. package/src/app/components/djgraph/__tests__/DJNode.test.tsx +24 -0
  44. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  45. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  46. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +117 -0
  47. package/src/app/constants.js +2 -0
  48. package/src/app/icons/AlertIcon.jsx +32 -0
  49. package/src/app/icons/CollapsedIcon.jsx +15 -0
  50. package/src/app/icons/DJLogo.jsx +36 -0
  51. package/src/app/icons/DeleteIcon.jsx +21 -0
  52. package/src/app/icons/EditIcon.jsx +18 -0
  53. package/src/app/icons/ExpandedIcon.jsx +15 -0
  54. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  55. package/src/app/icons/InvalidIcon.jsx +14 -0
  56. package/src/app/icons/LoadingIcon.jsx +14 -0
  57. package/src/app/icons/PythonIcon.jsx +52 -0
  58. package/src/app/icons/TableIcon.jsx +14 -0
  59. package/src/app/icons/ValidIcon.jsx +14 -0
  60. package/src/app/index.tsx +108 -0
  61. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +46 -0
  62. package/src/app/pages/AddEditNodePage/FullNameField.jsx +37 -0
  63. package/src/app/pages/AddEditNodePage/Loadable.jsx +16 -0
  64. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +89 -0
  65. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +103 -0
  66. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +132 -0
  67. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
  68. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
  69. package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
  70. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
  71. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
  72. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
  73. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +180 -0
  74. package/src/app/pages/AddEditNodePage/index.jsx +396 -0
  75. package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
  76. package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
  77. package/src/app/pages/AddEditTagPage/index.jsx +132 -0
  78. package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
  79. package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
  80. package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
  81. package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
  82. package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
  83. package/src/app/pages/LoginPage/index.jsx +17 -0
  84. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
  85. package/src/app/pages/NamespacePage/Explorer.jsx +57 -0
  86. package/src/app/pages/NamespacePage/Loadable.jsx +16 -0
  87. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +217 -0
  88. package/src/app/pages/NamespacePage/index.jsx +199 -0
  89. package/src/app/pages/NodePage/AddBackfillPopover.jsx +166 -0
  90. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +161 -0
  91. package/src/app/pages/NodePage/ClientCodePopover.jsx +46 -0
  92. package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
  93. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +149 -0
  94. package/src/app/pages/NodePage/Loadable.jsx +16 -0
  95. package/src/app/pages/NodePage/NodeColumnTab.jsx +200 -0
  96. package/src/app/pages/NodePage/NodeGraphTab.jsx +112 -0
  97. package/src/app/pages/NodePage/NodeHistory.jsx +212 -0
  98. package/src/app/pages/NodePage/NodeInfoTab.jsx +212 -0
  99. package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
  100. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +233 -0
  101. package/src/app/pages/NodePage/NodeSQLTab.jsx +82 -0
  102. package/src/app/pages/NodePage/NodeStatus.jsx +28 -0
  103. package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
  104. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +153 -0
  105. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +47 -0
  106. package/src/app/pages/NodePage/__tests__/ClientCodePopover.test.jsx +49 -0
  107. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +148 -0
  108. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +165 -0
  109. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +591 -0
  110. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +57 -0
  111. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +757 -0
  112. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  113. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +403 -0
  114. package/src/app/pages/NodePage/index.jsx +210 -0
  115. package/src/app/pages/NotFoundPage/Loadable.tsx +14 -0
  116. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  117. package/src/app/pages/NotFoundPage/index.tsx +23 -0
  118. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  119. package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +110 -0
  120. package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +37 -0
  121. package/src/app/pages/RegisterTablePage/index.jsx +142 -0
  122. package/src/app/pages/Root/Loadable.tsx +14 -0
  123. package/src/app/pages/Root/__tests__/index.test.jsx +77 -0
  124. package/src/app/pages/Root/assets/dj-logo.png +0 -0
  125. package/src/app/pages/Root/index.tsx +70 -0
  126. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  127. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  128. package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
  129. package/src/app/pages/TagPage/Loadable.jsx +16 -0
  130. package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
  131. package/src/app/pages/TagPage/index.jsx +79 -0
  132. package/src/app/providers/djclient.jsx +5 -0
  133. package/src/app/services/DJService.js +665 -0
  134. package/src/app/services/__tests__/DJService.test.jsx +804 -0
  135. package/src/index.tsx +48 -0
  136. package/src/mocks/mockNodes.jsx +1430 -0
  137. package/src/react-app-env.d.ts +4 -0
  138. package/src/reportWebVitals.ts +15 -0
  139. package/src/setupTests.ts +36 -0
  140. package/src/styles/dag.css +228 -0
  141. package/src/styles/index.css +1083 -0
  142. package/src/styles/loading.css +34 -0
  143. package/src/styles/login.css +81 -0
  144. package/src/styles/node-creation.scss +197 -0
  145. package/src/styles/styles.scss +44 -0
  146. package/src/styles/styles.scss.d.ts +9 -0
  147. package/src/utils/__tests__/__snapshots__/loadable.test.tsx.snap +17 -0
  148. package/src/utils/__tests__/loadable.test.tsx +53 -0
  149. package/src/utils/__tests__/request.test.ts +82 -0
  150. package/src/utils/form.jsx +23 -0
  151. package/src/utils/loadable.tsx +30 -0
  152. package/src/utils/request.ts +54 -0
  153. package/tsconfig.json +34 -0
  154. package/webpack.config.js +118 -0
@@ -0,0 +1,15 @@
1
+ const CollapsedIcon = props => (
2
+ <svg
3
+ stroke="currentColor"
4
+ fill="currentColor"
5
+ strokeWidth="0"
6
+ viewBox="0 0 512 512"
7
+ height="1em"
8
+ width="1em"
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ >
11
+ <path d="M48 256c0 114.9 93.1 208 208 208s208-93.1 208-208S370.9 48 256 48 48 141.1 48 256zm244.5 0l-81.9-81.1c-7.5-7.5-7.5-19.8 0-27.3s19.8-7.5 27.3 0l95.4 95.7c7.3 7.3 7.5 19.1.6 26.6l-94 94.3c-3.8 3.8-8.7 5.7-13.7 5.7-4.9 0-9.9-1.9-13.6-5.6-7.5-7.5-7.6-19.7 0-27.3l79.9-81z"></path>
12
+ </svg>
13
+ );
14
+
15
+ export default CollapsedIcon;
@@ -0,0 +1,36 @@
1
+ const DJLogo = props => (
2
+ <svg
3
+ width="30"
4
+ height="30"
5
+ style={{
6
+ marginRight: '10px',
7
+ marginBottom: '2px',
8
+ stroke: 'transparent',
9
+ strokeWidth: '0px',
10
+ }}
11
+ viewBox="0 0 83 83"
12
+ fill="none"
13
+ xmlns="http://www.w3.org/2000/svg"
14
+ xmlnsXlink="http://www.w3.org/1999/xlink"
15
+ >
16
+ <rect width="83" height="83" fill="url(#pattern0)" />
17
+ <rect width="83" height="83" stroke="black" />
18
+ <defs>
19
+ <pattern
20
+ id="pattern0"
21
+ patternContentUnits="objectBoundingBox"
22
+ width="1"
23
+ height="1"
24
+ >
25
+ <use xlinkHref="#image0_1_2" transform="scale(0.00364964)" />
26
+ </pattern>
27
+ <image
28
+ id="image0_1_2"
29
+ width="274"
30
+ height="274"
31
+ xlinkHref="data:image/png;base64,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"
32
+ />
33
+ </defs>
34
+ </svg>
35
+ );
36
+ export default DJLogo;
@@ -0,0 +1,21 @@
1
+ const DeleteIcon = props => (
2
+ <svg
3
+ className="feather feather-trash-2"
4
+ fill="none"
5
+ height="24"
6
+ stroke="currentColor"
7
+ strokeLinecap="round"
8
+ strokeLinejoin="round"
9
+ strokeWidth="2"
10
+ viewBox="0 0 24 24"
11
+ width="24"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ >
14
+ <polyline points="3 6 5 6 21 6" />
15
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
16
+ <line x1="10" x2="10" y1="11" y2="17" />
17
+ <line x1="14" x2="14" y1="11" y2="17" />
18
+ </svg>
19
+ );
20
+
21
+ export default DeleteIcon;
@@ -0,0 +1,18 @@
1
+ const EditIcon = props => (
2
+ <svg
3
+ className="feather feather-edit"
4
+ fill="none"
5
+ height="24"
6
+ stroke="currentColor"
7
+ strokeLinecap="round"
8
+ strokeLinejoin="round"
9
+ strokeWidth="2"
10
+ viewBox="0 0 24 24"
11
+ width="24"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ >
14
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
15
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
16
+ </svg>
17
+ );
18
+ export default EditIcon;
@@ -0,0 +1,15 @@
1
+ const ExpandedIcon = props => (
2
+ <svg
3
+ stroke="currentColor"
4
+ fill="currentColor"
5
+ strokeWidth="0"
6
+ viewBox="0 0 512 512"
7
+ height="1em"
8
+ width="1em"
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ >
11
+ <path d="M48 256c0 114.9 93.1 208 208 208s208-93.1 208-208S370.9 48 256 48 48 141.1 48 256zm289.1-43.4c7.5-7.5 19.8-7.5 27.3 0 3.8 3.8 5.6 8.7 5.6 13.6s-1.9 9.9-5.7 13.7l-94.3 94c-7.6 6.9-19.3 6.7-26.6-.6l-95.7-95.4c-7.5-7.5-7.6-19.7 0-27.3 7.5-7.5 19.7-7.6 27.3 0l81.1 81.9 81-79.9z"></path>
12
+ </svg>
13
+ );
14
+
15
+ export default ExpandedIcon;
@@ -0,0 +1,15 @@
1
+ const HorizontalHierarchyIcon = props => (
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="16"
5
+ height="16"
6
+ fill="currentColor"
7
+ className="bi bi-house-door-fill"
8
+ viewBox="0 0 16 16"
9
+ style={{ paddingBottom: '0.2rem' }}
10
+ >
11
+ <path d="M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z" />
12
+ </svg>
13
+ );
14
+
15
+ export default HorizontalHierarchyIcon;
@@ -0,0 +1,14 @@
1
+ const InvalidIcon = props => (
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="25"
5
+ height="25"
6
+ fill="currentColor"
7
+ className="bi bi-x-circle-fill"
8
+ viewBox="0 0 16 16"
9
+ >
10
+ <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" />
11
+ </svg>
12
+ );
13
+
14
+ export default InvalidIcon;
@@ -0,0 +1,14 @@
1
+ import '../../styles/loading.css';
2
+
3
+ export default function LoadingIcon() {
4
+ return (
5
+ <center>
6
+ <div class="lds-ring">
7
+ <div></div>
8
+ <div></div>
9
+ <div></div>
10
+ <div></div>
11
+ </div>
12
+ </center>
13
+ );
14
+ }
@@ -0,0 +1,52 @@
1
+ const PythonIcon = props => (
2
+ <svg
3
+ width="45px"
4
+ height="45px"
5
+ viewBox="0 0 64 64"
6
+ fill="none"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ >
9
+ <g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
10
+ <g
11
+ id="SVGRepo_tracerCarrier"
12
+ strokeLinecap="round"
13
+ strokeLinejoin="round"
14
+ ></g>
15
+ <g id="SVGRepo_iconCarrier">
16
+ <path
17
+ d="M31.885 16c-8.124 0-7.617 3.523-7.617 3.523l.01 3.65h7.752v1.095H21.197S16 23.678 16 31.876c0 8.196 4.537 7.906 4.537 7.906h2.708v-3.804s-.146-4.537 4.465-4.537h7.688s4.32.07 4.32-4.175v-7.019S40.374 16 31.885 16zm-4.275 2.454c.771 0 1.395.624 1.395 1.395s-.624 1.395-1.395 1.395a1.393 1.393 0 0 1-1.395-1.395c0-.771.624-1.395 1.395-1.395z"
18
+ fill="url(#a)"
19
+ ></path>
20
+ <path
21
+ d="M32.115 47.833c8.124 0 7.617-3.523 7.617-3.523l-.01-3.65H31.97v-1.095h10.832S48 40.155 48 31.958c0-8.197-4.537-7.906-4.537-7.906h-2.708v3.803s.146 4.537-4.465 4.537h-7.688s-4.32-.07-4.32 4.175v7.019s-.656 4.247 7.833 4.247zm4.275-2.454a1.393 1.393 0 0 1-1.395-1.395c0-.77.624-1.394 1.395-1.394s1.395.623 1.395 1.394c0 .772-.624 1.395-1.395 1.395z"
22
+ fill="url(#b)"
23
+ ></path>
24
+ <defs>
25
+ <linearGradient
26
+ id="a"
27
+ x1="19.075"
28
+ y1="18.782"
29
+ x2="34.898"
30
+ y2="34.658"
31
+ gradientUnits="userSpaceOnUse"
32
+ >
33
+ <stop stopColor="#387EB8"></stop>
34
+ <stop offset="1" stopColor="#366994"></stop>
35
+ </linearGradient>
36
+ <linearGradient
37
+ id="b"
38
+ x1="28.809"
39
+ y1="28.882"
40
+ x2="45.803"
41
+ y2="45.163"
42
+ gradientUnits="userSpaceOnUse"
43
+ >
44
+ <stop stopColor="#FFE052"></stop>
45
+ <stop offset="1" stopColor="#FFC331"></stop>
46
+ </linearGradient>
47
+ </defs>
48
+ </g>
49
+ </svg>
50
+ );
51
+
52
+ export default PythonIcon;
@@ -0,0 +1,14 @@
1
+ const TableIcon = props => (
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="16"
5
+ height="16"
6
+ fill="currentColor"
7
+ className="bi bi-table"
8
+ viewBox="0 0 16 16"
9
+ >
10
+ <path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z" />
11
+ </svg>
12
+ );
13
+
14
+ export default TableIcon;
@@ -0,0 +1,14 @@
1
+ const ValidIcon = props => (
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="25"
5
+ height="25"
6
+ fill="currentColor"
7
+ className="bi bi-check-circle-fill"
8
+ viewBox="0 0 16 16"
9
+ >
10
+ <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" />
11
+ </svg>
12
+ );
13
+
14
+ export default ValidIcon;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * This component is the skeleton around the actual pages, and only contains
3
+ * components that should be seen on all pages, like the logo or navigation bar.
4
+ */
5
+
6
+ import * as React from 'react';
7
+ import { Helmet } from 'react-helmet-async';
8
+ import { BrowserRouter, Routes, Route } from 'react-router-dom';
9
+
10
+ import { NamespacePage } from './pages/NamespacePage/Loadable';
11
+ import { NodePage } from './pages/NodePage/Loadable';
12
+ import { SQLBuilderPage } from './pages/SQLBuilderPage/Loadable';
13
+ import { TagPage } from './pages/TagPage/Loadable';
14
+ import { AddEditNodePage } from './pages/AddEditNodePage/Loadable';
15
+ import { AddEditTagPage } from './pages/AddEditTagPage/Loadable';
16
+ import { NotFoundPage } from './pages/NotFoundPage/Loadable';
17
+ import { LoginPage } from './pages/LoginPage';
18
+ import { RegisterTablePage } from './pages/RegisterTablePage';
19
+ import { Root } from './pages/Root/Loadable';
20
+ import DJClientContext from './providers/djclient';
21
+ import { DataJunctionAPI } from './services/DJService';
22
+ import { CookiesProvider, useCookies } from 'react-cookie';
23
+ import * as Constants from './constants';
24
+
25
+ export function App() {
26
+ const [cookies] = useCookies([Constants.LOGGED_IN_FLAG_COOKIE]);
27
+ return (
28
+ <CookiesProvider>
29
+ <BrowserRouter>
30
+ {cookies.__djlif || process.env.REACT_DISABLE_AUTH === 'true' ? (
31
+ <>
32
+ <Helmet
33
+ titleTemplate="DataJunction: %s"
34
+ defaultTitle="DataJunction: A Metrics Platform"
35
+ >
36
+ <meta
37
+ name="description"
38
+ content="DataJunction serves as a semantic layer to help manage metrics"
39
+ />
40
+ </Helmet>
41
+ <DJClientContext.Provider value={{ DataJunctionAPI }}>
42
+ <Routes>
43
+ <Route
44
+ path="/"
45
+ element={<Root />}
46
+ children={
47
+ <>
48
+ <Route path="nodes" key="nodes">
49
+ <Route path=":name" element={<NodePage />} />
50
+ <Route
51
+ path=":name/edit"
52
+ key="edit"
53
+ element={<AddEditNodePage />}
54
+ />
55
+ </Route>
56
+
57
+ <Route path="/" element={<NamespacePage />} key="index" />
58
+ <Route path="namespaces">
59
+ <Route
60
+ path=":namespace"
61
+ element={<NamespacePage />}
62
+ key="namespaces"
63
+ />
64
+ </Route>
65
+ <Route
66
+ path="create/tag"
67
+ key="createtag"
68
+ element={<AddEditTagPage />}
69
+ ></Route>
70
+ <Route
71
+ path="create/source"
72
+ key="register"
73
+ element={<RegisterTablePage />}
74
+ ></Route>
75
+ <Route path="create/:nodeType">
76
+ <Route
77
+ path=":initialNamespace"
78
+ key="create"
79
+ element={<AddEditNodePage />}
80
+ />
81
+ <Route
82
+ path=""
83
+ key="create"
84
+ element={<AddEditNodePage />}
85
+ />
86
+ </Route>
87
+ <Route
88
+ path="sql"
89
+ key="sql"
90
+ element={<SQLBuilderPage />}
91
+ />
92
+ <Route path="tags" key="tags">
93
+ <Route path=":name" element={<TagPage />} />
94
+ </Route>
95
+ </>
96
+ }
97
+ />
98
+ <Route path="*" element={<NotFoundPage />} />
99
+ </Routes>
100
+ </DJClientContext.Provider>
101
+ </>
102
+ ) : (
103
+ <LoginPage />
104
+ )}
105
+ </BrowserRouter>
106
+ </CookiesProvider>
107
+ );
108
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * A React Select component for use in Formik forms.
3
+ */
4
+ import { useField } from 'formik';
5
+ import Select from 'react-select';
6
+
7
+ export const FormikSelect = ({
8
+ selectOptions,
9
+ formikFieldName,
10
+ placeholder,
11
+ defaultValue,
12
+ style,
13
+ className = 'SelectInput',
14
+ isMulti = false,
15
+ }) => {
16
+ // eslint-disable-next-line no-unused-vars
17
+ const [field, _, helpers] = useField(formikFieldName);
18
+ const { setValue } = helpers;
19
+
20
+ // handles both multi-select and single-select cases
21
+ const getValue = options => {
22
+ if (options) {
23
+ return isMulti ? options.map(option => option.value) : options.value;
24
+ } else {
25
+ return isMulti ? [] : '';
26
+ }
27
+ };
28
+
29
+ return (
30
+ <Select
31
+ className={className}
32
+ defaultValue={defaultValue}
33
+ options={selectOptions}
34
+ name={field.name}
35
+ placeholder={placeholder}
36
+ onBlur={field.onBlur}
37
+ onChange={selected => setValue(getValue(selected))}
38
+ styles={style}
39
+ isMulti={isMulti}
40
+ />
41
+ );
42
+ };
43
+
44
+ FormikSelect.defaultProps = {
45
+ placeholder: '',
46
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * A field for the full node name, which is generated based on the node's input
3
+ * namespace and display name.
4
+ */
5
+ import { useField, useFormikContext } from 'formik';
6
+ import { useEffect } from 'react';
7
+
8
+ export const FullNameField = props => {
9
+ const { values, setFieldValue } = useFormikContext();
10
+ const [field, meta] = useField(props);
11
+
12
+ useEffect(() => {
13
+ // Set the value of the node's full name based on its namespace and display name
14
+ if (values.namespace && values.display_name) {
15
+ setFieldValue(
16
+ props.name,
17
+ `${values.namespace}.${values.display_name
18
+ .toLowerCase()
19
+ .replace(/ /g, '_')}` || '',
20
+ );
21
+ }
22
+ }, [setFieldValue, props.name, values]);
23
+
24
+ return (
25
+ <>
26
+ <input
27
+ {...props}
28
+ {...field}
29
+ className="FullNameField"
30
+ disabled="disabled"
31
+ id="FullName"
32
+ value={values.name || ''}
33
+ />
34
+ {!!meta.touched && !!meta.error && <div>{meta.error}</div>}
35
+ </>
36
+ );
37
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Asynchronously loads the component for the Node page
3
+ */
4
+
5
+ import * as React from 'react';
6
+ import { lazyLoad } from '../../../utils/loadable';
7
+
8
+ export const AddEditNodePage = () => {
9
+ return lazyLoad(
10
+ () => import('./index'),
11
+ module => module.AddEditNodePage,
12
+ {
13
+ fallback: <div></div>,
14
+ },
15
+ )();
16
+ };
@@ -0,0 +1,89 @@
1
+ /**
2
+ * SQL query input field, which consists of a CodeMirror SQL editor with autocompletion
3
+ * (for node names and columns) and syntax highlighting.
4
+ */
5
+ import React from 'react';
6
+ import { Field, useFormikContext } from 'formik';
7
+ import CodeMirror from '@uiw/react-codemirror';
8
+ import { langs } from '@uiw/codemirror-extensions-langs';
9
+
10
+ export const NodeQueryField = ({ djClient, value }) => {
11
+ const [schema, setSchema] = React.useState([]);
12
+ const formik = useFormikContext();
13
+ const sqlExt = langs.sql({ schema: schema });
14
+
15
+ const initialAutocomplete = async context => {
16
+ // Based on the parsed prefix, we load node names with that prefix
17
+ // into the autocomplete schema. At this stage we don't load the columns
18
+ // to save on unnecessary calls
19
+ const word = context.matchBefore(/[\.\w]*/);
20
+ const matches = await djClient.nodes(word.text);
21
+ matches.forEach(nodeName => {
22
+ if (schema[nodeName] === undefined) {
23
+ schema[nodeName] = [];
24
+ setSchema(schema);
25
+ }
26
+ });
27
+ };
28
+
29
+ const updateFormik = val => {
30
+ formik.setFieldValue('query', val);
31
+ };
32
+
33
+ const updateAutocomplete = async (value, _) => {
34
+ // If a particular node has been chosen, load the columns of that node into
35
+ // the autocomplete schema for column-level autocompletion
36
+ for (var nodeName in schema) {
37
+ if (
38
+ value.includes(nodeName) &&
39
+ (!schema.hasOwnProperty(nodeName) ||
40
+ (schema.hasOwnProperty(nodeName) && schema[nodeName].length === 0))
41
+ ) {
42
+ const nodeDetails = await djClient.node(nodeName);
43
+ schema[nodeName] = nodeDetails.columns.map(col => col.name);
44
+ setSchema(schema);
45
+ }
46
+ }
47
+ };
48
+
49
+ return (
50
+ <>
51
+ <Field
52
+ type="textarea"
53
+ style={{ display: 'none' }}
54
+ as="textarea"
55
+ name="query"
56
+ id="Query"
57
+ />
58
+ <div role="button" tabIndex={0} className="relative flex bg-[#282a36]">
59
+ <CodeMirror
60
+ id={'query'}
61
+ name={'query'}
62
+ extensions={[
63
+ sqlExt,
64
+ sqlExt.language.data.of({
65
+ autocomplete: initialAutocomplete,
66
+ }),
67
+ ]}
68
+ value={value}
69
+ options={{
70
+ theme: 'default',
71
+ lineNumbers: true,
72
+ }}
73
+ width="100%"
74
+ height="400px"
75
+ style={{
76
+ margin: '0 0 23px 0',
77
+ flex: 1,
78
+ fontSize: '150%',
79
+ textAlign: 'left',
80
+ }}
81
+ onChange={(value, viewUpdate) => {
82
+ updateFormik(value);
83
+ updateAutocomplete(value, viewUpdate);
84
+ }}
85
+ />
86
+ </div>
87
+ </>
88
+ );
89
+ };
@@ -0,0 +1,103 @@
1
+ import React from 'react';
2
+ import { fireEvent, screen, waitFor } from '@testing-library/react';
3
+ import fetchMock from 'jest-fetch-mock';
4
+ import userEvent from '@testing-library/user-event';
5
+ import {
6
+ initializeMockDJClient,
7
+ renderCreateNode,
8
+ renderEditNode,
9
+ testElement,
10
+ } from './index.test';
11
+ import { mocks } from '../../../../mocks/mockNodes';
12
+
13
+ describe('AddEditNodePage submission failed', () => {
14
+ beforeEach(() => {
15
+ fetchMock.resetMocks();
16
+ jest.clearAllMocks();
17
+ window.scrollTo = jest.fn();
18
+ });
19
+
20
+ it('for creating a node', async () => {
21
+ const mockDjClient = initializeMockDJClient();
22
+ mockDjClient.DataJunctionAPI.createNode.mockReturnValue({
23
+ status: 500,
24
+ json: { message: 'Some columns in the primary key [] were not found' },
25
+ });
26
+ mockDjClient.DataJunctionAPI.listTags.mockReturnValue([
27
+ { name: 'purpose', display_name: 'Purpose' },
28
+ { name: 'intent', display_name: 'Intent' },
29
+ ]);
30
+
31
+ const element = testElement(mockDjClient);
32
+ const { container } = renderCreateNode(element);
33
+
34
+ await userEvent.type(
35
+ screen.getByLabelText('Display Name'),
36
+ 'Some Test Metric',
37
+ );
38
+ await userEvent.type(screen.getByLabelText('Query'), 'SELECT * FROM test');
39
+ await userEvent.click(screen.getByText('Create dimension'));
40
+
41
+ await waitFor(() => {
42
+ expect(mockDjClient.DataJunctionAPI.createNode).toBeCalled();
43
+ expect(mockDjClient.DataJunctionAPI.createNode).toBeCalledWith(
44
+ 'dimension',
45
+ 'default.some_test_metric',
46
+ 'Some Test Metric',
47
+ '',
48
+ 'SELECT * FROM test',
49
+ 'draft',
50
+ 'default',
51
+ null,
52
+ );
53
+ expect(
54
+ screen.getByText(/Some columns in the primary key \[] were not found/),
55
+ ).toBeInTheDocument();
56
+ });
57
+
58
+ // After failed creation, it should return a failure message
59
+ expect(container.getElementsByClassName('alert')).toMatchSnapshot();
60
+ }, 60000);
61
+
62
+ it('for editing a node', async () => {
63
+ const mockDjClient = initializeMockDJClient();
64
+ mockDjClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
65
+ mockDjClient.DataJunctionAPI.patchNode.mockReturnValue({
66
+ status: 500,
67
+ json: { message: 'Update failed' },
68
+ });
69
+
70
+ mockDjClient.DataJunctionAPI.tagsNode.mockReturnValue({
71
+ status: 404,
72
+ json: { message: 'Some tags were not found' },
73
+ });
74
+
75
+ mockDjClient.DataJunctionAPI.listTags.mockReturnValue([
76
+ { name: 'purpose', display_name: 'Purpose' },
77
+ { name: 'intent', display_name: 'Intent' },
78
+ ]);
79
+
80
+ const element = testElement(mockDjClient);
81
+ renderEditNode(element);
82
+
83
+ await userEvent.type(screen.getByLabelText('Display Name'), '!!!');
84
+ await userEvent.type(screen.getByLabelText('Description'), '!!!');
85
+ await userEvent.click(screen.getByText('Save'));
86
+ await waitFor(async () => {
87
+ expect(mockDjClient.DataJunctionAPI.patchNode).toBeCalledTimes(1);
88
+ expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalled();
89
+ expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalledWith(
90
+ 'default.num_repair_orders',
91
+ [{ display_name: 'Purpose', name: 'purpose' }],
92
+ );
93
+ expect(mockDjClient.DataJunctionAPI.tagsNode).toReturnWith({
94
+ json: { message: 'Some tags were not found' },
95
+ status: 404,
96
+ });
97
+
98
+ expect(
99
+ await screen.getByText('Update failed, Some tags were not found'),
100
+ ).toBeInTheDocument();
101
+ });
102
+ }, 60000);
103
+ });