datajunction-ui 0.0.1-rc.10 → 0.0.1-rc.12
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 +4 -1
- package/src/app/__tests__/__snapshots__/index.test.tsx.snap +18 -16
- package/src/app/components/NamespaceHeader.jsx +4 -13
- package/src/app/components/QueryInfo.jsx +109 -0
- package/src/app/components/ToggleSwitch.jsx +17 -0
- package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
- package/src/app/components/djgraph/Collapse.jsx +46 -0
- package/src/app/components/djgraph/DJNode.jsx +56 -80
- package/src/app/components/djgraph/DJNodeColumns.jsx +68 -0
- package/src/app/components/djgraph/DJNodeDimensions.jsx +69 -0
- package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +82 -43
- package/src/app/icons/CollapsedIcon.jsx +15 -0
- package/src/app/icons/ExpandedIcon.jsx +15 -0
- package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
- package/src/app/icons/InvalidIcon.jsx +14 -0
- package/src/app/icons/PythonIcon.jsx +52 -0
- package/src/app/icons/TableIcon.jsx +14 -0
- package/src/app/icons/ValidIcon.jsx +14 -0
- package/src/app/index.tsx +3 -2
- package/src/app/pages/NamespacePage/Explorer.jsx +57 -0
- package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +24 -5
- package/src/app/pages/NamespacePage/index.jsx +78 -10
- package/src/app/pages/NodePage/ClientCodePopover.jsx +30 -0
- package/src/app/pages/NodePage/NodeColumnTab.jsx +36 -20
- package/src/app/pages/NodePage/NodeGraphTab.jsx +45 -17
- package/src/app/pages/NodePage/NodeHistory.jsx +71 -0
- package/src/app/pages/NodePage/NodeInfoTab.jsx +132 -49
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +151 -0
- package/src/app/pages/NodePage/NodeSQLTab.jsx +100 -0
- package/src/app/pages/NodePage/NodeStatus.jsx +14 -20
- package/src/app/pages/NodePage/index.jsx +43 -8
- package/src/app/pages/Root/index.tsx +5 -0
- package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
- package/src/app/pages/SQLBuilderPage/index.jsx +317 -0
- package/src/app/services/DJService.js +125 -1
- package/src/styles/dag.css +111 -5
- package/src/styles/index.css +328 -22
- package/webpack.config.js +4 -5
- package/.babelrc +0 -4
- package/.env.local +0 -4
- package/.env.production +0 -1
- package/.vscode/extensions.json +0 -7
- package/.vscode/launch.json +0 -15
- package/.vscode/settings.json +0 -25
- package/Dockerfile +0 -7
- package/src/app/components/DashboardItem.jsx +0 -29
- package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
- package/src/app/pages/ListNamespacesPage/index.jsx +0 -60
|
@@ -1,34 +1,28 @@
|
|
|
1
1
|
import { Component } from 'react';
|
|
2
|
+
import ValidIcon from '../../icons/ValidIcon';
|
|
3
|
+
import InvalidIcon from '../../icons/InvalidIcon';
|
|
2
4
|
|
|
3
5
|
export default class NodeStatus extends Component {
|
|
4
6
|
render() {
|
|
5
7
|
const { node } = this.props;
|
|
6
8
|
return (
|
|
7
|
-
|
|
9
|
+
<>
|
|
8
10
|
{node?.status === 'valid' ? (
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
height="25"
|
|
13
|
-
fill="currentColor"
|
|
14
|
-
className="bi bi-check-circle-fill"
|
|
15
|
-
viewBox="0 0 16 16"
|
|
11
|
+
<span
|
|
12
|
+
className="status__valid status"
|
|
13
|
+
style={{ alignContent: 'center' }}
|
|
16
14
|
>
|
|
17
|
-
<
|
|
18
|
-
</
|
|
15
|
+
<ValidIcon />
|
|
16
|
+
</span>
|
|
19
17
|
) : (
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
height="16"
|
|
24
|
-
fill="currentColor"
|
|
25
|
-
className="bi bi-x-circle-fill"
|
|
26
|
-
viewBox="0 0 16 16"
|
|
18
|
+
<span
|
|
19
|
+
className="status__invalid status"
|
|
20
|
+
style={{ alignContent: 'center' }}
|
|
27
21
|
>
|
|
28
|
-
<
|
|
29
|
-
</
|
|
22
|
+
<InvalidIcon />
|
|
23
|
+
</span>
|
|
30
24
|
)}
|
|
31
|
-
|
|
25
|
+
</>
|
|
32
26
|
);
|
|
33
27
|
}
|
|
34
28
|
}
|
|
@@ -6,7 +6,11 @@ import NamespaceHeader from '../../components/NamespaceHeader';
|
|
|
6
6
|
import NodeInfoTab from './NodeInfoTab';
|
|
7
7
|
import NodeColumnTab from './NodeColumnTab';
|
|
8
8
|
import NodeLineage from './NodeGraphTab';
|
|
9
|
+
import NodeHistory from './NodeHistory';
|
|
9
10
|
import DJClientContext from '../../providers/djclient';
|
|
11
|
+
import NodeSQLTab from './NodeSQLTab';
|
|
12
|
+
import NodeMaterializationTab from './NodeMaterializationTab';
|
|
13
|
+
import ClientCodePopover from './ClientCodePopover';
|
|
10
14
|
|
|
11
15
|
export function NodePage() {
|
|
12
16
|
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
@@ -37,7 +41,18 @@ export function NodePage() {
|
|
|
37
41
|
useEffect(() => {
|
|
38
42
|
const fetchData = async () => {
|
|
39
43
|
const data = await djClient.node(name);
|
|
44
|
+
data.createNodeClientCode = await djClient.clientCode(name);
|
|
40
45
|
setNode(data);
|
|
46
|
+
if (data.type === 'metric') {
|
|
47
|
+
const metric = await djClient.metric(name);
|
|
48
|
+
data.dimensions = metric.dimensions;
|
|
49
|
+
setNode(data);
|
|
50
|
+
}
|
|
51
|
+
if (data.type === 'cube') {
|
|
52
|
+
const cube = await djClient.cube(name);
|
|
53
|
+
data.cube_elements = cube.cube_elements;
|
|
54
|
+
setNode(data);
|
|
55
|
+
}
|
|
41
56
|
};
|
|
42
57
|
fetchData().catch(console.error);
|
|
43
58
|
}, [djClient, name]);
|
|
@@ -55,6 +70,18 @@ export function NodePage() {
|
|
|
55
70
|
id: 2,
|
|
56
71
|
name: 'Graph',
|
|
57
72
|
},
|
|
73
|
+
{
|
|
74
|
+
id: 3,
|
|
75
|
+
name: 'History',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 4,
|
|
79
|
+
name: 'SQL',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 5,
|
|
83
|
+
name: 'Materializations',
|
|
84
|
+
},
|
|
58
85
|
];
|
|
59
86
|
//
|
|
60
87
|
//
|
|
@@ -64,11 +91,21 @@ export function NodePage() {
|
|
|
64
91
|
tabToDisplay = node ? <NodeInfoTab node={node} /> : '';
|
|
65
92
|
break;
|
|
66
93
|
case 1:
|
|
67
|
-
tabToDisplay = <NodeColumnTab node={node} />;
|
|
94
|
+
tabToDisplay = <NodeColumnTab node={node} djClient={djClient} />;
|
|
68
95
|
break;
|
|
69
96
|
case 2:
|
|
70
97
|
tabToDisplay = <NodeLineage djNode={node} djClient={djClient} />;
|
|
71
98
|
break;
|
|
99
|
+
case 3:
|
|
100
|
+
tabToDisplay = <NodeHistory node={node} djClient={djClient} />;
|
|
101
|
+
break;
|
|
102
|
+
case 4:
|
|
103
|
+
tabToDisplay =
|
|
104
|
+
node.type === 'metric' ? <NodeSQLTab djNode={node} /> : <br />;
|
|
105
|
+
break;
|
|
106
|
+
case 5:
|
|
107
|
+
tabToDisplay = <NodeMaterializationTab node={node} djClient={djClient} />;
|
|
108
|
+
break;
|
|
72
109
|
default:
|
|
73
110
|
tabToDisplay = <NodeInfoTab node={node} />;
|
|
74
111
|
}
|
|
@@ -79,17 +116,15 @@ export function NodePage() {
|
|
|
79
116
|
<NamespaceHeader namespace={name.split('.').slice(0, -1).join('.')} />
|
|
80
117
|
<div className="card">
|
|
81
118
|
<div className="card-header">
|
|
82
|
-
<h3
|
|
119
|
+
<h3
|
|
120
|
+
className="card-title align-items-start flex-column"
|
|
121
|
+
style={{ display: 'inline-block' }}
|
|
122
|
+
>
|
|
83
123
|
<span className="card-label fw-bold text-gray-800">
|
|
84
124
|
{node?.display_name}
|
|
85
125
|
</span>
|
|
86
126
|
</h3>
|
|
87
|
-
<
|
|
88
|
-
className="fs-6 fw-semibold text-gray-400"
|
|
89
|
-
style={{ marginTop: '-4rem' }}
|
|
90
|
-
>
|
|
91
|
-
Updated {new Date(node?.updated_at).toDateString()}
|
|
92
|
-
</span>
|
|
127
|
+
<ClientCodePopover code={node?.createNodeClientCode} />
|
|
93
128
|
<div className="align-items-center row">
|
|
94
129
|
{TabsJson.map(buildTabs)}
|
|
95
130
|
</div>
|
|
@@ -27,6 +27,11 @@ export function Root() {
|
|
|
27
27
|
<a href="/">Explore</a>
|
|
28
28
|
</span>
|
|
29
29
|
</span>
|
|
30
|
+
<span className="menu-link">
|
|
31
|
+
<span className="menu-title">
|
|
32
|
+
<a href="/sql">SQL</a>
|
|
33
|
+
</span>
|
|
34
|
+
</span>
|
|
30
35
|
<span className="menu-link">
|
|
31
36
|
<span className="menu-title">
|
|
32
37
|
<a href="/">Help</a>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asynchronously loads the component for the Node page
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { lazyLoad } from '../../../utils/loadable';
|
|
7
|
+
|
|
8
|
+
export const SQLBuilderPage = props => {
|
|
9
|
+
return lazyLoad(
|
|
10
|
+
() => import('./index'),
|
|
11
|
+
module => module.SQLBuilderPage,
|
|
12
|
+
{
|
|
13
|
+
fallback: <div></div>,
|
|
14
|
+
},
|
|
15
|
+
)(props);
|
|
16
|
+
};
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { useContext, useEffect, useState } from 'react';
|
|
2
|
+
import NamespaceHeader from '../../components/NamespaceHeader';
|
|
3
|
+
import { DataJunctionAPI } from '../../services/DJService';
|
|
4
|
+
import DJClientContext from '../../providers/djclient';
|
|
5
|
+
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
6
|
+
import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
|
|
7
|
+
import { format } from 'sql-formatter';
|
|
8
|
+
import Select from 'react-select';
|
|
9
|
+
import QueryInfo from '../../components/QueryInfo';
|
|
10
|
+
|
|
11
|
+
export function SQLBuilderPage() {
|
|
12
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
13
|
+
const [stagedMetrics, setStagedMetrics] = useState([]);
|
|
14
|
+
const [metrics, setMetrics] = useState([]);
|
|
15
|
+
const [commonDimensionsList, setCommonDimensionsList] = useState([]);
|
|
16
|
+
const [selectedDimensions, setSelectedDimensions] = useState([]);
|
|
17
|
+
const [stagedDimensions, setStagedDimensions] = useState([]);
|
|
18
|
+
const [selectedMetrics, setSelectedMetrics] = useState([]);
|
|
19
|
+
const [query, setQuery] = useState('');
|
|
20
|
+
const [submittedQueryInfo, setSubmittedQueryInfo] = useState(null);
|
|
21
|
+
const [data, setData] = useState(null);
|
|
22
|
+
const [loadingData, setLoadingData] = useState(false);
|
|
23
|
+
const [viewData, setViewData] = useState(false);
|
|
24
|
+
const [showHelp, setShowHelp] = useState(true);
|
|
25
|
+
const [showNumRows, setShowNumRows] = useState(100);
|
|
26
|
+
const [displayedRows, setDisplayedRows] = useState(<></>);
|
|
27
|
+
const numRowsOptions = [
|
|
28
|
+
{
|
|
29
|
+
value: 10,
|
|
30
|
+
label: '10 Rows',
|
|
31
|
+
isFixed: true,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
value: 100,
|
|
35
|
+
label: '100 Rows',
|
|
36
|
+
isFixed: true,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
value: 1000,
|
|
40
|
+
label: '1,000 Rows',
|
|
41
|
+
isFixed: true,
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
const toggleViewData = () => setViewData(current => !current);
|
|
45
|
+
|
|
46
|
+
// Get data for the current selection of metrics and dimensions
|
|
47
|
+
const getData = () => {
|
|
48
|
+
setLoadingData(true);
|
|
49
|
+
const fetchData = async () => {
|
|
50
|
+
setData(null);
|
|
51
|
+
const queryInfo = await djClient.data(
|
|
52
|
+
selectedMetrics,
|
|
53
|
+
selectedDimensions,
|
|
54
|
+
);
|
|
55
|
+
setLoadingData(false);
|
|
56
|
+
setSubmittedQueryInfo(queryInfo);
|
|
57
|
+
queryInfo.numRows = 0;
|
|
58
|
+
if (queryInfo.results && queryInfo.results?.length) {
|
|
59
|
+
setData(queryInfo.results);
|
|
60
|
+
queryInfo.numRows = queryInfo.results[0].rows.length;
|
|
61
|
+
setViewData(true);
|
|
62
|
+
setShowNumRows(10);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
fetchData().catch(console.error);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const resetView = () => {
|
|
69
|
+
setQuery('');
|
|
70
|
+
setData(null);
|
|
71
|
+
setViewData(false);
|
|
72
|
+
};
|
|
73
|
+
const handleMetricSelect = event => {
|
|
74
|
+
const metrics = event.map(m => m.value);
|
|
75
|
+
resetView();
|
|
76
|
+
setStagedMetrics(metrics);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const handleMetricSelectorClose = () => {
|
|
80
|
+
setSelectedMetrics(stagedMetrics);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const handleDimensionSelect = event => {
|
|
84
|
+
const dimensions = event.map(d => d.value);
|
|
85
|
+
resetView();
|
|
86
|
+
setStagedDimensions(dimensions);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const handleDimensionSelectorClose = () => {
|
|
90
|
+
setSelectedDimensions(stagedDimensions);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Get metrics
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const fetchData = async () => {
|
|
96
|
+
const metrics = await djClient.metrics();
|
|
97
|
+
setMetrics(metrics.map(m => ({ value: m, label: m })));
|
|
98
|
+
};
|
|
99
|
+
fetchData().catch(console.error);
|
|
100
|
+
}, [djClient, djClient.metrics]);
|
|
101
|
+
|
|
102
|
+
// Get common dimensions
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const fetchData = async () => {
|
|
105
|
+
if (selectedMetrics.length) {
|
|
106
|
+
const commonDimensions = await djClient.commonDimensions(
|
|
107
|
+
selectedMetrics,
|
|
108
|
+
);
|
|
109
|
+
setCommonDimensionsList(
|
|
110
|
+
commonDimensions.map(d => ({
|
|
111
|
+
value: d.name,
|
|
112
|
+
label: d.name,
|
|
113
|
+
path: d.path.join(' ▶ '),
|
|
114
|
+
})),
|
|
115
|
+
);
|
|
116
|
+
} else {
|
|
117
|
+
setCommonDimensionsList([]);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
fetchData().catch(console.error);
|
|
121
|
+
}, [selectedMetrics, djClient]);
|
|
122
|
+
|
|
123
|
+
// Get SQL
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
const fetchData = async () => {
|
|
126
|
+
if (selectedMetrics.length && selectedDimensions.length) {
|
|
127
|
+
const query = await djClient.sqls(selectedMetrics, selectedDimensions);
|
|
128
|
+
setShowHelp(false);
|
|
129
|
+
setQuery(query.sql);
|
|
130
|
+
} else {
|
|
131
|
+
resetView();
|
|
132
|
+
setShowHelp(true);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
fetchData().catch(console.error);
|
|
136
|
+
}, [selectedMetrics, selectedDimensions, djClient]);
|
|
137
|
+
|
|
138
|
+
// Set number of rows to display
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
if (data) {
|
|
141
|
+
setDisplayedRows(
|
|
142
|
+
data[0].rows.slice(0, showNumRows).map((rowData, index) => (
|
|
143
|
+
<tr key={`data-row:${index}`}>
|
|
144
|
+
{rowData.map(rowValue => (
|
|
145
|
+
<td key={rowValue}>{rowValue}</td>
|
|
146
|
+
))}
|
|
147
|
+
</tr>
|
|
148
|
+
)),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}, [showNumRows, data]);
|
|
152
|
+
const formatOptionLabel = ({ value, label, path }) => (
|
|
153
|
+
<div className={`badge dimension_option`}>
|
|
154
|
+
<div>{label}</div>
|
|
155
|
+
<span className={`badge dimension_option_subheading`}>{path}</span>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// @ts-ignore
|
|
160
|
+
return (
|
|
161
|
+
<>
|
|
162
|
+
<div className="mid">
|
|
163
|
+
<NamespaceHeader namespace="" />
|
|
164
|
+
<div className="card">
|
|
165
|
+
<div className="card-header">
|
|
166
|
+
<h4>Metrics</h4>
|
|
167
|
+
<Select
|
|
168
|
+
name="metrics"
|
|
169
|
+
options={metrics}
|
|
170
|
+
noOptionsMessage={() => 'No metrics found.'}
|
|
171
|
+
placeholder={`${metrics.length} Available Metrics`}
|
|
172
|
+
isMulti
|
|
173
|
+
isClearable
|
|
174
|
+
closeMenuOnSelect={false}
|
|
175
|
+
onChange={handleMetricSelect}
|
|
176
|
+
onMenuClose={handleMetricSelectorClose}
|
|
177
|
+
/>
|
|
178
|
+
<h4>Shared Dimensions</h4>
|
|
179
|
+
<Select
|
|
180
|
+
name="dimensions"
|
|
181
|
+
formatOptionLabel={formatOptionLabel}
|
|
182
|
+
options={commonDimensionsList}
|
|
183
|
+
noOptionsMessage={() =>
|
|
184
|
+
'No shared dimensions found. Try selecting different metrics.'
|
|
185
|
+
}
|
|
186
|
+
placeholder={`${commonDimensionsList.length} Shared Dimensions`}
|
|
187
|
+
isMulti
|
|
188
|
+
isClearable
|
|
189
|
+
closeMenuOnSelect={false}
|
|
190
|
+
onChange={handleDimensionSelect}
|
|
191
|
+
onMenuClose={handleDimensionSelectorClose}
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
<div className="card-header">
|
|
195
|
+
{showHelp ? (
|
|
196
|
+
<div className="card-light-shadow">
|
|
197
|
+
<h6>Using the SQL Builder</h6>
|
|
198
|
+
<p>
|
|
199
|
+
The sql builder allows you to group multiple metrics along
|
|
200
|
+
with their shared dimensions. Using your selections,
|
|
201
|
+
DataJunction will generate the corresponding SQL.
|
|
202
|
+
</p>
|
|
203
|
+
<ol>
|
|
204
|
+
<li>
|
|
205
|
+
<b>Select Metrics:</b> Start by selecting one or more
|
|
206
|
+
metrics from the metrics dropdown.
|
|
207
|
+
</li>
|
|
208
|
+
<li>
|
|
209
|
+
<b>Select Dimensions:</b> Next, select the dimension
|
|
210
|
+
attributes you would like to include. As you select
|
|
211
|
+
additional metrics, the list of available dimensions will be
|
|
212
|
+
filtered to those shared by the selected metrics. If the
|
|
213
|
+
dimensions list is empty, no shared dimensions were
|
|
214
|
+
discovered.
|
|
215
|
+
</li>
|
|
216
|
+
<li>
|
|
217
|
+
<b>View the generated SQL Query:</b> As you make your
|
|
218
|
+
selections, the SQL required to retrieve the set of metrics
|
|
219
|
+
and dimensions will be generated below.
|
|
220
|
+
</li>
|
|
221
|
+
<li>
|
|
222
|
+
<b>Run the Query:</b> If query running is enabled by your
|
|
223
|
+
server, you can also run the generated SQL query to view a
|
|
224
|
+
sample of 100 records.
|
|
225
|
+
</li>
|
|
226
|
+
</ol>
|
|
227
|
+
</div>
|
|
228
|
+
) : (
|
|
229
|
+
<></>
|
|
230
|
+
)}
|
|
231
|
+
{query ? (
|
|
232
|
+
<>
|
|
233
|
+
{loadingData ? (
|
|
234
|
+
<span className="button-3 executing-button">
|
|
235
|
+
{'Running Query'}
|
|
236
|
+
</span>
|
|
237
|
+
) : (
|
|
238
|
+
<span className="button-3 execute-button" onClick={getData}>
|
|
239
|
+
{'Run Query'}
|
|
240
|
+
</span>
|
|
241
|
+
)}
|
|
242
|
+
{data ? (
|
|
243
|
+
viewData ? (
|
|
244
|
+
<>
|
|
245
|
+
<span
|
|
246
|
+
className="button-3 neutral-button"
|
|
247
|
+
onClick={toggleViewData}
|
|
248
|
+
>
|
|
249
|
+
{'View Query'}
|
|
250
|
+
</span>
|
|
251
|
+
<span style={{ display: 'inline-block' }}>
|
|
252
|
+
<Select
|
|
253
|
+
name="num-rows"
|
|
254
|
+
defaultValue={numRowsOptions[0]}
|
|
255
|
+
options={numRowsOptions}
|
|
256
|
+
onChange={e => setShowNumRows(e.value)}
|
|
257
|
+
/>
|
|
258
|
+
</span>
|
|
259
|
+
</>
|
|
260
|
+
) : (
|
|
261
|
+
<span
|
|
262
|
+
className="button-3 neutral-button"
|
|
263
|
+
onClick={toggleViewData}
|
|
264
|
+
>
|
|
265
|
+
{'View Data'}
|
|
266
|
+
</span>
|
|
267
|
+
)
|
|
268
|
+
) : (
|
|
269
|
+
<></>
|
|
270
|
+
)}
|
|
271
|
+
</>
|
|
272
|
+
) : (
|
|
273
|
+
<></>
|
|
274
|
+
)}
|
|
275
|
+
{submittedQueryInfo ? <QueryInfo {...submittedQueryInfo} /> : <></>}
|
|
276
|
+
<div>
|
|
277
|
+
{query && !viewData ? (
|
|
278
|
+
<SyntaxHighlighter language="sql" style={foundation}>
|
|
279
|
+
{format(query, {
|
|
280
|
+
language: 'spark',
|
|
281
|
+
tabWidth: 2,
|
|
282
|
+
keywordCase: 'upper',
|
|
283
|
+
denseOperators: true,
|
|
284
|
+
logicalOperatorNewline: 'before',
|
|
285
|
+
expressionWidth: 10,
|
|
286
|
+
})}
|
|
287
|
+
</SyntaxHighlighter>
|
|
288
|
+
) : (
|
|
289
|
+
''
|
|
290
|
+
)}
|
|
291
|
+
</div>
|
|
292
|
+
{data && viewData ? (
|
|
293
|
+
<div className="table-responsive">
|
|
294
|
+
<table className="card-inner-table table">
|
|
295
|
+
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
296
|
+
<tr>
|
|
297
|
+
{data[0].columns.map(columnName => (
|
|
298
|
+
<th key={columnName.name}>{columnName.name}</th>
|
|
299
|
+
))}
|
|
300
|
+
</tr>
|
|
301
|
+
</thead>
|
|
302
|
+
<tbody>{displayedRows}</tbody>
|
|
303
|
+
</table>
|
|
304
|
+
</div>
|
|
305
|
+
) : (
|
|
306
|
+
<></>
|
|
307
|
+
)}
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
</>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
SQLBuilderPage.defaultProps = {
|
|
316
|
+
djClient: DataJunctionAPI,
|
|
317
|
+
};
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { MarkerType } from 'reactflow';
|
|
2
2
|
|
|
3
|
-
const DJ_URL =
|
|
3
|
+
const DJ_URL = process.env.REACT_APP_DJ_URL
|
|
4
|
+
? process.env.REACT_APP_DJ_URL
|
|
5
|
+
: 'http://localhost:8000';
|
|
4
6
|
|
|
5
7
|
export const DataJunctionAPI = {
|
|
6
8
|
node: async function (name) {
|
|
7
9
|
const data = await (await fetch(DJ_URL + '/nodes/' + name + '/')).json();
|
|
10
|
+
data.primary_key = data.columns
|
|
11
|
+
.filter(col =>
|
|
12
|
+
col.attributes.some(attr => attr.attribute_type.name === 'primary_key'),
|
|
13
|
+
)
|
|
14
|
+
.map(col => col.name);
|
|
8
15
|
return data;
|
|
9
16
|
},
|
|
10
17
|
|
|
@@ -22,11 +29,64 @@ export const DataJunctionAPI = {
|
|
|
22
29
|
return data;
|
|
23
30
|
},
|
|
24
31
|
|
|
32
|
+
node_dag: async function (name) {
|
|
33
|
+
const data = await (
|
|
34
|
+
await fetch(DJ_URL + '/nodes/' + name + '/dag/')
|
|
35
|
+
).json();
|
|
36
|
+
return data;
|
|
37
|
+
},
|
|
38
|
+
|
|
25
39
|
metric: async function (name) {
|
|
26
40
|
const data = await (await fetch(DJ_URL + '/metrics/' + name + '/')).json();
|
|
27
41
|
return data;
|
|
28
42
|
},
|
|
29
43
|
|
|
44
|
+
clientCode: async function (name) {
|
|
45
|
+
const data = await (
|
|
46
|
+
await fetch(DJ_URL + '/client/python/new_node/' + name)
|
|
47
|
+
).json();
|
|
48
|
+
return data;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
cube: async function (name) {
|
|
52
|
+
const data = await (await fetch(DJ_URL + '/cubes/' + name + '/')).json();
|
|
53
|
+
return data;
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
metrics: async function (name) {
|
|
57
|
+
const data = await (await fetch(DJ_URL + '/metrics/')).json();
|
|
58
|
+
return data;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
commonDimensions: async function (metrics) {
|
|
62
|
+
const metricsQuery = '?' + metrics.map(m => `metric=${m}`).join('&');
|
|
63
|
+
const data = await (
|
|
64
|
+
await fetch(DJ_URL + '/metrics/common/dimensions/' + metricsQuery)
|
|
65
|
+
).json();
|
|
66
|
+
return data;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
history: async function (type, name, offset, limit) {
|
|
70
|
+
const data = await (
|
|
71
|
+
await fetch(
|
|
72
|
+
DJ_URL +
|
|
73
|
+
'/history/' +
|
|
74
|
+
type +
|
|
75
|
+
'/' +
|
|
76
|
+
name +
|
|
77
|
+
`/?offset=${offset ? offset : 0}&limit=${limit ? limit : 100}`,
|
|
78
|
+
)
|
|
79
|
+
).json();
|
|
80
|
+
return data;
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
revisions: async function (name) {
|
|
84
|
+
const data = await (
|
|
85
|
+
await fetch(DJ_URL + '/nodes/' + name + '/revisions/')
|
|
86
|
+
).json();
|
|
87
|
+
return data;
|
|
88
|
+
},
|
|
89
|
+
|
|
30
90
|
namespace: async function (nmspce) {
|
|
31
91
|
const data = await (
|
|
32
92
|
await fetch(DJ_URL + '/namespaces/' + nmspce + '/')
|
|
@@ -39,8 +99,72 @@ export const DataJunctionAPI = {
|
|
|
39
99
|
return data;
|
|
40
100
|
},
|
|
41
101
|
|
|
102
|
+
sql: async function (metric_name, selection) {
|
|
103
|
+
const data = await (
|
|
104
|
+
await fetch(
|
|
105
|
+
DJ_URL + '/sql/' + metric_name + '?' + new URLSearchParams(selection),
|
|
106
|
+
)
|
|
107
|
+
).json();
|
|
108
|
+
return data;
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
materializations: async function (node) {
|
|
112
|
+
const data = await (
|
|
113
|
+
await fetch(DJ_URL + `/nodes/${node}/materializations/`)
|
|
114
|
+
).json();
|
|
115
|
+
|
|
116
|
+
return await Promise.all(
|
|
117
|
+
data.map(async materialization => {
|
|
118
|
+
materialization.clientCode = await (
|
|
119
|
+
await fetch(
|
|
120
|
+
DJ_URL +
|
|
121
|
+
`/client/python/add_materialization/${node}/${materialization.name}`,
|
|
122
|
+
)
|
|
123
|
+
).json();
|
|
124
|
+
return materialization;
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
columns: async function (node) {
|
|
130
|
+
return await Promise.all(
|
|
131
|
+
node.columns.map(async col => {
|
|
132
|
+
col.clientCode = await (
|
|
133
|
+
await fetch(
|
|
134
|
+
DJ_URL +
|
|
135
|
+
`/client/python/link_dimension/${node.name}/${col.name}/${col.dimension?.name}`,
|
|
136
|
+
)
|
|
137
|
+
).json();
|
|
138
|
+
return col;
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
sqls: async function (metricSelection, dimensionSelection) {
|
|
144
|
+
const params = new URLSearchParams();
|
|
145
|
+
metricSelection.map(metric => params.append('metrics', metric));
|
|
146
|
+
dimensionSelection.map(dimension => params.append('dimensions', dimension));
|
|
147
|
+
const data = await (await fetch(DJ_URL + '/sql/?' + params)).json();
|
|
148
|
+
return data;
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
data: async function (metricSelection, dimensionSelection) {
|
|
152
|
+
const params = new URLSearchParams();
|
|
153
|
+
metricSelection.map(metric => params.append('metrics', metric));
|
|
154
|
+
dimensionSelection.map(dimension => params.append('dimensions', dimension));
|
|
155
|
+
const data = await (
|
|
156
|
+
await fetch(DJ_URL + '/data/?' + params + '&limit=100&async_=true')
|
|
157
|
+
).json();
|
|
158
|
+
return data;
|
|
159
|
+
},
|
|
160
|
+
|
|
42
161
|
lineage: async function (node) {},
|
|
43
162
|
|
|
163
|
+
compiledSql: async function (node) {
|
|
164
|
+
const data = await (await fetch(DJ_URL + `/sql/${node}/`)).json();
|
|
165
|
+
return data;
|
|
166
|
+
},
|
|
167
|
+
|
|
44
168
|
dag: async function (namespace = 'default') {
|
|
45
169
|
const edges = [];
|
|
46
170
|
const data = await (await fetch(DJ_URL + '/nodes/')).json();
|