datajunction-ui 0.0.14 → 0.0.16
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/package.json +1 -1
- package/src/app/components/__tests__/NodeMaterializationDelete.test.jsx +263 -0
- package/src/app/components/__tests__/QueryInfo.test.jsx +174 -46
- package/src/app/components/__tests__/Search.test.jsx +300 -56
- package/src/app/pages/AddEditNodePage/FormikSelect.jsx +15 -2
- package/src/app/pages/NamespacePage/Explorer.jsx +192 -21
- package/src/app/pages/NamespacePage/__tests__/AddNamespacePopover.test.jsx +283 -0
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +74 -41
- package/src/app/pages/NamespacePage/index.jsx +13 -7
- package/src/app/pages/NodePage/AddComplexDimensionLinkPopover.jsx +367 -0
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +1 -1
- package/src/app/pages/NodePage/ManageDimensionLinksDialog.jsx +526 -0
- package/src/app/pages/NodePage/NodeColumnTab.jsx +223 -58
- package/src/app/pages/NodePage/__tests__/AddComplexDimensionLinkPopover.test.jsx +459 -0
- package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +2 -6
- package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +19 -48
- package/src/app/pages/NodePage/__tests__/ManageDimensionLinksDialog.test.jsx +390 -0
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +22 -12
- package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +4 -2
- package/src/app/services/DJService.js +46 -6
- package/src/app/services/__tests__/DJService.test.jsx +551 -5
- package/webpack.config.js +1 -0
|
@@ -0,0 +1,367 @@
|
|
|
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 AddItemIcon from '../../icons/AddItemIcon';
|
|
7
|
+
import { displayMessageAfterSubmit } from '../../../utils/form';
|
|
8
|
+
import LoadingIcon from '../../icons/LoadingIcon';
|
|
9
|
+
import CodeMirror from '@uiw/react-codemirror';
|
|
10
|
+
import { langs } from '@uiw/codemirror-extensions-langs';
|
|
11
|
+
|
|
12
|
+
export default function AddComplexDimensionLinkPopover({
|
|
13
|
+
node,
|
|
14
|
+
dimensions,
|
|
15
|
+
existingLink = null,
|
|
16
|
+
isEditMode = false,
|
|
17
|
+
onSubmit,
|
|
18
|
+
}) {
|
|
19
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
20
|
+
const [popoverAnchor, setPopoverAnchor] = useState(false);
|
|
21
|
+
const ref = useRef(null);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const handleClickOutside = event => {
|
|
25
|
+
if (ref.current && !ref.current.contains(event.target)) {
|
|
26
|
+
setPopoverAnchor(false);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
document.addEventListener('click', handleClickOutside, true);
|
|
30
|
+
return () => {
|
|
31
|
+
document.removeEventListener('click', handleClickOutside, true);
|
|
32
|
+
};
|
|
33
|
+
}, [setPopoverAnchor]);
|
|
34
|
+
|
|
35
|
+
const joinTypeOptions = [
|
|
36
|
+
{ value: 'left', label: 'LEFT' },
|
|
37
|
+
{ value: 'right', label: 'RIGHT' },
|
|
38
|
+
{ value: 'inner', label: 'INNER' },
|
|
39
|
+
{ value: 'full', label: 'FULL' },
|
|
40
|
+
{ value: 'cross', label: 'CROSS' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const joinCardinalityOptions = [
|
|
44
|
+
{ value: 'one_to_one', label: 'ONE TO ONE' },
|
|
45
|
+
{ value: 'one_to_many', label: 'ONE TO MANY' },
|
|
46
|
+
{ value: 'many_to_one', label: 'MANY TO ONE' },
|
|
47
|
+
{ value: 'many_to_many', label: 'MANY TO MANY' },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const handleSubmit = async (values, { setSubmitting, setStatus }) => {
|
|
51
|
+
try {
|
|
52
|
+
// If editing, remove the old link first
|
|
53
|
+
if (isEditMode && existingLink) {
|
|
54
|
+
await djClient.removeComplexDimensionLink(
|
|
55
|
+
node.name,
|
|
56
|
+
existingLink.dimension.name,
|
|
57
|
+
existingLink.role,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Add the new/updated link
|
|
62
|
+
const response = await djClient.addComplexDimensionLink(
|
|
63
|
+
node.name,
|
|
64
|
+
values.dimensionNode,
|
|
65
|
+
values.joinOn.trim(),
|
|
66
|
+
values.joinType || 'left',
|
|
67
|
+
values.joinCardinality || 'many_to_one',
|
|
68
|
+
values.role?.trim() || null,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (response.status === 200 || response.status === 201) {
|
|
72
|
+
setStatus({
|
|
73
|
+
success: `Complex dimension link ${
|
|
74
|
+
isEditMode ? 'updated' : 'added'
|
|
75
|
+
} successfully!`,
|
|
76
|
+
});
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
setPopoverAnchor(false);
|
|
79
|
+
window.location.reload();
|
|
80
|
+
}, 1000);
|
|
81
|
+
} else {
|
|
82
|
+
setStatus({
|
|
83
|
+
failure:
|
|
84
|
+
response.json?.message ||
|
|
85
|
+
`Failed to ${isEditMode ? 'update' : 'add'} link`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
setStatus({ failure: error.message });
|
|
90
|
+
} finally {
|
|
91
|
+
setSubmitting(false);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<>
|
|
97
|
+
{isEditMode ? (
|
|
98
|
+
<button
|
|
99
|
+
onClick={() => setPopoverAnchor(!popoverAnchor)}
|
|
100
|
+
style={{
|
|
101
|
+
padding: '0.25rem 0.5rem',
|
|
102
|
+
fontSize: '0.75rem',
|
|
103
|
+
background: '#007bff',
|
|
104
|
+
color: 'white',
|
|
105
|
+
border: 'none',
|
|
106
|
+
borderRadius: '4px',
|
|
107
|
+
cursor: 'pointer',
|
|
108
|
+
marginRight: '0.5rem',
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
Edit
|
|
112
|
+
</button>
|
|
113
|
+
) : (
|
|
114
|
+
<button
|
|
115
|
+
className="edit_button"
|
|
116
|
+
aria-label="AddComplexDimensionLinkTogglePopover"
|
|
117
|
+
tabIndex="0"
|
|
118
|
+
onClick={() => setPopoverAnchor(!popoverAnchor)}
|
|
119
|
+
title="Add complex dimension link with custom SQL"
|
|
120
|
+
style={{
|
|
121
|
+
marginLeft: '0.5rem',
|
|
122
|
+
padding: '0.25rem 0.5rem',
|
|
123
|
+
fontSize: '0.875rem',
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
<AddItemIcon />
|
|
127
|
+
</button>
|
|
128
|
+
)}
|
|
129
|
+
{popoverAnchor && (
|
|
130
|
+
<>
|
|
131
|
+
{/* Backdrop overlay */}
|
|
132
|
+
<div
|
|
133
|
+
style={{
|
|
134
|
+
position: 'fixed',
|
|
135
|
+
top: 0,
|
|
136
|
+
left: 0,
|
|
137
|
+
right: 0,
|
|
138
|
+
bottom: 0,
|
|
139
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
140
|
+
zIndex: 1000,
|
|
141
|
+
display: 'flex',
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
justifyContent: 'center',
|
|
144
|
+
padding: '2rem',
|
|
145
|
+
}}
|
|
146
|
+
onClick={() => setPopoverAnchor(false)}
|
|
147
|
+
>
|
|
148
|
+
{/* Modal */}
|
|
149
|
+
<div
|
|
150
|
+
role="dialog"
|
|
151
|
+
aria-label="AddComplexDimensionLinkPopover"
|
|
152
|
+
ref={ref}
|
|
153
|
+
onClick={e => e.stopPropagation()}
|
|
154
|
+
style={{
|
|
155
|
+
backgroundColor: 'white',
|
|
156
|
+
borderRadius: '8px',
|
|
157
|
+
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.15)',
|
|
158
|
+
width: '65%',
|
|
159
|
+
maxWidth: '100%',
|
|
160
|
+
maxHeight: '100%',
|
|
161
|
+
overflow: 'visible',
|
|
162
|
+
padding: '1.5rem',
|
|
163
|
+
textTransform: 'none',
|
|
164
|
+
fontWeight: 'normal',
|
|
165
|
+
position: 'relative',
|
|
166
|
+
zIndex: 1000,
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<div
|
|
170
|
+
style={{ maxHeight: 'calc(85vh - 4rem)', overflowY: 'auto' }}
|
|
171
|
+
>
|
|
172
|
+
<Formik
|
|
173
|
+
initialValues={{
|
|
174
|
+
dimensionNode: existingLink?.dimension.name || '',
|
|
175
|
+
joinType: existingLink?.join_type || 'left',
|
|
176
|
+
joinOn: existingLink?.join_sql || '',
|
|
177
|
+
joinCardinality:
|
|
178
|
+
existingLink?.join_cardinality || 'many_to_one',
|
|
179
|
+
role: existingLink?.role || '',
|
|
180
|
+
}}
|
|
181
|
+
onSubmit={handleSubmit}
|
|
182
|
+
validate={values => {
|
|
183
|
+
const errors = {};
|
|
184
|
+
if (!values.dimensionNode) {
|
|
185
|
+
errors.dimensionNode = 'Required';
|
|
186
|
+
}
|
|
187
|
+
if (!values.joinOn) {
|
|
188
|
+
errors.joinOn = 'Required';
|
|
189
|
+
}
|
|
190
|
+
return errors;
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
{function Render({ isSubmitting, status, values }) {
|
|
194
|
+
return (
|
|
195
|
+
<Form>
|
|
196
|
+
<h3
|
|
197
|
+
style={{
|
|
198
|
+
margin: '0 0 1rem 0',
|
|
199
|
+
fontSize: '1.25rem',
|
|
200
|
+
fontWeight: '600',
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
{isEditMode ? 'Edit' : 'Add'} Complex Dimension Link
|
|
204
|
+
</h3>
|
|
205
|
+
{displayMessageAfterSubmit(status)}
|
|
206
|
+
|
|
207
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
208
|
+
<ErrorMessage name="dimensionNode" component="span" />
|
|
209
|
+
<label htmlFor="dimensionNode">
|
|
210
|
+
Dimension Node *
|
|
211
|
+
</label>
|
|
212
|
+
{isEditMode ? (
|
|
213
|
+
<div
|
|
214
|
+
style={{
|
|
215
|
+
padding: '0.5rem',
|
|
216
|
+
backgroundColor: '#f5f5f5',
|
|
217
|
+
borderRadius: '4px',
|
|
218
|
+
color: '#666',
|
|
219
|
+
}}
|
|
220
|
+
>
|
|
221
|
+
{existingLink?.dimension.name}
|
|
222
|
+
<small
|
|
223
|
+
style={{
|
|
224
|
+
display: 'block',
|
|
225
|
+
marginTop: '0.25rem',
|
|
226
|
+
fontSize: '0.75rem',
|
|
227
|
+
color: '#999',
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
To link a different dimension node, remove this
|
|
231
|
+
link and create a new one
|
|
232
|
+
</small>
|
|
233
|
+
</div>
|
|
234
|
+
) : (
|
|
235
|
+
<FormikSelect
|
|
236
|
+
selectOptions={dimensions}
|
|
237
|
+
formikFieldName="dimensionNode"
|
|
238
|
+
placeholder="Select dimension"
|
|
239
|
+
/>
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<div
|
|
244
|
+
style={{
|
|
245
|
+
display: 'flex',
|
|
246
|
+
gap: '1rem',
|
|
247
|
+
marginBottom: '1rem',
|
|
248
|
+
}}
|
|
249
|
+
>
|
|
250
|
+
<div style={{ flex: 1 }}>
|
|
251
|
+
<label htmlFor="joinType">Join Type</label>
|
|
252
|
+
<FormikSelect
|
|
253
|
+
selectOptions={joinTypeOptions}
|
|
254
|
+
formikFieldName="joinType"
|
|
255
|
+
placeholder="Select join type"
|
|
256
|
+
defaultValue={
|
|
257
|
+
values.joinType
|
|
258
|
+
? joinTypeOptions.find(
|
|
259
|
+
opt => opt.value === values.joinType,
|
|
260
|
+
)
|
|
261
|
+
: null
|
|
262
|
+
}
|
|
263
|
+
/>
|
|
264
|
+
</div>
|
|
265
|
+
<div style={{ flex: 1 }}>
|
|
266
|
+
<label htmlFor="joinCardinality">
|
|
267
|
+
Join Cardinality
|
|
268
|
+
</label>
|
|
269
|
+
<FormikSelect
|
|
270
|
+
selectOptions={joinCardinalityOptions}
|
|
271
|
+
formikFieldName="joinCardinality"
|
|
272
|
+
placeholder="Select join cardinality"
|
|
273
|
+
defaultValue={
|
|
274
|
+
values.joinCardinality
|
|
275
|
+
? joinCardinalityOptions.find(
|
|
276
|
+
opt =>
|
|
277
|
+
opt.value === values.joinCardinality,
|
|
278
|
+
)
|
|
279
|
+
: null
|
|
280
|
+
}
|
|
281
|
+
/>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
286
|
+
<ErrorMessage name="joinOn" component="span" />
|
|
287
|
+
<label htmlFor="joinOn">Join SQL *</label>
|
|
288
|
+
<small
|
|
289
|
+
style={{ color: '#6c757d', fontSize: '0.75rem' }}
|
|
290
|
+
>
|
|
291
|
+
Specify the join condition
|
|
292
|
+
</small>
|
|
293
|
+
<Field name="joinOn">
|
|
294
|
+
{({ field, form }) => (
|
|
295
|
+
<div
|
|
296
|
+
role="button"
|
|
297
|
+
tabIndex={0}
|
|
298
|
+
className="relative flex bg-[#282a36]"
|
|
299
|
+
>
|
|
300
|
+
<CodeMirror
|
|
301
|
+
id="joinOn"
|
|
302
|
+
name="joinOn"
|
|
303
|
+
extensions={[langs.sql()]}
|
|
304
|
+
value={field.value?.trim()}
|
|
305
|
+
placeholder="e.g., node_table.dimension_id = dimension_table.id"
|
|
306
|
+
width="100%"
|
|
307
|
+
height="150px"
|
|
308
|
+
style={{
|
|
309
|
+
fontSize: '14px',
|
|
310
|
+
textAlign: 'left',
|
|
311
|
+
}}
|
|
312
|
+
onChange={value => {
|
|
313
|
+
form.setFieldValue('joinOn', value);
|
|
314
|
+
}}
|
|
315
|
+
/>
|
|
316
|
+
</div>
|
|
317
|
+
)}
|
|
318
|
+
</Field>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
322
|
+
<label htmlFor="role">Role (Optional)</label>
|
|
323
|
+
<Field
|
|
324
|
+
type="text"
|
|
325
|
+
name="role"
|
|
326
|
+
id="role"
|
|
327
|
+
placeholder="e.g., birth_date, registration_date"
|
|
328
|
+
style={{
|
|
329
|
+
width: '100%',
|
|
330
|
+
padding: '0.5rem',
|
|
331
|
+
}}
|
|
332
|
+
/>
|
|
333
|
+
<small
|
|
334
|
+
style={{ color: '#6c757d', fontSize: '0.75rem' }}
|
|
335
|
+
>
|
|
336
|
+
Optional role if linking the same dimension multiple
|
|
337
|
+
times
|
|
338
|
+
</small>
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
<button
|
|
342
|
+
className="add_node"
|
|
343
|
+
type="submit"
|
|
344
|
+
aria-label="SaveComplexDimensionLink"
|
|
345
|
+
disabled={isSubmitting}
|
|
346
|
+
style={{ marginTop: '1rem' }}
|
|
347
|
+
>
|
|
348
|
+
{isSubmitting ? (
|
|
349
|
+
<LoadingIcon />
|
|
350
|
+
) : isEditMode ? (
|
|
351
|
+
'Save Changes'
|
|
352
|
+
) : (
|
|
353
|
+
'Add Link'
|
|
354
|
+
)}
|
|
355
|
+
</button>
|
|
356
|
+
</Form>
|
|
357
|
+
);
|
|
358
|
+
}}
|
|
359
|
+
</Formik>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
</>
|
|
364
|
+
)}
|
|
365
|
+
</>
|
|
366
|
+
);
|
|
367
|
+
}
|