datajunction-ui 0.0.1-rc.13 → 0.0.1-rc.15

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.1-rc.13",
3
+ "version": "0.0.1-rc.15",
4
4
  "description": "DataJunction Metrics Platform UI",
5
5
  "module": "src/index.tsx",
6
6
  "repository": {
Binary file
@@ -35,6 +35,7 @@ exports[`<App /> should render and match the snapshot 1`] = `
35
35
  "namespaces": [Function],
36
36
  "node": [Function],
37
37
  "node_dag": [Function],
38
+ "node_lineage": [Function],
38
39
  "nodesWithDimension": [Function],
39
40
  "revisions": [Function],
40
41
  "sql": [Function],
@@ -1,6 +1,5 @@
1
- import React, { memo } from 'react';
1
+ import { memo, useLayoutEffect, useRef, useState } from 'react';
2
2
  import { Handle, Position } from 'reactflow';
3
- import { DJNodeDimensions } from './DJNodeDimensions';
4
3
  import Collapse from './Collapse';
5
4
 
6
5
  function capitalize(string) {
@@ -43,6 +42,8 @@ export function DJNode({ id, data }) {
43
42
  <>
44
43
  <div
45
44
  className={'dj-node__full node_type__' + data.type + highlightNodeClass}
45
+ key={data.name}
46
+ style={{ width: '450px' }}
46
47
  >
47
48
  <div style={handleWrapperStyle}>
48
49
  <Handle
@@ -61,7 +62,8 @@ export function DJNode({ id, data }) {
61
62
  </div>
62
63
  </div>
63
64
  <div className="dj-node__body">
64
- <b>{capitalize(data.type)}</b>:{' '}
65
+ <b>{capitalize(data.type)}</b>
66
+ <br />{' '}
65
67
  <a href={`/nodes/${data.name}`}>
66
68
  {data.type === 'source' ? data.table : data.display_name}
67
69
  </a>
@@ -1,6 +1,5 @@
1
- import React, { useContext, useEffect, useState } from 'react';
1
+ import { useContext, useEffect, useState } from 'react';
2
2
  import DJClientContext from '../../providers/djclient';
3
- import Collapse from './Collapse';
4
3
 
5
4
  export function DJNodeDimensions(data) {
6
5
  const [dimensions, setDimensions] = useState([]);
@@ -0,0 +1,104 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import ReactFlow, {
3
+ addEdge,
4
+ MiniMap,
5
+ Controls,
6
+ Background,
7
+ useNodesState,
8
+ useEdgesState,
9
+ } from 'reactflow';
10
+
11
+ import '../../../styles/dag.css';
12
+ import 'reactflow/dist/style.css';
13
+ import DJNode from '../../components/djgraph/DJNode';
14
+ import dagre from 'dagre';
15
+
16
+ const getLayoutedElements = (
17
+ nodes,
18
+ edges,
19
+ direction = 'LR',
20
+ nodeWidth = 600,
21
+ ) => {
22
+ const dagreGraph = new dagre.graphlib.Graph();
23
+ dagreGraph.setDefaultEdgeLabel(() => ({}));
24
+
25
+ const isHorizontal = direction === 'TB';
26
+ dagreGraph.setGraph({
27
+ rankdir: direction,
28
+ nodesep: 40,
29
+ ranksep: 10,
30
+ ranker: 'longest-path',
31
+ });
32
+ const nodeHeightTracker = {};
33
+
34
+ nodes.forEach(node => {
35
+ nodeHeightTracker[node.id] =
36
+ Math.min(node.data.column_names.length, 10) * 40 + 250;
37
+ dagreGraph.setNode(node.id, {
38
+ width: nodeWidth,
39
+ height: nodeHeightTracker[node.id],
40
+ });
41
+ });
42
+
43
+ edges.forEach(edge => {
44
+ dagreGraph.setEdge(edge.source, edge.target);
45
+ });
46
+
47
+ dagre.layout(dagreGraph);
48
+
49
+ nodes.forEach(node => {
50
+ const nodeWithPosition = dagreGraph.node(node.id);
51
+ node.targetPosition = isHorizontal ? 'left' : 'top';
52
+ node.sourcePosition = isHorizontal ? 'right' : 'bottom';
53
+ node.position = {
54
+ x: nodeWithPosition.x - nodeWidth / 2,
55
+ y: nodeWithPosition.y - nodeHeightTracker[node.id] / 2,
56
+ };
57
+ node.width = nodeWidth;
58
+ node.height = nodeHeightTracker[node.id];
59
+ return node;
60
+ });
61
+
62
+ return { nodes: nodes, edges: edges };
63
+ };
64
+
65
+ const LayoutFlow = (djNode, saveGraph) => {
66
+ const nodeTypes = useMemo(() => ({ DJNode: DJNode }), []);
67
+
68
+ // These are used internally by ReactFlow (to update the nodes on the ReactFlow pane)
69
+ const [nodes, setNodes, onNodesChange] = useNodesState([]);
70
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
71
+
72
+ const minimapStyle = {
73
+ height: 100,
74
+ width: 150,
75
+ };
76
+
77
+ useEffect(() => {
78
+ saveGraph(getLayoutedElements, setNodes, setEdges).catch(console.error);
79
+ }, [djNode]);
80
+
81
+ const onConnect = useCallback(
82
+ params => setEdges(eds => addEdge(params, eds)),
83
+ [setEdges],
84
+ );
85
+ return (
86
+ <div style={{ height: '800px' }}>
87
+ <ReactFlow
88
+ nodes={nodes}
89
+ edges={edges}
90
+ nodeTypes={nodeTypes}
91
+ onNodesChange={onNodesChange}
92
+ onEdgesChange={onEdgesChange}
93
+ onConnect={onConnect}
94
+ snapToGrid={true}
95
+ fitView
96
+ >
97
+ <MiniMap style={minimapStyle} zoomable pannable />
98
+ <Controls />
99
+ <Background color="#aaa" gap={16} />
100
+ </ReactFlow>
101
+ </div>
102
+ );
103
+ };
104
+ export default LayoutFlow;
@@ -4,6 +4,11 @@ exports[`<DJNode /> should render and match the snapshot 1`] = `
4
4
  <React.Fragment>
5
5
  <div
6
6
  className="dj-node__full node_type__source"
7
+ style={
8
+ Object {
9
+ "width": "450px",
10
+ }
11
+ }
7
12
  >
8
13
  <div
9
14
  style={
@@ -52,7 +57,7 @@ exports[`<DJNode /> should render and match the snapshot 1`] = `
52
57
  <b>
53
58
  Source
54
59
  </b>
55
- :
60
+ <br />
56
61
 
57
62
  <a
58
63
  href="/nodes/shared.dimensions.accounts"
@@ -41,6 +41,9 @@ exports[`<NamespacePage /> should render and match the snapshot 1`] = `
41
41
  <th>
42
42
  Name
43
43
  </th>
44
+ <th>
45
+ Display Name
46
+ </th>
44
47
  <th>
45
48
  Type
46
49
  </th>
@@ -75,7 +75,7 @@ export function NamespacePage() {
75
75
  <tr>
76
76
  <td>
77
77
  <a href={'/nodes/' + node.name} className="link-table">
78
- {node.display_name}
78
+ {node.name}
79
79
  </a>
80
80
  <span
81
81
  className="rounded-pill badge bg-secondary-soft"
@@ -84,6 +84,11 @@ export function NamespacePage() {
84
84
  {node.version}
85
85
  </span>
86
86
  </td>
87
+ <td>
88
+ <a href={'/nodes/' + node.name} className="link-table">
89
+ {node.display_name}
90
+ </a>
91
+ </td>
87
92
  <td>
88
93
  <span className={'node_type__' + node.type + ' badge node_type'}>
89
94
  {node.type}
@@ -138,6 +143,7 @@ export function NamespacePage() {
138
143
  <thead>
139
144
  <tr>
140
145
  <th>Name</th>
146
+ <th>Display Name</th>
141
147
  <th>Type</th>
142
148
  <th>Status</th>
143
149
  <th>Mode</th>
@@ -1,4 +1,4 @@
1
- import { Component, useEffect, useState } from 'react';
1
+ import { useEffect, useState } from 'react';
2
2
  import ClientCodePopover from './ClientCodePopover';
3
3
 
4
4
  export default function NodeColumnTab({ node, djClient }) {
@@ -1,195 +1,113 @@
1
- import React, { useCallback, useContext, useEffect, useMemo } from 'react';
2
- import ReactFlow, {
3
- addEdge,
4
- MiniMap,
5
- Controls,
6
- Background,
7
- useNodesState,
8
- useEdgesState,
9
- MarkerType,
10
- } from 'reactflow';
1
+ import React, { useContext } from 'react';
2
+ import { MarkerType } from 'reactflow';
11
3
 
12
4
  import '../../../styles/dag.css';
13
5
  import 'reactflow/dist/style.css';
14
6
  import DJNode from '../../components/djgraph/DJNode';
15
- import dagre from 'dagre';
16
7
  import DJClientContext from '../../providers/djclient';
8
+ import LayoutFlow from '../../components/djgraph/LayoutFlow';
17
9
 
18
10
  const NodeLineage = djNode => {
19
11
  const djClient = useContext(DJClientContext).DataJunctionAPI;
20
- const nodeTypes = useMemo(() => ({ DJNode: DJNode }), []);
21
12
 
22
- const [nodes, setNodes, onNodesChange] = useNodesState([]);
23
- const [edges, setEdges, onEdgesChange] = useEdgesState([]);
24
-
25
- const minimapStyle = {
26
- height: 100,
27
- width: 150,
13
+ const createNode = node => {
14
+ const primary_key = node.columns
15
+ .filter(col =>
16
+ col.attributes.some(attr => attr.attribute_type.name === 'primary_key'),
17
+ )
18
+ .map(col => col.name);
19
+ const column_names = node.columns.map(col => {
20
+ return { name: col.name, type: col.type };
21
+ });
22
+ return {
23
+ id: String(node.name),
24
+ type: 'DJNode',
25
+ data: {
26
+ label:
27
+ node.table !== null
28
+ ? String(node.schema_ + '.' + node.table)
29
+ : 'default.' + node.name,
30
+ table: node.table,
31
+ name: String(node.name),
32
+ display_name: String(node.display_name),
33
+ type: node.type,
34
+ primary_key: primary_key,
35
+ column_names: column_names,
36
+ is_current: node.name === djNode.djNode.name,
37
+ },
38
+ };
28
39
  };
29
40
 
30
- const dagreGraph = useMemo(() => new dagre.graphlib.Graph(), []);
31
- dagreGraph.setDefaultEdgeLabel(() => ({}));
32
-
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,
41
+ const dimensionEdges = node => {
42
+ return node.columns
43
+ .filter(col => col.dimension)
44
+ .map(col => {
45
+ return {
46
+ id: col.dimension.name + '->' + node.name + '.' + col.name,
47
+ source: col.dimension.name,
48
+ sourceHandle: col.dimension.name,
49
+ target: node.name,
50
+ targetHandle: node.name + '.' + col.name,
51
+ draggable: true,
52
+ markerStart: {
53
+ type: MarkerType.Arrow,
54
+ width: 20,
55
+ height: 20,
56
+ color: '#b0b9c2',
57
+ },
58
+ style: {
59
+ strokeWidth: 3,
60
+ stroke: '#b0b9c2',
61
+ },
66
62
  };
67
- node.width = nodeWidth;
68
- node.height = nodeHeightTracker[node.id];
69
- return node;
70
63
  });
71
- return { nodes, edges };
72
- };
73
-
74
- const dagFetch = async () => {
75
- let related_nodes = await djClient.node_dag(djNode.djNode.name);
76
- // djNode.djNode.is_current = true;
77
- var djNodes = [djNode.djNode];
78
- for (const iterable of [related_nodes]) {
79
- for (const item of iterable) {
80
- if (item.type !== 'cube') {
81
- djNodes.push(item);
82
- }
83
- }
84
- }
85
- let edges = [];
86
- djNodes.forEach(obj => {
87
- obj.parents.forEach(parent => {
88
- if (parent.name) {
89
- edges.push({
90
- id: obj.name + '-' + parent.name,
91
- source: parent.name,
92
- sourceHandle: parent.name,
93
- target: obj.name,
94
- targetHandle: obj.name,
95
- animated: true,
96
- markerEnd: {
97
- type: MarkerType.Arrow,
98
- },
99
- style: {
100
- strokeWidth: 3,
101
- stroke: '#b0b9c2',
102
- },
103
- });
104
- }
105
- });
64
+ };
106
65
 
107
- obj.columns.forEach(col => {
108
- if (col.dimension) {
109
- const edge = {
110
- id: col.dimension.name + '->' + obj.name + '.' + col.name,
111
- source: col.dimension.name,
112
- sourceHandle: col.dimension.name,
113
- target: obj.name,
114
- targetHandle: obj.name + '.' + col.name,
115
- draggable: true,
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);
128
- }
129
- });
130
- });
131
- const nodes = djNodes.map(node => {
132
- const primary_key = node.columns
133
- .filter(col =>
134
- col.attributes.some(
135
- attr => attr.attribute_type.name === 'primary_key',
136
- ),
137
- )
138
- .map(col => col.name);
139
- const column_names = node.columns.map(col => {
140
- return { name: col.name, type: col.type };
141
- });
66
+ const parentEdges = node => {
67
+ return node.parents
68
+ .filter(parent => parent.name)
69
+ .map(parent => {
142
70
  return {
143
- id: String(node.name),
144
- type: 'DJNode',
145
- data: {
146
- label:
147
- node.table !== null
148
- ? String(node.schema_ + '.' + node.table)
149
- : 'default.' + node.name,
150
- table: node.table,
151
- name: String(node.name),
152
- display_name: String(node.display_name),
153
- type: node.type,
154
- primary_key: primary_key,
155
- column_names: column_names,
156
- is_current: node.name === djNode.djNode.name,
71
+ id: node.name + '-' + parent.name,
72
+ source: parent.name,
73
+ sourceHandle: parent.name,
74
+ target: node.name,
75
+ targetHandle: node.name,
76
+ animated: true,
77
+ markerEnd: {
78
+ type: MarkerType.Arrow,
79
+ },
80
+ style: {
81
+ strokeWidth: 3,
82
+ stroke: '#b0b9c2',
157
83
  },
158
84
  };
159
85
  });
160
- setNodes(nodes);
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
165
- setElementsLayout(nodes, edges);
166
- };
167
-
168
- dagFetch();
169
- }, [dagreGraph, djClient, djNode.djNode, setEdges, setNodes]);
170
-
171
- const onConnect = useCallback(
172
- params => setEdges(eds => addEdge(params, eds)),
173
- [setEdges],
174
- );
86
+ };
175
87
 
176
- return (
177
- <div style={{ height: '800px' }}>
178
- <ReactFlow
179
- nodes={nodes}
180
- edges={edges}
181
- nodeTypes={nodeTypes}
182
- onNodesChange={onNodesChange}
183
- onEdgesChange={onEdgesChange}
184
- onConnect={onConnect}
185
- snapToGrid={true}
186
- fitView
187
- >
188
- <MiniMap style={minimapStyle} zoomable pannable />
189
- <Controls />
190
- <Background color="#aaa" gap={16} />
191
- </ReactFlow>
192
- </div>
193
- );
88
+ const dagFetch = async (getLayoutedElements, setNodes, setEdges) => {
89
+ let related_nodes = await djClient.node_dag(djNode.djNode.name);
90
+ var djNodes = [djNode.djNode];
91
+ for (const iterable of [related_nodes]) {
92
+ for (const item of iterable) {
93
+ if (item.type !== 'cube') {
94
+ djNodes.push(item);
95
+ }
96
+ }
97
+ }
98
+ let edges = [];
99
+ djNodes.forEach(node => {
100
+ edges = edges.concat(parentEdges(node));
101
+ edges = edges.concat(dimensionEdges(node));
102
+ });
103
+ const nodes = djNodes.map(node => createNode(node));
104
+
105
+ // use dagre to determine the position of the parents (the DJ nodes)
106
+ // the positions of the columns are relative to each DJ node
107
+ getLayoutedElements(nodes, edges);
108
+ setNodes(nodes);
109
+ setEdges(edges);
110
+ };
111
+ return LayoutFlow(djNode, dagFetch);
194
112
  };
195
113
  export default NodeLineage;
@@ -111,7 +111,13 @@ export default function NodeHistory({ node, djClient }) {
111
111
  </div>
112
112
  );
113
113
  }
114
- return '';
114
+ return (
115
+ <div>
116
+ {JSON.stringify(event.details) === '{}'
117
+ ? ''
118
+ : JSON.stringify(event.details)}
119
+ </div>
120
+ );
115
121
  };
116
122
 
117
123
  const tableData = history => {
@@ -125,6 +131,7 @@ export default function NodeHistory({ node, djClient }) {
125
131
  </span>
126
132
  </td>
127
133
  <td>{event.entity_type}</td>
134
+ <td>{event.entity_name}</td>
128
135
  <td>{event.user ? event.user : 'unknown'}</td>
129
136
  <td>{event.created_at}</td>
130
137
  <td>{eventData(event)}</td>
@@ -161,6 +168,7 @@ export default function NodeHistory({ node, djClient }) {
161
168
  <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
162
169
  <th className="text-start">Activity</th>
163
170
  <th>Type</th>
171
+ <th>Name</th>
164
172
  <th>User</th>
165
173
  <th>Timestamp</th>
166
174
  <th>Details</th>
@@ -11,21 +11,8 @@ SyntaxHighlighter.registerLanguage('sql', sql);
11
11
  foundation.hljs['padding'] = '2rem';
12
12
 
13
13
  export default function NodeInfoTab({ node }) {
14
- const [compiledSQL, setCompiledSQL] = useState('');
15
14
  const [checked, setChecked] = useState(false);
16
15
  const nodeTags = node?.tags.map(tag => <div>{tag}</div>);
17
- const djClient = useContext(DJClientContext).DataJunctionAPI;
18
- useEffect(() => {
19
- const fetchData = async () => {
20
- const data = await djClient.compiledSql(node.name);
21
- if (data.sql) {
22
- setCompiledSQL(data.sql);
23
- } else {
24
- setCompiledSQL('/* Ran into an issue while generating compiled SQL */');
25
- }
26
- };
27
- fetchData().catch(console.error);
28
- }, [node, djClient]);
29
16
  function toggle(value) {
30
17
  return !value;
31
18
  }
@@ -38,18 +25,8 @@ export default function NodeInfoTab({ node }) {
38
25
  }}
39
26
  >
40
27
  <h6 className="mb-0 w-100">Query</h6>
41
- {['metric', 'dimension', 'transform'].indexOf(node?.type) > -1 ? (
42
- <ToggleSwitch
43
- id="toggleSwitch"
44
- checked={checked}
45
- onChange={() => setChecked(toggle)}
46
- toggleName="Show Compiled SQL"
47
- />
48
- ) : (
49
- <></>
50
- )}
51
28
  <SyntaxHighlighter language="sql" style={foundation}>
52
- {checked ? compiledSQL : node?.query}
29
+ {node?.query}
53
30
  </SyntaxHighlighter>
54
31
  </div>
55
32
  </div>
@@ -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;
@@ -3,7 +3,6 @@ import Select from 'react-select';
3
3
  import DJClientContext from '../../providers/djclient';
4
4
  import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
5
5
  import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
6
- import { format } from 'sql-formatter';
7
6
 
8
7
  const NodeSQLTab = djNode => {
9
8
  const djClient = useContext(DJClientContext).DataJunctionAPI;
@@ -12,6 +12,7 @@ import NodeSQLTab from './NodeSQLTab';
12
12
  import NodeMaterializationTab from './NodeMaterializationTab';
13
13
  import ClientCodePopover from './ClientCodePopover';
14
14
  import NodesWithDimension from './NodesWithDimension';
15
+ import NodeColumnLineage from './NodeLineageTab';
15
16
 
16
17
  export function NodePage() {
17
18
  const djClient = useContext(DJClientContext).DataJunctionAPI;
@@ -95,6 +96,11 @@ export function NodePage() {
95
96
  name: 'Linked Nodes',
96
97
  display: node?.type === 'dimension',
97
98
  },
99
+ {
100
+ id: 7,
101
+ name: 'Lineage',
102
+ display: node?.type === 'metric',
103
+ },
98
104
  ];
99
105
  };
100
106
 
@@ -124,6 +130,9 @@ export function NodePage() {
124
130
  case 6:
125
131
  tabToDisplay = <NodesWithDimension node={node} djClient={djClient} />;
126
132
  break;
133
+ case 7:
134
+ tabToDisplay = <NodeColumnLineage djNode={node} djClient={djClient} />;
135
+ break;
127
136
  default:
128
137
  tabToDisplay = <NodeInfoTab node={node} />;
129
138
  }
@@ -146,6 +155,17 @@ export function NodePage() {
146
155
  </span>
147
156
  </h3>
148
157
  <ClientCodePopover code={node?.createNodeClientCode} />
158
+ <div>
159
+ <a href={'/nodes/' + node?.name} className="link-table">
160
+ {node?.name}
161
+ </a>
162
+ <span
163
+ className="rounded-pill badge bg-secondary-soft"
164
+ style={{ marginLeft: '0.5rem' }}
165
+ >
166
+ {node?.version}
167
+ </span>
168
+ </div>
149
169
  <div className="align-items-center row">
150
170
  {tabsList(node).map(buildTabs)}
151
171
  </div>
@@ -34,7 +34,13 @@ export function Root() {
34
34
  </span>
35
35
  <span className="menu-link">
36
36
  <span className="menu-title">
37
- <a href="/">Help</a>
37
+ <a
38
+ href="https://www.datajunction.io"
39
+ target="_blank"
40
+ rel="noreferrer"
41
+ >
42
+ Docs
43
+ </a>
38
44
  </span>
39
45
  </span>
40
46
  </div>
@@ -4,7 +4,6 @@ import { DataJunctionAPI } from '../../services/DJService';
4
4
  import DJClientContext from '../../providers/djclient';
5
5
  import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
6
6
  import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
7
- import { format } from 'sql-formatter';
8
7
  import Select from 'react-select';
9
8
  import QueryInfo from '../../components/QueryInfo';
10
9
 
@@ -36,6 +36,13 @@ export const DataJunctionAPI = {
36
36
  return data;
37
37
  },
38
38
 
39
+ node_lineage: async function (name) {
40
+ const data = await (
41
+ await fetch(DJ_URL + '/nodes/' + name + '/lineage/')
42
+ ).json();
43
+ return data;
44
+ },
45
+
39
46
  metric: async function (name) {
40
47
  const data = await (await fetch(DJ_URL + '/metrics/' + name + '/')).json();
41
48
  return data;
package/webpack.config.js CHANGED
@@ -99,6 +99,7 @@ module.exports = {
99
99
  plugins: [
100
100
  new HtmlWebpackPlugin({
101
101
  template: path.resolve(__dirname, 'public', 'index.html'),
102
+ favicon: path.resolve(__dirname, 'public', 'favicon.ico'),
102
103
  }),
103
104
  new webpack.DefinePlugin({
104
105
  'process.env': JSON.stringify(process.env),