datajunction-ui 0.0.1-a45.dev5 → 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.
@@ -12,7 +12,7 @@ export const DataJunctionAPI = {
12
12
  },
13
13
 
14
14
  logout: async function () {
15
- await await fetch(`${DJ_URL}/logout/`, {
15
+ return await fetch(`${DJ_URL}/logout/`, {
16
16
  credentials: 'include',
17
17
  method: 'POST',
18
18
  });
@@ -388,19 +388,7 @@ export const DataJunctionAPI = {
388
388
  })
389
389
  ).json();
390
390
 
391
- return await Promise.all(
392
- data.map(async materialization => {
393
- materialization.clientCode = await (
394
- await fetch(
395
- `${DJ_URL}/datajunction-clients/python/add_materialization/${node}/${materialization.name}`,
396
- {
397
- credentials: 'include',
398
- },
399
- )
400
- ).json();
401
- return materialization;
402
- }),
403
- );
391
+ return data;
404
392
  },
405
393
 
406
394
  columns: async function (node) {
@@ -804,13 +792,7 @@ export const DataJunctionAPI = {
804
792
  );
805
793
  return { status: response.status, json: await response.json() };
806
794
  },
807
- runBackfill: async function (
808
- nodeName,
809
- materializationName,
810
- partitionColumn,
811
- from,
812
- to,
813
- ) {
795
+ runBackfill: async function (nodeName, materializationName, partitionValues) {
814
796
  const response = await fetch(
815
797
  `${DJ_URL}/nodes/${nodeName}/materializations/${materializationName}/backfill`,
816
798
  {
@@ -818,10 +800,29 @@ export const DataJunctionAPI = {
818
800
  headers: {
819
801
  'Content-Type': 'application/json',
820
802
  },
821
- body: JSON.stringify({
822
- column_name: partitionColumn,
823
- range: [from, to],
824
- }),
803
+ body: JSON.stringify(
804
+ partitionValues.map(partitionValue => {
805
+ return {
806
+ column_name: partitionValue.columnName,
807
+ range: partitionValue.range,
808
+ values: partitionValue.values,
809
+ };
810
+ }),
811
+ ),
812
+ credentials: 'include',
813
+ },
814
+ );
815
+ return { status: response.status, json: await response.json() };
816
+ },
817
+ deleteMaterialization: async function (nodeName, materializationName) {
818
+ console.log('deleting materialization', nodeName, materializationName);
819
+ const response = await fetch(
820
+ `${DJ_URL}/nodes/${nodeName}/materializations?materialization_name=${materializationName}`,
821
+ {
822
+ method: 'DELETE',
823
+ headers: {
824
+ 'Content-Type': 'application/json',
825
+ },
825
826
  credentials: 'include',
826
827
  },
827
828
  );
@@ -844,4 +845,15 @@ export const DataJunctionAPI = {
844
845
  })
845
846
  ).json();
846
847
  },
848
+ revalidate: async function (node) {
849
+ return await (
850
+ await fetch(`${DJ_URL}/nodes/${node}/validate`, {
851
+ method: 'POST',
852
+ headers: {
853
+ 'Content-Type': 'application/json',
854
+ },
855
+ credentials: 'include',
856
+ })
857
+ ).json();
858
+ },
847
859
  };
@@ -34,6 +34,22 @@ describe('DataJunctionAPI', () => {
34
34
  });
35
35
  });
36
36
 
37
+ it('calls catalogs correctly', async () => {
38
+ fetch.mockResponseOnce(JSON.stringify({}));
39
+ await DataJunctionAPI.catalogs();
40
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/catalogs`, {
41
+ credentials: 'include',
42
+ });
43
+ });
44
+
45
+ it('calls engines correctly', async () => {
46
+ fetch.mockResponseOnce(JSON.stringify({}));
47
+ await DataJunctionAPI.engines();
48
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/engines`, {
49
+ credentials: 'include',
50
+ });
51
+ });
52
+
37
53
  it('calls node correctly', async () => {
38
54
  fetch.mockResponseOnce(JSON.stringify(mocks.mockMetricNode));
39
55
  const nodeData = await DataJunctionAPI.node(mocks.mockMetricNode.name);
@@ -441,8 +457,8 @@ describe('DataJunctionAPI', () => {
441
457
  it('calls materializations correctly', async () => {
442
458
  const nodeName = 'default.sample_node';
443
459
  const mockMaterializations = [
444
- { name: 'materialization1', clientCode: 'from dj import DJClient' },
445
- { name: 'materialization2', clientCode: 'from dj import DJClient' },
460
+ { name: 'materialization1' },
461
+ { name: 'materialization2' },
446
462
  ];
447
463
 
448
464
  // Mock the first fetch call to return the list of materializations
@@ -465,14 +481,6 @@ describe('DataJunctionAPI', () => {
465
481
  },
466
482
  );
467
483
 
468
- // Check the subsequent fetch calls for clientCode
469
- mockMaterializations.forEach(mat => {
470
- expect(fetch).toHaveBeenCalledWith(
471
- `${DJ_URL}/datajunction-clients/python/add_materialization/${nodeName}/${mat.name}`,
472
- { credentials: 'include' },
473
- );
474
- });
475
-
476
484
  // Ensure the result contains the clientCode for each materialization
477
485
  expect(result).toEqual(mockMaterializations);
478
486
  });
@@ -965,13 +973,12 @@ describe('DataJunctionAPI', () => {
965
973
 
966
974
  it('calls runBackfill correctly', async () => {
967
975
  fetch.mockResponseOnce(JSON.stringify({}));
968
- await DataJunctionAPI.runBackfill(
969
- 'default.hard_hat',
970
- 'spark',
971
- 'hire_date',
972
- '20230101',
973
- '20230202',
974
- );
976
+ await DataJunctionAPI.runBackfill('default.hard_hat', 'spark', [
977
+ {
978
+ columnName: 'hire_date',
979
+ range: ['20230101', '20230202'],
980
+ },
981
+ ]);
975
982
  expect(fetch).toHaveBeenCalledWith(
976
983
  `${DJ_URL}/nodes/default.hard_hat/materializations/spark/backfill`,
977
984
  {
@@ -979,12 +986,37 @@ describe('DataJunctionAPI', () => {
979
986
  headers: {
980
987
  'Content-Type': 'application/json',
981
988
  },
982
- body: JSON.stringify({
983
- column_name: 'hire_date',
984
- range: ['20230101', '20230202'],
985
- }),
989
+ body: JSON.stringify([
990
+ {
991
+ column_name: 'hire_date',
992
+ range: ['20230101', '20230202'],
993
+ },
994
+ ]),
986
995
  method: 'POST',
987
996
  },
988
997
  );
989
998
  });
999
+
1000
+ it('calls materializationInfo correctly', async () => {
1001
+ fetch.mockResponseOnce(JSON.stringify({}));
1002
+ await DataJunctionAPI.materializationInfo();
1003
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/materialization/info`, {
1004
+ credentials: 'include',
1005
+ });
1006
+ });
1007
+
1008
+ it('calls revalidate correctly', async () => {
1009
+ fetch.mockResponseOnce(JSON.stringify({}));
1010
+ await DataJunctionAPI.revalidate('default.hard_hat');
1011
+ expect(fetch).toHaveBeenCalledWith(
1012
+ `${DJ_URL}/nodes/default.hard_hat/validate`,
1013
+ {
1014
+ credentials: 'include',
1015
+ method: 'POST',
1016
+ headers: {
1017
+ 'Content-Type': 'application/json',
1018
+ },
1019
+ },
1020
+ );
1021
+ });
990
1022
  });
@@ -41,7 +41,9 @@ export const mocks = {
41
41
  type: 'string',
42
42
  attributes: [],
43
43
  dimension: null,
44
- partition: null,
44
+ partition: {
45
+ type_: 'categorical',
46
+ },
45
47
  },
46
48
  ],
47
49
  updated_at: '2024-01-24T16:39:14.029366+00:00',
@@ -103,6 +105,7 @@ export const mocks = {
103
105
  created_at: '2023-08-21T16:48:56.841631+00:00',
104
106
  tags: [{ name: 'purpose', display_name: 'Purpose' }],
105
107
  dimension_links: [],
108
+ incompatible_druid_functions: ['IF'],
106
109
  dimensions: [
107
110
  {
108
111
  value: 'default.date_dim.dateint',
@@ -460,12 +463,14 @@ export const mocks = {
460
463
  {
461
464
  backfills: [
462
465
  {
463
- spec: {
464
- column_name: 'default_DOT_hard_hat_DOT_hire_date',
465
- values: null,
466
- range: ['20230101', '20230102'],
467
- },
468
- urls: [],
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'],
469
474
  },
470
475
  ],
471
476
  name: 'country_birth_date_contractor_id_379232101',
@@ -476,6 +481,7 @@ export const mocks = {
476
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",
477
482
  upstream_tables: ['default.roads.hard_hats'],
478
483
  },
484
+ strategy: 'incremental_time',
479
485
  schedule: '0 * * * *',
480
486
  job: 'SparkSqlMaterializationJob',
481
487
  output_tables: ['common.a', 'common.b'],
@@ -1552,4 +1558,53 @@ export const mocks = {
1552
1558
  tag_type: 'reports',
1553
1559
  },
1554
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
+ },
1555
1610
  };
@@ -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: #f0f8ff !important;
1022
- color: #24518f;
1023
- text-transform: uppercase;
1027
+ background-color: #2f7986 !important;
1028
+ color: #fff;
1029
+ text-transform: none;
1024
1030
  vertical-align: middle;
1025
- padding: 0.2rem 0.5rem 0.1rem 0.5rem;
1026
- border: 1px solid #819bc0;
1027
- font-size: 1.2rem;
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
+ }
@@ -254,6 +254,15 @@ form {
254
254
  }
255
255
  }
256
256
 
257
+ .warning {
258
+ background-color: rgb(253, 248, 242);
259
+ color: rgb(190, 105, 37);
260
+ svg {
261
+ filter: invert(16%) sepia(68%) saturate(2827%) hue-rotate(344deg)
262
+ brightness(96%) contrast(100%);
263
+ }
264
+ }
265
+
257
266
  .SourceCreationInput {
258
267
  margin: 0.5rem 0;
259
268
  display: inline-grid;
@@ -0,0 +1,4 @@
1
+ table {
2
+ width: 100%;
3
+ height: min-content;
4
+ }
@@ -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
- }