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
package/src/app/index.tsx
CHANGED
|
@@ -9,37 +9,90 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
|
|
9
9
|
|
|
10
10
|
import { NamespacePage } from './pages/NamespacePage/Loadable';
|
|
11
11
|
import { NodePage } from './pages/NodePage/Loadable';
|
|
12
|
+
import { SQLBuilderPage } from './pages/SQLBuilderPage/Loadable';
|
|
13
|
+
import { AddEditNodePage } from './pages/AddEditNodePage/Loadable';
|
|
12
14
|
import { NotFoundPage } from './pages/NotFoundPage/Loadable';
|
|
15
|
+
import { LoginPage } from './pages/LoginPage';
|
|
16
|
+
import { RegisterTablePage } from './pages/RegisterTablePage';
|
|
13
17
|
import { Root } from './pages/Root/Loadable';
|
|
14
|
-
import
|
|
18
|
+
import DJClientContext from './providers/djclient';
|
|
19
|
+
import { DataJunctionAPI } from './services/DJService';
|
|
20
|
+
import { CookiesProvider, useCookies } from 'react-cookie';
|
|
21
|
+
import * as Constants from './constants';
|
|
15
22
|
|
|
16
23
|
export function App() {
|
|
24
|
+
const [cookies] = useCookies([Constants.DJ_LOGGED_IN_FLAG_COOKIE]);
|
|
17
25
|
return (
|
|
18
|
-
<
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
<CookiesProvider>
|
|
27
|
+
<BrowserRouter>
|
|
28
|
+
{cookies.__djlif || process.env.REACT_DISABLE_AUTH === 'true' ? (
|
|
29
|
+
<>
|
|
30
|
+
<Helmet
|
|
31
|
+
titleTemplate="DataJunction: %s"
|
|
32
|
+
defaultTitle="DataJunction: A Metrics Platform"
|
|
33
|
+
>
|
|
34
|
+
<meta
|
|
35
|
+
name="description"
|
|
36
|
+
content="DataJunction serves as a semantic layer to help manage metrics"
|
|
37
|
+
/>
|
|
38
|
+
</Helmet>
|
|
39
|
+
<DJClientContext.Provider value={{ DataJunctionAPI }}>
|
|
40
|
+
<Routes>
|
|
41
|
+
<Route
|
|
42
|
+
path="/"
|
|
43
|
+
element={<Root />}
|
|
44
|
+
children={
|
|
45
|
+
<>
|
|
46
|
+
<Route path="nodes" key="nodes">
|
|
47
|
+
<Route path=":name" element={<NodePage />} />
|
|
48
|
+
<Route
|
|
49
|
+
path=":name/edit"
|
|
50
|
+
key="edit"
|
|
51
|
+
element={<AddEditNodePage />}
|
|
52
|
+
/>
|
|
53
|
+
</Route>
|
|
28
54
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
<Route path="/" element={<NamespacePage />} key="index" />
|
|
56
|
+
<Route path="namespaces">
|
|
57
|
+
<Route
|
|
58
|
+
path=":namespace"
|
|
59
|
+
element={<NamespacePage />}
|
|
60
|
+
key="namespaces"
|
|
61
|
+
/>
|
|
62
|
+
</Route>
|
|
63
|
+
<Route
|
|
64
|
+
path="create/source"
|
|
65
|
+
key="register"
|
|
66
|
+
element={<RegisterTablePage />}
|
|
67
|
+
></Route>
|
|
68
|
+
<Route path="create/:nodeType">
|
|
69
|
+
<Route
|
|
70
|
+
path=":initialNamespace"
|
|
71
|
+
key="create"
|
|
72
|
+
element={<AddEditNodePage />}
|
|
73
|
+
/>
|
|
74
|
+
<Route
|
|
75
|
+
path=""
|
|
76
|
+
key="create"
|
|
77
|
+
element={<AddEditNodePage />}
|
|
78
|
+
/>
|
|
79
|
+
</Route>
|
|
80
|
+
<Route
|
|
81
|
+
path="sql"
|
|
82
|
+
key="sql"
|
|
83
|
+
element={<SQLBuilderPage />}
|
|
84
|
+
/>
|
|
85
|
+
</>
|
|
86
|
+
}
|
|
87
|
+
/>
|
|
88
|
+
<Route path="*" element={<NotFoundPage />} />
|
|
89
|
+
</Routes>
|
|
90
|
+
</DJClientContext.Provider>
|
|
91
|
+
</>
|
|
92
|
+
) : (
|
|
93
|
+
<LoginPage />
|
|
94
|
+
)}
|
|
95
|
+
</BrowserRouter>
|
|
96
|
+
</CookiesProvider>
|
|
44
97
|
);
|
|
45
98
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A React Select component for use in Formik forms.
|
|
3
|
+
*/
|
|
4
|
+
import { useField } from 'formik';
|
|
5
|
+
import Select from 'react-select';
|
|
6
|
+
|
|
7
|
+
export const FormikSelect = ({
|
|
8
|
+
selectOptions,
|
|
9
|
+
formikFieldName,
|
|
10
|
+
placeholder,
|
|
11
|
+
defaultValue,
|
|
12
|
+
style,
|
|
13
|
+
className = 'SelectInput',
|
|
14
|
+
isMulti = false,
|
|
15
|
+
}) => {
|
|
16
|
+
// eslint-disable-next-line no-unused-vars
|
|
17
|
+
const [field, _, helpers] = useField(formikFieldName);
|
|
18
|
+
const { setValue } = helpers;
|
|
19
|
+
|
|
20
|
+
// handles both multi-select and single-select cases
|
|
21
|
+
const getValue = options => {
|
|
22
|
+
if (options) {
|
|
23
|
+
return isMulti ? options.map(option => option.value) : options.value;
|
|
24
|
+
} else {
|
|
25
|
+
return isMulti ? [] : '';
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Select
|
|
31
|
+
className={className}
|
|
32
|
+
defaultValue={defaultValue}
|
|
33
|
+
options={selectOptions}
|
|
34
|
+
name={field.name}
|
|
35
|
+
placeholder={placeholder}
|
|
36
|
+
onBlur={field.onBlur}
|
|
37
|
+
onChange={selected => setValue(getValue(selected))}
|
|
38
|
+
styles={style}
|
|
39
|
+
isMulti={isMulti}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
FormikSelect.defaultProps = {
|
|
45
|
+
placeholder: '',
|
|
46
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A field for the full node name, which is generated based on the node's input
|
|
3
|
+
* namespace and display name.
|
|
4
|
+
*/
|
|
5
|
+
import { useField, useFormikContext } from 'formik';
|
|
6
|
+
import { useEffect } from 'react';
|
|
7
|
+
|
|
8
|
+
export const FullNameField = props => {
|
|
9
|
+
const { values, setFieldValue } = useFormikContext();
|
|
10
|
+
const [field, meta] = useField(props);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
// Set the value of the node's full name based on its namespace and display name
|
|
14
|
+
if (values.namespace && values.display_name) {
|
|
15
|
+
setFieldValue(
|
|
16
|
+
props.name,
|
|
17
|
+
`${values.namespace}.${values.display_name
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/ /g, '_')}` || '',
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}, [setFieldValue, props.name, values]);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<input
|
|
27
|
+
{...props}
|
|
28
|
+
{...field}
|
|
29
|
+
className="FullNameField"
|
|
30
|
+
disabled="disabled"
|
|
31
|
+
id="FullName"
|
|
32
|
+
value={values.name || ''}
|
|
33
|
+
/>
|
|
34
|
+
{!!meta.touched && !!meta.error && <div>{meta.error}</div>}
|
|
35
|
+
</>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -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 AddEditNodePage = () => {
|
|
9
|
+
return lazyLoad(
|
|
10
|
+
() => import('./index'),
|
|
11
|
+
module => module.AddEditNodePage,
|
|
12
|
+
{
|
|
13
|
+
fallback: <div></div>,
|
|
14
|
+
},
|
|
15
|
+
)();
|
|
16
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL query input field, which consists of a CodeMirror SQL editor with autocompletion
|
|
3
|
+
* (for node names and columns) and syntax highlighting.
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { Field, useFormikContext } from 'formik';
|
|
7
|
+
import CodeMirror from '@uiw/react-codemirror';
|
|
8
|
+
import { langs } from '@uiw/codemirror-extensions-langs';
|
|
9
|
+
|
|
10
|
+
export const NodeQueryField = ({ djClient, value }) => {
|
|
11
|
+
const [schema, setSchema] = React.useState([]);
|
|
12
|
+
const formik = useFormikContext();
|
|
13
|
+
const sqlExt = langs.sql({ schema: schema });
|
|
14
|
+
|
|
15
|
+
const initialAutocomplete = async context => {
|
|
16
|
+
// Based on the parsed prefix, we load node names with that prefix
|
|
17
|
+
// into the autocomplete schema. At this stage we don't load the columns
|
|
18
|
+
// to save on unnecessary calls
|
|
19
|
+
const word = context.matchBefore(/[\.\w]*/);
|
|
20
|
+
const matches = await djClient.nodes(word.text);
|
|
21
|
+
matches.forEach(nodeName => {
|
|
22
|
+
if (schema[nodeName] === undefined) {
|
|
23
|
+
schema[nodeName] = [];
|
|
24
|
+
setSchema(schema);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const updateFormik = val => {
|
|
30
|
+
formik.setFieldValue('query', val);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const updateAutocomplete = async (value, _) => {
|
|
34
|
+
// If a particular node has been chosen, load the columns of that node into
|
|
35
|
+
// the autocomplete schema for column-level autocompletion
|
|
36
|
+
for (var nodeName in schema) {
|
|
37
|
+
if (
|
|
38
|
+
value.includes(nodeName) &&
|
|
39
|
+
(!schema.hasOwnProperty(nodeName) ||
|
|
40
|
+
(schema.hasOwnProperty(nodeName) && schema[nodeName].length === 0))
|
|
41
|
+
) {
|
|
42
|
+
const nodeDetails = await djClient.node(nodeName);
|
|
43
|
+
schema[nodeName] = nodeDetails.columns.map(col => col.name);
|
|
44
|
+
setSchema(schema);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
<Field
|
|
52
|
+
type="textarea"
|
|
53
|
+
style={{ display: 'none' }}
|
|
54
|
+
as="textarea"
|
|
55
|
+
name="query"
|
|
56
|
+
id="Query"
|
|
57
|
+
/>
|
|
58
|
+
<div role="button" tabIndex={0} className="relative flex bg-[#282a36]">
|
|
59
|
+
<CodeMirror
|
|
60
|
+
id={'query'}
|
|
61
|
+
name={'query'}
|
|
62
|
+
extensions={[
|
|
63
|
+
sqlExt,
|
|
64
|
+
sqlExt.language.data.of({
|
|
65
|
+
autocomplete: initialAutocomplete,
|
|
66
|
+
}),
|
|
67
|
+
]}
|
|
68
|
+
value={value}
|
|
69
|
+
options={{
|
|
70
|
+
theme: 'default',
|
|
71
|
+
lineNumbers: true,
|
|
72
|
+
}}
|
|
73
|
+
width="100%"
|
|
74
|
+
height="400px"
|
|
75
|
+
style={{
|
|
76
|
+
margin: '0 0 23px 0',
|
|
77
|
+
flex: 1,
|
|
78
|
+
fontSize: '150%',
|
|
79
|
+
textAlign: 'left',
|
|
80
|
+
}}
|
|
81
|
+
onChange={(value, viewUpdate) => {
|
|
82
|
+
updateFormik(value);
|
|
83
|
+
updateAutocomplete(value, viewUpdate);
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
</>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
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 {
|
|
6
|
+
initializeMockDJClient,
|
|
7
|
+
renderCreateNode,
|
|
8
|
+
renderEditNode,
|
|
9
|
+
testElement,
|
|
10
|
+
} from './index.test';
|
|
11
|
+
import { mocks } from '../../../../mocks/mockNodes';
|
|
12
|
+
|
|
13
|
+
describe('AddEditNodePage submission failed', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
fetchMock.resetMocks();
|
|
16
|
+
jest.clearAllMocks();
|
|
17
|
+
window.scrollTo = jest.fn();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('for creating a node', async () => {
|
|
21
|
+
const mockDjClient = initializeMockDJClient();
|
|
22
|
+
mockDjClient.DataJunctionAPI.createNode.mockReturnValue({
|
|
23
|
+
status: 500,
|
|
24
|
+
json: { message: 'Some columns in the primary key [] were not found' },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const element = testElement(mockDjClient);
|
|
28
|
+
const { container } = renderCreateNode(element);
|
|
29
|
+
|
|
30
|
+
await userEvent.type(
|
|
31
|
+
screen.getByLabelText('Display Name'),
|
|
32
|
+
'Some Test Metric',
|
|
33
|
+
);
|
|
34
|
+
await userEvent.type(screen.getByLabelText('Query'), 'SELECT * FROM test');
|
|
35
|
+
await userEvent.click(screen.getByText('Create dimension'));
|
|
36
|
+
|
|
37
|
+
await waitFor(() => {
|
|
38
|
+
expect(mockDjClient.DataJunctionAPI.createNode).toBeCalled();
|
|
39
|
+
expect(mockDjClient.DataJunctionAPI.createNode).toBeCalledWith(
|
|
40
|
+
'dimension',
|
|
41
|
+
'default.some_test_metric',
|
|
42
|
+
'Some Test Metric',
|
|
43
|
+
'',
|
|
44
|
+
'SELECT * FROM test',
|
|
45
|
+
'draft',
|
|
46
|
+
'default',
|
|
47
|
+
null,
|
|
48
|
+
);
|
|
49
|
+
expect(
|
|
50
|
+
screen.getByText(/Some columns in the primary key \[] were not found/),
|
|
51
|
+
).toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// After failed creation, it should return a failure message
|
|
55
|
+
expect(container.getElementsByClassName('alert')).toMatchSnapshot();
|
|
56
|
+
}, 60000);
|
|
57
|
+
|
|
58
|
+
it('for editing a node', async () => {
|
|
59
|
+
const mockDjClient = initializeMockDJClient();
|
|
60
|
+
mockDjClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
|
|
61
|
+
mockDjClient.DataJunctionAPI.patchNode.mockReturnValue({
|
|
62
|
+
status: 500,
|
|
63
|
+
json: { message: 'Update failed' },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const element = testElement(mockDjClient);
|
|
67
|
+
renderEditNode(element);
|
|
68
|
+
|
|
69
|
+
await userEvent.type(screen.getByLabelText('Display Name'), '!!!');
|
|
70
|
+
await userEvent.type(screen.getByLabelText('Description'), '!!!');
|
|
71
|
+
await userEvent.click(screen.getByText('Save'));
|
|
72
|
+
await waitFor(async () => {
|
|
73
|
+
expect(mockDjClient.DataJunctionAPI.patchNode).toBeCalledTimes(1);
|
|
74
|
+
expect(await screen.getByText('Update failed')).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
}, 60000);
|
|
77
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
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 {
|
|
6
|
+
initializeMockDJClient,
|
|
7
|
+
renderCreateNode,
|
|
8
|
+
renderEditNode,
|
|
9
|
+
testElement,
|
|
10
|
+
} from './index.test';
|
|
11
|
+
import { mocks } from '../../../../mocks/mockNodes';
|
|
12
|
+
|
|
13
|
+
describe('AddEditNodePage submission succeeded', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
fetchMock.resetMocks();
|
|
16
|
+
jest.clearAllMocks();
|
|
17
|
+
window.scrollTo = jest.fn();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('for creating a node', async () => {
|
|
21
|
+
const mockDjClient = initializeMockDJClient();
|
|
22
|
+
mockDjClient.DataJunctionAPI.createNode.mockReturnValue({
|
|
23
|
+
status: 500,
|
|
24
|
+
json: { message: 'Some columns in the primary key [] were not found' },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const element = testElement(mockDjClient);
|
|
28
|
+
const { container } = renderCreateNode(element);
|
|
29
|
+
|
|
30
|
+
await userEvent.type(
|
|
31
|
+
screen.getByLabelText('Display Name'),
|
|
32
|
+
'Some Test Metric',
|
|
33
|
+
);
|
|
34
|
+
await userEvent.type(screen.getByLabelText('Query'), 'SELECT * FROM test');
|
|
35
|
+
await userEvent.click(screen.getByText('Create dimension'));
|
|
36
|
+
|
|
37
|
+
await waitFor(() => {
|
|
38
|
+
expect(mockDjClient.DataJunctionAPI.createNode).toBeCalled();
|
|
39
|
+
expect(mockDjClient.DataJunctionAPI.createNode).toBeCalledWith(
|
|
40
|
+
'dimension',
|
|
41
|
+
'default.some_test_metric',
|
|
42
|
+
'Some Test Metric',
|
|
43
|
+
'',
|
|
44
|
+
'SELECT * FROM test',
|
|
45
|
+
'draft',
|
|
46
|
+
'default',
|
|
47
|
+
null,
|
|
48
|
+
);
|
|
49
|
+
expect(
|
|
50
|
+
screen.getByText(/Some columns in the primary key \[] were not found/),
|
|
51
|
+
).toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// After failed creation, it should return a failure message
|
|
55
|
+
expect(container.getElementsByClassName('alert')).toMatchSnapshot();
|
|
56
|
+
}, 60000);
|
|
57
|
+
|
|
58
|
+
it('for editing a node', async () => {
|
|
59
|
+
const mockDjClient = initializeMockDJClient();
|
|
60
|
+
|
|
61
|
+
mockDjClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
|
|
62
|
+
mockDjClient.DataJunctionAPI.patchNode = jest.fn();
|
|
63
|
+
mockDjClient.DataJunctionAPI.patchNode.mockReturnValue({
|
|
64
|
+
status: 201,
|
|
65
|
+
json: { name: 'default.num_repair_orders', type: 'metric' },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const element = testElement(mockDjClient);
|
|
69
|
+
renderEditNode(element);
|
|
70
|
+
|
|
71
|
+
await userEvent.type(screen.getByLabelText('Display Name'), '!!!');
|
|
72
|
+
await userEvent.type(screen.getByLabelText('Description'), '!!!');
|
|
73
|
+
await userEvent.click(screen.getByText('Save'));
|
|
74
|
+
|
|
75
|
+
await waitFor(async () => {
|
|
76
|
+
expect(mockDjClient.DataJunctionAPI.patchNode).toBeCalledTimes(1);
|
|
77
|
+
expect(mockDjClient.DataJunctionAPI.patchNode).toBeCalledWith(
|
|
78
|
+
'default.num_repair_orders',
|
|
79
|
+
'Default: Num Repair Orders!!!',
|
|
80
|
+
'Number of repair orders!!!',
|
|
81
|
+
'SELECT count(repair_order_id) default_DOT_num_repair_orders FROM default.repair_orders',
|
|
82
|
+
'published',
|
|
83
|
+
['repair_order_id', 'country'],
|
|
84
|
+
);
|
|
85
|
+
expect(
|
|
86
|
+
await screen.getByDisplayValue('repair_order_id, country'),
|
|
87
|
+
).toBeInTheDocument();
|
|
88
|
+
expect(
|
|
89
|
+
await screen.getByText(/Successfully updated metric node/),
|
|
90
|
+
).toBeInTheDocument();
|
|
91
|
+
});
|
|
92
|
+
}, 1000000);
|
|
93
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { Formik, Form } from 'formik';
|
|
5
|
+
import { FormikSelect } from '../FormikSelect';
|
|
6
|
+
|
|
7
|
+
describe('FormikSelect', () => {
|
|
8
|
+
const namespaces = [
|
|
9
|
+
{ value: 'default', label: 'default' },
|
|
10
|
+
{ value: 'basic', label: 'basic' },
|
|
11
|
+
{ value: 'basic.one', label: 'basic.one' },
|
|
12
|
+
{ value: 'basic.two', label: 'basic.two' },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const singleSelect = () => {
|
|
16
|
+
const utils = render(
|
|
17
|
+
<Formik initialValues={{ selectedOption: '' }} onSubmit={jest.fn()}>
|
|
18
|
+
<Form>
|
|
19
|
+
<FormikSelect
|
|
20
|
+
selectOptions={namespaces}
|
|
21
|
+
formikFieldName="namespace"
|
|
22
|
+
placeholder="Choose Namespace"
|
|
23
|
+
defaultValue={{
|
|
24
|
+
value: 'basic.one',
|
|
25
|
+
label: 'basic.one',
|
|
26
|
+
}}
|
|
27
|
+
/>
|
|
28
|
+
</Form>
|
|
29
|
+
</Formik>,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const selectInput = screen.getByRole('combobox');
|
|
33
|
+
return {
|
|
34
|
+
...utils,
|
|
35
|
+
selectInput,
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const multiSelect = () => {
|
|
40
|
+
const utils = render(
|
|
41
|
+
<Formik initialValues={{ selectedOption: '' }} onSubmit={jest.fn()}>
|
|
42
|
+
<Form>
|
|
43
|
+
<FormikSelect
|
|
44
|
+
selectOptions={namespaces}
|
|
45
|
+
formikFieldName="namespace"
|
|
46
|
+
placeholder="Choose Namespace"
|
|
47
|
+
defaultValue={{
|
|
48
|
+
value: 'basic.one',
|
|
49
|
+
label: 'basic.one',
|
|
50
|
+
}}
|
|
51
|
+
isMulti={true}
|
|
52
|
+
/>
|
|
53
|
+
</Form>
|
|
54
|
+
</Formik>,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const selectInput = screen.getByRole('combobox');
|
|
58
|
+
return {
|
|
59
|
+
...utils,
|
|
60
|
+
selectInput,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
it('renders the single select component with provided options', () => {
|
|
65
|
+
singleSelect();
|
|
66
|
+
userEvent.click(screen.getByRole('combobox')); // to open the dropdown
|
|
67
|
+
expect(screen.getByText('basic.one')).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('renders the multi-select component with provided options', () => {
|
|
71
|
+
multiSelect();
|
|
72
|
+
userEvent.click(screen.getByRole('combobox')); // to open the dropdown
|
|
73
|
+
expect(screen.getByText('basic.one')).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import { Formik, Form } from 'formik';
|
|
4
|
+
import { FullNameField } from '../FullNameField';
|
|
5
|
+
|
|
6
|
+
describe('FullNameField', () => {
|
|
7
|
+
const setup = initialValues => {
|
|
8
|
+
return render(
|
|
9
|
+
<Formik initialValues={initialValues} onSubmit={jest.fn()}>
|
|
10
|
+
<Form>
|
|
11
|
+
<FullNameField name="full_name" />
|
|
12
|
+
</Form>
|
|
13
|
+
</Formik>,
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
it('generates the full name based on namespace and display name', () => {
|
|
18
|
+
setup({ namespace: 'cats', display_name: 'Jasper the Cat' });
|
|
19
|
+
const fullNameInput = screen.getByRole('textbox');
|
|
20
|
+
waitFor(() => {
|
|
21
|
+
expect(fullNameInput.value).toBe('cats.jasper_the_cat');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('does not set the full name if namespace or display name is missing', () => {
|
|
26
|
+
setup({ namespace: '', display_name: '' });
|
|
27
|
+
|
|
28
|
+
const fullNameInput = screen.getByRole('textbox');
|
|
29
|
+
expect(fullNameInput.value).toBe('');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { Formik, Form } from 'formik';
|
|
4
|
+
import { NodeQueryField } from '../NodeQueryField';
|
|
5
|
+
|
|
6
|
+
describe('NodeQueryField', () => {
|
|
7
|
+
const mockDjClient = {
|
|
8
|
+
nodes: jest.fn(),
|
|
9
|
+
node: jest.fn(),
|
|
10
|
+
};
|
|
11
|
+
const renderWithFormik = (djClient = mockDjClient) => {
|
|
12
|
+
return render(
|
|
13
|
+
<Formik initialValues={{ query: '' }} onSubmit={jest.fn()}>
|
|
14
|
+
<Form>
|
|
15
|
+
<NodeQueryField djClient={djClient} />
|
|
16
|
+
</Form>
|
|
17
|
+
</Formik>,
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
jest.clearAllMocks();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders without crashing', () => {
|
|
26
|
+
renderWithFormik();
|
|
27
|
+
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
28
|
+
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`AddEditNodePage Create node page renders with the selected nodeType and namespace 1`] = `HTMLCollection []`;
|
|
4
|
+
|
|
5
|
+
exports[`AddEditNodePage submission failed for creating a node 1`] = `
|
|
6
|
+
HTMLCollection [
|
|
7
|
+
<div
|
|
8
|
+
class="message alert"
|
|
9
|
+
>
|
|
10
|
+
<svg
|
|
11
|
+
height="2em"
|
|
12
|
+
version="1.1"
|
|
13
|
+
viewBox="0 0 24 24"
|
|
14
|
+
width="2em"
|
|
15
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
16
|
+
>
|
|
17
|
+
<title>
|
|
18
|
+
alert_fill
|
|
19
|
+
</title>
|
|
20
|
+
<g
|
|
21
|
+
fill="none"
|
|
22
|
+
fill-rule="evenodd"
|
|
23
|
+
id="page-1"
|
|
24
|
+
stroke="none"
|
|
25
|
+
stroke-width="1"
|
|
26
|
+
>
|
|
27
|
+
<g
|
|
28
|
+
fill-rule="nonzero"
|
|
29
|
+
id="System"
|
|
30
|
+
transform="translate(-48.000000, -48.000000)"
|
|
31
|
+
>
|
|
32
|
+
<g
|
|
33
|
+
id="alert_fill"
|
|
34
|
+
transform="translate(48.000000, 48.000000)"
|
|
35
|
+
>
|
|
36
|
+
<path
|
|
37
|
+
d="M24,0 L24,24 L0,24 L0,0 L24,0 Z M12.5934901,23.257841 L12.5819402,23.2595131 L12.5108777,23.2950439 L12.4918791,23.2987469 L12.4918791,23.2987469 L12.4767152,23.2950439 L12.4056548,23.2595131 C12.3958229,23.2563662 12.3870493,23.2590235 12.3821421,23.2649074 L12.3780323,23.275831 L12.360941,23.7031097 L12.3658947,23.7234994 L12.3769048,23.7357139 L12.4804777,23.8096931 L12.4953491,23.8136134 L12.4953491,23.8136134 L12.5071152,23.8096931 L12.6106902,23.7357139 L12.6232938,23.7196733 L12.6232938,23.7196733 L12.6266527,23.7031097 L12.609561,23.275831 C12.6075724,23.2657013 12.6010112,23.2592993 12.5934901,23.257841 L12.5934901,23.257841 Z M12.8583906,23.1452862 L12.8445485,23.1473072 L12.6598443,23.2396597 L12.6498822,23.2499052 L12.6498822,23.2499052 L12.6471943,23.2611114 L12.6650943,23.6906389 L12.6699349,23.7034178 L12.6699349,23.7034178 L12.678386,23.7104931 L12.8793402,23.8032389 C12.8914285,23.8068999 12.9022333,23.8029875 12.9078286,23.7952264 L12.9118235,23.7811639 L12.8776777,23.1665331 C12.8752882,23.1545897 12.8674102,23.1470016 12.8583906,23.1452862 L12.8583906,23.1452862 Z M12.1430473,23.1473072 C12.1332178,23.1423925 12.1221763,23.1452606 12.1156365,23.1525954 L12.1099173,23.1665331 L12.0757714,23.7811639 C12.0751323,23.7926639 12.0828099,23.8018602 12.0926481,23.8045676 L12.108256,23.8032389 L12.3092106,23.7104931 L12.3186497,23.7024347 L12.3186497,23.7024347 L12.3225043,23.6906389 L12.340401,23.2611114 L12.337245,23.2485176 L12.337245,23.2485176 L12.3277531,23.2396597 L12.1430473,23.1473072 Z"
|
|
38
|
+
fill-rule="nonzero"
|
|
39
|
+
id="MingCute"
|
|
40
|
+
/>
|
|
41
|
+
<path
|
|
42
|
+
d="M13.299,3.1477 L21.933,18.1022 C22.5103,19.1022 21.7887,20.3522 20.634,20.3522 L3.36601,20.3522 C2.21131,20.3522 1.48962,19.1022 2.06697,18.1022 L10.7009,3.14771 C11.2783,2.14771 12.7217,2.1477 13.299,3.1477 Z M12,15 C11.4477,15 11,15.4477 11,16 C11,16.5523 11.4477,17 12,17 C12.5523,17 13,16.5523 13,16 C13,15.4477 12.5523,15 12,15 Z M12,8 C11.48715,8 11.0644908,8.38604429 11.0067275,8.88337975 L11,9 L11,13 C11,13.5523 11.4477,14 12,14 C12.51285,14 12.9355092,13.613973 12.9932725,13.1166239 L13,13 L13,9 C13,8.44772 12.5523,8 12,8 Z"
|
|
43
|
+
fill="#09244B"
|
|
44
|
+
id="shape"
|
|
45
|
+
/>
|
|
46
|
+
</g>
|
|
47
|
+
</g>
|
|
48
|
+
</g>
|
|
49
|
+
</svg>
|
|
50
|
+
Some columns in the primary key [] were not found
|
|
51
|
+
</div>,
|
|
52
|
+
]
|
|
53
|
+
`;
|