datajunction-ui 0.0.1-rc.9 → 0.0.2-0.dev1
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 +2 -0
- package/.prettierignore +3 -1
- package/Makefile +9 -0
- package/cleanup-deps.sh +70 -0
- package/dj-logo.svg +10 -0
- package/package.json +53 -14
- package/public/favicon.ico +0 -0
- package/public/index.html +1 -1
- package/runit.sh +30 -0
- package/runit2.sh +30 -0
- package/src/__tests__/reportWebVitals.test.jsx +44 -0
- package/src/app/__tests__/__snapshots__/index.test.tsx.snap +5 -109
- package/src/app/components/AddNodeDropdown.jsx +44 -0
- package/src/app/components/ListGroupItem.jsx +9 -1
- package/src/app/components/NamespaceHeader.jsx +4 -13
- package/src/app/components/NodeListActions.jsx +69 -0
- package/src/app/components/NodeMaterializationDelete.jsx +90 -0
- package/src/app/components/NotificationBell.tsx +223 -0
- package/src/app/components/QueryInfo.jsx +172 -0
- package/src/app/components/Search.jsx +94 -0
- package/src/app/components/Tab.jsx +8 -1
- package/src/app/components/ToggleSwitch.jsx +20 -0
- package/src/app/components/UserMenu.tsx +100 -0
- package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
- package/src/app/components/__tests__/NodeMaterializationDelete.test.jsx +263 -0
- package/src/app/components/__tests__/NotificationBell.test.tsx +302 -0
- package/src/app/components/__tests__/QueryInfo.test.jsx +183 -0
- package/src/app/components/__tests__/Search.test.jsx +307 -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__/UserMenu.test.tsx +241 -0
- package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +8 -3
- package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
- package/src/app/components/djgraph/Collapse.jsx +47 -0
- package/src/app/components/djgraph/DJNode.jsx +61 -83
- package/src/app/components/djgraph/DJNodeColumns.jsx +75 -0
- package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
- package/src/app/components/djgraph/LayoutFlow.jsx +106 -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/components/forms/Action.jsx +8 -0
- package/src/app/components/forms/NodeNameField.jsx +64 -0
- package/src/app/components/search.css +17 -0
- package/src/app/constants.js +2 -0
- package/src/app/icons/AddItemIcon.jsx +16 -0
- package/src/app/icons/AlertIcon.jsx +33 -0
- package/src/app/icons/CollapsedIcon.jsx +15 -0
- package/src/app/icons/CommitIcon.jsx +45 -0
- package/src/app/icons/DJLogo.jsx +36 -0
- package/src/app/icons/DeleteIcon.jsx +21 -0
- package/src/app/icons/DiffIcon.jsx +63 -0
- package/src/app/icons/EditIcon.jsx +18 -0
- package/src/app/icons/ExpandedIcon.jsx +15 -0
- package/src/app/icons/EyeIcon.jsx +20 -0
- package/src/app/icons/FilterIcon.jsx +7 -0
- package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
- package/src/app/icons/InvalidIcon.jsx +16 -0
- package/src/app/icons/JupyterExportIcon.jsx +25 -0
- package/src/app/icons/LoadingIcon.jsx +14 -0
- package/src/app/icons/NodeIcon.jsx +49 -0
- package/src/app/icons/NotificationIcon.jsx +27 -0
- package/src/app/icons/PythonIcon.jsx +14 -0
- package/src/app/icons/SettingsIcon.jsx +28 -0
- package/src/app/icons/TableIcon.jsx +14 -0
- package/src/app/icons/ValidIcon.jsx +16 -0
- package/src/app/icons/WrenchIcon.jsx +36 -0
- package/src/app/index.tsx +130 -37
- package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -0
- package/src/app/pages/AddEditNodePage/ColumnMetadata.jsx +61 -0
- package/src/app/pages/AddEditNodePage/ColumnsMetadataInput.jsx +72 -0
- package/src/app/pages/AddEditNodePage/ColumnsSelect.jsx +84 -0
- package/src/app/pages/AddEditNodePage/CustomMetadataField.jsx +144 -0
- package/src/app/pages/AddEditNodePage/DescriptionField.jsx +17 -0
- package/src/app/pages/AddEditNodePage/DisplayNameField.jsx +16 -0
- package/src/app/pages/AddEditNodePage/ExperimentationExtension.jsx +338 -0
- package/src/app/pages/AddEditNodePage/FormikSelect.jsx +64 -0
- package/src/app/pages/AddEditNodePage/FullNameField.jsx +38 -0
- package/src/app/pages/AddEditNodePage/Loadable.jsx +20 -0
- package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +75 -0
- package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +71 -0
- package/src/app/pages/AddEditNodePage/NamespaceField.jsx +40 -0
- package/src/app/pages/AddEditNodePage/NodeModeField.jsx +14 -0
- package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +94 -0
- package/src/app/pages/AddEditNodePage/OwnersField.jsx +54 -0
- package/src/app/pages/AddEditNodePage/RequiredDimensionsSelect.jsx +54 -0
- package/src/app/pages/AddEditNodePage/TagsField.jsx +47 -0
- package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +49 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +110 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +291 -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 +224 -0
- package/src/app/pages/AddEditNodePage/index.jsx +545 -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/CubeBuilderPage/DimensionsSelect.jsx +152 -0
- package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
- package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +75 -0
- package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +373 -0
- package/src/app/pages/CubeBuilderPage/index.jsx +291 -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 +232 -0
- package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
- package/src/app/pages/NamespacePage/NodeModeSelect.jsx +27 -0
- package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +30 -0
- package/src/app/pages/NamespacePage/TagSelect.jsx +44 -0
- package/src/app/pages/NamespacePage/UserSelect.jsx +47 -0
- package/src/app/pages/NamespacePage/__tests__/AddNamespacePopover.test.jsx +283 -0
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +331 -0
- package/src/app/pages/NamespacePage/index.jsx +356 -42
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +165 -0
- package/src/app/pages/NodePage/AddComplexDimensionLinkPopover.jsx +367 -0
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +222 -0
- package/src/app/pages/NodePage/AvailabilityStateBlock.jsx +67 -0
- package/src/app/pages/NodePage/ClientCodePopover.jsx +94 -0
- package/src/app/pages/NodePage/DimensionFilter.jsx +86 -0
- package/src/app/pages/NodePage/EditColumnDescriptionPopover.jsx +116 -0
- package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +164 -0
- package/src/app/pages/NodePage/ManageDimensionLinksDialog.jsx +526 -0
- package/src/app/pages/NodePage/MaterializationConfigField.jsx +60 -0
- package/src/app/pages/NodePage/NodeColumnTab.jsx +421 -30
- package/src/app/pages/NodePage/NodeDependenciesTab.jsx +153 -0
- package/src/app/pages/NodePage/NodeGraphTab.jsx +119 -148
- package/src/app/pages/NodePage/NodeHistory.jsx +236 -0
- package/src/app/pages/NodePage/NodeInfoTab.jsx +346 -49
- package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +585 -0
- package/src/app/pages/NodePage/NodeRevisionMaterializationTab.jsx +58 -0
- package/src/app/pages/NodePage/NodeStatus.jsx +100 -31
- package/src/app/pages/NodePage/NodeValidateTab.jsx +367 -0
- package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
- package/src/app/pages/NodePage/NotebookDownload.jsx +36 -0
- package/src/app/pages/NodePage/PartitionColumnPopover.jsx +151 -0
- package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
- package/src/app/pages/NodePage/RevisionDiff.jsx +209 -0
- package/src/app/pages/NodePage/WatchNodeButton.jsx +226 -0
- package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +56 -0
- package/src/app/pages/NodePage/__tests__/AddComplexDimensionLinkPopover.test.jsx +459 -0
- package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +87 -0
- package/src/app/pages/NodePage/__tests__/DimensionFilter.test.jsx +74 -0
- package/src/app/pages/NodePage/__tests__/EditColumnDescriptionPopover.test.jsx +149 -0
- package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +144 -0
- package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +132 -0
- package/src/app/pages/NodePage/__tests__/ManageDimensionLinksDialog.test.jsx +390 -0
- package/src/app/pages/NodePage/__tests__/NodeColumnTab.test.jsx +166 -0
- package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +151 -0
- package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +595 -0
- package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +58 -0
- package/src/app/pages/NodePage/__tests__/NodeMaterializationTab.test.jsx +190 -0
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +882 -0
- package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
- package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +19 -0
- package/src/app/pages/NodePage/index.jsx +190 -44
- package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
- package/src/app/pages/NotificationsPage/Loadable.jsx +6 -0
- package/src/app/pages/NotificationsPage/__tests__/index.test.jsx +287 -0
- package/src/app/pages/NotificationsPage/index.jsx +136 -0
- package/src/app/pages/OverviewPage/ByStatusPanel.jsx +69 -0
- package/src/app/pages/OverviewPage/DimensionNodeUsagePanel.jsx +48 -0
- package/src/app/pages/OverviewPage/GovernanceWarningsPanel.jsx +107 -0
- package/src/app/pages/OverviewPage/Loadable.jsx +16 -0
- package/src/app/pages/OverviewPage/NodesByTypePanel.jsx +63 -0
- package/src/app/pages/OverviewPage/OverviewPanel.jsx +94 -0
- package/src/app/pages/OverviewPage/TrendsPanel.jsx +66 -0
- package/src/app/pages/OverviewPage/__tests__/ByStatusPanel.test.jsx +36 -0
- package/src/app/pages/OverviewPage/__tests__/DimensionNodeUsagePanel.test.jsx +76 -0
- package/src/app/pages/OverviewPage/__tests__/GovernanceWarningsPanel.test.jsx +77 -0
- package/src/app/pages/OverviewPage/__tests__/NodesByTypePanel.test.jsx +86 -0
- package/src/app/pages/OverviewPage/__tests__/OverviewPanel.test.jsx +78 -0
- package/src/app/pages/OverviewPage/__tests__/TrendsPanel.test.jsx +120 -0
- package/src/app/pages/OverviewPage/__tests__/index.test.jsx +54 -0
- package/src/app/pages/OverviewPage/index.jsx +22 -0
- package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
- package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +112 -0
- package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +38 -0
- package/src/app/pages/RegisterTablePage/index.jsx +142 -0
- package/src/app/pages/Root/__tests__/index.test.jsx +44 -0
- package/src/app/pages/Root/index.tsx +92 -10
- 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/SettingsPage/CreateServiceAccountModal.jsx +152 -0
- package/src/app/pages/SettingsPage/Loadable.jsx +16 -0
- package/src/app/pages/SettingsPage/NotificationSubscriptionsSection.jsx +189 -0
- package/src/app/pages/SettingsPage/ProfileSection.jsx +41 -0
- package/src/app/pages/SettingsPage/ServiceAccountsSection.jsx +95 -0
- package/src/app/pages/SettingsPage/__tests__/CreateServiceAccountModal.test.jsx +318 -0
- package/src/app/pages/SettingsPage/__tests__/NotificationSubscriptionsSection.test.jsx +233 -0
- package/src/app/pages/SettingsPage/__tests__/ProfileSection.test.jsx +65 -0
- package/src/app/pages/SettingsPage/__tests__/ServiceAccountsSection.test.jsx +150 -0
- package/src/app/pages/SettingsPage/__tests__/index.test.jsx +184 -0
- package/src/app/pages/SettingsPage/index.jsx +148 -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/services/DJService.js +1444 -21
- package/src/app/services/__tests__/DJService.test.jsx +2118 -0
- package/src/app/utils/__tests__/date.test.js +198 -0
- package/src/app/utils/date.js +65 -0
- package/src/index.tsx +1 -0
- package/src/mocks/mockNodes.jsx +1477 -0
- package/src/setupTests.ts +31 -1
- package/src/styles/dag.css +117 -5
- package/src/styles/index.css +1028 -31
- package/src/styles/loading.css +34 -0
- package/src/styles/login.css +81 -0
- package/src/styles/nav-bar.css +274 -0
- package/src/styles/node-creation.scss +276 -0
- package/src/styles/node-list.css +4 -0
- package/src/styles/overview.css +72 -0
- package/src/styles/settings.css +787 -0
- package/src/styles/sorted-table.css +15 -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/webpack.config.js +17 -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/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
- package/src/app/pages/ListNamespacesPage/index.jsx +0 -62
- 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,20 @@
|
|
|
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 LazyAddEditNodePage = props => {
|
|
9
|
+
return lazyLoad(
|
|
10
|
+
() => import('./index'),
|
|
11
|
+
module => module.AddEditNodePage,
|
|
12
|
+
{
|
|
13
|
+
fallback: <div></div>,
|
|
14
|
+
},
|
|
15
|
+
)(props);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const AddEditNodePage = props => {
|
|
19
|
+
return <LazyAddEditNodePage {...props} />;
|
|
20
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metric unit select component
|
|
3
|
+
*/
|
|
4
|
+
import { ErrorMessage, Field } from 'formik';
|
|
5
|
+
import { useContext, useEffect, useState } from 'react';
|
|
6
|
+
import DJClientContext from '../../providers/djclient';
|
|
7
|
+
import { labelize } from '../../../utils/form';
|
|
8
|
+
|
|
9
|
+
export const MetricMetadataFields = () => {
|
|
10
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
11
|
+
|
|
12
|
+
// Metric metadata
|
|
13
|
+
const [metricUnits, setMetricUnits] = useState([]);
|
|
14
|
+
const [metricDirections, setMetricDirections] = useState([]);
|
|
15
|
+
|
|
16
|
+
// Get metric metadata values
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const fetchData = async () => {
|
|
19
|
+
const metadata = await djClient.listMetricMetadata();
|
|
20
|
+
setMetricDirections(metadata.directions);
|
|
21
|
+
setMetricUnits(metadata.units);
|
|
22
|
+
};
|
|
23
|
+
fetchData().catch(console.error);
|
|
24
|
+
}, [djClient]);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
style={{
|
|
29
|
+
borderRadius: '8px',
|
|
30
|
+
padding: '10px 10px 20px 10px',
|
|
31
|
+
margin: '32px 0',
|
|
32
|
+
background: '#f9f9f9',
|
|
33
|
+
width: 'max-content',
|
|
34
|
+
display: 'flex',
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
<div style={{ margin: '15px 25px' }}>
|
|
38
|
+
<ErrorMessage name="metric_direction" component="span" />
|
|
39
|
+
<label htmlFor="MetricDirection">Metric Direction</label>
|
|
40
|
+
<Field as="select" name="metric_direction" id="MetricDirection">
|
|
41
|
+
<option value=""></option>
|
|
42
|
+
{metricDirections.map(direction => (
|
|
43
|
+
<option value={direction} key={direction}>
|
|
44
|
+
{labelize(direction)}
|
|
45
|
+
</option>
|
|
46
|
+
))}
|
|
47
|
+
</Field>
|
|
48
|
+
</div>
|
|
49
|
+
<div style={{ margin: '15px 25px' }}>
|
|
50
|
+
<ErrorMessage name="metric_unit" component="span" />
|
|
51
|
+
<label htmlFor="MetricUnit">Metric Unit</label>
|
|
52
|
+
<Field as="select" name="metric_unit" id="MetricUnit">
|
|
53
|
+
<option value=""></option>
|
|
54
|
+
{metricUnits.map(unit => (
|
|
55
|
+
<option value={unit.name} key={unit.name}>
|
|
56
|
+
{unit.label}
|
|
57
|
+
</option>
|
|
58
|
+
))}
|
|
59
|
+
</Field>
|
|
60
|
+
</div>
|
|
61
|
+
<div style={{ margin: '15px 25px' }}>
|
|
62
|
+
<ErrorMessage name="significant_digits" component="span" />
|
|
63
|
+
<label htmlFor="SignificantDigits">Significant Digits</label>
|
|
64
|
+
<Field as="select" name="significant_digits" id="SignificantDigits">
|
|
65
|
+
<option value=""></option>
|
|
66
|
+
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(val => (
|
|
67
|
+
<option value={val} key={val}>
|
|
68
|
+
{val}
|
|
69
|
+
</option>
|
|
70
|
+
))}
|
|
71
|
+
</Field>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metric aggregate expression input field, which consists of a CodeMirror SQL
|
|
3
|
+
* editor with autocompletion for node columns and syntax highlighting.
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { ErrorMessage, Field, useFormikContext } from 'formik';
|
|
7
|
+
import CodeMirror from '@uiw/react-codemirror';
|
|
8
|
+
import { langs } from '@uiw/codemirror-extensions-langs';
|
|
9
|
+
|
|
10
|
+
export const MetricQueryField = ({ 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 selected upstream, we load the upstream node's columns
|
|
17
|
+
// into the autocomplete schema
|
|
18
|
+
const nodeName = formik.values['upstream_node'];
|
|
19
|
+
const nodeDetails = await djClient.node(nodeName);
|
|
20
|
+
nodeDetails.columns.forEach(col => {
|
|
21
|
+
schema[col.name] = [];
|
|
22
|
+
});
|
|
23
|
+
setSchema(schema);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const updateFormik = val => {
|
|
27
|
+
formik.setFieldValue('aggregate_expression', val);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="QueryInput MetricQueryInput NodeCreationInput">
|
|
32
|
+
<ErrorMessage name="query" component="span" />
|
|
33
|
+
<label htmlFor="Query">Aggregate Expression *</label>
|
|
34
|
+
<Field
|
|
35
|
+
type="textarea"
|
|
36
|
+
style={{ display: 'none' }}
|
|
37
|
+
as="textarea"
|
|
38
|
+
name="aggregate_expression"
|
|
39
|
+
id="Query"
|
|
40
|
+
/>
|
|
41
|
+
<div role="button" tabIndex={0} className="relative flex bg-[#282a36]">
|
|
42
|
+
<CodeMirror
|
|
43
|
+
id={'aggregate_expression'}
|
|
44
|
+
name={'aggregate_expression'}
|
|
45
|
+
extensions={[
|
|
46
|
+
sqlExt,
|
|
47
|
+
sqlExt.language.data.of({
|
|
48
|
+
autocomplete: initialAutocomplete,
|
|
49
|
+
}),
|
|
50
|
+
]}
|
|
51
|
+
value={value}
|
|
52
|
+
options={{
|
|
53
|
+
theme: 'default',
|
|
54
|
+
lineNumbers: true,
|
|
55
|
+
}}
|
|
56
|
+
width="100%"
|
|
57
|
+
height="100px"
|
|
58
|
+
style={{
|
|
59
|
+
margin: '0 0 23px 0',
|
|
60
|
+
flex: 1,
|
|
61
|
+
fontSize: '150%',
|
|
62
|
+
textAlign: 'left',
|
|
63
|
+
}}
|
|
64
|
+
onChange={(value, viewUpdate) => {
|
|
65
|
+
updateFormik(value);
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ErrorMessage } from 'formik';
|
|
2
|
+
import { FormikSelect } from './FormikSelect';
|
|
3
|
+
import { useContext, useEffect, useState } from 'react';
|
|
4
|
+
import DJClientContext from '../../providers/djclient';
|
|
5
|
+
|
|
6
|
+
export const NamespaceField = ({ initialNamespace }) => {
|
|
7
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
8
|
+
|
|
9
|
+
const [namespaces, setNamespaces] = useState([]);
|
|
10
|
+
|
|
11
|
+
// Get namespaces, only necessary when creating a node
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const fetchData = async () => {
|
|
14
|
+
const namespaces = await djClient.namespaces();
|
|
15
|
+
setNamespaces(
|
|
16
|
+
namespaces.map(m => ({
|
|
17
|
+
value: m['namespace'],
|
|
18
|
+
label: m['namespace'],
|
|
19
|
+
})),
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
fetchData().catch(console.error);
|
|
23
|
+
}, [djClient]);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="NamespaceInput">
|
|
27
|
+
<ErrorMessage name="namespace" component="span" />
|
|
28
|
+
<label htmlFor="namespace">Namespace *</label>
|
|
29
|
+
<FormikSelect
|
|
30
|
+
selectOptions={namespaces}
|
|
31
|
+
formikFieldName="namespace"
|
|
32
|
+
placeholder="Choose Namespace"
|
|
33
|
+
defaultValue={{
|
|
34
|
+
value: initialNamespace,
|
|
35
|
+
label: initialNamespace,
|
|
36
|
+
}}
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ErrorMessage, Field } from 'formik';
|
|
2
|
+
|
|
3
|
+
export const NodeModeField = () => {
|
|
4
|
+
return (
|
|
5
|
+
<div className="NodeModeInput NodeCreationInput">
|
|
6
|
+
<ErrorMessage name="mode" component="span" />
|
|
7
|
+
<label htmlFor="Mode">Mode</label>
|
|
8
|
+
<Field as="select" name="mode" id="Mode">
|
|
9
|
+
<option value="draft">Draft</option>
|
|
10
|
+
<option value="published">Published</option>
|
|
11
|
+
</Field>
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
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 { ErrorMessage, 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
|
+
<div className="QueryInput NodeCreationInput">
|
|
51
|
+
<ErrorMessage name="query" component="span" />
|
|
52
|
+
<label htmlFor="Query">Query *</label>
|
|
53
|
+
<Field
|
|
54
|
+
type="textarea"
|
|
55
|
+
style={{ display: 'none' }}
|
|
56
|
+
as="textarea"
|
|
57
|
+
name="query"
|
|
58
|
+
id="Query"
|
|
59
|
+
/>
|
|
60
|
+
<div role="button" tabIndex={0} className="relative flex bg-[#282a36]">
|
|
61
|
+
<CodeMirror
|
|
62
|
+
id={'query'}
|
|
63
|
+
name={'query'}
|
|
64
|
+
extensions={[
|
|
65
|
+
sqlExt,
|
|
66
|
+
sqlExt.language.data.of({
|
|
67
|
+
autocomplete: initialAutocomplete,
|
|
68
|
+
}),
|
|
69
|
+
]}
|
|
70
|
+
value={value}
|
|
71
|
+
placeholder={
|
|
72
|
+
'SELECT\n\tprimary_key,\n\tmeasure1,\n\tmeasure2,\n\tforeign_key_for_dimension1,\n\tforeign_key_for_dimension2\nFROM source.source_node\nWHERE ...'
|
|
73
|
+
}
|
|
74
|
+
options={{
|
|
75
|
+
theme: 'default',
|
|
76
|
+
lineNumbers: true,
|
|
77
|
+
}}
|
|
78
|
+
width="100%"
|
|
79
|
+
height="400px"
|
|
80
|
+
style={{
|
|
81
|
+
margin: '0 0 23px 0',
|
|
82
|
+
flex: 1,
|
|
83
|
+
fontSize: '150%',
|
|
84
|
+
textAlign: 'left',
|
|
85
|
+
}}
|
|
86
|
+
onChange={(value, viewUpdate) => {
|
|
87
|
+
updateFormik(value);
|
|
88
|
+
updateAutocomplete(value, viewUpdate);
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owner select field
|
|
3
|
+
*/
|
|
4
|
+
import { ErrorMessage } from 'formik';
|
|
5
|
+
import { useContext, useEffect, useState } from 'react';
|
|
6
|
+
import DJClientContext from '../../providers/djclient';
|
|
7
|
+
import { FormikSelect } from './FormikSelect';
|
|
8
|
+
|
|
9
|
+
export const OwnersField = ({ defaultValue }) => {
|
|
10
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
11
|
+
|
|
12
|
+
const [availableUsers, setAvailableUsers] = useState([]);
|
|
13
|
+
const [currentUser, setCurrentUser] = useState(null);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
async function fetchData() {
|
|
17
|
+
const users = await djClient.users();
|
|
18
|
+
setAvailableUsers(
|
|
19
|
+
users.map(user => {
|
|
20
|
+
return {
|
|
21
|
+
value: user.username,
|
|
22
|
+
label: user.username,
|
|
23
|
+
};
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
const current = await djClient.whoami();
|
|
27
|
+
setCurrentUser(current);
|
|
28
|
+
}
|
|
29
|
+
fetchData();
|
|
30
|
+
}, [djClient]);
|
|
31
|
+
|
|
32
|
+
return defaultValue || currentUser ? (
|
|
33
|
+
<div className="NodeCreationInput">
|
|
34
|
+
<ErrorMessage name="owners" component="span" />
|
|
35
|
+
<label htmlFor="Owners">Owners</label>
|
|
36
|
+
<span data-testid="select-owner">
|
|
37
|
+
<FormikSelect
|
|
38
|
+
className="MultiSelectInput"
|
|
39
|
+
defaultValue={
|
|
40
|
+
defaultValue || [
|
|
41
|
+
{ value: currentUser.username, label: currentUser.username },
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
selectOptions={availableUsers}
|
|
45
|
+
formikFieldName="owners"
|
|
46
|
+
placeholder="Select Owners"
|
|
47
|
+
isMulti={true}
|
|
48
|
+
/>
|
|
49
|
+
</span>
|
|
50
|
+
</div>
|
|
51
|
+
) : (
|
|
52
|
+
''
|
|
53
|
+
);
|
|
54
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Required dimensions select component
|
|
3
|
+
*/
|
|
4
|
+
import { ErrorMessage, useFormikContext } from 'formik';
|
|
5
|
+
import React, { useContext, useEffect, useState } from 'react';
|
|
6
|
+
import DJClientContext from '../../providers/djclient';
|
|
7
|
+
import { FormikSelect } from './FormikSelect';
|
|
8
|
+
|
|
9
|
+
export const RequiredDimensionsSelect = ({
|
|
10
|
+
defaultValue,
|
|
11
|
+
style,
|
|
12
|
+
className = 'MultiSelectInput',
|
|
13
|
+
}) => {
|
|
14
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
15
|
+
|
|
16
|
+
// Used to pull out current form values for node validation
|
|
17
|
+
const { values } = useFormikContext();
|
|
18
|
+
|
|
19
|
+
// Select options, i.e., the available dimensions
|
|
20
|
+
const [selectOptions, setSelectOptions] = useState([]);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const fetchData = async () => {
|
|
24
|
+
if (values.upstream_node) {
|
|
25
|
+
const data = await djClient.node(values.upstream_node);
|
|
26
|
+
setSelectOptions(
|
|
27
|
+
data.columns.map(col => {
|
|
28
|
+
return {
|
|
29
|
+
value: col.name,
|
|
30
|
+
label: col.name,
|
|
31
|
+
};
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
fetchData().catch(console.error);
|
|
37
|
+
}, [djClient, values.upstream_node]);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="RequiredDimensionsInput CubeCreationInput">
|
|
41
|
+
<ErrorMessage name="required_dimensions" component="span" />
|
|
42
|
+
<label htmlFor="requiredDimensions">Required Dimensions</label>
|
|
43
|
+
<FormikSelect
|
|
44
|
+
className={className}
|
|
45
|
+
defaultValue={defaultValue}
|
|
46
|
+
selectOptions={selectOptions}
|
|
47
|
+
formikFieldName={'required_dimensions'}
|
|
48
|
+
placeholder={'Choose Required Dimensions'}
|
|
49
|
+
styles={style}
|
|
50
|
+
isMulti={true}
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tags select field
|
|
3
|
+
*/
|
|
4
|
+
import { ErrorMessage } from 'formik';
|
|
5
|
+
import { useContext, useEffect, useState } from 'react';
|
|
6
|
+
import DJClientContext from '../../providers/djclient';
|
|
7
|
+
import { FormikSelect } from './FormikSelect';
|
|
8
|
+
|
|
9
|
+
export const TagsField = ({ defaultValue }) => {
|
|
10
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
11
|
+
|
|
12
|
+
// All available tags
|
|
13
|
+
const [tags, setTags] = useState([]);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const fetchData = async () => {
|
|
17
|
+
const tags = await djClient.listTags();
|
|
18
|
+
setTags(
|
|
19
|
+
tags.map(tag => ({
|
|
20
|
+
value: tag.name,
|
|
21
|
+
label: tag.display_name,
|
|
22
|
+
})),
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
fetchData().catch(console.error);
|
|
26
|
+
}, [djClient]);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
className="TagsInput"
|
|
31
|
+
style={{ width: '25%', margin: '1rem 0 1rem 1.2rem' }}
|
|
32
|
+
>
|
|
33
|
+
<ErrorMessage name="tags" component="span" />
|
|
34
|
+
<label htmlFor="tags">Tags</label>
|
|
35
|
+
<span data-testid="select-tags">
|
|
36
|
+
<FormikSelect
|
|
37
|
+
isMulti={true}
|
|
38
|
+
selectOptions={tags}
|
|
39
|
+
formikFieldName="tags"
|
|
40
|
+
className="MultiSelectInput"
|
|
41
|
+
placeholder="Choose Tags"
|
|
42
|
+
defaultValue={defaultValue}
|
|
43
|
+
/>
|
|
44
|
+
</span>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upstream node select field
|
|
3
|
+
*/
|
|
4
|
+
import { ErrorMessage } from 'formik';
|
|
5
|
+
import { useContext, useEffect, useState } from 'react';
|
|
6
|
+
import DJClientContext from '../../providers/djclient';
|
|
7
|
+
import { FormikSelect } from './FormikSelect';
|
|
8
|
+
|
|
9
|
+
export const UpstreamNodeField = ({ defaultValue }) => {
|
|
10
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
11
|
+
|
|
12
|
+
// All available nodes
|
|
13
|
+
const [availableNodes, setAvailableNodes] = useState([]);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
async function fetchData() {
|
|
17
|
+
const sources = await djClient.nodesWithType('source');
|
|
18
|
+
const transforms = await djClient.nodesWithType('transform');
|
|
19
|
+
const dimensions = await djClient.nodesWithType('dimension');
|
|
20
|
+
const nodes = sources.concat(transforms).concat(dimensions);
|
|
21
|
+
setAvailableNodes(
|
|
22
|
+
nodes.map(node => {
|
|
23
|
+
return {
|
|
24
|
+
value: node,
|
|
25
|
+
label: node,
|
|
26
|
+
};
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
fetchData();
|
|
31
|
+
}, [djClient]);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="NodeCreationInput">
|
|
35
|
+
<ErrorMessage name="mode" component="span" />
|
|
36
|
+
<label htmlFor="Mode">Upstream Node *</label>
|
|
37
|
+
<span data-testid="select-upstream-node">
|
|
38
|
+
<FormikSelect
|
|
39
|
+
className="SelectInput"
|
|
40
|
+
defaultValue={defaultValue}
|
|
41
|
+
selectOptions={availableNodes}
|
|
42
|
+
formikFieldName="upstream_node"
|
|
43
|
+
placeholder="Select Upstream Node"
|
|
44
|
+
isMulti={false}
|
|
45
|
+
/>
|
|
46
|
+
</span>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { fireEvent, 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
|
+
mockDjClient.DataJunctionAPI.listTags.mockReturnValue([
|
|
27
|
+
{ name: 'purpose', display_name: 'Purpose' },
|
|
28
|
+
{ name: 'intent', display_name: 'Intent' },
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const element = testElement(mockDjClient);
|
|
32
|
+
const { container } = renderCreateNode(element);
|
|
33
|
+
|
|
34
|
+
await userEvent.type(
|
|
35
|
+
screen.getByLabelText('Display Name *'),
|
|
36
|
+
'Some Test Metric',
|
|
37
|
+
);
|
|
38
|
+
await userEvent.type(
|
|
39
|
+
screen.getByLabelText('Query *'),
|
|
40
|
+
'SELECT * FROM test',
|
|
41
|
+
);
|
|
42
|
+
await userEvent.click(screen.getByText('Create dimension'));
|
|
43
|
+
|
|
44
|
+
await waitFor(() => {
|
|
45
|
+
expect(mockDjClient.DataJunctionAPI.createNode).toBeCalled();
|
|
46
|
+
expect(mockDjClient.DataJunctionAPI.createNode).toBeCalledWith(
|
|
47
|
+
'dimension',
|
|
48
|
+
'default.some_test_metric',
|
|
49
|
+
'Some Test Metric',
|
|
50
|
+
'',
|
|
51
|
+
'SELECT * FROM test',
|
|
52
|
+
'published',
|
|
53
|
+
'default',
|
|
54
|
+
null,
|
|
55
|
+
undefined,
|
|
56
|
+
undefined,
|
|
57
|
+
undefined,
|
|
58
|
+
null,
|
|
59
|
+
);
|
|
60
|
+
expect(
|
|
61
|
+
screen.getByText(/Some columns in the primary key \[] were not found/),
|
|
62
|
+
).toBeInTheDocument();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// After failed creation, it should return a failure message
|
|
66
|
+
expect(container.getElementsByClassName('alert')).toMatchSnapshot();
|
|
67
|
+
}, 60000);
|
|
68
|
+
|
|
69
|
+
it('for editing a node', async () => {
|
|
70
|
+
const mockDjClient = initializeMockDJClient();
|
|
71
|
+
mockDjClient.DataJunctionAPI.getNodeForEditing.mockReturnValue(
|
|
72
|
+
mocks.mockGetMetricNode,
|
|
73
|
+
);
|
|
74
|
+
mockDjClient.DataJunctionAPI.patchNode.mockReturnValue({
|
|
75
|
+
status: 500,
|
|
76
|
+
json: { message: 'Update failed' },
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
mockDjClient.DataJunctionAPI.tagsNode.mockReturnValue({
|
|
80
|
+
status: 404,
|
|
81
|
+
json: { message: 'Some tags were not found' },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
mockDjClient.DataJunctionAPI.listTags.mockReturnValue([
|
|
85
|
+
{ name: 'purpose', display_name: 'Purpose' },
|
|
86
|
+
{ name: 'intent', display_name: 'Intent' },
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const element = testElement(mockDjClient);
|
|
90
|
+
renderEditNode(element);
|
|
91
|
+
|
|
92
|
+
await userEvent.type(screen.getByLabelText('Display Name *'), '!!!');
|
|
93
|
+
await userEvent.type(screen.getByLabelText('Description'), '!!!');
|
|
94
|
+
await userEvent.click(screen.getByText('Save'));
|
|
95
|
+
await waitFor(async () => {
|
|
96
|
+
expect(mockDjClient.DataJunctionAPI.patchNode).toBeCalledTimes(1);
|
|
97
|
+
expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalled();
|
|
98
|
+
expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalledWith(
|
|
99
|
+
'default.num_repair_orders',
|
|
100
|
+
['purpose'],
|
|
101
|
+
);
|
|
102
|
+
expect(mockDjClient.DataJunctionAPI.tagsNode).toReturnWith({
|
|
103
|
+
json: { message: 'Some tags were not found' },
|
|
104
|
+
status: 404,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(await screen.getByText('Update failed')).toBeInTheDocument();
|
|
108
|
+
});
|
|
109
|
+
}, 60000);
|
|
110
|
+
});
|