datajunction-ui 0.0.1-a54 → 0.0.1-a54.dev0
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 +1 -1
- package/src/app/pages/NodePage/LinkComplexDimensionPopover.jsx +124 -0
- package/src/app/pages/NodePage/NodeColumnTab.jsx +1 -2
- package/src/app/pages/NodePage/NotebookDownload.jsx +37 -0
- package/src/app/pages/NodePage/index.jsx +2 -0
- package/src/app/services/DJService.js +14 -10
- package/src/app/services/__tests__/DJService.test.jsx +2 -14
- package/src/styles/index.css +9 -0
package/package.json
CHANGED
|
@@ -0,0 +1,124 @@
|
|
|
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 } from '../../../utils/form';
|
|
8
|
+
import LoadingIcon from '../../icons/LoadingIcon';
|
|
9
|
+
import { NodeQueryField } from '../AddEditNodePage/NodeQueryField';
|
|
10
|
+
|
|
11
|
+
export default function LinkComplexDimensionPopover({ link, onSubmit }) {
|
|
12
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
13
|
+
const [popoverAnchor, setPopoverAnchor] = useState(false);
|
|
14
|
+
const ref = useRef(null);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const handleClickOutside = event => {
|
|
18
|
+
if (ref.current && !ref.current.contains(event.target)) {
|
|
19
|
+
setPopoverAnchor(false);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
document.addEventListener('click', handleClickOutside, true);
|
|
23
|
+
return () => {
|
|
24
|
+
document.removeEventListener('click', handleClickOutside, true);
|
|
25
|
+
};
|
|
26
|
+
}, [setPopoverAnchor]);
|
|
27
|
+
|
|
28
|
+
const handleSubmit = async (
|
|
29
|
+
{ node, column, dimension },
|
|
30
|
+
{ setSubmitting, setStatus },
|
|
31
|
+
) => {
|
|
32
|
+
onSubmit();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const linkDimension = async (node, column, dimension, setStatus) => {
|
|
36
|
+
const response = await djClient.linkDimension(node, column, dimension);
|
|
37
|
+
if (response.status === 200 || response.status === 201) {
|
|
38
|
+
setStatus({ success: 'Saved!' });
|
|
39
|
+
} else {
|
|
40
|
+
setStatus({
|
|
41
|
+
failure: `${response.json.message}`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const unlinkDimension = async (node, column, currentDimension, setStatus) => {
|
|
47
|
+
const response = await djClient.unlinkDimension(
|
|
48
|
+
node,
|
|
49
|
+
column,
|
|
50
|
+
currentDimension,
|
|
51
|
+
);
|
|
52
|
+
if (response.status === 200 || response.status === 201) {
|
|
53
|
+
setStatus({ success: 'Removed dimension link!' });
|
|
54
|
+
} else {
|
|
55
|
+
setStatus({
|
|
56
|
+
failure: `${response.json.message}`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<>
|
|
63
|
+
<button
|
|
64
|
+
className="edit_button"
|
|
65
|
+
aria-label="LinkDimension"
|
|
66
|
+
tabIndex="0"
|
|
67
|
+
onClick={() => {
|
|
68
|
+
setPopoverAnchor(!popoverAnchor);
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<EditIcon />
|
|
72
|
+
</button>
|
|
73
|
+
<div
|
|
74
|
+
className="popover"
|
|
75
|
+
role="dialog"
|
|
76
|
+
aria-label="client-code"
|
|
77
|
+
style={{ display: popoverAnchor === false ? 'none' : 'block' }}
|
|
78
|
+
ref={ref}
|
|
79
|
+
>
|
|
80
|
+
<Formik
|
|
81
|
+
initialValues={{
|
|
82
|
+
link: link,
|
|
83
|
+
}}
|
|
84
|
+
onSubmit={handleSubmit}
|
|
85
|
+
>
|
|
86
|
+
{function Render({ isSubmitting, status, setFieldValue }) {
|
|
87
|
+
console.log('link', link);
|
|
88
|
+
return (
|
|
89
|
+
<Form>
|
|
90
|
+
{displayMessageAfterSubmit(status)}
|
|
91
|
+
<span data-testid="link-dimension"></span>
|
|
92
|
+
<input
|
|
93
|
+
// hidden={true}
|
|
94
|
+
disabled={true}
|
|
95
|
+
name="dimension"
|
|
96
|
+
value={link.dimension.name}
|
|
97
|
+
readOnly={true}
|
|
98
|
+
/>
|
|
99
|
+
<label htmlFor={'join_type'}>Join Type</label>
|
|
100
|
+
<input name="join_type" value={link.join_type} />
|
|
101
|
+
<label htmlFor={'join_sql'}>Join On</label>
|
|
102
|
+
<div style={{ width: '50%' }}>
|
|
103
|
+
<NodeQueryField
|
|
104
|
+
djClient={djClient}
|
|
105
|
+
value={link.join_sql ? link.join_sql : ''}
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
<button
|
|
109
|
+
className="add_node"
|
|
110
|
+
type="submit"
|
|
111
|
+
aria-label="SaveLinkDimension"
|
|
112
|
+
aria-hidden="false"
|
|
113
|
+
disabled={isSubmitting}
|
|
114
|
+
>
|
|
115
|
+
{isSubmitting ? <LoadingIcon /> : 'Save'}
|
|
116
|
+
</button>
|
|
117
|
+
</Form>
|
|
118
|
+
);
|
|
119
|
+
}}
|
|
120
|
+
</Formik>
|
|
121
|
+
</div>
|
|
122
|
+
</>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -92,7 +92,7 @@ export default function NodeColumnTab({ node, djClient }) {
|
|
|
92
92
|
};
|
|
93
93
|
|
|
94
94
|
const columnList = columns => {
|
|
95
|
-
return columns
|
|
95
|
+
return columns?.map(col => {
|
|
96
96
|
const dimensionLinks = (links.length > 0 ? links : node?.dimension_links)
|
|
97
97
|
.map(link => [
|
|
98
98
|
link.dimension.name,
|
|
@@ -151,7 +151,6 @@ export default function NodeColumnTab({ node, djClient }) {
|
|
|
151
151
|
options={dimensions}
|
|
152
152
|
onSubmit={async () => {
|
|
153
153
|
const res = await djClient.node(node.name);
|
|
154
|
-
setColumns(res.columns);
|
|
155
154
|
setLinks(res.dimension_links);
|
|
156
155
|
}}
|
|
157
156
|
/>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import DJClientContext from '../../providers/djclient';
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
|
|
4
|
+
export default function NotebookDownload({ node }) {
|
|
5
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
6
|
+
|
|
7
|
+
const downloadFile = async () => {
|
|
8
|
+
try {
|
|
9
|
+
const response = await djClient.notebookExportCube(node.name);
|
|
10
|
+
const notebook = await response.blob();
|
|
11
|
+
const url = window.URL.createObjectURL(new Blob([notebook]));
|
|
12
|
+
|
|
13
|
+
const link = document.createElement('a');
|
|
14
|
+
link.href = url;
|
|
15
|
+
link.setAttribute('download', 'notebook.ipynb');
|
|
16
|
+
document.body.appendChild(link);
|
|
17
|
+
link.click();
|
|
18
|
+
link.parentNode.removeChild(link);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error('Error downloading file: ', error);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<div
|
|
27
|
+
className="badge download_notebook"
|
|
28
|
+
style={{cursor: 'pointer', backgroundColor: '#ffefd0'}}
|
|
29
|
+
tabIndex="0"
|
|
30
|
+
height="45px"
|
|
31
|
+
onClick={downloadFile}
|
|
32
|
+
>
|
|
33
|
+
Export as Notebook
|
|
34
|
+
</div>
|
|
35
|
+
</>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -7,6 +7,7 @@ import NodeInfoTab from './NodeInfoTab';
|
|
|
7
7
|
import NodeColumnTab from './NodeColumnTab';
|
|
8
8
|
import NodeGraphTab from './NodeGraphTab';
|
|
9
9
|
import NodeHistory from './NodeHistory';
|
|
10
|
+
import NotebookDownload from './NotebookDownload';
|
|
10
11
|
import DJClientContext from '../../providers/djclient';
|
|
11
12
|
import NodeValidateTab from './NodeValidateTab';
|
|
12
13
|
import NodeMaterializationTab from './NodeMaterializationTab';
|
|
@@ -203,6 +204,7 @@ export function NodePage() {
|
|
|
203
204
|
>
|
|
204
205
|
{node?.version}
|
|
205
206
|
</span>
|
|
207
|
+
{node?.type === 'cube' ? <NotebookDownload node={node} /> : <></>}
|
|
206
208
|
</div>
|
|
207
209
|
<div className="align-items-center row">
|
|
208
210
|
{tabsList(node).map(buildTabs)}
|
|
@@ -399,16 +399,6 @@ export const DataJunctionAPI = {
|
|
|
399
399
|
columns: async function (node) {
|
|
400
400
|
return await Promise.all(
|
|
401
401
|
node.columns.map(async col => {
|
|
402
|
-
if (col.dimension) {
|
|
403
|
-
col.clientCode = await (
|
|
404
|
-
await fetch(
|
|
405
|
-
`${DJ_URL}/datajunction-clients/python/link_dimension/${node.name}/${col.name}/${col.dimension?.name}`,
|
|
406
|
-
{
|
|
407
|
-
credentials: 'include',
|
|
408
|
-
},
|
|
409
|
-
)
|
|
410
|
-
).json();
|
|
411
|
-
}
|
|
412
402
|
return col;
|
|
413
403
|
}),
|
|
414
404
|
);
|
|
@@ -462,6 +452,20 @@ export const DataJunctionAPI = {
|
|
|
462
452
|
).json();
|
|
463
453
|
},
|
|
464
454
|
|
|
455
|
+
notebookExportCube: async function (cube) {
|
|
456
|
+
return await fetch(`${DJ_URL}/datajunction-clients/python/notebook/?cube=${cube}`, {
|
|
457
|
+
credentials: 'include',
|
|
458
|
+
});
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
notebookExportNamespace: async function (namespace) {
|
|
462
|
+
return await (
|
|
463
|
+
await fetch(`${DJ_URL}/datajunction-clients/python/notebook/?namespace=${namespace}`, {
|
|
464
|
+
credentials: 'include',
|
|
465
|
+
})
|
|
466
|
+
).json();
|
|
467
|
+
},
|
|
468
|
+
|
|
465
469
|
stream: async function (metricSelection, dimensionSelection, filters) {
|
|
466
470
|
const params = new URLSearchParams();
|
|
467
471
|
metricSelection.map(metric => params.append('metrics', metric));
|
|
@@ -489,9 +489,9 @@ describe('DataJunctionAPI', () => {
|
|
|
489
489
|
const sampleNode = {
|
|
490
490
|
name: 'sampleNode',
|
|
491
491
|
columns: [
|
|
492
|
-
{ name: 'column1', dimension: { name: 'dimension1' },
|
|
492
|
+
{ name: 'column1', dimension: { name: 'dimension1' }, },
|
|
493
493
|
{ name: 'column2', dimension: null },
|
|
494
|
-
{ name: 'column3', dimension: { name: 'dimension2' },
|
|
494
|
+
{ name: 'column3', dimension: { name: 'dimension2' }, },
|
|
495
495
|
],
|
|
496
496
|
};
|
|
497
497
|
|
|
@@ -503,18 +503,6 @@ describe('DataJunctionAPI', () => {
|
|
|
503
503
|
fetch.mockResponses(...mockClientCodeResponses);
|
|
504
504
|
|
|
505
505
|
const result = await DataJunctionAPI.columns(sampleNode);
|
|
506
|
-
|
|
507
|
-
// Check the fetch calls for clientCode for columns with a dimension
|
|
508
|
-
sampleNode.columns.forEach(col => {
|
|
509
|
-
if (col.dimension) {
|
|
510
|
-
expect(fetch).toHaveBeenCalledWith(
|
|
511
|
-
`${DJ_URL}/datajunction-clients/python/link_dimension/${sampleNode.name}/${col.name}/${col.dimension.name}`,
|
|
512
|
-
{ credentials: 'include' },
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
// Ensure the result contains the clientCode for columns with a dimension and leaves others unchanged
|
|
518
506
|
expect(result).toEqual(sampleNode.columns);
|
|
519
507
|
});
|
|
520
508
|
|
package/src/styles/index.css
CHANGED
|
@@ -1065,6 +1065,15 @@ pre {
|
|
|
1065
1065
|
.tag_value:hover {
|
|
1066
1066
|
background-color: #5c3b8f50;
|
|
1067
1067
|
}
|
|
1068
|
+
.download_notebook {
|
|
1069
|
+
color: #5c3b8f;
|
|
1070
|
+
background-color: #ffefd0;
|
|
1071
|
+
margin: 0.25rem;
|
|
1072
|
+
font-size: 100%;
|
|
1073
|
+
}
|
|
1074
|
+
.download_notebook:hover {
|
|
1075
|
+
background-color: #fedda1 !important;
|
|
1076
|
+
}
|
|
1068
1077
|
|
|
1069
1078
|
.modal-backdrop.in {
|
|
1070
1079
|
filter: alpha(opacity=50);
|