datajunction-ui 0.0.1-a1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babel-plugin-macrosrc.js +5 -0
- package/.env +3 -0
- package/.eslintrc.js +20 -0
- package/.gitattributes +201 -0
- package/.husky/pre-commit +6 -0
- package/.nvmrc +1 -0
- package/.prettierignore +6 -0
- package/.prettierrc +9 -0
- package/.stylelintrc +7 -0
- package/LICENSE +22 -0
- package/Makefile +3 -0
- package/README.md +10 -0
- package/dj-logo.svg +10 -0
- package/internals/testing/loadable.mock.tsx +6 -0
- package/package.json +189 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +26 -0
- package/public/manifest.json +15 -0
- package/public/robots.txt +3 -0
- package/src/__tests__/reportWebVitals.test.jsx +44 -0
- package/src/app/__tests__/__snapshots__/index.test.tsx.snap +9 -0
- package/src/app/__tests__/index.test.tsx +14 -0
- package/src/app/components/DeleteNode.jsx +55 -0
- package/src/app/components/ListGroupItem.jsx +24 -0
- package/src/app/components/NamespaceHeader.jsx +31 -0
- package/src/app/components/QueryInfo.jsx +77 -0
- package/src/app/components/Tab.jsx +25 -0
- package/src/app/components/ToggleSwitch.jsx +20 -0
- package/src/app/components/__tests__/DeleteNode.test.jsx +53 -0
- package/src/app/components/__tests__/ListGroupItem.test.tsx +16 -0
- package/src/app/components/__tests__/NamespaceHeader.test.jsx +14 -0
- package/src/app/components/__tests__/QueryInfo.test.jsx +55 -0
- package/src/app/components/__tests__/Tab.test.jsx +27 -0
- package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
- package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +29 -0
- package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +47 -0
- package/src/app/components/djgraph/Collapse.jsx +46 -0
- package/src/app/components/djgraph/DJNode.jsx +89 -0
- package/src/app/components/djgraph/DJNodeColumns.jsx +71 -0
- package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
- package/src/app/components/djgraph/LayoutFlow.jsx +104 -0
- package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
- package/src/app/components/djgraph/__tests__/DJNode.test.tsx +24 -0
- package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
- package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
- package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +117 -0
- package/src/app/constants.js +2 -0
- package/src/app/icons/AlertIcon.jsx +32 -0
- package/src/app/icons/CollapsedIcon.jsx +15 -0
- package/src/app/icons/DJLogo.jsx +36 -0
- package/src/app/icons/DeleteIcon.jsx +21 -0
- package/src/app/icons/EditIcon.jsx +18 -0
- package/src/app/icons/ExpandedIcon.jsx +15 -0
- package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
- package/src/app/icons/InvalidIcon.jsx +14 -0
- package/src/app/icons/LoadingIcon.jsx +14 -0
- package/src/app/icons/PythonIcon.jsx +52 -0
- package/src/app/icons/TableIcon.jsx +14 -0
- package/src/app/icons/ValidIcon.jsx +14 -0
- package/src/app/index.tsx +108 -0
- package/src/app/pages/AddEditNodePage/FormikSelect.jsx +46 -0
- package/src/app/pages/AddEditNodePage/FullNameField.jsx +37 -0
- package/src/app/pages/AddEditNodePage/Loadable.jsx +16 -0
- package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +89 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +103 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +132 -0
- package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
- package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
- package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
- package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +180 -0
- package/src/app/pages/AddEditNodePage/index.jsx +396 -0
- package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
- package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
- package/src/app/pages/AddEditTagPage/index.jsx +132 -0
- package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
- package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
- package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
- package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
- package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
- package/src/app/pages/LoginPage/index.jsx +17 -0
- package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
- package/src/app/pages/NamespacePage/Explorer.jsx +57 -0
- package/src/app/pages/NamespacePage/Loadable.jsx +16 -0
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +217 -0
- package/src/app/pages/NamespacePage/index.jsx +199 -0
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +166 -0
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +161 -0
- package/src/app/pages/NodePage/ClientCodePopover.jsx +46 -0
- package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +149 -0
- package/src/app/pages/NodePage/Loadable.jsx +16 -0
- package/src/app/pages/NodePage/NodeColumnTab.jsx +200 -0
- package/src/app/pages/NodePage/NodeGraphTab.jsx +112 -0
- package/src/app/pages/NodePage/NodeHistory.jsx +212 -0
- package/src/app/pages/NodePage/NodeInfoTab.jsx +212 -0
- package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +233 -0
- package/src/app/pages/NodePage/NodeSQLTab.jsx +82 -0
- package/src/app/pages/NodePage/NodeStatus.jsx +28 -0
- package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
- package/src/app/pages/NodePage/PartitionColumnPopover.jsx +153 -0
- package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +47 -0
- package/src/app/pages/NodePage/__tests__/ClientCodePopover.test.jsx +49 -0
- package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +148 -0
- package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +165 -0
- package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +591 -0
- package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +57 -0
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +757 -0
- package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +403 -0
- package/src/app/pages/NodePage/index.jsx +210 -0
- package/src/app/pages/NotFoundPage/Loadable.tsx +14 -0
- package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
- package/src/app/pages/NotFoundPage/index.tsx +23 -0
- package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
- package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +110 -0
- package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +37 -0
- package/src/app/pages/RegisterTablePage/index.jsx +142 -0
- package/src/app/pages/Root/Loadable.tsx +14 -0
- package/src/app/pages/Root/__tests__/index.test.jsx +77 -0
- package/src/app/pages/Root/assets/dj-logo.png +0 -0
- package/src/app/pages/Root/index.tsx +70 -0
- package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
- package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
- package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
- package/src/app/pages/TagPage/Loadable.jsx +16 -0
- package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
- package/src/app/pages/TagPage/index.jsx +79 -0
- package/src/app/providers/djclient.jsx +5 -0
- package/src/app/services/DJService.js +665 -0
- package/src/app/services/__tests__/DJService.test.jsx +804 -0
- package/src/index.tsx +48 -0
- package/src/mocks/mockNodes.jsx +1430 -0
- package/src/react-app-env.d.ts +4 -0
- package/src/reportWebVitals.ts +15 -0
- package/src/setupTests.ts +36 -0
- package/src/styles/dag.css +228 -0
- package/src/styles/index.css +1083 -0
- package/src/styles/loading.css +34 -0
- package/src/styles/login.css +81 -0
- package/src/styles/node-creation.scss +197 -0
- package/src/styles/styles.scss +44 -0
- package/src/styles/styles.scss.d.ts +9 -0
- package/src/utils/__tests__/__snapshots__/loadable.test.tsx.snap +17 -0
- package/src/utils/__tests__/loadable.test.tsx +53 -0
- package/src/utils/__tests__/request.test.ts +82 -0
- package/src/utils/form.jsx +23 -0
- package/src/utils/loadable.tsx +30 -0
- package/src/utils/request.ts +54 -0
- package/tsconfig.json +34 -0
- package/webpack.config.js +118 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
|
3
|
+
import DJClientContext from '../../../providers/djclient';
|
|
4
|
+
import { NamespacePage } from '../index';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import userEvent from '@testing-library/user-event';
|
|
7
|
+
|
|
8
|
+
const mockDjClient = {
|
|
9
|
+
namespaces: jest.fn(),
|
|
10
|
+
namespace: jest.fn(),
|
|
11
|
+
addNamespace: jest.fn(),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe('NamespacePage', () => {
|
|
15
|
+
const original = window.location;
|
|
16
|
+
|
|
17
|
+
const reloadFn = () => {
|
|
18
|
+
window.location.reload();
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
beforeAll(() => {
|
|
22
|
+
Object.defineProperty(window, 'location', {
|
|
23
|
+
configurable: true,
|
|
24
|
+
value: { reload: jest.fn() },
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterAll(() => {
|
|
29
|
+
Object.defineProperty(window, 'location', {
|
|
30
|
+
configurable: true,
|
|
31
|
+
value: original,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
fetch.resetMocks();
|
|
37
|
+
mockDjClient.namespaces.mockResolvedValue([
|
|
38
|
+
{
|
|
39
|
+
namespace: 'common.one',
|
|
40
|
+
num_nodes: 3,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
namespace: 'common.one.a',
|
|
44
|
+
num_nodes: 6,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
namespace: 'common.one.b',
|
|
48
|
+
num_nodes: 17,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
namespace: 'common.one.c',
|
|
52
|
+
num_nodes: 64,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
namespace: 'default',
|
|
56
|
+
num_nodes: 41,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
namespace: 'default.fruits',
|
|
60
|
+
num_nodes: 1,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
namespace: 'default.fruits.citrus.lemons',
|
|
64
|
+
num_nodes: 1,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
namespace: 'default.vegetables',
|
|
68
|
+
num_nodes: 2,
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
mockDjClient.namespace.mockResolvedValue([
|
|
72
|
+
{
|
|
73
|
+
name: 'testNode',
|
|
74
|
+
display_name: 'Test Node',
|
|
75
|
+
type: 'transform',
|
|
76
|
+
mode: 'active',
|
|
77
|
+
updated_at: new Date(),
|
|
78
|
+
},
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
afterEach(() => {
|
|
83
|
+
jest.clearAllMocks();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('displays namespaces and renders nodes', async () => {
|
|
87
|
+
reloadFn();
|
|
88
|
+
const element = (
|
|
89
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
90
|
+
<NamespacePage />
|
|
91
|
+
</DJClientContext.Provider>
|
|
92
|
+
);
|
|
93
|
+
render(
|
|
94
|
+
<MemoryRouter initialEntries={['/namespaces/test.namespace']}>
|
|
95
|
+
<Routes>
|
|
96
|
+
<Route path="namespaces/:namespace" element={element} />
|
|
97
|
+
</Routes>
|
|
98
|
+
</MemoryRouter>,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
await waitFor(() => {
|
|
102
|
+
expect(mockDjClient.namespaces).toHaveBeenCalledTimes(1);
|
|
103
|
+
expect(screen.getByText('Namespaces')).toBeInTheDocument();
|
|
104
|
+
|
|
105
|
+
// check that it displays namespaces
|
|
106
|
+
expect(screen.getByText('common')).toBeInTheDocument();
|
|
107
|
+
expect(screen.getByText('one')).toBeInTheDocument();
|
|
108
|
+
expect(screen.getByText('fruits')).toBeInTheDocument();
|
|
109
|
+
expect(screen.getByText('vegetables')).toBeInTheDocument();
|
|
110
|
+
|
|
111
|
+
// check that it renders nodes
|
|
112
|
+
expect(screen.getByText('Test Node')).toBeInTheDocument();
|
|
113
|
+
|
|
114
|
+
// click to open and close tab
|
|
115
|
+
fireEvent.click(screen.getByText('common'));
|
|
116
|
+
fireEvent.click(screen.getByText('common'));
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
afterEach(() => {
|
|
121
|
+
jest.clearAllMocks();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('can add new namespace via add namespace popover', async () => {
|
|
125
|
+
mockDjClient.addNamespace.mockReturnValue({
|
|
126
|
+
status: 201,
|
|
127
|
+
json: {},
|
|
128
|
+
});
|
|
129
|
+
const element = (
|
|
130
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
131
|
+
<NamespacePage />
|
|
132
|
+
</DJClientContext.Provider>
|
|
133
|
+
);
|
|
134
|
+
render(
|
|
135
|
+
<MemoryRouter initialEntries={['/namespaces/test.namespace']}>
|
|
136
|
+
<Routes>
|
|
137
|
+
<Route path="namespaces/:namespace" element={element} />
|
|
138
|
+
</Routes>
|
|
139
|
+
</MemoryRouter>,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Find the button to toggle the add namespace popover
|
|
143
|
+
const addNamespaceToggle = screen.getByRole('button', {
|
|
144
|
+
name: 'AddNamespaceTogglePopover',
|
|
145
|
+
});
|
|
146
|
+
expect(addNamespaceToggle).toBeInTheDocument();
|
|
147
|
+
|
|
148
|
+
// Click the toggle and verify that the popover displays
|
|
149
|
+
fireEvent.click(addNamespaceToggle);
|
|
150
|
+
const addNamespacePopover = screen.getByRole('dialog', {
|
|
151
|
+
name: 'AddNamespacePopover',
|
|
152
|
+
});
|
|
153
|
+
expect(addNamespacePopover).toBeInTheDocument();
|
|
154
|
+
|
|
155
|
+
// Type in the new namespace
|
|
156
|
+
await userEvent.type(
|
|
157
|
+
screen.getByLabelText('Namespace'),
|
|
158
|
+
'some.random.namespace',
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Save
|
|
162
|
+
const saveNamespace = screen.getByRole('button', {
|
|
163
|
+
name: 'SaveNamespace',
|
|
164
|
+
});
|
|
165
|
+
await waitFor(() => {
|
|
166
|
+
fireEvent.click(saveNamespace);
|
|
167
|
+
});
|
|
168
|
+
expect(mockDjClient.addNamespace).toHaveBeenCalled();
|
|
169
|
+
expect(mockDjClient.addNamespace).toHaveBeenCalledWith(
|
|
170
|
+
'some.random.namespace',
|
|
171
|
+
);
|
|
172
|
+
expect(screen.getByText('Saved')).toBeInTheDocument();
|
|
173
|
+
expect(window.location.reload).toHaveBeenCalled();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('can fail to add namespace', async () => {
|
|
177
|
+
mockDjClient.addNamespace.mockReturnValue({
|
|
178
|
+
status: 500,
|
|
179
|
+
json: { message: 'you failed' },
|
|
180
|
+
});
|
|
181
|
+
const element = (
|
|
182
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
183
|
+
<NamespacePage />
|
|
184
|
+
</DJClientContext.Provider>
|
|
185
|
+
);
|
|
186
|
+
render(
|
|
187
|
+
<MemoryRouter initialEntries={['/namespaces/test.namespace']}>
|
|
188
|
+
<Routes>
|
|
189
|
+
<Route path="namespaces/:namespace" element={element} />
|
|
190
|
+
</Routes>
|
|
191
|
+
</MemoryRouter>,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// Open the add namespace popover
|
|
195
|
+
const addNamespaceToggle = screen.getByRole('button', {
|
|
196
|
+
name: 'AddNamespaceTogglePopover',
|
|
197
|
+
});
|
|
198
|
+
fireEvent.click(addNamespaceToggle);
|
|
199
|
+
|
|
200
|
+
// Type in the new namespace
|
|
201
|
+
await userEvent.type(
|
|
202
|
+
screen.getByLabelText('Namespace'),
|
|
203
|
+
'some.random.namespace',
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Save
|
|
207
|
+
const saveNamespace = screen.getByRole('button', {
|
|
208
|
+
name: 'SaveNamespace',
|
|
209
|
+
});
|
|
210
|
+
await waitFor(() => {
|
|
211
|
+
fireEvent.click(saveNamespace);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Should display failure alert
|
|
215
|
+
expect(screen.getByText('you failed')).toBeInTheDocument();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useParams } from 'react-router-dom';
|
|
3
|
+
import { useContext, useEffect, useState } from 'react';
|
|
4
|
+
import NodeStatus from '../NodePage/NodeStatus';
|
|
5
|
+
import DJClientContext from '../../providers/djclient';
|
|
6
|
+
import Explorer from '../NamespacePage/Explorer';
|
|
7
|
+
import EditIcon from '../../icons/EditIcon';
|
|
8
|
+
import DeleteNode from '../../components/DeleteNode';
|
|
9
|
+
import AddNamespacePopover from './AddNamespacePopover';
|
|
10
|
+
|
|
11
|
+
export function NamespacePage() {
|
|
12
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
13
|
+
var { namespace } = useParams();
|
|
14
|
+
|
|
15
|
+
const [state, setState] = useState({
|
|
16
|
+
namespace: namespace,
|
|
17
|
+
nodes: [],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const [namespaceHierarchy, setNamespaceHierarchy] = useState([]);
|
|
21
|
+
|
|
22
|
+
const createNamespaceHierarchy = namespaceList => {
|
|
23
|
+
const hierarchy = [];
|
|
24
|
+
|
|
25
|
+
for (const item of namespaceList) {
|
|
26
|
+
const namespaces = item.namespace.split('.');
|
|
27
|
+
let currentLevel = hierarchy;
|
|
28
|
+
|
|
29
|
+
let path = '';
|
|
30
|
+
for (const ns of namespaces) {
|
|
31
|
+
path += ns;
|
|
32
|
+
|
|
33
|
+
let existingNamespace = currentLevel.find(el => el.namespace === ns);
|
|
34
|
+
if (!existingNamespace) {
|
|
35
|
+
existingNamespace = {
|
|
36
|
+
namespace: ns,
|
|
37
|
+
children: [],
|
|
38
|
+
path: path,
|
|
39
|
+
};
|
|
40
|
+
currentLevel.push(existingNamespace);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
currentLevel = existingNamespace.children;
|
|
44
|
+
path += '.';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return hierarchy;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const fetchData = async () => {
|
|
52
|
+
const namespaces = await djClient.namespaces();
|
|
53
|
+
const hierarchy = createNamespaceHierarchy(namespaces);
|
|
54
|
+
setNamespaceHierarchy(hierarchy);
|
|
55
|
+
};
|
|
56
|
+
fetchData().catch(console.error);
|
|
57
|
+
}, [djClient, djClient.namespaces]);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const fetchData = async () => {
|
|
61
|
+
if (namespace === undefined && namespaceHierarchy !== undefined) {
|
|
62
|
+
namespace = namespaceHierarchy[0].namespace;
|
|
63
|
+
}
|
|
64
|
+
const nodes = await djClient.namespace(namespace);
|
|
65
|
+
const foundNodes = await Promise.all(nodes);
|
|
66
|
+
setState({
|
|
67
|
+
namespace: namespace,
|
|
68
|
+
nodes: foundNodes,
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
fetchData().catch(console.error);
|
|
72
|
+
}, [djClient, namespace, namespaceHierarchy]);
|
|
73
|
+
|
|
74
|
+
const nodesList = state.nodes.map(node => (
|
|
75
|
+
<tr>
|
|
76
|
+
<td>
|
|
77
|
+
<a href={'/nodes/' + node.name} className="link-table">
|
|
78
|
+
{node.name}
|
|
79
|
+
</a>
|
|
80
|
+
<span
|
|
81
|
+
className="rounded-pill badge bg-secondary-soft"
|
|
82
|
+
style={{ marginLeft: '0.5rem' }}
|
|
83
|
+
>
|
|
84
|
+
{node.version}
|
|
85
|
+
</span>
|
|
86
|
+
</td>
|
|
87
|
+
<td>
|
|
88
|
+
<a href={'/nodes/' + node.name} className="link-table">
|
|
89
|
+
{node.type !== 'source' ? node.display_name : ''}
|
|
90
|
+
</a>
|
|
91
|
+
</td>
|
|
92
|
+
<td>
|
|
93
|
+
<span className={'node_type__' + node.type + ' badge node_type'}>
|
|
94
|
+
{node.type}
|
|
95
|
+
</span>
|
|
96
|
+
</td>
|
|
97
|
+
<td>
|
|
98
|
+
<NodeStatus node={node} />
|
|
99
|
+
</td>
|
|
100
|
+
<td>
|
|
101
|
+
<span className="status">{node.mode}</span>
|
|
102
|
+
</td>
|
|
103
|
+
<td>
|
|
104
|
+
<span className="status">
|
|
105
|
+
{new Date(node.updated_at).toLocaleString('en-us')}
|
|
106
|
+
</span>
|
|
107
|
+
</td>
|
|
108
|
+
<td>
|
|
109
|
+
<a href={`/nodes/${node?.name}/edit`} style={{ marginLeft: '0.5rem' }}>
|
|
110
|
+
<EditIcon />
|
|
111
|
+
</a>
|
|
112
|
+
<DeleteNode nodeName={node?.name} />
|
|
113
|
+
</td>
|
|
114
|
+
</tr>
|
|
115
|
+
));
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div className="mid">
|
|
119
|
+
<div className="card">
|
|
120
|
+
<div className="card-header">
|
|
121
|
+
<h2>Explore</h2>
|
|
122
|
+
|
|
123
|
+
<span className="menu-link">
|
|
124
|
+
<span className="menu-title">
|
|
125
|
+
<div className="dropdown">
|
|
126
|
+
<span className="add_node">+ Add Node</span>
|
|
127
|
+
<div className="dropdown-content">
|
|
128
|
+
<a href={`/create/source`}>
|
|
129
|
+
<div className="node_type__source node_type_creation_heading">
|
|
130
|
+
Register Table
|
|
131
|
+
</div>
|
|
132
|
+
</a>
|
|
133
|
+
<a href={`/create/transform/${namespace}`}>
|
|
134
|
+
<div className="node_type__transform node_type_creation_heading">
|
|
135
|
+
Transform
|
|
136
|
+
</div>
|
|
137
|
+
</a>
|
|
138
|
+
<a href={`/create/metric/${namespace}`}>
|
|
139
|
+
<div className="node_type__metric node_type_creation_heading">
|
|
140
|
+
Metric
|
|
141
|
+
</div>
|
|
142
|
+
</a>
|
|
143
|
+
<a href={`/create/dimension/${namespace}`}>
|
|
144
|
+
<div className="node_type__dimension node_type_creation_heading">
|
|
145
|
+
Dimension
|
|
146
|
+
</div>
|
|
147
|
+
</a>
|
|
148
|
+
<a href={`/create/tag`}>
|
|
149
|
+
<div className="entity__tag node_type_creation_heading">
|
|
150
|
+
Tag
|
|
151
|
+
</div>
|
|
152
|
+
</a>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</span>
|
|
156
|
+
</span>
|
|
157
|
+
<div className="table-responsive">
|
|
158
|
+
<div className={`sidebar`}>
|
|
159
|
+
<span
|
|
160
|
+
style={{
|
|
161
|
+
textTransform: 'uppercase',
|
|
162
|
+
fontSize: '0.8125rem',
|
|
163
|
+
fontWeight: '600',
|
|
164
|
+
color: '#95aac9',
|
|
165
|
+
padding: '1rem 1rem 1rem 0',
|
|
166
|
+
}}
|
|
167
|
+
>
|
|
168
|
+
Namespaces <AddNamespacePopover />
|
|
169
|
+
</span>
|
|
170
|
+
{namespaceHierarchy
|
|
171
|
+
? namespaceHierarchy.map(child => (
|
|
172
|
+
<Explorer
|
|
173
|
+
item={child}
|
|
174
|
+
current={state.namespace}
|
|
175
|
+
defaultExpand={true}
|
|
176
|
+
/>
|
|
177
|
+
))
|
|
178
|
+
: null}
|
|
179
|
+
</div>
|
|
180
|
+
<table className="card-table table">
|
|
181
|
+
<thead>
|
|
182
|
+
<tr>
|
|
183
|
+
<th>Name</th>
|
|
184
|
+
<th>Display Name</th>
|
|
185
|
+
<th>Type</th>
|
|
186
|
+
<th>Status</th>
|
|
187
|
+
<th>Mode</th>
|
|
188
|
+
<th>Last Updated</th>
|
|
189
|
+
<th>Actions</th>
|
|
190
|
+
</tr>
|
|
191
|
+
</thead>
|
|
192
|
+
<tbody>{nodesList}</tbody>
|
|
193
|
+
</table>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { useContext, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import DJClientContext from '../../providers/djclient';
|
|
4
|
+
import { ErrorMessage, Field, Form, Formik } from 'formik';
|
|
5
|
+
import { FormikSelect } from '../AddEditNodePage/FormikSelect';
|
|
6
|
+
import EditIcon from '../../icons/EditIcon';
|
|
7
|
+
import { displayMessageAfterSubmit, labelize } from '../../../utils/form';
|
|
8
|
+
|
|
9
|
+
export default function AddBackfillPopover({
|
|
10
|
+
node,
|
|
11
|
+
materialization,
|
|
12
|
+
onSubmit,
|
|
13
|
+
}) {
|
|
14
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
15
|
+
const [popoverAnchor, setPopoverAnchor] = useState(false);
|
|
16
|
+
const ref = useRef(null);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const handleClickOutside = event => {
|
|
20
|
+
if (ref.current && !ref.current.contains(event.target)) {
|
|
21
|
+
setPopoverAnchor(false);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
document.addEventListener('click', handleClickOutside, true);
|
|
25
|
+
return () => {
|
|
26
|
+
document.removeEventListener('click', handleClickOutside, true);
|
|
27
|
+
};
|
|
28
|
+
}, [setPopoverAnchor]);
|
|
29
|
+
|
|
30
|
+
const partitionColumns = node.columns.filter(col => col.partition !== null);
|
|
31
|
+
|
|
32
|
+
const temporalPartitionColumns = partitionColumns.filter(
|
|
33
|
+
col => col.partition.type_ === 'temporal',
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const initialValues = {
|
|
37
|
+
node: node.name,
|
|
38
|
+
materializationName: materialization.name,
|
|
39
|
+
partitionColumn:
|
|
40
|
+
temporalPartitionColumns.length > 0
|
|
41
|
+
? temporalPartitionColumns[0].name
|
|
42
|
+
: '',
|
|
43
|
+
from: '',
|
|
44
|
+
to: '',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const savePartition = async (values, { setSubmitting, setStatus }) => {
|
|
48
|
+
setSubmitting(false);
|
|
49
|
+
const response = await djClient.runBackfill(
|
|
50
|
+
values.node,
|
|
51
|
+
values.materializationName,
|
|
52
|
+
values.partitionColumn,
|
|
53
|
+
values.from,
|
|
54
|
+
values.to,
|
|
55
|
+
);
|
|
56
|
+
if (response.status === 200 || response.status === 201) {
|
|
57
|
+
setStatus({ success: 'Saved!' });
|
|
58
|
+
} else {
|
|
59
|
+
setStatus({
|
|
60
|
+
failure: `${response.json.message}`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
onSubmit();
|
|
64
|
+
window.location.reload();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<>
|
|
69
|
+
<button
|
|
70
|
+
className="edit_button"
|
|
71
|
+
aria-label="AddBackfill"
|
|
72
|
+
tabIndex="0"
|
|
73
|
+
onClick={() => {
|
|
74
|
+
setPopoverAnchor(!popoverAnchor);
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<span className="add_node">+ Add Backfill</span>
|
|
78
|
+
</button>
|
|
79
|
+
<div
|
|
80
|
+
className="fade modal-backdrop in"
|
|
81
|
+
style={{ display: popoverAnchor === false ? 'none' : 'block' }}
|
|
82
|
+
></div>
|
|
83
|
+
<div
|
|
84
|
+
className="centerPopover"
|
|
85
|
+
role="dialog"
|
|
86
|
+
aria-label="client-code"
|
|
87
|
+
style={{
|
|
88
|
+
display: popoverAnchor === false ? 'none' : 'block',
|
|
89
|
+
width: '50%',
|
|
90
|
+
}}
|
|
91
|
+
ref={ref}
|
|
92
|
+
>
|
|
93
|
+
<Formik initialValues={initialValues} onSubmit={savePartition}>
|
|
94
|
+
{function Render({ isSubmitting, status, setFieldValue }) {
|
|
95
|
+
return (
|
|
96
|
+
<Form>
|
|
97
|
+
{displayMessageAfterSubmit(status)}
|
|
98
|
+
<h2>Run Backfill</h2>
|
|
99
|
+
<span data-testid="edit-partition">
|
|
100
|
+
<label htmlFor="engine" style={{ paddingBottom: '1rem' }}>
|
|
101
|
+
Engine
|
|
102
|
+
</label>
|
|
103
|
+
<Field as="select" name="engine" id="engine" disabled={true}>
|
|
104
|
+
<option value={materialization?.engine?.name}>
|
|
105
|
+
{materialization?.engine?.name}{' '}
|
|
106
|
+
{materialization?.engine?.version}
|
|
107
|
+
</option>
|
|
108
|
+
</Field>
|
|
109
|
+
</span>
|
|
110
|
+
<br />
|
|
111
|
+
<br />
|
|
112
|
+
<label htmlFor="partition" style={{ paddingBottom: '1rem' }}>
|
|
113
|
+
Partition Range
|
|
114
|
+
</label>
|
|
115
|
+
{node.columns
|
|
116
|
+
.filter(col => col.partition !== null)
|
|
117
|
+
.map(col => {
|
|
118
|
+
return (
|
|
119
|
+
<div
|
|
120
|
+
className="partition__full"
|
|
121
|
+
key={col.name}
|
|
122
|
+
style={{ width: '50%' }}
|
|
123
|
+
>
|
|
124
|
+
<div className="partition__header">
|
|
125
|
+
{col.display_name}
|
|
126
|
+
</div>
|
|
127
|
+
<div className="partition__body">
|
|
128
|
+
<span style={{ padding: '0.5rem' }}>From</span>{' '}
|
|
129
|
+
<Field
|
|
130
|
+
type="text"
|
|
131
|
+
name="from"
|
|
132
|
+
id={`${col.name}__from`}
|
|
133
|
+
placeholder="20230101"
|
|
134
|
+
default="20230101"
|
|
135
|
+
style={{ width: '7rem', paddingRight: '1rem' }}
|
|
136
|
+
/>{' '}
|
|
137
|
+
<span style={{ padding: '0.5rem' }}>To</span>
|
|
138
|
+
<Field
|
|
139
|
+
type="text"
|
|
140
|
+
name="to"
|
|
141
|
+
id={`${col.name}__to`}
|
|
142
|
+
placeholder="20230102"
|
|
143
|
+
default="20230102"
|
|
144
|
+
style={{ width: '7rem' }}
|
|
145
|
+
/>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
})}
|
|
150
|
+
<br />
|
|
151
|
+
<button
|
|
152
|
+
className="add_node"
|
|
153
|
+
type="submit"
|
|
154
|
+
aria-label="SaveEditColumn"
|
|
155
|
+
aria-hidden="false"
|
|
156
|
+
>
|
|
157
|
+
Save
|
|
158
|
+
</button>
|
|
159
|
+
</Form>
|
|
160
|
+
);
|
|
161
|
+
}}
|
|
162
|
+
</Formik>
|
|
163
|
+
</div>
|
|
164
|
+
</>
|
|
165
|
+
);
|
|
166
|
+
}
|