datajunction-ui 0.0.1-rc.9 → 0.0.2-3.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 (238) hide show
  1. package/.env +2 -0
  2. package/.prettierignore +3 -1
  3. package/Makefile +9 -0
  4. package/dj-logo.svg +10 -0
  5. package/package.json +53 -14
  6. package/public/favicon.ico +0 -0
  7. package/public/index.html +1 -1
  8. package/src/__tests__/reportWebVitals.test.jsx +44 -0
  9. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +5 -109
  10. package/src/app/components/AddNodeDropdown.jsx +44 -0
  11. package/src/app/components/ListGroupItem.jsx +9 -1
  12. package/src/app/components/NamespaceHeader.jsx +4 -13
  13. package/src/app/components/NodeListActions.jsx +69 -0
  14. package/src/app/components/NodeMaterializationDelete.jsx +90 -0
  15. package/src/app/components/NotificationBell.tsx +229 -0
  16. package/src/app/components/QueryInfo.jsx +172 -0
  17. package/src/app/components/Search.jsx +94 -0
  18. package/src/app/components/Tab.jsx +8 -1
  19. package/src/app/components/ToggleSwitch.jsx +20 -0
  20. package/src/app/components/UserMenu.tsx +92 -0
  21. package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
  22. package/src/app/components/__tests__/NodeMaterializationDelete.test.jsx +263 -0
  23. package/src/app/components/__tests__/NotificationBell.test.tsx +313 -0
  24. package/src/app/components/__tests__/QueryInfo.test.jsx +183 -0
  25. package/src/app/components/__tests__/Search.test.jsx +307 -0
  26. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  27. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  28. package/src/app/components/__tests__/UserMenu.test.tsx +248 -0
  29. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +8 -3
  30. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
  31. package/src/app/components/djgraph/Collapse.jsx +47 -0
  32. package/src/app/components/djgraph/DJNode.jsx +61 -83
  33. package/src/app/components/djgraph/DJNodeColumns.jsx +75 -0
  34. package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
  35. package/src/app/components/djgraph/LayoutFlow.jsx +106 -0
  36. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  37. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  38. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  39. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +84 -40
  40. package/src/app/components/forms/Action.jsx +8 -0
  41. package/src/app/components/forms/NodeNameField.jsx +64 -0
  42. package/src/app/components/search.css +17 -0
  43. package/src/app/constants.js +2 -0
  44. package/src/app/icons/AddItemIcon.jsx +16 -0
  45. package/src/app/icons/AlertIcon.jsx +33 -0
  46. package/src/app/icons/CollapsedIcon.jsx +15 -0
  47. package/src/app/icons/CommitIcon.jsx +45 -0
  48. package/src/app/icons/DJLogo.jsx +36 -0
  49. package/src/app/icons/DeleteIcon.jsx +21 -0
  50. package/src/app/icons/DiffIcon.jsx +63 -0
  51. package/src/app/icons/EditIcon.jsx +18 -0
  52. package/src/app/icons/ExpandedIcon.jsx +15 -0
  53. package/src/app/icons/EyeIcon.jsx +20 -0
  54. package/src/app/icons/FilterIcon.jsx +7 -0
  55. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  56. package/src/app/icons/InvalidIcon.jsx +16 -0
  57. package/src/app/icons/JupyterExportIcon.jsx +25 -0
  58. package/src/app/icons/LoadingIcon.jsx +14 -0
  59. package/src/app/icons/NodeIcon.jsx +49 -0
  60. package/src/app/icons/NotificationIcon.jsx +27 -0
  61. package/src/app/icons/PythonIcon.jsx +14 -0
  62. package/src/app/icons/SettingsIcon.jsx +28 -0
  63. package/src/app/icons/TableIcon.jsx +14 -0
  64. package/src/app/icons/ValidIcon.jsx +16 -0
  65. package/src/app/index.tsx +138 -38
  66. package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -0
  67. package/src/app/pages/AddEditNodePage/ColumnsSelect.jsx +84 -0
  68. package/src/app/pages/AddEditNodePage/CustomMetadataField.jsx +144 -0
  69. package/src/app/pages/AddEditNodePage/DescriptionField.jsx +17 -0
  70. package/src/app/pages/AddEditNodePage/DisplayNameField.jsx +16 -0
  71. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +64 -0
  72. package/src/app/pages/AddEditNodePage/FullNameField.jsx +38 -0
  73. package/src/app/pages/AddEditNodePage/Loadable.jsx +20 -0
  74. package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +75 -0
  75. package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +71 -0
  76. package/src/app/pages/AddEditNodePage/NamespaceField.jsx +40 -0
  77. package/src/app/pages/AddEditNodePage/NodeModeField.jsx +14 -0
  78. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +94 -0
  79. package/src/app/pages/AddEditNodePage/OwnersField.jsx +53 -0
  80. package/src/app/pages/AddEditNodePage/RequiredDimensionsSelect.jsx +54 -0
  81. package/src/app/pages/AddEditNodePage/TagsField.jsx +47 -0
  82. package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +49 -0
  83. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +110 -0
  84. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +291 -0
  85. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
  86. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
  87. package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
  88. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
  89. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
  90. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
  91. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +224 -0
  92. package/src/app/pages/AddEditNodePage/index.jsx +545 -0
  93. package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
  94. package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
  95. package/src/app/pages/AddEditTagPage/index.jsx +132 -0
  96. package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +152 -0
  97. package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
  98. package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +75 -0
  99. package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +373 -0
  100. package/src/app/pages/CubeBuilderPage/index.jsx +291 -0
  101. package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
  102. package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
  103. package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
  104. package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
  105. package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
  106. package/src/app/pages/LoginPage/index.jsx +17 -0
  107. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
  108. package/src/app/pages/NamespacePage/Explorer.jsx +232 -0
  109. package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
  110. package/src/app/pages/NamespacePage/NodeModeSelect.jsx +27 -0
  111. package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +30 -0
  112. package/src/app/pages/NamespacePage/TagSelect.jsx +44 -0
  113. package/src/app/pages/NamespacePage/UserSelect.jsx +47 -0
  114. package/src/app/pages/NamespacePage/__tests__/AddNamespacePopover.test.jsx +283 -0
  115. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +331 -0
  116. package/src/app/pages/NamespacePage/index.jsx +354 -42
  117. package/src/app/pages/NodePage/AddBackfillPopover.jsx +165 -0
  118. package/src/app/pages/NodePage/AddComplexDimensionLinkPopover.jsx +367 -0
  119. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +222 -0
  120. package/src/app/pages/NodePage/AvailabilityStateBlock.jsx +67 -0
  121. package/src/app/pages/NodePage/ClientCodePopover.jsx +116 -0
  122. package/src/app/pages/NodePage/DimensionFilter.jsx +86 -0
  123. package/src/app/pages/NodePage/EditColumnDescriptionPopover.jsx +116 -0
  124. package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
  125. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +164 -0
  126. package/src/app/pages/NodePage/ManageDimensionLinksDialog.jsx +526 -0
  127. package/src/app/pages/NodePage/MaterializationConfigField.jsx +60 -0
  128. package/src/app/pages/NodePage/NodeColumnTab.jsx +421 -30
  129. package/src/app/pages/NodePage/NodeDependenciesTab.jsx +155 -0
  130. package/src/app/pages/NodePage/NodeGraphTab.jsx +119 -148
  131. package/src/app/pages/NodePage/NodeHistory.jsx +236 -0
  132. package/src/app/pages/NodePage/NodeInfoTab.jsx +404 -49
  133. package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
  134. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +585 -0
  135. package/src/app/pages/NodePage/NodeRevisionMaterializationTab.jsx +58 -0
  136. package/src/app/pages/NodePage/NodeStatus.jsx +100 -31
  137. package/src/app/pages/NodePage/NodeValidateTab.jsx +367 -0
  138. package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
  139. package/src/app/pages/NodePage/NotebookDownload.jsx +36 -0
  140. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +151 -0
  141. package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
  142. package/src/app/pages/NodePage/RevisionDiff.jsx +209 -0
  143. package/src/app/pages/NodePage/WatchNodeButton.jsx +226 -0
  144. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +56 -0
  145. package/src/app/pages/NodePage/__tests__/AddComplexDimensionLinkPopover.test.jsx +459 -0
  146. package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +87 -0
  147. package/src/app/pages/NodePage/__tests__/DimensionFilter.test.jsx +74 -0
  148. package/src/app/pages/NodePage/__tests__/EditColumnDescriptionPopover.test.jsx +149 -0
  149. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +144 -0
  150. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +132 -0
  151. package/src/app/pages/NodePage/__tests__/ManageDimensionLinksDialog.test.jsx +390 -0
  152. package/src/app/pages/NodePage/__tests__/NodeColumnTab.test.jsx +166 -0
  153. package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +157 -0
  154. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +595 -0
  155. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +58 -0
  156. package/src/app/pages/NodePage/__tests__/NodeMaterializationTab.test.jsx +190 -0
  157. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +892 -0
  158. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  159. package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
  160. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +19 -0
  161. package/src/app/pages/NodePage/index.jsx +186 -45
  162. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  163. package/src/app/pages/NotificationsPage/Loadable.jsx +6 -0
  164. package/src/app/pages/NotificationsPage/__tests__/index.test.jsx +287 -0
  165. package/src/app/pages/NotificationsPage/index.jsx +136 -0
  166. package/src/app/pages/OverviewPage/ByStatusPanel.jsx +69 -0
  167. package/src/app/pages/OverviewPage/DimensionNodeUsagePanel.jsx +48 -0
  168. package/src/app/pages/OverviewPage/GovernanceWarningsPanel.jsx +107 -0
  169. package/src/app/pages/OverviewPage/Loadable.jsx +16 -0
  170. package/src/app/pages/OverviewPage/NodesByTypePanel.jsx +63 -0
  171. package/src/app/pages/OverviewPage/OverviewPanel.jsx +94 -0
  172. package/src/app/pages/OverviewPage/TrendsPanel.jsx +66 -0
  173. package/src/app/pages/OverviewPage/__tests__/ByStatusPanel.test.jsx +36 -0
  174. package/src/app/pages/OverviewPage/__tests__/DimensionNodeUsagePanel.test.jsx +76 -0
  175. package/src/app/pages/OverviewPage/__tests__/GovernanceWarningsPanel.test.jsx +77 -0
  176. package/src/app/pages/OverviewPage/__tests__/NodesByTypePanel.test.jsx +86 -0
  177. package/src/app/pages/OverviewPage/__tests__/OverviewPanel.test.jsx +78 -0
  178. package/src/app/pages/OverviewPage/__tests__/TrendsPanel.test.jsx +120 -0
  179. package/src/app/pages/OverviewPage/__tests__/index.test.jsx +54 -0
  180. package/src/app/pages/OverviewPage/index.jsx +22 -0
  181. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  182. package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +112 -0
  183. package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +38 -0
  184. package/src/app/pages/RegisterTablePage/index.jsx +142 -0
  185. package/src/app/pages/Root/__tests__/index.test.jsx +44 -0
  186. package/src/app/pages/Root/index.tsx +92 -10
  187. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  188. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  189. package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
  190. package/src/app/pages/SettingsPage/CreateServiceAccountModal.jsx +152 -0
  191. package/src/app/pages/SettingsPage/Loadable.jsx +16 -0
  192. package/src/app/pages/SettingsPage/NotificationSubscriptionsSection.jsx +189 -0
  193. package/src/app/pages/SettingsPage/ProfileSection.jsx +41 -0
  194. package/src/app/pages/SettingsPage/ServiceAccountsSection.jsx +95 -0
  195. package/src/app/pages/SettingsPage/__tests__/CreateServiceAccountModal.test.jsx +318 -0
  196. package/src/app/pages/SettingsPage/__tests__/NotificationSubscriptionsSection.test.jsx +233 -0
  197. package/src/app/pages/SettingsPage/__tests__/ProfileSection.test.jsx +65 -0
  198. package/src/app/pages/SettingsPage/__tests__/ServiceAccountsSection.test.jsx +150 -0
  199. package/src/app/pages/SettingsPage/__tests__/index.test.jsx +187 -0
  200. package/src/app/pages/SettingsPage/index.jsx +148 -0
  201. package/src/app/pages/TagPage/Loadable.jsx +16 -0
  202. package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
  203. package/src/app/pages/TagPage/index.jsx +79 -0
  204. package/src/app/providers/UserProvider.tsx +78 -0
  205. package/src/app/services/DJService.js +1487 -21
  206. package/src/app/services/__tests__/DJService.test.jsx +2194 -0
  207. package/src/app/utils/__tests__/date.test.js +198 -0
  208. package/src/app/utils/date.js +65 -0
  209. package/src/index.tsx +1 -0
  210. package/src/mocks/mockNodes.jsx +1477 -0
  211. package/src/setupTests.ts +31 -1
  212. package/src/styles/dag.css +117 -5
  213. package/src/styles/index.css +1028 -31
  214. package/src/styles/loading.css +34 -0
  215. package/src/styles/login.css +81 -0
  216. package/src/styles/nav-bar.css +274 -0
  217. package/src/styles/node-creation.scss +276 -0
  218. package/src/styles/node-list.css +4 -0
  219. package/src/styles/overview.css +72 -0
  220. package/src/styles/settings.css +787 -0
  221. package/src/styles/sorted-table.css +15 -0
  222. package/src/styles/styles.scss +44 -0
  223. package/src/styles/styles.scss.d.ts +9 -0
  224. package/src/utils/form.jsx +23 -0
  225. package/webpack.config.js +20 -7
  226. package/.babelrc +0 -4
  227. package/.env.local +0 -4
  228. package/.env.production +0 -1
  229. package/.github/pull_request_template.md +0 -11
  230. package/.github/workflows/ci.yml +0 -33
  231. package/.vscode/extensions.json +0 -7
  232. package/.vscode/launch.json +0 -15
  233. package/.vscode/settings.json +0 -25
  234. package/Dockerfile +0 -7
  235. package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
  236. package/src/app/pages/ListNamespacesPage/index.jsx +0 -62
  237. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -45
  238. package/src/app/pages/NamespacePage/__tests__/index.test.tsx +0 -14
@@ -0,0 +1,183 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import QueryInfo from '../QueryInfo';
4
+
5
+ describe('<QueryInfo />', () => {
6
+ const defaultProps = {
7
+ id: 'query-123',
8
+ state: 'completed',
9
+ engine_name: 'spark',
10
+ engine_version: '3.2.0',
11
+ errors: [],
12
+ links: [],
13
+ output_table: 'output.table',
14
+ scheduled: '2024-01-01 10:00:00',
15
+ started: '2024-01-01 10:05:00',
16
+ finished: '2024-01-01 10:15:00',
17
+ numRows: 1000,
18
+ isList: false,
19
+ };
20
+
21
+ it('renders table view when isList is false', () => {
22
+ const { getByText, container } = render(<QueryInfo {...defaultProps} />);
23
+
24
+ expect(getByText('Query ID')).toBeInTheDocument();
25
+ expect(getByText('query-123')).toBeInTheDocument();
26
+ expect(container.textContent).toContain('spark');
27
+ expect(getByText('completed')).toBeInTheDocument();
28
+ });
29
+
30
+ it('renders list view when isList is true', () => {
31
+ const { getByText } = render(<QueryInfo {...defaultProps} isList={true} />);
32
+
33
+ expect(getByText('Query ID')).toBeInTheDocument();
34
+ expect(getByText('State')).toBeInTheDocument();
35
+ expect(getByText('Engine')).toBeInTheDocument();
36
+ });
37
+
38
+ it('displays errors in table view', () => {
39
+ const propsWithErrors = {
40
+ ...defaultProps,
41
+ errors: ['Error 1', 'Error 2'],
42
+ };
43
+
44
+ const { getByText } = render(<QueryInfo {...propsWithErrors} />);
45
+
46
+ expect(getByText('Error 1')).toBeInTheDocument();
47
+ expect(getByText('Error 2')).toBeInTheDocument();
48
+ });
49
+
50
+ it('displays links in table view', () => {
51
+ const propsWithLinks = {
52
+ ...defaultProps,
53
+ links: ['https://example.com/query1', 'https://example.com/query2'],
54
+ };
55
+
56
+ const { getByText } = render(<QueryInfo {...propsWithLinks} />);
57
+
58
+ expect(getByText('https://example.com/query1')).toBeInTheDocument();
59
+ expect(getByText('https://example.com/query2')).toBeInTheDocument();
60
+ });
61
+
62
+ it('renders empty state when no errors', () => {
63
+ const { container } = render(<QueryInfo {...defaultProps} />);
64
+
65
+ const errorCell = container.querySelector('td:nth-child(6)');
66
+ expect(errorCell).toBeInTheDocument();
67
+ });
68
+
69
+ it('renders empty state when no links', () => {
70
+ const { container } = render(<QueryInfo {...defaultProps} />);
71
+
72
+ const linksCell = container.querySelector('td:nth-child(7)');
73
+ expect(linksCell).toBeInTheDocument();
74
+ });
75
+
76
+ it('displays all query information in table view', () => {
77
+ const { getByText } = render(<QueryInfo {...defaultProps} />);
78
+
79
+ expect(getByText('output.table')).toBeInTheDocument();
80
+ expect(getByText('1000')).toBeInTheDocument();
81
+ expect(getByText('2024-01-01 10:00:00')).toBeInTheDocument();
82
+ expect(getByText('2024-01-01 10:05:00')).toBeInTheDocument();
83
+ });
84
+
85
+ it('renders list view with query ID link when links present', () => {
86
+ const propsWithLinks = {
87
+ ...defaultProps,
88
+ links: ['https://example.com/query'],
89
+ isList: true,
90
+ };
91
+
92
+ const { container } = render(<QueryInfo {...propsWithLinks} />);
93
+
94
+ const link = container.querySelector('a[href="https://example.com/query"]');
95
+ expect(link).toBeInTheDocument();
96
+ expect(link).toHaveTextContent('query-123');
97
+ });
98
+
99
+ it('renders list view with query ID as text when no links', () => {
100
+ const propsNoLinks = {
101
+ ...defaultProps,
102
+ links: [],
103
+ isList: true,
104
+ };
105
+
106
+ const { getByText } = render(<QueryInfo {...propsNoLinks} />);
107
+
108
+ expect(getByText('query-123')).toBeInTheDocument();
109
+ });
110
+
111
+ it('displays errors with syntax highlighter in list view', () => {
112
+ const propsWithErrors = {
113
+ ...defaultProps,
114
+ errors: ['Syntax error on line 5', 'Connection timeout'],
115
+ isList: true,
116
+ };
117
+
118
+ const { getByText } = render(<QueryInfo {...propsWithErrors} />);
119
+
120
+ expect(getByText('Logs')).toBeInTheDocument();
121
+ });
122
+
123
+ it('displays finished timestamp in list view', () => {
124
+ const { getByText } = render(<QueryInfo {...defaultProps} isList={true} />);
125
+
126
+ expect(getByText('Finished')).toBeInTheDocument();
127
+ expect(getByText('2024-01-01 10:15:00')).toBeInTheDocument();
128
+ });
129
+
130
+ it('displays output table and row count in list view', () => {
131
+ const { getByText } = render(<QueryInfo {...defaultProps} isList={true} />);
132
+
133
+ expect(getByText('Output Table:')).toBeInTheDocument();
134
+ expect(getByText('output.table')).toBeInTheDocument();
135
+ expect(getByText('Rows:')).toBeInTheDocument();
136
+ expect(getByText('1000')).toBeInTheDocument();
137
+ });
138
+
139
+ it('displays multiple links in list view', () => {
140
+ const propsWithLinks = {
141
+ ...defaultProps,
142
+ links: ['https://link1.com', 'https://link2.com', 'https://link3.com'],
143
+ isList: true,
144
+ };
145
+
146
+ const { getByText } = render(<QueryInfo {...propsWithLinks} />);
147
+
148
+ expect(getByText('https://link1.com')).toBeInTheDocument();
149
+ expect(getByText('https://link2.com')).toBeInTheDocument();
150
+ expect(getByText('https://link3.com')).toBeInTheDocument();
151
+ });
152
+
153
+ it('renders empty logs section when no errors in list view', () => {
154
+ const { getByText } = render(<QueryInfo {...defaultProps} isList={true} />);
155
+
156
+ expect(getByText('Logs')).toBeInTheDocument();
157
+ });
158
+
159
+ it('displays engine name and version correctly', () => {
160
+ const { container } = render(<QueryInfo {...defaultProps} />);
161
+
162
+ const badges = container.querySelectorAll('.badge');
163
+ const engineBadge = Array.from(badges).find(b =>
164
+ b.textContent.includes('spark'),
165
+ );
166
+ expect(engineBadge).toBeTruthy();
167
+ expect(engineBadge.textContent).toContain('3.2.0');
168
+ });
169
+
170
+ it('handles undefined optional props', () => {
171
+ const minimalProps = {
172
+ id: 'query-456',
173
+ state: 'running',
174
+ engine_name: 'trino',
175
+ engine_version: '1.0',
176
+ };
177
+
178
+ const { getByText } = render(<QueryInfo {...minimalProps} />);
179
+
180
+ expect(getByText('query-456')).toBeInTheDocument();
181
+ expect(getByText('running')).toBeInTheDocument();
182
+ });
183
+ });
@@ -0,0 +1,307 @@
1
+ import React from 'react';
2
+ import { render, fireEvent, waitFor, screen } from '@testing-library/react';
3
+ import Search from '../Search';
4
+ import DJClientContext from '../../providers/djclient';
5
+
6
+ const mockDjClient = {
7
+ DataJunctionAPI: {
8
+ nodeDetails: jest.fn(),
9
+ listTags: jest.fn(),
10
+ },
11
+ };
12
+
13
+ describe('<Search />', () => {
14
+ beforeEach(() => {
15
+ jest.clearAllMocks();
16
+ });
17
+
18
+ const mockNodes = [
19
+ {
20
+ name: 'default.test_node',
21
+ display_name: 'Test Node',
22
+ description: 'A test node for testing',
23
+ type: 'transform',
24
+ },
25
+ {
26
+ name: 'default.another_node',
27
+ display_name: 'Another Node',
28
+ description: null, // Test null description
29
+ type: 'metric',
30
+ },
31
+ {
32
+ name: 'default.long_description_node',
33
+ display_name: 'Long Description',
34
+ description:
35
+ 'This is a very long description that exceeds 100 characters and should be truncated to prevent display issues in the search results interface',
36
+ type: 'dimension',
37
+ },
38
+ ];
39
+
40
+ const mockTags = [
41
+ {
42
+ name: 'test_tag',
43
+ display_name: 'Test Tag',
44
+ description: 'A test tag',
45
+ tag_type: 'business',
46
+ },
47
+ ];
48
+
49
+ it('renders search input', async () => {
50
+ mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
51
+ mockDjClient.DataJunctionAPI.listTags.mockResolvedValue(mockTags);
52
+
53
+ const { getByPlaceholderText } = render(
54
+ <DJClientContext.Provider value={mockDjClient}>
55
+ <Search />
56
+ </DJClientContext.Provider>,
57
+ );
58
+
59
+ expect(getByPlaceholderText('Search')).toBeInTheDocument();
60
+ });
61
+
62
+ it('fetches and initializes search data on mount', async () => {
63
+ mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
64
+ mockDjClient.DataJunctionAPI.listTags.mockResolvedValue(mockTags);
65
+
66
+ render(
67
+ <DJClientContext.Provider value={mockDjClient}>
68
+ <Search />
69
+ </DJClientContext.Provider>,
70
+ );
71
+
72
+ await waitFor(() => {
73
+ expect(mockDjClient.DataJunctionAPI.nodeDetails).toHaveBeenCalled();
74
+ expect(mockDjClient.DataJunctionAPI.listTags).toHaveBeenCalled();
75
+ });
76
+ });
77
+
78
+ it('displays search results when typing', async () => {
79
+ mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
80
+ mockDjClient.DataJunctionAPI.listTags.mockResolvedValue(mockTags);
81
+
82
+ const { getByPlaceholderText, getByText } = render(
83
+ <DJClientContext.Provider value={mockDjClient}>
84
+ <Search />
85
+ </DJClientContext.Provider>,
86
+ );
87
+
88
+ await waitFor(() => {
89
+ expect(mockDjClient.DataJunctionAPI.nodeDetails).toHaveBeenCalled();
90
+ });
91
+
92
+ const searchInput = getByPlaceholderText('Search');
93
+ fireEvent.change(searchInput, { target: { value: 'test' } });
94
+
95
+ await waitFor(() => {
96
+ expect(getByText(/Test Node/)).toBeInTheDocument();
97
+ });
98
+ });
99
+
100
+ it('displays nodes with correct URLs', async () => {
101
+ mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
102
+ mockDjClient.DataJunctionAPI.listTags.mockResolvedValue(mockTags);
103
+
104
+ const { getByPlaceholderText, container } = render(
105
+ <DJClientContext.Provider value={mockDjClient}>
106
+ <Search />
107
+ </DJClientContext.Provider>,
108
+ );
109
+
110
+ await waitFor(() => {
111
+ expect(mockDjClient.DataJunctionAPI.nodeDetails).toHaveBeenCalled();
112
+ });
113
+
114
+ const searchInput = getByPlaceholderText('Search');
115
+ fireEvent.change(searchInput, { target: { value: 'node' } });
116
+
117
+ await waitFor(() => {
118
+ const links = container.querySelectorAll('a[href^="/nodes/"]');
119
+ expect(links.length).toBeGreaterThan(0);
120
+ });
121
+ });
122
+
123
+ it('displays tags with correct URLs', async () => {
124
+ mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue([]);
125
+ mockDjClient.DataJunctionAPI.listTags.mockResolvedValue(mockTags);
126
+
127
+ const { getByPlaceholderText, container } = render(
128
+ <DJClientContext.Provider value={mockDjClient}>
129
+ <Search />
130
+ </DJClientContext.Provider>,
131
+ );
132
+
133
+ await waitFor(() => {
134
+ expect(mockDjClient.DataJunctionAPI.listTags).toHaveBeenCalled();
135
+ });
136
+
137
+ const searchInput = getByPlaceholderText('Search');
138
+ fireEvent.change(searchInput, { target: { value: 'tag' } });
139
+
140
+ await waitFor(() => {
141
+ const links = container.querySelectorAll('a[href^="/tags/"]');
142
+ expect(links.length).toBeGreaterThan(0);
143
+ });
144
+ });
145
+
146
+ it('truncates long descriptions', async () => {
147
+ mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
148
+ mockDjClient.DataJunctionAPI.listTags.mockResolvedValue([]);
149
+
150
+ const { getByPlaceholderText, getByText } = render(
151
+ <DJClientContext.Provider value={mockDjClient}>
152
+ <Search />
153
+ </DJClientContext.Provider>,
154
+ );
155
+
156
+ await waitFor(() => {
157
+ expect(mockDjClient.DataJunctionAPI.nodeDetails).toHaveBeenCalled();
158
+ });
159
+
160
+ const searchInput = getByPlaceholderText('Search');
161
+ fireEvent.change(searchInput, { target: { value: 'long' } });
162
+
163
+ await waitFor(() => {
164
+ expect(getByText(/\.\.\./)).toBeInTheDocument();
165
+ });
166
+ });
167
+
168
+ it('handles null descriptions', async () => {
169
+ mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
170
+ mockDjClient.DataJunctionAPI.listTags.mockResolvedValue([]);
171
+
172
+ const { getByPlaceholderText, getByText } = render(
173
+ <DJClientContext.Provider value={mockDjClient}>
174
+ <Search />
175
+ </DJClientContext.Provider>,
176
+ );
177
+
178
+ await waitFor(() => {
179
+ expect(mockDjClient.DataJunctionAPI.nodeDetails).toHaveBeenCalled();
180
+ });
181
+
182
+ const searchInput = getByPlaceholderText('Search');
183
+ fireEvent.change(searchInput, { target: { value: 'another' } });
184
+
185
+ await waitFor(() => {
186
+ expect(getByText(/Another Node/)).toBeInTheDocument();
187
+ });
188
+ });
189
+
190
+ it('limits search results to 20 items', async () => {
191
+ const manyNodes = Array.from({ length: 30 }, (_, i) => ({
192
+ name: `default.node${i}`,
193
+ display_name: `Node ${i}`,
194
+ description: `Description ${i}`,
195
+ type: 'transform',
196
+ }));
197
+
198
+ mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(manyNodes);
199
+ mockDjClient.DataJunctionAPI.listTags.mockResolvedValue([]);
200
+
201
+ const { getByPlaceholderText, container } = render(
202
+ <DJClientContext.Provider value={mockDjClient}>
203
+ <Search />
204
+ </DJClientContext.Provider>,
205
+ );
206
+
207
+ await waitFor(() => {
208
+ expect(mockDjClient.DataJunctionAPI.nodeDetails).toHaveBeenCalled();
209
+ });
210
+
211
+ const searchInput = getByPlaceholderText('Search');
212
+ fireEvent.change(searchInput, { target: { value: 'node' } });
213
+
214
+ await waitFor(() => {
215
+ const results = container.querySelectorAll('.search-result-item');
216
+ expect(results.length).toBeLessThanOrEqual(20);
217
+ });
218
+ });
219
+
220
+ it('handles error when fetching nodes', async () => {
221
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
222
+ mockDjClient.DataJunctionAPI.nodeDetails.mockRejectedValue(
223
+ new Error('Network error'),
224
+ );
225
+ mockDjClient.DataJunctionAPI.listTags.mockResolvedValue([]);
226
+
227
+ render(
228
+ <DJClientContext.Provider value={mockDjClient}>
229
+ <Search />
230
+ </DJClientContext.Provider>,
231
+ );
232
+
233
+ await waitFor(() => {
234
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
235
+ 'Error fetching nodes or tags:',
236
+ expect.any(Error),
237
+ );
238
+ });
239
+
240
+ consoleErrorSpy.mockRestore();
241
+ });
242
+
243
+ it('prevents form submission', async () => {
244
+ mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue([]);
245
+ mockDjClient.DataJunctionAPI.listTags.mockResolvedValue([]);
246
+
247
+ const { container } = render(
248
+ <DJClientContext.Provider value={mockDjClient}>
249
+ <Search />
250
+ </DJClientContext.Provider>,
251
+ );
252
+
253
+ const form = container.querySelector('form');
254
+
255
+ const submitEvent = new Event('submit', {
256
+ bubbles: true,
257
+ cancelable: true,
258
+ });
259
+ const preventDefaultSpy = jest.spyOn(submitEvent, 'preventDefault');
260
+
261
+ form.dispatchEvent(submitEvent);
262
+
263
+ expect(preventDefaultSpy).toHaveBeenCalled();
264
+ });
265
+
266
+ it('handles empty tags array', async () => {
267
+ mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
268
+ mockDjClient.DataJunctionAPI.listTags.mockResolvedValue(null);
269
+
270
+ const { getByPlaceholderText } = render(
271
+ <DJClientContext.Provider value={mockDjClient}>
272
+ <Search />
273
+ </DJClientContext.Provider>,
274
+ );
275
+
276
+ await waitFor(() => {
277
+ expect(mockDjClient.DataJunctionAPI.listTags).toHaveBeenCalled();
278
+ });
279
+
280
+ // Should not throw an error
281
+ const searchInput = getByPlaceholderText('Search');
282
+ expect(searchInput).toBeInTheDocument();
283
+ });
284
+
285
+ it('shows description separator correctly', async () => {
286
+ mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
287
+ mockDjClient.DataJunctionAPI.listTags.mockResolvedValue([]);
288
+
289
+ const { getByPlaceholderText, container } = render(
290
+ <DJClientContext.Provider value={mockDjClient}>
291
+ <Search />
292
+ </DJClientContext.Provider>,
293
+ );
294
+
295
+ await waitFor(() => {
296
+ expect(mockDjClient.DataJunctionAPI.nodeDetails).toHaveBeenCalled();
297
+ });
298
+
299
+ const searchInput = getByPlaceholderText('Search');
300
+ fireEvent.change(searchInput, { target: { value: 'test' } });
301
+
302
+ await waitFor(() => {
303
+ const results = container.querySelector('.search-result-item');
304
+ expect(results).toBeInTheDocument();
305
+ });
306
+ });
307
+ });
@@ -0,0 +1,27 @@
1
+ import * as React from 'react';
2
+ import { render, fireEvent } from '@testing-library/react';
3
+
4
+ import Tab from '../Tab';
5
+
6
+ describe('<Tab />', () => {
7
+ it('renders without crashing', () => {
8
+ render(<Tab />);
9
+ });
10
+
11
+ it('has the active class when selectedTab matches id', () => {
12
+ const { container } = render(<Tab id="1" selectedTab="1" />);
13
+ expect(container.querySelector('.col')).toHaveClass('active');
14
+ });
15
+
16
+ it('does not have the active class when selectedTab does not match id', () => {
17
+ const { container } = render(<Tab id="1" selectedTab="2" />);
18
+ expect(container.querySelector('.col')).not.toHaveClass('active');
19
+ });
20
+
21
+ it('calls onClick when the button is clicked', () => {
22
+ const onClickMock = jest.fn();
23
+ const { getByRole } = render(<Tab id="1" onClick={onClickMock} />);
24
+ fireEvent.click(getByRole('button'));
25
+ expect(onClickMock).toHaveBeenCalledTimes(1);
26
+ });
27
+ });
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import { render, fireEvent, screen } from '@testing-library/react';
3
+ import ToggleSwitch from '../ToggleSwitch';
4
+
5
+ describe('<ToggleSwitch />', () => {
6
+ const defaultProps = {
7
+ checked: false,
8
+ onChange: jest.fn(),
9
+ toggleName: 'Toggle Switch',
10
+ };
11
+
12
+ it('renders without crashing', () => {
13
+ render(<ToggleSwitch {...defaultProps} />);
14
+ });
15
+
16
+ it('displays the correct toggle name', () => {
17
+ render(<ToggleSwitch {...defaultProps} />);
18
+ expect(screen.getByText(defaultProps.toggleName)).toBeInTheDocument();
19
+ });
20
+
21
+ it('reflects the checked state correctly', () => {
22
+ render(<ToggleSwitch {...defaultProps} checked={true} />);
23
+ const checkbox = screen.getByRole('checkbox');
24
+ expect(checkbox).toBeChecked();
25
+ });
26
+
27
+ it('calls onChange with the correct value when toggled', () => {
28
+ render(<ToggleSwitch {...defaultProps} />);
29
+ const checkbox = screen.getByRole('checkbox');
30
+
31
+ fireEvent.click(checkbox);
32
+ expect(defaultProps.onChange).toHaveBeenCalledWith(true);
33
+
34
+ fireEvent.click(checkbox);
35
+ expect(checkbox).not.toBeChecked();
36
+ });
37
+
38
+ it('is unchecked by default if no checked prop is provided', () => {
39
+ render(<ToggleSwitch onChange={jest.fn()} toggleName="Test Toggle" />);
40
+ const checkbox = screen.getByRole('checkbox');
41
+ expect(checkbox).not.toBeChecked();
42
+ });
43
+ });