datajunction-ui 0.0.1-rc.0
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 +1 -0
- package/.env.local +4 -0
- package/.env.production +1 -0
- package/.eslintrc.js +20 -0
- package/.gitattributes +201 -0
- package/.github/pull_request_template.md +11 -0
- package/.github/workflows/ci.yml +33 -0
- package/.husky/pre-commit +6 -0
- package/.nvmrc +1 -0
- package/.prettierignore +4 -0
- package/.prettierrc +9 -0
- package/.stylelintrc +7 -0
- package/.vscode/extensions.json +8 -0
- package/.vscode/launch.json +15 -0
- package/.vscode/settings.json +25 -0
- package/Dockerfile +6 -0
- package/LICENSE +22 -0
- package/README.md +10 -0
- package/internals/testing/loadable.mock.tsx +6 -0
- package/package.json +150 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +29 -0
- package/public/manifest.json +15 -0
- package/public/robots.txt +3 -0
- package/src/app/__tests__/__snapshots__/index.test.tsx.snap +45 -0
- package/src/app/__tests__/index.test.tsx +14 -0
- package/src/app/components/ListGroupItem.jsx +17 -0
- package/src/app/components/NamespaceHeader.jsx +40 -0
- package/src/app/components/Tab.jsx +26 -0
- package/src/app/components/__tests__/ListGroupItem.test.tsx +16 -0
- package/src/app/components/__tests__/NamespaceHeader.test.jsx +14 -0
- package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +26 -0
- package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +63 -0
- package/src/app/components/djgraph/DJNode.jsx +111 -0
- package/src/app/components/djgraph/__tests__/DJNode.test.tsx +24 -0
- package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +73 -0
- package/src/app/index.tsx +53 -0
- package/src/app/pages/ListNamespacesPage/Loadable.jsx +23 -0
- package/src/app/pages/ListNamespacesPage/index.jsx +53 -0
- package/src/app/pages/NamespacePage/Loadable.jsx +23 -0
- package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +45 -0
- package/src/app/pages/NamespacePage/__tests__/index.test.tsx +14 -0
- package/src/app/pages/NamespacePage/index.jsx +93 -0
- package/src/app/pages/NodePage/Loadable.jsx +23 -0
- package/src/app/pages/NodePage/NodeColumnTab.jsx +44 -0
- package/src/app/pages/NodePage/NodeGraphTab.jsx +160 -0
- package/src/app/pages/NodePage/NodeInfoTab.jsx +87 -0
- package/src/app/pages/NodePage/NodeStatus.jsx +34 -0
- package/src/app/pages/NodePage/index.jsx +100 -0
- package/src/app/pages/NotFoundPage/Loadable.tsx +14 -0
- package/src/app/pages/NotFoundPage/P.ts +8 -0
- package/src/app/pages/NotFoundPage/__tests__/__snapshots__/index.test.tsx.snap +61 -0
- package/src/app/pages/NotFoundPage/__tests__/index.test.tsx +21 -0
- package/src/app/pages/NotFoundPage/index.tsx +45 -0
- package/src/app/pages/Root/Loadable.tsx +23 -0
- package/src/app/pages/Root/assets/dj-logo.png +0 -0
- package/src/app/pages/Root/index.tsx +42 -0
- package/src/app/services/DJService.js +124 -0
- package/src/index.tsx +47 -0
- package/src/react-app-env.d.ts +4 -0
- package/src/reportWebVitals.ts +15 -0
- package/src/setupTests.ts +8 -0
- package/src/styles/dag-styles.ts +117 -0
- package/src/styles/global-styles.ts +588 -0
- package/src/styles/index.css +546 -0
- package/src/utils/__tests__/__snapshots__/loadable.test.tsx.snap +17 -0
- package/src/utils/__tests__/loadable.test.tsx +53 -0
- package/src/utils/__tests__/request.test.ts +82 -0
- package/src/utils/loadable.tsx +30 -0
- package/src/utils/request.ts +54 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asynchronously loads the component for the root page
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { lazyLoad } from 'utils/loadable';
|
|
7
|
+
import styled from 'styled-components/macro';
|
|
8
|
+
|
|
9
|
+
const LoadingWrapper = styled.div`
|
|
10
|
+
width: 100%;
|
|
11
|
+
height: 100vh;
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
export const Root = lazyLoad(
|
|
18
|
+
() => import('./index'),
|
|
19
|
+
module => module.Root,
|
|
20
|
+
{
|
|
21
|
+
fallback: <LoadingWrapper></LoadingWrapper>,
|
|
22
|
+
},
|
|
23
|
+
);
|
|
Binary file
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Outlet } from 'react-router-dom';
|
|
2
|
+
import logo from './assets/dj-logo.png';
|
|
3
|
+
import { Helmet } from 'react-helmet-async';
|
|
4
|
+
|
|
5
|
+
export function Root() {
|
|
6
|
+
return (
|
|
7
|
+
<>
|
|
8
|
+
<Helmet>
|
|
9
|
+
<title>DataJunction</title>
|
|
10
|
+
<meta
|
|
11
|
+
name="description"
|
|
12
|
+
content="DataJunction Metrics Platform Webapp"
|
|
13
|
+
/>
|
|
14
|
+
</Helmet>
|
|
15
|
+
<div className="container d-flex align-items-center justify-content-between">
|
|
16
|
+
<div className="header">
|
|
17
|
+
<div className="logo">
|
|
18
|
+
<h2>
|
|
19
|
+
<img src={logo} alt="DJ Logo" width="15%" />
|
|
20
|
+
DataJunction
|
|
21
|
+
</h2>
|
|
22
|
+
</div>
|
|
23
|
+
<div className="menu">
|
|
24
|
+
<div className="menu-item here menu-here-bg menu-lg-down-accordion me-0 me-lg-2 fw-semibold">
|
|
25
|
+
<span className="menu-link">
|
|
26
|
+
<span className="menu-title">
|
|
27
|
+
<a href="/">Explore</a>
|
|
28
|
+
</span>
|
|
29
|
+
</span>
|
|
30
|
+
<span className="menu-link">
|
|
31
|
+
<span className="menu-title">
|
|
32
|
+
<a href="/">Help</a>
|
|
33
|
+
</span>
|
|
34
|
+
</span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<Outlet />
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { MarkerType } from 'reactflow';
|
|
2
|
+
|
|
3
|
+
const DJ_URL = 'http://localhost:8000'; //process.env.REACT_APP_DJ_URL;
|
|
4
|
+
|
|
5
|
+
export const DataJunctionAPI = {
|
|
6
|
+
node: async function (name) {
|
|
7
|
+
const data = await (await fetch(DJ_URL + '/nodes/' + name + '/')).json();
|
|
8
|
+
return data;
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
upstreams: async function (name) {
|
|
12
|
+
const data = await (
|
|
13
|
+
await fetch(DJ_URL + '/nodes/' + name + '/upstream/')
|
|
14
|
+
).json();
|
|
15
|
+
return data;
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
downstreams: async function (name) {
|
|
19
|
+
const data = await (
|
|
20
|
+
await fetch(DJ_URL + '/nodes/' + name + '/downstream/')
|
|
21
|
+
).json();
|
|
22
|
+
return data;
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
metric: async function (name) {
|
|
26
|
+
const data = await (await fetch(DJ_URL + '/metrics/' + name + '/')).json();
|
|
27
|
+
return data;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
namespace: async function (nmspce) {
|
|
31
|
+
const data = await (
|
|
32
|
+
await fetch(DJ_URL + '/namespaces/' + nmspce + '/')
|
|
33
|
+
).json();
|
|
34
|
+
return data;
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
namespaces: async function () {
|
|
38
|
+
const data = await (await fetch(DJ_URL + '/namespaces/')).json();
|
|
39
|
+
return data;
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
lineage: async function (node) {},
|
|
43
|
+
|
|
44
|
+
dag: async function (namespace = 'default') {
|
|
45
|
+
const edges = [];
|
|
46
|
+
const data = await (await fetch(DJ_URL + '/nodes/')).json();
|
|
47
|
+
|
|
48
|
+
// const metrics = await (await fetch(DJ_URL + '/metrics/')).json();
|
|
49
|
+
|
|
50
|
+
data.forEach(obj => {
|
|
51
|
+
obj.parents.forEach(parent => {
|
|
52
|
+
if (parent.name) {
|
|
53
|
+
edges.push({
|
|
54
|
+
id: obj.name + '-' + parent.name,
|
|
55
|
+
target: obj.name,
|
|
56
|
+
source: parent.name,
|
|
57
|
+
animated: true,
|
|
58
|
+
markerEnd: {
|
|
59
|
+
type: MarkerType.Arrow,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
obj.columns.forEach(col => {
|
|
66
|
+
if (col.dimension) {
|
|
67
|
+
edges.push({
|
|
68
|
+
id: obj.name + '-' + col.dimension.name,
|
|
69
|
+
target: obj.name,
|
|
70
|
+
source: col.dimension.name,
|
|
71
|
+
draggable: true,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
const namespaces = new Set(
|
|
77
|
+
data.flatMap(node => node.name.split('.').slice(0, -1)),
|
|
78
|
+
);
|
|
79
|
+
const namespaceNodes = Array.from(namespaces).map(namespace => {
|
|
80
|
+
return {
|
|
81
|
+
id: String(namespace),
|
|
82
|
+
type: 'DJNamespace',
|
|
83
|
+
data: {
|
|
84
|
+
label: String(namespace),
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const nodes = data.map((node, index) => {
|
|
90
|
+
const primary_key = node.columns
|
|
91
|
+
.filter(col =>
|
|
92
|
+
col.attributes.some(
|
|
93
|
+
attr => attr.attribute_type.name === 'primary_key',
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
.map(col => col.name);
|
|
97
|
+
const column_names = node.columns.map(col => {
|
|
98
|
+
return { name: col.name, type: col.type };
|
|
99
|
+
});
|
|
100
|
+
// const dimensions = node.type === "metric" ? metrics.filter(metric => metric.name === node.name)[0].dimensions : [];
|
|
101
|
+
return {
|
|
102
|
+
id: String(node.name),
|
|
103
|
+
type: 'DJNode',
|
|
104
|
+
data: {
|
|
105
|
+
label:
|
|
106
|
+
node.table !== null
|
|
107
|
+
? String(node.schema_ + '.' + node.table)
|
|
108
|
+
: String(node.name),
|
|
109
|
+
table: node.table,
|
|
110
|
+
name: String(node.name),
|
|
111
|
+
display_name: String(node.display_name),
|
|
112
|
+
type: node.type,
|
|
113
|
+
primary_key: primary_key,
|
|
114
|
+
column_names: column_names,
|
|
115
|
+
// dimensions: dimensions,
|
|
116
|
+
},
|
|
117
|
+
// parentNode: [node.name.split(".").slice(-2, -1)],
|
|
118
|
+
// extent: 'parent',
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return { edges: edges, nodes: nodes, namespaces: namespaceNodes };
|
|
123
|
+
},
|
|
124
|
+
};
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* index.tsx
|
|
3
|
+
*
|
|
4
|
+
* This is the entry file for the application, only setup and boilerplate
|
|
5
|
+
* code.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import 'react-app-polyfill/ie11';
|
|
9
|
+
import 'react-app-polyfill/stable';
|
|
10
|
+
|
|
11
|
+
import * as React from 'react';
|
|
12
|
+
import ReactDOM from 'react-dom/client';
|
|
13
|
+
import FontFaceObserver from 'fontfaceobserver';
|
|
14
|
+
|
|
15
|
+
// Use consistent styling
|
|
16
|
+
import 'sanitize.css/sanitize.css';
|
|
17
|
+
|
|
18
|
+
import { App } from 'app';
|
|
19
|
+
|
|
20
|
+
import { HelmetProvider } from 'react-helmet-async';
|
|
21
|
+
import reportWebVitals from 'reportWebVitals';
|
|
22
|
+
|
|
23
|
+
// Observe loading of Inter (to remove 'Inter', remove the <link> tag in
|
|
24
|
+
// the index.html file and this observer)
|
|
25
|
+
const openSansObserver = new FontFaceObserver('Inter', {});
|
|
26
|
+
|
|
27
|
+
// When Inter is loaded, add a font-family using Inter to the body
|
|
28
|
+
openSansObserver.load().then(() => {
|
|
29
|
+
document.body.classList.add('fontLoaded');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const root = ReactDOM.createRoot(
|
|
33
|
+
document.getElementById('root') as HTMLElement,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
root.render(
|
|
37
|
+
<HelmetProvider>
|
|
38
|
+
<React.StrictMode>
|
|
39
|
+
<App />
|
|
40
|
+
</React.StrictMode>
|
|
41
|
+
</HelmetProvider>,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// If you want to start measuring performance in your app, pass a function
|
|
45
|
+
// to log results (for example: reportWebVitals(console.log))
|
|
46
|
+
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
|
47
|
+
reportWebVitals();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ReportHandler } from 'web-vitals';
|
|
2
|
+
|
|
3
|
+
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
|
4
|
+
if (onPerfEntry && onPerfEntry instanceof Function) {
|
|
5
|
+
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
|
6
|
+
getCLS(onPerfEntry);
|
|
7
|
+
getFID(onPerfEntry);
|
|
8
|
+
getFCP(onPerfEntry);
|
|
9
|
+
getLCP(onPerfEntry);
|
|
10
|
+
getTTFB(onPerfEntry);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default reportWebVitals;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// react-testing-library renders your components to document.body,
|
|
2
|
+
// this adds jest-dom's custom assertions
|
|
3
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
4
|
+
|
|
5
|
+
import 'react-app-polyfill/ie11';
|
|
6
|
+
import 'react-app-polyfill/stable';
|
|
7
|
+
|
|
8
|
+
import 'jest-styled-components';
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createGlobalStyle } from 'styled-components';
|
|
2
|
+
// import { StyleConstants } from './StyleConstants';
|
|
3
|
+
/* istanbul ignore next */
|
|
4
|
+
export const DAGStyle = createGlobalStyle`
|
|
5
|
+
.react-flow__node-custom {
|
|
6
|
+
font-size: 1.2em;
|
|
7
|
+
width: 180px;
|
|
8
|
+
background: #f5f5f6;
|
|
9
|
+
color: #222;
|
|
10
|
+
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 15%), 0 2px 4px -1px rgb(0 0 0 / 8%);
|
|
11
|
+
border-radius: 2px;
|
|
12
|
+
}
|
|
13
|
+
.react-flow {
|
|
14
|
+
width: 100%; height: 800px; overflow: hidden; position: relative; z-index: 0;
|
|
15
|
+
}
|
|
16
|
+
.react-flow__node-custom .react-flow__handle {
|
|
17
|
+
top: 24px;
|
|
18
|
+
right: -15px;
|
|
19
|
+
width: 6px;
|
|
20
|
+
height: 10px;
|
|
21
|
+
border-radius: 2px;
|
|
22
|
+
background-color: #778899;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.react-flow__node.circle {
|
|
26
|
+
border-radius: 50%;
|
|
27
|
+
width: 60px;
|
|
28
|
+
height: 60px;
|
|
29
|
+
display: flex;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
align-items: center;
|
|
32
|
+
font-weight: 700;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.react-flow__node.annotation {
|
|
36
|
+
border-radius: 0;
|
|
37
|
+
text-align: left;
|
|
38
|
+
background: white;
|
|
39
|
+
border: none;
|
|
40
|
+
line-height: 1.4;
|
|
41
|
+
width: 225px;
|
|
42
|
+
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 15%), 0 2px 4px -1px rgb(0 0 0 / 8%);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.react-flow__node.annotation .react-flow__handle {
|
|
46
|
+
display: none;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.dj-node__header {
|
|
50
|
+
font-weight: 400;
|
|
51
|
+
text-transform: uppercase;
|
|
52
|
+
font-family: 'jetbrains-mono',monospace;
|
|
53
|
+
font-size: 1.5em;
|
|
54
|
+
padding-inline-start: 0.5rem;
|
|
55
|
+
padding-inline-end: 0.5rem;
|
|
56
|
+
padding-left: 0.75em;
|
|
57
|
+
padding-top: 0.35rem;
|
|
58
|
+
padding-bottom: 0.35rem;
|
|
59
|
+
border-bottom-width: 1px;
|
|
60
|
+
border-bottom-style: solid;
|
|
61
|
+
border-color: #c4cbd1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.dj-node__body {
|
|
65
|
+
background-color: rgb(254, 254, 254);
|
|
66
|
+
padding: 0.5em 0.7em 0.9em 0.7em;
|
|
67
|
+
font-family: 'nt-dapper',-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;
|
|
68
|
+
font-size: 1.7em;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.dj-node__full {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
height: 100%;
|
|
75
|
+
border-width: 1px;
|
|
76
|
+
border-style: solid;
|
|
77
|
+
border-image: initial;
|
|
78
|
+
border-color: #b0b9c2;
|
|
79
|
+
border-radius: 8px;
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.dj-node__metadata {
|
|
85
|
+
padding: 1em;
|
|
86
|
+
font-family: 'nt-dapper',-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;
|
|
87
|
+
font-size: 1em;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.collapse-button {
|
|
91
|
+
display: block;
|
|
92
|
+
width: 100%;
|
|
93
|
+
font: 0.8em "Montserrat", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
94
|
+
text-transform: uppercase;
|
|
95
|
+
border: none;
|
|
96
|
+
margin-top: 0.2em;
|
|
97
|
+
padding: 0.35em;
|
|
98
|
+
}
|
|
99
|
+
.collapse {
|
|
100
|
+
padding-top: 10px;
|
|
101
|
+
}
|
|
102
|
+
.collapse-content.collapsed {
|
|
103
|
+
display: none;
|
|
104
|
+
}
|
|
105
|
+
.collapsed-content.expanded {
|
|
106
|
+
display: block;
|
|
107
|
+
}
|
|
108
|
+
.collapse tr td {
|
|
109
|
+
padding-left: 5px;
|
|
110
|
+
padding-bottom: 5px;
|
|
111
|
+
}
|
|
112
|
+
.serif {
|
|
113
|
+
font-family: 'jetbrains-mono', monospace;
|
|
114
|
+
text-transform: lowercase;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
`;
|