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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datajunction-ui",
3
- "version": "0.0.1a54",
3
+ "version": "0.0.1-a54.dev0",
4
4
  "description": "DataJunction Metrics Platform UI",
5
5
  "module": "src/index.tsx",
6
6
  "repository": {
@@ -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.map(col => {
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' }, clientCode: {} },
492
+ { name: 'column1', dimension: { name: 'dimension1' }, },
493
493
  { name: 'column2', dimension: null },
494
- { name: 'column3', dimension: { name: 'dimension2' }, clientCode: {} },
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
 
@@ -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);