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.
@@ -13,6 +13,113 @@ export default function NodeHistory({ node, djClient }) {
13
13
  };
14
14
  fetchData().catch(console.error);
15
15
  }, [djClient, node]);
16
+
17
+ const eventData = event => {
18
+ console.log('event', event);
19
+ if (
20
+ event.activity_type === 'set_attribute' &&
21
+ event.entity_type === 'column_attribute'
22
+ ) {
23
+ return event.details.attributes
24
+ .map(attr => (
25
+ <div>
26
+ Set{' '}
27
+ <span className={`badge partition_value`}>{attr.column_name}</span>{' '}
28
+ as{' '}
29
+ <span className={`badge partition_value_highlight`}>
30
+ {attr.attribute_type_name}
31
+ </span>
32
+ </div>
33
+ ))
34
+ .reduce((prev, curr) => [prev, <br />, curr]);
35
+ }
36
+ if (event.activity_type === 'create' && event.entity_type === 'link') {
37
+ return (
38
+ <div>
39
+ Linked{' '}
40
+ <span className={`badge partition_value`}>
41
+ {event.details.column}
42
+ </span>{' '}
43
+ to
44
+ <span className={`badge partition_value_highlight`}>
45
+ {event.details.dimension}
46
+ </span>{' '}
47
+ via
48
+ <span className={`badge partition_value`}>
49
+ {event.details.dimension_column}
50
+ </span>
51
+ </div>
52
+ );
53
+ }
54
+ if (
55
+ event.activity_type === 'create' &&
56
+ event.entity_type === 'materialization'
57
+ ) {
58
+ return (
59
+ <div>
60
+ Initialized materialization{' '}
61
+ <span className={`badge partition_value`}>
62
+ {event.details.materialization}
63
+ </span>
64
+ </div>
65
+ );
66
+ }
67
+ if (
68
+ event.activity_type === 'create' &&
69
+ event.entity_type === 'availability'
70
+ ) {
71
+ return (
72
+ <div>
73
+ Materialized at{' '}
74
+ <span className={`badge partition_value_highlight`}>
75
+ {event.post.catalog}.{event.post.schema_}.{event.post.table}
76
+ </span>
77
+ from{' '}
78
+ <span className={`badge partition_value`}>
79
+ {event.post.min_temporal_partition}
80
+ </span>{' '}
81
+ to
82
+ <span className={`badge partition_value`}>
83
+ {event.post.max_temporal_partition}
84
+ </span>
85
+ </div>
86
+ );
87
+ }
88
+ if (
89
+ event.activity_type === 'status_change' &&
90
+ event.entity_type === 'node'
91
+ ) {
92
+ const expr = (
93
+ <div>
94
+ Caused by a change in upstream{' '}
95
+ <a href={`/nodes/${event.details['upstream_node']}`}>
96
+ {event.details['upstream_node']}
97
+ </a>
98
+ </div>
99
+ );
100
+ return (
101
+ <div>
102
+ Status changed from{' '}
103
+ <span className={`status__${event.pre['status']}`}>
104
+ {event.pre['status']}
105
+ </span>{' '}
106
+ to{' '}
107
+ <span className={`status__${event.post['status']}`}>
108
+ {event.post['status']}
109
+ </span>{' '}
110
+ {event.details['upstream_node'] !== undefined ? expr : ''}
111
+ </div>
112
+ );
113
+ }
114
+ return (
115
+ <div>
116
+ {JSON.stringify(event.details) === '{}'
117
+ ? ''
118
+ : JSON.stringify(event.details)}
119
+ </div>
120
+ );
121
+ };
122
+
16
123
  const tableData = history => {
17
124
  return history.map(event => (
18
125
  <tr>
@@ -27,6 +134,7 @@ export default function NodeHistory({ node, djClient }) {
27
134
  <td>{event.entity_name}</td>
28
135
  <td>{event.user ? event.user : 'unknown'}</td>
29
136
  <td>{event.created_at}</td>
137
+ <td>{eventData(event)}</td>
30
138
  </tr>
31
139
  ));
32
140
  };
@@ -45,7 +153,7 @@ export default function NodeHistory({ node, djClient }) {
45
153
  ));
46
154
  };
47
155
  return (
48
- <div className="table-responsive">
156
+ <div className="table-vertical">
49
157
  <table className="card-inner-table table">
50
158
  <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
51
159
  <th className="text-start">Version</th>
@@ -63,6 +171,7 @@ export default function NodeHistory({ node, djClient }) {
63
171
  <th>Name</th>
64
172
  <th>User</th>
65
173
  <th>Timestamp</th>
174
+ <th>Details</th>
66
175
  </thead>
67
176
  {tableData(history)}
68
177
  </table>
@@ -2,7 +2,6 @@ import { useState, useContext, useEffect } from 'react';
2
2
  import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
3
3
  import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
4
4
  import sql from 'react-syntax-highlighter/dist/esm/languages/hljs/sql';
5
- import { format } from 'sql-formatter';
6
5
  import NodeStatus from './NodeStatus';
7
6
  import ListGroupItem from '../../components/ListGroupItem';
8
7
  import ToggleSwitch from '../../components/ToggleSwitch';
@@ -18,7 +17,7 @@ export default function NodeInfoTab({ node }) {
18
17
  const djClient = useContext(DJClientContext).DataJunctionAPI;
19
18
  useEffect(() => {
20
19
  const fetchData = async () => {
21
- const data = await djClient.compiledSql(node.name);
20
+ const data = djClient.compiledSql(node.name);
22
21
  if (data.sql) {
23
22
  setCompiledSQL(data.sql);
24
23
  } else {
@@ -50,23 +49,7 @@ export default function NodeInfoTab({ node }) {
50
49
  <></>
51
50
  )}
52
51
  <SyntaxHighlighter language="sql" style={foundation}>
53
- {checked
54
- ? format(compiledSQL, {
55
- language: 'spark',
56
- tabWidth: 2,
57
- keywordCase: 'upper',
58
- denseOperators: true,
59
- logicalOperatorNewline: 'before',
60
- expressionWidth: 10,
61
- })
62
- : format(node?.query, {
63
- language: 'spark',
64
- tabWidth: 2,
65
- keywordCase: 'upper',
66
- denseOperators: true,
67
- logicalOperatorNewline: 'before',
68
- expressionWidth: 10,
69
- })}
52
+ {checked ? compiledSQL : node?.query}
70
53
  </SyntaxHighlighter>
71
54
  </div>
72
55
  </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;
@@ -82,14 +81,7 @@ const NodeSQLTab = djNode => {
82
81
  >
83
82
  <h6 className="mb-0 w-100">Query</h6>
84
83
  <SyntaxHighlighter language="sql" style={foundation}>
85
- {format(query, {
86
- language: 'spark',
87
- tabWidth: 2,
88
- keywordCase: 'upper',
89
- denseOperators: true,
90
- logicalOperatorNewline: 'before',
91
- expressionWidth: 10,
92
- })}
84
+ {query}
93
85
  </SyntaxHighlighter>
94
86
  </div>
95
87
  </div>
@@ -0,0 +1,40 @@
1
+ import { useEffect, useState } from 'react';
2
+ import * as React from 'react';
3
+
4
+ export default function NodesWithDimension({ node, djClient }) {
5
+ const [availableNodes, setAvailableNodes] = useState([]);
6
+
7
+ useEffect(() => {
8
+ const fetchData = async () => {
9
+ const data = await djClient.nodesWithDimension(node.name);
10
+ setAvailableNodes(data);
11
+ };
12
+ fetchData().catch(console.error);
13
+ }, [djClient, node]);
14
+ return (
15
+ <div className="table-responsive">
16
+ <table className="card-inner-table table">
17
+ <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
18
+ <th className="text-start">Name</th>
19
+ <th>Type</th>
20
+ </thead>
21
+ <tbody>
22
+ {availableNodes.map(node => (
23
+ <tr>
24
+ <td>
25
+ <a href={`/nodes/${node.name}`}>{node.display_name}</a>
26
+ </td>
27
+ <td>
28
+ <span
29
+ className={'node_type__' + node.type + ' badge node_type'}
30
+ >
31
+ {node.type}
32
+ </span>
33
+ </td>
34
+ </tr>
35
+ ))}
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+ );
40
+ }
@@ -11,6 +11,8 @@ import DJClientContext from '../../providers/djclient';
11
11
  import NodeSQLTab from './NodeSQLTab';
12
12
  import NodeMaterializationTab from './NodeMaterializationTab';
13
13
  import ClientCodePopover from './ClientCodePopover';
14
+ import NodesWithDimension from './NodesWithDimension';
15
+ import NodeColumnLineage from './NodeLineageTab';
14
16
 
15
17
  export function NodePage() {
16
18
  const djClient = useContext(DJClientContext).DataJunctionAPI;
@@ -25,7 +27,7 @@ export function NodePage() {
25
27
  };
26
28
 
27
29
  const buildTabs = tab => {
28
- return (
30
+ return tab.display ? (
29
31
  <Tab
30
32
  key={tab.id}
31
33
  id={tab.id}
@@ -33,7 +35,7 @@ export function NodePage() {
33
35
  onClick={onClickTab(tab.id)}
34
36
  selectedTab={state.selectedTab}
35
37
  />
36
- );
38
+ ) : null;
37
39
  };
38
40
 
39
41
  const { name } = useParams();
@@ -57,32 +59,51 @@ export function NodePage() {
57
59
  fetchData().catch(console.error);
58
60
  }, [djClient, name]);
59
61
 
60
- const TabsJson = [
61
- {
62
- id: 0,
63
- name: 'Info',
64
- },
65
- {
66
- id: 1,
67
- name: 'Columns',
68
- },
69
- {
70
- id: 2,
71
- name: 'Graph',
72
- },
73
- {
74
- id: 3,
75
- name: 'History',
76
- },
77
- {
78
- id: 4,
79
- name: 'SQL',
80
- },
81
- {
82
- id: 5,
83
- name: 'Materializations',
84
- },
85
- ];
62
+ const tabsList = node => {
63
+ return [
64
+ {
65
+ id: 0,
66
+ name: 'Info',
67
+ display: true,
68
+ },
69
+ {
70
+ id: 1,
71
+ name: 'Columns',
72
+ display: true,
73
+ },
74
+ {
75
+ id: 2,
76
+ name: 'Graph',
77
+ display: true,
78
+ },
79
+ {
80
+ id: 3,
81
+ name: 'History',
82
+ display: true,
83
+ },
84
+ {
85
+ id: 4,
86
+ name: 'SQL',
87
+ display: node?.type !== 'dimension' && node?.type !== 'source',
88
+ },
89
+ {
90
+ id: 5,
91
+ name: 'Materializations',
92
+ display: node?.type !== 'source',
93
+ },
94
+ {
95
+ id: 6,
96
+ name: 'Linked Nodes',
97
+ display: node?.type === 'dimension',
98
+ },
99
+ {
100
+ id: 7,
101
+ name: 'Lineage',
102
+ display: node?.type === 'metric',
103
+ },
104
+ ];
105
+ };
106
+
86
107
  //
87
108
  //
88
109
  let tabToDisplay = null;
@@ -106,6 +127,12 @@ export function NodePage() {
106
127
  case 5:
107
128
  tabToDisplay = <NodeMaterializationTab node={node} djClient={djClient} />;
108
129
  break;
130
+ case 6:
131
+ tabToDisplay = <NodesWithDimension node={node} djClient={djClient} />;
132
+ break;
133
+ case 7:
134
+ tabToDisplay = <NodeColumnLineage djNode={node} djClient={djClient} />;
135
+ break;
109
136
  default:
110
137
  tabToDisplay = <NodeInfoTab node={node} />;
111
138
  }
@@ -121,12 +148,26 @@ export function NodePage() {
121
148
  style={{ display: 'inline-block' }}
122
149
  >
123
150
  <span className="card-label fw-bold text-gray-800">
124
- {node?.display_name}
151
+ {node?.display_name}{' '}
152
+ <span className={'node_type__' + node?.type + ' badge node_type'}>
153
+ {node?.type}
154
+ </span>
125
155
  </span>
126
156
  </h3>
127
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>
128
169
  <div className="align-items-center row">
129
- {TabsJson.map(buildTabs)}
170
+ {tabsList(node).map(buildTabs)}
130
171
  </div>
131
172
  {tabToDisplay}
132
173
  </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>