datajunction-ui 0.0.1-a91 → 0.0.1-a92
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
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { useContext, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import DJClientContext from '../../providers/djclient';
|
|
4
|
+
import { Form, Formik } from 'formik';
|
|
5
|
+
import EditIcon from '../../icons/EditIcon';
|
|
6
|
+
import { displayMessageAfterSubmit } from '../../../utils/form';
|
|
7
|
+
|
|
8
|
+
export default function EditColumnDescriptionPopover({
|
|
9
|
+
column,
|
|
10
|
+
node,
|
|
11
|
+
onSubmit,
|
|
12
|
+
}) {
|
|
13
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
14
|
+
const [popoverAnchor, setPopoverAnchor] = useState(false);
|
|
15
|
+
const ref = useRef(null);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const handleClickOutside = event => {
|
|
19
|
+
if (ref.current && !ref.current.contains(event.target)) {
|
|
20
|
+
setPopoverAnchor(false);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
document.addEventListener('click', handleClickOutside, true);
|
|
24
|
+
return () => {
|
|
25
|
+
document.removeEventListener('click', handleClickOutside, true);
|
|
26
|
+
};
|
|
27
|
+
}, [setPopoverAnchor]);
|
|
28
|
+
|
|
29
|
+
const saveDescription = async (
|
|
30
|
+
{ node, column, description },
|
|
31
|
+
{ setSubmitting, setStatus },
|
|
32
|
+
) => {
|
|
33
|
+
setSubmitting(false);
|
|
34
|
+
const response = await djClient.setColumnDescription(
|
|
35
|
+
node,
|
|
36
|
+
column,
|
|
37
|
+
description,
|
|
38
|
+
);
|
|
39
|
+
if (response.status === 200 || response.status === 201) {
|
|
40
|
+
setStatus({ success: 'Saved!' });
|
|
41
|
+
} else {
|
|
42
|
+
setStatus({
|
|
43
|
+
failure: `${response.json.message}`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
onSubmit();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
<button
|
|
52
|
+
className="edit_button"
|
|
53
|
+
aria-label="EditColumnDescription"
|
|
54
|
+
tabIndex="0"
|
|
55
|
+
onClick={() => {
|
|
56
|
+
setPopoverAnchor(!popoverAnchor);
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<EditIcon />
|
|
60
|
+
</button>
|
|
61
|
+
<div
|
|
62
|
+
className="popover"
|
|
63
|
+
role="dialog"
|
|
64
|
+
aria-label="edit-description"
|
|
65
|
+
style={{ display: popoverAnchor === false ? 'none' : 'block' }}
|
|
66
|
+
ref={ref}
|
|
67
|
+
>
|
|
68
|
+
<Formik
|
|
69
|
+
initialValues={{
|
|
70
|
+
column: column.name,
|
|
71
|
+
node: node.name,
|
|
72
|
+
description: column.description || '',
|
|
73
|
+
}}
|
|
74
|
+
onSubmit={saveDescription}
|
|
75
|
+
>
|
|
76
|
+
{function Render({ isSubmitting, status, setFieldValue }) {
|
|
77
|
+
return (
|
|
78
|
+
<Form>
|
|
79
|
+
{displayMessageAfterSubmit(status)}
|
|
80
|
+
<div className="form-group mt-3">
|
|
81
|
+
<label htmlFor="description">Description</label>
|
|
82
|
+
<textarea
|
|
83
|
+
name="description"
|
|
84
|
+
className="form-control"
|
|
85
|
+
rows="3"
|
|
86
|
+
onChange={e => setFieldValue('description', e.target.value)}
|
|
87
|
+
defaultValue={column.description || ''}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
<input
|
|
91
|
+
hidden={true}
|
|
92
|
+
name="column"
|
|
93
|
+
value={column.name}
|
|
94
|
+
readOnly={true}
|
|
95
|
+
/>
|
|
96
|
+
<input
|
|
97
|
+
hidden={true}
|
|
98
|
+
name="node"
|
|
99
|
+
value={node.name}
|
|
100
|
+
readOnly={true}
|
|
101
|
+
/>
|
|
102
|
+
<button
|
|
103
|
+
className="btn btn-primary mt-3"
|
|
104
|
+
type="submit"
|
|
105
|
+
disabled={isSubmitting}
|
|
106
|
+
>
|
|
107
|
+
Save
|
|
108
|
+
</button>
|
|
109
|
+
</Form>
|
|
110
|
+
);
|
|
111
|
+
}}
|
|
112
|
+
</Formik>
|
|
113
|
+
</div>
|
|
114
|
+
</>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import EditColumnPopover from './EditColumnPopover';
|
|
4
|
+
import EditColumnDescriptionPopover from './EditColumnDescriptionPopover';
|
|
4
5
|
import LinkDimensionPopover from './LinkDimensionPopover';
|
|
5
6
|
import { labelize } from '../../../utils/form';
|
|
6
7
|
import PartitionColumnPopover from './PartitionColumnPopover';
|
|
@@ -113,6 +114,24 @@ export default function NodeColumnTab({ node, djClient }) {
|
|
|
113
114
|
{col.display_name}
|
|
114
115
|
</span>
|
|
115
116
|
</td>
|
|
117
|
+
<td>
|
|
118
|
+
<span
|
|
119
|
+
className=""
|
|
120
|
+
role="columnheader"
|
|
121
|
+
aria-label="ColumnDescription"
|
|
122
|
+
aria-hidden="false"
|
|
123
|
+
>
|
|
124
|
+
{col.description || ''}
|
|
125
|
+
<EditColumnDescriptionPopover
|
|
126
|
+
column={col}
|
|
127
|
+
node={node}
|
|
128
|
+
onSubmit={async () => {
|
|
129
|
+
const res = await djClient.node(node.name);
|
|
130
|
+
setColumns(res.columns);
|
|
131
|
+
}}
|
|
132
|
+
/>
|
|
133
|
+
</span>
|
|
134
|
+
</td>
|
|
116
135
|
<td>
|
|
117
136
|
<span
|
|
118
137
|
className={`node_type__${
|
|
@@ -188,6 +207,7 @@ export default function NodeColumnTab({ node, djClient }) {
|
|
|
188
207
|
<tr>
|
|
189
208
|
<th className="text-start">Column</th>
|
|
190
209
|
<th>Display Name</th>
|
|
210
|
+
<th>Description</th>
|
|
191
211
|
<th>Type</th>
|
|
192
212
|
{node?.type !== 'cube' ? (
|
|
193
213
|
<>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
render,
|
|
4
|
+
fireEvent,
|
|
5
|
+
waitFor,
|
|
6
|
+
screen,
|
|
7
|
+
within,
|
|
8
|
+
} from '@testing-library/react';
|
|
9
|
+
import EditColumnDescriptionPopover from '../EditColumnDescriptionPopover';
|
|
10
|
+
import DJClientContext from '../../../providers/djclient';
|
|
11
|
+
|
|
12
|
+
const mockDjClient = {
|
|
13
|
+
DataJunctionAPI: {
|
|
14
|
+
setColumnDescription: jest.fn(),
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe('<EditColumnDescriptionPopover />', () => {
|
|
19
|
+
it('renders correctly and handles successful form submission', async () => {
|
|
20
|
+
// Mock necessary data
|
|
21
|
+
const column = {
|
|
22
|
+
name: 'column1',
|
|
23
|
+
description: 'Initial description',
|
|
24
|
+
};
|
|
25
|
+
const node = { name: 'default.node1' };
|
|
26
|
+
|
|
27
|
+
// Mock onSubmit function
|
|
28
|
+
const onSubmitMock = jest.fn();
|
|
29
|
+
|
|
30
|
+
mockDjClient.DataJunctionAPI.setColumnDescription.mockReturnValue({
|
|
31
|
+
status: 200,
|
|
32
|
+
json: { message: 'Description updated successfully' },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Render the component
|
|
36
|
+
render(
|
|
37
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
38
|
+
<EditColumnDescriptionPopover
|
|
39
|
+
column={column}
|
|
40
|
+
node={node}
|
|
41
|
+
onSubmit={onSubmitMock}
|
|
42
|
+
/>
|
|
43
|
+
</DJClientContext.Provider>,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Open the popover
|
|
47
|
+
fireEvent.click(screen.getByLabelText('EditColumnDescription'));
|
|
48
|
+
|
|
49
|
+
// Update the description
|
|
50
|
+
const dialog = screen.getByRole('dialog', { name: /edit-description/i });
|
|
51
|
+
const descriptionTextarea = within(dialog).getByRole('textbox');
|
|
52
|
+
fireEvent.change(descriptionTextarea, {
|
|
53
|
+
target: { value: 'Updated description' },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Submit the form
|
|
57
|
+
fireEvent.click(screen.getByText('Save'));
|
|
58
|
+
|
|
59
|
+
// Expect setColumnDescription to be called
|
|
60
|
+
await waitFor(() => {
|
|
61
|
+
expect(
|
|
62
|
+
mockDjClient.DataJunctionAPI.setColumnDescription,
|
|
63
|
+
).toHaveBeenCalledWith('default.node1', 'column1', 'Updated description');
|
|
64
|
+
expect(screen.getByText('Saved!')).toBeInTheDocument();
|
|
65
|
+
expect(onSubmitMock).toHaveBeenCalled();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('handles failed form submission', async () => {
|
|
70
|
+
// Mock necessary data
|
|
71
|
+
const column = {
|
|
72
|
+
name: 'column1',
|
|
73
|
+
description: 'Initial description',
|
|
74
|
+
};
|
|
75
|
+
const node = { name: 'default.node1' };
|
|
76
|
+
|
|
77
|
+
// Mock onSubmit function
|
|
78
|
+
const onSubmitMock = jest.fn();
|
|
79
|
+
|
|
80
|
+
mockDjClient.DataJunctionAPI.setColumnDescription.mockReturnValue({
|
|
81
|
+
status: 500,
|
|
82
|
+
json: { message: 'Server error' },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Render the component
|
|
86
|
+
render(
|
|
87
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
88
|
+
<EditColumnDescriptionPopover
|
|
89
|
+
column={column}
|
|
90
|
+
node={node}
|
|
91
|
+
onSubmit={onSubmitMock}
|
|
92
|
+
/>
|
|
93
|
+
</DJClientContext.Provider>,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Open the popover
|
|
97
|
+
fireEvent.click(screen.getByLabelText('EditColumnDescription'));
|
|
98
|
+
|
|
99
|
+
// Update the description
|
|
100
|
+
const dialog = screen.getByRole('dialog', { name: /edit-description/i });
|
|
101
|
+
const descriptionTextarea = within(dialog).getByRole('textbox');
|
|
102
|
+
fireEvent.change(descriptionTextarea, {
|
|
103
|
+
target: { value: 'Updated description' },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Submit the form
|
|
107
|
+
fireEvent.click(screen.getByText('Save'));
|
|
108
|
+
|
|
109
|
+
// Expect setColumnDescription to be called and the failure message to show up
|
|
110
|
+
await waitFor(() => {
|
|
111
|
+
expect(
|
|
112
|
+
mockDjClient.DataJunctionAPI.setColumnDescription,
|
|
113
|
+
).toHaveBeenCalledWith('default.node1', 'column1', 'Updated description');
|
|
114
|
+
expect(screen.getByText('Server error')).toBeInTheDocument();
|
|
115
|
+
expect(onSubmitMock).toHaveBeenCalled();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('renders with empty initial description', async () => {
|
|
120
|
+
// Mock necessary data with no description
|
|
121
|
+
const column = {
|
|
122
|
+
name: 'column1',
|
|
123
|
+
description: null,
|
|
124
|
+
};
|
|
125
|
+
const node = { name: 'default.node1' };
|
|
126
|
+
|
|
127
|
+
// Mock onSubmit function
|
|
128
|
+
const onSubmitMock = jest.fn();
|
|
129
|
+
|
|
130
|
+
// Render the component
|
|
131
|
+
render(
|
|
132
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
133
|
+
<EditColumnDescriptionPopover
|
|
134
|
+
column={column}
|
|
135
|
+
node={node}
|
|
136
|
+
onSubmit={onSubmitMock}
|
|
137
|
+
/>
|
|
138
|
+
</DJClientContext.Provider>,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Open the popover
|
|
142
|
+
fireEvent.click(screen.getByLabelText('EditColumnDescription'));
|
|
143
|
+
|
|
144
|
+
// Check that the textarea is empty
|
|
145
|
+
const dialog = screen.getByRole('dialog', { name: /edit-description/i });
|
|
146
|
+
const descriptionTextarea = within(dialog).getByRole('textbox');
|
|
147
|
+
expect(descriptionTextarea.value).toBe('');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -742,6 +742,19 @@ export const DataJunctionAPI = {
|
|
|
742
742
|
);
|
|
743
743
|
return { status: response.status, json: await response.json() };
|
|
744
744
|
},
|
|
745
|
+
|
|
746
|
+
setColumnDescription: async function (nodeName, columnName, description) {
|
|
747
|
+
const response = await fetch(
|
|
748
|
+
`${DJ_URL}/nodes/${nodeName}/columns/${columnName}/description?description=${encodeURIComponent(
|
|
749
|
+
description,
|
|
750
|
+
)}`,
|
|
751
|
+
{
|
|
752
|
+
method: 'PATCH',
|
|
753
|
+
credentials: 'include',
|
|
754
|
+
},
|
|
755
|
+
);
|
|
756
|
+
return { status: response.status, json: await response.json() };
|
|
757
|
+
},
|
|
745
758
|
dimensions: async function () {
|
|
746
759
|
return await (
|
|
747
760
|
await fetch(`${DJ_URL}/dimensions`, {
|