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

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.
Files changed (48) hide show
  1. package/package.json +4 -1
  2. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +18 -16
  3. package/src/app/components/NamespaceHeader.jsx +4 -13
  4. package/src/app/components/QueryInfo.jsx +109 -0
  5. package/src/app/components/ToggleSwitch.jsx +17 -0
  6. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
  7. package/src/app/components/djgraph/Collapse.jsx +46 -0
  8. package/src/app/components/djgraph/DJNode.jsx +56 -80
  9. package/src/app/components/djgraph/DJNodeColumns.jsx +68 -0
  10. package/src/app/components/djgraph/DJNodeDimensions.jsx +69 -0
  11. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +82 -43
  12. package/src/app/icons/CollapsedIcon.jsx +15 -0
  13. package/src/app/icons/ExpandedIcon.jsx +15 -0
  14. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  15. package/src/app/icons/InvalidIcon.jsx +14 -0
  16. package/src/app/icons/PythonIcon.jsx +52 -0
  17. package/src/app/icons/TableIcon.jsx +14 -0
  18. package/src/app/icons/ValidIcon.jsx +14 -0
  19. package/src/app/index.tsx +3 -2
  20. package/src/app/pages/NamespacePage/Explorer.jsx +57 -0
  21. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +24 -5
  22. package/src/app/pages/NamespacePage/index.jsx +78 -10
  23. package/src/app/pages/NodePage/ClientCodePopover.jsx +30 -0
  24. package/src/app/pages/NodePage/NodeColumnTab.jsx +36 -20
  25. package/src/app/pages/NodePage/NodeGraphTab.jsx +45 -17
  26. package/src/app/pages/NodePage/NodeHistory.jsx +71 -0
  27. package/src/app/pages/NodePage/NodeInfoTab.jsx +132 -49
  28. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +151 -0
  29. package/src/app/pages/NodePage/NodeSQLTab.jsx +100 -0
  30. package/src/app/pages/NodePage/NodeStatus.jsx +14 -20
  31. package/src/app/pages/NodePage/index.jsx +43 -8
  32. package/src/app/pages/Root/index.tsx +5 -0
  33. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  34. package/src/app/pages/SQLBuilderPage/index.jsx +317 -0
  35. package/src/app/services/DJService.js +125 -1
  36. package/src/styles/dag.css +111 -5
  37. package/src/styles/index.css +328 -22
  38. package/webpack.config.js +4 -5
  39. package/.babelrc +0 -4
  40. package/.env.local +0 -4
  41. package/.env.production +0 -1
  42. package/.vscode/extensions.json +0 -7
  43. package/.vscode/launch.json +0 -15
  44. package/.vscode/settings.json +0 -25
  45. package/Dockerfile +0 -7
  46. package/src/app/components/DashboardItem.jsx +0 -29
  47. package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
  48. package/src/app/pages/ListNamespacesPage/index.jsx +0 -60
@@ -2,24 +2,41 @@
2
2
 
3
3
  exports[`<DJNode /> should render and match the snapshot 1`] = `
4
4
  <React.Fragment>
5
- <Memo(Handle)
6
- position="left"
7
- style={
8
- Object {
9
- "backgroundColor": "#ccc",
10
- }
11
- }
12
- type="target"
13
- />
14
5
  <div
15
- className="dj-node__full"
16
- style={
17
- Object {
18
- "backgroundColor": "#7EB46150",
19
- "color": "#7EB461",
20
- }
21
- }
6
+ className="dj-node__full node_type__source"
22
7
  >
8
+ <div
9
+ style={
10
+ Object {
11
+ "display": "flex",
12
+ "flexDirection": "column",
13
+ "height": "100%",
14
+ "justifyContent": "space-between",
15
+ "position": "absolute",
16
+ "top": "50%",
17
+ }
18
+ }
19
+ >
20
+ <Memo(Handle)
21
+ id="shared.dimensions.accounts"
22
+ position="left"
23
+ style={
24
+ Object {
25
+ "background": "transparent",
26
+ "border": "4px solid transparent",
27
+ "borderRadius": "12px",
28
+ "cursor": "pointer",
29
+ "height": "12px",
30
+ "left": 0,
31
+ "position": "absolute",
32
+ "top": "0px",
33
+ "transform": "translate(-100%, -50%)",
34
+ "width": "12px",
35
+ }
36
+ }
37
+ type="target"
38
+ />
39
+ </div>
23
40
  <div
24
41
  className="dj-node__header"
25
42
  >
@@ -37,37 +54,59 @@ exports[`<DJNode /> should render and match the snapshot 1`] = `
37
54
  </b>
38
55
  :
39
56
 
57
+ <a
58
+ href="/nodes/shared.dimensions.accounts"
59
+ />
40
60
  <Collapse
41
61
  collapsed={true}
62
+ data={
63
+ Object {
64
+ "column_names": Array [
65
+ "a",
66
+ ],
67
+ "name": "shared.dimensions.accounts",
68
+ "primary_key": Array [
69
+ "id",
70
+ ],
71
+ "type": "source",
72
+ }
73
+ }
42
74
  text="columns"
43
- >
44
- <div
45
- className="dj-node__metadata"
46
- >
47
- <tr>
48
- <td>
49
- <React.Fragment />
50
- </td>
51
- <td
52
- style={
53
- Object {
54
- "textAlign": "right",
55
- }
56
- }
57
- />
58
- </tr>
59
- </div>
60
- </Collapse>
75
+ />
61
76
  </div>
62
- </div>
63
- <Memo(Handle)
64
- position="right"
65
- style={
66
- Object {
67
- "backgroundColor": "#ccc",
77
+ <div
78
+ style={
79
+ Object {
80
+ "display": "flex",
81
+ "flexDirection": "column",
82
+ "height": "100%",
83
+ "justifyContent": "space-between",
84
+ "position": "absolute",
85
+ "right": 0,
86
+ "top": "50%",
87
+ }
68
88
  }
69
- }
70
- type="source"
71
- />
89
+ >
90
+ <Memo(Handle)
91
+ id="shared.dimensions.accounts"
92
+ position="right"
93
+ style={
94
+ Object {
95
+ "background": "transparent",
96
+ "border": "4px solid transparent",
97
+ "borderRadius": "12px",
98
+ "cursor": "pointer",
99
+ "height": "12px",
100
+ "left": 0,
101
+ "position": "absolute",
102
+ "top": "0px",
103
+ "transform": "translate(-90%, -50%)",
104
+ "width": "12px",
105
+ }
106
+ }
107
+ type="source"
108
+ />
109
+ </div>
110
+ </div>
72
111
  </React.Fragment>
73
112
  `;
@@ -0,0 +1,15 @@
1
+ const CollapsedIcon = props => (
2
+ <svg
3
+ stroke="currentColor"
4
+ fill="currentColor"
5
+ strokeWidth="0"
6
+ viewBox="0 0 512 512"
7
+ height="1em"
8
+ width="1em"
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ >
11
+ <path d="M48 256c0 114.9 93.1 208 208 208s208-93.1 208-208S370.9 48 256 48 48 141.1 48 256zm244.5 0l-81.9-81.1c-7.5-7.5-7.5-19.8 0-27.3s19.8-7.5 27.3 0l95.4 95.7c7.3 7.3 7.5 19.1.6 26.6l-94 94.3c-3.8 3.8-8.7 5.7-13.7 5.7-4.9 0-9.9-1.9-13.6-5.6-7.5-7.5-7.6-19.7 0-27.3l79.9-81z"></path>
12
+ </svg>
13
+ );
14
+
15
+ export default CollapsedIcon;
@@ -0,0 +1,15 @@
1
+ const ExpandedIcon = props => (
2
+ <svg
3
+ stroke="currentColor"
4
+ fill="currentColor"
5
+ strokeWidth="0"
6
+ viewBox="0 0 512 512"
7
+ height="1em"
8
+ width="1em"
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ >
11
+ <path d="M48 256c0 114.9 93.1 208 208 208s208-93.1 208-208S370.9 48 256 48 48 141.1 48 256zm289.1-43.4c7.5-7.5 19.8-7.5 27.3 0 3.8 3.8 5.6 8.7 5.6 13.6s-1.9 9.9-5.7 13.7l-94.3 94c-7.6 6.9-19.3 6.7-26.6-.6l-95.7-95.4c-7.5-7.5-7.6-19.7 0-27.3 7.5-7.5 19.7-7.6 27.3 0l81.1 81.9 81-79.9z"></path>
12
+ </svg>
13
+ );
14
+
15
+ export default ExpandedIcon;
@@ -0,0 +1,15 @@
1
+ const HorizontalHierarchyIcon = props => (
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="16"
5
+ height="16"
6
+ fill="currentColor"
7
+ className="bi bi-house-door-fill"
8
+ viewBox="0 0 16 16"
9
+ style={{ paddingBottom: '0.2rem' }}
10
+ >
11
+ <path d="M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z" />
12
+ </svg>
13
+ );
14
+
15
+ export default HorizontalHierarchyIcon;
@@ -0,0 +1,14 @@
1
+ const InvalidIcon = props => (
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="25"
5
+ height="25"
6
+ fill="currentColor"
7
+ className="bi bi-x-circle-fill"
8
+ viewBox="0 0 16 16"
9
+ >
10
+ <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" />
11
+ </svg>
12
+ );
13
+
14
+ export default InvalidIcon;
@@ -0,0 +1,52 @@
1
+ const PythonIcon = props => (
2
+ <svg
3
+ width="45px"
4
+ height="45px"
5
+ viewBox="0 0 64 64"
6
+ fill="none"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ >
9
+ <g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
10
+ <g
11
+ id="SVGRepo_tracerCarrier"
12
+ strokeLinecap="round"
13
+ strokeLinejoin="round"
14
+ ></g>
15
+ <g id="SVGRepo_iconCarrier">
16
+ <path
17
+ d="M31.885 16c-8.124 0-7.617 3.523-7.617 3.523l.01 3.65h7.752v1.095H21.197S16 23.678 16 31.876c0 8.196 4.537 7.906 4.537 7.906h2.708v-3.804s-.146-4.537 4.465-4.537h7.688s4.32.07 4.32-4.175v-7.019S40.374 16 31.885 16zm-4.275 2.454c.771 0 1.395.624 1.395 1.395s-.624 1.395-1.395 1.395a1.393 1.393 0 0 1-1.395-1.395c0-.771.624-1.395 1.395-1.395z"
18
+ fill="url(#a)"
19
+ ></path>
20
+ <path
21
+ d="M32.115 47.833c8.124 0 7.617-3.523 7.617-3.523l-.01-3.65H31.97v-1.095h10.832S48 40.155 48 31.958c0-8.197-4.537-7.906-4.537-7.906h-2.708v3.803s.146 4.537-4.465 4.537h-7.688s-4.32-.07-4.32 4.175v7.019s-.656 4.247 7.833 4.247zm4.275-2.454a1.393 1.393 0 0 1-1.395-1.395c0-.77.624-1.394 1.395-1.394s1.395.623 1.395 1.394c0 .772-.624 1.395-1.395 1.395z"
22
+ fill="url(#b)"
23
+ ></path>
24
+ <defs>
25
+ <linearGradient
26
+ id="a"
27
+ x1="19.075"
28
+ y1="18.782"
29
+ x2="34.898"
30
+ y2="34.658"
31
+ gradientUnits="userSpaceOnUse"
32
+ >
33
+ <stop stopColor="#387EB8"></stop>
34
+ <stop offset="1" stopColor="#366994"></stop>
35
+ </linearGradient>
36
+ <linearGradient
37
+ id="b"
38
+ x1="28.809"
39
+ y1="28.882"
40
+ x2="45.803"
41
+ y2="45.163"
42
+ gradientUnits="userSpaceOnUse"
43
+ >
44
+ <stop stopColor="#FFE052"></stop>
45
+ <stop offset="1" stopColor="#FFC331"></stop>
46
+ </linearGradient>
47
+ </defs>
48
+ </g>
49
+ </svg>
50
+ );
51
+
52
+ export default PythonIcon;
@@ -0,0 +1,14 @@
1
+ const TableIcon = props => (
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="16"
5
+ height="16"
6
+ fill="currentColor"
7
+ className="bi bi-table"
8
+ viewBox="0 0 16 16"
9
+ >
10
+ <path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z" />
11
+ </svg>
12
+ );
13
+
14
+ export default TableIcon;
@@ -0,0 +1,14 @@
1
+ const ValidIcon = props => (
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="25"
5
+ height="25"
6
+ fill="currentColor"
7
+ className="bi bi-check-circle-fill"
8
+ viewBox="0 0 16 16"
9
+ >
10
+ <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" />
11
+ </svg>
12
+ );
13
+
14
+ export default ValidIcon;
package/src/app/index.tsx CHANGED
@@ -9,9 +9,9 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom';
9
9
 
10
10
  import { NamespacePage } from './pages/NamespacePage/Loadable';
11
11
  import { NodePage } from './pages/NodePage/Loadable';
12
+ import { SQLBuilderPage } from './pages/SQLBuilderPage/Loadable';
12
13
  import { NotFoundPage } from './pages/NotFoundPage/Loadable';
13
14
  import { Root } from './pages/Root/Loadable';
14
- import { ListNamespacesPage } from './pages/ListNamespacesPage';
15
15
  import DJClientContext from './providers/djclient';
16
16
  import { DataJunctionAPI } from './services/DJService';
17
17
 
@@ -38,7 +38,7 @@ export function App() {
38
38
  <Route path=":name" element={<NodePage />} />
39
39
  </Route>
40
40
 
41
- <Route path="/" element={<ListNamespacesPage />} key="index" />
41
+ <Route path="/" element={<NamespacePage />} key="index" />
42
42
  <Route path="namespaces">
43
43
  <Route
44
44
  path=":namespace"
@@ -46,6 +46,7 @@ export function App() {
46
46
  key="namespaces"
47
47
  />
48
48
  </Route>
49
+ <Route path="sql" key="sql" element={<SQLBuilderPage />} />
49
50
  </>
50
51
  }
51
52
  />
@@ -0,0 +1,57 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import CollapsedIcon from '../../icons/CollapsedIcon';
3
+ import ExpandedIcon from '../../icons/ExpandedIcon';
4
+
5
+ const Explorer = ({ item = [], current }) => {
6
+ const [items, setItems] = useState([]);
7
+ const [expand, setExpand] = useState(false);
8
+ const [highlight, setHighlight] = useState(false);
9
+
10
+ useEffect(() => {
11
+ setItems(item);
12
+ setHighlight(current);
13
+ if (current === undefined || current?.startsWith(item.path)) {
14
+ setExpand(true);
15
+ } else setExpand(false);
16
+ }, [current, item]);
17
+
18
+ const handleClickOnParent = e => {
19
+ e.stopPropagation();
20
+ setExpand(prev => {
21
+ return !prev;
22
+ });
23
+ };
24
+
25
+ return (
26
+ <>
27
+ <div
28
+ className={`select-name ${
29
+ highlight === items.path ? 'select-name-highlight' : ''
30
+ }`}
31
+ onClick={handleClickOnParent}
32
+ >
33
+ {items.children && items.children.length > 0 ? (
34
+ <span>{!expand ? <CollapsedIcon /> : <ExpandedIcon />} </span>
35
+ ) : null}
36
+ <a href={`/namespaces/${items.path}`}>{items.namespace}</a>{' '}
37
+ </div>
38
+ {items.children
39
+ ? items.children.map((item, index) => (
40
+ <div
41
+ style={{
42
+ paddingLeft: '1.4rem',
43
+ marginLeft: '1rem',
44
+ borderLeft: '1px solid rgb(218 233 255)',
45
+ }}
46
+ >
47
+ <div className={`${expand ? '' : 'inactive'}`}>
48
+ <Explorer item={item} current={highlight} />
49
+ </div>
50
+ </div>
51
+ ))
52
+ : null}
53
+ </>
54
+ );
55
+ };
56
+
57
+ export default Explorer;
@@ -4,7 +4,6 @@ exports[`<NamespacePage /> should render and match the snapshot 1`] = `
4
4
  <div
5
5
  className="mid"
6
6
  >
7
- <NamespaceHeader />
8
7
  <div
9
8
  className="card"
10
9
  >
@@ -12,19 +11,33 @@ exports[`<NamespacePage /> should render and match the snapshot 1`] = `
12
11
  className="card-header"
13
12
  >
14
13
  <h2>
15
- Nodes
14
+ Explore
16
15
  </h2>
17
16
  <div
18
17
  className="table-responsive"
19
18
  >
19
+ <div
20
+ className="sidebar"
21
+ >
22
+ <span
23
+ style={
24
+ Object {
25
+ "color": "#95aac9",
26
+ "fontSize": "0.8125rem",
27
+ "fontWeight": "600",
28
+ "padding": "1rem 1rem 1rem 0",
29
+ "textTransform": "uppercase",
30
+ }
31
+ }
32
+ >
33
+ Namespaces
34
+ </span>
35
+ </div>
20
36
  <table
21
37
  className="card-table table"
22
38
  >
23
39
  <thead>
24
40
  <tr>
25
- <th>
26
- Namespace
27
- </th>
28
41
  <th>
29
42
  Name
30
43
  </th>
@@ -37,6 +50,12 @@ exports[`<NamespacePage /> should render and match the snapshot 1`] = `
37
50
  <th>
38
51
  Mode
39
52
  </th>
53
+ <th>
54
+ Tags
55
+ </th>
56
+ <th>
57
+ Last Updated
58
+ </th>
40
59
  </tr>
41
60
  </thead>
42
61
  <tbody />
@@ -1,21 +1,63 @@
1
1
  import * as React from 'react';
2
2
  import { useParams } from 'react-router-dom';
3
3
  import { useContext, useEffect, useState } from 'react';
4
- import NamespaceHeader from '../../components/NamespaceHeader';
5
4
  import NodeStatus from '../NodePage/NodeStatus';
6
5
  import DJClientContext from '../../providers/djclient';
6
+ import Explorer from '../NamespacePage/Explorer';
7
7
 
8
8
  export function NamespacePage() {
9
9
  const djClient = useContext(DJClientContext).DataJunctionAPI;
10
- const { namespace } = useParams();
10
+ var { namespace } = useParams();
11
11
 
12
12
  const [state, setState] = useState({
13
13
  namespace: namespace,
14
14
  nodes: [],
15
15
  });
16
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
+
17
56
  useEffect(() => {
18
57
  const fetchData = async () => {
58
+ if (namespace === undefined && namespaceHierarchy !== undefined) {
59
+ namespace = namespaceHierarchy.children[0].path;
60
+ }
19
61
  const djNodes = await djClient.namespace(namespace);
20
62
  const nodes = djNodes.map(node => {
21
63
  return djClient.node(node);
@@ -27,13 +69,10 @@ export function NamespacePage() {
27
69
  });
28
70
  };
29
71
  fetchData().catch(console.error);
30
- }, [djClient, namespace]);
72
+ }, [djClient, namespace, namespaceHierarchy]);
31
73
 
32
74
  const nodesList = state.nodes.map(node => (
33
75
  <tr>
34
- <td>
35
- <a href={'/namespaces/' + node.namespace}>{node.namespace}</a>
36
- </td>
37
76
  <td>
38
77
  <a href={'/nodes/' + node.name} className="link-table">
39
78
  {node.display_name}
@@ -56,25 +95,54 @@ export function NamespacePage() {
56
95
  <td>
57
96
  <span className="status">{node.mode}</span>
58
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>
59
106
  </tr>
60
107
  ));
61
108
 
62
- // @ts-ignore
63
109
  return (
64
110
  <div className="mid">
65
- <NamespaceHeader namespace={namespace} />
66
111
  <div className="card">
67
112
  <div className="card-header">
68
- <h2>Nodes</h2>
113
+ <h2>Explore</h2>
69
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>
70
137
  <table className="card-table table">
71
138
  <thead>
72
139
  <tr>
73
- <th>Namespace</th>
74
140
  <th>Name</th>
75
141
  <th>Type</th>
76
142
  <th>Status</th>
77
143
  <th>Mode</th>
144
+ <th>Tags</th>
145
+ <th>Last Updated</th>
78
146
  </tr>
79
147
  </thead>
80
148
  <tbody>{nodesList}</tbody>
@@ -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
+ }
@@ -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 class NodeColumnTab extends Component {
4
- columnList = node => {
5
- return node.columns.map(col => (
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>{col.dimension ? col.dimension.name : ''}</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
- render() {
30
- return (
31
- <div className="table-responsive">
32
- <table className="card-inner-table table">
33
- <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
34
- <th className="text-start">Column</th>
35
- <th>Type</th>
36
- <th>Dimension</th>
37
- <th>Attributes</th>
38
- </thead>
39
- {this.columnList(this.props.node)}
40
- </table>
41
- </div>
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
  }