datajunction-ui 0.0.1-a1
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/.babel-plugin-macrosrc.js +5 -0
- package/.env +3 -0
- package/.eslintrc.js +20 -0
- package/.gitattributes +201 -0
- package/.husky/pre-commit +6 -0
- package/.nvmrc +1 -0
- package/.prettierignore +6 -0
- package/.prettierrc +9 -0
- package/.stylelintrc +7 -0
- package/LICENSE +22 -0
- package/Makefile +3 -0
- package/README.md +10 -0
- package/dj-logo.svg +10 -0
- package/internals/testing/loadable.mock.tsx +6 -0
- package/package.json +189 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +26 -0
- package/public/manifest.json +15 -0
- package/public/robots.txt +3 -0
- package/src/__tests__/reportWebVitals.test.jsx +44 -0
- package/src/app/__tests__/__snapshots__/index.test.tsx.snap +9 -0
- package/src/app/__tests__/index.test.tsx +14 -0
- package/src/app/components/DeleteNode.jsx +55 -0
- package/src/app/components/ListGroupItem.jsx +24 -0
- package/src/app/components/NamespaceHeader.jsx +31 -0
- package/src/app/components/QueryInfo.jsx +77 -0
- package/src/app/components/Tab.jsx +25 -0
- package/src/app/components/ToggleSwitch.jsx +20 -0
- package/src/app/components/__tests__/DeleteNode.test.jsx +53 -0
- package/src/app/components/__tests__/ListGroupItem.test.tsx +16 -0
- package/src/app/components/__tests__/NamespaceHeader.test.jsx +14 -0
- package/src/app/components/__tests__/QueryInfo.test.jsx +55 -0
- package/src/app/components/__tests__/Tab.test.jsx +27 -0
- package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
- package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +29 -0
- package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +47 -0
- package/src/app/components/djgraph/Collapse.jsx +46 -0
- package/src/app/components/djgraph/DJNode.jsx +89 -0
- package/src/app/components/djgraph/DJNodeColumns.jsx +71 -0
- package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
- package/src/app/components/djgraph/LayoutFlow.jsx +104 -0
- package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
- package/src/app/components/djgraph/__tests__/DJNode.test.tsx +24 -0
- package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
- package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
- package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +117 -0
- package/src/app/constants.js +2 -0
- package/src/app/icons/AlertIcon.jsx +32 -0
- package/src/app/icons/CollapsedIcon.jsx +15 -0
- package/src/app/icons/DJLogo.jsx +36 -0
- package/src/app/icons/DeleteIcon.jsx +21 -0
- package/src/app/icons/EditIcon.jsx +18 -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/LoadingIcon.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 +108 -0
- package/src/app/pages/AddEditNodePage/FormikSelect.jsx +46 -0
- package/src/app/pages/AddEditNodePage/FullNameField.jsx +37 -0
- package/src/app/pages/AddEditNodePage/Loadable.jsx +16 -0
- package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +89 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +103 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +132 -0
- package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
- package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
- package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
- package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +180 -0
- package/src/app/pages/AddEditNodePage/index.jsx +396 -0
- package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
- package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
- package/src/app/pages/AddEditTagPage/index.jsx +132 -0
- package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
- package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
- package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
- package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
- package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
- package/src/app/pages/LoginPage/index.jsx +17 -0
- package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
- package/src/app/pages/NamespacePage/Explorer.jsx +57 -0
- package/src/app/pages/NamespacePage/Loadable.jsx +16 -0
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +217 -0
- package/src/app/pages/NamespacePage/index.jsx +199 -0
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +166 -0
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +161 -0
- package/src/app/pages/NodePage/ClientCodePopover.jsx +46 -0
- package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +149 -0
- package/src/app/pages/NodePage/Loadable.jsx +16 -0
- package/src/app/pages/NodePage/NodeColumnTab.jsx +200 -0
- package/src/app/pages/NodePage/NodeGraphTab.jsx +112 -0
- package/src/app/pages/NodePage/NodeHistory.jsx +212 -0
- package/src/app/pages/NodePage/NodeInfoTab.jsx +212 -0
- package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +233 -0
- package/src/app/pages/NodePage/NodeSQLTab.jsx +82 -0
- package/src/app/pages/NodePage/NodeStatus.jsx +28 -0
- package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
- package/src/app/pages/NodePage/PartitionColumnPopover.jsx +153 -0
- package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +47 -0
- package/src/app/pages/NodePage/__tests__/ClientCodePopover.test.jsx +49 -0
- package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +148 -0
- package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +165 -0
- package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +591 -0
- package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +57 -0
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +757 -0
- package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +403 -0
- package/src/app/pages/NodePage/index.jsx +210 -0
- package/src/app/pages/NotFoundPage/Loadable.tsx +14 -0
- package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
- package/src/app/pages/NotFoundPage/index.tsx +23 -0
- package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
- package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +110 -0
- package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +37 -0
- package/src/app/pages/RegisterTablePage/index.jsx +142 -0
- package/src/app/pages/Root/Loadable.tsx +14 -0
- package/src/app/pages/Root/__tests__/index.test.jsx +77 -0
- package/src/app/pages/Root/assets/dj-logo.png +0 -0
- package/src/app/pages/Root/index.tsx +70 -0
- package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
- package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
- package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
- package/src/app/pages/TagPage/Loadable.jsx +16 -0
- package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
- package/src/app/pages/TagPage/index.jsx +79 -0
- package/src/app/providers/djclient.jsx +5 -0
- package/src/app/services/DJService.js +665 -0
- package/src/app/services/__tests__/DJService.test.jsx +804 -0
- package/src/index.tsx +48 -0
- package/src/mocks/mockNodes.jsx +1430 -0
- package/src/react-app-env.d.ts +4 -0
- package/src/reportWebVitals.ts +15 -0
- package/src/setupTests.ts +36 -0
- package/src/styles/dag.css +228 -0
- package/src/styles/index.css +1083 -0
- package/src/styles/loading.css +34 -0
- package/src/styles/login.css +81 -0
- package/src/styles/node-creation.scss +197 -0
- package/src/styles/styles.scss +44 -0
- package/src/styles/styles.scss.d.ts +9 -0
- package/src/utils/__tests__/__snapshots__/loadable.test.tsx.snap +17 -0
- package/src/utils/__tests__/loadable.test.tsx +53 -0
- package/src/utils/__tests__/request.test.ts +82 -0
- package/src/utils/form.jsx +23 -0
- package/src/utils/loadable.tsx +30 -0
- package/src/utils/request.ts +54 -0
- package/tsconfig.json +34 -0
- package/webpack.config.js +118 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { MarkerType } from 'reactflow';
|
|
3
|
+
|
|
4
|
+
import '../../../styles/dag.css';
|
|
5
|
+
import 'reactflow/dist/style.css';
|
|
6
|
+
import DJClientContext from '../../providers/djclient';
|
|
7
|
+
import LayoutFlow from '../../components/djgraph/LayoutFlow';
|
|
8
|
+
|
|
9
|
+
const NodeLineage = djNode => {
|
|
10
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
11
|
+
|
|
12
|
+
const createNode = node => {
|
|
13
|
+
const primary_key = node.columns
|
|
14
|
+
.filter(col =>
|
|
15
|
+
col.attributes.some(attr => attr.attribute_type.name === 'primary_key'),
|
|
16
|
+
)
|
|
17
|
+
.map(col => col.name);
|
|
18
|
+
const column_names = node.columns.map(col => {
|
|
19
|
+
return { name: col.name, type: col.type };
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
id: String(node.name),
|
|
23
|
+
type: 'DJNode',
|
|
24
|
+
data: {
|
|
25
|
+
label:
|
|
26
|
+
node.table !== null
|
|
27
|
+
? String(node.schema_ + '.' + node.table)
|
|
28
|
+
: 'default.' + node.name,
|
|
29
|
+
table: node.table,
|
|
30
|
+
name: String(node.name),
|
|
31
|
+
display_name: String(node.display_name),
|
|
32
|
+
type: node.type,
|
|
33
|
+
primary_key: primary_key,
|
|
34
|
+
column_names: column_names,
|
|
35
|
+
is_current: node.name === djNode.djNode.name,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const dimensionEdges = node => {
|
|
41
|
+
return node.columns
|
|
42
|
+
.filter(col => col.dimension)
|
|
43
|
+
.map(col => {
|
|
44
|
+
return {
|
|
45
|
+
id: col.dimension.name + '->' + node.name + '.' + col.name,
|
|
46
|
+
source: col.dimension.name,
|
|
47
|
+
sourceHandle: col.dimension.name,
|
|
48
|
+
target: node.name,
|
|
49
|
+
targetHandle: node.name + '.' + col.name,
|
|
50
|
+
draggable: true,
|
|
51
|
+
markerStart: {
|
|
52
|
+
type: MarkerType.Arrow,
|
|
53
|
+
width: 20,
|
|
54
|
+
height: 20,
|
|
55
|
+
color: '#b0b9c2',
|
|
56
|
+
},
|
|
57
|
+
style: {
|
|
58
|
+
strokeWidth: 3,
|
|
59
|
+
stroke: '#b0b9c2',
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const parentEdges = node => {
|
|
66
|
+
return node.parents
|
|
67
|
+
.filter(parent => parent.name)
|
|
68
|
+
.map(parent => {
|
|
69
|
+
return {
|
|
70
|
+
id: node.name + '-' + parent.name,
|
|
71
|
+
source: parent.name,
|
|
72
|
+
sourceHandle: parent.name,
|
|
73
|
+
target: node.name,
|
|
74
|
+
targetHandle: node.name,
|
|
75
|
+
animated: true,
|
|
76
|
+
markerEnd: {
|
|
77
|
+
type: MarkerType.Arrow,
|
|
78
|
+
},
|
|
79
|
+
style: {
|
|
80
|
+
strokeWidth: 3,
|
|
81
|
+
stroke: '#b0b9c2',
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const dagFetch = async (getLayoutedElements, setNodes, setEdges) => {
|
|
88
|
+
let related_nodes = await djClient.node_dag(djNode.djNode.name);
|
|
89
|
+
var djNodes = [djNode.djNode];
|
|
90
|
+
for (const iterable of [related_nodes]) {
|
|
91
|
+
for (const item of iterable) {
|
|
92
|
+
if (item.type !== 'cube') {
|
|
93
|
+
djNodes.push(item);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
let edges = [];
|
|
98
|
+
djNodes.forEach(node => {
|
|
99
|
+
edges = edges.concat(parentEdges(node));
|
|
100
|
+
edges = edges.concat(dimensionEdges(node));
|
|
101
|
+
});
|
|
102
|
+
const nodes = djNodes.map(node => createNode(node));
|
|
103
|
+
|
|
104
|
+
// use dagre to determine the position of the parents (the DJ nodes)
|
|
105
|
+
// the positions of the columns are relative to each DJ node
|
|
106
|
+
getLayoutedElements(nodes, edges);
|
|
107
|
+
setNodes(nodes);
|
|
108
|
+
setEdges(edges);
|
|
109
|
+
};
|
|
110
|
+
return LayoutFlow(djNode, dagFetch);
|
|
111
|
+
};
|
|
112
|
+
export default NodeLineage;
|
|
@@ -0,0 +1,212 @@
|
|
|
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
|
+
if (node) {
|
|
10
|
+
const data = await djClient.history('node', node.name);
|
|
11
|
+
setHistory(data);
|
|
12
|
+
const revisions = await djClient.revisions(node.name);
|
|
13
|
+
setRevisions(revisions);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
fetchData().catch(console.error);
|
|
17
|
+
}, [djClient, node]);
|
|
18
|
+
|
|
19
|
+
const eventData = event => {
|
|
20
|
+
if (
|
|
21
|
+
event.activity_type === 'set_attribute' &&
|
|
22
|
+
event.entity_type === 'column_attribute'
|
|
23
|
+
) {
|
|
24
|
+
return event.details.attributes
|
|
25
|
+
.map(attr => (
|
|
26
|
+
<div
|
|
27
|
+
key={event.id}
|
|
28
|
+
role="cell"
|
|
29
|
+
aria-label="HistoryAttribute"
|
|
30
|
+
aria-hidden="false"
|
|
31
|
+
>
|
|
32
|
+
Set{' '}
|
|
33
|
+
<span className={`badge partition_value`}>
|
|
34
|
+
{event.details.column}
|
|
35
|
+
</span>{' '}
|
|
36
|
+
as{' '}
|
|
37
|
+
<span className={`badge partition_value_highlight`}>
|
|
38
|
+
{attr.name}
|
|
39
|
+
</span>
|
|
40
|
+
</div>
|
|
41
|
+
))
|
|
42
|
+
.reduce((prev, curr) => [prev, <br />, curr], []);
|
|
43
|
+
}
|
|
44
|
+
if (event.activity_type === 'create' && event.entity_type === 'link') {
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
key={event.id}
|
|
48
|
+
role="cell"
|
|
49
|
+
aria-label="HistoryCreateLink"
|
|
50
|
+
aria-hidden="false"
|
|
51
|
+
>
|
|
52
|
+
Linked{' '}
|
|
53
|
+
<span className={`badge partition_value`}>
|
|
54
|
+
{event.details.column}
|
|
55
|
+
</span>{' '}
|
|
56
|
+
to
|
|
57
|
+
<span className={`badge partition_value_highlight`}>
|
|
58
|
+
{event.details.dimension}
|
|
59
|
+
</span>{' '}
|
|
60
|
+
via
|
|
61
|
+
<span className={`badge partition_value`}>
|
|
62
|
+
{event.details.dimension_column}
|
|
63
|
+
</span>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (
|
|
68
|
+
event.activity_type === 'create' &&
|
|
69
|
+
event.entity_type === 'materialization'
|
|
70
|
+
) {
|
|
71
|
+
return (
|
|
72
|
+
<div
|
|
73
|
+
key={event.id}
|
|
74
|
+
role="cell"
|
|
75
|
+
aria-label="HistoryCreateMaterialization"
|
|
76
|
+
aria-hidden="false"
|
|
77
|
+
>
|
|
78
|
+
Initialized materialization{' '}
|
|
79
|
+
<span className={`badge partition_value`}>
|
|
80
|
+
{event.details.materialization}
|
|
81
|
+
</span>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (
|
|
86
|
+
event.activity_type === 'create' &&
|
|
87
|
+
event.entity_type === 'availability'
|
|
88
|
+
) {
|
|
89
|
+
return (
|
|
90
|
+
<div
|
|
91
|
+
key={event.id}
|
|
92
|
+
role="cell"
|
|
93
|
+
aria-label="HistoryCreateAvailability"
|
|
94
|
+
aria-hidden="false"
|
|
95
|
+
>
|
|
96
|
+
Materialized at{' '}
|
|
97
|
+
<span className={`badge partition_value_highlight`}>
|
|
98
|
+
{event.post.catalog}.{event.post.schema_}.{event.post.table}
|
|
99
|
+
</span>
|
|
100
|
+
from{' '}
|
|
101
|
+
<span className={`badge partition_value`}>
|
|
102
|
+
{event.post.min_temporal_partition}
|
|
103
|
+
</span>{' '}
|
|
104
|
+
to
|
|
105
|
+
<span className={`badge partition_value`}>
|
|
106
|
+
{event.post.max_temporal_partition}
|
|
107
|
+
</span>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
event.activity_type === 'status_change' &&
|
|
113
|
+
event.entity_type === 'node'
|
|
114
|
+
) {
|
|
115
|
+
const expr = (
|
|
116
|
+
<div>
|
|
117
|
+
Caused by a change in upstream{' '}
|
|
118
|
+
<a href={`/nodes/${event.details['upstream_node']}`}>
|
|
119
|
+
{event.details['upstream_node']}
|
|
120
|
+
</a>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
key={event.id}
|
|
126
|
+
role="cell"
|
|
127
|
+
aria-label="HistoryNodeStatusChange"
|
|
128
|
+
aria-hidden="false"
|
|
129
|
+
>
|
|
130
|
+
Status changed from{' '}
|
|
131
|
+
<span className={`status__${event.pre['status']}`}>
|
|
132
|
+
{event.pre['status']}
|
|
133
|
+
</span>{' '}
|
|
134
|
+
to{' '}
|
|
135
|
+
<span className={`status__${event.post['status']}`}>
|
|
136
|
+
{event.post['status']}
|
|
137
|
+
</span>{' '}
|
|
138
|
+
{event.details['upstream_node'] !== undefined ? expr : ''}
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return (
|
|
143
|
+
<div key={event.id}>
|
|
144
|
+
{JSON.stringify(event.details) === '{}'
|
|
145
|
+
? ''
|
|
146
|
+
: JSON.stringify(event.details)}
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const tableData = history => {
|
|
152
|
+
return history.map(event => (
|
|
153
|
+
<tr key={`history-row-${event.id}`}>
|
|
154
|
+
<td className="text-start">
|
|
155
|
+
<span
|
|
156
|
+
className={`history_type__${event.activity_type} badge node_type`}
|
|
157
|
+
>
|
|
158
|
+
{event.activity_type}
|
|
159
|
+
</span>
|
|
160
|
+
</td>
|
|
161
|
+
<td>{event.entity_type}</td>
|
|
162
|
+
<td>{event.entity_name}</td>
|
|
163
|
+
<td>{event.user ? event.user : 'unknown'}</td>
|
|
164
|
+
<td>{event.created_at}</td>
|
|
165
|
+
<td>{eventData(event)}</td>
|
|
166
|
+
</tr>
|
|
167
|
+
));
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const revisionsTable = revisions => {
|
|
171
|
+
return revisions.map(revision => (
|
|
172
|
+
<tr key={revision.version}>
|
|
173
|
+
<td className="text-start">
|
|
174
|
+
<span className={`badge node_type__source`}>{revision.version}</span>
|
|
175
|
+
</td>
|
|
176
|
+
<td>{revision.display_name}</td>
|
|
177
|
+
<td>{revision.description}</td>
|
|
178
|
+
<td>{revision.query}</td>
|
|
179
|
+
<td>{revision.tags}</td>
|
|
180
|
+
</tr>
|
|
181
|
+
));
|
|
182
|
+
};
|
|
183
|
+
return (
|
|
184
|
+
<div className="table-vertical">
|
|
185
|
+
<table className="card-inner-table table" aria-label="Revisions">
|
|
186
|
+
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
187
|
+
<tr>
|
|
188
|
+
<th className="text-start">Version</th>
|
|
189
|
+
<th>Display Name</th>
|
|
190
|
+
<th>Description</th>
|
|
191
|
+
<th>Query</th>
|
|
192
|
+
<th>Tags</th>
|
|
193
|
+
</tr>
|
|
194
|
+
</thead>
|
|
195
|
+
<tbody>{revisionsTable(revisions)}</tbody>
|
|
196
|
+
</table>
|
|
197
|
+
<table className="card-inner-table table" aria-label="Activity">
|
|
198
|
+
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
199
|
+
<tr>
|
|
200
|
+
<th className="text-start">Activity</th>
|
|
201
|
+
<th>Type</th>
|
|
202
|
+
<th>Name</th>
|
|
203
|
+
<th>User</th>
|
|
204
|
+
<th>Timestamp</th>
|
|
205
|
+
<th>Details</th>
|
|
206
|
+
</tr>
|
|
207
|
+
</thead>
|
|
208
|
+
<tbody>{tableData(history)}</tbody>
|
|
209
|
+
</table>
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { useState, useContext, useEffect } from 'react';
|
|
2
|
+
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
3
|
+
import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
|
|
4
|
+
import sql from 'react-syntax-highlighter/dist/esm/languages/hljs/sql';
|
|
5
|
+
import NodeStatus from './NodeStatus';
|
|
6
|
+
import ListGroupItem from '../../components/ListGroupItem';
|
|
7
|
+
import ToggleSwitch from '../../components/ToggleSwitch';
|
|
8
|
+
import DJClientContext from '../../providers/djclient';
|
|
9
|
+
|
|
10
|
+
SyntaxHighlighter.registerLanguage('sql', sql);
|
|
11
|
+
foundation.hljs['padding'] = '2rem';
|
|
12
|
+
|
|
13
|
+
export default function NodeInfoTab({ node }) {
|
|
14
|
+
const [compiledSQL, setCompiledSQL] = useState('');
|
|
15
|
+
const [checked, setChecked] = useState(false);
|
|
16
|
+
const nodeTags = node?.tags.map(tag => (
|
|
17
|
+
<div className={'badge tag_value'}>
|
|
18
|
+
<a href={`/tags/${tag.name}`}>{tag.display_name}</a>
|
|
19
|
+
</div>
|
|
20
|
+
));
|
|
21
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const fetchData = async () => {
|
|
24
|
+
if (checked === true) {
|
|
25
|
+
const data = await djClient.compiledSql(node.name);
|
|
26
|
+
if (data.sql) {
|
|
27
|
+
setCompiledSQL(data.sql);
|
|
28
|
+
} else {
|
|
29
|
+
setCompiledSQL(
|
|
30
|
+
'/* Ran into an issue while generating compiled SQL */',
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
fetchData().catch(console.error);
|
|
36
|
+
}, [node, djClient, checked]);
|
|
37
|
+
function toggle(value) {
|
|
38
|
+
return !value;
|
|
39
|
+
}
|
|
40
|
+
const queryDiv = node?.query ? (
|
|
41
|
+
<div className="list-group-item d-flex">
|
|
42
|
+
<div className="d-flex gap-2 w-100 justify-content-between py-3">
|
|
43
|
+
<div
|
|
44
|
+
style={{
|
|
45
|
+
width: window.innerWidth * 0.8,
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
<h6 className="mb-0 w-100">Query</h6>
|
|
49
|
+
{['metric', 'dimension', 'transform'].indexOf(node?.type) > -1 ? (
|
|
50
|
+
<ToggleSwitch
|
|
51
|
+
id="toggleSwitch"
|
|
52
|
+
checked={checked}
|
|
53
|
+
onChange={() => setChecked(toggle)}
|
|
54
|
+
toggleName="Show Compiled SQL"
|
|
55
|
+
/>
|
|
56
|
+
) : (
|
|
57
|
+
<></>
|
|
58
|
+
)}
|
|
59
|
+
<SyntaxHighlighter language="sql" style={foundation}>
|
|
60
|
+
{checked ? compiledSQL : node?.query}
|
|
61
|
+
</SyntaxHighlighter>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
) : (
|
|
66
|
+
<></>
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const displayCubeElement = cubeElem => {
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
className="button-3 cube-element"
|
|
73
|
+
key={cubeElem.name}
|
|
74
|
+
role="cell"
|
|
75
|
+
aria-label="CubeElement"
|
|
76
|
+
aria-hidden="false"
|
|
77
|
+
>
|
|
78
|
+
<a href={`/nodes/${cubeElem.node_name}`}>{cubeElem.display_name}</a>
|
|
79
|
+
<span
|
|
80
|
+
className={`badge node_type__${
|
|
81
|
+
cubeElem.type === 'metric' ? cubeElem.type : 'dimension'
|
|
82
|
+
}`}
|
|
83
|
+
style={{ fontSize: '100%', textTransform: 'capitalize' }}
|
|
84
|
+
>
|
|
85
|
+
{cubeElem.type === 'metric' ? cubeElem.type : 'dimension'}
|
|
86
|
+
</span>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const cubeElementsDiv = node?.cube_elements ? (
|
|
92
|
+
<div className="list-group-item d-flex">
|
|
93
|
+
<div className="d-flex gap-2 w-100 justify-content-between py-3">
|
|
94
|
+
<div
|
|
95
|
+
style={{
|
|
96
|
+
width: window.innerWidth * 0.8,
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
<h6 className="mb-0 w-100">Cube Elements</h6>
|
|
100
|
+
<div className={`list-group-item`}>
|
|
101
|
+
{node.cube_elements.map(cubeElem =>
|
|
102
|
+
cubeElem.type === 'metric' ? displayCubeElement(cubeElem) : '',
|
|
103
|
+
)}
|
|
104
|
+
{node.cube_elements.map(cubeElem =>
|
|
105
|
+
cubeElem.type !== 'metric' ? displayCubeElement(cubeElem) : '',
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
) : (
|
|
112
|
+
<></>
|
|
113
|
+
);
|
|
114
|
+
return (
|
|
115
|
+
<div className="list-group align-items-center justify-content-between flex-md-row gap-2">
|
|
116
|
+
<ListGroupItem label="Description" value={node?.description} />
|
|
117
|
+
<div className="list-group-item d-flex">
|
|
118
|
+
<div className="d-flex gap-2 w-100 justify-content-between py-3">
|
|
119
|
+
<div>
|
|
120
|
+
<h6 className="mb-0 w-100">Version</h6>
|
|
121
|
+
|
|
122
|
+
<p className="mb-0 opacity-75">
|
|
123
|
+
<span
|
|
124
|
+
className="rounded-pill badge bg-secondary-soft"
|
|
125
|
+
style={{ marginLeft: '0.5rem', fontSize: '100%' }}
|
|
126
|
+
role="dialog"
|
|
127
|
+
aria-hidden="false"
|
|
128
|
+
aria-label="Version"
|
|
129
|
+
>
|
|
130
|
+
{node?.version}
|
|
131
|
+
</span>
|
|
132
|
+
</p>
|
|
133
|
+
</div>
|
|
134
|
+
{node.type === 'source' ? (
|
|
135
|
+
<div>
|
|
136
|
+
<h6 className="mb-0 w-100">Table</h6>
|
|
137
|
+
<p
|
|
138
|
+
className="mb-0 opacity-75"
|
|
139
|
+
role="dialog"
|
|
140
|
+
aria-hidden="false"
|
|
141
|
+
aria-label="Table"
|
|
142
|
+
>
|
|
143
|
+
{node?.catalog.name}.{node?.schema_}.{node?.table}
|
|
144
|
+
</p>
|
|
145
|
+
</div>
|
|
146
|
+
) : (
|
|
147
|
+
<></>
|
|
148
|
+
)}
|
|
149
|
+
<div>
|
|
150
|
+
<h6 className="mb-0 w-100">Status</h6>
|
|
151
|
+
<p
|
|
152
|
+
className="mb-0 opacity-75"
|
|
153
|
+
role="dialog"
|
|
154
|
+
aria-hidden="false"
|
|
155
|
+
aria-label="NodeStatus"
|
|
156
|
+
>
|
|
157
|
+
<NodeStatus node={node} />
|
|
158
|
+
</p>
|
|
159
|
+
</div>
|
|
160
|
+
<div>
|
|
161
|
+
<h6 className="mb-0 w-100">Mode</h6>
|
|
162
|
+
<p className="mb-0 opacity-75">
|
|
163
|
+
<span
|
|
164
|
+
className="status"
|
|
165
|
+
role="dialog"
|
|
166
|
+
aria-hidden="false"
|
|
167
|
+
aria-label="Mode"
|
|
168
|
+
>
|
|
169
|
+
{node?.mode}
|
|
170
|
+
</span>
|
|
171
|
+
</p>
|
|
172
|
+
</div>
|
|
173
|
+
<div>
|
|
174
|
+
<h6 className="mb-0 w-100">Tags</h6>
|
|
175
|
+
<p
|
|
176
|
+
className="mb-0 opacity-75"
|
|
177
|
+
role="dialog"
|
|
178
|
+
aria-hidden="false"
|
|
179
|
+
aria-label="Tags"
|
|
180
|
+
>
|
|
181
|
+
{nodeTags}
|
|
182
|
+
</p>
|
|
183
|
+
</div>
|
|
184
|
+
<div>
|
|
185
|
+
<h6 className="mb-0 w-100">Primary Key</h6>
|
|
186
|
+
<p
|
|
187
|
+
className="mb-0 opacity-75"
|
|
188
|
+
role="dialog"
|
|
189
|
+
aria-hidden="false"
|
|
190
|
+
aria-label="PrimaryKey"
|
|
191
|
+
>
|
|
192
|
+
{node?.primary_key?.join(', ')}
|
|
193
|
+
</p>
|
|
194
|
+
</div>
|
|
195
|
+
<div>
|
|
196
|
+
<h6 className="mb-0 w-100">Last Updated</h6>
|
|
197
|
+
<p
|
|
198
|
+
className="mb-0 opacity-75"
|
|
199
|
+
role="dialog"
|
|
200
|
+
aria-hidden="false"
|
|
201
|
+
aria-label="UpdatedAt"
|
|
202
|
+
>
|
|
203
|
+
{new Date(node?.updated_at).toDateString()}
|
|
204
|
+
</p>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
{node?.type !== 'cube' ? queryDiv : ''}
|
|
209
|
+
{cubeElementsDiv}
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { MarkerType } from 'reactflow';
|
|
3
|
+
|
|
4
|
+
import '../../../styles/dag.css';
|
|
5
|
+
import 'reactflow/dist/style.css';
|
|
6
|
+
import DJClientContext from '../../providers/djclient';
|
|
7
|
+
import LayoutFlow from '../../components/djgraph/LayoutFlow';
|
|
8
|
+
|
|
9
|
+
const createDJNode = node => {
|
|
10
|
+
return {
|
|
11
|
+
id: node.node_name,
|
|
12
|
+
type: 'DJNode',
|
|
13
|
+
data: {
|
|
14
|
+
label: node.node_name,
|
|
15
|
+
name: node.node_name,
|
|
16
|
+
type: node.node_type,
|
|
17
|
+
table: node.node_type === 'source' ? node.node_name : '',
|
|
18
|
+
display_name:
|
|
19
|
+
node.node_type === 'source' ? node.node_name : node.display_name,
|
|
20
|
+
column_names: [{ name: node.column_name, type: '' }],
|
|
21
|
+
primary_key: [],
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const NodeColumnLineage = djNode => {
|
|
27
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
28
|
+
const dagFetch = async (getLayoutedElements, setNodes, setEdges) => {
|
|
29
|
+
let relatedNodes = await djClient.node_lineage(djNode.djNode.name);
|
|
30
|
+
let nodesMapping = {};
|
|
31
|
+
let edgesMapping = {};
|
|
32
|
+
let processing = relatedNodes;
|
|
33
|
+
while (processing.length > 0) {
|
|
34
|
+
let current = processing.pop();
|
|
35
|
+
let node = createDJNode(current);
|
|
36
|
+
if (node.id in nodesMapping) {
|
|
37
|
+
nodesMapping[node.id].data.column_names = Array.from(
|
|
38
|
+
new Set([
|
|
39
|
+
...nodesMapping[node.id].data.column_names.map(x => x.name),
|
|
40
|
+
...node.data.column_names.map(x => x.name),
|
|
41
|
+
]),
|
|
42
|
+
).map(x => {
|
|
43
|
+
return { name: x, type: '' };
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
nodesMapping[node.id] = node;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
current.lineage.forEach(lineageColumn => {
|
|
50
|
+
const sourceHandle =
|
|
51
|
+
lineageColumn.node_name + '.' + lineageColumn.column_name;
|
|
52
|
+
const targetHandle = current.node_name + '.' + current.column_name;
|
|
53
|
+
edgesMapping[sourceHandle + '->' + targetHandle] = {
|
|
54
|
+
id: sourceHandle + '->' + targetHandle,
|
|
55
|
+
source: lineageColumn.node_name,
|
|
56
|
+
sourceHandle: sourceHandle,
|
|
57
|
+
target: current.node_name,
|
|
58
|
+
targetHandle: targetHandle,
|
|
59
|
+
animated: true,
|
|
60
|
+
markerEnd: {
|
|
61
|
+
type: MarkerType.Arrow,
|
|
62
|
+
},
|
|
63
|
+
style: {
|
|
64
|
+
strokeWidth: 3,
|
|
65
|
+
stroke: '#b0b9c2',
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
processing.push(lineageColumn);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// use dagre to determine the position of the parents (the DJ nodes)
|
|
73
|
+
// the positions of the columns are relative to each DJ node
|
|
74
|
+
const elements = getLayoutedElements(
|
|
75
|
+
Object.keys(nodesMapping).map(key => nodesMapping[key]),
|
|
76
|
+
Object.keys(edgesMapping).map(key => edgesMapping[key]),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
setNodes(elements.nodes);
|
|
80
|
+
setEdges(elements.edges);
|
|
81
|
+
};
|
|
82
|
+
return LayoutFlow(djNode, dagFetch);
|
|
83
|
+
};
|
|
84
|
+
export default NodeColumnLineage;
|