datajunction-ui 0.0.1-rc.25 → 0.0.1-rc.27
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/pages/LoginPage/LoginForm.jsx +10 -2
- package/src/app/pages/LoginPage/SignupForm.jsx +15 -3
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +166 -0
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +161 -0
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +0 -1
- package/src/app/pages/NodePage/NodeColumnTab.jsx +91 -25
- package/src/app/pages/NodePage/NodeInfoTab.jsx +28 -22
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +158 -99
- 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__/NodePage.test.jsx +43 -14
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +101 -100
- package/src/app/services/DJService.js +87 -0
- package/src/app/services/__tests__/DJService.test.jsx +112 -0
- package/src/mocks/mockNodes.jsx +56 -67
- package/src/styles/dag.css +4 -0
- package/src/styles/index.css +70 -1
- package/src/styles/loading.css +31 -32
- package/src/styles/login.css +2 -2
package/package.json
CHANGED
|
@@ -63,13 +63,21 @@ export default function LoginForm({ setShowSignup }) {
|
|
|
63
63
|
<Field type="text" name="username" placeholder="Username" />
|
|
64
64
|
</div>
|
|
65
65
|
<div>
|
|
66
|
-
<ErrorMessage
|
|
66
|
+
<ErrorMessage
|
|
67
|
+
className="form-error"
|
|
68
|
+
name="username"
|
|
69
|
+
component="span"
|
|
70
|
+
/>
|
|
67
71
|
</div>
|
|
68
72
|
<div>
|
|
69
73
|
<Field type="password" name="password" placeholder="Password" />
|
|
70
74
|
</div>
|
|
71
75
|
<div>
|
|
72
|
-
<ErrorMessage
|
|
76
|
+
<ErrorMessage
|
|
77
|
+
className="form-error"
|
|
78
|
+
name="password"
|
|
79
|
+
component="span"
|
|
80
|
+
/>
|
|
73
81
|
</div>
|
|
74
82
|
<div>
|
|
75
83
|
<p>
|
|
@@ -81,13 +81,21 @@ export default function SignupForm({ setShowSignup }) {
|
|
|
81
81
|
<Field type="text" name="email" placeholder="Email" />
|
|
82
82
|
</div>
|
|
83
83
|
<div>
|
|
84
|
-
<ErrorMessage
|
|
84
|
+
<ErrorMessage
|
|
85
|
+
className="form-error"
|
|
86
|
+
name="email"
|
|
87
|
+
component="span"
|
|
88
|
+
/>
|
|
85
89
|
</div>
|
|
86
90
|
<div>
|
|
87
91
|
<Field type="text" name="signupUsername" placeholder="Username" />
|
|
88
92
|
</div>
|
|
89
93
|
<div>
|
|
90
|
-
<ErrorMessage
|
|
94
|
+
<ErrorMessage
|
|
95
|
+
className="form-error"
|
|
96
|
+
name="signupUsername"
|
|
97
|
+
component="span"
|
|
98
|
+
/>
|
|
91
99
|
</div>
|
|
92
100
|
<div>
|
|
93
101
|
<Field
|
|
@@ -97,7 +105,11 @@ export default function SignupForm({ setShowSignup }) {
|
|
|
97
105
|
/>
|
|
98
106
|
</div>
|
|
99
107
|
<div>
|
|
100
|
-
<ErrorMessage
|
|
108
|
+
<ErrorMessage
|
|
109
|
+
className="form-error"
|
|
110
|
+
name="signupPassword"
|
|
111
|
+
component="span"
|
|
112
|
+
/>
|
|
101
113
|
</div>
|
|
102
114
|
<div>
|
|
103
115
|
<p>
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
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 AddMaterializationPopover({ node, onSubmit }) {
|
|
10
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
11
|
+
const [popoverAnchor, setPopoverAnchor] = useState(false);
|
|
12
|
+
const [engines, setEngines] = useState([]);
|
|
13
|
+
const [defaultEngine, setDefaultEngine] = useState('');
|
|
14
|
+
|
|
15
|
+
const ref = useRef(null);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const fetchData = async () => {
|
|
19
|
+
const engines = await djClient.engines();
|
|
20
|
+
setEngines(engines);
|
|
21
|
+
setDefaultEngine(
|
|
22
|
+
engines && engines.length > 0
|
|
23
|
+
? engines[0].name + '__' + engines[0].version
|
|
24
|
+
: '',
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
fetchData().catch(console.error);
|
|
28
|
+
const handleClickOutside = event => {
|
|
29
|
+
if (ref.current && !ref.current.contains(event.target)) {
|
|
30
|
+
setPopoverAnchor(false);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
document.addEventListener('click', handleClickOutside, true);
|
|
34
|
+
return () => {
|
|
35
|
+
document.removeEventListener('click', handleClickOutside, true);
|
|
36
|
+
};
|
|
37
|
+
}, [djClient, setPopoverAnchor]);
|
|
38
|
+
|
|
39
|
+
const configureMaterialization = async (
|
|
40
|
+
values,
|
|
41
|
+
{ setSubmitting, setStatus },
|
|
42
|
+
) => {
|
|
43
|
+
setSubmitting(false);
|
|
44
|
+
const engineVersion = values.engine.split('__').slice(-1).join('');
|
|
45
|
+
const engineName = values.engine.split('__').slice(0, -1).join('');
|
|
46
|
+
const response = await djClient.materialize(
|
|
47
|
+
values.node,
|
|
48
|
+
engineName,
|
|
49
|
+
engineVersion,
|
|
50
|
+
values.schedule,
|
|
51
|
+
values.config,
|
|
52
|
+
);
|
|
53
|
+
if (response.status === 200 || response.status === 201) {
|
|
54
|
+
setStatus({ success: 'Saved!' });
|
|
55
|
+
} else {
|
|
56
|
+
setStatus({
|
|
57
|
+
failure: `${response.json.message}`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
onSubmit();
|
|
61
|
+
// window.location.reload();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<>
|
|
66
|
+
<button
|
|
67
|
+
className="edit_button"
|
|
68
|
+
aria-label="PartitionColumn"
|
|
69
|
+
tabIndex="0"
|
|
70
|
+
onClick={() => {
|
|
71
|
+
setPopoverAnchor(!popoverAnchor);
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
<span className="add_node">+ Add Materialization</span>
|
|
75
|
+
</button>
|
|
76
|
+
<div
|
|
77
|
+
className="fade modal-backdrop in"
|
|
78
|
+
style={{ display: popoverAnchor === false ? 'none' : 'block' }}
|
|
79
|
+
></div>
|
|
80
|
+
<div
|
|
81
|
+
className="centerPopover"
|
|
82
|
+
role="dialog"
|
|
83
|
+
aria-label="client-code"
|
|
84
|
+
style={{
|
|
85
|
+
display: popoverAnchor === false ? 'none' : 'block',
|
|
86
|
+
width: '50%',
|
|
87
|
+
}}
|
|
88
|
+
ref={ref}
|
|
89
|
+
>
|
|
90
|
+
<Formik
|
|
91
|
+
initialValues={{
|
|
92
|
+
node: node?.name,
|
|
93
|
+
engine: defaultEngine,
|
|
94
|
+
config: '{"spark": {"spark.executor.memory": "6g"}}',
|
|
95
|
+
schedule: '@daily',
|
|
96
|
+
}}
|
|
97
|
+
onSubmit={configureMaterialization}
|
|
98
|
+
>
|
|
99
|
+
{function Render({ isSubmitting, status, setFieldValue }) {
|
|
100
|
+
return (
|
|
101
|
+
<Form>
|
|
102
|
+
<h2>Configure Materialization</h2>
|
|
103
|
+
{displayMessageAfterSubmit(status)}
|
|
104
|
+
<span data-testid="edit-partition">
|
|
105
|
+
<label htmlFor="engine">Engine</label>
|
|
106
|
+
<Field as="select" name="engine">
|
|
107
|
+
<>
|
|
108
|
+
{engines?.map(engine => (
|
|
109
|
+
<option value={engine.name + '__' + engine.version}>
|
|
110
|
+
{engine.name} {engine.version}
|
|
111
|
+
</option>
|
|
112
|
+
))}
|
|
113
|
+
<option value=""></option>
|
|
114
|
+
</>
|
|
115
|
+
</Field>
|
|
116
|
+
</span>
|
|
117
|
+
<input
|
|
118
|
+
hidden={true}
|
|
119
|
+
name="node"
|
|
120
|
+
value={node?.name}
|
|
121
|
+
readOnly={true}
|
|
122
|
+
/>
|
|
123
|
+
<br />
|
|
124
|
+
<br />
|
|
125
|
+
<label htmlFor="schedule">Schedule</label>
|
|
126
|
+
<Field
|
|
127
|
+
type="text"
|
|
128
|
+
name="schedule"
|
|
129
|
+
id="schedule"
|
|
130
|
+
placeholder="Cron"
|
|
131
|
+
default="@daily"
|
|
132
|
+
/>
|
|
133
|
+
<br />
|
|
134
|
+
<br />
|
|
135
|
+
<div className="DescriptionInput">
|
|
136
|
+
<ErrorMessage name="description" component="span" />
|
|
137
|
+
<label htmlFor="Config">Config</label>
|
|
138
|
+
<Field
|
|
139
|
+
type="textarea"
|
|
140
|
+
as="textarea"
|
|
141
|
+
name="config"
|
|
142
|
+
id="Config"
|
|
143
|
+
placeholder="Optional engine-specific configuration (i.e., Spark conf etc)"
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
<button
|
|
147
|
+
className="add_node"
|
|
148
|
+
type="submit"
|
|
149
|
+
aria-label="SaveEditColumn"
|
|
150
|
+
aria-hidden="false"
|
|
151
|
+
>
|
|
152
|
+
Save
|
|
153
|
+
</button>
|
|
154
|
+
</Form>
|
|
155
|
+
);
|
|
156
|
+
}}
|
|
157
|
+
</Formik>
|
|
158
|
+
</div>
|
|
159
|
+
</>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
@@ -35,7 +35,6 @@ export default function LinkDimensionPopover({
|
|
|
35
35
|
{ setSubmitting, setStatus },
|
|
36
36
|
) => {
|
|
37
37
|
setSubmitting(false);
|
|
38
|
-
console.log('dimension', dimension, 'columnDimension', columnDimension);
|
|
39
38
|
if (columnDimension?.name && dimension === 'Remove') {
|
|
40
39
|
await unlinkDimension(node, column, columnDimension?.name, setStatus);
|
|
41
40
|
} else {
|
|
@@ -4,6 +4,7 @@ import * as React from 'react';
|
|
|
4
4
|
import EditColumnPopover from './EditColumnPopover';
|
|
5
5
|
import LinkDimensionPopover from './LinkDimensionPopover';
|
|
6
6
|
import { labelize } from '../../../utils/form';
|
|
7
|
+
import PartitionColumnPopover from './PartitionColumnPopover';
|
|
7
8
|
|
|
8
9
|
export default function NodeColumnTab({ node, djClient }) {
|
|
9
10
|
const [attributes, setAttributes] = useState([]);
|
|
@@ -49,6 +50,41 @@ export default function NodeColumnTab({ node, djClient }) {
|
|
|
49
50
|
));
|
|
50
51
|
};
|
|
51
52
|
|
|
53
|
+
const showColumnPartition = col => {
|
|
54
|
+
if (col.partition) {
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
<span
|
|
58
|
+
className="node_type badge node_type__blank"
|
|
59
|
+
key={`col-attr-partition-type`}
|
|
60
|
+
>
|
|
61
|
+
<span
|
|
62
|
+
className="partition_value badge"
|
|
63
|
+
key={`col-attr-partition-type`}
|
|
64
|
+
>
|
|
65
|
+
<b>Type:</b> {col.partition.type_}
|
|
66
|
+
</span>
|
|
67
|
+
<br />
|
|
68
|
+
<span
|
|
69
|
+
className="partition_value badge"
|
|
70
|
+
key={`col-attr-partition-type`}
|
|
71
|
+
>
|
|
72
|
+
<b>Format:</b> <code>{col.partition.format}</code>
|
|
73
|
+
</span>
|
|
74
|
+
<br />
|
|
75
|
+
<span
|
|
76
|
+
className="partition_value badge"
|
|
77
|
+
key={`col-attr-partition-type`}
|
|
78
|
+
>
|
|
79
|
+
<b>Granularity:</b> <code>{col.partition.granularity}</code>
|
|
80
|
+
</span>
|
|
81
|
+
</span>
|
|
82
|
+
</>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return '';
|
|
86
|
+
};
|
|
87
|
+
|
|
52
88
|
const columnList = columns => {
|
|
53
89
|
return columns.map(col => (
|
|
54
90
|
<tr key={col.name}>
|
|
@@ -72,7 +108,9 @@ export default function NodeColumnTab({ node, djClient }) {
|
|
|
72
108
|
</td>
|
|
73
109
|
<td>
|
|
74
110
|
<span
|
|
75
|
-
className=
|
|
111
|
+
className={`node_type__${
|
|
112
|
+
node.type === 'cube' ? col.type : 'transform'
|
|
113
|
+
} badge node_type`}
|
|
76
114
|
role="columnheader"
|
|
77
115
|
aria-label="ColumnType"
|
|
78
116
|
aria-hidden="false"
|
|
@@ -80,31 +118,52 @@ export default function NodeColumnTab({ node, djClient }) {
|
|
|
80
118
|
{col.type}
|
|
81
119
|
</span>
|
|
82
120
|
</td>
|
|
121
|
+
{node.type !== 'cube' ? (
|
|
122
|
+
<td>
|
|
123
|
+
{col.dimension !== undefined && col.dimension !== null ? (
|
|
124
|
+
<>
|
|
125
|
+
<a href={`/nodes/${col.dimension.name}`}>
|
|
126
|
+
{col.dimension.name}
|
|
127
|
+
</a>
|
|
128
|
+
<ClientCodePopover code={col.clientCode} />
|
|
129
|
+
</>
|
|
130
|
+
) : (
|
|
131
|
+
''
|
|
132
|
+
)}{' '}
|
|
133
|
+
<LinkDimensionPopover
|
|
134
|
+
column={col}
|
|
135
|
+
node={node}
|
|
136
|
+
options={dimensions}
|
|
137
|
+
onSubmit={async () => {
|
|
138
|
+
const res = await djClient.node(node.name);
|
|
139
|
+
setColumns(res.columns);
|
|
140
|
+
}}
|
|
141
|
+
/>
|
|
142
|
+
</td>
|
|
143
|
+
) : (
|
|
144
|
+
''
|
|
145
|
+
)}
|
|
146
|
+
{node.type !== 'cube' ? (
|
|
147
|
+
<td>
|
|
148
|
+
{showColumnAttributes(col)}
|
|
149
|
+
<EditColumnPopover
|
|
150
|
+
column={col}
|
|
151
|
+
node={node}
|
|
152
|
+
options={attributes}
|
|
153
|
+
onSubmit={async () => {
|
|
154
|
+
const res = await djClient.node(node.name);
|
|
155
|
+
setColumns(res.columns);
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
158
|
+
</td>
|
|
159
|
+
) : (
|
|
160
|
+
''
|
|
161
|
+
)}
|
|
83
162
|
<td>
|
|
84
|
-
{col
|
|
85
|
-
|
|
86
|
-
<a href={`/nodes/${col.dimension.name}`}>{col.dimension.name}</a>
|
|
87
|
-
<ClientCodePopover code={col.clientCode} />
|
|
88
|
-
</>
|
|
89
|
-
) : (
|
|
90
|
-
''
|
|
91
|
-
)}{' '}
|
|
92
|
-
<LinkDimensionPopover
|
|
93
|
-
column={col}
|
|
94
|
-
node={node}
|
|
95
|
-
options={dimensions}
|
|
96
|
-
onSubmit={async () => {
|
|
97
|
-
const res = await djClient.node(node.name);
|
|
98
|
-
setColumns(res.columns);
|
|
99
|
-
}}
|
|
100
|
-
/>
|
|
101
|
-
</td>
|
|
102
|
-
<td>
|
|
103
|
-
{showColumnAttributes(col)}
|
|
104
|
-
<EditColumnPopover
|
|
163
|
+
{showColumnPartition(col)}
|
|
164
|
+
<PartitionColumnPopover
|
|
105
165
|
column={col}
|
|
106
166
|
node={node}
|
|
107
|
-
options={attributes}
|
|
108
167
|
onSubmit={async () => {
|
|
109
168
|
const res = await djClient.node(node.name);
|
|
110
169
|
setColumns(res.columns);
|
|
@@ -123,8 +182,15 @@ export default function NodeColumnTab({ node, djClient }) {
|
|
|
123
182
|
<th className="text-start">Column</th>
|
|
124
183
|
<th>Display Name</th>
|
|
125
184
|
<th>Type</th>
|
|
126
|
-
|
|
127
|
-
|
|
185
|
+
{node?.type !== 'cube' ? (
|
|
186
|
+
<>
|
|
187
|
+
<th>Linked Dimension</th>
|
|
188
|
+
<th>Attributes</th>
|
|
189
|
+
</>
|
|
190
|
+
) : (
|
|
191
|
+
''
|
|
192
|
+
)}
|
|
193
|
+
<th>Partition</th>
|
|
128
194
|
</tr>
|
|
129
195
|
</thead>
|
|
130
196
|
<tbody>{columnList(columns)}</tbody>
|
|
@@ -66,6 +66,28 @@ export default function NodeInfoTab({ node }) {
|
|
|
66
66
|
<></>
|
|
67
67
|
);
|
|
68
68
|
|
|
69
|
+
const displayCubeElement = cubeElem => {
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
className="button-3 cube-element"
|
|
73
|
+
key={cubeElem.name}
|
|
74
|
+
role="cell"
|
|
75
|
+
aria-label="CubeElement"
|
|
76
|
+
aria-hidden="false"
|
|
77
|
+
>
|
|
78
|
+
<a href={`/nodes/${cubeElem.node_name}`}>{cubeElem.display_name}</a>
|
|
79
|
+
<span
|
|
80
|
+
className={`badge node_type__${
|
|
81
|
+
cubeElem.type === 'metric' ? cubeElem.type : 'dimension'
|
|
82
|
+
}`}
|
|
83
|
+
style={{ fontSize: '100%', textTransform: 'capitalize' }}
|
|
84
|
+
>
|
|
85
|
+
{cubeElem.type === 'metric' ? cubeElem.type : 'dimension'}
|
|
86
|
+
</span>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
69
91
|
const cubeElementsDiv = node?.cube_elements ? (
|
|
70
92
|
<div className="list-group-item d-flex">
|
|
71
93
|
<div className="d-flex gap-2 w-100 justify-content-between py-3">
|
|
@@ -76,28 +98,12 @@ export default function NodeInfoTab({ node }) {
|
|
|
76
98
|
>
|
|
77
99
|
<h6 className="mb-0 w-100">Cube Elements</h6>
|
|
78
100
|
<div className={`list-group-item`}>
|
|
79
|
-
{node.cube_elements.map(cubeElem =>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
aria-hidden="false"
|
|
86
|
-
>
|
|
87
|
-
<a href={`/nodes/${cubeElem.node_name}`}>
|
|
88
|
-
{cubeElem.type === 'metric'
|
|
89
|
-
? cubeElem.node_name
|
|
90
|
-
: cubeElem.name}
|
|
91
|
-
</a>
|
|
92
|
-
<span
|
|
93
|
-
className={`badge node_type__${
|
|
94
|
-
cubeElem.type === 'metric' ? cubeElem.type : 'dimension'
|
|
95
|
-
}`}
|
|
96
|
-
>
|
|
97
|
-
{cubeElem.type === 'metric' ? cubeElem.type : 'dimension'}
|
|
98
|
-
</span>
|
|
99
|
-
</div>
|
|
100
|
-
))}
|
|
101
|
+
{node.cube_elements.map(cubeElem =>
|
|
102
|
+
cubeElem.type === 'metric' ? displayCubeElement(cubeElem) : '',
|
|
103
|
+
)}
|
|
104
|
+
{node.cube_elements.map(cubeElem =>
|
|
105
|
+
cubeElem.type !== 'metric' ? displayCubeElement(cubeElem) : '',
|
|
106
|
+
)}
|
|
101
107
|
</div>
|
|
102
108
|
</div>
|
|
103
109
|
</div>
|