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

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.
@@ -2,9 +2,9 @@ name: CI/CD
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [master]
5
+ branches: [main]
6
6
  pull_request:
7
- branches: [master]
7
+ branches: [main]
8
8
 
9
9
  jobs:
10
10
  build:
@@ -19,7 +19,7 @@ jobs:
19
19
  uses: actions/checkout@v2
20
20
 
21
21
  - name: Set up Node.js ${{ matrix.node-version }}
22
- uses: actions/setup-node@v3.6.
22
+ uses: actions/setup-node@v3
23
23
  with:
24
24
  node-version: ${{ matrix.node-version }}
25
25
 
package/.prettierignore CHANGED
@@ -1,4 +1,6 @@
1
1
  build/
2
2
  node_modules/
3
3
  package-lock.json
4
- yarn.lock
4
+ yarn.lock
5
+ dist/
6
+ coverage/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datajunction-ui",
3
- "version": "0.0.1-rc.1",
3
+ "version": "0.0.1-rc.10",
4
4
  "description": "DataJunction Metrics Platform UI",
5
5
  "module": "src/index.tsx",
6
6
  "repository": {
@@ -38,14 +38,13 @@
38
38
  "@types/testing-library__jest-dom": "^5.14.5",
39
39
  "@types/webpack": "^5.28.0",
40
40
  "@types/webpack-env": "^1.18.0",
41
+ "babel-loader": "9.1.2",
41
42
  "chalk": "4.1.2",
42
43
  "cross-env": "7.0.3",
44
+ "css-loader": "6.7.3",
43
45
  "dagre": "^0.8.5",
44
- "eslint-config-prettier": "8.5.0",
45
- "eslint-plugin-prettier": "4.2.1",
46
- "eslint-plugin-react-hooks": "4.6.0",
47
- "fbemitter": "3.0.0",
48
- "flux": "4.0.4",
46
+ "datajunction": "0.0.1-rc.0",
47
+ "file-loader": "6.2.0",
49
48
  "fontfaceobserver": "2.3.0",
50
49
  "husky": "8.0.1",
51
50
  "i18next": "21.9.2",
@@ -76,6 +75,7 @@
76
75
  "serve": "14.0.1",
77
76
  "shelljs": "0.8.5",
78
77
  "sql-formatter": "^12.2.0",
78
+ "style-loader": "3.3.2",
79
79
  "stylelint": "14.12.0",
80
80
  "stylelint-config-recommended": "9.0.0",
81
81
  "ts-loader": "9.4.2",
@@ -83,7 +83,8 @@
83
83
  "typescript": "4.6.4",
84
84
  "web-vitals": "2.1.4",
85
85
  "webpack": "5.81.0",
86
- "webpack-cli": "5.0.2"
86
+ "webpack-cli": "5.0.2",
87
+ "webpack-dev-server": "4.13.3"
87
88
  },
88
89
  "scripts": {
89
90
  "webpack-start": "webpack-dev-server --open",
@@ -102,7 +103,8 @@
102
103
  "generate": "plop --plopfile internals/generators/plopfile.ts",
103
104
  "cleanAndSetup": "ts-node ./internals/scripts/clean.ts",
104
105
  "prettify": "prettier --write",
105
- "extract-messages": "i18next-scanner --config=internals/extractMessages/i18next-scanner.config.js"
106
+ "extract-messages": "i18next-scanner --config=internals/extractMessages/i18next-scanner.config.js",
107
+ "prepublishOnly": "webpack --mode=production"
106
108
  },
107
109
  "eslintConfig": {
108
110
  "extends": "react-app"
@@ -150,6 +152,9 @@
150
152
  },
151
153
  "devDependencies": {
152
154
  "@babel/plugin-proposal-class-properties": "7.18.6",
155
+ "eslint-config-prettier": "8.8.0",
156
+ "eslint-plugin-prettier": "4.2.1",
157
+ "eslint-plugin-react-hooks": "4.6.0",
153
158
  "html-webpack-plugin": "5.5.1",
154
159
  "jest": "^29.5.0",
155
160
  "mini-css-extract-plugin": "2.7.5"
@@ -14,30 +14,70 @@ exports[`<App /> should render and match the snapshot 1`] = `
14
14
  name="description"
15
15
  />
16
16
  </Helmet>
17
- <Routes>
18
- <Route
19
- element={<Unknown />}
20
- path="/"
21
- >
22
- <React.Fragment>
23
- <Route
24
- element={<Unknown />}
25
- path="nodes/:name"
26
- />
27
- <Route
28
- element={<ListNamespacesPage />}
29
- path="namespaces"
30
- />
31
- <Route
32
- element={<Unknown />}
33
- path="namespaces/:namespace"
34
- />
35
- </React.Fragment>
36
- </Route>
37
- <Route
38
- element={<Unknown />}
39
- path="*"
40
- />
41
- </Routes>
17
+ <Context.Provider
18
+ value={
19
+ Object {
20
+ "DataJunctionAPI": Object {
21
+ "dag": [Function],
22
+ "downstreams": [Function],
23
+ "lineage": [Function],
24
+ "metric": [Function],
25
+ "namespace": [Function],
26
+ "namespaces": [Function],
27
+ "node": [Function],
28
+ "upstreams": [Function],
29
+ },
30
+ }
31
+ }
32
+ >
33
+ <Routes>
34
+ <Route
35
+ element={<Unknown />}
36
+ path="/"
37
+ >
38
+ <React.Fragment>
39
+ <Route
40
+ path="nodes"
41
+ >
42
+ <Route
43
+ element={<NodePage />}
44
+ path=":name"
45
+ />
46
+ </Route>
47
+ <Route
48
+ element={
49
+ <ListNamespacesPage
50
+ djClient={
51
+ Object {
52
+ "dag": [Function],
53
+ "downstreams": [Function],
54
+ "lineage": [Function],
55
+ "metric": [Function],
56
+ "namespace": [Function],
57
+ "namespaces": [Function],
58
+ "node": [Function],
59
+ "upstreams": [Function],
60
+ }
61
+ }
62
+ />
63
+ }
64
+ path="/"
65
+ />
66
+ <Route
67
+ path="namespaces"
68
+ >
69
+ <Route
70
+ element={<NamespacePage />}
71
+ path=":namespace"
72
+ />
73
+ </Route>
74
+ </React.Fragment>
75
+ </Route>
76
+ <Route
77
+ element={<Unknown />}
78
+ path="*"
79
+ />
80
+ </Routes>
81
+ </Context.Provider>
42
82
  </BrowserRouter>
43
83
  `;
@@ -0,0 +1,29 @@
1
+ import { Component } from 'react';
2
+
3
+ export default class DashboardItem extends Component {
4
+ render() {
5
+ const { label, value } = this.props;
6
+ return (
7
+ // <div className="col-xl col-md-6 col-12">
8
+ // <div className="card">
9
+ // <div className="card-body">
10
+ // <div className="align-items-center row">
11
+ // <div className="col"><h6 className="text-uppercase text-muted mb-2">{ label }</h6><span
12
+ // className="h2 mb-0">{ value }</span><span className="mt-n1 ms-2 badge bg-success-soft">+3.5%</span></div>
13
+ // <div className="col-auto">
14
+ // <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
15
+ // stroke-linecap="round" stroke-linejoin="round" className="feather feather-dollar-sign text-muted">
16
+ // <g>
17
+ // <line x1="12" y1="1" x2="12" y2="23"></line>
18
+ // <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
19
+ // </g>
20
+ // </svg>
21
+ // </div>
22
+ // </div>
23
+ // </div>
24
+ // </div>
25
+ // </div>
26
+ <div></div>
27
+ );
28
+ }
29
+ }
@@ -7,15 +7,8 @@ export default class Tab extends Component {
7
7
  <div className={selectedTab === id ? 'col active' : 'col'}>
8
8
  <div className="header-tabs nav-overflow nav nav-tabs">
9
9
  <div className="nav-item">
10
- <button
11
- id={id}
12
- role="button"
13
- className="nav-link"
14
- tabIndex="0"
15
- onClick={onClick}
16
- >
10
+ <button id={id} className="nav-link" tabIndex="0" onClick={onClick}>
17
11
  {this.props.name}
18
- {/*<span className="rounded-pill badge bg-secondary-soft">823</span>*/}
19
12
  </button>
20
13
  </div>
21
14
  </div>
@@ -61,12 +61,12 @@ export function DJNode({ id, data }) {
61
61
  <td style={{ textAlign: 'right' }}>{col.type}</td>
62
62
  </tr>
63
63
  ));
64
- const dimensionsRenderer = data =>
65
- data.dimensions.map(dim => (
66
- <tr>
67
- <td>{dim}</td>
68
- </tr>
69
- ));
64
+ // const dimensionsRenderer = data =>
65
+ // data.dimensions.map(dim => (
66
+ // <tr>
67
+ // <td>{dim}</td>
68
+ // </tr>
69
+ // ));
70
70
 
71
71
  return (
72
72
  <>
package/src/app/index.tsx CHANGED
@@ -12,6 +12,8 @@ import { NodePage } from './pages/NodePage/Loadable';
12
12
  import { NotFoundPage } from './pages/NotFoundPage/Loadable';
13
13
  import { Root } from './pages/Root/Loadable';
14
14
  import { ListNamespacesPage } from './pages/ListNamespacesPage';
15
+ import DJClientContext from './providers/djclient';
16
+ import { DataJunctionAPI } from './services/DJService';
15
17
 
16
18
  export function App() {
17
19
  return (
@@ -25,21 +27,31 @@ export function App() {
25
27
  content="DataJunction serves as a semantic layer to help manage metrics"
26
28
  />
27
29
  </Helmet>
30
+ <DJClientContext.Provider value={{ DataJunctionAPI }}>
31
+ <Routes>
32
+ <Route
33
+ path="/"
34
+ element={<Root />}
35
+ children={
36
+ <>
37
+ <Route path="nodes" key="nodes">
38
+ <Route path=":name" element={<NodePage />} />
39
+ </Route>
28
40
 
29
- <Routes>
30
- <Route
31
- path="/"
32
- element={<Root />}
33
- children={
34
- <>
35
- <Route path="nodes/:name" element={<NodePage />} />
36
- <Route path="namespaces" element={<ListNamespacesPage />} />
37
- <Route path="namespaces/:namespace" element={<NamespacePage />} />
38
- </>
39
- }
40
- />
41
- <Route path="*" element={<NotFoundPage />} />
42
- </Routes>
41
+ <Route path="/" element={<ListNamespacesPage />} key="index" />
42
+ <Route path="namespaces">
43
+ <Route
44
+ path=":namespace"
45
+ element={<NamespacePage />}
46
+ key="namespaces"
47
+ />
48
+ </Route>
49
+ </>
50
+ }
51
+ />
52
+ <Route path="*" element={<NotFoundPage />} />
53
+ </Routes>
54
+ </DJClientContext.Provider>
43
55
  </BrowserRouter>
44
56
  );
45
57
  }
@@ -1,29 +1,33 @@
1
1
  import * as React from 'react';
2
- import { useEffect, useState } from 'react';
3
- import { DataJunctionAPI } from '../../services/DJService';
2
+ import { useContext, useEffect, useState } from 'react';
4
3
  import NamespaceHeader from '../../components/NamespaceHeader';
4
+ import { DataJunctionAPI } from '../../services/DJService';
5
+ import DJClientContext from '../../providers/djclient';
6
+ // const datajunction = require('datajunction');
7
+ // const dj = new datajunction.DJClient('http://localhost:8000');
5
8
 
6
9
  export function ListNamespacesPage() {
10
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
7
11
  const [state, setState] = useState({
8
12
  namespaces: [],
9
13
  });
10
14
 
11
15
  useEffect(() => {
12
16
  const fetchData = async () => {
13
- const namespaces = await DataJunctionAPI.namespaces();
17
+ const namespaces = await djClient.namespaces();
14
18
  setState({
15
19
  namespaces: namespaces,
16
20
  });
17
21
  };
18
22
  fetchData().catch(console.error);
19
- }, []);
23
+ }, [djClient, djClient.namespaces]);
20
24
 
21
25
  const namespacesList = state.namespaces.map(node => (
22
26
  <tr>
23
27
  <td>
24
28
  <a href={'/namespaces/' + node.namespace}>{node.namespace}</a>
25
29
  </td>
26
- <td>5</td>
30
+ <td></td>
27
31
  </tr>
28
32
  ));
29
33
 
@@ -50,3 +54,7 @@ export function ListNamespacesPage() {
50
54
  </>
51
55
  );
52
56
  }
57
+
58
+ ListNamespacesPage.defaultProps = {
59
+ djClient: DataJunctionAPI,
60
+ };
@@ -5,10 +5,12 @@
5
5
  import * as React from 'react';
6
6
  import { lazyLoad } from '../../../utils/loadable';
7
7
 
8
- export const NamespacePage = lazyLoad(
9
- () => import('./index'),
10
- module => module.NamespacePage,
11
- {
12
- fallback: <div></div>,
13
- },
14
- );
8
+ export const NamespacePage = props => {
9
+ return lazyLoad(
10
+ () => import('./index'),
11
+ module => module.NamespacePage,
12
+ {
13
+ fallback: <div></div>,
14
+ },
15
+ )(props);
16
+ };
@@ -21,22 +21,25 @@ exports[`<NamespacePage /> should render and match the snapshot 1`] = `
21
21
  className="card-table table"
22
22
  >
23
23
  <thead>
24
- <th>
25
- Namespace
26
- </th>
27
- <th>
28
- Name
29
- </th>
30
- <th>
31
- Type
32
- </th>
33
- <th>
34
- Status
35
- </th>
36
- <th>
37
- Mode
38
- </th>
24
+ <tr>
25
+ <th>
26
+ Namespace
27
+ </th>
28
+ <th>
29
+ Name
30
+ </th>
31
+ <th>
32
+ Type
33
+ </th>
34
+ <th>
35
+ Status
36
+ </th>
37
+ <th>
38
+ Mode
39
+ </th>
40
+ </tr>
39
41
  </thead>
42
+ <tbody />
40
43
  </table>
41
44
  </div>
42
45
  </div>
@@ -1,20 +1,12 @@
1
1
  import * as React from 'react';
2
2
  import { useParams } from 'react-router-dom';
3
- import { useEffect, useState } from 'react';
4
- import { DataJunctionAPI } from '../../services/DJService';
3
+ import { useContext, useEffect, useState } from 'react';
5
4
  import NamespaceHeader from '../../components/NamespaceHeader';
6
5
  import NodeStatus from '../NodePage/NodeStatus';
7
-
8
- export async function loader({ params }) {
9
- const djNode = await DataJunctionAPI.node(params.name);
10
- if (djNode.type === 'metric') {
11
- const metricNode = await DataJunctionAPI.metric(params.name);
12
- djNode.dimensions = metricNode.dimensions;
13
- }
14
- return djNode;
15
- }
6
+ import DJClientContext from '../../providers/djclient';
16
7
 
17
8
  export function NamespacePage() {
9
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
18
10
  const { namespace } = useParams();
19
11
 
20
12
  const [state, setState] = useState({
@@ -24,9 +16,9 @@ export function NamespacePage() {
24
16
 
25
17
  useEffect(() => {
26
18
  const fetchData = async () => {
27
- const djNodes = await DataJunctionAPI.namespace(namespace);
19
+ const djNodes = await djClient.namespace(namespace);
28
20
  const nodes = djNodes.map(node => {
29
- return DataJunctionAPI.node(node);
21
+ return djClient.node(node);
30
22
  });
31
23
  const foundNodes = await Promise.all(nodes);
32
24
  setState({
@@ -35,7 +27,7 @@ export function NamespacePage() {
35
27
  });
36
28
  };
37
29
  fetchData().catch(console.error);
38
- }, [namespace]);
30
+ }, [djClient, namespace]);
39
31
 
40
32
  const nodesList = state.nodes.map(node => (
41
33
  <tr>
@@ -77,13 +69,15 @@ export function NamespacePage() {
77
69
  <div className="table-responsive">
78
70
  <table className="card-table table">
79
71
  <thead>
80
- <th>Namespace</th>
81
- <th>Name</th>
82
- <th>Type</th>
83
- <th>Status</th>
84
- <th>Mode</th>
72
+ <tr>
73
+ <th>Namespace</th>
74
+ <th>Name</th>
75
+ <th>Type</th>
76
+ <th>Status</th>
77
+ <th>Mode</th>
78
+ </tr>
85
79
  </thead>
86
- {nodesList}
80
+ <tbody>{nodesList}</tbody>
87
81
  </table>
88
82
  </div>
89
83
  </div>
@@ -5,10 +5,12 @@
5
5
  import * as React from 'react';
6
6
  import { lazyLoad } from '../../../utils/loadable';
7
7
 
8
- export const NodePage = lazyLoad(
9
- () => import('./index'),
10
- module => module.NodePage,
11
- {
12
- fallback: <div></div>,
13
- },
14
- );
8
+ export const NodePage = props => {
9
+ return lazyLoad(
10
+ () => import('./index'),
11
+ module => module.NodePage,
12
+ {
13
+ fallback: <div></div>,
14
+ },
15
+ )(props);
16
+ };
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo } from 'react';
1
+ import React, { useCallback, useContext, useEffect, useMemo } from 'react';
2
2
  import ReactFlow, {
3
3
  addEdge,
4
4
  MiniMap,
@@ -12,10 +12,11 @@ import ReactFlow, {
12
12
  import '../../../styles/dag.css';
13
13
  import 'reactflow/dist/style.css';
14
14
  import DJNode from '../../components/djgraph/DJNode';
15
- import { DataJunctionAPI } from '../../services/DJService';
16
15
  import dagre from 'dagre';
16
+ import DJClientContext from '../../providers/djclient';
17
17
 
18
18
  const NodeLineage = djNode => {
19
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
19
20
  const nodeTypes = useMemo(() => ({ DJNode: DJNode }), []);
20
21
 
21
22
  const [nodes, setNodes, onNodesChange] = useNodesState([]);
@@ -25,47 +26,48 @@ const NodeLineage = djNode => {
25
26
  height: 100,
26
27
  width: 150,
27
28
  };
28
- const dagreGraph = new dagre.graphlib.Graph();
29
- dagreGraph.setDefaultEdgeLabel(() => ({}));
30
29
 
31
- const setElementsLayout = (
32
- nodes,
33
- edges,
34
- direction = 'LR',
35
- nodeWidth = 800,
36
- nodeHeight = 150,
37
- ) => {
38
- const isHorizontal = direction === 'TB';
39
- dagreGraph.setGraph({ rankdir: direction });
40
-
41
- nodes.forEach(node => {
42
- dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
43
- });
44
-
45
- edges.forEach(edge => {
46
- dagreGraph.setEdge(edge.source, edge.target);
47
- });
48
-
49
- dagre.layout(dagreGraph);
50
-
51
- nodes.forEach(node => {
52
- const nodeWithPosition = dagreGraph.node(node.id);
53
- node.targetPosition = isHorizontal ? 'left' : 'top';
54
- node.sourcePosition = isHorizontal ? 'right' : 'bottom';
55
- node.position = {
56
- x: nodeWithPosition.x - nodeWidth / 2,
57
- y: nodeWithPosition.y - nodeHeight / 2,
58
- };
59
- return node;
60
- });
61
-
62
- return { nodes, edges };
63
- };
30
+ const dagreGraph = useMemo(() => new dagre.graphlib.Graph(), []);
31
+ dagreGraph.setDefaultEdgeLabel(() => ({}));
64
32
 
65
33
  useEffect(() => {
34
+ const setElementsLayout = (
35
+ nodes,
36
+ edges,
37
+ direction = 'LR',
38
+ nodeWidth = 800,
39
+ nodeHeight = 150,
40
+ ) => {
41
+ const isHorizontal = direction === 'TB';
42
+ dagreGraph.setGraph({ rankdir: direction });
43
+
44
+ nodes.forEach(node => {
45
+ dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
46
+ });
47
+
48
+ edges.forEach(edge => {
49
+ dagreGraph.setEdge(edge.source, edge.target);
50
+ });
51
+
52
+ dagre.layout(dagreGraph);
53
+
54
+ nodes.forEach(node => {
55
+ const nodeWithPosition = dagreGraph.node(node.id);
56
+ node.targetPosition = isHorizontal ? 'left' : 'top';
57
+ node.sourcePosition = isHorizontal ? 'right' : 'bottom';
58
+ node.position = {
59
+ x: nodeWithPosition.x - nodeWidth / 2,
60
+ y: nodeWithPosition.y - nodeHeight / 2,
61
+ };
62
+ return node;
63
+ });
64
+
65
+ return { nodes, edges };
66
+ };
67
+
66
68
  const dagFetch = async () => {
67
- let upstreams = await DataJunctionAPI.upstreams(djNode.djNode.name);
68
- let downstreams = await DataJunctionAPI.downstreams(djNode.djNode.name);
69
+ let upstreams = await djClient.upstreams(djNode.djNode.name);
70
+ let downstreams = await djClient.downstreams(djNode.djNode.name);
69
71
  var djNodes = [djNode.djNode];
70
72
  for (const iterable of [upstreams, downstreams]) {
71
73
  for (const item of iterable) {
@@ -117,7 +119,7 @@ const NodeLineage = djNode => {
117
119
  label:
118
120
  node.table !== null
119
121
  ? String(node.schema_ + '.' + node.table)
120
- : String(node.name),
122
+ : 'default.' + node.name,
121
123
  table: node.table,
122
124
  name: String(node.name),
123
125
  display_name: String(node.display_name),
@@ -130,18 +132,17 @@ const NodeLineage = djNode => {
130
132
  // extent: 'parent',
131
133
  };
132
134
  });
133
- console.log(djNodes);
134
135
  setNodes(nodes);
135
136
  setEdges(edges);
136
137
  setElementsLayout(nodes, edges);
137
138
  };
138
139
 
139
140
  dagFetch();
140
- }, []);
141
+ }, [dagreGraph, djClient, djNode.djNode, setEdges, setNodes]);
141
142
 
142
143
  const onConnect = useCallback(
143
144
  params => setEdges(eds => addEdge(params, eds)),
144
- [],
145
+ [setEdges],
145
146
  );
146
147
 
147
148
  return (