datajunction-ui 0.0.1-a1
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/.babel-plugin-macrosrc.js +5 -0
- package/.env +3 -0
- package/.eslintrc.js +20 -0
- package/.gitattributes +201 -0
- package/.husky/pre-commit +6 -0
- package/.nvmrc +1 -0
- package/.prettierignore +6 -0
- package/.prettierrc +9 -0
- package/.stylelintrc +7 -0
- package/LICENSE +22 -0
- package/Makefile +3 -0
- package/README.md +10 -0
- package/dj-logo.svg +10 -0
- package/internals/testing/loadable.mock.tsx +6 -0
- package/package.json +189 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +26 -0
- package/public/manifest.json +15 -0
- package/public/robots.txt +3 -0
- package/src/__tests__/reportWebVitals.test.jsx +44 -0
- package/src/app/__tests__/__snapshots__/index.test.tsx.snap +9 -0
- package/src/app/__tests__/index.test.tsx +14 -0
- package/src/app/components/DeleteNode.jsx +55 -0
- package/src/app/components/ListGroupItem.jsx +24 -0
- package/src/app/components/NamespaceHeader.jsx +31 -0
- package/src/app/components/QueryInfo.jsx +77 -0
- package/src/app/components/Tab.jsx +25 -0
- package/src/app/components/ToggleSwitch.jsx +20 -0
- package/src/app/components/__tests__/DeleteNode.test.jsx +53 -0
- package/src/app/components/__tests__/ListGroupItem.test.tsx +16 -0
- package/src/app/components/__tests__/NamespaceHeader.test.jsx +14 -0
- package/src/app/components/__tests__/QueryInfo.test.jsx +55 -0
- package/src/app/components/__tests__/Tab.test.jsx +27 -0
- package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
- package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +29 -0
- package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +47 -0
- package/src/app/components/djgraph/Collapse.jsx +46 -0
- package/src/app/components/djgraph/DJNode.jsx +89 -0
- package/src/app/components/djgraph/DJNodeColumns.jsx +71 -0
- package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
- package/src/app/components/djgraph/LayoutFlow.jsx +104 -0
- package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
- package/src/app/components/djgraph/__tests__/DJNode.test.tsx +24 -0
- package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
- package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
- package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +117 -0
- package/src/app/constants.js +2 -0
- package/src/app/icons/AlertIcon.jsx +32 -0
- package/src/app/icons/CollapsedIcon.jsx +15 -0
- package/src/app/icons/DJLogo.jsx +36 -0
- package/src/app/icons/DeleteIcon.jsx +21 -0
- package/src/app/icons/EditIcon.jsx +18 -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/LoadingIcon.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 +108 -0
- package/src/app/pages/AddEditNodePage/FormikSelect.jsx +46 -0
- package/src/app/pages/AddEditNodePage/FullNameField.jsx +37 -0
- package/src/app/pages/AddEditNodePage/Loadable.jsx +16 -0
- package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +89 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +103 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +132 -0
- package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
- package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
- package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
- package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +180 -0
- package/src/app/pages/AddEditNodePage/index.jsx +396 -0
- package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
- package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
- package/src/app/pages/AddEditTagPage/index.jsx +132 -0
- package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
- package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
- package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
- package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
- package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
- package/src/app/pages/LoginPage/index.jsx +17 -0
- package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
- package/src/app/pages/NamespacePage/Explorer.jsx +57 -0
- package/src/app/pages/NamespacePage/Loadable.jsx +16 -0
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +217 -0
- package/src/app/pages/NamespacePage/index.jsx +199 -0
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +166 -0
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +161 -0
- package/src/app/pages/NodePage/ClientCodePopover.jsx +46 -0
- package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +149 -0
- package/src/app/pages/NodePage/Loadable.jsx +16 -0
- package/src/app/pages/NodePage/NodeColumnTab.jsx +200 -0
- package/src/app/pages/NodePage/NodeGraphTab.jsx +112 -0
- package/src/app/pages/NodePage/NodeHistory.jsx +212 -0
- package/src/app/pages/NodePage/NodeInfoTab.jsx +212 -0
- package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +233 -0
- package/src/app/pages/NodePage/NodeSQLTab.jsx +82 -0
- package/src/app/pages/NodePage/NodeStatus.jsx +28 -0
- package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
- package/src/app/pages/NodePage/PartitionColumnPopover.jsx +153 -0
- package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +47 -0
- package/src/app/pages/NodePage/__tests__/ClientCodePopover.test.jsx +49 -0
- package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +148 -0
- package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +165 -0
- package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +591 -0
- package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +57 -0
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +757 -0
- package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +403 -0
- package/src/app/pages/NodePage/index.jsx +210 -0
- package/src/app/pages/NotFoundPage/Loadable.tsx +14 -0
- package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
- package/src/app/pages/NotFoundPage/index.tsx +23 -0
- package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
- package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +110 -0
- package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +37 -0
- package/src/app/pages/RegisterTablePage/index.jsx +142 -0
- package/src/app/pages/Root/Loadable.tsx +14 -0
- package/src/app/pages/Root/__tests__/index.test.jsx +77 -0
- package/src/app/pages/Root/assets/dj-logo.png +0 -0
- package/src/app/pages/Root/index.tsx +70 -0
- package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
- package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
- package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
- package/src/app/pages/TagPage/Loadable.jsx +16 -0
- package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
- package/src/app/pages/TagPage/index.jsx +79 -0
- package/src/app/providers/djclient.jsx +5 -0
- package/src/app/services/DJService.js +665 -0
- package/src/app/services/__tests__/DJService.test.jsx +804 -0
- package/src/index.tsx +48 -0
- package/src/mocks/mockNodes.jsx +1430 -0
- package/src/react-app-env.d.ts +4 -0
- package/src/reportWebVitals.ts +15 -0
- package/src/setupTests.ts +36 -0
- package/src/styles/dag.css +228 -0
- package/src/styles/index.css +1083 -0
- package/src/styles/loading.css +34 -0
- package/src/styles/login.css +81 -0
- package/src/styles/node-creation.scss +197 -0
- package/src/styles/styles.scss +44 -0
- package/src/styles/styles.scss.d.ts +9 -0
- package/src/utils/__tests__/__snapshots__/loadable.test.tsx.snap +17 -0
- package/src/utils/__tests__/loadable.test.tsx +53 -0
- package/src/utils/__tests__/request.test.ts +82 -0
- package/src/utils/form.jsx +23 -0
- package/src/utils/loadable.tsx +30 -0
- package/src/utils/request.ts +54 -0
- package/tsconfig.json +34 -0
- package/webpack.config.js +118 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import ClientCodePopover from './ClientCodePopover';
|
|
3
|
+
import TableIcon from '../../icons/TableIcon';
|
|
4
|
+
import AddMaterializationPopover from './AddMaterializationPopover';
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import AddBackfillPopover from './AddBackfillPopover';
|
|
7
|
+
|
|
8
|
+
const cronstrue = require('cronstrue');
|
|
9
|
+
|
|
10
|
+
export default function NodeMaterializationTab({ node, djClient }) {
|
|
11
|
+
const [materializations, setMaterializations] = useState([]);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const fetchData = async () => {
|
|
14
|
+
if (node) {
|
|
15
|
+
const data = await djClient.materializations(node.name);
|
|
16
|
+
setMaterializations(data);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
fetchData().catch(console.error);
|
|
20
|
+
}, [djClient, node]);
|
|
21
|
+
//
|
|
22
|
+
// const rangePartition = partition => {
|
|
23
|
+
// return (
|
|
24
|
+
// <div>
|
|
25
|
+
// <span className="badge partition_value">
|
|
26
|
+
// <span className="badge partition_value">{partition.range[0]}</span>to
|
|
27
|
+
// <span className="badge partition_value">{partition.range[1]}</span>
|
|
28
|
+
// </span>
|
|
29
|
+
// </div>
|
|
30
|
+
// );
|
|
31
|
+
// };
|
|
32
|
+
|
|
33
|
+
const partitionColumnsMap = node
|
|
34
|
+
? Object.fromEntries(
|
|
35
|
+
node?.columns
|
|
36
|
+
.filter(col => col.partition !== null)
|
|
37
|
+
.map(col => [col.name, col.display_name]),
|
|
38
|
+
)
|
|
39
|
+
: {};
|
|
40
|
+
const cron = materialization => {
|
|
41
|
+
var parsedCron = '';
|
|
42
|
+
try {
|
|
43
|
+
parsedCron = cronstrue.toString(materialization.schedule);
|
|
44
|
+
} catch (e) {}
|
|
45
|
+
return parsedCron;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const materializationRows = materializations => {
|
|
49
|
+
return materializations.map(materialization => (
|
|
50
|
+
<tr key={materialization.name}>
|
|
51
|
+
<td className="text-start node_name">
|
|
52
|
+
<span className={`badge cron`}>{materialization.schedule}</span>
|
|
53
|
+
<div className={`cron-description`}>{cron(materialization)} </div>
|
|
54
|
+
</td>
|
|
55
|
+
<td>
|
|
56
|
+
{materialization.engine.name}
|
|
57
|
+
<br />
|
|
58
|
+
{materialization.engine.version}
|
|
59
|
+
<ClientCodePopover code={materialization.clientCode} />
|
|
60
|
+
</td>
|
|
61
|
+
<td>
|
|
62
|
+
{node.columns
|
|
63
|
+
.filter(col => col.partition !== null)
|
|
64
|
+
.map(column => {
|
|
65
|
+
return (
|
|
66
|
+
<div className="partition__full" key={column.name}>
|
|
67
|
+
<div className="partition__header">{column.display_name}</div>
|
|
68
|
+
<div className="partition__body">
|
|
69
|
+
<code>{column.name}</code>
|
|
70
|
+
<span className="badge partition_value">
|
|
71
|
+
{column.partition.type_}
|
|
72
|
+
</span>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
})}
|
|
77
|
+
</td>
|
|
78
|
+
<td>
|
|
79
|
+
{materialization.output_tables.map(table => (
|
|
80
|
+
<div className={`table__full`} key={table}>
|
|
81
|
+
<div className="table__header">
|
|
82
|
+
<TableIcon />{' '}
|
|
83
|
+
<span className={`entity-info`}>
|
|
84
|
+
{table.split('.')[0] + '.' + table.split('.')[1]}
|
|
85
|
+
</span>
|
|
86
|
+
</div>
|
|
87
|
+
<div className={`table__body upstream_tables`}>
|
|
88
|
+
{table.split('.')[2]}
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
))}
|
|
92
|
+
</td>
|
|
93
|
+
<td>
|
|
94
|
+
{materialization.backfills.map(backfill => (
|
|
95
|
+
<a href={backfill.urls[0]} className="partitionLink">
|
|
96
|
+
<div className="partition__full" key={backfill.spec.column_name}>
|
|
97
|
+
<div className="partition__header">
|
|
98
|
+
{partitionColumnsMap[backfill.spec.column_name]}
|
|
99
|
+
</div>
|
|
100
|
+
<div className="partition__body">
|
|
101
|
+
<span className="badge partition_value">
|
|
102
|
+
{backfill.spec.range[0]}
|
|
103
|
+
</span>
|
|
104
|
+
to
|
|
105
|
+
<span className="badge partition_value">
|
|
106
|
+
{backfill.spec.range[1]}
|
|
107
|
+
</span>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</a>
|
|
111
|
+
))}
|
|
112
|
+
<AddBackfillPopover node={node} materialization={materialization} />
|
|
113
|
+
</td>
|
|
114
|
+
<td>
|
|
115
|
+
{materialization.urls.map((url, idx) => (
|
|
116
|
+
<a href={url} key={`url-${idx}`}>
|
|
117
|
+
[{idx + 1}]
|
|
118
|
+
</a>
|
|
119
|
+
))}
|
|
120
|
+
</td>
|
|
121
|
+
</tr>
|
|
122
|
+
));
|
|
123
|
+
};
|
|
124
|
+
return (
|
|
125
|
+
<>
|
|
126
|
+
<div className="table-vertical">
|
|
127
|
+
<div>
|
|
128
|
+
<h2>Materializations</h2>
|
|
129
|
+
<AddMaterializationPopover node={node} />
|
|
130
|
+
{materializations.length > 0 ? (
|
|
131
|
+
<table
|
|
132
|
+
className="card-inner-table table"
|
|
133
|
+
aria-label="Materializations"
|
|
134
|
+
aria-hidden="false"
|
|
135
|
+
>
|
|
136
|
+
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
137
|
+
<tr>
|
|
138
|
+
<th className="text-start">Schedule</th>
|
|
139
|
+
<th>Engine</th>
|
|
140
|
+
<th>Partitions</th>
|
|
141
|
+
<th>Output Tables</th>
|
|
142
|
+
<th>Backfills</th>
|
|
143
|
+
<th>URLs</th>
|
|
144
|
+
</tr>
|
|
145
|
+
</thead>
|
|
146
|
+
<tbody>
|
|
147
|
+
{materializationRows(
|
|
148
|
+
materializations.filter(
|
|
149
|
+
materialization =>
|
|
150
|
+
!(
|
|
151
|
+
materialization.name === 'default' &&
|
|
152
|
+
node.type === 'cube'
|
|
153
|
+
),
|
|
154
|
+
),
|
|
155
|
+
)}
|
|
156
|
+
</tbody>
|
|
157
|
+
</table>
|
|
158
|
+
) : (
|
|
159
|
+
<div className="message alert" style={{ marginTop: '10px' }}>
|
|
160
|
+
No materialization workflows configured for this node.
|
|
161
|
+
</div>
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
<div>
|
|
165
|
+
<h2>Materialized Datasets</h2>
|
|
166
|
+
{node && node.availability !== null ? (
|
|
167
|
+
<table
|
|
168
|
+
className="card-inner-table table"
|
|
169
|
+
aria-label="Availability"
|
|
170
|
+
aria-hidden="false"
|
|
171
|
+
>
|
|
172
|
+
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
173
|
+
<tr>
|
|
174
|
+
<th className="text-start">Catalog</th>
|
|
175
|
+
<th>Schema</th>
|
|
176
|
+
<th>Table</th>
|
|
177
|
+
<th>Valid Through</th>
|
|
178
|
+
<th>Partitions</th>
|
|
179
|
+
</tr>
|
|
180
|
+
</thead>
|
|
181
|
+
<tbody>
|
|
182
|
+
<tr>
|
|
183
|
+
<td>{node.availability.schema_}</td>
|
|
184
|
+
<td>
|
|
185
|
+
{
|
|
186
|
+
<div
|
|
187
|
+
className={`table__full`}
|
|
188
|
+
key={node.availability.table}
|
|
189
|
+
>
|
|
190
|
+
<div className="table__header">
|
|
191
|
+
<TableIcon />{' '}
|
|
192
|
+
<span className={`entity-info`}>
|
|
193
|
+
{node.availability.catalog +
|
|
194
|
+
'.' +
|
|
195
|
+
node.availability.schema_}
|
|
196
|
+
</span>
|
|
197
|
+
</div>
|
|
198
|
+
<div className={`table__body upstream_tables`}>
|
|
199
|
+
<a href={node.availability.url}>
|
|
200
|
+
{node.availability.table}
|
|
201
|
+
</a>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
}
|
|
205
|
+
</td>
|
|
206
|
+
<td>{node.availability.valid_through_ts}</td>
|
|
207
|
+
<td>
|
|
208
|
+
<span
|
|
209
|
+
className={`badge partition_value`}
|
|
210
|
+
style={{ fontSize: '100%' }}
|
|
211
|
+
>
|
|
212
|
+
<span className={`badge partition_value_highlight`}>
|
|
213
|
+
{node.availability.min_temporal_partition}
|
|
214
|
+
</span>
|
|
215
|
+
to
|
|
216
|
+
<span className={`badge partition_value_highlight`}>
|
|
217
|
+
{node.availability.max_temporal_partition}
|
|
218
|
+
</span>
|
|
219
|
+
</span>
|
|
220
|
+
</td>
|
|
221
|
+
</tr>
|
|
222
|
+
</tbody>
|
|
223
|
+
</table>
|
|
224
|
+
) : (
|
|
225
|
+
<div className="message alert" style={{ marginTop: '10px' }}>
|
|
226
|
+
No materialized datasets available for this node.
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useContext, useEffect, useState } from 'react';
|
|
2
|
+
import Select from 'react-select';
|
|
3
|
+
import DJClientContext from '../../providers/djclient';
|
|
4
|
+
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
5
|
+
import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
|
|
6
|
+
|
|
7
|
+
const NodeSQLTab = djNode => {
|
|
8
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
9
|
+
const [query, setQuery] = useState('');
|
|
10
|
+
|
|
11
|
+
const [selection, setSelection] = useState({
|
|
12
|
+
dimensions: [],
|
|
13
|
+
filters: [],
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const fetchData = async () => {
|
|
18
|
+
const query = await djClient.sql(djNode.djNode.name, selection);
|
|
19
|
+
setQuery(query.sql);
|
|
20
|
+
};
|
|
21
|
+
fetchData().catch(console.error);
|
|
22
|
+
}, [djClient, djNode.djNode.name, selection]);
|
|
23
|
+
const dimensionsList = djNode.djNode.dimensions
|
|
24
|
+
? djNode.djNode.dimensions.map(dim => ({
|
|
25
|
+
value: dim.name,
|
|
26
|
+
label: dim.name + ` (${dim.type})`,
|
|
27
|
+
}))
|
|
28
|
+
: [''];
|
|
29
|
+
|
|
30
|
+
const handleSubmit = event => {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handleChange = event => {
|
|
35
|
+
setSelection({ filters: [], dimensions: event.map(dim => dim.value) });
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<form
|
|
40
|
+
id="retrieve-sql"
|
|
41
|
+
name="retrieve-sql"
|
|
42
|
+
onSubmit={handleSubmit.bind(this)}
|
|
43
|
+
>
|
|
44
|
+
<div>
|
|
45
|
+
<h4>Group By</h4>
|
|
46
|
+
<Select
|
|
47
|
+
name="dimensions"
|
|
48
|
+
options={dimensionsList}
|
|
49
|
+
isMulti
|
|
50
|
+
isClearable
|
|
51
|
+
onChange={handleChange}
|
|
52
|
+
/>
|
|
53
|
+
{/*<h4>Filters</h4>*/}
|
|
54
|
+
{/*<Select*/}
|
|
55
|
+
{/* name="filter_name"*/}
|
|
56
|
+
{/* options={dimensionsList}*/}
|
|
57
|
+
{/* className="filters_attribute"*/}
|
|
58
|
+
{/*/>*/}
|
|
59
|
+
{/*<Select*/}
|
|
60
|
+
{/* name="filter_operator"*/}
|
|
61
|
+
{/* options={options}*/}
|
|
62
|
+
{/* className="filters_attribute"*/}
|
|
63
|
+
{/*/>*/}
|
|
64
|
+
{/*<textarea name="filter_value" className="filters_attribute" />*/}
|
|
65
|
+
|
|
66
|
+
<div
|
|
67
|
+
style={{
|
|
68
|
+
width: window.innerWidth * 0.8,
|
|
69
|
+
marginTop: '2rem',
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<h6 className="mb-0 w-100">Query</h6>
|
|
73
|
+
<SyntaxHighlighter language="sql" style={foundation}>
|
|
74
|
+
{query}
|
|
75
|
+
</SyntaxHighlighter>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</form>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default NodeSQLTab;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Component } from 'react';
|
|
2
|
+
import ValidIcon from '../../icons/ValidIcon';
|
|
3
|
+
import InvalidIcon from '../../icons/InvalidIcon';
|
|
4
|
+
|
|
5
|
+
export default class NodeStatus extends Component {
|
|
6
|
+
render() {
|
|
7
|
+
const { node } = this.props;
|
|
8
|
+
return (
|
|
9
|
+
<>
|
|
10
|
+
{node?.status === 'valid' ? (
|
|
11
|
+
<span
|
|
12
|
+
className="status__valid status"
|
|
13
|
+
style={{ alignContent: 'center' }}
|
|
14
|
+
>
|
|
15
|
+
<ValidIcon />
|
|
16
|
+
</span>
|
|
17
|
+
) : (
|
|
18
|
+
<span
|
|
19
|
+
className="status__invalid status"
|
|
20
|
+
style={{ alignContent: 'center' }}
|
|
21
|
+
>
|
|
22
|
+
<InvalidIcon />
|
|
23
|
+
</span>
|
|
24
|
+
)}
|
|
25
|
+
</>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
export default function NodesWithDimension({ node, djClient }) {
|
|
5
|
+
const [availableNodes, setAvailableNodes] = useState([]);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const fetchData = async () => {
|
|
9
|
+
const data = await djClient.nodesWithDimension(node.name);
|
|
10
|
+
setAvailableNodes(data);
|
|
11
|
+
};
|
|
12
|
+
fetchData().catch(console.error);
|
|
13
|
+
}, [djClient, node]);
|
|
14
|
+
return (
|
|
15
|
+
<div className="table-responsive">
|
|
16
|
+
<table className="card-inner-table table">
|
|
17
|
+
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
18
|
+
<tr>
|
|
19
|
+
<th className="text-start">Name</th>
|
|
20
|
+
<th>Type</th>
|
|
21
|
+
</tr>
|
|
22
|
+
</thead>
|
|
23
|
+
<tbody>
|
|
24
|
+
{availableNodes.map(node => (
|
|
25
|
+
<tr>
|
|
26
|
+
<td>
|
|
27
|
+
<a href={`/nodes/${node.name}`}>{node.display_name}</a>
|
|
28
|
+
</td>
|
|
29
|
+
<td>
|
|
30
|
+
<span
|
|
31
|
+
className={'node_type__' + node.type + ' badge node_type'}
|
|
32
|
+
>
|
|
33
|
+
{node.type}
|
|
34
|
+
</span>
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
))}
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { useContext, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import DJClientContext from '../../providers/djclient';
|
|
4
|
+
import { Field, Form, Formik } from 'formik';
|
|
5
|
+
import { FormikSelect } from '../AddEditNodePage/FormikSelect';
|
|
6
|
+
import EditIcon from '../../icons/EditIcon';
|
|
7
|
+
import { displayMessageAfterSubmit, labelize } from '../../../utils/form';
|
|
8
|
+
|
|
9
|
+
export default function PartitionColumnPopover({ column, node, onSubmit }) {
|
|
10
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
11
|
+
const [popoverAnchor, setPopoverAnchor] = useState(false);
|
|
12
|
+
const ref = useRef(null);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const handleClickOutside = event => {
|
|
16
|
+
if (ref.current && !ref.current.contains(event.target)) {
|
|
17
|
+
setPopoverAnchor(false);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
document.addEventListener('click', handleClickOutside, true);
|
|
21
|
+
return () => {
|
|
22
|
+
document.removeEventListener('click', handleClickOutside, true);
|
|
23
|
+
};
|
|
24
|
+
}, [setPopoverAnchor]);
|
|
25
|
+
|
|
26
|
+
const savePartition = async (
|
|
27
|
+
{ node, column, partition_type, format, granularity },
|
|
28
|
+
{ setSubmitting, setStatus },
|
|
29
|
+
) => {
|
|
30
|
+
setSubmitting(false);
|
|
31
|
+
const response = await djClient.setPartition(
|
|
32
|
+
node,
|
|
33
|
+
column,
|
|
34
|
+
partition_type,
|
|
35
|
+
format,
|
|
36
|
+
granularity,
|
|
37
|
+
);
|
|
38
|
+
if (response.status === 200 || response.status === 201) {
|
|
39
|
+
setStatus({ success: 'Saved!' });
|
|
40
|
+
} else {
|
|
41
|
+
setStatus({
|
|
42
|
+
failure: `${response.json.message}`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
onSubmit();
|
|
46
|
+
// window.location.reload();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
<button
|
|
52
|
+
className="edit_button"
|
|
53
|
+
aria-label="PartitionColumn"
|
|
54
|
+
tabIndex="0"
|
|
55
|
+
onClick={() => {
|
|
56
|
+
setPopoverAnchor(!popoverAnchor);
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<EditIcon />
|
|
60
|
+
</button>
|
|
61
|
+
<div
|
|
62
|
+
className="popover"
|
|
63
|
+
role="dialog"
|
|
64
|
+
aria-label="client-code"
|
|
65
|
+
style={{ display: popoverAnchor === false ? 'none' : 'block' }}
|
|
66
|
+
ref={ref}
|
|
67
|
+
>
|
|
68
|
+
<Formik
|
|
69
|
+
initialValues={{
|
|
70
|
+
column: column.name,
|
|
71
|
+
node: node.name,
|
|
72
|
+
partition_type: '',
|
|
73
|
+
format: 'yyyyMMdd',
|
|
74
|
+
granularity: 'day',
|
|
75
|
+
}}
|
|
76
|
+
onSubmit={savePartition}
|
|
77
|
+
>
|
|
78
|
+
{function Render({ values, isSubmitting, status, setFieldValue }) {
|
|
79
|
+
return (
|
|
80
|
+
<Form>
|
|
81
|
+
{displayMessageAfterSubmit(status)}
|
|
82
|
+
<span data-testid="edit-partition">
|
|
83
|
+
<label htmlFor="react-select-3-input">Partition Type</label>
|
|
84
|
+
<Field
|
|
85
|
+
as="select"
|
|
86
|
+
name="partition_type"
|
|
87
|
+
id="partitionType"
|
|
88
|
+
placeholder="Partition Type"
|
|
89
|
+
>
|
|
90
|
+
<option value=""></option>
|
|
91
|
+
<option value="temporal">Temporal</option>
|
|
92
|
+
<option value="categorical">Categorical</option>
|
|
93
|
+
</Field>
|
|
94
|
+
</span>
|
|
95
|
+
<input
|
|
96
|
+
hidden={true}
|
|
97
|
+
name="column"
|
|
98
|
+
value={column.name}
|
|
99
|
+
readOnly={true}
|
|
100
|
+
/>
|
|
101
|
+
<input
|
|
102
|
+
hidden={true}
|
|
103
|
+
name="node"
|
|
104
|
+
value={node.name}
|
|
105
|
+
readOnly={true}
|
|
106
|
+
/>
|
|
107
|
+
<br />
|
|
108
|
+
<br />
|
|
109
|
+
{values.partition_type === 'temporal' ? (
|
|
110
|
+
<>
|
|
111
|
+
<label htmlFor="react-select-3-input">
|
|
112
|
+
Partition Format
|
|
113
|
+
</label>
|
|
114
|
+
<Field
|
|
115
|
+
type="text"
|
|
116
|
+
name="format"
|
|
117
|
+
id="partitionFormat"
|
|
118
|
+
placeholder="Optional temporal partition format (ex: yyyyMMdd)"
|
|
119
|
+
/>
|
|
120
|
+
<br />
|
|
121
|
+
<br />
|
|
122
|
+
<label htmlFor="react-select-3-input">
|
|
123
|
+
Partition Granularity
|
|
124
|
+
</label>
|
|
125
|
+
<Field
|
|
126
|
+
as="select"
|
|
127
|
+
name="granularity"
|
|
128
|
+
id="partitionGranularity"
|
|
129
|
+
placeholder="Granularity"
|
|
130
|
+
>
|
|
131
|
+
<option value="day">Day</option>
|
|
132
|
+
<option value="hour">Hour</option>
|
|
133
|
+
</Field>
|
|
134
|
+
</>
|
|
135
|
+
) : (
|
|
136
|
+
''
|
|
137
|
+
)}
|
|
138
|
+
<button
|
|
139
|
+
className="add_node"
|
|
140
|
+
type="submit"
|
|
141
|
+
aria-label="SaveEditColumn"
|
|
142
|
+
aria-hidden="false"
|
|
143
|
+
>
|
|
144
|
+
Save
|
|
145
|
+
</button>
|
|
146
|
+
</Form>
|
|
147
|
+
);
|
|
148
|
+
}}
|
|
149
|
+
</Formik>
|
|
150
|
+
</div>
|
|
151
|
+
</>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent, waitFor, screen } from '@testing-library/react';
|
|
3
|
+
import EditColumnPopover from '../EditColumnPopover';
|
|
4
|
+
import DJClientContext from '../../../providers/djclient';
|
|
5
|
+
import AddBackfillPopover from '../AddBackfillPopover';
|
|
6
|
+
import { mocks } from '../../../../mocks/mockNodes';
|
|
7
|
+
|
|
8
|
+
const mockDjClient = {
|
|
9
|
+
DataJunctionAPI: {
|
|
10
|
+
runBackfill: jest.fn(),
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe('<AddBackfillPopover />', () => {
|
|
15
|
+
it('renders correctly and handles form submission', async () => {
|
|
16
|
+
// Mock onSubmit function
|
|
17
|
+
const onSubmitMock = jest.fn();
|
|
18
|
+
|
|
19
|
+
mockDjClient.DataJunctionAPI.runBackfill.mockReturnValue({
|
|
20
|
+
status: 201,
|
|
21
|
+
json: { message: '' },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Render the component
|
|
25
|
+
const { getByLabelText, getByText } = render(
|
|
26
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
27
|
+
<AddBackfillPopover
|
|
28
|
+
node={mocks.mockMetricNode}
|
|
29
|
+
materialization={mocks.nodeMaterializations}
|
|
30
|
+
onSubmit={onSubmitMock}
|
|
31
|
+
/>
|
|
32
|
+
</DJClientContext.Provider>,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// Open the popover
|
|
36
|
+
fireEvent.click(getByLabelText('AddBackfill'));
|
|
37
|
+
|
|
38
|
+
fireEvent.click(getByText('Save'));
|
|
39
|
+
getByText('Save').click();
|
|
40
|
+
|
|
41
|
+
// Expect setAttributes to be called
|
|
42
|
+
await waitFor(() => {
|
|
43
|
+
expect(mockDjClient.DataJunctionAPI.runBackfill).toHaveBeenCalled();
|
|
44
|
+
expect(getByText('Saved!')).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import ClientCodePopover from '../ClientCodePopover';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
|
|
6
|
+
describe('<ClientCodePopover />', () => {
|
|
7
|
+
const defaultProps = {
|
|
8
|
+
code: "print('Hello, World!')",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
it('toggles the code popover visibility when the button is clicked', async () => {
|
|
12
|
+
render(<ClientCodePopover {...defaultProps} />);
|
|
13
|
+
|
|
14
|
+
const button = screen.getByRole('button', 'code-button');
|
|
15
|
+
|
|
16
|
+
// Initially, the popover should be hidden
|
|
17
|
+
expect(screen.getByRole('dialog', { hidden: true })).toHaveStyle(
|
|
18
|
+
'display: none',
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
// Clicking the button should display the popover
|
|
22
|
+
fireEvent.click(button);
|
|
23
|
+
expect(screen.getByRole('dialog', { hidden: true })).not.toHaveStyle(
|
|
24
|
+
'display: none',
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// Clicking the button again should hide the popover
|
|
28
|
+
fireEvent.click(button);
|
|
29
|
+
expect(screen.getByRole('dialog', { hidden: true })).toHaveStyle(
|
|
30
|
+
'display: none',
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Trigger onClose by pressing <escape>
|
|
34
|
+
userEvent.keyboard('{Escape}');
|
|
35
|
+
// fireEvent.click(screen.getByTestId('body').firstChild());
|
|
36
|
+
await waitFor(() => {
|
|
37
|
+
expect(screen.getByRole('dialog', { hidden: true })).toHaveStyle(
|
|
38
|
+
'display: none',
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('renders the provided code within the SyntaxHighlighter', () => {
|
|
44
|
+
render(<ClientCodePopover {...defaultProps} />);
|
|
45
|
+
expect(screen.getByRole('dialog', { hidden: true })).toHaveTextContent(
|
|
46
|
+
defaultProps.code,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
});
|