datajunction-ui 0.0.1-rc.10 → 0.0.1-rc.11

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.
Files changed (48) hide show
  1. package/package.json +4 -1
  2. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +18 -16
  3. package/src/app/components/NamespaceHeader.jsx +4 -13
  4. package/src/app/components/QueryInfo.jsx +109 -0
  5. package/src/app/components/ToggleSwitch.jsx +17 -0
  6. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
  7. package/src/app/components/djgraph/Collapse.jsx +46 -0
  8. package/src/app/components/djgraph/DJNode.jsx +56 -80
  9. package/src/app/components/djgraph/DJNodeColumns.jsx +68 -0
  10. package/src/app/components/djgraph/DJNodeDimensions.jsx +69 -0
  11. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +82 -43
  12. package/src/app/icons/CollapsedIcon.jsx +15 -0
  13. package/src/app/icons/ExpandedIcon.jsx +15 -0
  14. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  15. package/src/app/icons/InvalidIcon.jsx +14 -0
  16. package/src/app/icons/PythonIcon.jsx +52 -0
  17. package/src/app/icons/TableIcon.jsx +14 -0
  18. package/src/app/icons/ValidIcon.jsx +14 -0
  19. package/src/app/index.tsx +3 -2
  20. package/src/app/pages/NamespacePage/Explorer.jsx +57 -0
  21. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +24 -5
  22. package/src/app/pages/NamespacePage/index.jsx +78 -10
  23. package/src/app/pages/NodePage/ClientCodePopover.jsx +30 -0
  24. package/src/app/pages/NodePage/NodeColumnTab.jsx +36 -20
  25. package/src/app/pages/NodePage/NodeGraphTab.jsx +45 -17
  26. package/src/app/pages/NodePage/NodeHistory.jsx +71 -0
  27. package/src/app/pages/NodePage/NodeInfoTab.jsx +132 -49
  28. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +151 -0
  29. package/src/app/pages/NodePage/NodeSQLTab.jsx +100 -0
  30. package/src/app/pages/NodePage/NodeStatus.jsx +14 -20
  31. package/src/app/pages/NodePage/index.jsx +43 -8
  32. package/src/app/pages/Root/index.tsx +5 -0
  33. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  34. package/src/app/pages/SQLBuilderPage/index.jsx +344 -0
  35. package/src/app/services/DJService.js +125 -1
  36. package/src/styles/dag.css +111 -5
  37. package/src/styles/index.css +338 -22
  38. package/webpack.config.js +4 -5
  39. package/.babelrc +0 -4
  40. package/.env.local +0 -4
  41. package/.env.production +0 -1
  42. package/.vscode/extensions.json +0 -7
  43. package/.vscode/launch.json +0 -15
  44. package/.vscode/settings.json +0 -25
  45. package/Dockerfile +0 -7
  46. package/src/app/components/DashboardItem.jsx +0 -29
  47. package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
  48. package/src/app/pages/ListNamespacesPage/index.jsx +0 -60
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datajunction-ui",
3
- "version": "0.0.1-rc.10",
3
+ "version": "0.0.1-rc.11",
4
4
  "description": "DataJunction Metrics Platform UI",
5
5
  "module": "src/index.tsx",
6
6
  "repository": {
@@ -32,6 +32,7 @@
32
32
  "@types/react": "^18.0.20",
33
33
  "@types/react-dom": "^18.0.6",
34
34
  "@types/react-redux": "^7.1.24",
35
+ "@types/react-select": "5.0.1",
35
36
  "@types/react-test-renderer": "^18.0.0",
36
37
  "@types/rimraf": "^3.0.2",
37
38
  "@types/shelljs": "^0.8.11",
@@ -40,6 +41,7 @@
40
41
  "@types/webpack-env": "^1.18.0",
41
42
  "babel-loader": "9.1.2",
42
43
  "chalk": "4.1.2",
44
+ "cronstrue": "2.27.0",
43
45
  "cross-env": "7.0.3",
44
46
  "css-loader": "6.7.3",
45
47
  "dagre": "^0.8.5",
@@ -65,6 +67,7 @@
65
67
  "react-redux": "7.2.8",
66
68
  "react-router-dom": "6.3.0",
67
69
  "react-scripts": "5.0.1",
70
+ "react-select": "5.7.3",
68
71
  "react-syntax-highlighter": "^15.5.0",
69
72
  "react-test-renderer": "18.2.0",
70
73
  "reactflow": "^11.7.0",
@@ -18,13 +18,26 @@ exports[`<App /> should render and match the snapshot 1`] = `
18
18
  value={
19
19
  Object {
20
20
  "DataJunctionAPI": Object {
21
+ "clientCode": [Function],
22
+ "columns": [Function],
23
+ "commonDimensions": [Function],
24
+ "compiledSql": [Function],
25
+ "cube": [Function],
21
26
  "dag": [Function],
27
+ "data": [Function],
22
28
  "downstreams": [Function],
29
+ "history": [Function],
23
30
  "lineage": [Function],
31
+ "materializations": [Function],
24
32
  "metric": [Function],
33
+ "metrics": [Function],
25
34
  "namespace": [Function],
26
35
  "namespaces": [Function],
27
36
  "node": [Function],
37
+ "node_dag": [Function],
38
+ "revisions": [Function],
39
+ "sql": [Function],
40
+ "sqls": [Function],
28
41
  "upstreams": [Function],
29
42
  },
30
43
  }
@@ -45,22 +58,7 @@ exports[`<App /> should render and match the snapshot 1`] = `
45
58
  />
46
59
  </Route>
47
60
  <Route
48
- element={
49
- <ListNamespacesPage
50
- djClient={
51
- Object {
52
- "dag": [Function],
53
- "downstreams": [Function],
54
- "lineage": [Function],
55
- "metric": [Function],
56
- "namespace": [Function],
57
- "namespaces": [Function],
58
- "node": [Function],
59
- "upstreams": [Function],
60
- }
61
- }
62
- />
63
- }
61
+ element={<NamespacePage />}
64
62
  path="/"
65
63
  />
66
64
  <Route
@@ -71,6 +69,10 @@ exports[`<App /> should render and match the snapshot 1`] = `
71
69
  path=":namespace"
72
70
  />
73
71
  </Route>
72
+ <Route
73
+ element={<SQLBuilderPage />}
74
+ path="sql"
75
+ />
74
76
  </React.Fragment>
75
77
  </Route>
76
78
  <Route
@@ -1,4 +1,5 @@
1
1
  import { Component } from 'react';
2
+ import HorizontalHierarchyIcon from '../icons/HorizontalHierarchyIcon';
2
3
 
3
4
  export default class NamespaceHeader extends Component {
4
5
  render() {
@@ -6,7 +7,7 @@ export default class NamespaceHeader extends Component {
6
7
  const namespaceParts = namespace.split('.');
7
8
  const namespaceList = namespaceParts.map((piece, index) => {
8
9
  return (
9
- <li className="breadcrumb-item">
10
+ <li className="breadcrumb-item" key={index}>
10
11
  <a
11
12
  className="link-body-emphasis"
12
13
  href={'/namespaces/' + namespaceParts.slice(0, index + 1).join('.')}
@@ -19,18 +20,8 @@ export default class NamespaceHeader extends Component {
19
20
  return (
20
21
  <ol className="breadcrumb breadcrumb-chevron p-3 bg-body-tertiary rounded-3">
21
22
  <li className="breadcrumb-item">
22
- <a href="/namespaces/">
23
- <svg
24
- xmlns="http://www.w3.org/2000/svg"
25
- width="16"
26
- height="16"
27
- fill="currentColor"
28
- className="bi bi-house-door-fill"
29
- viewBox="0 0 16 16"
30
- style={{ paddingBottom: '0.2rem' }}
31
- >
32
- <path d="M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z" />
33
- </svg>
23
+ <a href="/">
24
+ <HorizontalHierarchyIcon />
34
25
  </a>
35
26
  </li>
36
27
  {namespaceList}
@@ -0,0 +1,109 @@
1
+ export default function QueryInfo({
2
+ id,
3
+ state,
4
+ engine_name,
5
+ engine_version,
6
+ errors,
7
+ links,
8
+ output_table,
9
+ scheduled,
10
+ started,
11
+ numRows,
12
+ }) {
13
+ const stateIcon =
14
+ state === 'FINISHED' ? (
15
+ <span className="status__valid status" style={{ alignContent: 'center' }}>
16
+ <svg
17
+ xmlns="http://www.w3.org/2000/svg"
18
+ width="25"
19
+ height="25"
20
+ fill="currentColor"
21
+ className="bi bi-check-circle-fill"
22
+ viewBox="0 0 16 16"
23
+ >
24
+ <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z" />
25
+ </svg>
26
+ </span>
27
+ ) : (
28
+ <span
29
+ className="status__invalid status"
30
+ style={{ alignContent: 'center' }}
31
+ >
32
+ <svg
33
+ xmlns="http://www.w3.org/2000/svg"
34
+ width="16"
35
+ height="16"
36
+ fill="currentColor"
37
+ className="bi bi-x-circle-fill"
38
+ viewBox="0 0 16 16"
39
+ >
40
+ <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z" />
41
+ </svg>
42
+ </span>
43
+ );
44
+
45
+ return (
46
+ <div className="table-responsive">
47
+ <table className="card-inner-table table">
48
+ <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
49
+ <tr>
50
+ <th>Query ID</th>
51
+ <th>Engine</th>
52
+ <th>State</th>
53
+ <th>Scheduled</th>
54
+ <th>Started</th>
55
+ <th>Errors</th>
56
+ <th>Links</th>
57
+ <th>Output Table</th>
58
+ <th>Number of Rows</th>
59
+ </tr>
60
+ </thead>
61
+ <tbody>
62
+ <tr>
63
+ <td>
64
+ <span className="rounded-pill badge bg-secondary-soft">{id}</span>
65
+ </td>
66
+ <td>
67
+ <span className="rounded-pill badge bg-secondary-soft">
68
+ {engine_name}
69
+ {' - '}
70
+ {engine_version}
71
+ </span>
72
+ </td>
73
+ <td>{stateIcon}</td>
74
+ <td>{scheduled}</td>
75
+ <td>{started}</td>
76
+ <td>
77
+ {errors.length ? (
78
+ errors.map(e => (
79
+ <p>
80
+ <span className="rounded-pill badge bg-secondary-error">
81
+ {e}
82
+ </span>
83
+ </p>
84
+ ))
85
+ ) : (
86
+ <></>
87
+ )}
88
+ </td>
89
+ <td>
90
+ {links?.length ? (
91
+ links.map(link => (
92
+ <p>
93
+ <a href={link} target="_blank" rel="noreferrer">
94
+ {link}
95
+ </a>
96
+ </p>
97
+ ))
98
+ ) : (
99
+ <></>
100
+ )}
101
+ </td>
102
+ <td>{output_table}</td>
103
+ <td>{numRows}</td>
104
+ </tr>
105
+ </tbody>
106
+ </table>
107
+ </div>
108
+ );
109
+ }
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+
3
+ const ToggleSwitch = ({ checked, onChange, toggleName }) => (
4
+ <>
5
+ <input
6
+ id="show-compiled-sql-toggle"
7
+ type="checkbox"
8
+ className="checkbox"
9
+ checked={checked}
10
+ onChange={e => onChange(e.target.checked)}
11
+ />
12
+ <label htmlFor="show-compiled-sql-toggle" className="switch"></label>{' '}
13
+ {toggleName}
14
+ </>
15
+ );
16
+
17
+ export default ToggleSwitch;
@@ -8,25 +8,9 @@ exports[`<NamespaceHeader /> should render and match the snapshot 1`] = `
8
8
  className="breadcrumb-item"
9
9
  >
10
10
  <a
11
- href="/namespaces/"
11
+ href="/"
12
12
  >
13
- <svg
14
- className="bi bi-house-door-fill"
15
- fill="currentColor"
16
- height="16"
17
- style={
18
- Object {
19
- "paddingBottom": "0.2rem",
20
- }
21
- }
22
- viewBox="0 0 16 16"
23
- width="16"
24
- xmlns="http://www.w3.org/2000/svg"
25
- >
26
- <path
27
- d="M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z"
28
- />
29
- </svg>
13
+ <HorizontalHierarchyIcon />
30
14
  </a>
31
15
  </li>
32
16
  <li
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { DJNodeDimensions } from './DJNodeDimensions';
3
+ import { DJNodeColumns } from './DJNodeColumns';
4
+
5
+ export default function Collapse({ collapsed, text, data }) {
6
+ const [isCollapsed, setIsCollapsed] = React.useState(collapsed);
7
+
8
+ return (
9
+ <>
10
+ <div className="collapse">
11
+ {data.type === 'metric' ? (
12
+ <button
13
+ className="collapse-button"
14
+ onClick={() => setIsCollapsed(!isCollapsed)}
15
+ >
16
+ {isCollapsed ? '\u25B6 Show' : '\u25BC Hide'} {text}
17
+ </button>
18
+ ) : (
19
+ ''
20
+ )}
21
+ <div
22
+ className={`collapse-content ${
23
+ isCollapsed && data.type === 'metric' ? 'collapsed' : 'expanded'
24
+ }`}
25
+ aria-expanded={isCollapsed}
26
+ >
27
+ {data.type !== 'metric'
28
+ ? isCollapsed
29
+ ? DJNodeColumns({ data: data, limit: 10 })
30
+ : DJNodeColumns({ data: data, limit: 100 })
31
+ : DJNodeDimensions(data)}
32
+ </div>
33
+ {data.type !== 'metric' && data.column_names.length > 10 ? (
34
+ <button
35
+ className="collapse-button"
36
+ onClick={() => setIsCollapsed(!isCollapsed)}
37
+ >
38
+ {isCollapsed ? '\u25B6 More' : '\u25BC Less'} {text}
39
+ </button>
40
+ ) : (
41
+ ''
42
+ )}
43
+ </div>
44
+ </>
45
+ );
46
+ }
@@ -1,81 +1,57 @@
1
1
  import React, { memo } from 'react';
2
2
  import { Handle, Position } from 'reactflow';
3
-
4
- function renderBasedOnDJNodeType(param) {
5
- switch (param) {
6
- case 'source':
7
- return { backgroundColor: '#7EB46150', color: '#7EB461' };
8
- case 'transform':
9
- return { backgroundColor: '#6DAAA750', color: '#6DAAA7' };
10
- case 'dimension':
11
- return { backgroundColor: '#CF7D2950', color: '#CF7D29' };
12
- case 'metric':
13
- return { backgroundColor: '#A27E8650', color: '#A27E86' };
14
- case 'cube':
15
- return { backgroundColor: '#C2180750', color: '#C21807' };
16
- default:
17
- return {};
18
- }
19
- }
3
+ import { DJNodeDimensions } from './DJNodeDimensions';
4
+ import Collapse from './Collapse';
20
5
 
21
6
  function capitalize(string) {
22
7
  return string.charAt(0).toUpperCase() + string.slice(1);
23
8
  }
24
9
 
25
- const Collapse = ({ collapsed, text, children }) => {
26
- const [isCollapsed, setIsCollapsed] = React.useState(collapsed);
27
-
28
- return (
29
- <>
30
- <div className="collapse">
31
- <button
32
- className="collapse-button"
33
- onClick={() => setIsCollapsed(!isCollapsed)}
34
- >
35
- {isCollapsed ? '\u25B6 Show' : '\u25BC Hide'} {text}
36
- </button>
37
- <div
38
- className={`collapse-content ${
39
- isCollapsed ? 'collapsed' : 'expanded'
40
- }`}
41
- aria-expanded={isCollapsed}
42
- >
43
- {children}
44
- </div>
45
- </div>
46
- </>
47
- );
48
- };
49
-
50
10
  export function DJNode({ id, data }) {
51
- const columnsRenderer = data =>
52
- data.column_names.map(col => (
53
- <tr>
54
- <td>
55
- {data.primary_key.includes(col.name) ? (
56
- <b>{col.name} (PK)</b>
57
- ) : (
58
- <>{col.name}</>
59
- )}
60
- </td>
61
- <td style={{ textAlign: 'right' }}>{col.type}</td>
62
- </tr>
63
- ));
64
- // const dimensionsRenderer = data =>
65
- // data.dimensions.map(dim => (
66
- // <tr>
67
- // <td>{dim}</td>
68
- // </tr>
69
- // ));
11
+ const handleWrapperStyle = {
12
+ display: 'flex',
13
+ position: 'absolute',
14
+ height: '100%',
15
+ flexDirection: 'column',
16
+ top: '50%',
17
+ justifyContent: 'space-between',
18
+ };
19
+ const handleWrapperStyleRight = { ...handleWrapperStyle, ...{ right: 0 } };
70
20
 
21
+ const handleStyle = {
22
+ width: '12px',
23
+ height: '12px',
24
+ borderRadius: '12px',
25
+ background: 'transparent',
26
+ border: '4px solid transparent',
27
+ cursor: 'pointer',
28
+ position: 'absolute',
29
+ top: '0px',
30
+ left: 0,
31
+ };
32
+ const handleStyleLeft = percentage => {
33
+ return {
34
+ ...handleStyle,
35
+ ...{
36
+ transform: 'translate(-' + percentage + '%, -50%)',
37
+ },
38
+ };
39
+ };
40
+ const highlightNodeClass =
41
+ data.is_current === true ? ' dj-node_highlight' : '';
71
42
  return (
72
43
  <>
73
- <Handle
74
- type="target"
75
- position={Position.Left}
76
- style={{ backgroundColor: '#ccc' }}
77
- />
78
- <div className="dj-node__full" style={renderBasedOnDJNodeType(data.type)}>
44
+ <div
45
+ className={'dj-node__full node_type__' + data.type + highlightNodeClass}
46
+ >
47
+ <div style={handleWrapperStyle}>
48
+ <Handle
49
+ type="target"
50
+ id={data.name}
51
+ position={Position.Left}
52
+ style={handleStyleLeft(100)}
53
+ />
54
+ </div>
79
55
  <div className="dj-node__header">
80
56
  <div className="serif">
81
57
  {data.name
@@ -86,24 +62,24 @@ export function DJNode({ id, data }) {
86
62
  </div>
87
63
  <div className="dj-node__body">
88
64
  <b>{capitalize(data.type)}</b>:{' '}
89
- {data.type === 'source' ? data.table : data.display_name}
65
+ <a href={`/nodes/${data.name}`}>
66
+ {data.type === 'source' ? data.table : data.display_name}
67
+ </a>
90
68
  <Collapse
91
69
  collapsed={true}
92
70
  text={data.type !== 'metric' ? 'columns' : 'dimensions'}
93
- >
94
- <div className="dj-node__metadata">
95
- {
96
- data.type !== 'metric' ? columnsRenderer(data) : '' // dimensionsRenderer(data)
97
- }
98
- </div>
99
- </Collapse>
71
+ data={data}
72
+ />
73
+ </div>
74
+ <div style={handleWrapperStyleRight}>
75
+ <Handle
76
+ type="source"
77
+ id={data.name}
78
+ position={Position.Right}
79
+ style={handleStyleLeft(90)}
80
+ />
100
81
  </div>
101
82
  </div>
102
- <Handle
103
- type="source"
104
- position={Position.Right}
105
- style={{ backgroundColor: '#ccc' }}
106
- />
107
83
  </>
108
84
  );
109
85
  }
@@ -0,0 +1,68 @@
1
+ import { Handle } from 'reactflow';
2
+ import React from 'react';
3
+
4
+ export function DJNodeColumns({ data, limit }) {
5
+ const handleWrapperStyle = {
6
+ display: 'flex',
7
+ position: 'absolute',
8
+ height: '100%',
9
+ flexDirection: 'column',
10
+ top: '50%',
11
+ justifyContent: 'space-between',
12
+ };
13
+ const handleWrapperStyleRight = { ...handleWrapperStyle, ...{ right: 0 } };
14
+
15
+ const handleStyle = {
16
+ width: '12px',
17
+ height: '12px',
18
+ borderRadius: '12px',
19
+ background: 'transparent',
20
+ border: '4px solid transparent',
21
+ cursor: 'pointer',
22
+ position: 'absolute',
23
+ top: '0px',
24
+ left: 0,
25
+ };
26
+ const handleStyleLeft = percentage => {
27
+ return {
28
+ ...handleStyle,
29
+ ...{
30
+ transform: 'translate(-' + percentage + '%, -50%)',
31
+ },
32
+ };
33
+ };
34
+ return data.column_names.slice(0, limit).map(col => (
35
+ <div className={'custom-node-subheader node_type__' + data.type}>
36
+ <div style={handleWrapperStyle}>
37
+ <Handle
38
+ type="target"
39
+ position="left"
40
+ id={data.name + '.' + col.name}
41
+ style={handleStyleLeft(100)}
42
+ />
43
+ </div>
44
+ <div
45
+ className="custom-node-port"
46
+ id={data.name + '.' + col.name}
47
+ key={'i-' + data.name + '.' + col.name}
48
+ >
49
+ {data.primary_key.includes(col.name) ? (
50
+ <b>{col.name} (PK)</b>
51
+ ) : (
52
+ <>{col.name}</>
53
+ )}
54
+ <span style={{ marginLeft: '0.25rem' }} className={'badge'}>
55
+ {col.type}
56
+ </span>
57
+ </div>
58
+ <div style={handleWrapperStyleRight}>
59
+ <Handle
60
+ type="source"
61
+ position="right"
62
+ id={data.name + '.' + col.name}
63
+ style={handleStyle}
64
+ />
65
+ </div>
66
+ </div>
67
+ ));
68
+ }
@@ -0,0 +1,69 @@
1
+ import React, { useContext, useEffect, useState } from 'react';
2
+ import DJClientContext from '../../providers/djclient';
3
+ import Collapse from './Collapse';
4
+
5
+ export function DJNodeDimensions(data) {
6
+ const [dimensions, setDimensions] = useState([]);
7
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
8
+ useEffect(() => {
9
+ if (data.type === 'metric') {
10
+ async function getDimensions() {
11
+ try {
12
+ const metricData = await djClient.metric(data.name);
13
+ setDimensions(metricData.dimensions);
14
+ } catch (err) {
15
+ console.log(err);
16
+ }
17
+ }
18
+ getDimensions();
19
+ }
20
+ }, [data, djClient]);
21
+ const dimensionsToObject = dimensions => {
22
+ return dimensions.map(dim => {
23
+ const [attribute, ...nodeName] = dim.name.split('.').reverse();
24
+ return {
25
+ dimension: nodeName.reverse().join('.'),
26
+ path: dim.path,
27
+ column: attribute,
28
+ };
29
+ });
30
+ };
31
+ const groupedDimensions = dims =>
32
+ dims.reduce((acc, current) => {
33
+ const dimKey = current.dimension + ' via ' + current.path.slice(-1);
34
+ acc[dimKey] = acc[dimKey] || {
35
+ dimension: current.dimension,
36
+ path: current.path.slice(-1),
37
+ columns: [],
38
+ };
39
+ acc[dimKey].columns.push(current.column);
40
+ return acc;
41
+ }, {});
42
+ const dimensionsRenderer = grouped =>
43
+ Object.entries(grouped).map(([dimKey, dimValue]) => {
44
+ if (Array.isArray(dimValue.columns)) {
45
+ const attributes = dimValue.columns.map(col => {
46
+ return <span className={'badge white_badge'}>{col}</span>;
47
+ });
48
+ return (
49
+ <div className={'custom-node-subheader node_type__' + data.type}>
50
+ <div className="custom-node-port">
51
+ <a href={`/nodes/${dimValue.dimension}`}>{dimValue.dimension}</a>{' '}
52
+ <div className={'badge node_type__metric text-black'}>
53
+ {dimValue.path}
54
+ </div>
55
+ </div>
56
+ <div className={'dimension_attributes'}>{attributes}</div>
57
+ </div>
58
+ );
59
+ }
60
+ return <></>;
61
+ });
62
+ return (
63
+ <>
64
+ {dimensions.length <= 0
65
+ ? ''
66
+ : dimensionsRenderer(groupedDimensions(dimensionsToObject(dimensions)))}
67
+ </>
68
+ );
69
+ }