datajunction-ui 0.0.1-rc.8 → 0.0.2

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 (205) 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/QueryInfo.jsx +172 -0
  16. package/src/app/components/Search.jsx +94 -0
  17. package/src/app/components/Tab.jsx +8 -1
  18. package/src/app/components/ToggleSwitch.jsx +20 -0
  19. package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
  20. package/src/app/components/__tests__/QueryInfo.test.jsx +55 -0
  21. package/src/app/components/__tests__/Search.test.jsx +63 -0
  22. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  23. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  24. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +8 -3
  25. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
  26. package/src/app/components/djgraph/Collapse.jsx +47 -0
  27. package/src/app/components/djgraph/DJNode.jsx +61 -83
  28. package/src/app/components/djgraph/DJNodeColumns.jsx +75 -0
  29. package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
  30. package/src/app/components/djgraph/LayoutFlow.jsx +106 -0
  31. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  32. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  33. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  34. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +84 -40
  35. package/src/app/components/forms/Action.jsx +8 -0
  36. package/src/app/components/forms/NodeNameField.jsx +64 -0
  37. package/src/app/components/search.css +17 -0
  38. package/src/app/constants.js +2 -0
  39. package/src/app/icons/AddItemIcon.jsx +16 -0
  40. package/src/app/icons/AlertIcon.jsx +33 -0
  41. package/src/app/icons/CollapsedIcon.jsx +15 -0
  42. package/src/app/icons/CommitIcon.jsx +45 -0
  43. package/src/app/icons/DJLogo.jsx +36 -0
  44. package/src/app/icons/DeleteIcon.jsx +21 -0
  45. package/src/app/icons/DiffIcon.jsx +63 -0
  46. package/src/app/icons/EditIcon.jsx +18 -0
  47. package/src/app/icons/ExpandedIcon.jsx +15 -0
  48. package/src/app/icons/EyeIcon.jsx +20 -0
  49. package/src/app/icons/FilterIcon.jsx +7 -0
  50. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  51. package/src/app/icons/InvalidIcon.jsx +16 -0
  52. package/src/app/icons/JupyterExportIcon.jsx +25 -0
  53. package/src/app/icons/LoadingIcon.jsx +14 -0
  54. package/src/app/icons/NodeIcon.jsx +49 -0
  55. package/src/app/icons/PythonIcon.jsx +14 -0
  56. package/src/app/icons/TableIcon.jsx +14 -0
  57. package/src/app/icons/ValidIcon.jsx +16 -0
  58. package/src/app/index.tsx +118 -37
  59. package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -0
  60. package/src/app/pages/AddEditNodePage/ColumnsSelect.jsx +84 -0
  61. package/src/app/pages/AddEditNodePage/DescriptionField.jsx +17 -0
  62. package/src/app/pages/AddEditNodePage/DisplayNameField.jsx +16 -0
  63. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +51 -0
  64. package/src/app/pages/AddEditNodePage/FullNameField.jsx +38 -0
  65. package/src/app/pages/AddEditNodePage/Loadable.jsx +20 -0
  66. package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +75 -0
  67. package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +71 -0
  68. package/src/app/pages/AddEditNodePage/NamespaceField.jsx +40 -0
  69. package/src/app/pages/AddEditNodePage/NodeModeField.jsx +14 -0
  70. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +94 -0
  71. package/src/app/pages/AddEditNodePage/OwnersField.jsx +54 -0
  72. package/src/app/pages/AddEditNodePage/RequiredDimensionsSelect.jsx +54 -0
  73. package/src/app/pages/AddEditNodePage/TagsField.jsx +47 -0
  74. package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +49 -0
  75. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +109 -0
  76. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +287 -0
  77. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
  78. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
  79. package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
  80. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
  81. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
  82. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
  83. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +224 -0
  84. package/src/app/pages/AddEditNodePage/index.jsx +506 -0
  85. package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
  86. package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
  87. package/src/app/pages/AddEditTagPage/index.jsx +132 -0
  88. package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +152 -0
  89. package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
  90. package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +75 -0
  91. package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +373 -0
  92. package/src/app/pages/CubeBuilderPage/index.jsx +291 -0
  93. package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
  94. package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
  95. package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
  96. package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
  97. package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
  98. package/src/app/pages/LoginPage/index.jsx +17 -0
  99. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
  100. package/src/app/pages/NamespacePage/Explorer.jsx +61 -0
  101. package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
  102. package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +30 -0
  103. package/src/app/pages/NamespacePage/TagSelect.jsx +44 -0
  104. package/src/app/pages/NamespacePage/UserSelect.jsx +47 -0
  105. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +297 -0
  106. package/src/app/pages/NamespacePage/index.jsx +319 -42
  107. package/src/app/pages/NodePage/AddBackfillPopover.jsx +165 -0
  108. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +222 -0
  109. package/src/app/pages/NodePage/AvailabilityStateBlock.jsx +67 -0
  110. package/src/app/pages/NodePage/ClientCodePopover.jsx +94 -0
  111. package/src/app/pages/NodePage/DimensionFilter.jsx +86 -0
  112. package/src/app/pages/NodePage/EditColumnDescriptionPopover.jsx +116 -0
  113. package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
  114. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +164 -0
  115. package/src/app/pages/NodePage/MaterializationConfigField.jsx +60 -0
  116. package/src/app/pages/NodePage/NodeColumnTab.jsx +256 -30
  117. package/src/app/pages/NodePage/NodeDependenciesTab.jsx +153 -0
  118. package/src/app/pages/NodePage/NodeGraphTab.jsx +123 -150
  119. package/src/app/pages/NodePage/NodeHistory.jsx +236 -0
  120. package/src/app/pages/NodePage/NodeInfoTab.jsx +325 -49
  121. package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
  122. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +585 -0
  123. package/src/app/pages/NodePage/NodeRevisionMaterializationTab.jsx +58 -0
  124. package/src/app/pages/NodePage/NodeStatus.jsx +100 -31
  125. package/src/app/pages/NodePage/NodeValidateTab.jsx +367 -0
  126. package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
  127. package/src/app/pages/NodePage/NotebookDownload.jsx +36 -0
  128. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +151 -0
  129. package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
  130. package/src/app/pages/NodePage/RevisionDiff.jsx +209 -0
  131. package/src/app/pages/NodePage/WatchNodeButton.jsx +226 -0
  132. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +56 -0
  133. package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +87 -0
  134. package/src/app/pages/NodePage/__tests__/DimensionFilter.test.jsx +74 -0
  135. package/src/app/pages/NodePage/__tests__/EditColumnDescriptionPopover.test.jsx +149 -0
  136. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +148 -0
  137. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +161 -0
  138. package/src/app/pages/NodePage/__tests__/NodeColumnTab.test.jsx +166 -0
  139. package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +151 -0
  140. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +595 -0
  141. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +58 -0
  142. package/src/app/pages/NodePage/__tests__/NodeMaterializationTab.test.jsx +190 -0
  143. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +872 -0
  144. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  145. package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
  146. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +19 -0
  147. package/src/app/pages/NodePage/index.jsx +190 -44
  148. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  149. package/src/app/pages/OverviewPage/ByStatusPanel.jsx +69 -0
  150. package/src/app/pages/OverviewPage/DimensionNodeUsagePanel.jsx +48 -0
  151. package/src/app/pages/OverviewPage/GovernanceWarningsPanel.jsx +107 -0
  152. package/src/app/pages/OverviewPage/Loadable.jsx +16 -0
  153. package/src/app/pages/OverviewPage/NodesByTypePanel.jsx +63 -0
  154. package/src/app/pages/OverviewPage/OverviewPanel.jsx +94 -0
  155. package/src/app/pages/OverviewPage/TrendsPanel.jsx +66 -0
  156. package/src/app/pages/OverviewPage/__tests__/ByStatusPanel.test.jsx +36 -0
  157. package/src/app/pages/OverviewPage/__tests__/DimensionNodeUsagePanel.test.jsx +76 -0
  158. package/src/app/pages/OverviewPage/__tests__/GovernanceWarningsPanel.test.jsx +77 -0
  159. package/src/app/pages/OverviewPage/__tests__/NodesByTypePanel.test.jsx +86 -0
  160. package/src/app/pages/OverviewPage/__tests__/OverviewPanel.test.jsx +78 -0
  161. package/src/app/pages/OverviewPage/__tests__/TrendsPanel.test.jsx +120 -0
  162. package/src/app/pages/OverviewPage/__tests__/index.test.jsx +54 -0
  163. package/src/app/pages/OverviewPage/index.jsx +22 -0
  164. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  165. package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +110 -0
  166. package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +38 -0
  167. package/src/app/pages/RegisterTablePage/index.jsx +142 -0
  168. package/src/app/pages/Root/__tests__/index.test.jsx +79 -0
  169. package/src/app/pages/Root/index.tsx +84 -6
  170. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  171. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  172. package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
  173. package/src/app/pages/TagPage/Loadable.jsx +16 -0
  174. package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
  175. package/src/app/pages/TagPage/index.jsx +79 -0
  176. package/src/app/services/DJService.js +1314 -21
  177. package/src/app/services/__tests__/DJService.test.jsx +1559 -0
  178. package/src/index.tsx +1 -0
  179. package/src/mocks/mockNodes.jsx +1474 -0
  180. package/src/setupTests.ts +31 -1
  181. package/src/styles/dag.css +117 -5
  182. package/src/styles/index.css +1027 -30
  183. package/src/styles/loading.css +34 -0
  184. package/src/styles/login.css +81 -0
  185. package/src/styles/node-creation.scss +276 -0
  186. package/src/styles/node-list.css +4 -0
  187. package/src/styles/overview.css +72 -0
  188. package/src/styles/sorted-table.css +15 -0
  189. package/src/styles/styles.scss +44 -0
  190. package/src/styles/styles.scss.d.ts +9 -0
  191. package/src/utils/form.jsx +23 -0
  192. package/webpack.config.js +16 -6
  193. package/.babelrc +0 -4
  194. package/.env.local +0 -4
  195. package/.env.production +0 -1
  196. package/.github/pull_request_template.md +0 -11
  197. package/.github/workflows/ci.yml +0 -33
  198. package/.vscode/extensions.json +0 -7
  199. package/.vscode/launch.json +0 -15
  200. package/.vscode/settings.json +0 -25
  201. package/Dockerfile +0 -7
  202. package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
  203. package/src/app/pages/ListNamespacesPage/index.jsx +0 -62
  204. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -45
  205. package/src/app/pages/NamespacePage/__tests__/index.test.tsx +0 -14
@@ -1,44 +1,270 @@
1
- import { Component } from 'react';
2
-
3
- export default class NodeColumnTab extends Component {
4
- columnList = node => {
5
- return node.columns.map(col => (
6
- <tr>
7
- <td className="text-start">{col.name}</td>
8
- <td>
9
- <span className="node_type__transform badge node_type">
10
- {col.type}
1
+ import { useEffect, useState } from 'react';
2
+ import * as React from 'react';
3
+ import EditColumnPopover from './EditColumnPopover';
4
+ import EditColumnDescriptionPopover from './EditColumnDescriptionPopover';
5
+ import LinkDimensionPopover from './LinkDimensionPopover';
6
+ import { labelize } from '../../../utils/form';
7
+ import PartitionColumnPopover from './PartitionColumnPopover';
8
+ import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
9
+ import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
10
+ import { link } from 'fs';
11
+
12
+ export default function NodeColumnTab({ node, djClient }) {
13
+ const [attributes, setAttributes] = useState([]);
14
+ const [dimensions, setDimensions] = useState([]);
15
+ const [columns, setColumns] = useState([]);
16
+ const [links, setLinks] = useState([]);
17
+
18
+ useEffect(() => {
19
+ const fetchData = async () => {
20
+ if (node) {
21
+ setColumns(await djClient.columns(node));
22
+ }
23
+ };
24
+ fetchData().catch(console.error);
25
+ }, [djClient, node]);
26
+
27
+ useEffect(() => {
28
+ const fetchData = async () => {
29
+ const attributes = await djClient.attributes();
30
+ const options = attributes.map(attr => {
31
+ return { value: attr.name, label: labelize(attr.name) };
32
+ });
33
+ setAttributes(options);
34
+ };
35
+ fetchData().catch(console.error);
36
+ }, [djClient]);
37
+
38
+ useEffect(() => {
39
+ const fetchData = async () => {
40
+ const dimensions = await djClient.dimensions();
41
+ const options = dimensions.map(dim => {
42
+ return {
43
+ value: dim.name,
44
+ label: `${dim.name} (${dim.indegree} links)`,
45
+ };
46
+ });
47
+ setDimensions(options);
48
+ };
49
+ fetchData().catch(console.error);
50
+ }, [djClient]);
51
+
52
+ const showColumnAttributes = col => {
53
+ return col.attributes.map((attr, idx) => (
54
+ <span
55
+ className="node_type__dimension badge node_type"
56
+ key={`col-attr-${col.name}-${idx}`}
57
+ >
58
+ {attr.attribute_type.name.replace(/_/, ' ')}
59
+ </span>
60
+ ));
61
+ };
62
+
63
+ const showColumnPartition = col => {
64
+ if (col.partition) {
65
+ return (
66
+ <>
67
+ <span className="node_type badge node_type__blank">
68
+ <span className="partition_value badge">
69
+ <b>Type:</b> {col.partition.type_}
70
+ </span>
71
+ <br />
72
+ <span className="partition_value badge">
73
+ <b>Format:</b> <code>{col.partition.format}</code>
74
+ </span>
75
+ <br />
76
+ <span className="partition_value badge">
77
+ <b>Granularity:</b> <code>{col.partition.granularity}</code>
78
+ </span>
11
79
  </span>
12
- </td>
13
- <td>{col.dimension ? col.dimension.name : ''}</td>
14
- <td>
15
- {col.attributes.find(
16
- attr => attr.attribute_type.name === 'dimension',
17
- ) ? (
18
- <span className="node_type__dimension badge node_type">
19
- dimensional
80
+ </>
81
+ );
82
+ }
83
+ return '';
84
+ };
85
+
86
+ const columnList = columns => {
87
+ return columns?.map(col => {
88
+ const dimensionLinks = (links.length > 0 ? links : node?.dimension_links)
89
+ .map(link => [
90
+ link.dimension.name,
91
+ Object.entries(link.foreign_keys).filter(
92
+ entry => entry[0] === node.name + '.' + col.name,
93
+ ),
94
+ ])
95
+ .filter(keys => keys[1].length >= 1);
96
+ const referencedDimensionNode =
97
+ dimensionLinks.length > 0 ? dimensionLinks[0][0] : null;
98
+ return (
99
+ <tr key={col.name}>
100
+ <td
101
+ className="text-start"
102
+ role="columnheader"
103
+ aria-label="ColumnName"
104
+ aria-hidden="false"
105
+ >
106
+ {col.name}
107
+ </td>
108
+ <td>
109
+ <span
110
+ className=""
111
+ role="columnheader"
112
+ aria-label="ColumnDisplayName"
113
+ aria-hidden="false"
114
+ >
115
+ {col.display_name}
116
+ </span>
117
+ </td>
118
+ <td>
119
+ <span
120
+ className=""
121
+ role="columnheader"
122
+ aria-label="ColumnDescription"
123
+ aria-hidden="false"
124
+ >
125
+ {col.description || ''}
126
+ <EditColumnDescriptionPopover
127
+ column={col}
128
+ node={node}
129
+ onSubmit={async () => {
130
+ const res = await djClient.node(node.name);
131
+ setColumns(res.columns);
132
+ }}
133
+ />
134
+ </span>
135
+ </td>
136
+ <td>
137
+ <span
138
+ className={`node_type__${
139
+ node.type === 'cube' ? col.type : 'transform'
140
+ } badge node_type`}
141
+ role="columnheader"
142
+ aria-label="ColumnType"
143
+ aria-hidden="false"
144
+ >
145
+ {col.type}
20
146
  </span>
147
+ </td>
148
+ {node.type !== 'cube' ? (
149
+ <td>
150
+ {dimensionLinks.length > 0
151
+ ? dimensionLinks.map(link => (
152
+ <span
153
+ className="rounded-pill badge bg-secondary-soft"
154
+ style={{ fontSize: '14px' }}
155
+ key={link[0]}
156
+ >
157
+ <a href={`/nodes/${link[0]}`}>{link[0]}</a>
158
+ </span>
159
+ ))
160
+ : ''}
161
+ <LinkDimensionPopover
162
+ column={col}
163
+ dimensionNodes={dimensionLinks.map(link => link[0])}
164
+ node={node}
165
+ options={dimensions}
166
+ onSubmit={async () => {
167
+ const res = await djClient.node(node.name);
168
+ setLinks(res.dimension_links);
169
+ }}
170
+ />
171
+ </td>
21
172
  ) : (
22
173
  ''
23
174
  )}
24
- </td>
25
- </tr>
26
- ));
175
+ {node.type !== 'cube' ? (
176
+ <td>
177
+ {showColumnAttributes(col)}
178
+ <EditColumnPopover
179
+ column={col}
180
+ node={node}
181
+ options={attributes}
182
+ onSubmit={async () => {
183
+ const res = await djClient.node(node.name);
184
+ setColumns(res.columns);
185
+ }}
186
+ />
187
+ </td>
188
+ ) : (
189
+ ''
190
+ )}
191
+ <td>
192
+ {showColumnPartition(col)}
193
+ <PartitionColumnPopover
194
+ column={col}
195
+ node={node}
196
+ onSubmit={async () => {
197
+ const res = await djClient.node(node.name);
198
+ setColumns(res.columns);
199
+ }}
200
+ />
201
+ </td>
202
+ </tr>
203
+ );
204
+ });
27
205
  };
28
206
 
29
- render() {
30
- return (
207
+ return (
208
+ <>
31
209
  <div className="table-responsive">
32
210
  <table className="card-inner-table table">
33
211
  <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
34
- <th className="text-start">Column</th>
35
- <th>Type</th>
36
- <th>Dimension</th>
37
- <th>Attributes</th>
212
+ <tr>
213
+ <th className="text-start">Column</th>
214
+ <th>Display Name</th>
215
+ <th>Description</th>
216
+ <th>Type</th>
217
+ {node?.type !== 'cube' ? (
218
+ <>
219
+ <th>Linked Dimension</th>
220
+ <th>Attributes</th>
221
+ </>
222
+ ) : (
223
+ ''
224
+ )}
225
+ <th>Partition</th>
226
+ </tr>
227
+ </thead>
228
+ <tbody>{columnList(columns)}</tbody>
229
+ </table>
230
+ </div>
231
+ <div>
232
+ <h3>Linked Dimensions (Custom Join SQL)</h3>
233
+ <table className="card-inner-table table">
234
+ <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
235
+ <tr>
236
+ <th className="text-start">Dimension Node</th>
237
+ <th>Join Type</th>
238
+ <th>Join SQL</th>
239
+ <th>Role</th>
240
+ </tr>
38
241
  </thead>
39
- {this.columnList(this.props.node)}
242
+ <tbody>
243
+ {node?.dimension_links.map(link => {
244
+ return (
245
+ <tr key={link.dimension.name}>
246
+ <td>
247
+ <a href={'/nodes/' + link.dimension.name}>
248
+ {link.dimension.name}
249
+ </a>
250
+ </td>
251
+ <td>{link.join_type.toUpperCase()}</td>
252
+ <td style={{ width: '25rem', maxWidth: 'none' }}>
253
+ <SyntaxHighlighter
254
+ language="sql"
255
+ style={foundation}
256
+ wrapLongLines={true}
257
+ >
258
+ {link.join_sql}
259
+ </SyntaxHighlighter>
260
+ </td>
261
+ <td>{link.role}</td>
262
+ </tr>
263
+ );
264
+ })}
265
+ </tbody>
40
266
  </table>
41
267
  </div>
42
- );
43
- }
268
+ </>
269
+ );
44
270
  }
@@ -0,0 +1,153 @@
1
+ import { useEffect, useState } from 'react';
2
+ import * as React from 'react';
3
+ import { labelize } from '../../../utils/form';
4
+ import LoadingIcon from '../../icons/LoadingIcon';
5
+
6
+ export default function NodeDependenciesTab({ node, djClient }) {
7
+ const [nodeDAG, setNodeDAG] = useState({
8
+ upstreams: [],
9
+ downstreams: [],
10
+ dimensions: [],
11
+ });
12
+
13
+ const [retrieved, setRetrieved] = useState(false);
14
+
15
+ useEffect(() => {
16
+ const fetchData = async () => {
17
+ if (node) {
18
+ let upstreams = await djClient.upstreams(node.name);
19
+ let downstreams = await djClient.downstreams(node.name);
20
+ let dimensions = await djClient.nodeDimensions(node.name);
21
+ setNodeDAG({
22
+ upstreams: upstreams,
23
+ downstreams: downstreams,
24
+ dimensions: dimensions,
25
+ });
26
+ setRetrieved(true);
27
+ }
28
+ };
29
+ fetchData().catch(console.error);
30
+ }, [djClient, node]);
31
+
32
+ // Builds the block of dimensions selectors, grouped by node name + path
33
+ return (
34
+ <div>
35
+ <h2>Upstreams</h2>
36
+ {retrieved ? (
37
+ <NodeList nodes={nodeDAG.upstreams} />
38
+ ) : (
39
+ <span style={{ display: 'block' }}>
40
+ <LoadingIcon centered={false} />
41
+ </span>
42
+ )}
43
+ <h2>Downstreams</h2>
44
+ {retrieved ? (
45
+ <NodeList nodes={nodeDAG.downstreams} />
46
+ ) : (
47
+ <span style={{ display: 'block' }}>
48
+ <LoadingIcon centered={false} />
49
+ </span>
50
+ )}
51
+ <h2>Dimensions</h2>
52
+ {retrieved ? (
53
+ <NodeDimensionsList rawDimensions={nodeDAG.dimensions} />
54
+ ) : (
55
+ <span style={{ display: 'block' }}>
56
+ <LoadingIcon centered={false} />
57
+ </span>
58
+ )}
59
+ </div>
60
+ );
61
+ }
62
+
63
+ export function NodeDimensionsList({ rawDimensions }) {
64
+ const dimensions = Object.entries(
65
+ rawDimensions.reduce((group, dimension) => {
66
+ group[dimension.node_name + dimension.path] =
67
+ group[dimension.node_name + dimension.path] ?? [];
68
+ group[dimension.node_name + dimension.path].push(dimension);
69
+ return group;
70
+ }, {}),
71
+ );
72
+ return (
73
+ <div style={{ padding: '0.5rem' }}>
74
+ {dimensions.map(grouping => {
75
+ const dimensionsInGroup = grouping[1];
76
+ const role = dimensionsInGroup[0].path
77
+ .map(pathItem => pathItem.split('.').slice(-1))
78
+ .join(' → ');
79
+ const fullPath = dimensionsInGroup[0].path.join(' → ');
80
+ const groupHeader = (
81
+ <span
82
+ style={{
83
+ fontWeight: 'normal',
84
+ marginBottom: '15px',
85
+ marginTop: '15px',
86
+ }}
87
+ >
88
+ <a href={`/nodes/${dimensionsInGroup[0].node_name}`}>
89
+ <b>{dimensionsInGroup[0].node_display_name}</b>
90
+ </a>{' '}
91
+ with role{' '}
92
+ <span className="HighlightPath">
93
+ <b>{role}</b>
94
+ </span>{' '}
95
+ via <span className="HighlightPath">{fullPath}</span>
96
+ </span>
97
+ );
98
+ const dimensionGroupOptions = dimensionsInGroup.map(dim => {
99
+ return {
100
+ value: dim.name,
101
+ label:
102
+ labelize(dim.name.split('.').slice(-1)[0]) +
103
+ (dim.properties.includes('primary_key') ? ' (PK)' : ''),
104
+ };
105
+ });
106
+ return (
107
+ <details key={grouping[0]}>
108
+ <summary style={{ marginBottom: '10px' }}>{groupHeader}</summary>
109
+ <div className="dimensionsList">
110
+ {dimensionGroupOptions.map(dimension => {
111
+ return (
112
+ <div key={dimension.value}>
113
+ {dimension.label.split('[').slice(0)[0]} ⇢{' '}
114
+ <code className="DimensionAttribute">
115
+ {dimension.value}
116
+ </code>
117
+ </div>
118
+ );
119
+ })}
120
+ </div>
121
+ </details>
122
+ );
123
+ })}
124
+ </div>
125
+ );
126
+ }
127
+
128
+ export function NodeList({ nodes }) {
129
+ return nodes && nodes.length > 0 ? (
130
+ <ul className="backfills">
131
+ {nodes?.map(node => (
132
+ <li
133
+ className="backfill"
134
+ style={{ marginBottom: '5px' }}
135
+ key={node.name}
136
+ >
137
+ <span
138
+ className={`node_type__${node.type} badge node_type`}
139
+ style={{ marginRight: '5px' }}
140
+ role="dialog"
141
+ aria-hidden="false"
142
+ aria-label="NodeType"
143
+ >
144
+ {node.type}
145
+ </span>
146
+ <a href={`/nodes/${node.name}`}>{node.name}</a>
147
+ </li>
148
+ ))}
149
+ </ul>
150
+ ) : (
151
+ <span style={{ display: 'block' }}>None</span>
152
+ );
153
+ }