datajunction-ui 0.0.1-a45.dev14 → 0.0.1-a46
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/Makefile +5 -0
- package/package.json +2 -2
- package/src/app/components/NodeMaterializationDelete.jsx +80 -0
- package/src/app/pages/CubeBuilderPage/index.jsx +19 -4
- package/src/app/pages/NamespacePage/index.jsx +2 -1
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +46 -51
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +33 -24
- package/src/app/pages/NodePage/MaterializationConfigField.jsx +38 -31
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +179 -110
- package/src/app/pages/NodePage/NodeStatus.jsx +94 -21
- package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
- package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +13 -3
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +12 -5
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +276 -191
- package/src/app/services/DJService.js +37 -25
- package/src/app/services/__tests__/DJService.test.jsx +37 -21
- package/src/mocks/mockNodes.jsx +61 -7
- package/src/styles/index.css +128 -11
- package/src/styles/node-list.css +4 -0
- package/src/app/components/forms/NodeTagsInput.jsx +0 -61
- package/src/app/pages/AddEditNodePage/QueryTesterSection.jsx +0 -62
- package/src/app/pages/QueryRunner.jsx +0 -31
package/src/mocks/mockNodes.jsx
CHANGED
|
@@ -41,7 +41,9 @@ export const mocks = {
|
|
|
41
41
|
type: 'string',
|
|
42
42
|
attributes: [],
|
|
43
43
|
dimension: null,
|
|
44
|
-
partition:
|
|
44
|
+
partition: {
|
|
45
|
+
type_: 'categorical',
|
|
46
|
+
},
|
|
45
47
|
},
|
|
46
48
|
],
|
|
47
49
|
updated_at: '2024-01-24T16:39:14.029366+00:00',
|
|
@@ -461,12 +463,14 @@ export const mocks = {
|
|
|
461
463
|
{
|
|
462
464
|
backfills: [
|
|
463
465
|
{
|
|
464
|
-
spec:
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
466
|
+
spec: [
|
|
467
|
+
{
|
|
468
|
+
column_name: 'default_DOT_hard_hat_DOT_hire_date',
|
|
469
|
+
values: null,
|
|
470
|
+
range: ['20230101', '20230102'],
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
urls: ['a', 'b'],
|
|
470
474
|
},
|
|
471
475
|
],
|
|
472
476
|
name: 'country_birth_date_contractor_id_379232101',
|
|
@@ -477,6 +481,7 @@ export const mocks = {
|
|
|
477
481
|
"SELECT default_DOT_hard_hats.address,\n\tdefault_DOT_hard_hats.birth_date,\n\tdefault_DOT_hard_hats.city,\n\tdefault_DOT_hard_hats.contractor_id,\n\tdefault_DOT_hard_hats.country,\n\tdefault_DOT_hard_hats.first_name,\n\tdefault_DOT_hard_hats.hard_hat_id,\n\tdefault_DOT_hard_hats.hire_date,\n\tdefault_DOT_hard_hats.last_name,\n\tdefault_DOT_hard_hats.manager,\n\tdefault_DOT_hard_hats.postal_code,\n\tdefault_DOT_hard_hats.state,\n\tdefault_DOT_hard_hats.title \n FROM roads.hard_hats AS default_DOT_hard_hats \n WHERE default_DOT_hard_hats.country IN ('DE', 'MY') AND default_DOT_hard_hats.contractor_id BETWEEN 1 AND 10\n\n",
|
|
478
482
|
upstream_tables: ['default.roads.hard_hats'],
|
|
479
483
|
},
|
|
484
|
+
strategy: 'incremental_time',
|
|
480
485
|
schedule: '0 * * * *',
|
|
481
486
|
job: 'SparkSqlMaterializationJob',
|
|
482
487
|
output_tables: ['common.a', 'common.b'],
|
|
@@ -1553,4 +1558,53 @@ export const mocks = {
|
|
|
1553
1558
|
tag_type: 'reports',
|
|
1554
1559
|
},
|
|
1555
1560
|
],
|
|
1561
|
+
materializationInfo: {
|
|
1562
|
+
job_types: [
|
|
1563
|
+
{
|
|
1564
|
+
name: 'spark_sql',
|
|
1565
|
+
label: 'Spark SQL',
|
|
1566
|
+
description: 'Spark SQL materialization job',
|
|
1567
|
+
allowed_node_types: ['transform', 'dimension', 'cube'],
|
|
1568
|
+
job_class: 'SparkSqlMaterializationJob',
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
name: 'druid_measures_cube',
|
|
1572
|
+
label: 'Druid Measures Cube (Pre-Agg Cube)',
|
|
1573
|
+
description:
|
|
1574
|
+
"Used to materialize a cube's measures to Druid for low-latency access to a set of metrics and dimensions. While the logical cube definition is at the level of metrics and dimensions, this materialized Druid cube will contain measures and dimensions, with rollup configured on the measures where appropriate.",
|
|
1575
|
+
allowed_node_types: ['cube'],
|
|
1576
|
+
job_class: 'DruidMeasuresCubeMaterializationJob',
|
|
1577
|
+
},
|
|
1578
|
+
{
|
|
1579
|
+
name: 'druid_metrics_cube',
|
|
1580
|
+
label: 'Druid Metrics Cube (Post-Agg Cube)',
|
|
1581
|
+
description:
|
|
1582
|
+
"Used to materialize a cube of metrics and dimensions to Druid for low-latency access. The materialized cube is at the metric level, meaning that all metrics will be aggregated to the level of the cube's dimensions.",
|
|
1583
|
+
allowed_node_types: ['cube'],
|
|
1584
|
+
job_class: 'DruidMetricsCubeMaterializationJob',
|
|
1585
|
+
},
|
|
1586
|
+
],
|
|
1587
|
+
strategies: [
|
|
1588
|
+
{
|
|
1589
|
+
name: 'full',
|
|
1590
|
+
label: 'Full',
|
|
1591
|
+
},
|
|
1592
|
+
{
|
|
1593
|
+
name: 'snapshot',
|
|
1594
|
+
label: 'Snapshot',
|
|
1595
|
+
},
|
|
1596
|
+
{
|
|
1597
|
+
name: 'snapshot_partition',
|
|
1598
|
+
label: 'Snapshot Partition',
|
|
1599
|
+
},
|
|
1600
|
+
{
|
|
1601
|
+
name: 'incremental_time',
|
|
1602
|
+
label: 'Incremental Time',
|
|
1603
|
+
},
|
|
1604
|
+
{
|
|
1605
|
+
name: 'view',
|
|
1606
|
+
label: 'View',
|
|
1607
|
+
},
|
|
1608
|
+
],
|
|
1609
|
+
},
|
|
1556
1610
|
};
|
package/src/styles/index.css
CHANGED
|
@@ -593,6 +593,12 @@ tbody th {
|
|
|
593
593
|
color: #777777;
|
|
594
594
|
}
|
|
595
595
|
|
|
596
|
+
.strategy {
|
|
597
|
+
background-color: rgb(255, 239, 215) !important;
|
|
598
|
+
color: #6c3b21;
|
|
599
|
+
font-size: 1rem;
|
|
600
|
+
}
|
|
601
|
+
|
|
596
602
|
.status__valid {
|
|
597
603
|
color: #00b368;
|
|
598
604
|
}
|
|
@@ -1018,17 +1024,19 @@ pre {
|
|
|
1018
1024
|
}
|
|
1019
1025
|
|
|
1020
1026
|
.add_button {
|
|
1021
|
-
background-color: #
|
|
1022
|
-
color: #
|
|
1023
|
-
text-transform:
|
|
1027
|
+
background-color: #2f7986 !important;
|
|
1028
|
+
color: #fff;
|
|
1029
|
+
text-transform: none;
|
|
1024
1030
|
vertical-align: middle;
|
|
1025
|
-
padding:
|
|
1026
|
-
|
|
1027
|
-
|
|
1031
|
+
padding-right: 1rem;
|
|
1032
|
+
padding-left: 1rem;
|
|
1033
|
+
padding-top: 0.5rem;
|
|
1034
|
+
padding-bottom: 0.5rem;
|
|
1035
|
+
margin-bottom: 0.5rem !important;
|
|
1036
|
+
font-size: 1rem;
|
|
1028
1037
|
border-radius: 0.5rem;
|
|
1029
1038
|
word-wrap: break-word;
|
|
1030
1039
|
white-space: break-spaces;
|
|
1031
|
-
margin-left: 1rem;
|
|
1032
1040
|
}
|
|
1033
1041
|
|
|
1034
1042
|
.edit_button {
|
|
@@ -1090,10 +1098,6 @@ pre {
|
|
|
1090
1098
|
transition: opacity 0.15s linear;
|
|
1091
1099
|
}
|
|
1092
1100
|
|
|
1093
|
-
.partitionLink:hover {
|
|
1094
|
-
text-decoration: none;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
1101
|
.dimensionsList {
|
|
1098
1102
|
padding: 12px;
|
|
1099
1103
|
opacity: 1;
|
|
@@ -1202,3 +1206,116 @@ pre {
|
|
|
1202
1206
|
text-transform: uppercase;
|
|
1203
1207
|
padding-left: 10px;
|
|
1204
1208
|
}
|
|
1209
|
+
|
|
1210
|
+
.validation_error {
|
|
1211
|
+
border: #b34b0025 1px solid;
|
|
1212
|
+
border-left: #b34b00 5px solid;
|
|
1213
|
+
padding-left: 20px;
|
|
1214
|
+
padding-top: 5px;
|
|
1215
|
+
padding-bottom: 5px;
|
|
1216
|
+
font-size: small;
|
|
1217
|
+
width: 600px;
|
|
1218
|
+
word-wrap: break-word;
|
|
1219
|
+
margin-top: 3px;
|
|
1220
|
+
background-color: #ffffff;
|
|
1221
|
+
margin-bottom: 3px;
|
|
1222
|
+
margin-left: -20px;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
.partitionLink {
|
|
1226
|
+
/*border: 1px solid #ccc4d5;*/
|
|
1227
|
+
padding: 12px;
|
|
1228
|
+
margin: 5px;
|
|
1229
|
+
border-radius: 0.375rem;
|
|
1230
|
+
background-color: #ccc4d525;
|
|
1231
|
+
}
|
|
1232
|
+
.partitionLink:hover {
|
|
1233
|
+
background-color: rgba(157, 147, 168, 0.15);
|
|
1234
|
+
}
|
|
1235
|
+
.partitionLink a:hover {
|
|
1236
|
+
text-decoration: none;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
.backfills {
|
|
1240
|
+
margin-left: -4rem;
|
|
1241
|
+
--spacing : 1.5rem;
|
|
1242
|
+
--radius : 10px;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
.backfills li{
|
|
1246
|
+
display : block;
|
|
1247
|
+
position : relative;
|
|
1248
|
+
padding-left : calc(2 * var(--spacing) - var(--radius) - 2px);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
.backfills ul{
|
|
1252
|
+
margin-left : calc(var(--radius) - var(--spacing));
|
|
1253
|
+
padding-left : 2rem;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
.backfills ul li{
|
|
1257
|
+
border-left : 2px solid #ddd;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
.backfills ul li:last-child{
|
|
1261
|
+
border-color : transparent;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
.backfills ul li::before{
|
|
1265
|
+
content : '';
|
|
1266
|
+
display : block;
|
|
1267
|
+
position : absolute;
|
|
1268
|
+
top : calc(var(--spacing) / -2);
|
|
1269
|
+
left : -2px;
|
|
1270
|
+
width : calc(var(--spacing) + 2px);
|
|
1271
|
+
height : calc(var(--spacing) + 1px);
|
|
1272
|
+
border : solid #ddd;
|
|
1273
|
+
border-width : 0 0 2px 2px;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
.backfills summary{
|
|
1277
|
+
display : block;
|
|
1278
|
+
cursor : pointer;
|
|
1279
|
+
margin-bottom: 10px;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
.backfills summary::marker,
|
|
1283
|
+
.backfills summary::-webkit-details-marker{
|
|
1284
|
+
display : none;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
.backfills summary:focus{
|
|
1288
|
+
outline : none;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
.backfills summary:focus-visible{
|
|
1292
|
+
outline : 1px dotted #000;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
.backfills summary::before{
|
|
1296
|
+
z-index : 1;
|
|
1297
|
+
/*background : #696 url('expand-collapse.svg') 0 0;*/
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
.backfills details[open] > summary::before{
|
|
1301
|
+
background-position : calc(-2 * var(--radius)) 0;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
.backfills_header {
|
|
1305
|
+
font-size: 16px;
|
|
1306
|
+
margin-left: 18px;
|
|
1307
|
+
border-left: 2px solid #ddd;
|
|
1308
|
+
padding-left: 40px;
|
|
1309
|
+
margin-bottom: 10px;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
.tr {
|
|
1313
|
+
display: inline-flex;
|
|
1314
|
+
}
|
|
1315
|
+
.tr li {
|
|
1316
|
+
padding-right: 10px;
|
|
1317
|
+
}
|
|
1318
|
+
.td {
|
|
1319
|
+
display: table-cell;
|
|
1320
|
+
padding: 8px;
|
|
1321
|
+
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { ErrorMessage } from 'formik';
|
|
2
|
-
import { FormikSelect } from '../../pages/AddEditNodePage/FormikSelect';
|
|
3
|
-
import { Action } from './Action';
|
|
4
|
-
import { useContext, useEffect, useState } from 'react';
|
|
5
|
-
import DJClientContext from '../../providers/djclient';
|
|
6
|
-
|
|
7
|
-
export default function NodeTagsInput({ action, node }) {
|
|
8
|
-
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
9
|
-
const [tags, setTags] = useState([]);
|
|
10
|
-
|
|
11
|
-
// Get list of tags
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
const fetchData = async () => {
|
|
14
|
-
const tags = await djClient.listTags();
|
|
15
|
-
setTags(
|
|
16
|
-
tags.map(tag => ({
|
|
17
|
-
value: tag.name,
|
|
18
|
-
label: tag.display_name,
|
|
19
|
-
})),
|
|
20
|
-
);
|
|
21
|
-
};
|
|
22
|
-
fetchData().catch(console.error);
|
|
23
|
-
}, [djClient, djClient.listTags]);
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<div
|
|
27
|
-
className="TagsInput"
|
|
28
|
-
style={{ width: '25%', margin: '1rem 0 1rem 1.2rem' }}
|
|
29
|
-
>
|
|
30
|
-
<ErrorMessage name="tags" component="span" />
|
|
31
|
-
<label htmlFor="react-select-3-input">Tags</label>
|
|
32
|
-
<span data-testid="select-tags">
|
|
33
|
-
{action === Action.Edit && node?.tags?.length >= 0 ? (
|
|
34
|
-
<FormikSelect
|
|
35
|
-
className=""
|
|
36
|
-
isMulti={true}
|
|
37
|
-
selectOptions={tags}
|
|
38
|
-
formikFieldName="tags"
|
|
39
|
-
placeholder="Choose Tags"
|
|
40
|
-
defaultValue={node?.tags?.map(t => {
|
|
41
|
-
return { value: t.name, label: t.display_name };
|
|
42
|
-
})}
|
|
43
|
-
/>
|
|
44
|
-
) : (
|
|
45
|
-
''
|
|
46
|
-
)}
|
|
47
|
-
{action === Action.Add ? (
|
|
48
|
-
<FormikSelect
|
|
49
|
-
className=""
|
|
50
|
-
isMulti={true}
|
|
51
|
-
selectOptions={tags}
|
|
52
|
-
formikFieldName="tags"
|
|
53
|
-
placeholder="Choose Tags"
|
|
54
|
-
/>
|
|
55
|
-
) : (
|
|
56
|
-
''
|
|
57
|
-
)}
|
|
58
|
-
</span>
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Query tester component
|
|
3
|
-
*/
|
|
4
|
-
import { ErrorMessage, useFormikContext } from 'formik';
|
|
5
|
-
import React, { useContext, useEffect, useState } from 'react';
|
|
6
|
-
import DJClientContext from '../../providers/djclient';
|
|
7
|
-
import { FormikSelect } from './FormikSelect';
|
|
8
|
-
import QueryBuilder from 'react-querybuilder';
|
|
9
|
-
|
|
10
|
-
export const QueryTesterSection = ({}) => {
|
|
11
|
-
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
12
|
-
|
|
13
|
-
const [fields, setFields] = useState([]);
|
|
14
|
-
const [filters, setFilters] = useState({
|
|
15
|
-
combinator: 'and',
|
|
16
|
-
rules: [],
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
// Used to pull out current form values for node validation
|
|
20
|
-
const { values } = useFormikContext();
|
|
21
|
-
|
|
22
|
-
// Select options, i.e., the available dimensions
|
|
23
|
-
const [selectOptions, setSelectOptions] = useState([]);
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
const fetchData = async () => {
|
|
27
|
-
if (values.query) {
|
|
28
|
-
const data = await djClient.node(values.upstream_node);
|
|
29
|
-
setSelectOptions(
|
|
30
|
-
data.columns.map(col => {
|
|
31
|
-
return {
|
|
32
|
-
value: col.name,
|
|
33
|
-
label: col.name,
|
|
34
|
-
};
|
|
35
|
-
}),
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
fetchData().catch(console.error);
|
|
40
|
-
}, [djClient, values.upstream_node]);
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<>
|
|
44
|
-
<h4>Test Query</h4>
|
|
45
|
-
<label>Add Filters</label>
|
|
46
|
-
<QueryBuilder
|
|
47
|
-
fields={fields}
|
|
48
|
-
query={filters}
|
|
49
|
-
onQueryChange={q => setFilters(q)}
|
|
50
|
-
/>
|
|
51
|
-
<span
|
|
52
|
-
className="button-3 execute-button"
|
|
53
|
-
// onClick={getData}
|
|
54
|
-
role="button"
|
|
55
|
-
aria-label="RunQuery"
|
|
56
|
-
aria-hidden="false"
|
|
57
|
-
>
|
|
58
|
-
{'Run Query'}
|
|
59
|
-
</span>
|
|
60
|
-
</>
|
|
61
|
-
);
|
|
62
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import QueryBuilder from 'react-querybuilder';
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
|
|
4
|
-
export function QueryRunner() {
|
|
5
|
-
const [fields, setFields] = useState([]);
|
|
6
|
-
const [filters, setFilters] = useState({
|
|
7
|
-
combinator: 'and',
|
|
8
|
-
rules: [],
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
<>
|
|
13
|
-
<h4>Test Query</h4>
|
|
14
|
-
<label>Add Filters</label>
|
|
15
|
-
<QueryBuilder
|
|
16
|
-
fields={fields}
|
|
17
|
-
query={filters}
|
|
18
|
-
onQueryChange={q => setFilters(q)}
|
|
19
|
-
/>
|
|
20
|
-
<span
|
|
21
|
-
className="button-3 execute-button"
|
|
22
|
-
// onClick={getData}
|
|
23
|
-
role="button"
|
|
24
|
-
aria-label="RunQuery"
|
|
25
|
-
aria-hidden="false"
|
|
26
|
-
>
|
|
27
|
-
{'Run Query'}
|
|
28
|
-
</span>
|
|
29
|
-
</>
|
|
30
|
-
);
|
|
31
|
-
}
|