datajunction-ui 0.0.1-a35.dev0 → 0.0.1-a37
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/forms/Action.jsx +8 -0
- package/src/app/components/forms/NodeNameField.jsx +64 -0
- package/src/app/components/forms/NodeTagsInput.jsx +61 -0
- package/src/app/index.tsx +18 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +1 -1
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +2 -5
- package/src/app/pages/AddEditNodePage/index.jsx +16 -7
- package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +154 -0
- package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
- package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +79 -0
- package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +405 -0
- package/src/app/pages/CubeBuilderPage/index.jsx +254 -0
- package/src/app/pages/NamespacePage/index.jsx +5 -0
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +13 -6
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +47 -24
- package/src/app/pages/NodePage/NodeInfoTab.jsx +6 -1
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +44 -32
- package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +0 -1
- package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +84 -0
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +78 -174
- package/src/app/services/DJService.js +66 -12
- package/src/app/services/__tests__/DJService.test.jsx +72 -7
- package/src/styles/index.css +4 -0
- package/src/styles/node-creation.scss +12 -0
package/package.json
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ErrorMessage, Field } from 'formik';
|
|
2
|
+
import { FormikSelect } from '../../pages/AddEditNodePage/FormikSelect';
|
|
3
|
+
import { FullNameField } from '../../pages/AddEditNodePage/FullNameField';
|
|
4
|
+
import React, { useContext, useEffect, useState } from 'react';
|
|
5
|
+
import DJClientContext from '../../providers/djclient';
|
|
6
|
+
import { useParams } from 'react-router-dom';
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* This component creates the namespace selector, display name input, and
|
|
10
|
+
* derived name fields in a form. It can be reused any time we need to create
|
|
11
|
+
* a new node.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export default function NodeNameField() {
|
|
15
|
+
const [namespaces, setNamespaces] = useState([]);
|
|
16
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
17
|
+
let { initialNamespace } = useParams();
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const fetchData = async () => {
|
|
21
|
+
const namespaces = await djClient.namespaces();
|
|
22
|
+
setNamespaces(
|
|
23
|
+
namespaces.map(m => ({
|
|
24
|
+
value: m['namespace'],
|
|
25
|
+
label: m['namespace'],
|
|
26
|
+
})),
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
fetchData().catch(console.error);
|
|
30
|
+
}, [djClient, djClient.metrics]);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<div className="NamespaceInput">
|
|
35
|
+
<ErrorMessage name="namespace" component="span" />
|
|
36
|
+
<label htmlFor="react-select-3-input">Namespace *</label>
|
|
37
|
+
<FormikSelect
|
|
38
|
+
selectOptions={namespaces}
|
|
39
|
+
formikFieldName="namespace"
|
|
40
|
+
placeholder="Choose Namespace"
|
|
41
|
+
defaultValue={{
|
|
42
|
+
value: initialNamespace,
|
|
43
|
+
label: initialNamespace,
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
<div className="DisplayNameInput NodeCreationInput">
|
|
48
|
+
<ErrorMessage name="display_name" component="span" />
|
|
49
|
+
<label htmlFor="displayName">Display Name *</label>
|
|
50
|
+
<Field
|
|
51
|
+
type="text"
|
|
52
|
+
name="display_name"
|
|
53
|
+
id="displayName"
|
|
54
|
+
placeholder="Human readable display name"
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
<div className="FullNameInput NodeCreationInput">
|
|
58
|
+
<ErrorMessage name="name" component="span" />
|
|
59
|
+
<label htmlFor="FullName">Full Name</label>
|
|
60
|
+
<FullNameField type="text" name="name" />
|
|
61
|
+
</div>
|
|
62
|
+
</>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ErrorMessage } from 'formik';
|
|
2
|
+
import { FormikSelect } from '../../pages/AddEditNodePage/FormikSelect';
|
|
3
|
+
import { Action } from './Action';
|
|
4
|
+
import { useContext, useEffect, useState } from 'react';
|
|
5
|
+
import DJClientContext from '../../providers/djclient';
|
|
6
|
+
|
|
7
|
+
export default function NodeTagsInput({ action, node }) {
|
|
8
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
9
|
+
const [tags, setTags] = useState([]);
|
|
10
|
+
|
|
11
|
+
// Get list of tags
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const fetchData = async () => {
|
|
14
|
+
const tags = await djClient.listTags();
|
|
15
|
+
setTags(
|
|
16
|
+
tags.map(tag => ({
|
|
17
|
+
value: tag.name,
|
|
18
|
+
label: tag.display_name,
|
|
19
|
+
})),
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
fetchData().catch(console.error);
|
|
23
|
+
}, [djClient, djClient.listTags]);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
className="TagsInput"
|
|
28
|
+
style={{ width: '25%', margin: '1rem 0 1rem 1.2rem' }}
|
|
29
|
+
>
|
|
30
|
+
<ErrorMessage name="tags" component="span" />
|
|
31
|
+
<label htmlFor="react-select-3-input">Tags</label>
|
|
32
|
+
<span data-testid="select-tags">
|
|
33
|
+
{action === Action.Edit && node?.tags?.length >= 0 ? (
|
|
34
|
+
<FormikSelect
|
|
35
|
+
className=""
|
|
36
|
+
isMulti={true}
|
|
37
|
+
selectOptions={tags}
|
|
38
|
+
formikFieldName="tags"
|
|
39
|
+
placeholder="Choose Tags"
|
|
40
|
+
defaultValue={node?.tags?.map(t => {
|
|
41
|
+
return { value: t.name, label: t.display_name };
|
|
42
|
+
})}
|
|
43
|
+
/>
|
|
44
|
+
) : (
|
|
45
|
+
''
|
|
46
|
+
)}
|
|
47
|
+
{action === Action.Add ? (
|
|
48
|
+
<FormikSelect
|
|
49
|
+
className=""
|
|
50
|
+
isMulti={true}
|
|
51
|
+
selectOptions={tags}
|
|
52
|
+
formikFieldName="tags"
|
|
53
|
+
placeholder="Choose Tags"
|
|
54
|
+
/>
|
|
55
|
+
) : (
|
|
56
|
+
''
|
|
57
|
+
)}
|
|
58
|
+
</span>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
package/src/app/index.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
|
|
10
10
|
import { NamespacePage } from './pages/NamespacePage/Loadable';
|
|
11
11
|
import { NodePage } from './pages/NodePage/Loadable';
|
|
12
12
|
import { SQLBuilderPage } from './pages/SQLBuilderPage/Loadable';
|
|
13
|
+
import { CubeBuilderPage } from './pages/CubeBuilderPage/Loadable';
|
|
13
14
|
import { TagPage } from './pages/TagPage/Loadable';
|
|
14
15
|
import { AddEditNodePage } from './pages/AddEditNodePage/Loadable';
|
|
15
16
|
import { AddEditTagPage } from './pages/AddEditTagPage/Loadable';
|
|
@@ -52,6 +53,11 @@ export function App() {
|
|
|
52
53
|
key="edit"
|
|
53
54
|
element={<AddEditNodePage />}
|
|
54
55
|
/>
|
|
56
|
+
<Route
|
|
57
|
+
path=":name/edit-cube"
|
|
58
|
+
key="edit-cube"
|
|
59
|
+
element={<CubeBuilderPage />}
|
|
60
|
+
/>
|
|
55
61
|
</Route>
|
|
56
62
|
|
|
57
63
|
<Route path="/" element={<NamespacePage />} key="index" />
|
|
@@ -72,6 +78,18 @@ export function App() {
|
|
|
72
78
|
key="register"
|
|
73
79
|
element={<RegisterTablePage />}
|
|
74
80
|
></Route>
|
|
81
|
+
<Route path="/create/cube">
|
|
82
|
+
<Route
|
|
83
|
+
path=":initialNamespace"
|
|
84
|
+
key="create"
|
|
85
|
+
element={<CubeBuilderPage />}
|
|
86
|
+
/>
|
|
87
|
+
<Route
|
|
88
|
+
path=""
|
|
89
|
+
key="create"
|
|
90
|
+
element={<CubeBuilderPage />}
|
|
91
|
+
/>
|
|
92
|
+
</Route>
|
|
75
93
|
<Route path="create/:nodeType">
|
|
76
94
|
<Route
|
|
77
95
|
path=":initialNamespace"
|
|
@@ -93,7 +93,7 @@ describe('AddEditNodePage submission failed', () => {
|
|
|
93
93
|
expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalled();
|
|
94
94
|
expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalledWith(
|
|
95
95
|
'default.num_repair_orders',
|
|
96
|
-
['purpose'],
|
|
96
|
+
[{ display_name: 'Purpose', name: 'purpose' }],
|
|
97
97
|
);
|
|
98
98
|
expect(mockDjClient.DataJunctionAPI.tagsNode).toReturnWith({
|
|
99
99
|
json: { message: 'Some tags were not found' },
|
|
@@ -113,22 +113,19 @@ describe('AddEditNodePage submission succeeded', () => {
|
|
|
113
113
|
'Number of repair orders!!!',
|
|
114
114
|
'SELECT count(repair_order_id) default_DOT_num_repair_orders FROM default.repair_orders',
|
|
115
115
|
'published',
|
|
116
|
-
|
|
116
|
+
null,
|
|
117
117
|
'neutral',
|
|
118
118
|
'unitless',
|
|
119
119
|
);
|
|
120
120
|
expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalledTimes(1);
|
|
121
121
|
expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalledWith(
|
|
122
122
|
'default.num_repair_orders',
|
|
123
|
-
['purpose'],
|
|
123
|
+
[{ display_name: 'Purpose', name: 'purpose' }],
|
|
124
124
|
);
|
|
125
125
|
|
|
126
126
|
expect(mockDjClient.DataJunctionAPI.listMetricMetadata).toBeCalledTimes(
|
|
127
127
|
1,
|
|
128
128
|
);
|
|
129
|
-
expect(
|
|
130
|
-
await screen.getByDisplayValue('repair_order_id, country'),
|
|
131
|
-
).toBeInTheDocument();
|
|
132
129
|
expect(
|
|
133
130
|
await screen.getByText(/Successfully updated metric node/),
|
|
134
131
|
).toBeInTheDocument();
|
|
@@ -10,7 +10,7 @@ import { useContext, useEffect, useState } from 'react';
|
|
|
10
10
|
import DJClientContext from '../../providers/djclient';
|
|
11
11
|
import 'styles/node-creation.scss';
|
|
12
12
|
import AlertIcon from '../../icons/AlertIcon';
|
|
13
|
-
import { useParams } from 'react-router-dom';
|
|
13
|
+
import { useParams, useNavigate } from 'react-router-dom';
|
|
14
14
|
import { FullNameField } from './FullNameField';
|
|
15
15
|
import { FormikSelect } from './FormikSelect';
|
|
16
16
|
import { NodeQueryField } from './NodeQueryField';
|
|
@@ -27,6 +27,7 @@ class Action {
|
|
|
27
27
|
|
|
28
28
|
export function AddEditNodePage() {
|
|
29
29
|
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
30
|
+
const navigate = useNavigate();
|
|
30
31
|
|
|
31
32
|
let { nodeType, initialNamespace, name } = useParams();
|
|
32
33
|
const action = name !== undefined ? Action.Edit : Action.Add;
|
|
@@ -201,13 +202,18 @@ export function AddEditNodePage() {
|
|
|
201
202
|
'mode',
|
|
202
203
|
'tags',
|
|
203
204
|
];
|
|
205
|
+
const primaryKey = data.columns
|
|
206
|
+
.filter(
|
|
207
|
+
col =>
|
|
208
|
+
col.attributes &&
|
|
209
|
+
col.attributes.filter(
|
|
210
|
+
attr => attr.attribute_type.name === 'primary_key',
|
|
211
|
+
).length > 0,
|
|
212
|
+
)
|
|
213
|
+
.map(col => col.name);
|
|
204
214
|
fields.forEach(field => {
|
|
205
|
-
if (
|
|
206
|
-
field
|
|
207
|
-
data[field] !== undefined &&
|
|
208
|
-
Array.isArray(data[field])
|
|
209
|
-
) {
|
|
210
|
-
data[field] = data[field].join(', ');
|
|
215
|
+
if (field === 'primary_key') {
|
|
216
|
+
setFieldValue(field, primaryKey.join(', '));
|
|
211
217
|
} else {
|
|
212
218
|
setFieldValue(field, data[field] || '', false);
|
|
213
219
|
}
|
|
@@ -363,6 +369,9 @@ export function AddEditNodePage() {
|
|
|
363
369
|
// Check if node type can be edited
|
|
364
370
|
if (!nodeCanBeEdited(data.type)) {
|
|
365
371
|
setNode(null);
|
|
372
|
+
if (data.type === 'cube') {
|
|
373
|
+
navigate(`/nodes/${data.name}/edit-cube`);
|
|
374
|
+
}
|
|
366
375
|
setMessage(
|
|
367
376
|
`Node ${name} is of type ${data.type} and cannot be edited`,
|
|
368
377
|
);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A select component for picking dimensions
|
|
3
|
+
*/
|
|
4
|
+
import { useField, useFormikContext } from 'formik';
|
|
5
|
+
import Select from 'react-select';
|
|
6
|
+
import React, { useContext, useEffect, useState } from 'react';
|
|
7
|
+
import DJClientContext from '../../providers/djclient';
|
|
8
|
+
import { labelize } from '../../../utils/form';
|
|
9
|
+
|
|
10
|
+
export const DimensionsSelect = ({ cube }) => {
|
|
11
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
12
|
+
const { values, setFieldValue } = useFormikContext();
|
|
13
|
+
|
|
14
|
+
// eslint-disable-next-line no-unused-vars
|
|
15
|
+
const [field, _, helpers] = useField('dimensions');
|
|
16
|
+
const { setValue } = helpers;
|
|
17
|
+
|
|
18
|
+
// All common dimensions for the selected metrics, grouped by the dimension node and path
|
|
19
|
+
const [allDimensionsOptions, setAllDimensionsOptions] = useState([]);
|
|
20
|
+
|
|
21
|
+
// The selected dimensions, also grouped by dimension node and path
|
|
22
|
+
const [selectedDimensionsByGroup, setSelectedDimensionsByGroup] = useState(
|
|
23
|
+
{},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// The existing cube node's dimensions, if editing a cube
|
|
27
|
+
const [defaultDimensions, setDefaultDimensions] = useState([]);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const fetchData = async () => {
|
|
31
|
+
let cubeDimensions = undefined;
|
|
32
|
+
if (cube) {
|
|
33
|
+
cubeDimensions = cube?.cube_elements
|
|
34
|
+
.filter(element => element.type === 'dimension')
|
|
35
|
+
.map(cubeDim => {
|
|
36
|
+
return {
|
|
37
|
+
value: cubeDim.node_name + '.' + cubeDim.name,
|
|
38
|
+
label:
|
|
39
|
+
labelize(cubeDim.name) +
|
|
40
|
+
(cubeDim.is_primary_key ? ' (PK)' : ''),
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
setDefaultDimensions(cubeDimensions);
|
|
44
|
+
setValue(cubeDimensions.map(m => m.value));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (values.metrics && values.metrics.length > 0) {
|
|
48
|
+
// Populate the common dimensions list based on the selected metrics
|
|
49
|
+
const commonDimensions = await djClient.commonDimensions(
|
|
50
|
+
values.metrics,
|
|
51
|
+
);
|
|
52
|
+
const grouped = Object.entries(
|
|
53
|
+
commonDimensions.reduce((group, dimension) => {
|
|
54
|
+
group[dimension.node_name + dimension.path] =
|
|
55
|
+
group[dimension.node_name + dimension.path] ?? [];
|
|
56
|
+
group[dimension.node_name + dimension.path].push(dimension);
|
|
57
|
+
return group;
|
|
58
|
+
}, {}),
|
|
59
|
+
);
|
|
60
|
+
setAllDimensionsOptions(grouped);
|
|
61
|
+
|
|
62
|
+
// Set the selected cube dimensions if an existing cube is being edited
|
|
63
|
+
if (cube) {
|
|
64
|
+
const currentSelectedDimensionsByGroup = selectedDimensionsByGroup;
|
|
65
|
+
grouped.forEach(grouping => {
|
|
66
|
+
const dimensionsInGroup = grouping[1];
|
|
67
|
+
currentSelectedDimensionsByGroup[dimensionsInGroup[0].node_name] =
|
|
68
|
+
getValue(
|
|
69
|
+
cubeDimensions.filter(
|
|
70
|
+
dim =>
|
|
71
|
+
dimensionsInGroup.filter(x => {
|
|
72
|
+
return dim.value === x.name;
|
|
73
|
+
}).length > 0,
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
setSelectedDimensionsByGroup(currentSelectedDimensionsByGroup);
|
|
77
|
+
setValue(Object.values(currentSelectedDimensionsByGroup).flat(2));
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
setAllDimensionsOptions([]);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
fetchData().catch(console.error);
|
|
85
|
+
}, [djClient, setFieldValue, setValue, values.metrics, cube]);
|
|
86
|
+
|
|
87
|
+
// Retrieves the selected values as a list (since it is a multi-select)
|
|
88
|
+
const getValue = options => {
|
|
89
|
+
if (options) {
|
|
90
|
+
return options.map(option => option.value);
|
|
91
|
+
} else {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Builds the block of dimensions selectors, grouped by node name + path
|
|
97
|
+
return allDimensionsOptions.map(grouping => {
|
|
98
|
+
const dimensionsInGroup = grouping[1];
|
|
99
|
+
const groupHeader = (
|
|
100
|
+
<h5
|
|
101
|
+
style={{
|
|
102
|
+
fontWeight: 'normal',
|
|
103
|
+
marginBottom: '5px',
|
|
104
|
+
marginTop: '15px',
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
<a href={`/nodes/${dimensionsInGroup[0].node_name}`}>
|
|
108
|
+
<b>{dimensionsInGroup[0].node_display_name}</b>
|
|
109
|
+
</a>{' '}
|
|
110
|
+
via{' '}
|
|
111
|
+
<span className="HighlightPath">
|
|
112
|
+
{dimensionsInGroup[0].path.join(' → ')}
|
|
113
|
+
</span>
|
|
114
|
+
</h5>
|
|
115
|
+
);
|
|
116
|
+
const dimensionGroupOptions = dimensionsInGroup.map(dim => {
|
|
117
|
+
return {
|
|
118
|
+
value: dim.name,
|
|
119
|
+
label:
|
|
120
|
+
labelize(dim.name.split('.').slice(-1)[0]) +
|
|
121
|
+
(dim.is_primary_key ? ' (PK)' : ''),
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
//
|
|
125
|
+
const cubeDimensions = defaultDimensions.filter(
|
|
126
|
+
dim =>
|
|
127
|
+
dimensionGroupOptions.filter(x => {
|
|
128
|
+
return dim.value === x.value;
|
|
129
|
+
}).length > 0,
|
|
130
|
+
);
|
|
131
|
+
return (
|
|
132
|
+
<>
|
|
133
|
+
{groupHeader}
|
|
134
|
+
<span data-testid={'dimensions-' + dimensionsInGroup[0].node_name}>
|
|
135
|
+
<Select
|
|
136
|
+
className=""
|
|
137
|
+
name={'dimensions-' + dimensionsInGroup[0].node_name}
|
|
138
|
+
defaultValue={cubeDimensions}
|
|
139
|
+
options={dimensionGroupOptions}
|
|
140
|
+
isMulti
|
|
141
|
+
isClearable
|
|
142
|
+
closeMenuOnSelect={false}
|
|
143
|
+
onChange={selected => {
|
|
144
|
+
selectedDimensionsByGroup[dimensionsInGroup[0].node_name] =
|
|
145
|
+
getValue(selected);
|
|
146
|
+
setSelectedDimensionsByGroup(selectedDimensionsByGroup);
|
|
147
|
+
setValue(Object.values(selectedDimensionsByGroup).flat(2));
|
|
148
|
+
}}
|
|
149
|
+
/>
|
|
150
|
+
</span>
|
|
151
|
+
</>
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asynchronously loads the component for the Node page
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { lazyLoad } from '../../../utils/loadable';
|
|
7
|
+
|
|
8
|
+
export const CubeBuilderPage = props => {
|
|
9
|
+
return lazyLoad(
|
|
10
|
+
() => import('./index'),
|
|
11
|
+
module => module.CubeBuilderPage,
|
|
12
|
+
{
|
|
13
|
+
fallback: <div></div>,
|
|
14
|
+
},
|
|
15
|
+
)(props);
|
|
16
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A select component for picking metrics.
|
|
3
|
+
*/
|
|
4
|
+
import { useField, useFormikContext } from 'formik';
|
|
5
|
+
import Select from 'react-select';
|
|
6
|
+
import React, { useContext, useEffect, useState } from 'react';
|
|
7
|
+
import DJClientContext from '../../providers/djclient';
|
|
8
|
+
|
|
9
|
+
export const MetricsSelect = ({ cube }) => {
|
|
10
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
11
|
+
const { values } = useFormikContext();
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line no-unused-vars
|
|
14
|
+
const [field, _, helpers] = useField('metrics');
|
|
15
|
+
const { setValue } = helpers;
|
|
16
|
+
|
|
17
|
+
// All metrics options
|
|
18
|
+
const [metrics, setMetrics] = useState([]);
|
|
19
|
+
|
|
20
|
+
// The existing cube's metrics, if editing a cube
|
|
21
|
+
const [defaultMetrics, setDefaultMetrics] = useState([]);
|
|
22
|
+
|
|
23
|
+
// Get metrics
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const fetchData = async () => {
|
|
26
|
+
if (cube && cube !== []) {
|
|
27
|
+
const cubeMetrics = cube?.cube_elements
|
|
28
|
+
.filter(element => element.type === 'metric')
|
|
29
|
+
.map(metric => {
|
|
30
|
+
return {
|
|
31
|
+
value: metric.node_name,
|
|
32
|
+
label: metric.node_name,
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
setDefaultMetrics(cubeMetrics);
|
|
36
|
+
await setValue(cubeMetrics.map(m => m.value));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const metrics = await djClient.metrics();
|
|
40
|
+
setMetrics(metrics.map(m => ({ value: m, label: m })));
|
|
41
|
+
console.log('metrics', metrics);
|
|
42
|
+
};
|
|
43
|
+
fetchData().catch(console.error);
|
|
44
|
+
}, [djClient, djClient.metrics, cube]);
|
|
45
|
+
|
|
46
|
+
const getValue = options => {
|
|
47
|
+
if (options) {
|
|
48
|
+
return options.map(option => option.value);
|
|
49
|
+
} else {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const render = () => {
|
|
55
|
+
if (
|
|
56
|
+
metrics.length > 0 ||
|
|
57
|
+
(cube !== undefined && defaultMetrics.length > 0 && metrics.length > 0)
|
|
58
|
+
) {
|
|
59
|
+
return (
|
|
60
|
+
<Select
|
|
61
|
+
defaultValue={defaultMetrics}
|
|
62
|
+
options={metrics}
|
|
63
|
+
name="metrics"
|
|
64
|
+
placeholder={`${metrics.length} Available Metrics`}
|
|
65
|
+
onBlur={field.onBlur}
|
|
66
|
+
onChange={selected => {
|
|
67
|
+
setValue(getValue(selected));
|
|
68
|
+
}}
|
|
69
|
+
noOptionsMessage={() => 'No metrics found.'}
|
|
70
|
+
isMulti
|
|
71
|
+
isClearable
|
|
72
|
+
closeMenuOnSelect={false}
|
|
73
|
+
isDisabled={!!(values.metrics.length && values.dimensions.length)}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
return render();
|
|
79
|
+
};
|