datajunction-ui 0.0.1-rc.2 → 0.0.1-rc.22
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/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 +131 -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 +388 -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
|
@@ -1,14 +1,23 @@
|
|
|
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 Tab from '../../components/Tab';
|
|
6
5
|
import NamespaceHeader from '../../components/NamespaceHeader';
|
|
7
6
|
import NodeInfoTab from './NodeInfoTab';
|
|
8
7
|
import NodeColumnTab from './NodeColumnTab';
|
|
9
8
|
import NodeLineage from './NodeGraphTab';
|
|
9
|
+
import NodeHistory from './NodeHistory';
|
|
10
|
+
import DJClientContext from '../../providers/djclient';
|
|
11
|
+
import NodeSQLTab from './NodeSQLTab';
|
|
12
|
+
import NodeMaterializationTab from './NodeMaterializationTab';
|
|
13
|
+
import ClientCodePopover from './ClientCodePopover';
|
|
14
|
+
import NodesWithDimension from './NodesWithDimension';
|
|
15
|
+
import NodeColumnLineage from './NodeLineageTab';
|
|
16
|
+
import EditIcon from '../../icons/EditIcon';
|
|
17
|
+
import AlertIcon from '../../icons/AlertIcon';
|
|
10
18
|
|
|
11
19
|
export function NodePage() {
|
|
20
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
12
21
|
const [state, setState] = useState({
|
|
13
22
|
selectedTab: 0,
|
|
14
23
|
});
|
|
@@ -20,7 +29,7 @@ export function NodePage() {
|
|
|
20
29
|
};
|
|
21
30
|
|
|
22
31
|
const buildTabs = tab => {
|
|
23
|
-
return (
|
|
32
|
+
return tab.display ? (
|
|
24
33
|
<Tab
|
|
25
34
|
key={tab.id}
|
|
26
35
|
id={tab.id}
|
|
@@ -28,72 +37,173 @@ export function NodePage() {
|
|
|
28
37
|
onClick={onClickTab(tab.id)}
|
|
29
38
|
selectedTab={state.selectedTab}
|
|
30
39
|
/>
|
|
31
|
-
);
|
|
40
|
+
) : null;
|
|
32
41
|
};
|
|
33
42
|
|
|
34
43
|
const { name } = useParams();
|
|
35
44
|
|
|
36
45
|
useEffect(() => {
|
|
37
46
|
const fetchData = async () => {
|
|
38
|
-
const data = await
|
|
47
|
+
const data = await djClient.node(name);
|
|
48
|
+
data.createNodeClientCode = await djClient.clientCode(name);
|
|
39
49
|
setNode(data);
|
|
50
|
+
if (data.type === 'metric') {
|
|
51
|
+
const metric = await djClient.metric(name);
|
|
52
|
+
data.dimensions = metric.dimensions;
|
|
53
|
+
setNode(data);
|
|
54
|
+
}
|
|
55
|
+
if (data.type === 'cube') {
|
|
56
|
+
const cube = await djClient.cube(name);
|
|
57
|
+
data.cube_elements = cube.cube_elements;
|
|
58
|
+
setNode(data);
|
|
59
|
+
}
|
|
40
60
|
};
|
|
41
61
|
fetchData().catch(console.error);
|
|
42
|
-
}, [name]);
|
|
62
|
+
}, [djClient, name]);
|
|
63
|
+
|
|
64
|
+
const tabsList = node => {
|
|
65
|
+
return [
|
|
66
|
+
{
|
|
67
|
+
id: 0,
|
|
68
|
+
name: 'Info',
|
|
69
|
+
display: true,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 1,
|
|
73
|
+
name: 'Columns',
|
|
74
|
+
display: true,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 2,
|
|
78
|
+
name: 'Graph',
|
|
79
|
+
display: true,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 3,
|
|
83
|
+
name: 'History',
|
|
84
|
+
display: true,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 4,
|
|
88
|
+
name: 'SQL',
|
|
89
|
+
display: node?.type !== 'dimension' && node?.type !== 'source',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 5,
|
|
93
|
+
name: 'Materializations',
|
|
94
|
+
display: node?.type !== 'source',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 6,
|
|
98
|
+
name: 'Linked Nodes',
|
|
99
|
+
display: node?.type === 'dimension',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 7,
|
|
103
|
+
name: 'Lineage',
|
|
104
|
+
display: node?.type === 'metric',
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
};
|
|
43
108
|
|
|
44
|
-
const TabsJson = [
|
|
45
|
-
{
|
|
46
|
-
id: 0,
|
|
47
|
-
name: 'Info',
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
id: 1,
|
|
51
|
-
name: 'Columns',
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
id: 2,
|
|
55
|
-
name: 'Graph',
|
|
56
|
-
},
|
|
57
|
-
];
|
|
58
109
|
//
|
|
59
110
|
//
|
|
60
111
|
let tabToDisplay = null;
|
|
61
112
|
switch (state.selectedTab) {
|
|
62
113
|
case 0:
|
|
63
|
-
tabToDisplay =
|
|
114
|
+
tabToDisplay =
|
|
115
|
+
node && node.message === undefined ? <NodeInfoTab node={node} /> : '';
|
|
64
116
|
break;
|
|
65
117
|
case 1:
|
|
66
|
-
tabToDisplay = <NodeColumnTab node={node} />;
|
|
118
|
+
tabToDisplay = <NodeColumnTab node={node} djClient={djClient} />;
|
|
67
119
|
break;
|
|
68
120
|
case 2:
|
|
69
|
-
tabToDisplay = <NodeLineage djNode={node} />;
|
|
121
|
+
tabToDisplay = <NodeLineage djNode={node} djClient={djClient} />;
|
|
122
|
+
break;
|
|
123
|
+
case 3:
|
|
124
|
+
tabToDisplay = <NodeHistory node={node} djClient={djClient} />;
|
|
125
|
+
break;
|
|
126
|
+
case 4:
|
|
127
|
+
tabToDisplay =
|
|
128
|
+
node?.type === 'metric' ? <NodeSQLTab djNode={node} /> : <br />;
|
|
129
|
+
break;
|
|
130
|
+
case 5:
|
|
131
|
+
tabToDisplay = <NodeMaterializationTab node={node} djClient={djClient} />;
|
|
70
132
|
break;
|
|
71
|
-
|
|
133
|
+
case 6:
|
|
134
|
+
tabToDisplay = <NodesWithDimension node={node} djClient={djClient} />;
|
|
135
|
+
break;
|
|
136
|
+
case 7:
|
|
137
|
+
tabToDisplay = <NodeColumnLineage djNode={node} djClient={djClient} />;
|
|
138
|
+
break;
|
|
139
|
+
default: /* istanbul ignore next */
|
|
72
140
|
tabToDisplay = <NodeInfoTab node={node} />;
|
|
73
141
|
}
|
|
74
|
-
|
|
75
142
|
// @ts-ignore
|
|
76
143
|
return (
|
|
77
144
|
<div className="node__header">
|
|
78
145
|
<NamespaceHeader namespace={name.split('.').slice(0, -1).join('.')} />
|
|
79
146
|
<div className="card">
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
147
|
+
{node?.message === undefined ? (
|
|
148
|
+
<div className="card-header">
|
|
149
|
+
<h3
|
|
150
|
+
className="card-title align-items-start flex-column"
|
|
151
|
+
style={{ display: 'inline-block' }}
|
|
152
|
+
>
|
|
153
|
+
<span
|
|
154
|
+
className="card-label fw-bold text-gray-800"
|
|
155
|
+
role="dialog"
|
|
156
|
+
aria-hidden="false"
|
|
157
|
+
aria-label="DisplayName"
|
|
158
|
+
>
|
|
159
|
+
{node?.display_name}{' '}
|
|
160
|
+
<span
|
|
161
|
+
className={'node_type__' + node?.type + ' badge node_type'}
|
|
162
|
+
role="dialog"
|
|
163
|
+
aria-hidden="false"
|
|
164
|
+
aria-label="NodeType"
|
|
165
|
+
>
|
|
166
|
+
{node?.type}
|
|
167
|
+
</span>
|
|
168
|
+
</span>
|
|
169
|
+
</h3>
|
|
170
|
+
<a
|
|
171
|
+
href={`/nodes/${node?.name}/edit`}
|
|
172
|
+
style={{ marginLeft: '0.5rem' }}
|
|
173
|
+
>
|
|
174
|
+
<EditIcon />
|
|
175
|
+
</a>
|
|
176
|
+
<ClientCodePopover code={node?.createNodeClientCode} />
|
|
177
|
+
<div>
|
|
178
|
+
<a
|
|
179
|
+
href={'/nodes/' + node?.name}
|
|
180
|
+
className="link-table"
|
|
181
|
+
role="dialog"
|
|
182
|
+
aria-hidden="false"
|
|
183
|
+
aria-label="NodeName"
|
|
184
|
+
>
|
|
185
|
+
{node?.name}
|
|
186
|
+
</a>
|
|
187
|
+
<span
|
|
188
|
+
className="rounded-pill badge bg-secondary-soft"
|
|
189
|
+
style={{ marginLeft: '0.5rem' }}
|
|
190
|
+
>
|
|
191
|
+
{node?.version}
|
|
192
|
+
</span>
|
|
193
|
+
</div>
|
|
194
|
+
<div className="align-items-center row">
|
|
195
|
+
{tabsList(node).map(buildTabs)}
|
|
196
|
+
</div>
|
|
197
|
+
{tabToDisplay}
|
|
198
|
+
</div>
|
|
199
|
+
) : node?.message !== undefined ? (
|
|
200
|
+
<div className="message alert" style={{ margin: '20px' }}>
|
|
201
|
+
<AlertIcon />
|
|
202
|
+
Node `{name}` does not exist!
|
|
94
203
|
</div>
|
|
95
|
-
|
|
96
|
-
|
|
204
|
+
) : (
|
|
205
|
+
''
|
|
206
|
+
)}
|
|
97
207
|
</div>
|
|
98
208
|
</div>
|
|
99
209
|
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import { NotFoundPage } from '../index';
|
|
4
|
+
import { HelmetProvider } from 'react-helmet-async';
|
|
5
|
+
|
|
6
|
+
describe('<NotFoundPage />', () => {
|
|
7
|
+
it('displays the correct 404 message ', () => {
|
|
8
|
+
const { getByText } = render(
|
|
9
|
+
<HelmetProvider>
|
|
10
|
+
<NotFoundPage />
|
|
11
|
+
</HelmetProvider>,
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
expect(getByText('Page not found.')).toBeInTheDocument();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asynchronously loads the component for the Node page
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { lazyLoad } from '../../../utils/loadable';
|
|
7
|
+
|
|
8
|
+
export const RegisterTablePage = () => {
|
|
9
|
+
return lazyLoad(
|
|
10
|
+
() => import('./index'),
|
|
11
|
+
module => module.RegisterTablePage,
|
|
12
|
+
{
|
|
13
|
+
fallback: <div></div>,
|
|
14
|
+
},
|
|
15
|
+
)();
|
|
16
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node add + edit page for transforms, metrics, and dimensions. The creation and edit flow for these
|
|
3
|
+
* node types is largely the same, with minor differences handled server-side. For the `query`
|
|
4
|
+
* field, this page will render a CodeMirror SQL editor with autocompletion and syntax highlighting.
|
|
5
|
+
*/
|
|
6
|
+
import { ErrorMessage, Field, Form, Formik } from 'formik';
|
|
7
|
+
|
|
8
|
+
import NamespaceHeader from '../../components/NamespaceHeader';
|
|
9
|
+
import React, { useContext, useEffect, useState } from 'react';
|
|
10
|
+
import DJClientContext from '../../providers/djclient';
|
|
11
|
+
import 'styles/node-creation.scss';
|
|
12
|
+
import AlertIcon from '../../icons/AlertIcon';
|
|
13
|
+
import ValidIcon from '../../icons/ValidIcon';
|
|
14
|
+
import { FormikSelect } from '../AddEditNodePage/FormikSelect';
|
|
15
|
+
|
|
16
|
+
export function RegisterTablePage() {
|
|
17
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
18
|
+
const [catalogs, setCatalogs] = useState([]);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const fetchData = async () => {
|
|
22
|
+
const catalogs = await djClient.catalogs();
|
|
23
|
+
setCatalogs(
|
|
24
|
+
catalogs.map(catalog => {
|
|
25
|
+
return { value: catalog.name, label: catalog.name };
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
fetchData().catch(console.error);
|
|
30
|
+
}, [djClient, djClient.namespaces]);
|
|
31
|
+
|
|
32
|
+
const initialValues = {
|
|
33
|
+
catalog: '',
|
|
34
|
+
schema: '',
|
|
35
|
+
table: '',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const validator = values => {
|
|
39
|
+
const errors = {};
|
|
40
|
+
if (!values.table) {
|
|
41
|
+
errors.table = 'Required';
|
|
42
|
+
}
|
|
43
|
+
if (!values.schema) {
|
|
44
|
+
errors.schema = 'Required';
|
|
45
|
+
}
|
|
46
|
+
return errors;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handleSubmit = async (values, { setSubmitting, setStatus }) => {
|
|
50
|
+
const { status, json } = await djClient.registerTable(
|
|
51
|
+
values.catalog,
|
|
52
|
+
values.schema,
|
|
53
|
+
values.table,
|
|
54
|
+
);
|
|
55
|
+
if (status === 200 || status === 201) {
|
|
56
|
+
setStatus({
|
|
57
|
+
success: (
|
|
58
|
+
<>
|
|
59
|
+
Successfully registered source node{' '}
|
|
60
|
+
<a href={`/nodes/${json.name}`}>{json.name}</a>, which references
|
|
61
|
+
table {values.catalog}.{values.schema}.{values.table}.
|
|
62
|
+
</>
|
|
63
|
+
),
|
|
64
|
+
});
|
|
65
|
+
} else {
|
|
66
|
+
setStatus({
|
|
67
|
+
failure: `${json.message}`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
setSubmitting(false);
|
|
71
|
+
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const displayMessageAfterSubmit = status => {
|
|
75
|
+
return status?.success !== undefined ? (
|
|
76
|
+
<div className="message success">
|
|
77
|
+
<ValidIcon />
|
|
78
|
+
{status?.success}
|
|
79
|
+
</div>
|
|
80
|
+
) : status?.failure !== undefined ? (
|
|
81
|
+
alertMessage(status?.failure)
|
|
82
|
+
) : (
|
|
83
|
+
''
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const alertMessage = message => {
|
|
88
|
+
return (
|
|
89
|
+
<div className="message alert">
|
|
90
|
+
<AlertIcon />
|
|
91
|
+
{message}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className="mid">
|
|
98
|
+
<NamespaceHeader namespace="" />
|
|
99
|
+
<div className="card">
|
|
100
|
+
<div className="card-header">
|
|
101
|
+
<h2>
|
|
102
|
+
Register{' '}
|
|
103
|
+
<span className={`node_type__source node_type_creation_heading`}>
|
|
104
|
+
Source
|
|
105
|
+
</span>
|
|
106
|
+
</h2>
|
|
107
|
+
<center>
|
|
108
|
+
<Formik
|
|
109
|
+
initialValues={initialValues}
|
|
110
|
+
validate={validator}
|
|
111
|
+
onSubmit={handleSubmit}
|
|
112
|
+
>
|
|
113
|
+
{function Render({ isSubmitting, status, setFieldValue }) {
|
|
114
|
+
return (
|
|
115
|
+
<Form>
|
|
116
|
+
{displayMessageAfterSubmit(status)}
|
|
117
|
+
{
|
|
118
|
+
<>
|
|
119
|
+
<div className="SourceCreationInput">
|
|
120
|
+
<ErrorMessage name="catalog" component="span" />
|
|
121
|
+
<label htmlFor="catalog">Catalog</label>
|
|
122
|
+
<FormikSelect
|
|
123
|
+
selectOptions={catalogs}
|
|
124
|
+
formikFieldName="catalog"
|
|
125
|
+
placeholder="Choose Catalog"
|
|
126
|
+
defaultValue={catalogs[0]}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
<div className="SourceCreationInput">
|
|
130
|
+
<ErrorMessage name="schema" component="span" />
|
|
131
|
+
<label htmlFor="schema">Schema</label>
|
|
132
|
+
<Field
|
|
133
|
+
type="text"
|
|
134
|
+
name="schema"
|
|
135
|
+
id="schema"
|
|
136
|
+
placeholder="Schema"
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
<div className="SourceCreationInput NodeCreationInput">
|
|
140
|
+
<ErrorMessage name="table" component="span" />
|
|
141
|
+
<label htmlFor="table">Table</label>
|
|
142
|
+
<Field
|
|
143
|
+
type="text"
|
|
144
|
+
name="table"
|
|
145
|
+
id="table"
|
|
146
|
+
placeholder="Table"
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
<button type="submit" disabled={isSubmitting}>
|
|
150
|
+
Register
|
|
151
|
+
</button>
|
|
152
|
+
</>
|
|
153
|
+
}
|
|
154
|
+
</Form>
|
|
155
|
+
);
|
|
156
|
+
}}
|
|
157
|
+
</Formik>
|
|
158
|
+
</center>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import { Root } from '../index';
|
|
4
|
+
import DJClientContext from '../../../providers/djclient';
|
|
5
|
+
import { HelmetProvider } from 'react-helmet-async';
|
|
6
|
+
|
|
7
|
+
describe('<Root />', () => {
|
|
8
|
+
const mockDjClient = {
|
|
9
|
+
logout: jest.fn(),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
it('renders with the correct title and navigation', async () => {
|
|
13
|
+
render(
|
|
14
|
+
<HelmetProvider>
|
|
15
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
16
|
+
<Root />
|
|
17
|
+
</DJClientContext.Provider>
|
|
18
|
+
</HelmetProvider>,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
waitFor(() => {
|
|
22
|
+
expect(document.title).toEqual('DataJunction');
|
|
23
|
+
const metaDescription = document.querySelector(
|
|
24
|
+
"meta[name='description']",
|
|
25
|
+
);
|
|
26
|
+
expect(metaDescription).toBeInTheDocument();
|
|
27
|
+
expect(metaDescription.content).toBe(
|
|
28
|
+
'DataJunction Metrics Platform Webapp',
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
expect(screen.getByText(/^DataJunction$/)).toBeInTheDocument();
|
|
32
|
+
expect(screen.getByText('Explore').closest('a')).toHaveAttribute(
|
|
33
|
+
'href',
|
|
34
|
+
'/',
|
|
35
|
+
);
|
|
36
|
+
expect(screen.getByText('SQL').closest('a')).toHaveAttribute(
|
|
37
|
+
'href',
|
|
38
|
+
'/sql',
|
|
39
|
+
);
|
|
40
|
+
expect(screen.getByText('Docs').closest('a')).toHaveAttribute(
|
|
41
|
+
'href',
|
|
42
|
+
'https://www.datajunction.io',
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('renders Logout button unless REACT_DISABLE_AUTH is true', () => {
|
|
48
|
+
process.env.REACT_DISABLE_AUTH = 'false';
|
|
49
|
+
render(
|
|
50
|
+
<HelmetProvider>
|
|
51
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
52
|
+
<Root />
|
|
53
|
+
</DJClientContext.Provider>
|
|
54
|
+
</HelmetProvider>,
|
|
55
|
+
);
|
|
56
|
+
expect(screen.getByText('Logout')).toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('calls logout and reloads window on logout button click', () => {
|
|
60
|
+
process.env.REACT_DISABLE_AUTH = 'false';
|
|
61
|
+
const originalLocation = window.location;
|
|
62
|
+
delete window.location;
|
|
63
|
+
window.location = { ...originalLocation, reload: jest.fn() };
|
|
64
|
+
|
|
65
|
+
render(
|
|
66
|
+
<HelmetProvider>
|
|
67
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
68
|
+
<Root />
|
|
69
|
+
</DJClientContext.Provider>
|
|
70
|
+
</HelmetProvider>,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
screen.getByText('Logout').click();
|
|
74
|
+
expect(mockDjClient.logout).toHaveBeenCalled();
|
|
75
|
+
window.location = originalLocation;
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
1
2
|
import { Outlet } from 'react-router-dom';
|
|
2
|
-
import
|
|
3
|
+
import DJLogo from '../../icons/DJLogo';
|
|
3
4
|
import { Helmet } from 'react-helmet-async';
|
|
5
|
+
import DJClientContext from '../../providers/djclient';
|
|
4
6
|
|
|
5
7
|
export function Root() {
|
|
8
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
9
|
+
|
|
10
|
+
const handleLogout = async () => {
|
|
11
|
+
await djClient.logout();
|
|
12
|
+
window.location.reload();
|
|
13
|
+
};
|
|
6
14
|
return (
|
|
7
15
|
<>
|
|
8
16
|
<Helmet>
|
|
@@ -16,8 +24,8 @@ export function Root() {
|
|
|
16
24
|
<div className="header">
|
|
17
25
|
<div className="logo">
|
|
18
26
|
<h2>
|
|
19
|
-
<
|
|
20
|
-
|
|
27
|
+
<DJLogo />
|
|
28
|
+
Data<b>Junction</b>
|
|
21
29
|
</h2>
|
|
22
30
|
</div>
|
|
23
31
|
<div className="menu">
|
|
@@ -29,12 +37,32 @@ export function Root() {
|
|
|
29
37
|
</span>
|
|
30
38
|
<span className="menu-link">
|
|
31
39
|
<span className="menu-title">
|
|
32
|
-
<a href="/">
|
|
40
|
+
<a href="/sql">SQL</a>
|
|
41
|
+
</span>
|
|
42
|
+
</span>
|
|
43
|
+
<span className="menu-link">
|
|
44
|
+
<span className="menu-title">
|
|
45
|
+
<a
|
|
46
|
+
href="https://www.datajunction.io"
|
|
47
|
+
target="_blank"
|
|
48
|
+
rel="noreferrer"
|
|
49
|
+
>
|
|
50
|
+
Docs
|
|
51
|
+
</a>
|
|
33
52
|
</span>
|
|
34
53
|
</span>
|
|
35
54
|
</div>
|
|
36
55
|
</div>
|
|
37
56
|
</div>
|
|
57
|
+
{process.env.REACT_DISABLE_AUTH === 'true' ? (
|
|
58
|
+
''
|
|
59
|
+
) : (
|
|
60
|
+
<span className="menu-link">
|
|
61
|
+
<span className="menu-title">
|
|
62
|
+
<button onClick={handleLogout}>Logout</button>
|
|
63
|
+
</span>
|
|
64
|
+
</span>
|
|
65
|
+
)}
|
|
38
66
|
</div>
|
|
39
67
|
<Outlet />
|
|
40
68
|
</>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asynchronously loads the component for the Node page
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { lazyLoad } from '../../../utils/loadable';
|
|
7
|
+
|
|
8
|
+
export const SQLBuilderPage = props => {
|
|
9
|
+
return lazyLoad(
|
|
10
|
+
() => import('./index'),
|
|
11
|
+
module => module.SQLBuilderPage,
|
|
12
|
+
{
|
|
13
|
+
fallback: <div></div>,
|
|
14
|
+
},
|
|
15
|
+
)(props);
|
|
16
|
+
};
|