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,390 @@
1
+ import React from 'react';
2
+ import {
3
+ render,
4
+ fireEvent,
5
+ waitFor,
6
+ screen,
7
+ act,
8
+ } from '@testing-library/react';
9
+ import ManageDimensionLinksDialog from '../ManageDimensionLinksDialog';
10
+ import DJClientContext from '../../../providers/djclient';
11
+
12
+ // Mock window.location.reload
13
+ delete window.location;
14
+ window.location = { reload: jest.fn() };
15
+
16
+ // Mock window.confirm
17
+ window.confirm = jest.fn(() => true);
18
+
19
+ const mockDjClient = {
20
+ DataJunctionAPI: {
21
+ linkDimension: jest.fn(),
22
+ unlinkDimension: jest.fn(),
23
+ addReferenceDimensionLink: jest.fn(),
24
+ removeReferenceDimensionLink: jest.fn(),
25
+ },
26
+ node: jest.fn().mockResolvedValue({
27
+ name: 'default.test_dimension',
28
+ columns: [{ name: 'id', type: 'int' }],
29
+ }),
30
+ };
31
+
32
+ describe('<ManageDimensionLinksDialog />', () => {
33
+ beforeEach(() => {
34
+ jest.clearAllMocks();
35
+ window.alert = jest.fn();
36
+ window.confirm.mockReturnValue(true);
37
+ });
38
+
39
+ const defaultProps = {
40
+ column: { name: 'test_column', type: 'int' },
41
+ node: { name: 'default.node1' },
42
+ dimensions: [
43
+ { value: 'default.dim1', label: 'dim1 (5 links)' },
44
+ { value: 'default.dim2', label: 'dim2 (3 links)' },
45
+ ],
46
+ fkLinks: [],
47
+ onSubmit: jest.fn(),
48
+ };
49
+
50
+ it('renders the toggle button', () => {
51
+ const { getByLabelText } = render(
52
+ <DJClientContext.Provider value={mockDjClient}>
53
+ <ManageDimensionLinksDialog {...defaultProps} />
54
+ </DJClientContext.Provider>,
55
+ );
56
+
57
+ expect(getByLabelText('ManageDimensionLinksToggle')).toBeInTheDocument();
58
+ });
59
+
60
+ it('opens modal when toggle button is clicked', async () => {
61
+ const { getByLabelText, getByRole } = render(
62
+ <DJClientContext.Provider value={mockDjClient}>
63
+ <ManageDimensionLinksDialog {...defaultProps} />
64
+ </DJClientContext.Provider>,
65
+ );
66
+
67
+ fireEvent.click(getByLabelText('ManageDimensionLinksToggle'));
68
+
69
+ await waitFor(() => {
70
+ expect(
71
+ getByRole('dialog', { name: 'ManageDimensionLinksDialog' }),
72
+ ).toBeInTheDocument();
73
+ });
74
+ });
75
+
76
+ it('displays column name and type in header', async () => {
77
+ const { getByLabelText, getByText } = render(
78
+ <DJClientContext.Provider value={mockDjClient}>
79
+ <ManageDimensionLinksDialog
80
+ column={{ name: 'test_column', type: 'varchar' }}
81
+ node={defaultProps.node}
82
+ dimensions={defaultProps.dimensions}
83
+ fkLinks={[]}
84
+ onSubmit={defaultProps.onSubmit}
85
+ />
86
+ </DJClientContext.Provider>,
87
+ );
88
+
89
+ fireEvent.click(getByLabelText('ManageDimensionLinksToggle'));
90
+
91
+ await waitFor(() => {
92
+ expect(getByText('test_column')).toBeInTheDocument();
93
+ expect(getByText('varchar')).toBeInTheDocument();
94
+ });
95
+ });
96
+
97
+ it('shows two tabs: FK Links and Reference Links', async () => {
98
+ const { getByLabelText, getByText } = render(
99
+ <DJClientContext.Provider value={mockDjClient}>
100
+ <ManageDimensionLinksDialog {...defaultProps} />
101
+ </DJClientContext.Provider>,
102
+ );
103
+
104
+ fireEvent.click(getByLabelText('ManageDimensionLinksToggle'));
105
+
106
+ await waitFor(() => {
107
+ expect(getByText('FK Links')).toBeInTheDocument();
108
+ expect(getByText('Reference Links')).toBeInTheDocument();
109
+ });
110
+ });
111
+
112
+ it('switches to reference links tab when clicked', async () => {
113
+ const { getByLabelText, getByText } = render(
114
+ <DJClientContext.Provider value={mockDjClient}>
115
+ <ManageDimensionLinksDialog {...defaultProps} />
116
+ </DJClientContext.Provider>,
117
+ );
118
+
119
+ fireEvent.click(getByLabelText('ManageDimensionLinksToggle'));
120
+
121
+ await waitFor(() => {
122
+ expect(getByText('Reference Links')).toBeInTheDocument();
123
+ });
124
+
125
+ fireEvent.click(getByText('Reference Links'));
126
+
127
+ await waitFor(() => {
128
+ expect(getByText('Dimension Node *')).toBeInTheDocument();
129
+ expect(getByText('Dimension Column *')).toBeInTheDocument();
130
+ });
131
+ });
132
+
133
+ it('displays FK links when provided', async () => {
134
+ const propsWithFkLinks = {
135
+ ...defaultProps,
136
+ fkLinks: ['default.dim1', 'default.dim2'],
137
+ };
138
+
139
+ const { getByLabelText } = render(
140
+ <DJClientContext.Provider value={mockDjClient}>
141
+ <ManageDimensionLinksDialog {...propsWithFkLinks} />
142
+ </DJClientContext.Provider>,
143
+ );
144
+
145
+ fireEvent.click(getByLabelText('ManageDimensionLinksToggle'));
146
+
147
+ await waitFor(() => {
148
+ // The FK Links tab should be displayed by default
149
+ expect(getByLabelText('ManageDimensionLinksToggle')).toBeInTheDocument();
150
+ });
151
+ });
152
+
153
+ it('shows FK Links form with select dimensions field', async () => {
154
+ const { getByLabelText, getByText } = render(
155
+ <DJClientContext.Provider value={mockDjClient}>
156
+ <ManageDimensionLinksDialog {...defaultProps} />
157
+ </DJClientContext.Provider>,
158
+ );
159
+
160
+ fireEvent.click(getByLabelText('ManageDimensionLinksToggle'));
161
+
162
+ await waitFor(() => {
163
+ expect(getByText('Select Dimensions')).toBeInTheDocument();
164
+ expect(getByText('Save')).toBeInTheDocument();
165
+ });
166
+ });
167
+
168
+ it('displays reference link form fields', async () => {
169
+ const { getByLabelText, getByText, getByPlaceholderText } = render(
170
+ <DJClientContext.Provider value={mockDjClient}>
171
+ <ManageDimensionLinksDialog {...defaultProps} />
172
+ </DJClientContext.Provider>,
173
+ );
174
+
175
+ fireEvent.click(getByLabelText('ManageDimensionLinksToggle'));
176
+
177
+ await waitFor(() => {
178
+ expect(getByText('Reference Links')).toBeInTheDocument();
179
+ });
180
+
181
+ // Switch to reference link tab
182
+ fireEvent.click(getByText('Reference Links'));
183
+
184
+ await waitFor(() => {
185
+ expect(getByText('Dimension Node *')).toBeInTheDocument();
186
+ expect(getByText('Dimension Column *')).toBeInTheDocument();
187
+ expect(
188
+ getByPlaceholderText('e.g., birth_date, registration_date'),
189
+ ).toBeInTheDocument();
190
+ expect(getByText('Add Link')).toBeInTheDocument();
191
+ });
192
+ });
193
+
194
+ it('handles removing reference link', async () => {
195
+ mockDjClient.DataJunctionAPI.removeReferenceDimensionLink.mockResolvedValue(
196
+ {
197
+ status: 200,
198
+ },
199
+ );
200
+
201
+ const propsWithReferenceLink = {
202
+ ...defaultProps,
203
+ referenceLink: {
204
+ dimension: 'default.dim1',
205
+ dimension_column: 'id',
206
+ },
207
+ };
208
+
209
+ const { getByLabelText, getByText } = render(
210
+ <DJClientContext.Provider value={mockDjClient}>
211
+ <ManageDimensionLinksDialog {...propsWithReferenceLink} />
212
+ </DJClientContext.Provider>,
213
+ );
214
+
215
+ fireEvent.click(getByLabelText('ManageDimensionLinksToggle'));
216
+
217
+ await waitFor(() => {
218
+ expect(getByText('Reference Links')).toBeInTheDocument();
219
+ });
220
+
221
+ // Switch to reference link tab
222
+ fireEvent.click(getByText('Reference Links'));
223
+
224
+ await waitFor(() => {
225
+ expect(getByText('Remove Link')).toBeInTheDocument();
226
+ });
227
+
228
+ // Remove the link
229
+ const removeButton = getByText('Remove Link');
230
+ await act(async () => {
231
+ fireEvent.click(removeButton);
232
+ });
233
+
234
+ await waitFor(() => {
235
+ expect(window.confirm).toHaveBeenCalledWith(
236
+ 'Are you sure you want to remove this reference link?',
237
+ );
238
+ expect(
239
+ mockDjClient.DataJunctionAPI.removeReferenceDimensionLink,
240
+ ).toHaveBeenCalledWith('default.node1', 'test_column');
241
+ expect(window.location.reload).toHaveBeenCalled();
242
+ });
243
+ });
244
+
245
+ it('does not remove reference link when user cancels confirm dialog', async () => {
246
+ window.confirm.mockReturnValueOnce(false);
247
+
248
+ const propsWithReferenceLink = {
249
+ ...defaultProps,
250
+ referenceLink: {
251
+ dimension: 'default.dim1',
252
+ dimension_column: 'id',
253
+ },
254
+ };
255
+
256
+ const { getByLabelText, getByText } = render(
257
+ <DJClientContext.Provider value={mockDjClient}>
258
+ <ManageDimensionLinksDialog {...propsWithReferenceLink} />
259
+ </DJClientContext.Provider>,
260
+ );
261
+
262
+ fireEvent.click(getByLabelText('ManageDimensionLinksToggle'));
263
+ fireEvent.click(getByText('Reference Links'));
264
+
265
+ await waitFor(() => {
266
+ expect(getByText('Remove Link')).toBeInTheDocument();
267
+ });
268
+
269
+ const removeButton = getByText('Remove Link');
270
+ await act(async () => {
271
+ fireEvent.click(removeButton);
272
+ });
273
+
274
+ expect(
275
+ mockDjClient.DataJunctionAPI.removeReferenceDimensionLink,
276
+ ).not.toHaveBeenCalled();
277
+ });
278
+
279
+ it('handles failed reference link removal', async () => {
280
+ mockDjClient.DataJunctionAPI.removeReferenceDimensionLink.mockResolvedValue(
281
+ {
282
+ status: 500,
283
+ json: { message: 'Server error' },
284
+ },
285
+ );
286
+
287
+ const propsWithReferenceLink = {
288
+ ...defaultProps,
289
+ referenceLink: {
290
+ dimension: 'default.dim1',
291
+ dimension_column: 'id',
292
+ },
293
+ };
294
+
295
+ const { getByLabelText, getByText } = render(
296
+ <DJClientContext.Provider value={mockDjClient}>
297
+ <ManageDimensionLinksDialog {...propsWithReferenceLink} />
298
+ </DJClientContext.Provider>,
299
+ );
300
+
301
+ fireEvent.click(getByLabelText('ManageDimensionLinksToggle'));
302
+ fireEvent.click(getByText('Reference Links'));
303
+
304
+ await waitFor(() => {
305
+ expect(getByText('Remove Link')).toBeInTheDocument();
306
+ });
307
+
308
+ const removeButton = getByText('Remove Link');
309
+ await act(async () => {
310
+ fireEvent.click(removeButton);
311
+ });
312
+
313
+ await waitFor(() => {
314
+ expect(window.alert).toHaveBeenCalled();
315
+ expect(window.location.reload).not.toHaveBeenCalled();
316
+ });
317
+ });
318
+
319
+ it('shows Update Link button when reference link exists', async () => {
320
+ const propsWithReferenceLink = {
321
+ ...defaultProps,
322
+ referenceLink: {
323
+ dimension: 'default.dim1',
324
+ dimension_column: 'user_id',
325
+ },
326
+ };
327
+
328
+ const { getByLabelText, getByText } = render(
329
+ <DJClientContext.Provider value={mockDjClient}>
330
+ <ManageDimensionLinksDialog {...propsWithReferenceLink} />
331
+ </DJClientContext.Provider>,
332
+ );
333
+
334
+ fireEvent.click(getByLabelText('ManageDimensionLinksToggle'));
335
+ fireEvent.click(getByText('Reference Links'));
336
+
337
+ await waitFor(() => {
338
+ expect(getByText('Update Link')).toBeInTheDocument();
339
+ expect(getByText('Remove Link')).toBeInTheDocument();
340
+ });
341
+ });
342
+
343
+ it('shows validation error when submitting reference link without required fields', async () => {
344
+ const { getByLabelText, getByText, getAllByText } = render(
345
+ <DJClientContext.Provider value={mockDjClient}>
346
+ <ManageDimensionLinksDialog {...defaultProps} />
347
+ </DJClientContext.Provider>,
348
+ );
349
+
350
+ fireEvent.click(getByLabelText('ManageDimensionLinksToggle'));
351
+ fireEvent.click(getByText('Reference Links'));
352
+
353
+ await waitFor(() => {
354
+ expect(getByText('Add Link')).toBeInTheDocument();
355
+ });
356
+
357
+ // Try to submit without filling required fields
358
+ const saveButton = getByText('Add Link');
359
+ await act(async () => {
360
+ fireEvent.click(saveButton);
361
+ });
362
+
363
+ await waitFor(() => {
364
+ const requiredErrors = getAllByText('Required');
365
+ expect(requiredErrors.length).toBeGreaterThan(0);
366
+ });
367
+ });
368
+
369
+ it('closes modal when clicking backdrop', async () => {
370
+ const { getByLabelText, getByRole, queryByRole } = render(
371
+ <DJClientContext.Provider value={mockDjClient}>
372
+ <ManageDimensionLinksDialog {...defaultProps} />
373
+ </DJClientContext.Provider>,
374
+ );
375
+
376
+ fireEvent.click(getByLabelText('ManageDimensionLinksToggle'));
377
+
378
+ await waitFor(() => {
379
+ expect(getByRole('dialog')).toBeInTheDocument();
380
+ });
381
+
382
+ // Click the backdrop
383
+ const backdrop = getByRole('dialog').parentElement;
384
+ fireEvent.click(backdrop);
385
+
386
+ await waitFor(() => {
387
+ expect(queryByRole('dialog')).not.toBeInTheDocument();
388
+ });
389
+ });
390
+ });
@@ -0,0 +1,166 @@
1
+ import React from 'react';
2
+ import { render, waitFor, screen } from '@testing-library/react';
3
+ import NodeColumnTab from '../NodeColumnTab';
4
+
5
+ describe('<NodeColumnTab />', () => {
6
+ const mockDjClient = {
7
+ node: jest.fn(),
8
+ columns: jest.fn(),
9
+ attributes: jest.fn(),
10
+ dimensions: jest.fn(),
11
+ };
12
+
13
+ const mockNodeColumns = [
14
+ {
15
+ name: 'repair_order_id',
16
+ display_name: 'Repair Order Id',
17
+ type: 'int',
18
+ attributes: [],
19
+ dimension: null,
20
+ },
21
+ {
22
+ name: 'municipality_id',
23
+ display_name: 'Municipality Id',
24
+ type: 'string',
25
+ attributes: [],
26
+ dimension: null,
27
+ },
28
+ {
29
+ name: 'hard_hat_id',
30
+ display_name: 'Hard Hat Id',
31
+ type: 'int',
32
+ attributes: [],
33
+ dimension: null,
34
+ },
35
+ {
36
+ name: 'order_date',
37
+ display_name: 'Order Date',
38
+ type: 'date',
39
+ attributes: [],
40
+ dimension: null,
41
+ },
42
+ {
43
+ name: 'required_date',
44
+ display_name: 'Required Date',
45
+ type: 'date',
46
+ attributes: [],
47
+ dimension: null,
48
+ },
49
+ {
50
+ name: 'dispatched_date',
51
+ display_name: 'Dispatched Date',
52
+ type: 'date',
53
+ attributes: [],
54
+ dimension: null,
55
+ },
56
+ {
57
+ name: 'dispatcher_id',
58
+ display_name: 'Dispatcher Id',
59
+ type: 'int',
60
+ attributes: [],
61
+ dimension: null,
62
+ },
63
+ ];
64
+
65
+ const mockNode = {
66
+ node_revision_id: 1,
67
+ node_id: 1,
68
+ type: 'source',
69
+ name: 'default.repair_orders',
70
+ display_name: 'Default: Repair Orders',
71
+ version: 'v1.0',
72
+ status: 'valid',
73
+ mode: 'published',
74
+ catalog: {
75
+ id: 1,
76
+ uuid: '0fc18295-e1a2-4c3c-b72a-894725c12488',
77
+ created_at: '2023-08-21T16:48:51.146121+00:00',
78
+ updated_at: '2023-08-21T16:48:51.146122+00:00',
79
+ extra_params: {},
80
+ name: 'warehouse',
81
+ },
82
+ schema_: 'roads',
83
+ table: 'repair_orders',
84
+ description: 'Repair orders',
85
+ query: null,
86
+ availability: null,
87
+ columns: mockNodeColumns,
88
+ updated_at: '2023-08-21T16:48:52.880498+00:00',
89
+ materializations: [],
90
+ parents: [],
91
+ dimension_links: [
92
+ {
93
+ dimension: {
94
+ name: 'default.contractor',
95
+ },
96
+ join_type: 'left',
97
+ join_sql:
98
+ 'default.contractor.contractor_id = default.repair_orders.contractor_id',
99
+ join_cardinality: 'one_to_one',
100
+ role: 'contractor',
101
+ foreign_keys: {
102
+ 'default.repair_orders.contractor_id':
103
+ 'default.contractor.contractor_id',
104
+ },
105
+ },
106
+ ],
107
+ };
108
+
109
+ const mockAttributes = [
110
+ {
111
+ uniqueness_scope: [],
112
+ namespace: 'system',
113
+ name: 'primary_key',
114
+ description:
115
+ 'Points to a column which is part of the primary key of the node',
116
+ allowed_node_types: ['source', 'transform', 'dimension'],
117
+ id: 1,
118
+ },
119
+ {
120
+ uniqueness_scope: [],
121
+ namespace: 'system',
122
+ name: 'dimension',
123
+ description: 'Points to a dimension attribute column',
124
+ allowed_node_types: ['source', 'transform'],
125
+ id: 2,
126
+ },
127
+ ];
128
+
129
+ const mockDimensions = ['default.contractor', 'default.hard_hat'];
130
+
131
+ beforeEach(() => {
132
+ // Reset the mocks before each test
133
+ mockDjClient.node.mockReset();
134
+ mockDjClient.columns.mockReset();
135
+ mockDjClient.attributes.mockReset();
136
+ mockDjClient.dimensions.mockReset();
137
+ });
138
+
139
+ it('renders node columns and dimension links', async () => {
140
+ mockDjClient.node.mockReturnValue(mockNode);
141
+ mockDjClient.columns.mockReturnValue(mockNodeColumns);
142
+ mockDjClient.attributes.mockReturnValue(mockAttributes);
143
+ mockDjClient.dimensions.mockReturnValue(mockDimensions);
144
+
145
+ render(<NodeColumnTab node={mockNode} djClient={mockDjClient} />);
146
+
147
+ await waitFor(() => {
148
+ // Displays the columns
149
+ for (const column of mockNode.columns) {
150
+ expect(screen.getByText(column.name)).toBeInTheDocument();
151
+ expect(screen.getByText(column.display_name)).toBeInTheDocument();
152
+ }
153
+
154
+ // Displays the dimension links
155
+ for (const dimensionLink of mockNode.dimension_links) {
156
+ const link = screen
157
+ .getByText(dimensionLink.dimension.name)
158
+ .closest('a');
159
+ expect(link).toHaveAttribute(
160
+ 'href',
161
+ `/nodes/${dimensionLink.dimension.name}`,
162
+ );
163
+ }
164
+ });
165
+ });
166
+ });
@@ -0,0 +1,151 @@
1
+ import React from 'react';
2
+ import { render, waitFor, screen } from '@testing-library/react';
3
+ import NodeDependenciesTab from '../NodeDependenciesTab';
4
+
5
+ describe('<NodeDependenciesTab />', () => {
6
+ const mockDjClient = {
7
+ node: jest.fn(),
8
+ nodeDimensions: jest.fn(),
9
+ upstreams: jest.fn(),
10
+ downstreams: jest.fn(),
11
+ };
12
+
13
+ const mockNode = {
14
+ node_revision_id: 1,
15
+ node_id: 1,
16
+ type: 'source',
17
+ name: 'default.repair_orders',
18
+ display_name: 'Default: Repair Orders',
19
+ version: 'v1.0',
20
+ status: 'valid',
21
+ mode: 'published',
22
+ catalog: {
23
+ id: 1,
24
+ uuid: '0fc18295-e1a2-4c3c-b72a-894725c12488',
25
+ created_at: '2023-08-21T16:48:51.146121+00:00',
26
+ updated_at: '2023-08-21T16:48:51.146122+00:00',
27
+ extra_params: {},
28
+ name: 'warehouse',
29
+ },
30
+ schema_: 'roads',
31
+ table: 'repair_orders',
32
+ description: 'Repair orders',
33
+ query: null,
34
+ availability: null,
35
+ columns: [
36
+ {
37
+ name: 'repair_order_id',
38
+ type: 'int',
39
+ attributes: [],
40
+ dimension: null,
41
+ },
42
+ {
43
+ name: 'municipality_id',
44
+ type: 'string',
45
+ attributes: [],
46
+ dimension: null,
47
+ },
48
+ {
49
+ name: 'hard_hat_id',
50
+ type: 'int',
51
+ attributes: [],
52
+ dimension: null,
53
+ },
54
+ {
55
+ name: 'order_date',
56
+ type: 'date',
57
+ attributes: [],
58
+ dimension: null,
59
+ },
60
+ {
61
+ name: 'required_date',
62
+ type: 'date',
63
+ attributes: [],
64
+ dimension: null,
65
+ },
66
+ {
67
+ name: 'dispatched_date',
68
+ type: 'date',
69
+ attributes: [],
70
+ dimension: null,
71
+ },
72
+ {
73
+ name: 'dispatcher_id',
74
+ type: 'int',
75
+ attributes: [],
76
+ dimension: null,
77
+ },
78
+ ],
79
+ updated_at: '2023-08-21T16:48:52.880498+00:00',
80
+ materializations: [],
81
+ parents: [],
82
+ dimension_links: [
83
+ {
84
+ dimension: {
85
+ name: 'default.contractor',
86
+ },
87
+ join_type: 'left',
88
+ join_sql:
89
+ 'default.contractor.contractor_id = default.repair_orders.contractor_id',
90
+ join_cardinality: 'one_to_one',
91
+ role: 'contractor',
92
+ },
93
+ ],
94
+ };
95
+
96
+ const mockDimensions = [
97
+ {
98
+ properties: [],
99
+ name: 'default.dispatcher.company_name',
100
+ node_display_name: 'Default: Dispatcher',
101
+ node_name: 'default.dispatcher',
102
+ path: ['default.repair_orders_fact.dispatcher_id'],
103
+ type: 'string',
104
+ },
105
+ {
106
+ properties: ['primary_key'],
107
+ name: 'default.dispatcher.dispatcher_id',
108
+ node_display_name: 'Default: Dispatcher',
109
+ node_name: 'default.dispatcher',
110
+ path: ['default.repair_orders_fact.dispatcher_id'],
111
+ type: 'int',
112
+ },
113
+ {
114
+ properties: [],
115
+ name: 'default.hard_hat.city',
116
+ node_display_name: 'Default: Hard Hat',
117
+ node_name: 'default.hard_hat',
118
+ path: ['default.repair_orders_fact.hard_hat_id'],
119
+ type: 'string',
120
+ },
121
+ {
122
+ properties: ['primary_key'],
123
+ name: 'default.hard_hat.hard_hat_id',
124
+ node_display_name: 'Default: Hard Hat',
125
+ node_name: 'default.hard_hat',
126
+ path: ['default.repair_orders_fact.hard_hat_id'],
127
+ type: 'int',
128
+ },
129
+ ];
130
+
131
+ beforeEach(() => {
132
+ // Reset the mocks before each test
133
+ mockDjClient.nodeDimensions.mockReset();
134
+ mockDjClient.upstreams.mockReset();
135
+ mockDjClient.downstreams.mockReset();
136
+ });
137
+
138
+ it('renders nodes with dimensions', async () => {
139
+ mockDjClient.nodeDimensions.mockReturnValue(mockDimensions);
140
+ mockDjClient.upstreams.mockReturnValue([mockNode]);
141
+ mockDjClient.downstreams.mockReturnValue([mockNode]);
142
+ render(<NodeDependenciesTab node={mockNode} djClient={mockDjClient} />);
143
+ await waitFor(() => {
144
+ for (const dimension of mockDimensions) {
145
+ const link = screen.getByText(dimension.node_display_name).closest('a');
146
+ expect(link).toHaveAttribute('href', `/nodes/${dimension.node_name}`);
147
+ expect(screen.getByText(dimension.name)).toBeInTheDocument();
148
+ }
149
+ });
150
+ });
151
+ });