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,151 @@
1
+ import { useContext, useEffect, useRef, useState } from 'react';
2
+ import * as React from 'react';
3
+ import DJClientContext from '../../providers/djclient';
4
+ import { Field, Form, Formik } from 'formik';
5
+ import { FormikSelect } from '../AddEditNodePage/FormikSelect';
6
+ import EditIcon from '../../icons/EditIcon';
7
+ import { displayMessageAfterSubmit, labelize } from '../../../utils/form';
8
+
9
+ export default function PartitionColumnPopover({ column, node, onSubmit }) {
10
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
11
+ const [popoverAnchor, setPopoverAnchor] = useState(false);
12
+ const ref = useRef(null);
13
+
14
+ useEffect(() => {
15
+ const handleClickOutside = event => {
16
+ if (ref.current && !ref.current.contains(event.target)) {
17
+ setPopoverAnchor(false);
18
+ }
19
+ };
20
+ document.addEventListener('click', handleClickOutside, true);
21
+ return () => {
22
+ document.removeEventListener('click', handleClickOutside, true);
23
+ };
24
+ }, [setPopoverAnchor]);
25
+
26
+ const savePartition = async (
27
+ { node, column, partition_type, format, granularity },
28
+ { setSubmitting, setStatus },
29
+ ) => {
30
+ setSubmitting(false);
31
+ const response = await djClient.setPartition(
32
+ node,
33
+ column,
34
+ partition_type,
35
+ format,
36
+ granularity,
37
+ );
38
+ if (response.status === 200 || response.status === 201) {
39
+ setStatus({ success: 'Saved!' });
40
+ } else {
41
+ setStatus({
42
+ failure: `${response.json.message}`,
43
+ });
44
+ }
45
+ onSubmit();
46
+ // window.location.reload();
47
+ };
48
+
49
+ return (
50
+ <>
51
+ <button
52
+ className="edit_button"
53
+ aria-label="PartitionColumn"
54
+ tabIndex="0"
55
+ onClick={() => {
56
+ setPopoverAnchor(!popoverAnchor);
57
+ }}
58
+ >
59
+ <EditIcon />
60
+ </button>
61
+ <div
62
+ className="popover"
63
+ role="dialog"
64
+ aria-label="client-code"
65
+ style={{ display: popoverAnchor === false ? 'none' : 'block' }}
66
+ ref={ref}
67
+ >
68
+ <Formik
69
+ initialValues={{
70
+ column: column.name,
71
+ node: node.name,
72
+ partition_type: '',
73
+ format: 'yyyyMMdd',
74
+ granularity: 'day',
75
+ }}
76
+ onSubmit={savePartition}
77
+ >
78
+ {function Render({ values, isSubmitting, status, setFieldValue }) {
79
+ return (
80
+ <Form>
81
+ {displayMessageAfterSubmit(status)}
82
+ <span data-testid="edit-partition">
83
+ <label htmlFor="partitionType">Partition Type</label>
84
+ <Field
85
+ as="select"
86
+ name="partition_type"
87
+ id="partitionType"
88
+ placeholder="Partition Type"
89
+ >
90
+ <option value=""></option>
91
+ <option value="temporal">Temporal</option>
92
+ <option value="categorical">Categorical</option>
93
+ </Field>
94
+ </span>
95
+ <input
96
+ hidden={true}
97
+ name="column"
98
+ value={column.name}
99
+ readOnly={true}
100
+ />
101
+ <input
102
+ hidden={true}
103
+ name="node"
104
+ value={node.name}
105
+ readOnly={true}
106
+ />
107
+ <br />
108
+ <br />
109
+ {values.partition_type === 'temporal' ? (
110
+ <>
111
+ <label htmlFor="partitionFormat">Partition Format</label>
112
+ <Field
113
+ type="text"
114
+ name="format"
115
+ id="partitionFormat"
116
+ placeholder="Optional temporal partition format (ex: yyyyMMdd)"
117
+ />
118
+ <br />
119
+ <br />
120
+ <label htmlFor="partitionGranularity">
121
+ Partition Granularity
122
+ </label>
123
+ <Field
124
+ as="select"
125
+ name="granularity"
126
+ id="partitionGranularity"
127
+ placeholder="Granularity"
128
+ >
129
+ <option value="day">Day</option>
130
+ <option value="hour">Hour</option>
131
+ </Field>
132
+ </>
133
+ ) : (
134
+ ''
135
+ )}
136
+ <button
137
+ className="add_node"
138
+ type="submit"
139
+ aria-label="SaveEditColumn"
140
+ aria-hidden="false"
141
+ >
142
+ Save
143
+ </button>
144
+ </Form>
145
+ );
146
+ }}
147
+ </Formik>
148
+ </div>
149
+ </>
150
+ );
151
+ }
@@ -0,0 +1,60 @@
1
+ import * as React from 'react';
2
+ import { Field } from 'formik';
3
+
4
+ export default function PartitionValueForm({ col, materialization }) {
5
+ if (col.partition.type_ === 'temporal') {
6
+ return (
7
+ <>
8
+ <div
9
+ className="partition__full"
10
+ key={col.name}
11
+ style={{ width: '50%' }}
12
+ >
13
+ <div className="partition__header">{col.display_name}</div>
14
+ <div className="partition__body">
15
+ <span style={{ padding: '0.5rem' }}>From</span>{' '}
16
+ <Field
17
+ type="text"
18
+ name={`partitionValues.['${col.name}'].from`}
19
+ id={`${col.name}.from`}
20
+ placeholder="20230101"
21
+ default="20230101"
22
+ style={{ width: '7rem', paddingRight: '1rem' }}
23
+ />{' '}
24
+ <span style={{ padding: '0.5rem' }}>To</span>
25
+ <Field
26
+ type="text"
27
+ name={`partitionValues.['${col.name}'].to`}
28
+ id={`${col.name}.to`}
29
+ placeholder="20230102"
30
+ default="20230102"
31
+ style={{ width: '7rem' }}
32
+ />
33
+ </div>
34
+ </div>
35
+ </>
36
+ );
37
+ } else {
38
+ return (
39
+ <>
40
+ <div
41
+ className="partition__full"
42
+ key={col.name}
43
+ style={{ width: '50%' }}
44
+ >
45
+ <div className="partition__header">{col.display_name}</div>
46
+ <div className="partition__body">
47
+ <Field
48
+ type="text"
49
+ name={`partitionValues.['${col.name}']`}
50
+ id={col.name}
51
+ placeholder=""
52
+ default=""
53
+ style={{ width: '7rem', paddingRight: '1rem' }}
54
+ />
55
+ </div>
56
+ </div>
57
+ </>
58
+ );
59
+ }
60
+ }
@@ -0,0 +1,209 @@
1
+ import { useContext, useEffect, useState } from 'react';
2
+ import * as React from 'react';
3
+ import { diffLines, formatLines } from 'unidiff';
4
+ import { parseDiff, Diff, Hunk } from 'react-diff-view';
5
+
6
+ import { useParams } from 'react-router-dom';
7
+ import DJClientContext from '../../providers/djclient';
8
+ import NamespaceHeader from '../../components/NamespaceHeader';
9
+ import { labelize } from '../../../utils/form';
10
+ import DiffIcon from '../../icons/DiffIcon';
11
+ import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
12
+ import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
13
+ import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql';
14
+
15
+ SyntaxHighlighter.registerLanguage('sql', sql);
16
+ foundation.hljs['padding'] = '2rem';
17
+
18
+ export default function RevisionDiff() {
19
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
20
+ const [revisions, setRevisions] = useState([]);
21
+
22
+ const { name, revision } = useParams();
23
+
24
+ useEffect(() => {
25
+ const fetchData = async () => {
26
+ const revisions = await djClient.revisions(name);
27
+ setRevisions(revisions);
28
+ };
29
+ fetchData().catch(console.error);
30
+ }, [djClient, name]);
31
+
32
+ const thisRevision = revisions
33
+ .map((rev, idx) => [idx, rev])
34
+ .filter((rev, idx) => {
35
+ return rev[1].version === revision;
36
+ });
37
+ const prevRevision = revisions.filter(
38
+ (rev, idx) => idx + 1 === thisRevision[0][0],
39
+ );
40
+
41
+ const EMPTY_HUNKS = [];
42
+
43
+ const revisionDiff = (older, newer) => {
44
+ const diffObj = {};
45
+ const fields = [
46
+ 'display_name',
47
+ 'version',
48
+ 'query',
49
+ 'mode',
50
+ 'status',
51
+ 'description',
52
+ 'columns',
53
+ // 'catalog',
54
+ 'schema',
55
+ 'table',
56
+ 'updated_at',
57
+ ];
58
+ if (older) {
59
+ for (const key of fields) {
60
+ if (older[key] && (key !== 'columns' || older.type === 'cube')) {
61
+ diffObj[key] = {};
62
+ diffObj[key].diffText = formatLines(
63
+ diffLines(
64
+ older
65
+ ? key === 'columns'
66
+ ? older[key].map(col => col.name).join('\n')
67
+ : older[key].toString()
68
+ : '',
69
+ newer
70
+ ? key === 'columns'
71
+ ? newer[key].map(col => col.name).join('\n')
72
+ : newer[key].toString()
73
+ : '',
74
+ ),
75
+ {
76
+ context: 5000,
77
+ },
78
+ );
79
+ const [diff] = parseDiff(diffObj[key].diffText, {
80
+ nearbySequences: 'zip',
81
+ });
82
+ diffObj[key].diff = diff;
83
+ }
84
+ }
85
+ }
86
+ return diffObj;
87
+ };
88
+
89
+ const diffObjects = revisionDiff(
90
+ prevRevision[0],
91
+ thisRevision[0] ? thisRevision[0][1] : thisRevision[0],
92
+ );
93
+
94
+ return (
95
+ <div className="node__header">
96
+ <NamespaceHeader
97
+ namespace={(prevRevision[0] ? prevRevision[0].name : '')
98
+ .split('.')
99
+ .slice(0, -1)
100
+ .join('.')}
101
+ />
102
+ <div className="card">
103
+ <div className="card-header">
104
+ <h3
105
+ className="card-title align-items-start flex-column"
106
+ style={{ display: 'inline-block' }}
107
+ >
108
+ <span
109
+ className="card-label fw-bold text-gray-800"
110
+ role="dialog"
111
+ aria-hidden="false"
112
+ aria-label="DisplayName"
113
+ >
114
+ <a
115
+ href={'/nodes/' + prevRevision[0]?.name}
116
+ className=""
117
+ role="dialog"
118
+ aria-hidden="false"
119
+ aria-label="NodeName"
120
+ >
121
+ {prevRevision[0]?.name}
122
+ </a>{' '}
123
+ <span
124
+ className={
125
+ 'node_type__' + prevRevision[0]?.type + ' badge node_type'
126
+ }
127
+ role="dialog"
128
+ aria-hidden="false"
129
+ aria-label="NodeType"
130
+ >
131
+ {prevRevision[0]?.type}
132
+ </span>
133
+ </span>
134
+ </h3>
135
+ <div>
136
+ <span
137
+ className="rounded-pill badge version"
138
+ style={{ marginLeft: '0.5rem', fontSize: '16px' }}
139
+ >
140
+ {prevRevision[0]?.version}
141
+ </span>
142
+ <DiffIcon />
143
+ <span
144
+ className="rounded-pill badge version"
145
+ style={{ marginLeft: '0.3rem', fontSize: '16px' }}
146
+ >
147
+ {thisRevision[0] ? thisRevision[0][1].version : ''}
148
+ </span>{' '}
149
+ </div>
150
+ {Object.keys(diffObjects).map(field => {
151
+ return (
152
+ <div className="diff" aria-label={'DiffView'} role={'gridcell'}>
153
+ <h4>
154
+ {labelize(field)}{' '}
155
+ <small className="no-change-banner">
156
+ {diffObjects[field]?.diff.hunks.length > 0
157
+ ? ''
158
+ : 'no change'}
159
+ </small>
160
+ </h4>
161
+ {diffObjects[field]?.diff.hunks.length > 0 ? (
162
+ <Diff
163
+ viewType="split"
164
+ diffType=""
165
+ hunks={diffObjects[field]?.diff.hunks || EMPTY_HUNKS}
166
+ tokens={diffObjects[field]?.tokens}
167
+ >
168
+ {hunks =>
169
+ hunks.map(hunk => <Hunk key={hunk.content} hunk={hunk} />)
170
+ }
171
+ </Diff>
172
+ ) : (
173
+ <div className="no-change">
174
+ {prevRevision[0] ? (
175
+ field === 'query' ? (
176
+ <>
177
+ <SyntaxHighlighter
178
+ language="sql"
179
+ style={foundation}
180
+ wrapLongLines={true}
181
+ >
182
+ {prevRevision[0].query}
183
+ </SyntaxHighlighter>
184
+ </>
185
+ ) : field === 'columns' ? (
186
+ <div>
187
+ {prevRevision[0][field].map(col => (
188
+ <>
189
+ {col.name}
190
+ <br />
191
+ </>
192
+ ))}
193
+ </div>
194
+ ) : (
195
+ prevRevision[0][field].toString()
196
+ )
197
+ ) : (
198
+ ''
199
+ )}
200
+ </div>
201
+ )}
202
+ </div>
203
+ );
204
+ })}
205
+ </div>
206
+ </div>
207
+ </div>
208
+ );
209
+ }
@@ -0,0 +1,226 @@
1
+ import { useContext, useState, useRef, useEffect } from 'react';
2
+ import DJClientContext from '../../providers/djclient';
3
+ import EyeIcon from '../../icons/EyeIcon';
4
+ import ExpandedIcon from '../../icons/ExpandedIcon';
5
+ import CollapsedIcon from '../../icons/CollapsedIcon';
6
+
7
+ const EVENT_TYPES = ['delete', 'update'];
8
+
9
+ export default function WatchButton({ node }) {
10
+ if (!node || !node.name || !node.type) {
11
+ return null;
12
+ }
13
+
14
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
15
+ const [selectedEvents, setSelectedEvents] = useState([]);
16
+ const [loading, setLoading] = useState(false);
17
+ const [dropdownOpen, setDropdownOpen] = useState(false);
18
+ const dropdownRef = useRef();
19
+
20
+ // Load existing preferences
21
+ useEffect(() => {
22
+ const loadPreferences = async () => {
23
+ try {
24
+ const preferences = await djClient.getNotificationPreferences({
25
+ entity_name: node.name,
26
+ });
27
+
28
+ const matched = preferences.find(item =>
29
+ item.alert_types.includes('web'),
30
+ );
31
+
32
+ if (matched) {
33
+ setSelectedEvents(matched.activity_types);
34
+ }
35
+ } catch (err) {
36
+ console.error('Failed to load notification preferences', err);
37
+ }
38
+ };
39
+
40
+ loadPreferences();
41
+ }, [djClient, node.name, node.type]);
42
+
43
+ // Close dropdown on outside click
44
+ useEffect(() => {
45
+ const handleClickOutside = event => {
46
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
47
+ setDropdownOpen(false);
48
+ }
49
+ };
50
+
51
+ document.addEventListener('mousedown', handleClickOutside);
52
+ return () => document.removeEventListener('mousedown', handleClickOutside);
53
+ }, []);
54
+ const toggleEvent = async event => {
55
+ const isSelected = selectedEvents.includes(event);
56
+
57
+ try {
58
+ setLoading(true);
59
+ let updatedEvents;
60
+
61
+ if (!isSelected) {
62
+ updatedEvents = Array.from(new Set([...selectedEvents, event]));
63
+ } else {
64
+ updatedEvents = selectedEvents.filter(e => e !== event);
65
+ }
66
+
67
+ if (updatedEvents.length === 0) {
68
+ await djClient.unsubscribeFromNotifications({
69
+ entity_type: 'node',
70
+ entity_name: node.name,
71
+ });
72
+ } else {
73
+ await djClient.subscribeToNotifications({
74
+ entity_type: 'node',
75
+ entity_name: node.name,
76
+ activity_types: updatedEvents,
77
+ alert_types: ['web'],
78
+ });
79
+ }
80
+
81
+ setSelectedEvents(updatedEvents);
82
+ } catch (err) {
83
+ console.error('Failed to update preference', err);
84
+ } finally {
85
+ setLoading(false);
86
+ }
87
+ };
88
+
89
+ const handleWatchClick = async () => {
90
+ try {
91
+ setLoading(true);
92
+ if (selectedEvents.length === 0) {
93
+ await djClient.subscribeToNotifications({
94
+ entity_type: 'node',
95
+ entity_name: node.name,
96
+ activity_types: EVENT_TYPES,
97
+ alert_types: ['web'],
98
+ });
99
+ setSelectedEvents(EVENT_TYPES);
100
+ } else {
101
+ await djClient.unsubscribeFromNotifications({
102
+ entity_type: 'node',
103
+ entity_name: node.name,
104
+ });
105
+ setSelectedEvents([]);
106
+ }
107
+ } catch (err) {
108
+ console.error('Watch toggle failed', err);
109
+ } finally {
110
+ setLoading(false);
111
+ }
112
+ };
113
+
114
+ return (
115
+ <div
116
+ className="btn-group"
117
+ ref={dropdownRef}
118
+ style={{
119
+ position: 'relative',
120
+ display: 'inline-flex',
121
+ verticalAlign: 'middle',
122
+ }}
123
+ >
124
+ <button
125
+ className="button-3"
126
+ onClick={handleWatchClick}
127
+ disabled={loading}
128
+ style={{
129
+ borderTopRightRadius: 0,
130
+ borderBottomRightRadius: 0,
131
+ marginRight: 0,
132
+ height: '2.5rem',
133
+ display: 'flex',
134
+ alignItems: 'center',
135
+ gap: '6px',
136
+ }}
137
+ >
138
+ <EyeIcon />
139
+ Watch
140
+ {selectedEvents.length > 0 && (
141
+ <span
142
+ style={{
143
+ backgroundColor: '#e2e6ed',
144
+ color: '#333',
145
+ padding: '2px 6px',
146
+ borderRadius: '999px',
147
+ fontSize: '0.75rem',
148
+ fontWeight: 500,
149
+ lineHeight: 1,
150
+ }}
151
+ >
152
+ {selectedEvents.length}
153
+ </span>
154
+ )}
155
+ </button>
156
+
157
+ <button
158
+ className="button-3"
159
+ onClick={() => setDropdownOpen(prev => !prev)}
160
+ disabled={loading}
161
+ style={{
162
+ borderTopLeftRadius: 0,
163
+ borderBottomLeftRadius: 0,
164
+ marginLeft: 0,
165
+ height: '2.5rem',
166
+ }}
167
+ aria-label="Toggle dropdown"
168
+ >
169
+ {dropdownOpen ? <ExpandedIcon /> : <CollapsedIcon />}
170
+ </button>
171
+
172
+ {dropdownOpen && (
173
+ <ul
174
+ className="p-2"
175
+ style={{
176
+ display: 'block',
177
+ minWidth: '220px',
178
+ position: 'absolute',
179
+ top: '100%',
180
+ right: 0,
181
+ zIndex: 9999,
182
+ backgroundColor: '#fff',
183
+ border: '1px solid #ccc',
184
+ borderRadius: '0.5rem',
185
+ marginTop: '0.25rem',
186
+ boxShadow: '0px 6px 16px rgba(0, 0, 0, 0.1)',
187
+ padding: '0.5rem 0',
188
+ }}
189
+ >
190
+ {EVENT_TYPES.map(event => {
191
+ const isSelected = selectedEvents.includes(event);
192
+ return (
193
+ <li
194
+ key={event}
195
+ onClick={() => toggleEvent(event)}
196
+ style={{
197
+ display: 'flex',
198
+ alignItems: 'center',
199
+ padding: '0.5rem 1rem',
200
+ cursor: 'pointer',
201
+ backgroundColor: isSelected ? '#f0f4f8' : 'transparent',
202
+ fontWeight: isSelected ? '600' : '400',
203
+ fontSize: '0.9rem',
204
+ color: '#333',
205
+ borderLeft: isSelected
206
+ ? '4px solid #7983ff'
207
+ : '4px solid transparent',
208
+ transition: 'background 0.2s',
209
+ }}
210
+ >
211
+ <input
212
+ className="form-check-input"
213
+ type="checkbox"
214
+ checked={isSelected}
215
+ readOnly
216
+ style={{ marginRight: '0.75rem' }}
217
+ />
218
+ {event}
219
+ </li>
220
+ );
221
+ })}
222
+ </ul>
223
+ )}
224
+ </div>
225
+ );
226
+ }
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import { render, fireEvent, waitFor } from '@testing-library/react';
3
+ import DJClientContext from '../../../providers/djclient';
4
+ import AddBackfillPopover from '../AddBackfillPopover';
5
+ import { mocks } from '../../../../mocks/mockNodes';
6
+
7
+ const mockDjClient = {
8
+ DataJunctionAPI: {
9
+ runBackfill: jest.fn(),
10
+ },
11
+ };
12
+
13
+ let reloadMock = jest.fn();
14
+
15
+ beforeEach(() => {
16
+ delete window.location;
17
+ window.location = { reload: reloadMock };
18
+ });
19
+
20
+ afterEach(() => {
21
+ reloadMock.mockClear();
22
+ });
23
+
24
+ describe('<AddBackfillPopover />', () => {
25
+ it('renders correctly and handles form submission', async () => {
26
+ // Mock onSubmit function
27
+ const onSubmitMock = jest.fn();
28
+
29
+ mockDjClient.DataJunctionAPI.runBackfill.mockReturnValue({
30
+ status: 201,
31
+ json: { message: '' },
32
+ });
33
+
34
+ // Render the component
35
+ const { getByLabelText, getByText } = render(
36
+ <DJClientContext.Provider value={mockDjClient}>
37
+ <AddBackfillPopover
38
+ node={mocks.mockTransformNode}
39
+ materialization={mocks.nodeMaterializations}
40
+ onSubmit={onSubmitMock}
41
+ />
42
+ </DJClientContext.Provider>,
43
+ );
44
+
45
+ // Open the popover
46
+ fireEvent.click(getByLabelText('AddBackfill'));
47
+
48
+ fireEvent.click(getByText('Save'));
49
+
50
+ // Expect setAttributes to be called
51
+ await waitFor(() => {
52
+ expect(mockDjClient.DataJunctionAPI.runBackfill).toHaveBeenCalled();
53
+ expect(getByText('Saved!')).toBeInTheDocument();
54
+ });
55
+ });
56
+ });