@eeacms/volto-tableau 7.0.5 → 7.2.0
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/CHANGELOG.md +38 -17
- package/jest.setup.js +29 -2
- package/locales/de/LC_MESSAGES/volto.po +55 -0
- package/locales/en/LC_MESSAGES/volto.po +55 -0
- package/locales/it/LC_MESSAGES/volto.po +55 -0
- package/locales/ro/LC_MESSAGES/volto.po +55 -0
- package/locales/volto.pot +56 -1
- package/package.json +1 -1
- package/src/Blocks/EmbedTableauVisualization/Edit.jsx +1 -0
- package/src/Blocks/EmbedTableauVisualization/View.jsx +140 -43
- package/src/Blocks/EmbedTableauVisualization/__snapshots__/Edit.test.jsx.snap +90 -10
- package/src/Blocks/EmbedTableauVisualization/schema.js +44 -2
- package/src/Tableau/Tableau.jsx +114 -29
- package/src/Tableau/helpers.js +129 -1
- package/src/Views/VisualizationView.jsx +31 -19
- package/src/Widgets/CreatableSelectWidget.jsx +300 -0
- package/src/Widgets/VisualizationViewWidget.jsx +34 -18
- package/src/Widgets/VisualizationViewWidget.test.jsx +1 -0
- package/src/Widgets/VisualizationWidget.jsx +113 -29
- package/src/Widgets/VisualizationWidget.test.jsx +1 -1
- package/src/Widgets/index.js +2 -1
- package/src/Widgets/schema.js +314 -41
- package/src/index.js +6 -1
|
@@ -1,33 +1,46 @@
|
|
|
1
|
-
import React, { useEffect } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { flattenToAppURL } from '@plone/volto/helpers';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { withRouter } from 'react-router';
|
|
4
3
|
import { connect } from 'react-redux';
|
|
5
4
|
import { compose } from 'redux';
|
|
5
|
+
import { isFunction } from 'lodash';
|
|
6
|
+
import { Message } from 'semantic-ui-react';
|
|
7
|
+
import { flattenToAppURL } from '@plone/volto/helpers';
|
|
6
8
|
import { getContent } from '@plone/volto/actions';
|
|
7
9
|
import PrivacyProtection from '@eeacms/volto-embed/PrivacyProtection/PrivacyProtection';
|
|
8
|
-
import { pickMetadata } from '@eeacms/volto-embed/helpers';
|
|
9
10
|
import Tableau from '@eeacms/volto-tableau/Tableau/Tableau';
|
|
11
|
+
import {
|
|
12
|
+
getQuery,
|
|
13
|
+
getTableauVisualization,
|
|
14
|
+
getParameters,
|
|
15
|
+
getFilters,
|
|
16
|
+
} from '@eeacms/volto-tableau/Tableau/helpers';
|
|
17
|
+
|
|
18
|
+
const timer = {};
|
|
10
19
|
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
? props.tableauContent?.tableau_visualization
|
|
17
|
-
: props.content?.tableau_visualization) ||
|
|
18
|
-
props.data.tableau_visualization ||
|
|
19
|
-
{};
|
|
20
|
-
return {
|
|
21
|
-
...pickMetadata(content),
|
|
22
|
-
...tableau_visualization,
|
|
23
|
-
};
|
|
20
|
+
function debounce(func, wait = 500, id) {
|
|
21
|
+
if (!isFunction(func)) return;
|
|
22
|
+
const name = id || func.name || 'generic';
|
|
23
|
+
if (timer[name]) clearTimeout(timer[name]);
|
|
24
|
+
timer[name] = setTimeout(func, wait);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
const View = (props) => {
|
|
27
|
-
const {
|
|
28
|
+
const {
|
|
29
|
+
isBlock,
|
|
30
|
+
id,
|
|
31
|
+
location,
|
|
32
|
+
mode,
|
|
33
|
+
data,
|
|
34
|
+
getContent,
|
|
35
|
+
useVisibilitySensor,
|
|
36
|
+
data_query,
|
|
37
|
+
discodata_query,
|
|
38
|
+
tableau_vis_url,
|
|
39
|
+
content,
|
|
40
|
+
tableauContent,
|
|
41
|
+
} = props;
|
|
28
42
|
const {
|
|
29
43
|
with_notes = true,
|
|
30
|
-
with_sources = true,
|
|
31
44
|
with_more_info = true,
|
|
32
45
|
with_download = true,
|
|
33
46
|
with_share = true,
|
|
@@ -35,59 +48,135 @@ const View = (props) => {
|
|
|
35
48
|
tableau_height,
|
|
36
49
|
} = data;
|
|
37
50
|
|
|
38
|
-
const
|
|
51
|
+
const [tableauVisualization, setTableauVisualization] = useState(() =>
|
|
52
|
+
getTableauVisualization({
|
|
53
|
+
isBlock,
|
|
54
|
+
data,
|
|
55
|
+
content,
|
|
56
|
+
tableauContent,
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
const [query, setQuery] = useState(() => {
|
|
60
|
+
return getQuery({ data_query, location, tableau_vis_url, discodata_query });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const [extraParameters, setExtraParameters] = useState(() =>
|
|
64
|
+
getParameters({ tableauVisualization, query, data }),
|
|
65
|
+
);
|
|
66
|
+
const [extraFilters, setExtraFilters] = useState(() =>
|
|
67
|
+
getFilters({ tableauVisualization, query, data }),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Update tableau visualization
|
|
72
|
+
*/
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
setTableauVisualization(
|
|
75
|
+
getTableauVisualization({
|
|
76
|
+
isBlock,
|
|
77
|
+
data,
|
|
78
|
+
content,
|
|
79
|
+
tableauContent,
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
}, [isBlock, data, content, tableauContent]);
|
|
39
83
|
|
|
40
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Update query
|
|
86
|
+
*/
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
setQuery(
|
|
89
|
+
getQuery({ data_query, location, tableau_vis_url, discodata_query }),
|
|
90
|
+
);
|
|
91
|
+
}, [tableau_vis_url, data_query, discodata_query, location]);
|
|
41
92
|
|
|
42
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Update extra parameters
|
|
95
|
+
*/
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
debounce(
|
|
98
|
+
() => {
|
|
99
|
+
setExtraParameters(
|
|
100
|
+
getParameters({
|
|
101
|
+
tableauVisualization,
|
|
102
|
+
query,
|
|
103
|
+
data,
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
},
|
|
107
|
+
500,
|
|
108
|
+
'setExtraParameters',
|
|
109
|
+
);
|
|
110
|
+
}, [tableauVisualization, query, data]);
|
|
43
111
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Update extra filters
|
|
114
|
+
*/
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
debounce(
|
|
117
|
+
() => {
|
|
118
|
+
setExtraFilters(
|
|
119
|
+
getFilters({
|
|
120
|
+
tableauVisualization,
|
|
121
|
+
query,
|
|
122
|
+
data,
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
},
|
|
126
|
+
500,
|
|
127
|
+
'setExtraFilters',
|
|
128
|
+
);
|
|
129
|
+
}, [tableauVisualization, query, data]);
|
|
53
130
|
|
|
54
131
|
useEffect(() => {
|
|
55
|
-
|
|
132
|
+
/**
|
|
133
|
+
* If we are in edit mode, we need to fetch the content of the
|
|
134
|
+
* tableau visualization
|
|
135
|
+
*/
|
|
136
|
+
const tableauVisId = flattenToAppURL(tableauVisualization['@id'] || '');
|
|
56
137
|
if (
|
|
57
|
-
isBlock &&
|
|
58
138
|
mode === 'edit' &&
|
|
139
|
+
!tableauVisualization.error &&
|
|
140
|
+
isBlock &&
|
|
59
141
|
tableau_vis_url &&
|
|
60
142
|
tableau_vis_url !== tableauVisId
|
|
61
143
|
) {
|
|
62
144
|
getContent(tableau_vis_url, null, id);
|
|
63
145
|
}
|
|
64
|
-
}, [id, isBlock, getContent, mode, tableau_vis_url,
|
|
146
|
+
}, [id, isBlock, getContent, mode, tableau_vis_url, tableauVisualization]);
|
|
65
147
|
|
|
66
148
|
if (props.mode === 'edit' && !tableau_vis_url) {
|
|
67
149
|
return <Message>Please select a tableau from block editor.</Message>;
|
|
68
150
|
}
|
|
69
151
|
|
|
152
|
+
if (tableauVisualization?.error) {
|
|
153
|
+
return (
|
|
154
|
+
<p dangerouslySetInnerHTML={{ __html: tableauVisualization.error }} />
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
70
158
|
return (
|
|
71
159
|
<div className="embed-tableau">
|
|
72
160
|
<PrivacyProtection
|
|
73
161
|
{...props}
|
|
74
|
-
data={{ ...data, url:
|
|
162
|
+
data={{ ...data, url: tableauVisualization?.url }}
|
|
75
163
|
useVisibilitySensor={useVisibilitySensor}
|
|
76
164
|
>
|
|
77
165
|
<Tableau
|
|
78
166
|
data={{
|
|
79
|
-
...
|
|
167
|
+
...tableauVisualization,
|
|
80
168
|
tableau_height:
|
|
81
|
-
tableau_height ||
|
|
169
|
+
tableau_height || tableauVisualization.tableau_height,
|
|
82
170
|
with_notes,
|
|
83
|
-
with_sources,
|
|
171
|
+
with_sources: true,
|
|
84
172
|
with_more_info,
|
|
85
173
|
with_download,
|
|
86
174
|
with_share,
|
|
87
175
|
with_enlarge,
|
|
88
176
|
tableau_vis_url,
|
|
89
177
|
}}
|
|
90
|
-
|
|
178
|
+
extraParameters={extraParameters}
|
|
179
|
+
extraFilters={extraFilters}
|
|
91
180
|
/>
|
|
92
181
|
</PrivacyProtection>
|
|
93
182
|
</div>
|
|
@@ -95,11 +184,19 @@ const View = (props) => {
|
|
|
95
184
|
};
|
|
96
185
|
|
|
97
186
|
export default compose(
|
|
187
|
+
withRouter,
|
|
98
188
|
connect(
|
|
99
|
-
(state, props) =>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
189
|
+
(state, props) => {
|
|
190
|
+
const tableau_vis_url = flattenToAppURL(props.data.tableau_vis_url || '');
|
|
191
|
+
const pathname = flattenToAppURL(state.content.data['@id']);
|
|
192
|
+
return {
|
|
193
|
+
tableauContent: state.content?.subrequests?.[props.id]?.data,
|
|
194
|
+
discodata_query: state.discodata_query,
|
|
195
|
+
data_query: state.connected_data_parameters.byContextPath[pathname],
|
|
196
|
+
isBlock: !!props.data?.['@type'],
|
|
197
|
+
tableau_vis_url,
|
|
198
|
+
};
|
|
199
|
+
},
|
|
103
200
|
{
|
|
104
201
|
getContent,
|
|
105
202
|
},
|
|
@@ -50,12 +50,31 @@ Array [
|
|
|
50
50
|
className="ui segment form attached"
|
|
51
51
|
>
|
|
52
52
|
<div
|
|
53
|
-
className="mocked-
|
|
53
|
+
className="mocked-default-widget"
|
|
54
54
|
id="mocked-field-tableau_vis_url"
|
|
55
55
|
>
|
|
56
56
|
Tableau visualization
|
|
57
57
|
-
|
|
58
|
-
|
|
58
|
+
<div>
|
|
59
|
+
<p>
|
|
60
|
+
When using context query parameters please use the corresponding field name from the Tableau service.
|
|
61
|
+
</p>
|
|
62
|
+
<p>
|
|
63
|
+
NOTE: The embeded tableau dashboard must have the parameters defined in the
|
|
64
|
+
|
|
65
|
+
<span
|
|
66
|
+
style={
|
|
67
|
+
Object {
|
|
68
|
+
"color": "rgb(15, 130, 204)",
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
>
|
|
72
|
+
'Dynamic parameters'
|
|
73
|
+
</span>
|
|
74
|
+
|
|
75
|
+
list so that the context query parameters can take effect.
|
|
76
|
+
</p>
|
|
77
|
+
</div>
|
|
59
78
|
</div>
|
|
60
79
|
<div
|
|
61
80
|
className="mocked-default-widget"
|
|
@@ -135,14 +154,6 @@ Array [
|
|
|
135
154
|
-
|
|
136
155
|
No description
|
|
137
156
|
</div>
|
|
138
|
-
<div
|
|
139
|
-
className="mocked-boolean-widget"
|
|
140
|
-
id="mocked-field-with_sources"
|
|
141
|
-
>
|
|
142
|
-
Show sources
|
|
143
|
-
-
|
|
144
|
-
Will show sources set in this page Data provenance
|
|
145
|
-
</div>
|
|
146
157
|
<div
|
|
147
158
|
className="mocked-boolean-widget"
|
|
148
159
|
id="mocked-field-with_more_info"
|
|
@@ -181,6 +192,75 @@ Array [
|
|
|
181
192
|
</div>
|
|
182
193
|
</div>
|
|
183
194
|
</div>
|
|
195
|
+
<div
|
|
196
|
+
className="accordion ui fluid styled form"
|
|
197
|
+
>
|
|
198
|
+
<div
|
|
199
|
+
id="blockform-fieldset-options"
|
|
200
|
+
>
|
|
201
|
+
<div
|
|
202
|
+
className="title"
|
|
203
|
+
onClick={[Function]}
|
|
204
|
+
>
|
|
205
|
+
Parameters
|
|
206
|
+
<svg
|
|
207
|
+
className="icon"
|
|
208
|
+
dangerouslySetInnerHTML={
|
|
209
|
+
Object {
|
|
210
|
+
"__html": undefined,
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
onClick={null}
|
|
214
|
+
style={
|
|
215
|
+
Object {
|
|
216
|
+
"fill": "currentColor",
|
|
217
|
+
"height": "20px",
|
|
218
|
+
"width": "auto",
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
viewBox=""
|
|
222
|
+
xmlns=""
|
|
223
|
+
/>
|
|
224
|
+
</div>
|
|
225
|
+
<div
|
|
226
|
+
className="content"
|
|
227
|
+
>
|
|
228
|
+
<div
|
|
229
|
+
aria-hidden={true}
|
|
230
|
+
className="rah-static rah-static--height-zero"
|
|
231
|
+
style={
|
|
232
|
+
Object {
|
|
233
|
+
"height": 0,
|
|
234
|
+
"overflow": "hidden",
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
>
|
|
238
|
+
<div
|
|
239
|
+
style={
|
|
240
|
+
Object {
|
|
241
|
+
"WebkitTransition": "opacity 500ms ease 0ms",
|
|
242
|
+
"opacity": 0,
|
|
243
|
+
"transition": "opacity 500ms ease 0ms",
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
>
|
|
247
|
+
<div
|
|
248
|
+
className="ui segment attached"
|
|
249
|
+
>
|
|
250
|
+
<div
|
|
251
|
+
className="mocked-default-widget"
|
|
252
|
+
id="mocked-field-staticParameters"
|
|
253
|
+
>
|
|
254
|
+
Static parameters
|
|
255
|
+
-
|
|
256
|
+
Set a list of static parameters.
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
184
264
|
<div
|
|
185
265
|
className="accordion ui fluid styled form"
|
|
186
266
|
>
|
|
@@ -53,6 +53,22 @@ const getProtectionSchema = () => ({
|
|
|
53
53
|
required: [],
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
const staticParameters = {
|
|
57
|
+
title: 'Parameter',
|
|
58
|
+
fieldsets: [{ id: 'default', title: 'Default', fields: ['field', 'value'] }],
|
|
59
|
+
properties: {
|
|
60
|
+
field: {
|
|
61
|
+
title: 'Tableau fieldname',
|
|
62
|
+
type: 'text',
|
|
63
|
+
},
|
|
64
|
+
value: {
|
|
65
|
+
title: 'Value',
|
|
66
|
+
type: 'text',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
required: [],
|
|
70
|
+
};
|
|
71
|
+
|
|
56
72
|
export default (props) => {
|
|
57
73
|
return {
|
|
58
74
|
title: 'Embed Dashboard (Tableau)',
|
|
@@ -67,13 +83,17 @@ export default (props) => {
|
|
|
67
83
|
title: 'Toolbar',
|
|
68
84
|
fields: [
|
|
69
85
|
'with_notes',
|
|
70
|
-
'with_sources',
|
|
71
86
|
'with_more_info',
|
|
72
87
|
'with_download',
|
|
73
88
|
'with_share',
|
|
74
89
|
'with_enlarge',
|
|
75
90
|
],
|
|
76
91
|
},
|
|
92
|
+
{
|
|
93
|
+
id: 'options',
|
|
94
|
+
title: 'Parameters',
|
|
95
|
+
fields: ['staticParameters'],
|
|
96
|
+
},
|
|
77
97
|
{
|
|
78
98
|
id: 'privacy',
|
|
79
99
|
title: 'Privacy',
|
|
@@ -83,7 +103,23 @@ export default (props) => {
|
|
|
83
103
|
properties: {
|
|
84
104
|
tableau_vis_url: {
|
|
85
105
|
title: 'Tableau visualization',
|
|
86
|
-
widget: '
|
|
106
|
+
widget: 'internal_url',
|
|
107
|
+
description: (
|
|
108
|
+
<div>
|
|
109
|
+
<p>
|
|
110
|
+
When using context query parameters please use the corresponding
|
|
111
|
+
field name from the Tableau service.
|
|
112
|
+
</p>
|
|
113
|
+
<p>
|
|
114
|
+
NOTE: The embeded tableau dashboard must have the parameters
|
|
115
|
+
defined in the{' '}
|
|
116
|
+
<span style={{ color: 'rgb(15, 130, 204)' }}>
|
|
117
|
+
'Dynamic parameters'
|
|
118
|
+
</span>{' '}
|
|
119
|
+
list so that the context query parameters can take effect.
|
|
120
|
+
</p>
|
|
121
|
+
</div>
|
|
122
|
+
),
|
|
87
123
|
},
|
|
88
124
|
with_notes: {
|
|
89
125
|
title: 'Show note',
|
|
@@ -134,6 +170,12 @@ export default (props) => {
|
|
|
134
170
|
widget: 'object',
|
|
135
171
|
schema: getProtectionSchema(),
|
|
136
172
|
},
|
|
173
|
+
staticParameters: {
|
|
174
|
+
title: 'Static parameters',
|
|
175
|
+
widget: 'object_list',
|
|
176
|
+
schema: staticParameters,
|
|
177
|
+
description: 'Set a list of static parameters.',
|
|
178
|
+
},
|
|
137
179
|
},
|
|
138
180
|
|
|
139
181
|
required: ['tableau_vis_url'],
|
package/src/Tableau/Tableau.jsx
CHANGED
|
@@ -9,7 +9,22 @@ import React, {
|
|
|
9
9
|
} from 'react';
|
|
10
10
|
import { connect } from 'react-redux';
|
|
11
11
|
import { toast } from 'react-toastify';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
isEqual,
|
|
14
|
+
isUndefined,
|
|
15
|
+
isNaN,
|
|
16
|
+
isNumber,
|
|
17
|
+
forOwn,
|
|
18
|
+
find,
|
|
19
|
+
includes,
|
|
20
|
+
isArray,
|
|
21
|
+
isString,
|
|
22
|
+
isInteger,
|
|
23
|
+
isBoolean,
|
|
24
|
+
toString,
|
|
25
|
+
toInteger,
|
|
26
|
+
toNumber,
|
|
27
|
+
} from 'lodash';
|
|
13
28
|
import cx from 'classnames';
|
|
14
29
|
import { Button } from 'semantic-ui-react';
|
|
15
30
|
import { Toast, Icon } from '@plone/volto/components';
|
|
@@ -48,7 +63,9 @@ const TableauDebug = ({ mode, data, vizState, url, version, clearData }) => {
|
|
|
48
63
|
return (
|
|
49
64
|
<div className="tableau-debug">
|
|
50
65
|
{!url && !vizState.error && <p className="tableau-error">URL required</p>}
|
|
51
|
-
{vizState.error &&
|
|
66
|
+
{isString(vizState.error) && (
|
|
67
|
+
<p className="tableau-error">{vizState.error}</p>
|
|
68
|
+
)}
|
|
52
69
|
{vizState.loaded && url && (
|
|
53
70
|
<h3 className="tableau-version">
|
|
54
71
|
Tableau <span className="version">{version}</span>
|
|
@@ -93,6 +110,7 @@ const Tableau = forwardRef((props, ref) => {
|
|
|
93
110
|
data = {},
|
|
94
111
|
breakpoints = {},
|
|
95
112
|
extraFilters = {},
|
|
113
|
+
extraParameters = {},
|
|
96
114
|
extraOptions = {},
|
|
97
115
|
mode = 'view',
|
|
98
116
|
screen = {},
|
|
@@ -100,6 +118,7 @@ const Tableau = forwardRef((props, ref) => {
|
|
|
100
118
|
setVizState,
|
|
101
119
|
onChangeBlock,
|
|
102
120
|
} = props;
|
|
121
|
+
|
|
103
122
|
const {
|
|
104
123
|
data_provenance = {},
|
|
105
124
|
figure_note = [],
|
|
@@ -244,10 +263,10 @@ const Tableau = forwardRef((props, ref) => {
|
|
|
244
263
|
hideToolbar,
|
|
245
264
|
toolbarPosition,
|
|
246
265
|
device: !!breakpointUrl ? device : 'desktop',
|
|
266
|
+
...extraOptions,
|
|
247
267
|
...data.filters,
|
|
248
|
-
...data.parameters,
|
|
249
268
|
...extraFilters,
|
|
250
|
-
...
|
|
269
|
+
...extraParameters,
|
|
251
270
|
onFirstInteractive: () => {
|
|
252
271
|
onVizStateUpdate(true, true, null);
|
|
253
272
|
setInitiateViz(false);
|
|
@@ -296,32 +315,11 @@ const Tableau = forwardRef((props, ref) => {
|
|
|
296
315
|
},
|
|
297
316
|
});
|
|
298
317
|
} catch (e) {
|
|
299
|
-
onVizStateUpdate(false, false, e
|
|
318
|
+
onVizStateUpdate(false, false, e?.get_message?.() || e);
|
|
300
319
|
setInitiateViz(false);
|
|
301
320
|
}
|
|
302
321
|
};
|
|
303
322
|
|
|
304
|
-
const addExtraFilters = (extraFilters) => {
|
|
305
|
-
const worksheets =
|
|
306
|
-
viz.current.getWorkbook().getActiveSheet().getWorksheets() || [];
|
|
307
|
-
|
|
308
|
-
worksheets.forEach((worksheet) => {
|
|
309
|
-
if (worksheet.getSheetType() === tableau.DashboardObjectType.WORKSHEET) {
|
|
310
|
-
Object.keys(extraFilters).forEach((filter) => {
|
|
311
|
-
if (!extraFilters[filter]) {
|
|
312
|
-
worksheet.clearFilterAsync(filter);
|
|
313
|
-
} else {
|
|
314
|
-
worksheet.applyFilterAsync(
|
|
315
|
-
filter,
|
|
316
|
-
extraFilters[filter],
|
|
317
|
-
tableau.FilterUpdateType.REPLACE,
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
};
|
|
324
|
-
|
|
325
323
|
const updateScale = () => {
|
|
326
324
|
const iframe = vizEl.current.querySelector('iframe');
|
|
327
325
|
const { sheetSize = {} } = viz.current.getVizSize() || {};
|
|
@@ -358,12 +356,99 @@ const Tableau = forwardRef((props, ref) => {
|
|
|
358
356
|
/* eslint-disable-next-line */
|
|
359
357
|
}, [loaded, loading, initiateViz]);
|
|
360
358
|
|
|
359
|
+
/**
|
|
360
|
+
* TODO: make this work
|
|
361
|
+
*/
|
|
362
|
+
// useEffect(() => {
|
|
363
|
+
// async function addExtraFilters() {
|
|
364
|
+
// if (vizState.current.loaded && viz.current) {
|
|
365
|
+
// const dashboard = viz.current.getWorkbook().getActiveSheet();
|
|
366
|
+
// const tableauFilters = await dashboard.getFiltersAsync();
|
|
367
|
+
|
|
368
|
+
// forOwn(extraFilters, (value, fieldName) => {
|
|
369
|
+
// const tableauFilter = find(
|
|
370
|
+
// tableauFilters,
|
|
371
|
+
// (f) => f.getFieldName() === fieldName,
|
|
372
|
+
// );
|
|
373
|
+
// if (!tableauFilter) return;
|
|
374
|
+
// if (!value) {
|
|
375
|
+
// tableauFilter.getWorksheet().clearFilterAsync(fieldName);
|
|
376
|
+
// return;
|
|
377
|
+
// }
|
|
378
|
+
// const filterType = tableauFilter.getFilterType();
|
|
379
|
+
// if (filterType === 'categorical') {
|
|
380
|
+
// if (!isArray(value)) {
|
|
381
|
+
// value = [value];
|
|
382
|
+
// }
|
|
383
|
+
// dashboard.applyFilterAsync(
|
|
384
|
+
// fieldName,
|
|
385
|
+
// value,
|
|
386
|
+
// tableau.FilterUpdateType.REPLACE,
|
|
387
|
+
// );
|
|
388
|
+
// }
|
|
389
|
+
// /**
|
|
390
|
+
// * TODO: handle other filter types
|
|
391
|
+
// */
|
|
392
|
+
// });
|
|
393
|
+
// }
|
|
394
|
+
// }
|
|
395
|
+
// addExtraFilters();
|
|
396
|
+
// /* eslint-disable-next-line */
|
|
397
|
+
// }, [loaded, JSON.stringify(extraFilters)]);
|
|
398
|
+
|
|
361
399
|
useEffect(() => {
|
|
362
|
-
|
|
363
|
-
|
|
400
|
+
async function addExtraParameters() {
|
|
401
|
+
if (vizState.current.loaded && viz.current) {
|
|
402
|
+
const workbook = viz.current.getWorkbook();
|
|
403
|
+
const tableauParameters = await workbook.getParametersAsync();
|
|
404
|
+
forOwn(extraParameters, (value, fieldName) => {
|
|
405
|
+
const tableauParameter = find(
|
|
406
|
+
tableauParameters,
|
|
407
|
+
(p) => p.getName() === fieldName,
|
|
408
|
+
);
|
|
409
|
+
if (!tableauParameter || !value) return;
|
|
410
|
+
const allowableValuesType = tableauParameter.getAllowableValuesType();
|
|
411
|
+
const dataType = tableauParameter.getDataType();
|
|
412
|
+
if (includes(['all', 'list'], allowableValuesType)) {
|
|
413
|
+
const values = tableauParameter
|
|
414
|
+
.getAllowableValues()
|
|
415
|
+
?.map((v) => v.value);
|
|
416
|
+
if (!isArray(value)) {
|
|
417
|
+
value = [value];
|
|
418
|
+
}
|
|
419
|
+
value = value
|
|
420
|
+
.filter((v) => includes(values, v))
|
|
421
|
+
.map((v) => {
|
|
422
|
+
if (dataType === 'string' && !isString(v)) {
|
|
423
|
+
return toString(v);
|
|
424
|
+
}
|
|
425
|
+
if (dataType === 'integer' && !isInteger(v)) {
|
|
426
|
+
return toInteger(v);
|
|
427
|
+
}
|
|
428
|
+
if (dataType === 'float' && !isNumber(v)) {
|
|
429
|
+
return toNumber(v);
|
|
430
|
+
}
|
|
431
|
+
if (dataType === 'boolean' && !isBoolean(v)) {
|
|
432
|
+
return !!v;
|
|
433
|
+
}
|
|
434
|
+
return v;
|
|
435
|
+
});
|
|
436
|
+
if (value.length) {
|
|
437
|
+
workbook.changeParameterValueAsync(fieldName, value);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (allowableValuesType === 'range' && value) {
|
|
441
|
+
/**
|
|
442
|
+
* TODO: handle range parameters
|
|
443
|
+
*/
|
|
444
|
+
workbook.changeParameterValueAsync(fieldName, value);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
364
448
|
}
|
|
449
|
+
addExtraParameters();
|
|
365
450
|
/* eslint-disable-next-line */
|
|
366
|
-
}, [JSON.stringify(
|
|
451
|
+
}, [loaded, JSON.stringify(extraParameters)]);
|
|
367
452
|
|
|
368
453
|
useEffect(() => {
|
|
369
454
|
if (vizState.current.loaded && viz.current && autoScale) {
|