datajunction-ui 0.0.42 → 0.0.44
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.
package/package.json
CHANGED
|
@@ -4,7 +4,6 @@ import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
|
|
|
4
4
|
import sql from 'react-syntax-highlighter/dist/esm/languages/hljs/sql';
|
|
5
5
|
import NodeStatus from './NodeStatus';
|
|
6
6
|
import ListGroupItem from '../../components/ListGroupItem';
|
|
7
|
-
import ToggleSwitch from '../../components/ToggleSwitch';
|
|
8
7
|
import DJClientContext from '../../providers/djclient';
|
|
9
8
|
import { labelize } from '../../../utils/form';
|
|
10
9
|
|
|
@@ -30,9 +29,6 @@ foundation.hljs['padding'] = '2rem';
|
|
|
30
29
|
// }
|
|
31
30
|
|
|
32
31
|
export default function NodeInfoTab({ node }) {
|
|
33
|
-
const [compiledSQL, setCompiledSQL] = useState('');
|
|
34
|
-
const [checked, setChecked] = useState(false);
|
|
35
|
-
|
|
36
32
|
// For metrics
|
|
37
33
|
const [metricInfo, setMetricInfo] = useState(null);
|
|
38
34
|
|
|
@@ -47,29 +43,16 @@ export default function NodeInfoTab({ node }) {
|
|
|
47
43
|
));
|
|
48
44
|
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
49
45
|
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
const fetchData = async () => {
|
|
52
|
-
if (checked === true) {
|
|
53
|
-
const data = await djClient.compiledSql(node.name);
|
|
54
|
-
if (data.sql) {
|
|
55
|
-
setCompiledSQL(data.sql);
|
|
56
|
-
} else {
|
|
57
|
-
setCompiledSQL(
|
|
58
|
-
'/* Ran into an issue while generating compiled SQL */',
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
fetchData().catch(console.error);
|
|
64
|
-
}, [node, djClient, checked]);
|
|
65
|
-
|
|
66
46
|
useEffect(() => {
|
|
67
47
|
const fetchData = async () => {
|
|
68
48
|
const metric = await djClient.getMetric(node.name);
|
|
49
|
+
// For derived metrics (multiple parents), upstream_node is null
|
|
50
|
+
const parents = metric.current.parents || [];
|
|
51
|
+
const upstreamNode = parents.length === 1 ? parents[0]?.name : null;
|
|
69
52
|
setMetricInfo({
|
|
70
53
|
metric_metadata: metric.current.metricMetadata,
|
|
71
54
|
required_dimensions: metric.current.requiredDimensions,
|
|
72
|
-
upstream_node:
|
|
55
|
+
upstream_node: upstreamNode,
|
|
73
56
|
expression: metric.current.metricMetadata?.expression,
|
|
74
57
|
incompatible_druid_functions:
|
|
75
58
|
metric.current.metricMetadata?.incompatibleDruidFunctions || [],
|
|
@@ -93,9 +76,6 @@ export default function NodeInfoTab({ node }) {
|
|
|
93
76
|
}
|
|
94
77
|
}, [node, djClient]);
|
|
95
78
|
|
|
96
|
-
function toggle(value) {
|
|
97
|
-
return !value;
|
|
98
|
-
}
|
|
99
79
|
const metricsWarning =
|
|
100
80
|
node?.type === 'metric' &&
|
|
101
81
|
metricInfo?.incompatible_druid_functions?.length > 0 ? (
|
|
@@ -134,17 +114,23 @@ export default function NodeInfoTab({ node }) {
|
|
|
134
114
|
node?.type === 'metric' ? (
|
|
135
115
|
<div className="list-group-item d-flex">
|
|
136
116
|
<div className="gap-2 w-100 justify-content-between py-3">
|
|
137
|
-
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
<
|
|
141
|
-
{metricInfo?.upstream_node}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
117
|
+
{metricInfo?.upstream_node && (
|
|
118
|
+
<div style={{ marginBottom: '30px' }}>
|
|
119
|
+
<h6 className="mb-0 w-100">Upstream Node</h6>
|
|
120
|
+
<p>
|
|
121
|
+
<a href={`/nodes/${metricInfo?.upstream_node}`}>
|
|
122
|
+
{metricInfo?.upstream_node}
|
|
123
|
+
</a>
|
|
124
|
+
</p>
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
145
127
|
<div>
|
|
146
128
|
<h6 className="mb-0 w-100">Aggregate Expression</h6>
|
|
147
|
-
<SyntaxHighlighter
|
|
129
|
+
<SyntaxHighlighter
|
|
130
|
+
language="sql"
|
|
131
|
+
style={foundation}
|
|
132
|
+
wrapLongLines={true}
|
|
133
|
+
>
|
|
148
134
|
{metricInfo?.expression}
|
|
149
135
|
</SyntaxHighlighter>
|
|
150
136
|
</div>
|
|
@@ -162,18 +148,12 @@ export default function NodeInfoTab({ node }) {
|
|
|
162
148
|
}}
|
|
163
149
|
>
|
|
164
150
|
<h6 className="mb-0 w-100">Query</h6>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
/>
|
|
172
|
-
) : (
|
|
173
|
-
<></>
|
|
174
|
-
)}
|
|
175
|
-
<SyntaxHighlighter language="sql" style={foundation}>
|
|
176
|
-
{checked ? compiledSQL : node?.query}
|
|
151
|
+
<SyntaxHighlighter
|
|
152
|
+
language="sql"
|
|
153
|
+
style={foundation}
|
|
154
|
+
wrapLongLines={true}
|
|
155
|
+
>
|
|
156
|
+
{node?.query}
|
|
177
157
|
</SyntaxHighlighter>
|
|
178
158
|
</div>
|
|
179
159
|
</div>
|
|
@@ -213,7 +193,26 @@ export default function NodeInfoTab({ node }) {
|
|
|
213
193
|
node?.type === 'metric' ? (
|
|
214
194
|
<div className="list-group-item d-flex">
|
|
215
195
|
<div className="d-flex gap-2 w-100 py-3">
|
|
216
|
-
<div>
|
|
196
|
+
<div style={{ marginRight: '2rem' }}>
|
|
197
|
+
<h6 className="mb-0 w-100">Output Type</h6>
|
|
198
|
+
<p
|
|
199
|
+
className="mb-0 opacity-75"
|
|
200
|
+
role="dialog"
|
|
201
|
+
aria-hidden="false"
|
|
202
|
+
aria-label="OutputType"
|
|
203
|
+
>
|
|
204
|
+
<code
|
|
205
|
+
style={{
|
|
206
|
+
background: '#f5f5f5',
|
|
207
|
+
padding: '2px 6px',
|
|
208
|
+
borderRadius: '3px',
|
|
209
|
+
}}
|
|
210
|
+
>
|
|
211
|
+
{node?.columns?.[0]?.type || 'Unknown'}
|
|
212
|
+
</code>
|
|
213
|
+
</p>
|
|
214
|
+
</div>
|
|
215
|
+
<div style={{ marginRight: '2rem' }}>
|
|
217
216
|
<h6 className="mb-0 w-100">Direction</h6>
|
|
218
217
|
<p
|
|
219
218
|
className="mb-0 opacity-75"
|
|
@@ -270,7 +269,11 @@ export default function NodeInfoTab({ node }) {
|
|
|
270
269
|
}}
|
|
271
270
|
>
|
|
272
271
|
<h6 className="mb-0 w-100">Custom Metadata</h6>
|
|
273
|
-
<SyntaxHighlighter
|
|
272
|
+
<SyntaxHighlighter
|
|
273
|
+
language="json"
|
|
274
|
+
style={foundation}
|
|
275
|
+
wrapLongLines={true}
|
|
276
|
+
>
|
|
274
277
|
{JSON.stringify(node.custom_metadata, null, 2)}
|
|
275
278
|
</SyntaxHighlighter>
|
|
276
279
|
</div>
|
|
@@ -326,12 +329,12 @@ export default function NodeInfoTab({ node }) {
|
|
|
326
329
|
<a href={`/nodes/${node?.name}`}>{dim}</a>
|
|
327
330
|
</span>
|
|
328
331
|
))
|
|
329
|
-
:
|
|
332
|
+
: metricInfo?.required_dimensions?.map((dim, idx) => (
|
|
330
333
|
<span
|
|
331
334
|
key={`rd-${idx}`}
|
|
332
335
|
className="rounded-pill badge bg-secondary-soft PrimaryKey"
|
|
333
336
|
>
|
|
334
|
-
<a href={`/nodes/${
|
|
337
|
+
<a href={`/nodes/${metricInfo?.upstream_node}`}>{dim.name}</a>
|
|
335
338
|
</span>
|
|
336
339
|
))}
|
|
337
340
|
</p>
|
|
@@ -591,32 +591,6 @@ describe('<NodePage />', () => {
|
|
|
591
591
|
});
|
|
592
592
|
});
|
|
593
593
|
|
|
594
|
-
it('renders compiled sql correctly', async () => {
|
|
595
|
-
const djClient = mockDJClient();
|
|
596
|
-
djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockTransformNode);
|
|
597
|
-
djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
|
|
598
|
-
djClient.DataJunctionAPI.compiledSql.mockReturnValue('select 1');
|
|
599
|
-
|
|
600
|
-
const element = (
|
|
601
|
-
<DJClientContext.Provider value={djClient}>
|
|
602
|
-
<NodePage />
|
|
603
|
-
</DJClientContext.Provider>
|
|
604
|
-
);
|
|
605
|
-
render(
|
|
606
|
-
<MemoryRouter initialEntries={[`/nodes/${mocks.mockTransformNode.name}`]}>
|
|
607
|
-
<Routes>
|
|
608
|
-
<Route path="nodes/:name" element={element} />
|
|
609
|
-
</Routes>
|
|
610
|
-
</MemoryRouter>,
|
|
611
|
-
);
|
|
612
|
-
await waitFor(() => {
|
|
613
|
-
fireEvent.click(screen.getByRole('checkbox', { name: 'ToggleSwitch' }));
|
|
614
|
-
expect(djClient.DataJunctionAPI.compiledSql).toHaveBeenCalledWith(
|
|
615
|
-
mocks.mockTransformNode.name,
|
|
616
|
-
);
|
|
617
|
-
});
|
|
618
|
-
});
|
|
619
|
-
|
|
620
594
|
it('renders an empty NodeMaterialization tab correctly', async () => {
|
|
621
595
|
const djClient = mockDJClient();
|
|
622
596
|
djClient.DataJunctionAPI.node.mockResolvedValue(mocks.mockMetricNode);
|
|
@@ -708,113 +682,6 @@ describe('<NodePage />', () => {
|
|
|
708
682
|
});
|
|
709
683
|
});
|
|
710
684
|
|
|
711
|
-
it('renders the NodeValidate tab', async () => {
|
|
712
|
-
const djClient = mockDJClient();
|
|
713
|
-
window.scrollTo = jest.fn();
|
|
714
|
-
djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
|
|
715
|
-
djClient.DataJunctionAPI.nodeDimensions.mockReturnValue([]);
|
|
716
|
-
djClient.DataJunctionAPI.getMetric.mockReturnValue(
|
|
717
|
-
mocks.mockMetricNodeJson,
|
|
718
|
-
);
|
|
719
|
-
djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
|
|
720
|
-
djClient.DataJunctionAPI.sql.mockReturnValue({
|
|
721
|
-
sql: 'SELECT * FROM testNode',
|
|
722
|
-
});
|
|
723
|
-
const streamNodeData = {
|
|
724
|
-
onmessage: jest.fn(),
|
|
725
|
-
onerror: jest.fn(),
|
|
726
|
-
close: jest.fn(),
|
|
727
|
-
};
|
|
728
|
-
djClient.DataJunctionAPI.streamNodeData.mockResolvedValue(streamNodeData);
|
|
729
|
-
djClient.DataJunctionAPI.streamNodeData.mockResolvedValueOnce({
|
|
730
|
-
state: 'FINISHED',
|
|
731
|
-
results: [
|
|
732
|
-
{
|
|
733
|
-
columns: [{ name: 'column1' }, { name: 'column2' }],
|
|
734
|
-
rows: [
|
|
735
|
-
[1, 'value1'],
|
|
736
|
-
[2, 'value2'],
|
|
737
|
-
],
|
|
738
|
-
},
|
|
739
|
-
],
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
const element = (
|
|
743
|
-
<DJClientContext.Provider value={djClient}>
|
|
744
|
-
<NodePage />
|
|
745
|
-
</DJClientContext.Provider>
|
|
746
|
-
);
|
|
747
|
-
render(
|
|
748
|
-
<MemoryRouter
|
|
749
|
-
initialEntries={['/nodes/default.num_repair_orders/validate']}
|
|
750
|
-
>
|
|
751
|
-
<Routes>
|
|
752
|
-
<Route path="nodes/:name/:tab" element={element} />
|
|
753
|
-
</Routes>
|
|
754
|
-
</MemoryRouter>,
|
|
755
|
-
);
|
|
756
|
-
|
|
757
|
-
await waitFor(() => {
|
|
758
|
-
expect(screen.getByText('Group By')).toBeInTheDocument();
|
|
759
|
-
expect(screen.getByText('Add Filters')).toBeInTheDocument();
|
|
760
|
-
expect(screen.getByText('Generated Query')).toBeInTheDocument();
|
|
761
|
-
expect(screen.getByText('Results')).toBeInTheDocument();
|
|
762
|
-
});
|
|
763
|
-
// Click on the 'Validate' tab
|
|
764
|
-
fireEvent.click(screen.getByRole('button', { name: '► Validate' }));
|
|
765
|
-
|
|
766
|
-
await waitFor(() => {
|
|
767
|
-
expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
|
|
768
|
-
mocks.mockMetricNode.name,
|
|
769
|
-
);
|
|
770
|
-
expect(djClient.DataJunctionAPI.sql).toHaveBeenCalledWith(
|
|
771
|
-
mocks.mockMetricNode.name,
|
|
772
|
-
{ dimensions: [], filters: [] },
|
|
773
|
-
);
|
|
774
|
-
expect(djClient.DataJunctionAPI.nodeDimensions).toHaveBeenCalledWith(
|
|
775
|
-
mocks.mockMetricNode.name,
|
|
776
|
-
);
|
|
777
|
-
});
|
|
778
|
-
|
|
779
|
-
// Click on 'Run' to run the node query
|
|
780
|
-
const runButton = screen.getByText('► Run');
|
|
781
|
-
fireEvent.click(runButton);
|
|
782
|
-
|
|
783
|
-
await waitFor(() => {
|
|
784
|
-
expect(djClient.DataJunctionAPI.streamNodeData).toHaveBeenCalledWith(
|
|
785
|
-
mocks.mockMetricNode.name,
|
|
786
|
-
{ dimensions: [], filters: [] },
|
|
787
|
-
);
|
|
788
|
-
expect(streamNodeData.onmessage).toBeDefined();
|
|
789
|
-
expect(streamNodeData.onerror).toBeDefined();
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
const infoTab = screen.getByRole('button', { name: 'QueryInfo' });
|
|
793
|
-
const resultsTab = screen.getByText('Results');
|
|
794
|
-
|
|
795
|
-
// Initially, the Results tab should be active
|
|
796
|
-
expect(resultsTab).toHaveClass('active');
|
|
797
|
-
expect(infoTab).not.toHaveClass('active');
|
|
798
|
-
|
|
799
|
-
// Click on the Info tab first
|
|
800
|
-
fireEvent.click(infoTab);
|
|
801
|
-
|
|
802
|
-
await waitFor(() => {
|
|
803
|
-
// Now, the Info tab should be active
|
|
804
|
-
expect(infoTab).toHaveClass('active');
|
|
805
|
-
expect(resultsTab).not.toHaveClass('active');
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
// Click on the Results tab
|
|
809
|
-
fireEvent.click(resultsTab);
|
|
810
|
-
|
|
811
|
-
await waitFor(() => {
|
|
812
|
-
// Now, the Results tab should be active again
|
|
813
|
-
expect(resultsTab).toHaveClass('active');
|
|
814
|
-
expect(infoTab).not.toHaveClass('active');
|
|
815
|
-
});
|
|
816
|
-
});
|
|
817
|
-
|
|
818
685
|
it('renders a NodeColumnLineage tab correctly', async () => {
|
|
819
686
|
const djClient = mockDJClient();
|
|
820
687
|
djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
|
|
@@ -4,15 +4,17 @@ exports[`<NodePage /> renders the NodeInfo tab correctly for a metric node 1`] =
|
|
|
4
4
|
HTMLCollection [
|
|
5
5
|
<code
|
|
6
6
|
class="language-sql"
|
|
7
|
-
style="white-space: pre;"
|
|
7
|
+
style="white-space: pre-wrap;"
|
|
8
8
|
>
|
|
9
|
-
<span
|
|
10
|
-
class="hljs-built_in"
|
|
11
|
-
>
|
|
12
|
-
count
|
|
13
|
-
</span>
|
|
14
9
|
<span>
|
|
15
|
-
|
|
10
|
+
<span
|
|
11
|
+
class="hljs-built_in"
|
|
12
|
+
>
|
|
13
|
+
count
|
|
14
|
+
</span>
|
|
15
|
+
<span>
|
|
16
|
+
(repair_order_id)
|
|
17
|
+
</span>
|
|
16
18
|
</span>
|
|
17
19
|
</code>,
|
|
18
20
|
]
|
|
@@ -9,7 +9,6 @@ import NodeGraphTab from './NodeGraphTab';
|
|
|
9
9
|
import NodeHistory from './NodeHistory';
|
|
10
10
|
import NotebookDownload from './NotebookDownload';
|
|
11
11
|
import DJClientContext from '../../providers/djclient';
|
|
12
|
-
import NodeValidateTab from './NodeValidateTab';
|
|
13
12
|
import NodeMaterializationTab from './NodeMaterializationTab';
|
|
14
13
|
import NodePreAggregationsTab from './NodePreAggregationsTab';
|
|
15
14
|
import ClientCodePopover from './ClientCodePopover';
|
|
@@ -35,6 +34,11 @@ export function NodePage() {
|
|
|
35
34
|
const [node, setNode] = useState(null);
|
|
36
35
|
|
|
37
36
|
const onClickTab = id => () => {
|
|
37
|
+
// Preview tab redirects to Query Planner instead of showing content
|
|
38
|
+
if (id === 'preview') {
|
|
39
|
+
navigate(`/planner?metrics=${encodeURIComponent(name)}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
38
42
|
navigate(`/nodes/${name}/${id}`);
|
|
39
43
|
setState({ selectedTab: id });
|
|
40
44
|
};
|
|
@@ -74,7 +78,7 @@ export function NodePage() {
|
|
|
74
78
|
{
|
|
75
79
|
id: 'columns',
|
|
76
80
|
name: 'Columns',
|
|
77
|
-
display:
|
|
81
|
+
display: node?.type !== 'metric',
|
|
78
82
|
},
|
|
79
83
|
{
|
|
80
84
|
id: 'graph',
|
|
@@ -86,15 +90,10 @@ export function NodePage() {
|
|
|
86
90
|
name: 'History',
|
|
87
91
|
display: true,
|
|
88
92
|
},
|
|
89
|
-
{
|
|
90
|
-
id: 'validate',
|
|
91
|
-
name: '► Validate',
|
|
92
|
-
display: node?.type !== 'source',
|
|
93
|
-
},
|
|
94
93
|
{
|
|
95
94
|
id: 'materializations',
|
|
96
95
|
name: 'Materializations',
|
|
97
|
-
display: node?.type !== 'source',
|
|
96
|
+
display: node?.type !== 'source' && node?.type !== 'metric',
|
|
98
97
|
},
|
|
99
98
|
{
|
|
100
99
|
id: 'linked',
|
|
@@ -111,6 +110,11 @@ export function NodePage() {
|
|
|
111
110
|
name: 'Dependencies',
|
|
112
111
|
display: node?.type !== 'cube',
|
|
113
112
|
},
|
|
113
|
+
{
|
|
114
|
+
id: 'preview',
|
|
115
|
+
name: 'Preview →',
|
|
116
|
+
display: node?.type === 'metric',
|
|
117
|
+
},
|
|
114
118
|
];
|
|
115
119
|
};
|
|
116
120
|
let tabToDisplay = null;
|
|
@@ -128,9 +132,6 @@ export function NodePage() {
|
|
|
128
132
|
case 'history':
|
|
129
133
|
tabToDisplay = <NodeHistory node={node} djClient={djClient} />;
|
|
130
134
|
break;
|
|
131
|
-
case 'validate':
|
|
132
|
-
tabToDisplay = <NodeValidateTab node={node} djClient={djClient} />;
|
|
133
|
-
break;
|
|
134
135
|
case 'materializations':
|
|
135
136
|
// Cube nodes use cube-specific materialization tab
|
|
136
137
|
// Other nodes (transform, metric, dimension) use pre-aggregations tab
|