datajunction-ui 0.0.1-rc.24 → 0.0.1-rc.26
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/.env +1 -0
- package/package.json +3 -2
- package/src/app/components/Tab.jsx +0 -1
- package/src/app/constants.js +2 -2
- package/src/app/icons/LoadingIcon.jsx +14 -0
- package/src/app/index.tsx +11 -1
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +28 -2
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +44 -9
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +1 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +0 -50
- package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +2 -0
- package/src/app/pages/AddEditNodePage/index.jsx +60 -6
- 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 +34 -2
- package/src/app/pages/LoginPage/index.jsx +9 -82
- package/src/app/pages/NamespacePage/index.jsx +5 -0
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +166 -0
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +161 -0
- package/src/app/pages/NodePage/EditColumnPopover.jsx +1 -1
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +0 -1
- package/src/app/pages/NodePage/NodeColumnTab.jsx +102 -25
- package/src/app/pages/NodePage/NodeInfoTab.jsx +33 -23
- 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 +47 -17
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +101 -100
- package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +1 -0
- package/src/app/pages/Root/index.tsx +1 -1
- 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/services/DJService.js +166 -1
- package/src/app/services/__tests__/DJService.test.jsx +196 -1
- package/src/mocks/mockNodes.jsx +64 -31
- package/src/styles/dag.css +4 -0
- package/src/styles/index.css +89 -1
- package/src/styles/loading.css +34 -0
- package/src/styles/login.css +17 -3
- package/src/utils/form.jsx +2 -2
|
@@ -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}>
|
|
@@ -62,39 +98,72 @@ export default function NodeColumnTab({ node, djClient }) {
|
|
|
62
98
|
</td>
|
|
63
99
|
<td>
|
|
64
100
|
<span
|
|
65
|
-
className="
|
|
101
|
+
className=""
|
|
66
102
|
role="columnheader"
|
|
67
|
-
aria-label="
|
|
103
|
+
aria-label="ColumnDisplayName"
|
|
68
104
|
aria-hidden="false"
|
|
69
105
|
>
|
|
70
|
-
{col.
|
|
106
|
+
{col.display_name}
|
|
71
107
|
</span>
|
|
72
108
|
</td>
|
|
73
109
|
<td>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
node={node}
|
|
85
|
-
options={dimensions}
|
|
86
|
-
onSubmit={async () => {
|
|
87
|
-
const res = await djClient.node(node.name);
|
|
88
|
-
setColumns(res.columns);
|
|
89
|
-
}}
|
|
90
|
-
/>
|
|
110
|
+
<span
|
|
111
|
+
className={`node_type__${
|
|
112
|
+
node.type === 'cube' ? col.type : 'transform'
|
|
113
|
+
} badge node_type`}
|
|
114
|
+
role="columnheader"
|
|
115
|
+
aria-label="ColumnType"
|
|
116
|
+
aria-hidden="false"
|
|
117
|
+
>
|
|
118
|
+
{col.type}
|
|
119
|
+
</span>
|
|
91
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
|
+
)}
|
|
92
162
|
<td>
|
|
93
|
-
{
|
|
94
|
-
<
|
|
163
|
+
{showColumnPartition(col)}
|
|
164
|
+
<PartitionColumnPopover
|
|
95
165
|
column={col}
|
|
96
166
|
node={node}
|
|
97
|
-
options={attributes}
|
|
98
167
|
onSubmit={async () => {
|
|
99
168
|
const res = await djClient.node(node.name);
|
|
100
169
|
setColumns(res.columns);
|
|
@@ -111,9 +180,17 @@ export default function NodeColumnTab({ node, djClient }) {
|
|
|
111
180
|
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
112
181
|
<tr>
|
|
113
182
|
<th className="text-start">Column</th>
|
|
183
|
+
<th>Display Name</th>
|
|
114
184
|
<th>Type</th>
|
|
115
|
-
|
|
116
|
-
|
|
185
|
+
{node?.type !== 'cube' ? (
|
|
186
|
+
<>
|
|
187
|
+
<th>Linked Dimension</th>
|
|
188
|
+
<th>Attributes</th>
|
|
189
|
+
</>
|
|
190
|
+
) : (
|
|
191
|
+
''
|
|
192
|
+
)}
|
|
193
|
+
<th>Partition</th>
|
|
117
194
|
</tr>
|
|
118
195
|
</thead>
|
|
119
196
|
<tbody>{columnList(columns)}</tbody>
|
|
@@ -13,7 +13,11 @@ foundation.hljs['padding'] = '2rem';
|
|
|
13
13
|
export default function NodeInfoTab({ node }) {
|
|
14
14
|
const [compiledSQL, setCompiledSQL] = useState('');
|
|
15
15
|
const [checked, setChecked] = useState(false);
|
|
16
|
-
const nodeTags = node?.tags.map(tag =>
|
|
16
|
+
const nodeTags = node?.tags.map(tag => (
|
|
17
|
+
<div className={'badge tag_value'}>
|
|
18
|
+
<a href={`/tags/${tag.name}`}>{tag.display_name}</a>
|
|
19
|
+
</div>
|
|
20
|
+
));
|
|
17
21
|
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
18
22
|
useEffect(() => {
|
|
19
23
|
const fetchData = async () => {
|
|
@@ -62,6 +66,28 @@ export default function NodeInfoTab({ node }) {
|
|
|
62
66
|
<></>
|
|
63
67
|
);
|
|
64
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
|
+
|
|
65
91
|
const cubeElementsDiv = node?.cube_elements ? (
|
|
66
92
|
<div className="list-group-item d-flex">
|
|
67
93
|
<div className="d-flex gap-2 w-100 justify-content-between py-3">
|
|
@@ -72,28 +98,12 @@ export default function NodeInfoTab({ node }) {
|
|
|
72
98
|
>
|
|
73
99
|
<h6 className="mb-0 w-100">Cube Elements</h6>
|
|
74
100
|
<div className={`list-group-item`}>
|
|
75
|
-
{node.cube_elements.map(cubeElem =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
aria-hidden="false"
|
|
82
|
-
>
|
|
83
|
-
<a href={`/nodes/${cubeElem.node_name}`}>
|
|
84
|
-
{cubeElem.type === 'metric'
|
|
85
|
-
? cubeElem.node_name
|
|
86
|
-
: cubeElem.name}
|
|
87
|
-
</a>
|
|
88
|
-
<span
|
|
89
|
-
className={`badge node_type__${
|
|
90
|
-
cubeElem.type === 'metric' ? cubeElem.type : 'dimension'
|
|
91
|
-
}`}
|
|
92
|
-
>
|
|
93
|
-
{cubeElem.type === 'metric' ? cubeElem.type : 'dimension'}
|
|
94
|
-
</span>
|
|
95
|
-
</div>
|
|
96
|
-
))}
|
|
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
|
+
)}
|
|
97
107
|
</div>
|
|
98
108
|
</div>
|
|
99
109
|
</div>
|