datajunction-ui 0.0.1-rc.12 → 0.0.1-rc.14

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/.env CHANGED
@@ -1 +1,2 @@
1
1
  REACT_APP_DJ_URL=http://localhost:8000
2
+ REACT_USE_SSE=true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datajunction-ui",
3
- "version": "0.0.1-rc.12",
3
+ "version": "0.0.1-rc.14",
4
4
  "description": "DataJunction Metrics Platform UI",
5
5
  "module": "src/index.tsx",
6
6
  "repository": {
Binary file
@@ -35,9 +35,12 @@ 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],
39
+ "nodesWithDimension": [Function],
38
40
  "revisions": [Function],
39
41
  "sql": [Function],
40
42
  "sqls": [Function],
43
+ "stream": [Function],
41
44
  "upstreams": [Function],
42
45
  },
43
46
  }
@@ -10,38 +10,6 @@ export default function QueryInfo({
10
10
  started,
11
11
  numRows,
12
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
13
  return (
46
14
  <div className="table-responsive">
47
15
  <table className="card-inner-table table">
@@ -70,11 +38,11 @@ export default function QueryInfo({
70
38
  {engine_version}
71
39
  </span>
72
40
  </td>
73
- <td>{stateIcon}</td>
41
+ <td>{state}</td>
74
42
  <td>{scheduled}</td>
75
43
  <td>{started}</td>
76
44
  <td>
77
- {errors.length ? (
45
+ {errors?.length ? (
78
46
  errors.map(e => (
79
47
  <p>
80
48
  <span className="rounded-pill badge bg-secondary-error">
@@ -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>
@@ -20,7 +20,7 @@ export function NamespacePage() {
20
20
  const hierarchy = [];
21
21
 
22
22
  for (const item of namespaceList) {
23
- const namespaces = item.namespace.split('.');
23
+ const namespaces = item.split('.');
24
24
  let currentLevel = hierarchy;
25
25
 
26
26
  let path = '';
@@ -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;