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
@@ -1,51 +1,1010 @@
1
1
  import { MarkerType } from 'reactflow';
2
2
 
3
- const DJ_URL = 'http://localhost:8000'; //process.env.REACT_APP_DJ_URL;
3
+ const DJ_URL = process.env.REACT_APP_DJ_URL
4
+ ? process.env.REACT_APP_DJ_URL
5
+ : 'http://localhost:8000';
6
+
7
+ const DJ_GQL = process.env.REACT_APP_DJ_GQL
8
+ ? process.env.REACT_APP_DJ_GQL
9
+ : process.env.REACT_APP_DJ_URL + '/graphql';
4
10
 
5
11
  export const DataJunctionAPI = {
12
+ listNodesForLanding: async function (
13
+ namespace,
14
+ nodeTypes,
15
+ tags,
16
+ editedBy,
17
+ before,
18
+ after,
19
+ limit,
20
+ sortConfig,
21
+ mode,
22
+ ) {
23
+ const query = `
24
+ query ListNodes($namespace: String, $nodeTypes: [NodeType!], $tags: [String!], $editedBy: String, $mode: NodeMode, $before: String, $after: String, $limit: Int, $orderBy: NodeSortField, $ascending: Boolean) {
25
+ findNodesPaginated(
26
+ namespace: $namespace
27
+ nodeTypes: $nodeTypes
28
+ tags: $tags
29
+ editedBy: $editedBy
30
+ mode: $mode
31
+ limit: $limit
32
+ before: $before
33
+ after: $after
34
+ orderBy: $orderBy,
35
+ ascending: $ascending
36
+ ) {
37
+ pageInfo {
38
+ hasNextPage
39
+ endCursor
40
+ hasPrevPage
41
+ startCursor
42
+ }
43
+ edges {
44
+ node {
45
+ name
46
+ type
47
+ currentVersion
48
+ tags {
49
+ name
50
+ tagType
51
+ }
52
+ editedBy
53
+ current {
54
+ displayName
55
+ status
56
+ mode
57
+ updatedAt
58
+ }
59
+ createdBy {
60
+ username
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+ `;
67
+ const sortOrderMapping = {
68
+ name: 'NAME',
69
+ displayName: 'DISPLAY_NAME',
70
+ type: 'TYPE',
71
+ status: 'STATUS',
72
+ updatedAt: 'UPDATED_AT',
73
+ };
74
+
75
+ return await (
76
+ await fetch(DJ_GQL, {
77
+ method: 'POST',
78
+ headers: {
79
+ 'Content-Type': 'application/json',
80
+ },
81
+ credentials: 'include',
82
+ body: JSON.stringify({
83
+ query,
84
+ variables: {
85
+ namespace: namespace,
86
+ nodeTypes: nodeTypes,
87
+ tags: tags,
88
+ editedBy: editedBy,
89
+ mode: mode || null,
90
+ before: before,
91
+ after: after,
92
+ limit: limit,
93
+ orderBy: sortOrderMapping[sortConfig.key],
94
+ ascending: sortConfig.direction === 'ascending',
95
+ },
96
+ }),
97
+ })
98
+ ).json();
99
+ },
100
+
101
+ whoami: async function () {
102
+ return await (
103
+ await fetch(`${DJ_URL}/whoami/`, { credentials: 'include' })
104
+ ).json();
105
+ },
106
+
107
+ querySystemMetric: async function ({
108
+ metric,
109
+ dimensions = [],
110
+ filters = [],
111
+ orderby = [],
112
+ }) {
113
+ const params = new URLSearchParams();
114
+ dimensions.forEach(d => params.append('dimensions', d));
115
+ filters.forEach(f => params.append('filters', f));
116
+ orderby.forEach(o => params.append('orderby', o));
117
+
118
+ const url = `${DJ_URL}/system/data/${metric}?${params.toString()}`;
119
+ const res = await fetch(url, { credentials: 'include' });
120
+
121
+ if (!res.ok) {
122
+ throw new Error(`Failed to fetch metric data ${metric}: ${res.status}`);
123
+ }
124
+ return await res.json();
125
+ },
126
+
127
+ querySystemMetricSingleDimension: async function ({
128
+ metric,
129
+ dimension,
130
+ filters = [],
131
+ orderby = [],
132
+ }) {
133
+ const results = await DataJunctionAPI.querySystemMetric({
134
+ metric: metric,
135
+ dimensions: [dimension],
136
+ filters: filters,
137
+ orderby: orderby,
138
+ });
139
+ return results.map(row => {
140
+ return {
141
+ name:
142
+ row.find(entry => entry.col === dimension)?.value?.toString() ??
143
+ 'unknown',
144
+ value: row.find(entry => entry.col === metric)?.value ?? 0,
145
+ };
146
+ });
147
+ },
148
+
149
+ system: {
150
+ node_counts_by_active: async function () {
151
+ return DataJunctionAPI.querySystemMetricSingleDimension({
152
+ metric: 'system.dj.number_of_nodes',
153
+ dimension: 'system.dj.is_active.active_id',
154
+ });
155
+ },
156
+ node_counts_by_type: async function () {
157
+ return DataJunctionAPI.querySystemMetricSingleDimension({
158
+ metric: 'system.dj.number_of_nodes',
159
+ dimension: 'system.dj.node_type.type',
160
+ filters: ['system.dj.is_active.active_id=true'],
161
+ orderby: ['system.dj.node_type.type'],
162
+ });
163
+ },
164
+ node_counts_by_status: async function () {
165
+ return DataJunctionAPI.querySystemMetricSingleDimension({
166
+ metric: 'system.dj.number_of_nodes',
167
+ dimension: 'system.dj.nodes.status',
168
+ filters: ['system.dj.is_active.active_id=true'],
169
+ orderby: ['system.dj.nodes.status'],
170
+ });
171
+ },
172
+ nodes_without_description: async function () {
173
+ return DataJunctionAPI.querySystemMetricSingleDimension({
174
+ metric: 'system.dj.node_without_description',
175
+ dimension: 'system.dj.node_type.type',
176
+ filters: ['system.dj.is_active.active_id=true'],
177
+ orderby: ['system.dj.node_type.type'],
178
+ });
179
+ },
180
+ node_trends: async function () {
181
+ const results = await (
182
+ await fetch(
183
+ `${DJ_URL}/system/data/system.dj.number_of_nodes?dimensions=system.dj.nodes.created_at_week&dimensions=system.dj.node_type.type&filters=system.dj.nodes.created_at_week>=20240101&orderby=system.dj.nodes.created_at_week`,
184
+ { credentials: 'include' },
185
+ )
186
+ ).json();
187
+ const byDateint = {};
188
+ results.forEach(row => {
189
+ const dateint = row.find(
190
+ r => r.col === 'system.dj.nodes.created_at_week',
191
+ )?.value;
192
+ const nodeType = row.find(
193
+ r => r.col === 'system.dj.node_type.type',
194
+ )?.value;
195
+ const count = row.find(
196
+ r => r.col === 'system.dj.number_of_nodes',
197
+ )?.value;
198
+ if (!byDateint[dateint]) {
199
+ byDateint[dateint] = { date: dateint };
200
+ }
201
+ byDateint[dateint][nodeType] =
202
+ (byDateint[dateint][nodeType] || 0) + count;
203
+ });
204
+ return Object.entries(byDateint).map(([dateint, data]) => {
205
+ return {
206
+ date: dateint,
207
+ ...data,
208
+ };
209
+ });
210
+ },
211
+ materialization_counts_by_type: async function () {
212
+ return DataJunctionAPI.querySystemMetricSingleDimension({
213
+ metric: 'system.dj.number_of_materializations',
214
+ dimension: 'system.dj.node_type.type',
215
+ filters: ['system.dj.is_active.active_id=true'],
216
+ orderby: ['system.dj.node_type.type'],
217
+ });
218
+ },
219
+
220
+ dimensions: async function () {
221
+ return await (
222
+ await fetch(`${DJ_URL}/system/dimensions`, {
223
+ credentials: 'include',
224
+ })
225
+ ).json();
226
+ },
227
+ },
228
+
229
+ logout: async function () {
230
+ return await fetch(`${DJ_URL}/logout/`, {
231
+ credentials: 'include',
232
+ method: 'POST',
233
+ });
234
+ },
235
+
236
+ catalogs: async function () {
237
+ return await (
238
+ await fetch(`${DJ_URL}/catalogs`, {
239
+ credentials: 'include',
240
+ })
241
+ ).json();
242
+ },
243
+
244
+ engines: async function () {
245
+ return await (
246
+ await fetch(`${DJ_URL}/engines`, {
247
+ credentials: 'include',
248
+ })
249
+ ).json();
250
+ },
251
+
6
252
  node: async function (name) {
7
- const data = await (await fetch(DJ_URL + '/nodes/' + name + '/')).json();
253
+ const data = await (
254
+ await fetch(`${DJ_URL}/nodes/${name}/`, {
255
+ credentials: 'include',
256
+ })
257
+ ).json();
258
+ if (data.message !== undefined) {
259
+ return data;
260
+ }
261
+ data.primary_key = data.columns
262
+ .filter(col =>
263
+ col.attributes.some(attr => attr.attribute_type.name === 'primary_key'),
264
+ )
265
+ .map(col => col.name);
8
266
  return data;
9
267
  },
10
268
 
269
+ getNodeForEditing: async function (name) {
270
+ const query = `
271
+ query GetNodeForEditing($name: String!) {
272
+ findNodes (names: [$name]) {
273
+ name
274
+ type
275
+ current {
276
+ displayName
277
+ description
278
+ primaryKey
279
+ query
280
+ parents { name }
281
+ metricMetadata {
282
+ direction
283
+ unit { name }
284
+ expression
285
+ significantDigits
286
+ incompatibleDruidFunctions
287
+ }
288
+ requiredDimensions {
289
+ name
290
+ }
291
+ mode
292
+ customMetadata
293
+ }
294
+ tags {
295
+ name
296
+ displayName
297
+ }
298
+ owners {
299
+ username
300
+ }
301
+ }
302
+ }
303
+ `;
304
+
305
+ const results = await (
306
+ await fetch(DJ_GQL, {
307
+ method: 'POST',
308
+ headers: {
309
+ 'Content-Type': 'application/json',
310
+ },
311
+ credentials: 'include',
312
+ body: JSON.stringify({
313
+ query,
314
+ variables: {
315
+ name: name,
316
+ },
317
+ }),
318
+ })
319
+ ).json();
320
+ if (results.data.findNodes.length === 0) {
321
+ return null;
322
+ }
323
+ return results.data.findNodes[0];
324
+ },
325
+
326
+ // Fetch basic node info for multiple nodes by name (for Settings page)
327
+ getNodesByNames: async function (names) {
328
+ if (!names || names.length === 0) {
329
+ return [];
330
+ }
331
+ const query = `
332
+ query GetNodesByNames($names: [String!]) {
333
+ findNodes(names: $names) {
334
+ name
335
+ type
336
+ current {
337
+ displayName
338
+ status
339
+ mode
340
+ }
341
+ }
342
+ }
343
+ `;
344
+
345
+ const results = await (
346
+ await fetch(DJ_GQL, {
347
+ method: 'POST',
348
+ headers: {
349
+ 'Content-Type': 'application/json',
350
+ },
351
+ credentials: 'include',
352
+ body: JSON.stringify({
353
+ query,
354
+ variables: { names },
355
+ }),
356
+ })
357
+ ).json();
358
+ return results.data?.findNodes || [];
359
+ },
360
+
361
+ getMetric: async function (name) {
362
+ const query = `
363
+ query GetMetric($name: String!) {
364
+ findNodes (names: [$name]) {
365
+ name
366
+ current {
367
+ parents { name }
368
+ metricMetadata {
369
+ direction
370
+ unit { name }
371
+ expression
372
+ significantDigits
373
+ incompatibleDruidFunctions
374
+ }
375
+ requiredDimensions {
376
+ name
377
+ }
378
+ }
379
+ }
380
+ }
381
+ `;
382
+
383
+ const results = await (
384
+ await fetch(DJ_GQL, {
385
+ method: 'POST',
386
+ headers: {
387
+ 'Content-Type': 'application/json',
388
+ },
389
+ credentials: 'include',
390
+ body: JSON.stringify({
391
+ query,
392
+ variables: {
393
+ name: name,
394
+ },
395
+ }),
396
+ })
397
+ ).json();
398
+ return results.data.findNodes[0];
399
+ },
400
+
401
+ getCubeForEditing: async function (name) {
402
+ const query = `
403
+ query GetCubeForEditing($name: String!) {
404
+ findNodes(names: [$name]) {
405
+ name
406
+ type
407
+ owners {
408
+ username
409
+ }
410
+ current {
411
+ displayName
412
+ description
413
+ mode
414
+ cubeMetrics {
415
+ name
416
+ }
417
+ cubeDimensions {
418
+ name
419
+ attribute
420
+ properties
421
+ }
422
+ }
423
+ tags {
424
+ name
425
+ displayName
426
+ }
427
+ }
428
+ }
429
+ `;
430
+
431
+ const results = await (
432
+ await fetch(DJ_GQL, {
433
+ method: 'POST',
434
+ headers: {
435
+ 'Content-Type': 'application/json',
436
+ },
437
+ credentials: 'include',
438
+ body: JSON.stringify({
439
+ query,
440
+ variables: {
441
+ name: name,
442
+ },
443
+ }),
444
+ })
445
+ ).json();
446
+ if (results.data.findNodes.length === 0) {
447
+ return null;
448
+ }
449
+ return results.data.findNodes[0];
450
+ },
451
+
452
+ nodes: async function (prefix) {
453
+ const queryParams = prefix ? `?prefix=${prefix}` : '';
454
+ return await (
455
+ await fetch(`${DJ_URL}/nodes/${queryParams}`, {
456
+ credentials: 'include',
457
+ })
458
+ ).json();
459
+ },
460
+
461
+ nodesWithType: async function (nodeType) {
462
+ return await (
463
+ await fetch(`${DJ_URL}/nodes/?node_type=${nodeType}`, {
464
+ credentials: 'include',
465
+ })
466
+ ).json();
467
+ },
468
+
469
+ nodeDetails: async () => {
470
+ return await (
471
+ await fetch(`${DJ_URL}/nodes/details/`, {
472
+ credentials: 'include',
473
+ })
474
+ ).json();
475
+ },
476
+
477
+ validateNode: async function (
478
+ nodeType,
479
+ name,
480
+ display_name,
481
+ description,
482
+ query,
483
+ ) {
484
+ const response = await fetch(`${DJ_URL}/nodes/validate`, {
485
+ method: 'POST',
486
+ headers: {
487
+ 'Content-Type': 'application/json',
488
+ },
489
+ body: JSON.stringify({
490
+ name: name,
491
+ display_name: display_name,
492
+ description: description,
493
+ query: query,
494
+ type: nodeType,
495
+ mode: 'published',
496
+ }),
497
+ credentials: 'include',
498
+ });
499
+ return { status: response.status, json: await response.json() };
500
+ },
501
+
502
+ createNode: async function (
503
+ nodeType,
504
+ name,
505
+ display_name,
506
+ description,
507
+ query,
508
+ mode,
509
+ namespace,
510
+ primary_key,
511
+ metric_direction,
512
+ metric_unit,
513
+ required_dimensions,
514
+ custom_metadata,
515
+ ) {
516
+ const metricMetadata =
517
+ metric_direction || metric_unit
518
+ ? {
519
+ direction: metric_direction,
520
+ unit: metric_unit,
521
+ }
522
+ : null;
523
+ const response = await fetch(`${DJ_URL}/nodes/${nodeType}`, {
524
+ method: 'POST',
525
+ headers: {
526
+ 'Content-Type': 'application/json',
527
+ },
528
+ body: JSON.stringify({
529
+ name: name,
530
+ display_name: display_name,
531
+ description: description,
532
+ query: query,
533
+ mode: mode,
534
+ namespace: namespace,
535
+ primary_key: primary_key,
536
+ metric_metadata: metricMetadata,
537
+ required_dimensions: required_dimensions,
538
+ custom_metadata: custom_metadata,
539
+ }),
540
+ credentials: 'include',
541
+ });
542
+ return { status: response.status, json: await response.json() };
543
+ },
544
+
545
+ patchNode: async function (
546
+ name,
547
+ display_name,
548
+ description,
549
+ query,
550
+ mode,
551
+ primary_key,
552
+ metric_direction,
553
+ metric_unit,
554
+ significant_digits,
555
+ required_dimensions,
556
+ owners,
557
+ custom_metadata,
558
+ ) {
559
+ try {
560
+ const metricMetadata =
561
+ metric_direction || metric_unit
562
+ ? {
563
+ direction: metric_direction,
564
+ unit: metric_unit,
565
+ significant_digits: significant_digits || null,
566
+ }
567
+ : null;
568
+ const response = await fetch(`${DJ_URL}/nodes/${name}`, {
569
+ method: 'PATCH',
570
+ headers: {
571
+ 'Content-Type': 'application/json',
572
+ },
573
+ body: JSON.stringify({
574
+ display_name: display_name,
575
+ description: description,
576
+ query: query,
577
+ mode: mode,
578
+ primary_key: primary_key,
579
+ metric_metadata: metricMetadata,
580
+ required_dimensions: required_dimensions,
581
+ owners: owners,
582
+ custom_metadata: custom_metadata,
583
+ }),
584
+ credentials: 'include',
585
+ });
586
+ return { status: response.status, json: await response.json() };
587
+ } catch (error) {
588
+ return { status: 500, json: { message: 'Update failed' } };
589
+ }
590
+ },
591
+
592
+ createCube: async function (
593
+ name,
594
+ display_name,
595
+ description,
596
+ mode,
597
+ metrics,
598
+ dimensions,
599
+ filters,
600
+ ) {
601
+ const response = await fetch(`${DJ_URL}/nodes/cube`, {
602
+ method: 'POST',
603
+ headers: {
604
+ 'Content-Type': 'application/json',
605
+ },
606
+ body: JSON.stringify({
607
+ name: name,
608
+ display_name: display_name,
609
+ description: description,
610
+ metrics: metrics,
611
+ dimensions: dimensions,
612
+ filters: filters,
613
+ mode: mode,
614
+ }),
615
+ credentials: 'include',
616
+ });
617
+ return { status: response.status, json: await response.json() };
618
+ },
619
+
620
+ patchCube: async function (
621
+ name,
622
+ display_name,
623
+ description,
624
+ mode,
625
+ metrics,
626
+ dimensions,
627
+ filters,
628
+ owners,
629
+ ) {
630
+ const url = `${DJ_URL}/nodes/${name}`;
631
+ const response = await fetch(url, {
632
+ method: 'PATCH',
633
+ headers: {
634
+ 'Content-Type': 'application/json',
635
+ },
636
+ body: JSON.stringify({
637
+ display_name: display_name,
638
+ description: description,
639
+ metrics: metrics,
640
+ dimensions: dimensions,
641
+ filters: filters || [],
642
+ mode: mode,
643
+ owners: owners,
644
+ }),
645
+ credentials: 'include',
646
+ });
647
+ return { status: response.status, json: await response.json() };
648
+ },
649
+
650
+ refreshLatestMaterialization: async function (name) {
651
+ const url = `${DJ_URL}/nodes/${name}?refresh_materialization=true`;
652
+ const response = await fetch(url, {
653
+ method: 'PATCH',
654
+ headers: {
655
+ 'Content-Type': 'application/json',
656
+ },
657
+ body: JSON.stringify({}),
658
+ credentials: 'include',
659
+ });
660
+ return { status: response.status, json: await response.json() };
661
+ },
662
+
663
+ registerTable: async function (catalog, schema, table) {
664
+ const response = await fetch(
665
+ `${DJ_URL}/register/table/${catalog}/${schema}/${table}`,
666
+ {
667
+ method: 'POST',
668
+ headers: {
669
+ 'Content-Type': 'application/json',
670
+ },
671
+ credentials: 'include',
672
+ },
673
+ );
674
+ return { status: response.status, json: await response.json() };
675
+ },
676
+
11
677
  upstreams: async function (name) {
12
- const data = await (
13
- await fetch(DJ_URL + '/nodes/' + name + '/upstream/')
678
+ return await (
679
+ await fetch(`${DJ_URL}/nodes/${name}/upstream/`, {
680
+ credentials: 'include',
681
+ })
14
682
  ).json();
15
- return data;
16
683
  },
17
684
 
18
685
  downstreams: async function (name) {
19
- const data = await (
20
- await fetch(DJ_URL + '/nodes/' + name + '/downstream/')
686
+ return await (
687
+ await fetch(`${DJ_URL}/nodes/${name}/downstream/`, {
688
+ credentials: 'include',
689
+ })
690
+ ).json();
691
+ },
692
+
693
+ // GraphQL-based upstream/downstream queries - more efficient as they only fetch needed fields
694
+ upstreamsGQL: async function (nodeNames) {
695
+ const names = Array.isArray(nodeNames) ? nodeNames : [nodeNames];
696
+ const query = `
697
+ query GetUpstreamNodes($nodeNames: [String!]!) {
698
+ upstreamNodes(nodeNames: $nodeNames) {
699
+ name
700
+ type
701
+ }
702
+ }
703
+ `;
704
+ const results = await (
705
+ await fetch(DJ_GQL, {
706
+ method: 'POST',
707
+ headers: { 'Content-Type': 'application/json' },
708
+ credentials: 'include',
709
+ body: JSON.stringify({ query, variables: { nodeNames: names } }),
710
+ })
711
+ ).json();
712
+ return results.data?.upstreamNodes || [];
713
+ },
714
+
715
+ downstreamsGQL: async function (nodeNames) {
716
+ const names = Array.isArray(nodeNames) ? nodeNames : [nodeNames];
717
+ const query = `
718
+ query GetDownstreamNodes($nodeNames: [String!]!) {
719
+ downstreamNodes(nodeNames: $nodeNames) {
720
+ name
721
+ type
722
+ }
723
+ }
724
+ `;
725
+ const results = await (
726
+ await fetch(DJ_GQL, {
727
+ method: 'POST',
728
+ headers: { 'Content-Type': 'application/json' },
729
+ credentials: 'include',
730
+ body: JSON.stringify({ query, variables: { nodeNames: names } }),
731
+ })
732
+ ).json();
733
+ return results.data?.downstreamNodes || [];
734
+ },
735
+
736
+ node_dag: async function (name) {
737
+ return await (
738
+ await fetch(`${DJ_URL}/nodes/${name}/dag/`, {
739
+ credentials: 'include',
740
+ })
741
+ ).json();
742
+ },
743
+
744
+ node_lineage: async function (name) {
745
+ return await (
746
+ await fetch(`${DJ_URL}/nodes/${name}/lineage/`, {
747
+ credentials: 'include',
748
+ })
21
749
  ).json();
22
- return data;
23
750
  },
24
751
 
25
752
  metric: async function (name) {
26
- const data = await (await fetch(DJ_URL + '/metrics/' + name + '/')).json();
27
- return data;
753
+ return await (
754
+ await fetch(`${DJ_URL}/metrics/${name}/`, {
755
+ credentials: 'include',
756
+ })
757
+ ).json();
28
758
  },
29
759
 
30
- namespace: async function (nmspce) {
760
+ clientCode: async function (name) {
761
+ return await (
762
+ await fetch(`${DJ_URL}/datajunction-clients/python/new_node/${name}`, {
763
+ credentials: 'include',
764
+ })
765
+ ).json();
766
+ },
767
+
768
+ cube: async function (name) {
769
+ return await (
770
+ await fetch(`${DJ_URL}/cubes/${name}/`, {
771
+ credentials: 'include',
772
+ })
773
+ ).json();
774
+ },
775
+
776
+ metrics: async function (name) {
777
+ return await (
778
+ await fetch(`${DJ_URL}/metrics/`, {
779
+ credentials: 'include',
780
+ })
781
+ ).json();
782
+ },
783
+
784
+ commonDimensions: async function (metrics) {
785
+ const metricsQuery = '?' + metrics.map(m => `metric=${m}`).join('&');
786
+ return await (
787
+ await fetch(`${DJ_URL}/metrics/common/dimensions/${metricsQuery}`, {
788
+ credentials: 'include',
789
+ })
790
+ ).json();
791
+ },
792
+
793
+ history: async function (type, name, offset, limit) {
794
+ return await (
795
+ await fetch(
796
+ `${DJ_URL}/history?node=${name}&offset=${offset ? offset : 0}&limit=${
797
+ limit ? limit : 100
798
+ }`,
799
+ {
800
+ credentials: 'include',
801
+ },
802
+ )
803
+ ).json();
804
+ },
805
+
806
+ revisions: async function (name) {
807
+ return await (
808
+ await fetch(`${DJ_URL}/nodes/${name}/revisions/`, {
809
+ credentials: 'include',
810
+ })
811
+ ).json();
812
+ },
813
+
814
+ namespace: async function (nmspce, editedBy) {
815
+ return await (
816
+ await fetch(
817
+ `${DJ_URL}/namespaces/${nmspce}?edited_by=${editedBy}&with_edited_by=true`,
818
+ {
819
+ credentials: 'include',
820
+ },
821
+ )
822
+ ).json();
823
+ },
824
+
825
+ namespaces: async function () {
826
+ return await (
827
+ await fetch(`${DJ_URL}/namespaces/`, {
828
+ credentials: 'include',
829
+ })
830
+ ).json();
831
+ },
832
+
833
+ sql: async function (metric_name, selection) {
834
+ const params = new URLSearchParams(selection);
835
+ for (const [key, value] of Object.entries(selection)) {
836
+ if (Array.isArray(value)) {
837
+ params.delete(key);
838
+ value.forEach(v => params.append(key, v));
839
+ }
840
+ }
841
+
842
+ return await (
843
+ await fetch(`${DJ_URL}/sql/${metric_name}?${params}`, {
844
+ credentials: 'include',
845
+ })
846
+ ).json();
847
+ },
848
+
849
+ nodesWithDimension: async function (name) {
850
+ return await (
851
+ await fetch(`${DJ_URL}/dimensions/${name}/nodes/`, {
852
+ credentials: 'include',
853
+ })
854
+ ).json();
855
+ },
856
+
857
+ materializations: async function (node) {
31
858
  const data = await (
32
- await fetch(DJ_URL + '/namespaces/' + nmspce + '/')
859
+ await fetch(
860
+ `${DJ_URL}/nodes/${node}/materializations?show_inactive=true&include_all_revisions=true`,
861
+ {
862
+ credentials: 'include',
863
+ },
864
+ )
33
865
  ).json();
866
+
34
867
  return data;
35
868
  },
36
869
 
37
- namespaces: async function () {
38
- const data = await (await fetch(DJ_URL + '/namespaces/')).json();
870
+ availabilityStates: async function (node) {
871
+ const data = await (
872
+ await fetch(`${DJ_URL}/nodes/${node}/availability/`, {
873
+ credentials: 'include',
874
+ })
875
+ ).json();
876
+
39
877
  return data;
40
878
  },
41
879
 
880
+ columns: async function (node) {
881
+ return await Promise.all(
882
+ node.columns.map(async col => {
883
+ return col;
884
+ }),
885
+ );
886
+ },
887
+
888
+ sqls: async function (metricSelection, dimensionSelection, filters) {
889
+ const params = new URLSearchParams();
890
+ metricSelection.map(metric => params.append('metrics', metric));
891
+ dimensionSelection.map(dimension => params.append('dimensions', dimension));
892
+ params.append('filters', filters);
893
+ return await (
894
+ await fetch(`${DJ_URL}/sql/?${params}`, {
895
+ credentials: 'include',
896
+ })
897
+ ).json();
898
+ },
899
+
900
+ data: async function (metricSelection, dimensionSelection) {
901
+ const params = new URLSearchParams();
902
+ metricSelection.map(metric => params.append('metrics', metric));
903
+ dimensionSelection.map(dimension => params.append('dimensions', dimension));
904
+ return await (
905
+ await fetch(`${DJ_URL}/data/?` + params + '&limit=10000', {
906
+ credentials: 'include',
907
+ })
908
+ ).json();
909
+ },
910
+
911
+ nodeData: async function (nodeName, selection = null) {
912
+ if (selection === null) {
913
+ selection = {
914
+ dimensions: [],
915
+ filters: [],
916
+ };
917
+ }
918
+ const params = new URLSearchParams(selection);
919
+ for (const [key, value] of Object.entries(selection)) {
920
+ if (Array.isArray(value)) {
921
+ params.delete(key);
922
+ value.forEach(v => params.append(key, v));
923
+ }
924
+ }
925
+ params.append('limit', '1000');
926
+ params.append('async_', 'true');
927
+
928
+ return await (
929
+ await fetch(`${DJ_URL}/data/${nodeName}?${params}`, {
930
+ credentials: 'include',
931
+ headers: { 'Cache-Control': 'max-age=86400' },
932
+ })
933
+ ).json();
934
+ },
935
+
936
+ notebookExportCube: async function (cube) {
937
+ return await fetch(
938
+ `${DJ_URL}/datajunction-clients/python/notebook/?cube=${cube}`,
939
+ {
940
+ credentials: 'include',
941
+ },
942
+ );
943
+ },
944
+
945
+ notebookExportNamespace: async function (namespace) {
946
+ return await (
947
+ await fetch(
948
+ `${DJ_URL}/datajunction-clients/python/notebook/?namespace=${namespace}`,
949
+ {
950
+ credentials: 'include',
951
+ },
952
+ )
953
+ ).json();
954
+ },
955
+
956
+ stream: async function (metricSelection, dimensionSelection, filters) {
957
+ const params = new URLSearchParams();
958
+ metricSelection.map(metric => params.append('metrics', metric));
959
+ dimensionSelection.map(dimension => params.append('dimensions', dimension));
960
+ params.append('filters', filters);
961
+ return new EventSource(
962
+ `${DJ_URL}/stream/?${params}&limit=10000&async_=true`,
963
+ {
964
+ withCredentials: true,
965
+ },
966
+ );
967
+ },
968
+
969
+ streamNodeData: async function (nodeName, selection = null) {
970
+ if (selection === null) {
971
+ selection = {
972
+ dimensions: [],
973
+ filters: [],
974
+ };
975
+ }
976
+ const params = new URLSearchParams(selection);
977
+ for (const [key, value] of Object.entries(selection)) {
978
+ if (Array.isArray(value)) {
979
+ params.delete(key);
980
+ value.forEach(v => params.append(key, v));
981
+ }
982
+ }
983
+ params.append('limit', '1000');
984
+ params.append('async_', 'true');
985
+
986
+ return new EventSource(`${DJ_URL}/stream/${nodeName}?${params}`, {
987
+ withCredentials: true,
988
+ });
989
+ },
990
+
42
991
  lineage: async function (node) {},
43
992
 
993
+ compiledSql: async function (node) {
994
+ return await (
995
+ await fetch(`${DJ_URL}/sql/${node}/`, {
996
+ credentials: 'include',
997
+ })
998
+ ).json();
999
+ },
1000
+
44
1001
  dag: async function (namespace = 'default') {
45
1002
  const edges = [];
46
- const data = await (await fetch(DJ_URL + '/nodes/')).json();
47
-
48
- // const metrics = await (await fetch(DJ_URL + '/metrics/')).json();
1003
+ const data = await (
1004
+ await fetch(`${DJ_URL}/nodes/`, {
1005
+ credentials: 'include',
1006
+ })
1007
+ ).json();
49
1008
 
50
1009
  data.forEach(obj => {
51
1010
  obj.parents.forEach(parent => {
@@ -97,7 +1056,6 @@ export const DataJunctionAPI = {
97
1056
  const column_names = node.columns.map(col => {
98
1057
  return { name: col.name, type: col.type };
99
1058
  });
100
- // const dimensions = node.type === "metric" ? metrics.filter(metric => metric.name === node.name)[0].dimensions : [];
101
1059
  return {
102
1060
  id: String(node.name),
103
1061
  type: 'DJNode',
@@ -112,13 +1070,521 @@ export const DataJunctionAPI = {
112
1070
  type: node.type,
113
1071
  primary_key: primary_key,
114
1072
  column_names: column_names,
115
- // dimensions: dimensions,
116
1073
  },
117
- // parentNode: [node.name.split(".").slice(-2, -1)],
118
- // extent: 'parent',
119
1074
  };
120
1075
  });
121
1076
 
122
1077
  return { edges: edges, nodes: nodes, namespaces: namespaceNodes };
123
1078
  },
1079
+ attributes: async function () {
1080
+ return await (
1081
+ await fetch(`${DJ_URL}/attributes`, {
1082
+ credentials: 'include',
1083
+ })
1084
+ ).json();
1085
+ },
1086
+ setAttributes: async function (nodeName, columnName, attributes) {
1087
+ const response = await fetch(
1088
+ `${DJ_URL}/nodes/${nodeName}/columns/${columnName}/attributes`,
1089
+ {
1090
+ method: 'POST',
1091
+ headers: {
1092
+ 'Content-Type': 'application/json',
1093
+ },
1094
+ body: JSON.stringify(
1095
+ attributes.map(attribute => {
1096
+ return {
1097
+ namespace: 'system',
1098
+ name: attribute,
1099
+ };
1100
+ }),
1101
+ ),
1102
+ credentials: 'include',
1103
+ },
1104
+ );
1105
+ return { status: response.status, json: await response.json() };
1106
+ },
1107
+
1108
+ setColumnDescription: async function (nodeName, columnName, description) {
1109
+ const response = await fetch(
1110
+ `${DJ_URL}/nodes/${nodeName}/columns/${columnName}/description?description=${encodeURIComponent(
1111
+ description,
1112
+ )}`,
1113
+ {
1114
+ method: 'PATCH',
1115
+ credentials: 'include',
1116
+ },
1117
+ );
1118
+ return { status: response.status, json: await response.json() };
1119
+ },
1120
+ dimensions: async function () {
1121
+ return await (
1122
+ await fetch(`${DJ_URL}/dimensions`, {
1123
+ credentials: 'include',
1124
+ })
1125
+ ).json();
1126
+ },
1127
+ nodeDimensions: async function (nodeName) {
1128
+ return await (
1129
+ await fetch(`${DJ_URL}/nodes/${nodeName}/dimensions`, {
1130
+ credentials: 'include',
1131
+ })
1132
+ ).json();
1133
+ },
1134
+ linkDimension: async function (nodeName, columnName, dimensionName) {
1135
+ const response = await fetch(
1136
+ `${DJ_URL}/nodes/${nodeName}/columns/${columnName}?dimension=${dimensionName}`,
1137
+ {
1138
+ method: 'POST',
1139
+ headers: {
1140
+ 'Content-Type': 'application/json',
1141
+ },
1142
+ credentials: 'include',
1143
+ },
1144
+ );
1145
+ return { status: response.status, json: await response.json() };
1146
+ },
1147
+ unlinkDimension: async function (nodeName, columnName, dimensionName) {
1148
+ const response = await fetch(
1149
+ `${DJ_URL}/nodes/${nodeName}/columns/${columnName}?dimension=${dimensionName}`,
1150
+ {
1151
+ method: 'DELETE',
1152
+ headers: {
1153
+ 'Content-Type': 'application/json',
1154
+ },
1155
+ credentials: 'include',
1156
+ },
1157
+ );
1158
+ return { status: response.status, json: await response.json() };
1159
+ },
1160
+
1161
+ addComplexDimensionLink: async function (
1162
+ nodeName,
1163
+ dimensionNode,
1164
+ joinOn,
1165
+ joinType = null,
1166
+ joinCardinality = null,
1167
+ role = null,
1168
+ ) {
1169
+ const response = await fetch(`${DJ_URL}/nodes/${nodeName}/link`, {
1170
+ method: 'POST',
1171
+ headers: {
1172
+ 'Content-Type': 'application/json',
1173
+ },
1174
+ body: JSON.stringify({
1175
+ dimension_node: dimensionNode,
1176
+ join_type: joinType,
1177
+ join_on: joinOn,
1178
+ join_cardinality: joinCardinality,
1179
+ role: role,
1180
+ }),
1181
+ credentials: 'include',
1182
+ });
1183
+ return { status: response.status, json: await response.json() };
1184
+ },
1185
+
1186
+ removeComplexDimensionLink: async function (
1187
+ nodeName,
1188
+ dimensionNode,
1189
+ role = null,
1190
+ ) {
1191
+ const response = await fetch(`${DJ_URL}/nodes/${nodeName}/link`, {
1192
+ method: 'DELETE',
1193
+ headers: {
1194
+ 'Content-Type': 'application/json',
1195
+ },
1196
+ body: JSON.stringify({
1197
+ dimension_node: dimensionNode,
1198
+ role: role,
1199
+ }),
1200
+ credentials: 'include',
1201
+ });
1202
+ return { status: response.status, json: await response.json() };
1203
+ },
1204
+
1205
+ addReferenceDimensionLink: async function (
1206
+ nodeName,
1207
+ nodeColumn,
1208
+ dimensionNode,
1209
+ dimensionColumn,
1210
+ role = null,
1211
+ ) {
1212
+ const url = new URL(
1213
+ `${DJ_URL}/nodes/${nodeName}/columns/${nodeColumn}/link`,
1214
+ );
1215
+ url.searchParams.append('dimension_node', dimensionNode);
1216
+ url.searchParams.append('dimension_column', dimensionColumn);
1217
+ if (role) {
1218
+ url.searchParams.append('role', role);
1219
+ }
1220
+
1221
+ const response = await fetch(url.toString(), {
1222
+ method: 'POST',
1223
+ headers: {
1224
+ 'Content-Type': 'application/json',
1225
+ },
1226
+ credentials: 'include',
1227
+ });
1228
+ return { status: response.status, json: await response.json() };
1229
+ },
1230
+
1231
+ removeReferenceDimensionLink: async function (nodeName, nodeColumn) {
1232
+ const response = await fetch(
1233
+ `${DJ_URL}/nodes/${nodeName}/columns/${nodeColumn}/link`,
1234
+ {
1235
+ method: 'DELETE',
1236
+ headers: {
1237
+ 'Content-Type': 'application/json',
1238
+ },
1239
+ credentials: 'include',
1240
+ },
1241
+ );
1242
+ return { status: response.status, json: await response.json() };
1243
+ },
1244
+
1245
+ deactivate: async function (nodeName) {
1246
+ const response = await fetch(`${DJ_URL}/nodes/${nodeName}`, {
1247
+ method: 'DELETE',
1248
+ headers: {
1249
+ 'Content-Type': 'application/json',
1250
+ },
1251
+ credentials: 'include',
1252
+ });
1253
+ return { status: response.status, json: await response.json() };
1254
+ },
1255
+ addNamespace: async function (namespace) {
1256
+ const response = await fetch(`${DJ_URL}/namespaces/${namespace}`, {
1257
+ method: 'POST',
1258
+ headers: {
1259
+ 'Content-Type': 'application/json',
1260
+ },
1261
+ credentials: 'include',
1262
+ });
1263
+ return { status: response.status, json: await response.json() };
1264
+ },
1265
+ listTags: async function () {
1266
+ const response = await fetch(`${DJ_URL}/tags`, {
1267
+ method: 'GET',
1268
+ headers: {
1269
+ 'Content-Type': 'application/json',
1270
+ },
1271
+ credentials: 'include',
1272
+ });
1273
+ return await response.json();
1274
+ },
1275
+ users: async function () {
1276
+ return await (
1277
+ await fetch(`${DJ_URL}/users?with_activity=true`, {
1278
+ credentials: 'include',
1279
+ })
1280
+ ).json();
1281
+ },
1282
+ getTag: async function (tagName) {
1283
+ const response = await fetch(`${DJ_URL}/tags/${tagName}`, {
1284
+ method: 'GET',
1285
+ headers: {
1286
+ 'Content-Type': 'application/json',
1287
+ },
1288
+ credentials: 'include',
1289
+ });
1290
+ return await response.json();
1291
+ },
1292
+ listNodesForTag: async function (tagName) {
1293
+ const response = await fetch(`${DJ_URL}/tags/${tagName}/nodes`, {
1294
+ method: 'GET',
1295
+ headers: {
1296
+ 'Content-Type': 'application/json',
1297
+ },
1298
+ credentials: 'include',
1299
+ });
1300
+ return await response.json();
1301
+ },
1302
+ tagsNode: async function (nodeName, tagNames) {
1303
+ const url = tagNames
1304
+ .map(value => `tag_names=${encodeURIComponent(value)}`)
1305
+ .join('&');
1306
+ const response = await fetch(`${DJ_URL}/nodes/${nodeName}/tags?${url}`, {
1307
+ method: 'POST',
1308
+ headers: {
1309
+ 'Content-Type': 'application/json',
1310
+ },
1311
+ credentials: 'include',
1312
+ });
1313
+ return { status: response.status, json: await response.json() };
1314
+ },
1315
+ addTag: async function (name, displayName, tagType, description) {
1316
+ const response = await fetch(`${DJ_URL}/tags`, {
1317
+ method: 'POST',
1318
+ headers: {
1319
+ 'Content-Type': 'application/json',
1320
+ },
1321
+ body: JSON.stringify({
1322
+ name: name,
1323
+ display_name: displayName,
1324
+ tag_type: tagType,
1325
+ description: description,
1326
+ }),
1327
+ credentials: 'include',
1328
+ });
1329
+ return { status: response.status, json: await response.json() };
1330
+ },
1331
+ editTag: async function (name, description, displayName) {
1332
+ const updates = {};
1333
+ if (description) {
1334
+ updates.description = description;
1335
+ }
1336
+ if (displayName) {
1337
+ updates.display_name = displayName;
1338
+ }
1339
+
1340
+ const response = await fetch(`${DJ_URL}/tags/${name}`, {
1341
+ method: 'PATCH',
1342
+ headers: {
1343
+ 'Content-Type': 'application/json',
1344
+ },
1345
+ body: JSON.stringify(updates),
1346
+ credentials: 'include',
1347
+ });
1348
+ return { status: response.status, json: await response.json() };
1349
+ },
1350
+ setPartition: async function (
1351
+ nodeName,
1352
+ columnName,
1353
+ partitionType,
1354
+ format,
1355
+ granularity,
1356
+ ) {
1357
+ const body = {
1358
+ type_: partitionType,
1359
+ };
1360
+ if (format) {
1361
+ body.format = format;
1362
+ }
1363
+ if (granularity) {
1364
+ body.granularity = granularity;
1365
+ }
1366
+ const response = await fetch(
1367
+ `${DJ_URL}/nodes/${nodeName}/columns/${columnName}/partition`,
1368
+ {
1369
+ method: 'POST',
1370
+ headers: {
1371
+ 'Content-Type': 'application/json',
1372
+ },
1373
+ body: JSON.stringify(body),
1374
+ credentials: 'include',
1375
+ },
1376
+ );
1377
+ return { status: response.status, json: await response.json() };
1378
+ },
1379
+ materialize: async function (nodeName, jobType, strategy, schedule, config) {
1380
+ const response = await fetch(
1381
+ `${DJ_URL}/nodes/${nodeName}/materialization`,
1382
+ {
1383
+ method: 'POST',
1384
+ headers: {
1385
+ 'Content-Type': 'application/json',
1386
+ },
1387
+ body: JSON.stringify({
1388
+ job: jobType,
1389
+ strategy: strategy,
1390
+ schedule: schedule,
1391
+ config: config,
1392
+ }),
1393
+ credentials: 'include',
1394
+ },
1395
+ );
1396
+ return { status: response.status, json: await response.json() };
1397
+ },
1398
+ materializeCube: async function (
1399
+ nodeName,
1400
+ jobType,
1401
+ strategy,
1402
+ schedule,
1403
+ lookbackWindow,
1404
+ ) {
1405
+ const response = await fetch(
1406
+ `${DJ_URL}/nodes/${nodeName}/materialization`,
1407
+ {
1408
+ method: 'POST',
1409
+ headers: {
1410
+ 'Content-Type': 'application/json',
1411
+ },
1412
+ body: JSON.stringify({
1413
+ job: jobType,
1414
+ strategy: strategy,
1415
+ schedule: schedule,
1416
+ lookback_window: lookbackWindow,
1417
+ }),
1418
+ credentials: 'include',
1419
+ },
1420
+ );
1421
+ return { status: response.status, json: await response.json() };
1422
+ },
1423
+ runBackfill: async function (nodeName, materializationName, partitionValues) {
1424
+ const response = await fetch(
1425
+ `${DJ_URL}/nodes/${nodeName}/materializations/${materializationName}/backfill`,
1426
+ {
1427
+ method: 'POST',
1428
+ headers: {
1429
+ 'Content-Type': 'application/json',
1430
+ },
1431
+ body: JSON.stringify(
1432
+ partitionValues.map(partitionValue => {
1433
+ return {
1434
+ column_name: partitionValue.columnName,
1435
+ range: partitionValue.range,
1436
+ values: partitionValue.values,
1437
+ };
1438
+ }),
1439
+ ),
1440
+ credentials: 'include',
1441
+ },
1442
+ );
1443
+ return { status: response.status, json: await response.json() };
1444
+ },
1445
+ deleteMaterialization: async function (
1446
+ nodeName,
1447
+ materializationName,
1448
+ nodeVersion = null,
1449
+ ) {
1450
+ let url = `${DJ_URL}/nodes/${nodeName}/materializations?materialization_name=${materializationName}`;
1451
+ if (nodeVersion) {
1452
+ url += `&node_version=${nodeVersion}`;
1453
+ }
1454
+ const response = await fetch(url, {
1455
+ method: 'DELETE',
1456
+ headers: {
1457
+ 'Content-Type': 'application/json',
1458
+ },
1459
+ credentials: 'include',
1460
+ });
1461
+ return { status: response.status, json: await response.json() };
1462
+ },
1463
+ listMetricMetadata: async function () {
1464
+ const response = await fetch(`${DJ_URL}/metrics/metadata`, {
1465
+ method: 'GET',
1466
+ headers: {
1467
+ 'Content-Type': 'application/json',
1468
+ },
1469
+ credentials: 'include',
1470
+ });
1471
+ return await response.json();
1472
+ },
1473
+ materializationInfo: async function () {
1474
+ return await (
1475
+ await fetch(`${DJ_URL}/materialization/info`, {
1476
+ credentials: 'include',
1477
+ })
1478
+ ).json();
1479
+ },
1480
+ revalidate: async function (node) {
1481
+ return await (
1482
+ await fetch(`${DJ_URL}/nodes/${node}/validate`, {
1483
+ method: 'POST',
1484
+ headers: {
1485
+ 'Content-Type': 'application/json',
1486
+ },
1487
+ credentials: 'include',
1488
+ })
1489
+ ).json();
1490
+ },
1491
+ // GET /notifications/
1492
+ getNotificationPreferences: async function (params = {}) {
1493
+ const query = new URLSearchParams(params).toString();
1494
+ return await (
1495
+ await fetch(`${DJ_URL}/notifications/${query ? `?${query}` : ''}`, {
1496
+ credentials: 'include',
1497
+ })
1498
+ ).json();
1499
+ },
1500
+
1501
+ // POST /notifications/subscribe
1502
+ subscribeToNotifications: async function ({
1503
+ entity_type,
1504
+ entity_name,
1505
+ activity_types,
1506
+ alert_types,
1507
+ }) {
1508
+ const response = await fetch(`${DJ_URL}/notifications/subscribe`, {
1509
+ method: 'POST',
1510
+ headers: {
1511
+ 'Content-Type': 'application/json',
1512
+ },
1513
+ credentials: 'include',
1514
+ body: JSON.stringify({
1515
+ entity_type,
1516
+ entity_name,
1517
+ activity_types,
1518
+ alert_types,
1519
+ }),
1520
+ });
1521
+
1522
+ return {
1523
+ status: response.status,
1524
+ json: await response.json(),
1525
+ };
1526
+ },
1527
+
1528
+ // DELETE /notifications/unsubscribe
1529
+ unsubscribeFromNotifications: async function ({ entity_type, entity_name }) {
1530
+ const url = new URL(`${DJ_URL}/notifications/unsubscribe`);
1531
+ url.searchParams.append('entity_type', entity_type);
1532
+ url.searchParams.append('entity_name', entity_name);
1533
+
1534
+ const response = await fetch(url.toString(), {
1535
+ method: 'DELETE',
1536
+ credentials: 'include',
1537
+ });
1538
+
1539
+ return {
1540
+ status: response.status,
1541
+ json: await response.json(),
1542
+ };
1543
+ },
1544
+
1545
+ // GET /history/ with only_subscribed filter
1546
+ getSubscribedHistory: async function (limit = 10) {
1547
+ return await (
1548
+ await fetch(`${DJ_URL}/history/?only_subscribed=true&limit=${limit}`, {
1549
+ credentials: 'include',
1550
+ })
1551
+ ).json();
1552
+ },
1553
+
1554
+ // POST /notifications/mark-read
1555
+ markNotificationsRead: async function () {
1556
+ const response = await fetch(`${DJ_URL}/notifications/mark-read`, {
1557
+ method: 'POST',
1558
+ credentials: 'include',
1559
+ });
1560
+ return await response.json();
1561
+ },
1562
+
1563
+ // Service Account APIs
1564
+ listServiceAccounts: async function () {
1565
+ const response = await fetch(`${DJ_URL}/service-accounts`, {
1566
+ credentials: 'include',
1567
+ });
1568
+ return await response.json();
1569
+ },
1570
+
1571
+ createServiceAccount: async function (name) {
1572
+ const response = await fetch(`${DJ_URL}/service-accounts`, {
1573
+ method: 'POST',
1574
+ headers: {
1575
+ 'Content-Type': 'application/json',
1576
+ },
1577
+ credentials: 'include',
1578
+ body: JSON.stringify({ name }),
1579
+ });
1580
+ return await response.json();
1581
+ },
1582
+
1583
+ deleteServiceAccount: async function (clientId) {
1584
+ const response = await fetch(`${DJ_URL}/service-accounts/${clientId}`, {
1585
+ method: 'DELETE',
1586
+ credentials: 'include',
1587
+ });
1588
+ return await response.json();
1589
+ },
124
1590
  };