datajunction-ui 0.0.1-a1
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/.babel-plugin-macrosrc.js +5 -0
- package/.env +3 -0
- package/.eslintrc.js +20 -0
- package/.gitattributes +201 -0
- package/.husky/pre-commit +6 -0
- package/.nvmrc +1 -0
- package/.prettierignore +6 -0
- package/.prettierrc +9 -0
- package/.stylelintrc +7 -0
- package/LICENSE +22 -0
- package/Makefile +3 -0
- package/README.md +10 -0
- package/dj-logo.svg +10 -0
- package/internals/testing/loadable.mock.tsx +6 -0
- package/package.json +189 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +26 -0
- package/public/manifest.json +15 -0
- package/public/robots.txt +3 -0
- package/src/__tests__/reportWebVitals.test.jsx +44 -0
- package/src/app/__tests__/__snapshots__/index.test.tsx.snap +9 -0
- package/src/app/__tests__/index.test.tsx +14 -0
- package/src/app/components/DeleteNode.jsx +55 -0
- package/src/app/components/ListGroupItem.jsx +24 -0
- package/src/app/components/NamespaceHeader.jsx +31 -0
- package/src/app/components/QueryInfo.jsx +77 -0
- package/src/app/components/Tab.jsx +25 -0
- package/src/app/components/ToggleSwitch.jsx +20 -0
- package/src/app/components/__tests__/DeleteNode.test.jsx +53 -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__/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 +29 -0
- package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +47 -0
- package/src/app/components/djgraph/Collapse.jsx +46 -0
- package/src/app/components/djgraph/DJNode.jsx +89 -0
- 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__/DJNode.test.tsx +24 -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 +117 -0
- 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/LoadingIcon.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 +108 -0
- 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 +103 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +132 -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 +54 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
- package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +180 -0
- package/src/app/pages/AddEditNodePage/index.jsx +396 -0
- package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
- package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
- package/src/app/pages/AddEditTagPage/index.jsx +132 -0
- package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
- package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
- package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -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 +17 -0
- package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
- package/src/app/pages/NamespacePage/Explorer.jsx +57 -0
- package/src/app/pages/NamespacePage/Loadable.jsx +16 -0
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +217 -0
- package/src/app/pages/NamespacePage/index.jsx +199 -0
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +166 -0
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +161 -0
- package/src/app/pages/NodePage/ClientCodePopover.jsx +46 -0
- package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +149 -0
- package/src/app/pages/NodePage/Loadable.jsx +16 -0
- package/src/app/pages/NodePage/NodeColumnTab.jsx +200 -0
- package/src/app/pages/NodePage/NodeGraphTab.jsx +112 -0
- package/src/app/pages/NodePage/NodeHistory.jsx +212 -0
- package/src/app/pages/NodePage/NodeInfoTab.jsx +212 -0
- package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +233 -0
- package/src/app/pages/NodePage/NodeSQLTab.jsx +82 -0
- package/src/app/pages/NodePage/NodeStatus.jsx +28 -0
- package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
- package/src/app/pages/NodePage/PartitionColumnPopover.jsx +153 -0
- package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +47 -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 +757 -0
- package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +403 -0
- package/src/app/pages/NodePage/index.jsx +210 -0
- package/src/app/pages/NotFoundPage/Loadable.tsx +14 -0
- package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
- package/src/app/pages/NotFoundPage/index.tsx +23 -0
- package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
- package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +110 -0
- package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +37 -0
- package/src/app/pages/RegisterTablePage/index.jsx +142 -0
- package/src/app/pages/Root/Loadable.tsx +14 -0
- package/src/app/pages/Root/__tests__/index.test.jsx +77 -0
- package/src/app/pages/Root/assets/dj-logo.png +0 -0
- package/src/app/pages/Root/index.tsx +70 -0
- 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/pages/TagPage/Loadable.jsx +16 -0
- package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
- package/src/app/pages/TagPage/index.jsx +79 -0
- package/src/app/providers/djclient.jsx +5 -0
- package/src/app/services/DJService.js +665 -0
- package/src/app/services/__tests__/DJService.test.jsx +804 -0
- package/src/index.tsx +48 -0
- package/src/mocks/mockNodes.jsx +1430 -0
- package/src/react-app-env.d.ts +4 -0
- package/src/reportWebVitals.ts +15 -0
- package/src/setupTests.ts +36 -0
- package/src/styles/dag.css +228 -0
- package/src/styles/index.css +1083 -0
- package/src/styles/loading.css +34 -0
- package/src/styles/login.css +81 -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/__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/form.jsx +23 -0
- package/src/utils/loadable.tsx +30 -0
- package/src/utils/request.ts +54 -0
- package/tsconfig.json +34 -0
- package/webpack.config.js +118 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import DJClientContext from '../providers/djclient';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import DeleteIcon from '../icons/DeleteIcon';
|
|
4
|
+
import { Form, Formik } from 'formik';
|
|
5
|
+
import { useContext } from 'react';
|
|
6
|
+
import { displayMessageAfterSubmit } from '../../utils/form';
|
|
7
|
+
|
|
8
|
+
export default function DeleteNode({ nodeName }) {
|
|
9
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
10
|
+
const deleteNode = async (values, { setSubmitting, setStatus }) => {
|
|
11
|
+
const { status, json } = await djClient.deactivate(values.nodeName);
|
|
12
|
+
if (status === 200 || status === 201 || status === 204) {
|
|
13
|
+
setStatus({
|
|
14
|
+
success: <>Successfully deleted node {values.nodeName}</>,
|
|
15
|
+
});
|
|
16
|
+
} else {
|
|
17
|
+
setStatus({
|
|
18
|
+
failure: `${json.message}`,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
setSubmitting(false);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const initialValues = {
|
|
25
|
+
nodeName: nodeName,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Formik initialValues={initialValues} onSubmit={deleteNode}>
|
|
30
|
+
{function Render({ isSubmitting, status, setFieldValue }) {
|
|
31
|
+
return (
|
|
32
|
+
<Form className="deleteNode">
|
|
33
|
+
{displayMessageAfterSubmit(status)}
|
|
34
|
+
{
|
|
35
|
+
<>
|
|
36
|
+
<button
|
|
37
|
+
type="submit"
|
|
38
|
+
disabled={isSubmitting}
|
|
39
|
+
style={{
|
|
40
|
+
marginLeft: 0,
|
|
41
|
+
all: 'unset',
|
|
42
|
+
color: '#005c72',
|
|
43
|
+
cursor: 'pointer',
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
<DeleteIcon />
|
|
47
|
+
</button>
|
|
48
|
+
</>
|
|
49
|
+
}
|
|
50
|
+
</Form>
|
|
51
|
+
);
|
|
52
|
+
}}
|
|
53
|
+
</Formik>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Component } from 'react';
|
|
2
|
+
|
|
3
|
+
export default class ListGroupItem extends Component {
|
|
4
|
+
render() {
|
|
5
|
+
const { label, value } = this.props;
|
|
6
|
+
return (
|
|
7
|
+
<div className="list-group-item d-flex">
|
|
8
|
+
<div className="d-flex gap-2 w-100 justify-content-between py-3">
|
|
9
|
+
<div>
|
|
10
|
+
<h6 className="mb-0 w-100">{label}</h6>
|
|
11
|
+
<p
|
|
12
|
+
className="mb-0 opacity-75"
|
|
13
|
+
role="dialog"
|
|
14
|
+
aria-hidden="false"
|
|
15
|
+
aria-label={label}
|
|
16
|
+
>
|
|
17
|
+
{value}
|
|
18
|
+
</p>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Component } from 'react';
|
|
2
|
+
import HorizontalHierarchyIcon from '../icons/HorizontalHierarchyIcon';
|
|
3
|
+
|
|
4
|
+
export default class NamespaceHeader extends Component {
|
|
5
|
+
render() {
|
|
6
|
+
const { namespace } = this.props;
|
|
7
|
+
const namespaceParts = namespace.split('.');
|
|
8
|
+
const namespaceList = namespaceParts.map((piece, index) => {
|
|
9
|
+
return (
|
|
10
|
+
<li className="breadcrumb-item" key={index}>
|
|
11
|
+
<a
|
|
12
|
+
className="link-body-emphasis"
|
|
13
|
+
href={'/namespaces/' + namespaceParts.slice(0, index + 1).join('.')}
|
|
14
|
+
>
|
|
15
|
+
{piece}
|
|
16
|
+
</a>
|
|
17
|
+
</li>
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
return (
|
|
21
|
+
<ol className="breadcrumb breadcrumb-chevron p-3 bg-body-tertiary rounded-3">
|
|
22
|
+
<li className="breadcrumb-item">
|
|
23
|
+
<a href="/">
|
|
24
|
+
<HorizontalHierarchyIcon />
|
|
25
|
+
</a>
|
|
26
|
+
</li>
|
|
27
|
+
{namespaceList}
|
|
28
|
+
</ol>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export default function QueryInfo({
|
|
2
|
+
id,
|
|
3
|
+
state,
|
|
4
|
+
engine_name,
|
|
5
|
+
engine_version,
|
|
6
|
+
errors,
|
|
7
|
+
links,
|
|
8
|
+
output_table,
|
|
9
|
+
scheduled,
|
|
10
|
+
started,
|
|
11
|
+
numRows,
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="table-responsive">
|
|
15
|
+
<table className="card-inner-table table">
|
|
16
|
+
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
17
|
+
<tr>
|
|
18
|
+
<th>Query ID</th>
|
|
19
|
+
<th>Engine</th>
|
|
20
|
+
<th>State</th>
|
|
21
|
+
<th>Scheduled</th>
|
|
22
|
+
<th>Started</th>
|
|
23
|
+
<th>Errors</th>
|
|
24
|
+
<th>Links</th>
|
|
25
|
+
<th>Output Table</th>
|
|
26
|
+
<th>Number of Rows</th>
|
|
27
|
+
</tr>
|
|
28
|
+
</thead>
|
|
29
|
+
<tbody>
|
|
30
|
+
<tr>
|
|
31
|
+
<td>
|
|
32
|
+
<span className="rounded-pill badge bg-secondary-soft">{id}</span>
|
|
33
|
+
</td>
|
|
34
|
+
<td>
|
|
35
|
+
<span className="rounded-pill badge bg-secondary-soft">
|
|
36
|
+
{engine_name}
|
|
37
|
+
{' - '}
|
|
38
|
+
{engine_version}
|
|
39
|
+
</span>
|
|
40
|
+
</td>
|
|
41
|
+
<td>{state}</td>
|
|
42
|
+
<td>{scheduled}</td>
|
|
43
|
+
<td>{started}</td>
|
|
44
|
+
<td>
|
|
45
|
+
{errors?.length ? (
|
|
46
|
+
errors.map((e, idx) => (
|
|
47
|
+
<p key={`error-${idx}`}>
|
|
48
|
+
<span className="rounded-pill badge bg-secondary-error">
|
|
49
|
+
{e}
|
|
50
|
+
</span>
|
|
51
|
+
</p>
|
|
52
|
+
))
|
|
53
|
+
) : (
|
|
54
|
+
<></>
|
|
55
|
+
)}
|
|
56
|
+
</td>
|
|
57
|
+
<td>
|
|
58
|
+
{links?.length ? (
|
|
59
|
+
links.map((link, idx) => (
|
|
60
|
+
<p key={idx}>
|
|
61
|
+
<a href={link} target="_blank" rel="noreferrer">
|
|
62
|
+
{link}
|
|
63
|
+
</a>
|
|
64
|
+
</p>
|
|
65
|
+
))
|
|
66
|
+
) : (
|
|
67
|
+
<></>
|
|
68
|
+
)}
|
|
69
|
+
</td>
|
|
70
|
+
<td>{output_table}</td>
|
|
71
|
+
<td>{numRows}</td>
|
|
72
|
+
</tr>
|
|
73
|
+
</tbody>
|
|
74
|
+
</table>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Component } from 'react';
|
|
2
|
+
|
|
3
|
+
export default class Tab extends Component {
|
|
4
|
+
render() {
|
|
5
|
+
const { id, onClick, selectedTab } = this.props;
|
|
6
|
+
return (
|
|
7
|
+
<div className={selectedTab === id ? 'col active' : 'col'}>
|
|
8
|
+
<div className="header-tabs nav-overflow nav nav-tabs">
|
|
9
|
+
<div className="nav-item">
|
|
10
|
+
<button
|
|
11
|
+
id={id}
|
|
12
|
+
className="nav-link"
|
|
13
|
+
tabIndex="0"
|
|
14
|
+
onClick={onClick}
|
|
15
|
+
aria-label={this.props.name}
|
|
16
|
+
aria-hidden="false"
|
|
17
|
+
>
|
|
18
|
+
{this.props.name}
|
|
19
|
+
</button>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const ToggleSwitch = ({ checked, onChange, toggleName }) => (
|
|
4
|
+
<>
|
|
5
|
+
<input
|
|
6
|
+
id="show-compiled-sql-toggle"
|
|
7
|
+
role="checkbox"
|
|
8
|
+
aria-label="ToggleSwitch"
|
|
9
|
+
aria-hidden="false"
|
|
10
|
+
type="checkbox"
|
|
11
|
+
className="checkbox"
|
|
12
|
+
checked={checked}
|
|
13
|
+
onChange={e => onChange(e.target.checked)}
|
|
14
|
+
/>
|
|
15
|
+
<label htmlFor="show-compiled-sql-toggle" className="switch"></label>{' '}
|
|
16
|
+
{toggleName}
|
|
17
|
+
</>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export default ToggleSwitch;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import fetchMock from 'jest-fetch-mock';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
import { render } from '../../../setupTests';
|
|
6
|
+
import DJClientContext from '../../providers/djclient';
|
|
7
|
+
import DeleteNode from '../DeleteNode';
|
|
8
|
+
|
|
9
|
+
describe('<DeleteNode />', () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
fetchMock.resetMocks();
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
window.scrollTo = jest.fn();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const renderElement = djClient => {
|
|
17
|
+
return render(
|
|
18
|
+
<DJClientContext.Provider value={djClient}>
|
|
19
|
+
<DeleteNode nodeName="default.hard_hat" />
|
|
20
|
+
</DJClientContext.Provider>,
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const initializeMockDJClient = () => {
|
|
25
|
+
return {
|
|
26
|
+
DataJunctionAPI: {
|
|
27
|
+
deactivate: jest.fn(),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
it('deletes a node when clicked', async () => {
|
|
33
|
+
const mockDjClient = initializeMockDJClient();
|
|
34
|
+
mockDjClient.DataJunctionAPI.deactivate.mockReturnValue({
|
|
35
|
+
status: 204,
|
|
36
|
+
json: { name: 'source.warehouse.schema.some_table' },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
renderElement(mockDjClient);
|
|
40
|
+
|
|
41
|
+
await userEvent.click(screen.getByRole('button'));
|
|
42
|
+
|
|
43
|
+
await waitFor(() => {
|
|
44
|
+
expect(mockDjClient.DataJunctionAPI.deactivate).toBeCalled();
|
|
45
|
+
expect(mockDjClient.DataJunctionAPI.deactivate).toBeCalledWith(
|
|
46
|
+
'default.hard_hat',
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
expect(
|
|
50
|
+
screen.getByText('Successfully deleted node default.hard_hat'),
|
|
51
|
+
).toBeInTheDocument();
|
|
52
|
+
}, 60000);
|
|
53
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createRenderer } from 'react-test-renderer/shallow';
|
|
3
|
+
|
|
4
|
+
import ListGroupItem from '../ListGroupItem';
|
|
5
|
+
|
|
6
|
+
const renderer = createRenderer();
|
|
7
|
+
|
|
8
|
+
describe('<ListGroupItem />', () => {
|
|
9
|
+
it('should render and match the snapshot', () => {
|
|
10
|
+
renderer.render(
|
|
11
|
+
<ListGroupItem label="Name" value={<span>Something</span>} />,
|
|
12
|
+
);
|
|
13
|
+
const renderedOutput = renderer.getRenderOutput();
|
|
14
|
+
expect(renderedOutput).toMatchSnapshot();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createRenderer } from 'react-test-renderer/shallow';
|
|
3
|
+
|
|
4
|
+
import NamespaceHeader from '../NamespaceHeader';
|
|
5
|
+
|
|
6
|
+
const renderer = createRenderer();
|
|
7
|
+
|
|
8
|
+
describe('<NamespaceHeader />', () => {
|
|
9
|
+
it('should render and match the snapshot', () => {
|
|
10
|
+
renderer.render(<NamespaceHeader namespace="shared.dimensions.accounts" />);
|
|
11
|
+
const renderedOutput = renderer.getRenderOutput();
|
|
12
|
+
expect(renderedOutput).toMatchSnapshot();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import QueryInfo from '../QueryInfo';
|
|
4
|
+
|
|
5
|
+
describe('<QueryInfo />', () => {
|
|
6
|
+
const defaultProps = {
|
|
7
|
+
id: '123',
|
|
8
|
+
state: 'Running',
|
|
9
|
+
engine_name: 'Spark SQL',
|
|
10
|
+
engine_version: '1.0',
|
|
11
|
+
errors: ['Error 1', 'Error 2'],
|
|
12
|
+
links: ['http://example.com', 'http://example2.com'],
|
|
13
|
+
output_table: 'table1',
|
|
14
|
+
scheduled: '2023-09-06',
|
|
15
|
+
started: '2023-09-07',
|
|
16
|
+
numRows: 1000,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
it('renders without crashing', () => {
|
|
20
|
+
render(<QueryInfo {...defaultProps} />);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('displays correct query information', () => {
|
|
24
|
+
render(<QueryInfo {...defaultProps} />);
|
|
25
|
+
|
|
26
|
+
expect(screen.getByText(defaultProps.id)).toBeInTheDocument();
|
|
27
|
+
expect(
|
|
28
|
+
screen.getByText(
|
|
29
|
+
`${defaultProps.engine_name} - ${defaultProps.engine_version}`,
|
|
30
|
+
),
|
|
31
|
+
).toBeInTheDocument();
|
|
32
|
+
expect(screen.getByText(defaultProps.state)).toBeInTheDocument();
|
|
33
|
+
expect(screen.getByText(defaultProps.scheduled)).toBeInTheDocument();
|
|
34
|
+
expect(screen.getByText(defaultProps.started)).toBeInTheDocument();
|
|
35
|
+
expect(screen.getByText(defaultProps.output_table)).toBeInTheDocument();
|
|
36
|
+
expect(screen.getByText(String(defaultProps.numRows))).toBeInTheDocument();
|
|
37
|
+
defaultProps.errors.forEach(error => {
|
|
38
|
+
expect(screen.getByText(error)).toBeInTheDocument();
|
|
39
|
+
});
|
|
40
|
+
defaultProps.links.forEach(link => {
|
|
41
|
+
expect(screen.getByText(link)).toHaveAttribute('href', link);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('does not render errors and links when they are not provided', () => {
|
|
46
|
+
render(<QueryInfo {...defaultProps} errors={[]} links={[]} />);
|
|
47
|
+
|
|
48
|
+
defaultProps.errors.forEach(error => {
|
|
49
|
+
expect(screen.queryByText(error)).not.toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
defaultProps.links.forEach(link => {
|
|
52
|
+
expect(screen.queryByText(link)).not.toBeInTheDocument();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
|
3
|
+
|
|
4
|
+
import Tab from '../Tab';
|
|
5
|
+
|
|
6
|
+
describe('<Tab />', () => {
|
|
7
|
+
it('renders without crashing', () => {
|
|
8
|
+
render(<Tab />);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('has the active class when selectedTab matches id', () => {
|
|
12
|
+
const { container } = render(<Tab id="1" selectedTab="1" />);
|
|
13
|
+
expect(container.querySelector('.col')).toHaveClass('active');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('does not have the active class when selectedTab does not match id', () => {
|
|
17
|
+
const { container } = render(<Tab id="1" selectedTab="2" />);
|
|
18
|
+
expect(container.querySelector('.col')).not.toHaveClass('active');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('calls onClick when the button is clicked', () => {
|
|
22
|
+
const onClickMock = jest.fn();
|
|
23
|
+
const { getByRole } = render(<Tab id="1" onClick={onClickMock} />);
|
|
24
|
+
fireEvent.click(getByRole('button'));
|
|
25
|
+
expect(onClickMock).toHaveBeenCalledTimes(1);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent, screen } from '@testing-library/react';
|
|
3
|
+
import ToggleSwitch from '../ToggleSwitch';
|
|
4
|
+
|
|
5
|
+
describe('<ToggleSwitch />', () => {
|
|
6
|
+
const defaultProps = {
|
|
7
|
+
checked: false,
|
|
8
|
+
onChange: jest.fn(),
|
|
9
|
+
toggleName: 'Toggle Switch',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
it('renders without crashing', () => {
|
|
13
|
+
render(<ToggleSwitch {...defaultProps} />);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('displays the correct toggle name', () => {
|
|
17
|
+
render(<ToggleSwitch {...defaultProps} />);
|
|
18
|
+
expect(screen.getByText(defaultProps.toggleName)).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('reflects the checked state correctly', () => {
|
|
22
|
+
render(<ToggleSwitch {...defaultProps} checked={true} />);
|
|
23
|
+
const checkbox = screen.getByRole('checkbox');
|
|
24
|
+
expect(checkbox).toBeChecked();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('calls onChange with the correct value when toggled', () => {
|
|
28
|
+
render(<ToggleSwitch {...defaultProps} />);
|
|
29
|
+
const checkbox = screen.getByRole('checkbox');
|
|
30
|
+
|
|
31
|
+
fireEvent.click(checkbox);
|
|
32
|
+
expect(defaultProps.onChange).toHaveBeenCalledWith(true);
|
|
33
|
+
|
|
34
|
+
fireEvent.click(checkbox);
|
|
35
|
+
expect(checkbox).not.toBeChecked();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('is unchecked by default if no checked prop is provided', () => {
|
|
39
|
+
render(<ToggleSwitch onChange={jest.fn()} toggleName="Test Toggle" />);
|
|
40
|
+
const checkbox = screen.getByRole('checkbox');
|
|
41
|
+
expect(checkbox).not.toBeChecked();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<ListGroupItem /> should render and match the snapshot 1`] = `
|
|
4
|
+
<div
|
|
5
|
+
className="list-group-item d-flex"
|
|
6
|
+
>
|
|
7
|
+
<div
|
|
8
|
+
className="d-flex gap-2 w-100 justify-content-between py-3"
|
|
9
|
+
>
|
|
10
|
+
<div>
|
|
11
|
+
<h6
|
|
12
|
+
className="mb-0 w-100"
|
|
13
|
+
>
|
|
14
|
+
Name
|
|
15
|
+
</h6>
|
|
16
|
+
<p
|
|
17
|
+
aria-hidden="false"
|
|
18
|
+
aria-label="Name"
|
|
19
|
+
className="mb-0 opacity-75"
|
|
20
|
+
role="dialog"
|
|
21
|
+
>
|
|
22
|
+
<span>
|
|
23
|
+
Something
|
|
24
|
+
</span>
|
|
25
|
+
</p>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
`;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<NamespaceHeader /> should render and match the snapshot 1`] = `
|
|
4
|
+
<ol
|
|
5
|
+
className="breadcrumb breadcrumb-chevron p-3 bg-body-tertiary rounded-3"
|
|
6
|
+
>
|
|
7
|
+
<li
|
|
8
|
+
className="breadcrumb-item"
|
|
9
|
+
>
|
|
10
|
+
<a
|
|
11
|
+
href="/"
|
|
12
|
+
>
|
|
13
|
+
<HorizontalHierarchyIcon />
|
|
14
|
+
</a>
|
|
15
|
+
</li>
|
|
16
|
+
<li
|
|
17
|
+
className="breadcrumb-item"
|
|
18
|
+
>
|
|
19
|
+
<a
|
|
20
|
+
className="link-body-emphasis"
|
|
21
|
+
href="/namespaces/shared"
|
|
22
|
+
>
|
|
23
|
+
shared
|
|
24
|
+
</a>
|
|
25
|
+
</li>
|
|
26
|
+
<li
|
|
27
|
+
className="breadcrumb-item"
|
|
28
|
+
>
|
|
29
|
+
<a
|
|
30
|
+
className="link-body-emphasis"
|
|
31
|
+
href="/namespaces/shared.dimensions"
|
|
32
|
+
>
|
|
33
|
+
dimensions
|
|
34
|
+
</a>
|
|
35
|
+
</li>
|
|
36
|
+
<li
|
|
37
|
+
className="breadcrumb-item"
|
|
38
|
+
>
|
|
39
|
+
<a
|
|
40
|
+
className="link-body-emphasis"
|
|
41
|
+
href="/namespaces/shared.dimensions.accounts"
|
|
42
|
+
>
|
|
43
|
+
accounts
|
|
44
|
+
</a>
|
|
45
|
+
</li>
|
|
46
|
+
</ol>
|
|
47
|
+
`;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DJNodeDimensions } from './DJNodeDimensions';
|
|
3
|
+
import { DJNodeColumns } from './DJNodeColumns';
|
|
4
|
+
|
|
5
|
+
export default function Collapse({ collapsed, text, data }) {
|
|
6
|
+
const [isCollapsed, setIsCollapsed] = React.useState(collapsed);
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<>
|
|
10
|
+
<div className="collapse">
|
|
11
|
+
{data.type === 'metric' ? (
|
|
12
|
+
<button
|
|
13
|
+
className="collapse-button"
|
|
14
|
+
onClick={() => setIsCollapsed(!isCollapsed)}
|
|
15
|
+
>
|
|
16
|
+
{isCollapsed ? '\u25B6 Show' : '\u25BC Hide'} {text}
|
|
17
|
+
</button>
|
|
18
|
+
) : (
|
|
19
|
+
''
|
|
20
|
+
)}
|
|
21
|
+
<div
|
|
22
|
+
className={`collapse-content ${
|
|
23
|
+
isCollapsed && data.type === 'metric' ? 'collapsed' : 'expanded'
|
|
24
|
+
}`}
|
|
25
|
+
aria-expanded={isCollapsed}
|
|
26
|
+
>
|
|
27
|
+
{data.type !== 'metric'
|
|
28
|
+
? isCollapsed
|
|
29
|
+
? DJNodeColumns({ data: data, limit: 10 })
|
|
30
|
+
: DJNodeColumns({ data: data, limit: 100 })
|
|
31
|
+
: DJNodeDimensions(data)}
|
|
32
|
+
</div>
|
|
33
|
+
{data.type !== 'metric' && data.column_names.length > 10 ? (
|
|
34
|
+
<button
|
|
35
|
+
className="collapse-button"
|
|
36
|
+
onClick={() => setIsCollapsed(!isCollapsed)}
|
|
37
|
+
>
|
|
38
|
+
{isCollapsed ? '\u25B6 More' : '\u25BC Less'} {text}
|
|
39
|
+
</button>
|
|
40
|
+
) : (
|
|
41
|
+
''
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
</>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
2
|
+
import { Handle, Position } from 'reactflow';
|
|
3
|
+
import Collapse from './Collapse';
|
|
4
|
+
|
|
5
|
+
function capitalize(string) {
|
|
6
|
+
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function DJNode({ id, data }) {
|
|
10
|
+
const handleWrapperStyle = {
|
|
11
|
+
display: 'flex',
|
|
12
|
+
position: 'absolute',
|
|
13
|
+
height: '100%',
|
|
14
|
+
flexDirection: 'column',
|
|
15
|
+
top: '50%',
|
|
16
|
+
justifyContent: 'space-between',
|
|
17
|
+
};
|
|
18
|
+
const handleWrapperStyleRight = { ...handleWrapperStyle, ...{ right: 0 } };
|
|
19
|
+
|
|
20
|
+
const handleStyle = {
|
|
21
|
+
width: '12px',
|
|
22
|
+
height: '12px',
|
|
23
|
+
borderRadius: '12px',
|
|
24
|
+
background: 'transparent',
|
|
25
|
+
border: '4px solid transparent',
|
|
26
|
+
cursor: 'pointer',
|
|
27
|
+
position: 'absolute',
|
|
28
|
+
top: '0px',
|
|
29
|
+
left: 0,
|
|
30
|
+
};
|
|
31
|
+
const handleStyleLeft = percentage => {
|
|
32
|
+
return {
|
|
33
|
+
...handleStyle,
|
|
34
|
+
...{
|
|
35
|
+
transform: 'translate(-' + percentage + '%, -50%)',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const highlightNodeClass =
|
|
40
|
+
data.is_current === true ? ' dj-node_highlight' : '';
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
<div
|
|
44
|
+
className={'dj-node__full node_type__' + data.type + highlightNodeClass}
|
|
45
|
+
key={data.name}
|
|
46
|
+
style={{ width: '450px' }}
|
|
47
|
+
>
|
|
48
|
+
<div style={handleWrapperStyle}>
|
|
49
|
+
<Handle
|
|
50
|
+
type="target"
|
|
51
|
+
id={data.name}
|
|
52
|
+
position={Position.Left}
|
|
53
|
+
style={handleStyleLeft(100)}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="dj-node__header">
|
|
57
|
+
<div className="serif">
|
|
58
|
+
{data.name
|
|
59
|
+
.split('.')
|
|
60
|
+
.slice(0, data.name.split('.').length - 1)
|
|
61
|
+
.join(' \u25B6 ')}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div className="dj-node__body">
|
|
65
|
+
<b>{capitalize(data.type)}</b>
|
|
66
|
+
<br />{' '}
|
|
67
|
+
<a href={`/nodes/${data.name}`}>
|
|
68
|
+
{data.type === 'source' ? data.table : data.display_name}
|
|
69
|
+
</a>
|
|
70
|
+
<Collapse
|
|
71
|
+
collapsed={true}
|
|
72
|
+
text={data.type !== 'metric' ? 'columns' : 'dimensions'}
|
|
73
|
+
data={data}
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
<div style={handleWrapperStyleRight}>
|
|
77
|
+
<Handle
|
|
78
|
+
type="source"
|
|
79
|
+
id={data.name}
|
|
80
|
+
position={Position.Right}
|
|
81
|
+
style={handleStyleLeft(90)}
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default memo(DJNode);
|