datajunction-ui 0.0.1-rc.1 → 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.
- package/.github/workflows/ci.yml +3 -3
- package/.prettierignore +3 -1
- package/package.json +16 -8
- package/src/app/__tests__/__snapshots__/index.test.tsx.snap +67 -25
- package/src/app/components/NamespaceHeader.jsx +4 -13
- package/src/app/components/QueryInfo.jsx +109 -0
- package/src/app/components/Tab.jsx +1 -8
- package/src/app/components/ToggleSwitch.jsx +17 -0
- package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
- package/src/app/components/djgraph/Collapse.jsx +46 -0
- package/src/app/components/djgraph/DJNode.jsx +56 -80
- package/src/app/components/djgraph/DJNodeColumns.jsx +68 -0
- package/src/app/components/djgraph/DJNodeDimensions.jsx +69 -0
- package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +82 -43
- package/src/app/icons/CollapsedIcon.jsx +15 -0
- package/src/app/icons/ExpandedIcon.jsx +15 -0
- package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
- package/src/app/icons/InvalidIcon.jsx +14 -0
- package/src/app/icons/PythonIcon.jsx +52 -0
- package/src/app/icons/TableIcon.jsx +14 -0
- package/src/app/icons/ValidIcon.jsx +14 -0
- package/src/app/index.tsx +28 -15
- package/src/app/pages/NamespacePage/Explorer.jsx +57 -0
- package/src/app/pages/NamespacePage/Loadable.jsx +9 -7
- package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +39 -17
- package/src/app/pages/NamespacePage/index.jsx +90 -28
- package/src/app/pages/NodePage/ClientCodePopover.jsx +30 -0
- package/src/app/pages/NodePage/Loadable.jsx +9 -7
- package/src/app/pages/NodePage/NodeColumnTab.jsx +36 -20
- package/src/app/pages/NodePage/NodeGraphTab.jsx +83 -54
- package/src/app/pages/NodePage/NodeHistory.jsx +71 -0
- package/src/app/pages/NodePage/NodeInfoTab.jsx +132 -49
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +151 -0
- package/src/app/pages/NodePage/NodeSQLTab.jsx +100 -0
- package/src/app/pages/NodePage/NodeStatus.jsx +14 -20
- package/src/app/pages/NodePage/index.jsx +49 -13
- package/src/app/pages/Root/index.tsx +5 -0
- package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
- package/src/app/pages/SQLBuilderPage/index.jsx +344 -0
- package/src/app/providers/djclient.jsx +5 -0
- package/src/app/services/DJService.js +125 -1
- package/src/styles/dag.css +111 -5
- package/src/styles/index.css +343 -25
- package/tsconfig.json +1 -1
- package/webpack.config.js +22 -6
- package/.babelrc +0 -4
- package/.env.local +0 -4
- package/.env.production +0 -1
- package/.vscode/extensions.json +0 -7
- package/.vscode/launch.json +0 -15
- package/.vscode/settings.json +0 -25
- package/Dockerfile +0 -7
- package/dist/5fa71a03d45dc2e3d61447f3013a303d.png +0 -0
- package/dist/index.html +0 -26
- package/dist/main.js +0 -23303
- package/dist/vendor.js +0 -281
- package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
- package/src/app/pages/ListNamespacesPage/index.jsx +0 -52
|
@@ -1,32 +1,66 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useParams } from 'react-router-dom';
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
4
|
-
import { DataJunctionAPI } from '../../services/DJService';
|
|
5
|
-
import NamespaceHeader from '../../components/NamespaceHeader';
|
|
3
|
+
import { useContext, useEffect, useState } from 'react';
|
|
6
4
|
import NodeStatus from '../NodePage/NodeStatus';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const djNode = await DataJunctionAPI.node(params.name);
|
|
10
|
-
if (djNode.type === 'metric') {
|
|
11
|
-
const metricNode = await DataJunctionAPI.metric(params.name);
|
|
12
|
-
djNode.dimensions = metricNode.dimensions;
|
|
13
|
-
}
|
|
14
|
-
return djNode;
|
|
15
|
-
}
|
|
5
|
+
import DJClientContext from '../../providers/djclient';
|
|
6
|
+
import Explorer from '../NamespacePage/Explorer';
|
|
16
7
|
|
|
17
8
|
export function NamespacePage() {
|
|
18
|
-
const
|
|
9
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
10
|
+
var { namespace } = useParams();
|
|
19
11
|
|
|
20
12
|
const [state, setState] = useState({
|
|
21
13
|
namespace: namespace,
|
|
22
14
|
nodes: [],
|
|
23
15
|
});
|
|
24
16
|
|
|
17
|
+
const [namespaceHierarchy, setNamespaceHierarchy] = useState([]);
|
|
18
|
+
|
|
19
|
+
const createNamespaceHierarchy = namespaceList => {
|
|
20
|
+
const hierarchy = [];
|
|
21
|
+
|
|
22
|
+
for (const item of namespaceList) {
|
|
23
|
+
const namespaces = item.namespace.split('.');
|
|
24
|
+
let currentLevel = hierarchy;
|
|
25
|
+
|
|
26
|
+
let path = '';
|
|
27
|
+
for (const ns of namespaces) {
|
|
28
|
+
path += ns;
|
|
29
|
+
|
|
30
|
+
let existingNamespace = currentLevel.find(el => el.namespace === ns);
|
|
31
|
+
if (!existingNamespace) {
|
|
32
|
+
existingNamespace = {
|
|
33
|
+
namespace: ns,
|
|
34
|
+
children: [],
|
|
35
|
+
path: path,
|
|
36
|
+
};
|
|
37
|
+
currentLevel.push(existingNamespace);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
currentLevel = existingNamespace.children;
|
|
41
|
+
path += '.';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return hierarchy;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const fetchData = async () => {
|
|
49
|
+
const namespaces = await djClient.namespaces();
|
|
50
|
+
const hierarchy = createNamespaceHierarchy(namespaces);
|
|
51
|
+
setNamespaceHierarchy(hierarchy);
|
|
52
|
+
};
|
|
53
|
+
fetchData().catch(console.error);
|
|
54
|
+
}, [djClient, djClient.namespaces]);
|
|
55
|
+
|
|
25
56
|
useEffect(() => {
|
|
26
57
|
const fetchData = async () => {
|
|
27
|
-
|
|
58
|
+
if (namespace === undefined && namespaceHierarchy !== undefined) {
|
|
59
|
+
namespace = namespaceHierarchy.children[0].path;
|
|
60
|
+
}
|
|
61
|
+
const djNodes = await djClient.namespace(namespace);
|
|
28
62
|
const nodes = djNodes.map(node => {
|
|
29
|
-
return
|
|
63
|
+
return djClient.node(node);
|
|
30
64
|
});
|
|
31
65
|
const foundNodes = await Promise.all(nodes);
|
|
32
66
|
setState({
|
|
@@ -35,13 +69,10 @@ export function NamespacePage() {
|
|
|
35
69
|
});
|
|
36
70
|
};
|
|
37
71
|
fetchData().catch(console.error);
|
|
38
|
-
}, [namespace]);
|
|
72
|
+
}, [djClient, namespace, namespaceHierarchy]);
|
|
39
73
|
|
|
40
74
|
const nodesList = state.nodes.map(node => (
|
|
41
75
|
<tr>
|
|
42
|
-
<td>
|
|
43
|
-
<a href={'/namespaces/' + node.namespace}>{node.namespace}</a>
|
|
44
|
-
</td>
|
|
45
76
|
<td>
|
|
46
77
|
<a href={'/nodes/' + node.name} className="link-table">
|
|
47
78
|
{node.display_name}
|
|
@@ -64,26 +95,57 @@ export function NamespacePage() {
|
|
|
64
95
|
<td>
|
|
65
96
|
<span className="status">{node.mode}</span>
|
|
66
97
|
</td>
|
|
98
|
+
<td>
|
|
99
|
+
<span className="status">{node.tags}</span>
|
|
100
|
+
</td>
|
|
101
|
+
<td>
|
|
102
|
+
<span className="status">
|
|
103
|
+
{new Date(node.updated_at).toLocaleString('en-us')}
|
|
104
|
+
</span>
|
|
105
|
+
</td>
|
|
67
106
|
</tr>
|
|
68
107
|
));
|
|
69
108
|
|
|
70
|
-
// @ts-ignore
|
|
71
109
|
return (
|
|
72
110
|
<div className="mid">
|
|
73
|
-
<NamespaceHeader namespace={namespace} />
|
|
74
111
|
<div className="card">
|
|
75
112
|
<div className="card-header">
|
|
76
|
-
<h2>
|
|
113
|
+
<h2>Explore</h2>
|
|
77
114
|
<div className="table-responsive">
|
|
115
|
+
<div className={`sidebar`}>
|
|
116
|
+
<span
|
|
117
|
+
style={{
|
|
118
|
+
textTransform: 'uppercase',
|
|
119
|
+
fontSize: '0.8125rem',
|
|
120
|
+
fontWeight: '600',
|
|
121
|
+
color: '#95aac9',
|
|
122
|
+
padding: '1rem 1rem 1rem 0',
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
Namespaces
|
|
126
|
+
</span>
|
|
127
|
+
{namespaceHierarchy
|
|
128
|
+
? namespaceHierarchy.map(child => (
|
|
129
|
+
<Explorer
|
|
130
|
+
item={child}
|
|
131
|
+
current={state.namespace}
|
|
132
|
+
defaultExpand={true}
|
|
133
|
+
/>
|
|
134
|
+
))
|
|
135
|
+
: null}
|
|
136
|
+
</div>
|
|
78
137
|
<table className="card-table table">
|
|
79
138
|
<thead>
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
139
|
+
<tr>
|
|
140
|
+
<th>Name</th>
|
|
141
|
+
<th>Type</th>
|
|
142
|
+
<th>Status</th>
|
|
143
|
+
<th>Mode</th>
|
|
144
|
+
<th>Tags</th>
|
|
145
|
+
<th>Last Updated</th>
|
|
146
|
+
</tr>
|
|
85
147
|
</thead>
|
|
86
|
-
{nodesList}
|
|
148
|
+
<tbody>{nodesList}</tbody>
|
|
87
149
|
</table>
|
|
88
150
|
</div>
|
|
89
151
|
</div>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { nightOwl } from 'react-syntax-highlighter/src/styles/hljs';
|
|
4
|
+
import PythonIcon from '../../icons/PythonIcon';
|
|
5
|
+
|
|
6
|
+
export default function ClientCodePopover({ code }) {
|
|
7
|
+
const [codeAnchor, setCodeAnchor] = useState(false);
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<button
|
|
12
|
+
className="code-button"
|
|
13
|
+
tabIndex="0"
|
|
14
|
+
height="45px"
|
|
15
|
+
onClick={() => setCodeAnchor(!codeAnchor)}
|
|
16
|
+
>
|
|
17
|
+
<PythonIcon />
|
|
18
|
+
</button>
|
|
19
|
+
<div
|
|
20
|
+
id={`node-create-code`}
|
|
21
|
+
onClose={() => setCodeAnchor(null)}
|
|
22
|
+
style={{ display: codeAnchor === false ? 'none' : 'block' }}
|
|
23
|
+
>
|
|
24
|
+
<SyntaxHighlighter language="python" style={nightOwl}>
|
|
25
|
+
{code}
|
|
26
|
+
</SyntaxHighlighter>
|
|
27
|
+
</div>
|
|
28
|
+
</>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
import * as React from 'react';
|
|
6
6
|
import { lazyLoad } from '../../../utils/loadable';
|
|
7
7
|
|
|
8
|
-
export const NodePage =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
export const NodePage = props => {
|
|
9
|
+
return lazyLoad(
|
|
10
|
+
() => import('./index'),
|
|
11
|
+
module => module.NodePage,
|
|
12
|
+
{
|
|
13
|
+
fallback: <div></div>,
|
|
14
|
+
},
|
|
15
|
+
)(props);
|
|
16
|
+
};
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import { Component } from 'react';
|
|
1
|
+
import { Component, useEffect, useState } from 'react';
|
|
2
|
+
import ClientCodePopover from './ClientCodePopover';
|
|
2
3
|
|
|
3
|
-
export default
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
export default function NodeColumnTab({ node, djClient }) {
|
|
5
|
+
const [columns, setColumns] = useState([]);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const fetchData = async () => {
|
|
8
|
+
setColumns(await djClient.columns(node));
|
|
9
|
+
};
|
|
10
|
+
fetchData().catch(console.error);
|
|
11
|
+
}, [djClient, node]);
|
|
12
|
+
|
|
13
|
+
const columnList = columns => {
|
|
14
|
+
return columns.map(col => (
|
|
6
15
|
<tr>
|
|
7
16
|
<td className="text-start">{col.name}</td>
|
|
8
17
|
<td>
|
|
@@ -10,7 +19,16 @@ export default class NodeColumnTab extends Component {
|
|
|
10
19
|
{col.type}
|
|
11
20
|
</span>
|
|
12
21
|
</td>
|
|
13
|
-
<td>
|
|
22
|
+
<td>
|
|
23
|
+
{col.dimension ? (
|
|
24
|
+
<>
|
|
25
|
+
<a href={`/nodes/${col.dimension.name}`}>{col.dimension.name}</a>
|
|
26
|
+
<ClientCodePopover code={col.clientCode} />
|
|
27
|
+
</>
|
|
28
|
+
) : (
|
|
29
|
+
''
|
|
30
|
+
)}{' '}
|
|
31
|
+
</td>
|
|
14
32
|
<td>
|
|
15
33
|
{col.attributes.find(
|
|
16
34
|
attr => attr.attribute_type.name === 'dimension',
|
|
@@ -26,19 +44,17 @@ export default class NodeColumnTab extends Component {
|
|
|
26
44
|
));
|
|
27
45
|
};
|
|
28
46
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
);
|
|
43
|
-
}
|
|
47
|
+
return (
|
|
48
|
+
<div className="table-responsive">
|
|
49
|
+
<table className="card-inner-table table">
|
|
50
|
+
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
51
|
+
<th className="text-start">Column</th>
|
|
52
|
+
<th>Type</th>
|
|
53
|
+
<th>Dimension</th>
|
|
54
|
+
<th>Attributes</th>
|
|
55
|
+
</thead>
|
|
56
|
+
{columnList(columns)}
|
|
57
|
+
</table>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
44
60
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo } from 'react';
|
|
1
|
+
import React, { useCallback, useContext, useEffect, useMemo } from 'react';
|
|
2
2
|
import ReactFlow, {
|
|
3
3
|
addEdge,
|
|
4
4
|
MiniMap,
|
|
@@ -12,10 +12,11 @@ import ReactFlow, {
|
|
|
12
12
|
import '../../../styles/dag.css';
|
|
13
13
|
import 'reactflow/dist/style.css';
|
|
14
14
|
import DJNode from '../../components/djgraph/DJNode';
|
|
15
|
-
import { DataJunctionAPI } from '../../services/DJService';
|
|
16
15
|
import dagre from 'dagre';
|
|
16
|
+
import DJClientContext from '../../providers/djclient';
|
|
17
17
|
|
|
18
18
|
const NodeLineage = djNode => {
|
|
19
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
19
20
|
const nodeTypes = useMemo(() => ({ DJNode: DJNode }), []);
|
|
20
21
|
|
|
21
22
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
|
@@ -25,51 +26,60 @@ const NodeLineage = djNode => {
|
|
|
25
26
|
height: 100,
|
|
26
27
|
width: 150,
|
|
27
28
|
};
|
|
28
|
-
const dagreGraph = new dagre.graphlib.Graph();
|
|
29
|
-
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
|
30
29
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
edges,
|
|
34
|
-
direction = 'LR',
|
|
35
|
-
nodeWidth = 800,
|
|
36
|
-
nodeHeight = 150,
|
|
37
|
-
) => {
|
|
38
|
-
const isHorizontal = direction === 'TB';
|
|
39
|
-
dagreGraph.setGraph({ rankdir: direction });
|
|
40
|
-
|
|
41
|
-
nodes.forEach(node => {
|
|
42
|
-
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
edges.forEach(edge => {
|
|
46
|
-
dagreGraph.setEdge(edge.source, edge.target);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
dagre.layout(dagreGraph);
|
|
50
|
-
|
|
51
|
-
nodes.forEach(node => {
|
|
52
|
-
const nodeWithPosition = dagreGraph.node(node.id);
|
|
53
|
-
node.targetPosition = isHorizontal ? 'left' : 'top';
|
|
54
|
-
node.sourcePosition = isHorizontal ? 'right' : 'bottom';
|
|
55
|
-
node.position = {
|
|
56
|
-
x: nodeWithPosition.x - nodeWidth / 2,
|
|
57
|
-
y: nodeWithPosition.y - nodeHeight / 2,
|
|
58
|
-
};
|
|
59
|
-
return node;
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
return { nodes, edges };
|
|
63
|
-
};
|
|
30
|
+
const dagreGraph = useMemo(() => new dagre.graphlib.Graph(), []);
|
|
31
|
+
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
|
64
32
|
|
|
65
33
|
useEffect(() => {
|
|
34
|
+
const setElementsLayout = (
|
|
35
|
+
nodes,
|
|
36
|
+
edges,
|
|
37
|
+
direction = 'LR',
|
|
38
|
+
nodeWidth = 800,
|
|
39
|
+
) => {
|
|
40
|
+
const isHorizontal = direction === 'TB';
|
|
41
|
+
dagreGraph.setGraph({ rankdir: direction });
|
|
42
|
+
const nodeHeightTracker = {};
|
|
43
|
+
|
|
44
|
+
nodes.forEach(node => {
|
|
45
|
+
nodeHeightTracker[node.id] =
|
|
46
|
+
Math.min(node.data.column_names.length, 10) * 37 + 250;
|
|
47
|
+
dagreGraph.setNode(node.id, {
|
|
48
|
+
width: nodeWidth,
|
|
49
|
+
height: nodeHeightTracker[node.id],
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
edges.forEach(edge => {
|
|
54
|
+
dagreGraph.setEdge(edge.source, edge.target);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
dagre.layout(dagreGraph);
|
|
58
|
+
|
|
59
|
+
nodes.forEach(node => {
|
|
60
|
+
const nodeWithPosition = dagreGraph.node(node.id);
|
|
61
|
+
node.targetPosition = isHorizontal ? 'left' : 'top';
|
|
62
|
+
node.sourcePosition = isHorizontal ? 'right' : 'bottom';
|
|
63
|
+
node.position = {
|
|
64
|
+
x: nodeWithPosition.x - nodeWidth / 2,
|
|
65
|
+
y: nodeWithPosition.y - nodeHeightTracker[node.id] / 2,
|
|
66
|
+
};
|
|
67
|
+
node.width = nodeWidth;
|
|
68
|
+
node.height = nodeHeightTracker[node.id];
|
|
69
|
+
return node;
|
|
70
|
+
});
|
|
71
|
+
return { nodes, edges };
|
|
72
|
+
};
|
|
73
|
+
|
|
66
74
|
const dagFetch = async () => {
|
|
67
|
-
let
|
|
68
|
-
|
|
75
|
+
let related_nodes = await djClient.node_dag(djNode.djNode.name);
|
|
76
|
+
// djNode.djNode.is_current = true;
|
|
69
77
|
var djNodes = [djNode.djNode];
|
|
70
|
-
for (const iterable of [
|
|
78
|
+
for (const iterable of [related_nodes]) {
|
|
71
79
|
for (const item of iterable) {
|
|
72
|
-
|
|
80
|
+
if (item.type !== 'cube') {
|
|
81
|
+
djNodes.push(item);
|
|
82
|
+
}
|
|
73
83
|
}
|
|
74
84
|
}
|
|
75
85
|
let edges = [];
|
|
@@ -78,24 +88,43 @@ const NodeLineage = djNode => {
|
|
|
78
88
|
if (parent.name) {
|
|
79
89
|
edges.push({
|
|
80
90
|
id: obj.name + '-' + parent.name,
|
|
81
|
-
target: obj.name,
|
|
82
91
|
source: parent.name,
|
|
92
|
+
sourceHandle: parent.name,
|
|
93
|
+
target: obj.name,
|
|
94
|
+
targetHandle: obj.name,
|
|
83
95
|
animated: true,
|
|
84
96
|
markerEnd: {
|
|
85
97
|
type: MarkerType.Arrow,
|
|
86
98
|
},
|
|
99
|
+
style: {
|
|
100
|
+
strokeWidth: 3,
|
|
101
|
+
stroke: '#b0b9c2',
|
|
102
|
+
},
|
|
87
103
|
});
|
|
88
104
|
}
|
|
89
105
|
});
|
|
90
106
|
|
|
91
107
|
obj.columns.forEach(col => {
|
|
92
108
|
if (col.dimension) {
|
|
93
|
-
|
|
94
|
-
id: obj.name + '
|
|
95
|
-
target: obj.name,
|
|
109
|
+
const edge = {
|
|
110
|
+
id: col.dimension.name + '->' + obj.name + '.' + col.name,
|
|
96
111
|
source: col.dimension.name,
|
|
112
|
+
sourceHandle: col.dimension.name,
|
|
113
|
+
target: obj.name,
|
|
114
|
+
targetHandle: obj.name + '.' + col.name,
|
|
97
115
|
draggable: true,
|
|
98
|
-
|
|
116
|
+
markerStart: {
|
|
117
|
+
type: MarkerType.Arrow,
|
|
118
|
+
width: 20,
|
|
119
|
+
height: 20,
|
|
120
|
+
color: '#b0b9c2',
|
|
121
|
+
},
|
|
122
|
+
style: {
|
|
123
|
+
strokeWidth: 3,
|
|
124
|
+
stroke: '#b0b9c2',
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
edges.push(edge);
|
|
99
128
|
}
|
|
100
129
|
});
|
|
101
130
|
});
|
|
@@ -117,35 +146,35 @@ const NodeLineage = djNode => {
|
|
|
117
146
|
label:
|
|
118
147
|
node.table !== null
|
|
119
148
|
? String(node.schema_ + '.' + node.table)
|
|
120
|
-
:
|
|
149
|
+
: 'default.' + node.name,
|
|
121
150
|
table: node.table,
|
|
122
151
|
name: String(node.name),
|
|
123
152
|
display_name: String(node.display_name),
|
|
124
153
|
type: node.type,
|
|
125
154
|
primary_key: primary_key,
|
|
126
155
|
column_names: column_names,
|
|
127
|
-
|
|
156
|
+
is_current: node.name === djNode.djNode.name,
|
|
128
157
|
},
|
|
129
|
-
// parentNode: [node.name.split(".").slice(-2, -1)],
|
|
130
|
-
// extent: 'parent',
|
|
131
158
|
};
|
|
132
159
|
});
|
|
133
|
-
console.log(djNodes);
|
|
134
160
|
setNodes(nodes);
|
|
135
161
|
setEdges(edges);
|
|
162
|
+
|
|
163
|
+
// use dagre to determine the position of the parents (the DJ nodes)
|
|
164
|
+
// the positions of the columns are relative to each DJ node
|
|
136
165
|
setElementsLayout(nodes, edges);
|
|
137
166
|
};
|
|
138
167
|
|
|
139
168
|
dagFetch();
|
|
140
|
-
}, []);
|
|
169
|
+
}, [dagreGraph, djClient, djNode.djNode, setEdges, setNodes]);
|
|
141
170
|
|
|
142
171
|
const onConnect = useCallback(
|
|
143
172
|
params => setEdges(eds => addEdge(params, eds)),
|
|
144
|
-
[],
|
|
173
|
+
[setEdges],
|
|
145
174
|
);
|
|
146
175
|
|
|
147
176
|
return (
|
|
148
|
-
<div style={{ height: '
|
|
177
|
+
<div style={{ height: '800px' }}>
|
|
149
178
|
<ReactFlow
|
|
150
179
|
nodes={nodes}
|
|
151
180
|
edges={edges}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export default function NodeHistory({ node, djClient }) {
|
|
4
|
+
const [history, setHistory] = useState([]);
|
|
5
|
+
const [revisions, setRevisions] = useState([]);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const fetchData = async () => {
|
|
9
|
+
const data = await djClient.history('node', node.name);
|
|
10
|
+
setHistory(data);
|
|
11
|
+
const revisions = await djClient.revisions(node.name);
|
|
12
|
+
setRevisions(revisions);
|
|
13
|
+
};
|
|
14
|
+
fetchData().catch(console.error);
|
|
15
|
+
}, [djClient, node]);
|
|
16
|
+
const tableData = history => {
|
|
17
|
+
return history.map(event => (
|
|
18
|
+
<tr>
|
|
19
|
+
<td className="text-start">
|
|
20
|
+
<span
|
|
21
|
+
className={`history_type__${event.activity_type} badge node_type`}
|
|
22
|
+
>
|
|
23
|
+
{event.activity_type}
|
|
24
|
+
</span>
|
|
25
|
+
</td>
|
|
26
|
+
<td>{event.entity_type}</td>
|
|
27
|
+
<td>{event.entity_name}</td>
|
|
28
|
+
<td>{event.user ? event.user : 'unknown'}</td>
|
|
29
|
+
<td>{event.created_at}</td>
|
|
30
|
+
</tr>
|
|
31
|
+
));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const revisionsTable = revisions => {
|
|
35
|
+
return revisions.map(revision => (
|
|
36
|
+
<tr>
|
|
37
|
+
<td className="text-start">
|
|
38
|
+
<span className={`badge node_type__source`}>{revision.version}</span>
|
|
39
|
+
</td>
|
|
40
|
+
<td>{revision.display_name}</td>
|
|
41
|
+
<td>{revision.description}</td>
|
|
42
|
+
<td>{revision.query}</td>
|
|
43
|
+
<td>{revision.tags}</td>
|
|
44
|
+
</tr>
|
|
45
|
+
));
|
|
46
|
+
};
|
|
47
|
+
return (
|
|
48
|
+
<div className="table-responsive">
|
|
49
|
+
<table className="card-inner-table table">
|
|
50
|
+
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
51
|
+
<th className="text-start">Version</th>
|
|
52
|
+
<th>Display Name</th>
|
|
53
|
+
<th>Description</th>
|
|
54
|
+
<th>Query</th>
|
|
55
|
+
<th>Tags</th>
|
|
56
|
+
</thead>
|
|
57
|
+
{revisionsTable(revisions)}
|
|
58
|
+
</table>
|
|
59
|
+
<table className="card-inner-table table">
|
|
60
|
+
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
61
|
+
<th className="text-start">Activity</th>
|
|
62
|
+
<th>Type</th>
|
|
63
|
+
<th>Name</th>
|
|
64
|
+
<th>User</th>
|
|
65
|
+
<th>Timestamp</th>
|
|
66
|
+
</thead>
|
|
67
|
+
{tableData(history)}
|
|
68
|
+
</table>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|