datajunction-ui 0.0.1-a61 → 0.0.1-a63
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/AddNodeDropdown.jsx +3 -5
- package/src/app/icons/FilterIcon.jsx +7 -0
- package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
- package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +27 -0
- package/src/app/pages/NamespacePage/TagSelect.jsx +40 -0
- package/src/app/pages/NamespacePage/UserSelect.jsx +43 -0
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +32 -0
- package/src/app/pages/NamespacePage/index.jsx +135 -43
- package/src/app/services/DJService.js +20 -7
- package/src/app/services/__tests__/DJService.test.jsx +1 -1
- package/src/styles/index.css +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export default function
|
|
1
|
+
export default function AddNodeDropdown({ namespace }) {
|
|
2
2
|
return (
|
|
3
|
-
<span className="menu-link">
|
|
3
|
+
<span className="menu-link" style={{ margin: '0.5em 0 0 1em' }}>
|
|
4
4
|
<span className="menu-title">
|
|
5
5
|
<div className="dropdown">
|
|
6
6
|
<span className="add_node">+ Add Node</span>
|
|
@@ -26,9 +26,7 @@ export default function AddNodeDropDown({ namespace }) {
|
|
|
26
26
|
</div>
|
|
27
27
|
</a>
|
|
28
28
|
<a href={`/create/tag`}>
|
|
29
|
-
<div className="entity__tag node_type_creation_heading">
|
|
30
|
-
Tag
|
|
31
|
-
</div>
|
|
29
|
+
<div className="entity__tag node_type_creation_heading">Tag</div>
|
|
32
30
|
</a>
|
|
33
31
|
<a href={`/create/cube/${namespace}`}>
|
|
34
32
|
<div className="node_type__cube node_type_creation_heading">
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { components } from 'react-select';
|
|
2
|
+
|
|
3
|
+
const Control = ({ children, ...props }) => {
|
|
4
|
+
const { label, onLabelClick } = props.selectProps;
|
|
5
|
+
const style = {
|
|
6
|
+
cursor: 'pointer',
|
|
7
|
+
padding: '10px 5px 10px 12px',
|
|
8
|
+
color: 'rgb(112, 110, 115)',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<components.Control {...props}>
|
|
13
|
+
<span onMouseDown={onLabelClick} style={style}>
|
|
14
|
+
{label}
|
|
15
|
+
</span>
|
|
16
|
+
{children}
|
|
17
|
+
</components.Control>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default Control;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Select from 'react-select';
|
|
2
|
+
import Control from './FieldControl';
|
|
3
|
+
|
|
4
|
+
export default function NodeTypeSelect({ onChange }) {
|
|
5
|
+
return (
|
|
6
|
+
<span className="menu-link" style={{ marginLeft: '30px', width: '300px' }}
|
|
7
|
+
data-testid="select-node-type">
|
|
8
|
+
<Select
|
|
9
|
+
name="node_type"
|
|
10
|
+
isClearable
|
|
11
|
+
label="Node Type"
|
|
12
|
+
components={{ Control }}
|
|
13
|
+
onChange={e => onChange(e)}
|
|
14
|
+
styles={{
|
|
15
|
+
control: styles => ({ ...styles, backgroundColor: 'white' }),
|
|
16
|
+
}}
|
|
17
|
+
options={[
|
|
18
|
+
{value: 'source', label: 'Source'},
|
|
19
|
+
{value: 'transform', label: 'Transform'},
|
|
20
|
+
{value: 'dimension', label: 'Dimension'},
|
|
21
|
+
{value: 'metric', label: 'Metric'},
|
|
22
|
+
{value: 'cube', label: 'Cube'},
|
|
23
|
+
]}
|
|
24
|
+
/>
|
|
25
|
+
</span>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useContext, useEffect, useState } from 'react';
|
|
2
|
+
import DJClientContext from '../../providers/djclient';
|
|
3
|
+
import Control from './FieldControl';
|
|
4
|
+
|
|
5
|
+
import Select from 'react-select';
|
|
6
|
+
|
|
7
|
+
export default function TagSelect({ onChange }) {
|
|
8
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
9
|
+
|
|
10
|
+
const [retrieved, setRetrieved] = useState(false);
|
|
11
|
+
const [tags, setTags] = useState([]);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const fetchData = async () => {
|
|
15
|
+
const tags = await djClient.listTags();
|
|
16
|
+
setTags(tags);
|
|
17
|
+
setRetrieved(true);
|
|
18
|
+
};
|
|
19
|
+
fetchData().catch(console.error);
|
|
20
|
+
}, [djClient]);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<span className="menu-link" style={{ marginLeft: '30px', width: '350px' }} data-testid="select-tag">
|
|
24
|
+
<Select
|
|
25
|
+
name="tags"
|
|
26
|
+
isClearable
|
|
27
|
+
isMulti
|
|
28
|
+
label="Tags"
|
|
29
|
+
components={{ Control }}
|
|
30
|
+
onChange={e => onChange(e)}
|
|
31
|
+
options={tags?.map(tag => {
|
|
32
|
+
return {
|
|
33
|
+
value: tag.name,
|
|
34
|
+
label: tag.display_name,
|
|
35
|
+
};
|
|
36
|
+
})}
|
|
37
|
+
/>
|
|
38
|
+
</span>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useContext, useEffect, useState } from 'react';
|
|
2
|
+
import DJClientContext from '../../providers/djclient';
|
|
3
|
+
import Control from './FieldControl';
|
|
4
|
+
|
|
5
|
+
import Select from 'react-select';
|
|
6
|
+
|
|
7
|
+
export default function UserSelect({ onChange, currentUser }) {
|
|
8
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
9
|
+
const [retrieved, setRetrieved] = useState(false);
|
|
10
|
+
const [users, setUsers] = useState([]);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const fetchData = async () => {
|
|
14
|
+
const users = await djClient.users();
|
|
15
|
+
setUsers(users);
|
|
16
|
+
setRetrieved(true);
|
|
17
|
+
};
|
|
18
|
+
fetchData().catch(console.error);
|
|
19
|
+
}, [djClient]);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<span className="menu-link" style={{ marginLeft: '30px', width: '400px' }} data-testid="select-user">
|
|
23
|
+
{retrieved ? (
|
|
24
|
+
<Select
|
|
25
|
+
name="edited_by"
|
|
26
|
+
isClearable
|
|
27
|
+
label="Edited By"
|
|
28
|
+
components={{ Control }}
|
|
29
|
+
onChange={e => onChange(e)}
|
|
30
|
+
defaultValue={{
|
|
31
|
+
value: currentUser,
|
|
32
|
+
label: currentUser,
|
|
33
|
+
}}
|
|
34
|
+
options={users?.map(user => {
|
|
35
|
+
return { value: user.username, label: user.username };
|
|
36
|
+
})}
|
|
37
|
+
/>
|
|
38
|
+
) : (
|
|
39
|
+
''
|
|
40
|
+
)}
|
|
41
|
+
</span>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -9,6 +9,9 @@ const mockDjClient = {
|
|
|
9
9
|
namespaces: jest.fn(),
|
|
10
10
|
namespace: jest.fn(),
|
|
11
11
|
addNamespace: jest.fn(),
|
|
12
|
+
whoami: jest.fn(),
|
|
13
|
+
users: jest.fn(),
|
|
14
|
+
listTags: jest.fn(),
|
|
12
15
|
};
|
|
13
16
|
|
|
14
17
|
describe('NamespacePage', () => {
|
|
@@ -34,6 +37,9 @@ describe('NamespacePage', () => {
|
|
|
34
37
|
|
|
35
38
|
beforeEach(() => {
|
|
36
39
|
fetch.resetMocks();
|
|
40
|
+
mockDjClient.whoami.mockResolvedValue({username: 'dj'});
|
|
41
|
+
mockDjClient.users.mockResolvedValue([{username: 'dj'}, {username: 'user1'}]);
|
|
42
|
+
mockDjClient.listTags.mockResolvedValue([{name: 'tag1'}, {name: 'tag2'}]);
|
|
37
43
|
mockDjClient.namespaces.mockResolvedValue([
|
|
38
44
|
{
|
|
39
45
|
namespace: 'common.one',
|
|
@@ -75,6 +81,8 @@ describe('NamespacePage', () => {
|
|
|
75
81
|
type: 'transform',
|
|
76
82
|
mode: 'active',
|
|
77
83
|
updated_at: new Date(),
|
|
84
|
+
tags: [{name: 'tag1'}],
|
|
85
|
+
edited_by: ['dj'],
|
|
78
86
|
},
|
|
79
87
|
]);
|
|
80
88
|
});
|
|
@@ -111,6 +119,30 @@ describe('NamespacePage', () => {
|
|
|
111
119
|
// check that it renders nodes
|
|
112
120
|
expect(screen.getByText('Test Node')).toBeInTheDocument();
|
|
113
121
|
|
|
122
|
+
// check that it sorts nodes
|
|
123
|
+
fireEvent.click(screen.getByText('name'));
|
|
124
|
+
fireEvent.click(screen.getByText('display name'));
|
|
125
|
+
|
|
126
|
+
// check that we can filter by node type
|
|
127
|
+
const selectNodeType = screen.getAllByTestId('select-node-type')[0];
|
|
128
|
+
expect(selectNodeType).toBeDefined();
|
|
129
|
+
expect(selectNodeType).not.toBeNull();
|
|
130
|
+
fireEvent.keyDown(selectNodeType.firstChild, { key: 'ArrowDown' });
|
|
131
|
+
fireEvent.click(screen.getByText('Source'));
|
|
132
|
+
|
|
133
|
+
// check that we can filter by tag
|
|
134
|
+
const selectTag = screen.getAllByTestId('select-tag')[0];
|
|
135
|
+
expect(selectTag).toBeDefined();
|
|
136
|
+
expect(selectTag).not.toBeNull();
|
|
137
|
+
fireEvent.keyDown(selectTag.firstChild, { key: 'ArrowDown' });
|
|
138
|
+
|
|
139
|
+
// check that we can filter by user
|
|
140
|
+
const selectUser = screen.getAllByTestId('select-user')[0];
|
|
141
|
+
expect(selectUser).toBeDefined();
|
|
142
|
+
expect(selectUser).not.toBeNull();
|
|
143
|
+
fireEvent.keyDown(selectUser.firstChild, { key: 'ArrowDown' });
|
|
144
|
+
// fireEvent.click(screen.getByText('dj'));
|
|
145
|
+
|
|
114
146
|
// click to open and close tab
|
|
115
147
|
fireEvent.click(screen.getByText('common'));
|
|
116
148
|
fireEvent.click(screen.getByText('common'));
|
|
@@ -7,6 +7,12 @@ import Explorer from '../NamespacePage/Explorer';
|
|
|
7
7
|
import AddNodeDropdown from '../../components/AddNodeDropdown';
|
|
8
8
|
import NodeListActions from '../../components/NodeListActions';
|
|
9
9
|
import AddNamespacePopover from './AddNamespacePopover';
|
|
10
|
+
import FilterIcon from '../../icons/FilterIcon';
|
|
11
|
+
import LoadingIcon from '../../icons/LoadingIcon';
|
|
12
|
+
import UserSelect from './UserSelect';
|
|
13
|
+
import NodeTypeSelect from './NodeTypeSelect';
|
|
14
|
+
import TagSelect from './TagSelect';
|
|
15
|
+
|
|
10
16
|
import 'styles/node-list.css';
|
|
11
17
|
import 'styles/sorted-table.css';
|
|
12
18
|
|
|
@@ -23,12 +29,39 @@ export function NamespacePage() {
|
|
|
23
29
|
namespace: namespace,
|
|
24
30
|
nodes: [],
|
|
25
31
|
});
|
|
32
|
+
const [retrieved, setRetrieved] = useState(false);
|
|
33
|
+
const [currentUser, setCurrentUser] = useState(null);
|
|
34
|
+
|
|
35
|
+
const [filters, setFilters] = useState({
|
|
36
|
+
tags: [],
|
|
37
|
+
node_type: '',
|
|
38
|
+
edited_by: currentUser?.username,
|
|
39
|
+
});
|
|
26
40
|
|
|
27
41
|
const [namespaceHierarchy, setNamespaceHierarchy] = useState([]);
|
|
28
42
|
|
|
29
|
-
const [sortConfig, setSortConfig] = useState({
|
|
43
|
+
const [sortConfig, setSortConfig] = useState({
|
|
44
|
+
key: 'updated_at',
|
|
45
|
+
direction: DESC,
|
|
46
|
+
});
|
|
30
47
|
const sortedNodes = React.useMemo(() => {
|
|
31
48
|
let sortableData = [...Object.values(state.nodes)];
|
|
49
|
+
if (filters.node_type !== '' && filters.node_type !== null) {
|
|
50
|
+
sortableData = sortableData.filter(
|
|
51
|
+
node => node.type === filters.node_type,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (filters.tags) {
|
|
55
|
+
sortableData = sortableData.filter(node => {
|
|
56
|
+
const nodeTags = node.tags.map(tag => tag.name);
|
|
57
|
+
return filters.tags.every(item => nodeTags.includes(item));
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (filters.edited_by) {
|
|
61
|
+
sortableData = sortableData.filter(node => {
|
|
62
|
+
return node.edited_by.includes(filters.edited_by);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
32
65
|
if (sortConfig !== null) {
|
|
33
66
|
sortableData.sort((a, b) => {
|
|
34
67
|
if (a[sortConfig.key] < b[sortConfig.key]) {
|
|
@@ -41,9 +74,9 @@ export function NamespacePage() {
|
|
|
41
74
|
});
|
|
42
75
|
}
|
|
43
76
|
return sortableData;
|
|
44
|
-
}, [state.nodes, sortConfig]);
|
|
77
|
+
}, [state.nodes, filters, sortConfig]);
|
|
45
78
|
|
|
46
|
-
const requestSort =
|
|
79
|
+
const requestSort = key => {
|
|
47
80
|
let direction = ASC;
|
|
48
81
|
if (sortConfig.key === key && sortConfig.direction === ASC) {
|
|
49
82
|
direction = DESC;
|
|
@@ -51,7 +84,7 @@ export function NamespacePage() {
|
|
|
51
84
|
setSortConfig({ key, direction });
|
|
52
85
|
};
|
|
53
86
|
|
|
54
|
-
const getClassNamesFor =
|
|
87
|
+
const getClassNamesFor = name => {
|
|
55
88
|
if (sortConfig.key === name) {
|
|
56
89
|
return sortConfig.direction;
|
|
57
90
|
}
|
|
@@ -91,6 +124,8 @@ export function NamespacePage() {
|
|
|
91
124
|
const namespaces = await djClient.namespaces();
|
|
92
125
|
const hierarchy = createNamespaceHierarchy(namespaces);
|
|
93
126
|
setNamespaceHierarchy(hierarchy);
|
|
127
|
+
const currentUser = await djClient.whoami();
|
|
128
|
+
setCurrentUser(currentUser);
|
|
94
129
|
};
|
|
95
130
|
fetchData().catch(console.error);
|
|
96
131
|
}, [djClient, djClient.namespaces]);
|
|
@@ -106,53 +141,106 @@ export function NamespacePage() {
|
|
|
106
141
|
namespace: namespace,
|
|
107
142
|
nodes: foundNodes,
|
|
108
143
|
});
|
|
144
|
+
setRetrieved(true);
|
|
109
145
|
};
|
|
110
146
|
fetchData().catch(console.error);
|
|
111
147
|
}, [djClient, namespace, namespaceHierarchy]);
|
|
112
148
|
|
|
113
|
-
const nodesList =
|
|
114
|
-
|
|
115
|
-
<
|
|
116
|
-
<
|
|
117
|
-
{node.name}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
<
|
|
128
|
-
{
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<
|
|
133
|
-
{node.type}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
<
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
const nodesList = retrieved ? (
|
|
150
|
+
sortedNodes.map(node => (
|
|
151
|
+
<tr>
|
|
152
|
+
<td>
|
|
153
|
+
<a href={'/nodes/' + node.name} className="link-table">
|
|
154
|
+
{node.name}
|
|
155
|
+
</a>
|
|
156
|
+
<span
|
|
157
|
+
className="rounded-pill badge bg-secondary-soft"
|
|
158
|
+
style={{ marginLeft: '0.5rem' }}
|
|
159
|
+
>
|
|
160
|
+
{node.version}
|
|
161
|
+
</span>
|
|
162
|
+
</td>
|
|
163
|
+
<td>
|
|
164
|
+
<a href={'/nodes/' + node.name} className="link-table">
|
|
165
|
+
{node.type !== 'source' ? node.display_name : ''}
|
|
166
|
+
</a>
|
|
167
|
+
</td>
|
|
168
|
+
<td>
|
|
169
|
+
<span className={'node_type__' + node.type + ' badge node_type'}>
|
|
170
|
+
{node.type}
|
|
171
|
+
</span>
|
|
172
|
+
</td>
|
|
173
|
+
<td>
|
|
174
|
+
<NodeStatus node={node} revalidate={false} />
|
|
175
|
+
</td>
|
|
176
|
+
<td>
|
|
177
|
+
<span className="status">
|
|
178
|
+
{new Date(node.updated_at).toLocaleString('en-us')}
|
|
179
|
+
</span>
|
|
180
|
+
</td>
|
|
181
|
+
<td>
|
|
182
|
+
<NodeListActions nodeName={node?.name} />
|
|
183
|
+
</td>
|
|
184
|
+
</tr>
|
|
185
|
+
))
|
|
186
|
+
) : (
|
|
187
|
+
<span style={{ display: 'block', marginTop: '2rem' }}>
|
|
188
|
+
<LoadingIcon />
|
|
189
|
+
</span>
|
|
190
|
+
);
|
|
149
191
|
|
|
150
192
|
return (
|
|
151
193
|
<div className="mid">
|
|
152
194
|
<div className="card">
|
|
153
195
|
<div className="card-header">
|
|
154
196
|
<h2>Explore</h2>
|
|
155
|
-
<
|
|
197
|
+
<div class="menu" style={{ margin: '0 0 20px 0' }}>
|
|
198
|
+
<div
|
|
199
|
+
className="menu-link"
|
|
200
|
+
style={{
|
|
201
|
+
marginTop: '0.7em',
|
|
202
|
+
color: '#777',
|
|
203
|
+
fontFamily: "'Jost'",
|
|
204
|
+
fontSize: '18px',
|
|
205
|
+
marginRight: '10px',
|
|
206
|
+
marginLeft: '15px',
|
|
207
|
+
}}
|
|
208
|
+
>
|
|
209
|
+
<FilterIcon />
|
|
210
|
+
</div>
|
|
211
|
+
<div
|
|
212
|
+
className="menu-link"
|
|
213
|
+
style={{
|
|
214
|
+
marginTop: '0.6em',
|
|
215
|
+
color: '#777',
|
|
216
|
+
fontFamily: "'Jost'",
|
|
217
|
+
fontSize: '18px',
|
|
218
|
+
marginRight: '10px',
|
|
219
|
+
}}
|
|
220
|
+
>
|
|
221
|
+
Filter By
|
|
222
|
+
</div>
|
|
223
|
+
<NodeTypeSelect
|
|
224
|
+
onChange={entry =>
|
|
225
|
+
setFilters({ ...filters, node_type: entry ? entry.value : '' })
|
|
226
|
+
}
|
|
227
|
+
/>
|
|
228
|
+
<TagSelect
|
|
229
|
+
onChange={entry =>
|
|
230
|
+
setFilters({
|
|
231
|
+
...filters,
|
|
232
|
+
tags: entry ? entry.map(tag => tag.value) : [],
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
/>
|
|
236
|
+
<UserSelect
|
|
237
|
+
onChange={entry =>
|
|
238
|
+
setFilters({ ...filters, edited_by: entry ? entry.value : '' })
|
|
239
|
+
}
|
|
240
|
+
currentUser={currentUser?.username}
|
|
241
|
+
/>
|
|
242
|
+
<AddNodeDropdown namespace={namespace} />
|
|
243
|
+
</div>
|
|
156
244
|
<div className="table-responsive">
|
|
157
245
|
<div className={`sidebar`}>
|
|
158
246
|
<span
|
|
@@ -164,7 +252,7 @@ export function NamespacePage() {
|
|
|
164
252
|
padding: '1rem 1rem 1rem 0',
|
|
165
253
|
}}
|
|
166
254
|
>
|
|
167
|
-
Namespaces <AddNamespacePopover namespace={namespace}/>
|
|
255
|
+
Namespaces <AddNamespacePopover namespace={namespace} />
|
|
168
256
|
</span>
|
|
169
257
|
{namespaceHierarchy
|
|
170
258
|
? namespaceHierarchy.map(child => (
|
|
@@ -182,7 +270,11 @@ export function NamespacePage() {
|
|
|
182
270
|
{fields.map(field => {
|
|
183
271
|
return (
|
|
184
272
|
<th>
|
|
185
|
-
<button
|
|
273
|
+
<button
|
|
274
|
+
type="button"
|
|
275
|
+
onClick={() => requestSort(field)}
|
|
276
|
+
className={'sortable ' + getClassNamesFor(field)}
|
|
277
|
+
>
|
|
186
278
|
{field.replace('_', ' ')}
|
|
187
279
|
</button>
|
|
188
280
|
</th>
|
|
@@ -348,7 +348,7 @@ export const DataJunctionAPI = {
|
|
|
348
348
|
|
|
349
349
|
namespace: async function (nmspce) {
|
|
350
350
|
return await (
|
|
351
|
-
await fetch(`${DJ_URL}/namespaces/${nmspce}
|
|
351
|
+
await fetch(`${DJ_URL}/namespaces/${nmspce}/?with_edited_by=true`, {
|
|
352
352
|
credentials: 'include',
|
|
353
353
|
})
|
|
354
354
|
).json();
|
|
@@ -453,16 +453,22 @@ export const DataJunctionAPI = {
|
|
|
453
453
|
},
|
|
454
454
|
|
|
455
455
|
notebookExportCube: async function (cube) {
|
|
456
|
-
return await fetch(
|
|
457
|
-
|
|
458
|
-
|
|
456
|
+
return await fetch(
|
|
457
|
+
`${DJ_URL}/datajunction-clients/python/notebook/?cube=${cube}`,
|
|
458
|
+
{
|
|
459
|
+
credentials: 'include',
|
|
460
|
+
},
|
|
461
|
+
);
|
|
459
462
|
},
|
|
460
463
|
|
|
461
464
|
notebookExportNamespace: async function (namespace) {
|
|
462
465
|
return await (
|
|
463
|
-
await fetch(
|
|
464
|
-
|
|
465
|
-
|
|
466
|
+
await fetch(
|
|
467
|
+
`${DJ_URL}/datajunction-clients/python/notebook/?namespace=${namespace}`,
|
|
468
|
+
{
|
|
469
|
+
credentials: 'include',
|
|
470
|
+
},
|
|
471
|
+
)
|
|
466
472
|
).json();
|
|
467
473
|
},
|
|
468
474
|
|
|
@@ -732,6 +738,13 @@ export const DataJunctionAPI = {
|
|
|
732
738
|
});
|
|
733
739
|
return await response.json();
|
|
734
740
|
},
|
|
741
|
+
users: async function () {
|
|
742
|
+
return await (
|
|
743
|
+
await fetch(`${DJ_URL}/users?with_activity=true`, {
|
|
744
|
+
credentials: 'include',
|
|
745
|
+
})
|
|
746
|
+
).json();
|
|
747
|
+
},
|
|
735
748
|
getTag: async function (tagName) {
|
|
736
749
|
const response = await fetch(`${DJ_URL}/tags/${tagName}`, {
|
|
737
750
|
method: 'GET',
|
|
@@ -426,7 +426,7 @@ describe('DataJunctionAPI', () => {
|
|
|
426
426
|
const nmspce = 'sampleNamespace';
|
|
427
427
|
fetch.mockResponseOnce(JSON.stringify({}));
|
|
428
428
|
await DataJunctionAPI.namespace(nmspce);
|
|
429
|
-
expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/namespaces/${nmspce}
|
|
429
|
+
expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/namespaces/${nmspce}/?with_edited_by=true`, {
|
|
430
430
|
credentials: 'include',
|
|
431
431
|
});
|
|
432
432
|
});
|
package/src/styles/index.css
CHANGED
|
@@ -289,6 +289,10 @@ tr {
|
|
|
289
289
|
grid-gap: 8px;
|
|
290
290
|
padding: 8px;
|
|
291
291
|
}
|
|
292
|
+
.table-responsive {
|
|
293
|
+
width: auto;
|
|
294
|
+
grid-template-columns: 200px auto;
|
|
295
|
+
}
|
|
292
296
|
.table-vertical {
|
|
293
297
|
display: contents;
|
|
294
298
|
}
|
|
@@ -732,7 +736,7 @@ pre {
|
|
|
732
736
|
}
|
|
733
737
|
.menu-link {
|
|
734
738
|
display: inline-grid;
|
|
735
|
-
margin:
|
|
739
|
+
margin: 0;
|
|
736
740
|
float: right;
|
|
737
741
|
}
|
|
738
742
|
.menu-item .menu-link {
|