datajunction-ui 0.0.1-rc.2 → 0.0.1-rc.21
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/.prettierignore +3 -1
- package/dj-logo.svg +10 -0
- package/package.json +43 -13
- package/public/favicon.ico +0 -0
- package/src/__tests__/reportWebVitals.test.jsx +44 -0
- package/src/app/__tests__/__snapshots__/index.test.tsx.snap +5 -39
- package/src/app/components/DeleteNode.jsx +79 -0
- package/src/app/components/ListGroupItem.jsx +8 -1
- package/src/app/components/NamespaceHeader.jsx +4 -13
- package/src/app/components/QueryInfo.jsx +77 -0
- package/src/app/components/Tab.jsx +3 -2
- package/src/app/components/ToggleSwitch.jsx +20 -0
- package/src/app/components/__tests__/QueryInfo.test.jsx +55 -0
- package/src/app/components/__tests__/Tab.test.jsx +27 -0
- package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
- package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +3 -0
- package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
- package/src/app/components/djgraph/Collapse.jsx +46 -0
- package/src/app/components/djgraph/DJNode.jsx +60 -82
- package/src/app/components/djgraph/DJNodeColumns.jsx +71 -0
- package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
- package/src/app/components/djgraph/LayoutFlow.jsx +104 -0
- package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
- package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
- package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
- package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +84 -40
- package/src/app/constants.js +2 -0
- package/src/app/icons/AlertIcon.jsx +32 -0
- package/src/app/icons/CollapsedIcon.jsx +15 -0
- package/src/app/icons/DJLogo.jsx +36 -0
- package/src/app/icons/DeleteIcon.jsx +21 -0
- package/src/app/icons/EditIcon.jsx +18 -0
- package/src/app/icons/ExpandedIcon.jsx +15 -0
- package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
- package/src/app/icons/InvalidIcon.jsx +14 -0
- package/src/app/icons/PythonIcon.jsx +52 -0
- package/src/app/icons/TableIcon.jsx +14 -0
- package/src/app/icons/ValidIcon.jsx +14 -0
- package/src/app/index.tsx +79 -26
- package/src/app/pages/AddEditNodePage/FormikSelect.jsx +46 -0
- package/src/app/pages/AddEditNodePage/FullNameField.jsx +37 -0
- package/src/app/pages/AddEditNodePage/Loadable.jsx +16 -0
- package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +89 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +77 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +93 -0
- package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
- package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
- package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +53 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +53 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
- package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +178 -0
- package/src/app/pages/AddEditNodePage/index.jsx +357 -0
- package/src/app/pages/LoginPage/__tests__/index.test.jsx +70 -0
- package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
- package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
- package/src/app/pages/LoginPage/index.jsx +90 -0
- package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +86 -0
- package/src/app/pages/NamespacePage/Explorer.jsx +57 -0
- package/src/app/pages/NamespacePage/Loadable.jsx +9 -7
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +95 -0
- package/src/app/pages/NamespacePage/index.jsx +132 -31
- package/src/app/pages/NodePage/ClientCodePopover.jsx +32 -0
- package/src/app/pages/NodePage/EditColumnPopover.jsx +102 -0
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +135 -0
- package/src/app/pages/NodePage/Loadable.jsx +9 -7
- package/src/app/pages/NodePage/NodeColumnTab.jsx +106 -27
- package/src/app/pages/NodePage/NodeGraphTab.jsx +94 -148
- package/src/app/pages/NodePage/NodeHistory.jsx +212 -0
- package/src/app/pages/NodePage/NodeInfoTab.jsx +166 -51
- package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +174 -0
- package/src/app/pages/NodePage/NodeSQLTab.jsx +82 -0
- package/src/app/pages/NodePage/NodeStatus.jsx +14 -20
- package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
- package/src/app/pages/NodePage/__tests__/ClientCodePopover.test.jsx +49 -0
- package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +148 -0
- package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +165 -0
- package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +591 -0
- package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +57 -0
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +725 -0
- package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +402 -0
- package/src/app/pages/NodePage/index.jsx +151 -41
- package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
- package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
- package/src/app/pages/RegisterTablePage/index.jsx +163 -0
- package/src/app/pages/Root/__tests__/index.test.jsx +77 -0
- package/src/app/pages/Root/index.tsx +32 -4
- package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
- package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
- package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
- package/src/app/providers/djclient.jsx +5 -0
- package/src/app/services/DJService.js +398 -22
- package/src/app/services/__tests__/DJService.test.jsx +609 -0
- package/src/mocks/mockNodes.jsx +1397 -0
- package/src/setupTests.ts +31 -1
- package/src/styles/dag.css +111 -5
- package/src/styles/index.css +467 -31
- package/src/styles/login.css +67 -0
- package/src/styles/node-creation.scss +197 -0
- package/src/styles/styles.scss +44 -0
- package/src/styles/styles.scss.d.ts +9 -0
- package/src/utils/form.jsx +23 -0
- package/tsconfig.json +1 -5
- package/webpack.config.js +29 -6
- package/.babelrc +0 -4
- package/.env.local +0 -4
- package/.env.production +0 -1
- package/.github/pull_request_template.md +0 -11
- package/.github/workflows/ci.yml +0 -33
- package/.vscode/extensions.json +0 -7
- package/.vscode/launch.json +0 -15
- package/.vscode/settings.json +0 -25
- package/Dockerfile +0 -7
- package/dist/5fa71a03d45dc2e3d61447f3013a303d.png +0 -0
- package/dist/index.html +0 -1
- package/dist/main.js +0 -23303
- package/dist/static/main.05a86d446163fd5f17d3.js +0 -2
- package/dist/static/main.05a86d446163fd5f17d3.js.LICENSE.txt +0 -98
- package/dist/static/main.9e53bed734dae98e5b10.js +0 -2
- package/dist/static/main.9e53bed734dae98e5b10.js.LICENSE.txt +0 -98
- package/dist/static/main.js +0 -2
- package/dist/static/main.js.LICENSE.txt +0 -98
- package/dist/static/vendor.05a86d446163fd5f17d3.js +0 -2
- package/dist/static/vendor.05a86d446163fd5f17d3.js.LICENSE.txt +0 -29
- package/dist/static/vendor.9e53bed734dae98e5b10.js +0 -2
- package/dist/static/vendor.9e53bed734dae98e5b10.js.LICENSE.txt +0 -29
- package/dist/static/vendor.js +0 -2
- package/dist/static/vendor.js.LICENSE.txt +0 -29
- package/dist/vendor.js +0 -281
- package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
- package/src/app/pages/ListNamespacesPage/index.jsx +0 -52
- package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -45
- package/src/app/pages/NamespacePage/__tests__/index.test.tsx +0 -14
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Formik, Form, Field, ErrorMessage } from 'formik';
|
|
3
|
+
import '../../../styles/login.css';
|
|
4
|
+
import logo from '../Root/assets/dj-logo.png';
|
|
5
|
+
import GitHubLoginButton from './assets/sign-in-with-github.png';
|
|
6
|
+
|
|
7
|
+
export function LoginPage() {
|
|
8
|
+
const [, setError] = useState('');
|
|
9
|
+
const githubLoginURL = new URL('/github/login/', process.env.REACT_APP_DJ_URL)
|
|
10
|
+
.href;
|
|
11
|
+
|
|
12
|
+
const handleBasicLogin = async ({ username, password }) => {
|
|
13
|
+
const data = new FormData();
|
|
14
|
+
data.append('username', username);
|
|
15
|
+
data.append('password', password);
|
|
16
|
+
await fetch(`${process.env.REACT_APP_DJ_URL}/basic/login/`, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
body: data,
|
|
19
|
+
credentials: 'include',
|
|
20
|
+
}).catch(error => {
|
|
21
|
+
setError(error ? JSON.stringify(error) : '');
|
|
22
|
+
});
|
|
23
|
+
window.location.reload();
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="container">
|
|
28
|
+
<div className="login">
|
|
29
|
+
<center>
|
|
30
|
+
<Formik
|
|
31
|
+
initialValues={{ username: '', password: '' }}
|
|
32
|
+
validate={values => {
|
|
33
|
+
const errors = {};
|
|
34
|
+
if (!values.username) {
|
|
35
|
+
errors.username = 'Required';
|
|
36
|
+
}
|
|
37
|
+
if (!values.password) {
|
|
38
|
+
errors.password = 'Required';
|
|
39
|
+
}
|
|
40
|
+
return errors;
|
|
41
|
+
}}
|
|
42
|
+
onSubmit={(values, { setSubmitting }) => {
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
handleBasicLogin(values);
|
|
45
|
+
setSubmitting(false);
|
|
46
|
+
}, 400);
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
{({ isSubmitting }) => (
|
|
50
|
+
<Form>
|
|
51
|
+
<div className="logo-title">
|
|
52
|
+
<img src={logo} alt="DJ Logo" width="75px" height="75px" />
|
|
53
|
+
<h2>DataJunction</h2>
|
|
54
|
+
</div>
|
|
55
|
+
<div className="inputContainer">
|
|
56
|
+
<ErrorMessage name="username" component="span" />
|
|
57
|
+
<Field type="text" name="username" placeholder="Username" />
|
|
58
|
+
</div>
|
|
59
|
+
<div>
|
|
60
|
+
<ErrorMessage name="password" component="span" />
|
|
61
|
+
<Field
|
|
62
|
+
type="password"
|
|
63
|
+
name="password"
|
|
64
|
+
placeholder="Password"
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
<button type="submit" disabled={isSubmitting}>
|
|
68
|
+
Login
|
|
69
|
+
</button>
|
|
70
|
+
{process.env.REACT_ENABLE_GITHUB_OAUTH === 'true' ? (
|
|
71
|
+
<div>
|
|
72
|
+
<a href={githubLoginURL}>
|
|
73
|
+
<img
|
|
74
|
+
src={GitHubLoginButton}
|
|
75
|
+
alt="Sign in with GitHub"
|
|
76
|
+
width="200px"
|
|
77
|
+
/>
|
|
78
|
+
</a>
|
|
79
|
+
</div>
|
|
80
|
+
) : (
|
|
81
|
+
''
|
|
82
|
+
)}
|
|
83
|
+
</Form>
|
|
84
|
+
)}
|
|
85
|
+
</Formik>
|
|
86
|
+
</center>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useContext, useState } from 'react';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import DJClientContext from '../../providers/djclient';
|
|
4
|
+
import { ErrorMessage, Field, Form, Formik } from 'formik';
|
|
5
|
+
import { FormikSelect } from '../AddEditNodePage/FormikSelect';
|
|
6
|
+
import EditIcon from '../../icons/EditIcon';
|
|
7
|
+
import { displayMessageAfterSubmit, labelize } from '../../../utils/form';
|
|
8
|
+
|
|
9
|
+
export default function AddNamespacePopover({ namespace, onSubmit }) {
|
|
10
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
11
|
+
const [popoverAnchor, setPopoverAnchor] = useState(false);
|
|
12
|
+
|
|
13
|
+
const addNamespace = async ({ namespace }, { setSubmitting, setStatus }) => {
|
|
14
|
+
setSubmitting(false);
|
|
15
|
+
const response = await djClient.addNamespace(namespace);
|
|
16
|
+
if (response.status === 200 || response.status === 201) {
|
|
17
|
+
setStatus({ success: 'Saved' });
|
|
18
|
+
} else {
|
|
19
|
+
setStatus({
|
|
20
|
+
failure: `${response.json.message}`,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
onSubmit();
|
|
24
|
+
window.location.reload();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<button
|
|
30
|
+
className="edit_button"
|
|
31
|
+
aria-label="AddNamespaceTogglePopover"
|
|
32
|
+
tabIndex="0"
|
|
33
|
+
onClick={() => {
|
|
34
|
+
setPopoverAnchor(!popoverAnchor);
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
<EditIcon />
|
|
38
|
+
</button>
|
|
39
|
+
<div
|
|
40
|
+
className="popover"
|
|
41
|
+
role="dialog"
|
|
42
|
+
aria-label="AddNamespacePopover"
|
|
43
|
+
style={{
|
|
44
|
+
display: popoverAnchor === false ? 'none' : 'block',
|
|
45
|
+
width: '200px !important',
|
|
46
|
+
textTransform: 'none',
|
|
47
|
+
fontWeight: 'normal',
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<Formik
|
|
51
|
+
initialValues={{
|
|
52
|
+
namespace: '',
|
|
53
|
+
}}
|
|
54
|
+
onSubmit={addNamespace}
|
|
55
|
+
>
|
|
56
|
+
{function Render({ isSubmitting, status, setFieldValue }) {
|
|
57
|
+
return (
|
|
58
|
+
<Form>
|
|
59
|
+
{displayMessageAfterSubmit(status)}
|
|
60
|
+
<span data-testid="add-namespace">
|
|
61
|
+
<ErrorMessage name="namespace" component="span" />
|
|
62
|
+
<label htmlFor="namespace">Namespace</label>
|
|
63
|
+
<Field
|
|
64
|
+
type="text"
|
|
65
|
+
name="namespace"
|
|
66
|
+
id="namespace"
|
|
67
|
+
placeholder="New namespace"
|
|
68
|
+
/>
|
|
69
|
+
</span>
|
|
70
|
+
<button
|
|
71
|
+
className="add_node"
|
|
72
|
+
type="submit"
|
|
73
|
+
aria-label="SaveNamespace"
|
|
74
|
+
aria-hidden="false"
|
|
75
|
+
style={{ marginTop: '1rem' }}
|
|
76
|
+
>
|
|
77
|
+
Save
|
|
78
|
+
</button>
|
|
79
|
+
</Form>
|
|
80
|
+
);
|
|
81
|
+
}}
|
|
82
|
+
</Formik>
|
|
83
|
+
</div>
|
|
84
|
+
</>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -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;
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
import * as React from 'react';
|
|
6
6
|
import { lazyLoad } from '../../../utils/loadable';
|
|
7
7
|
|
|
8
|
-
export const NamespacePage =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
|
3
|
+
import DJClientContext from '../../../providers/djclient';
|
|
4
|
+
import { NamespacePage } from '../index';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
const mockDjClient = {
|
|
8
|
+
namespaces: jest.fn(),
|
|
9
|
+
namespace: jest.fn(),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe('NamespacePage', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
mockDjClient.namespaces.mockResolvedValue([
|
|
15
|
+
{
|
|
16
|
+
namespace: 'common.one',
|
|
17
|
+
num_nodes: 3,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
namespace: 'common.one.a',
|
|
21
|
+
num_nodes: 6,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
namespace: 'common.one.b',
|
|
25
|
+
num_nodes: 17,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
namespace: 'common.one.c',
|
|
29
|
+
num_nodes: 64,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
namespace: 'default',
|
|
33
|
+
num_nodes: 41,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
namespace: 'default.fruits',
|
|
37
|
+
num_nodes: 1,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
namespace: 'default.fruits.citrus.lemons',
|
|
41
|
+
num_nodes: 1,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
namespace: 'default.vegetables',
|
|
45
|
+
num_nodes: 2,
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
mockDjClient.namespace.mockResolvedValue([
|
|
49
|
+
{
|
|
50
|
+
name: 'testNode',
|
|
51
|
+
display_name: 'Test Node',
|
|
52
|
+
type: 'transform',
|
|
53
|
+
mode: 'active',
|
|
54
|
+
updated_at: new Date(),
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('displays namespaces and renders nodes', async () => {
|
|
60
|
+
const element = (
|
|
61
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
62
|
+
<NamespacePage />
|
|
63
|
+
</DJClientContext.Provider>
|
|
64
|
+
);
|
|
65
|
+
render(
|
|
66
|
+
<MemoryRouter initialEntries={['/namespaces/test.namespace']}>
|
|
67
|
+
<Routes>
|
|
68
|
+
<Route path="namespaces/:namespace" element={element} />
|
|
69
|
+
</Routes>
|
|
70
|
+
</MemoryRouter>,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
await waitFor(() => {
|
|
74
|
+
expect(mockDjClient.namespaces).toHaveBeenCalledTimes(1);
|
|
75
|
+
expect(screen.getByText('Namespaces')).toBeInTheDocument();
|
|
76
|
+
|
|
77
|
+
// check that it displays namespaces
|
|
78
|
+
expect(screen.getByText('common')).toBeInTheDocument();
|
|
79
|
+
expect(screen.getByText('one')).toBeInTheDocument();
|
|
80
|
+
expect(screen.getByText('fruits')).toBeInTheDocument();
|
|
81
|
+
expect(screen.getByText('vegetables')).toBeInTheDocument();
|
|
82
|
+
|
|
83
|
+
// check that it renders nodes
|
|
84
|
+
expect(screen.getByText('Test Node')).toBeInTheDocument();
|
|
85
|
+
|
|
86
|
+
// click to open and close tab
|
|
87
|
+
fireEvent.click(screen.getByText('common'));
|
|
88
|
+
fireEvent.click(screen.getByText('common'));
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
afterEach(() => {
|
|
93
|
+
jest.clearAllMocks();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -1,33 +1,67 @@
|
|
|
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';
|
|
5
|
-
import NamespaceHeader from '../../components/NamespaceHeader';
|
|
3
|
+
import { useContext, useEffect, useState } from 'react';
|
|
6
4
|
import NodeStatus from '../NodePage/NodeStatus';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
djNode.dimensions = metricNode.dimensions;
|
|
13
|
-
}
|
|
14
|
-
return djNode;
|
|
15
|
-
}
|
|
5
|
+
import DJClientContext from '../../providers/djclient';
|
|
6
|
+
import Explorer from '../NamespacePage/Explorer';
|
|
7
|
+
import EditIcon from '../../icons/EditIcon';
|
|
8
|
+
import DeleteNode from '../../components/DeleteNode';
|
|
9
|
+
import AddNamespacePopover from './AddNamespacePopover';
|
|
16
10
|
|
|
17
11
|
export function NamespacePage() {
|
|
18
|
-
const
|
|
12
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
13
|
+
var { namespace } = useParams();
|
|
19
14
|
|
|
20
15
|
const [state, setState] = useState({
|
|
21
16
|
namespace: namespace,
|
|
22
17
|
nodes: [],
|
|
23
18
|
});
|
|
24
19
|
|
|
20
|
+
const [namespaceHierarchy, setNamespaceHierarchy] = useState([]);
|
|
21
|
+
|
|
22
|
+
const createNamespaceHierarchy = namespaceList => {
|
|
23
|
+
const hierarchy = [];
|
|
24
|
+
|
|
25
|
+
for (const item of namespaceList) {
|
|
26
|
+
const namespaces = item.namespace.split('.');
|
|
27
|
+
let currentLevel = hierarchy;
|
|
28
|
+
|
|
29
|
+
let path = '';
|
|
30
|
+
for (const ns of namespaces) {
|
|
31
|
+
path += ns;
|
|
32
|
+
|
|
33
|
+
let existingNamespace = currentLevel.find(el => el.namespace === ns);
|
|
34
|
+
if (!existingNamespace) {
|
|
35
|
+
existingNamespace = {
|
|
36
|
+
namespace: ns,
|
|
37
|
+
children: [],
|
|
38
|
+
path: path,
|
|
39
|
+
};
|
|
40
|
+
currentLevel.push(existingNamespace);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
currentLevel = existingNamespace.children;
|
|
44
|
+
path += '.';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return hierarchy;
|
|
48
|
+
};
|
|
49
|
+
|
|
25
50
|
useEffect(() => {
|
|
26
51
|
const fetchData = async () => {
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
52
|
+
const namespaces = await djClient.namespaces();
|
|
53
|
+
const hierarchy = createNamespaceHierarchy(namespaces);
|
|
54
|
+
setNamespaceHierarchy(hierarchy);
|
|
55
|
+
};
|
|
56
|
+
fetchData().catch(console.error);
|
|
57
|
+
}, [djClient, djClient.namespaces]);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const fetchData = async () => {
|
|
61
|
+
if (namespace === undefined && namespaceHierarchy !== undefined) {
|
|
62
|
+
namespace = namespaceHierarchy[0].namespace;
|
|
63
|
+
}
|
|
64
|
+
const nodes = await djClient.namespace(namespace);
|
|
31
65
|
const foundNodes = await Promise.all(nodes);
|
|
32
66
|
setState({
|
|
33
67
|
namespace: namespace,
|
|
@@ -35,16 +69,13 @@ export function NamespacePage() {
|
|
|
35
69
|
});
|
|
36
70
|
};
|
|
37
71
|
fetchData().catch(console.error);
|
|
38
|
-
}, [namespace]);
|
|
72
|
+
}, [djClient, namespace, namespaceHierarchy]);
|
|
39
73
|
|
|
40
74
|
const nodesList = state.nodes.map(node => (
|
|
41
75
|
<tr>
|
|
42
|
-
<td>
|
|
43
|
-
<a href={'/namespaces/' + node.namespace}>{node.namespace}</a>
|
|
44
|
-
</td>
|
|
45
76
|
<td>
|
|
46
77
|
<a href={'/nodes/' + node.name} className="link-table">
|
|
47
|
-
{node.
|
|
78
|
+
{node.name}
|
|
48
79
|
</a>
|
|
49
80
|
<span
|
|
50
81
|
className="rounded-pill badge bg-secondary-soft"
|
|
@@ -53,6 +84,11 @@ export function NamespacePage() {
|
|
|
53
84
|
{node.version}
|
|
54
85
|
</span>
|
|
55
86
|
</td>
|
|
87
|
+
<td>
|
|
88
|
+
<a href={'/nodes/' + node.name} className="link-table">
|
|
89
|
+
{node.type !== 'source' ? node.display_name : ''}
|
|
90
|
+
</a>
|
|
91
|
+
</td>
|
|
56
92
|
<td>
|
|
57
93
|
<span className={'node_type__' + node.type + ' badge node_type'}>
|
|
58
94
|
{node.type}
|
|
@@ -64,26 +100,91 @@ export function NamespacePage() {
|
|
|
64
100
|
<td>
|
|
65
101
|
<span className="status">{node.mode}</span>
|
|
66
102
|
</td>
|
|
103
|
+
<td>
|
|
104
|
+
<span className="status">
|
|
105
|
+
{new Date(node.updated_at).toLocaleString('en-us')}
|
|
106
|
+
</span>
|
|
107
|
+
</td>
|
|
108
|
+
<td>
|
|
109
|
+
<a href={`/nodes/${node?.name}/edit`} style={{ marginLeft: '0.5rem' }}>
|
|
110
|
+
<EditIcon />
|
|
111
|
+
</a>
|
|
112
|
+
<DeleteNode nodeName={node?.name} />
|
|
113
|
+
</td>
|
|
67
114
|
</tr>
|
|
68
115
|
));
|
|
69
116
|
|
|
70
|
-
// @ts-ignore
|
|
71
117
|
return (
|
|
72
118
|
<div className="mid">
|
|
73
|
-
<NamespaceHeader namespace={namespace} />
|
|
74
119
|
<div className="card">
|
|
75
120
|
<div className="card-header">
|
|
76
|
-
<h2>
|
|
121
|
+
<h2>Explore</h2>
|
|
122
|
+
|
|
123
|
+
<span className="menu-link">
|
|
124
|
+
<span className="menu-title">
|
|
125
|
+
<div className="dropdown">
|
|
126
|
+
<span className="add_node">+ Add Node</span>
|
|
127
|
+
<div className="dropdown-content">
|
|
128
|
+
<a href={`/create/source`}>
|
|
129
|
+
<div className="node_type__source node_type_creation_heading">
|
|
130
|
+
Register Table
|
|
131
|
+
</div>
|
|
132
|
+
</a>
|
|
133
|
+
<a href={`/create/transform/${namespace}`}>
|
|
134
|
+
<div className="node_type__transform node_type_creation_heading">
|
|
135
|
+
Transform
|
|
136
|
+
</div>
|
|
137
|
+
</a>
|
|
138
|
+
<a href={`/create/metric/${namespace}`}>
|
|
139
|
+
<div className="node_type__metric node_type_creation_heading">
|
|
140
|
+
Metric
|
|
141
|
+
</div>
|
|
142
|
+
</a>
|
|
143
|
+
<a href={`/create/dimension/${namespace}`}>
|
|
144
|
+
<div className="node_type__dimension node_type_creation_heading">
|
|
145
|
+
Dimension
|
|
146
|
+
</div>
|
|
147
|
+
</a>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</span>
|
|
151
|
+
</span>
|
|
77
152
|
<div className="table-responsive">
|
|
153
|
+
<div className={`sidebar`}>
|
|
154
|
+
<span
|
|
155
|
+
style={{
|
|
156
|
+
textTransform: 'uppercase',
|
|
157
|
+
fontSize: '0.8125rem',
|
|
158
|
+
fontWeight: '600',
|
|
159
|
+
color: '#95aac9',
|
|
160
|
+
padding: '1rem 1rem 1rem 0',
|
|
161
|
+
}}
|
|
162
|
+
>
|
|
163
|
+
Namespaces <AddNamespacePopover />
|
|
164
|
+
</span>
|
|
165
|
+
{namespaceHierarchy
|
|
166
|
+
? namespaceHierarchy.map(child => (
|
|
167
|
+
<Explorer
|
|
168
|
+
item={child}
|
|
169
|
+
current={state.namespace}
|
|
170
|
+
defaultExpand={true}
|
|
171
|
+
/>
|
|
172
|
+
))
|
|
173
|
+
: null}
|
|
174
|
+
</div>
|
|
78
175
|
<table className="card-table table">
|
|
79
176
|
<thead>
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
177
|
+
<tr>
|
|
178
|
+
<th>Name</th>
|
|
179
|
+
<th>Display Name</th>
|
|
180
|
+
<th>Type</th>
|
|
181
|
+
<th>Status</th>
|
|
182
|
+
<th>Mode</th>
|
|
183
|
+
<th>Last Updated</th>
|
|
184
|
+
<th>Actions</th>
|
|
185
|
+
</tr>
|
|
85
186
|
</thead>
|
|
86
|
-
{nodesList}
|
|
187
|
+
<tbody>{nodesList}</tbody>
|
|
87
188
|
</table>
|
|
88
189
|
</div>
|
|
89
190
|
</div>
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
aria-label="code-button"
|
|
14
|
+
tabIndex="0"
|
|
15
|
+
height="45px"
|
|
16
|
+
onClick={() => setCodeAnchor(!codeAnchor)}
|
|
17
|
+
>
|
|
18
|
+
<PythonIcon />
|
|
19
|
+
</button>
|
|
20
|
+
<div
|
|
21
|
+
id={`node-create-code`}
|
|
22
|
+
role="dialog"
|
|
23
|
+
aria-label="client-code"
|
|
24
|
+
style={{ display: codeAnchor === false ? 'none' : 'block' }}
|
|
25
|
+
>
|
|
26
|
+
<SyntaxHighlighter language="python" style={nightOwl}>
|
|
27
|
+
{code}
|
|
28
|
+
</SyntaxHighlighter>
|
|
29
|
+
</div>
|
|
30
|
+
</>
|
|
31
|
+
);
|
|
32
|
+
}
|