@eeacms/volto-tableau 3.0.8 → 4.1.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 +20 -7
- package/Jenkinsfile +2 -0
- package/jest-addon.config.js +2 -2
- package/package.json +1 -1
- package/src/{TableauBlock → Blocks/EmbedTableauVisualization}/Edit.jsx +7 -7
- package/src/Blocks/EmbedTableauVisualization/View.jsx +66 -0
- package/src/Blocks/EmbedTableauVisualization/index.js +30 -0
- package/src/Blocks/{EmbedEEATableauBlock → EmbedTableauVisualization}/schema.js +32 -13
- package/src/Blocks/TableauBlock/Edit.jsx +41 -0
- package/src/Blocks/TableauBlock/View.jsx +101 -0
- package/src/Blocks/TableauBlock/index.js +30 -0
- package/src/Blocks/TableauBlock/schema.js +224 -0
- package/src/Blocks/index.js +9 -0
- package/src/Tableau/Tableau.jsx +430 -0
- package/src/Tableau/helpers.js +41 -0
- package/src/Utils/Download/Download.jsx +72 -0
- package/src/Utils/JsonCodeSnippet/JsonCodeSnippet.jsx +48 -0
- package/src/Utils/Share/Share.jsx +21 -0
- package/src/Utils/Sources/Sources.jsx +66 -0
- package/src/Views/VisualizationView.jsx +18 -32
- package/src/Widgets/VisualizationWidget.jsx +92 -122
- package/src/Widgets/schema.js +88 -115
- package/src/helpers.js +15 -34
- package/src/hooks.js +18 -0
- package/src/icons/download.svg +5 -0
- package/src/index.js +4 -66
- package/src/less/tableau.less +172 -70
- package/src/less/tableau.variables +7 -13
- package/src/Blocks/EmbedEEATableauBlock/Edit.jsx +0 -56
- package/src/Blocks/EmbedEEATableauBlock/View.jsx +0 -74
- package/src/ConnectedTableau/ConnectedTableau.jsx +0 -29
- package/src/CustomWidgets/UrlParamsWidget.jsx +0 -29
- package/src/DownloadExtras/TableauDownload.jsx +0 -124
- package/src/DownloadExtras/TableauFullscreen.jsx +0 -78
- package/src/DownloadExtras/TableauShare.jsx +0 -81
- package/src/DownloadExtras/style.less +0 -152
- package/src/Sources/Sources.jsx +0 -50
- package/src/Sources/index.js +0 -3
- package/src/Sources/style.css +0 -7
- package/src/Tableau/View.jsx +0 -254
- package/src/TableauBlock/View.jsx +0 -109
- package/src/TableauBlock/schema.js +0 -124
- package/src/Widgets/style.less +0 -8
- package/src/actions.js +0 -9
- package/src/constants.js +0 -1
- package/src/downloadHelpers/downloadHelpers.js +0 -25
- package/src/middleware.js +0 -39
- package/src/store.js +0 -72
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSheetnamesChoices,
|
|
3
|
+
canChangeVizData,
|
|
4
|
+
} from '@eeacms/volto-tableau/Tableau/helpers';
|
|
5
|
+
|
|
6
|
+
const urlParametersSchema = {
|
|
7
|
+
title: 'Parameter',
|
|
8
|
+
fieldsets: [
|
|
9
|
+
{ id: 'default', title: 'Default', fields: ['field', 'urlParam'] },
|
|
10
|
+
],
|
|
11
|
+
properties: {
|
|
12
|
+
field: {
|
|
13
|
+
title: 'Tableau fieldname',
|
|
14
|
+
type: 'text',
|
|
15
|
+
},
|
|
16
|
+
urlParam: {
|
|
17
|
+
title: 'URL param',
|
|
18
|
+
type: 'text',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
required: [],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const staticParameters = {
|
|
25
|
+
title: 'Parameter',
|
|
26
|
+
fieldsets: [{ id: 'default', title: 'Default', fields: ['field', 'value'] }],
|
|
27
|
+
properties: {
|
|
28
|
+
field: {
|
|
29
|
+
title: 'Tableau fieldname',
|
|
30
|
+
type: 'text',
|
|
31
|
+
},
|
|
32
|
+
value: {
|
|
33
|
+
title: 'Value',
|
|
34
|
+
type: 'text',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: [],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const breakpointUrlSchema = (config) => {
|
|
41
|
+
const breakpoints = config.blocks.blocksConfig.tableau_block.breakpoints;
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
title: 'URL',
|
|
45
|
+
fieldsets: [{ id: 'default', title: 'Default', fields: ['device', 'url'] }],
|
|
46
|
+
properties: {
|
|
47
|
+
device: {
|
|
48
|
+
title: 'Device',
|
|
49
|
+
choices: Object.keys(breakpoints).map((breakpoint) => [
|
|
50
|
+
breakpoint,
|
|
51
|
+
breakpoint,
|
|
52
|
+
]),
|
|
53
|
+
},
|
|
54
|
+
url: {
|
|
55
|
+
title: 'Url',
|
|
56
|
+
widget: 'textarea',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: [],
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const sourceSchema = {
|
|
64
|
+
title: 'Source',
|
|
65
|
+
fieldsets: [
|
|
66
|
+
{
|
|
67
|
+
id: 'default',
|
|
68
|
+
title: 'Default',
|
|
69
|
+
fields: ['title', 'organisation', 'link'],
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
properties: {
|
|
73
|
+
title: {
|
|
74
|
+
title: 'Title',
|
|
75
|
+
},
|
|
76
|
+
organisation: {
|
|
77
|
+
title: 'Organization',
|
|
78
|
+
},
|
|
79
|
+
link: {
|
|
80
|
+
title: 'Link',
|
|
81
|
+
widget: 'url',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
required: [],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default (config, viz, vizState) => {
|
|
88
|
+
const isDisabled = !canChangeVizData(viz, vizState);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
title: 'Tableau',
|
|
92
|
+
fieldsets: [
|
|
93
|
+
{
|
|
94
|
+
id: 'default',
|
|
95
|
+
title: 'Default',
|
|
96
|
+
fields: [
|
|
97
|
+
'url',
|
|
98
|
+
'title',
|
|
99
|
+
'description',
|
|
100
|
+
'with_sources',
|
|
101
|
+
'with_download',
|
|
102
|
+
'with_share',
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'options',
|
|
107
|
+
title: 'Options',
|
|
108
|
+
fields: [
|
|
109
|
+
'sheetname',
|
|
110
|
+
'hideTabs',
|
|
111
|
+
'hideToolbar',
|
|
112
|
+
'autoScale',
|
|
113
|
+
'toolbarPosition',
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'extra_options',
|
|
118
|
+
title: 'Extra options',
|
|
119
|
+
fields: ['urlParameters', 'staticParameters', 'breakpointUrls'],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: 'sources',
|
|
123
|
+
title: 'Sources',
|
|
124
|
+
fields: ['sources'],
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
properties: {
|
|
128
|
+
url: {
|
|
129
|
+
title: 'Url',
|
|
130
|
+
widget: 'textarea',
|
|
131
|
+
isDisabled,
|
|
132
|
+
},
|
|
133
|
+
title: {
|
|
134
|
+
title: 'Title',
|
|
135
|
+
widget: 'textarea',
|
|
136
|
+
},
|
|
137
|
+
description: {
|
|
138
|
+
title: 'Description',
|
|
139
|
+
widget: 'textarea',
|
|
140
|
+
},
|
|
141
|
+
with_download: {
|
|
142
|
+
title: 'Show download button',
|
|
143
|
+
type: 'boolean',
|
|
144
|
+
defaultValue: true,
|
|
145
|
+
},
|
|
146
|
+
with_share: {
|
|
147
|
+
title: 'Show share button',
|
|
148
|
+
type: 'boolean',
|
|
149
|
+
defaultValue: true,
|
|
150
|
+
},
|
|
151
|
+
with_sources: {
|
|
152
|
+
title: 'Show sources',
|
|
153
|
+
type: 'boolean',
|
|
154
|
+
defaultValue: true,
|
|
155
|
+
},
|
|
156
|
+
sources: {
|
|
157
|
+
title: 'Sources',
|
|
158
|
+
widget: 'object_list',
|
|
159
|
+
schema: sourceSchema,
|
|
160
|
+
},
|
|
161
|
+
sheetname: {
|
|
162
|
+
title: 'Sheetname',
|
|
163
|
+
choices: getSheetnamesChoices(viz),
|
|
164
|
+
isDisabled,
|
|
165
|
+
},
|
|
166
|
+
hideTabs: {
|
|
167
|
+
title: 'Hide tabs',
|
|
168
|
+
type: 'boolean',
|
|
169
|
+
default: false,
|
|
170
|
+
isDisabled,
|
|
171
|
+
},
|
|
172
|
+
hideToolbar: {
|
|
173
|
+
title: 'Hide toolbar',
|
|
174
|
+
type: 'boolean',
|
|
175
|
+
default: false,
|
|
176
|
+
isDisabled,
|
|
177
|
+
},
|
|
178
|
+
autoScale: {
|
|
179
|
+
title: 'Auto scale',
|
|
180
|
+
type: 'boolean',
|
|
181
|
+
default: false,
|
|
182
|
+
description: 'Scale down tableau according to width',
|
|
183
|
+
isDisabled,
|
|
184
|
+
},
|
|
185
|
+
toolbarPosition: {
|
|
186
|
+
title: 'Toolbar position',
|
|
187
|
+
choices: [
|
|
188
|
+
['Top', 'Top'],
|
|
189
|
+
['Bottom', 'Bottom'],
|
|
190
|
+
],
|
|
191
|
+
default: 'Top',
|
|
192
|
+
isDisabled,
|
|
193
|
+
},
|
|
194
|
+
urlParameters: {
|
|
195
|
+
title: 'URL parameters',
|
|
196
|
+
widget: 'object_list',
|
|
197
|
+
schema: urlParametersSchema,
|
|
198
|
+
description: 'Set a list of url parameters to filter the tableau',
|
|
199
|
+
isDisabled,
|
|
200
|
+
},
|
|
201
|
+
staticParameters: {
|
|
202
|
+
title: 'Static parameters',
|
|
203
|
+
widget: 'object_list',
|
|
204
|
+
schema: staticParameters,
|
|
205
|
+
description: (
|
|
206
|
+
<>
|
|
207
|
+
Set a list of static parameters.
|
|
208
|
+
<br />
|
|
209
|
+
<b>NOTE: You need to trigger a refresh for this to take effect</b>
|
|
210
|
+
</>
|
|
211
|
+
),
|
|
212
|
+
isDisabled,
|
|
213
|
+
},
|
|
214
|
+
breakpointUrls: {
|
|
215
|
+
title: 'Breakpoint urls',
|
|
216
|
+
widget: 'object_list',
|
|
217
|
+
schema: breakpointUrlSchema(config),
|
|
218
|
+
description: 'Set different vizualization for specific breakpoint',
|
|
219
|
+
isDisabled,
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
required: ['url'],
|
|
223
|
+
};
|
|
224
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import installEmbedTableauVisualization from './EmbedTableauVisualization';
|
|
2
|
+
import installTableauBlock from './TableauBlock';
|
|
3
|
+
|
|
4
|
+
export default (config) => {
|
|
5
|
+
return [installEmbedTableauVisualization, installTableauBlock].reduce(
|
|
6
|
+
(acc, apply) => apply(acc),
|
|
7
|
+
config,
|
|
8
|
+
);
|
|
9
|
+
};
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useEffect,
|
|
3
|
+
useImperativeHandle,
|
|
4
|
+
useState,
|
|
5
|
+
useRef,
|
|
6
|
+
useMemo,
|
|
7
|
+
useCallback,
|
|
8
|
+
forwardRef,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { connect } from 'react-redux';
|
|
11
|
+
import { toast } from 'react-toastify';
|
|
12
|
+
import isEqual from 'lodash/isEqual';
|
|
13
|
+
import isUndefined from 'lodash/isUndefined';
|
|
14
|
+
import cx from 'classnames';
|
|
15
|
+
import { Button } from 'semantic-ui-react';
|
|
16
|
+
import { Toast, Icon } from '@plone/volto/components';
|
|
17
|
+
import { useTableau } from '@eeacms/volto-tableau/hooks';
|
|
18
|
+
import JsonCodeSnippet from '@eeacms/volto-tableau/Utils/JsonCodeSnippet/JsonCodeSnippet';
|
|
19
|
+
import Sources from '@eeacms/volto-tableau/Utils/Sources/Sources';
|
|
20
|
+
import Download from '@eeacms/volto-tableau/Utils/Download/Download';
|
|
21
|
+
import Share from '@eeacms/volto-tableau/Utils/Share/Share';
|
|
22
|
+
import { getSheetnames, getActiveSheetname, getDevice } from './helpers';
|
|
23
|
+
|
|
24
|
+
import resetSVG from '@plone/volto/icons/reset.svg';
|
|
25
|
+
|
|
26
|
+
const TableauDebug = ({ mode, data, vizState, url, version, clearData }) => {
|
|
27
|
+
const { loaded, error } = vizState;
|
|
28
|
+
const { filters = {}, parameters = {} } = data;
|
|
29
|
+
|
|
30
|
+
const showTableauInfo = mode === 'edit' && (!url || (loaded && url) || error);
|
|
31
|
+
|
|
32
|
+
if (!showTableauInfo) return null;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="tableau-debug">
|
|
36
|
+
{!url && !vizState.error && <p className="tableau-error">URL required</p>}
|
|
37
|
+
{vizState.error && <p className="tableau-error">{vizState.error}</p>}
|
|
38
|
+
{vizState.loaded && url && (
|
|
39
|
+
<h3 className="tableau-version">
|
|
40
|
+
Tableau <span className="version">{version}</span>
|
|
41
|
+
</h3>
|
|
42
|
+
)}
|
|
43
|
+
|
|
44
|
+
{vizState.loaded && url && (
|
|
45
|
+
<>
|
|
46
|
+
<p>
|
|
47
|
+
Apply filters / parameters inside of tableau to set default static
|
|
48
|
+
filters / parameters.
|
|
49
|
+
</p>
|
|
50
|
+
<p className="important-note">
|
|
51
|
+
Click{' '}
|
|
52
|
+
<button className="clear-filter-button" onClick={clearData}>
|
|
53
|
+
here
|
|
54
|
+
</button>{' '}
|
|
55
|
+
to reset applied filters / parameters.
|
|
56
|
+
</p>
|
|
57
|
+
<code className="json-snippet">
|
|
58
|
+
<JsonCodeSnippet obj={{ filters, parameters }} />
|
|
59
|
+
</code>
|
|
60
|
+
</>
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const Tableau = forwardRef((props, ref) => {
|
|
67
|
+
const vizEl = useRef(null);
|
|
68
|
+
const viz = useRef();
|
|
69
|
+
const vizState = useRef({});
|
|
70
|
+
const dataRef = useRef(props.data || {});
|
|
71
|
+
const [initiateViz, setInitiateViz] = useState(false);
|
|
72
|
+
const [loaded, setLoaded] = useState(false);
|
|
73
|
+
const [loading, setLoading] = useState(false);
|
|
74
|
+
const [error, setError] = useState(null);
|
|
75
|
+
const {
|
|
76
|
+
block,
|
|
77
|
+
data = {},
|
|
78
|
+
breakpoints = {},
|
|
79
|
+
extraFilters = {},
|
|
80
|
+
extraOptions = {},
|
|
81
|
+
mode = 'view',
|
|
82
|
+
screen = {},
|
|
83
|
+
version = '2.8.0',
|
|
84
|
+
sources,
|
|
85
|
+
setVizState,
|
|
86
|
+
onChangeBlock,
|
|
87
|
+
} = props;
|
|
88
|
+
const {
|
|
89
|
+
autoScale = false,
|
|
90
|
+
hideTabs = false,
|
|
91
|
+
hideToolbar = false,
|
|
92
|
+
sheetname = '',
|
|
93
|
+
toolbarPosition = 'Top',
|
|
94
|
+
breakpointUrls = [],
|
|
95
|
+
with_sources,
|
|
96
|
+
with_download,
|
|
97
|
+
with_share,
|
|
98
|
+
} = data;
|
|
99
|
+
|
|
100
|
+
const device = useMemo(
|
|
101
|
+
() => getDevice(breakpoints, screen.page?.width || Infinity),
|
|
102
|
+
[breakpoints, screen],
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const breakpointUrl = useMemo(
|
|
106
|
+
() =>
|
|
107
|
+
breakpointUrls.filter((breakpoint) => breakpoint.device === device)[0]
|
|
108
|
+
?.url,
|
|
109
|
+
[breakpointUrls, device],
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const url = breakpointUrl || data.url;
|
|
113
|
+
|
|
114
|
+
// Load tableau from script tag
|
|
115
|
+
const tableau = useTableau(version);
|
|
116
|
+
|
|
117
|
+
const onFilterChange = (filter) => {
|
|
118
|
+
let value;
|
|
119
|
+
const filters = dataRef.current.filters;
|
|
120
|
+
const newFilters = { ...filters };
|
|
121
|
+
const fieldName = filter.getFieldName();
|
|
122
|
+
const filterType = filter.getFilterType();
|
|
123
|
+
switch (filterType) {
|
|
124
|
+
// https://community.tableau.com/s/idea/0874T000000HA8hQAG/detail
|
|
125
|
+
// Only categorical filters are allowed
|
|
126
|
+
case tableau.FilterType.CATEGORICAL:
|
|
127
|
+
const isAllSelected = filter.getIsAllSelected();
|
|
128
|
+
if (!isAllSelected) {
|
|
129
|
+
value = filter
|
|
130
|
+
.getAppliedValues()
|
|
131
|
+
.map((appliedValue) => appliedValue.value);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
case tableau.FilterType.QUANTITATIVE:
|
|
135
|
+
break;
|
|
136
|
+
case tableau.FilterType.HIERARCHICAL:
|
|
137
|
+
break;
|
|
138
|
+
case tableau.FilterType.RELATIVE_DATE:
|
|
139
|
+
break;
|
|
140
|
+
default:
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
if (isUndefined(value) && !isUndefined(newFilters[fieldName])) {
|
|
144
|
+
delete newFilters[fieldName];
|
|
145
|
+
} else if (!isUndefined(value)) {
|
|
146
|
+
newFilters[fieldName] = value;
|
|
147
|
+
}
|
|
148
|
+
if (!isEqual(newFilters, filters)) {
|
|
149
|
+
onChangeBlock(block, {
|
|
150
|
+
...dataRef.current,
|
|
151
|
+
filters: {
|
|
152
|
+
...newFilters,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const onParameterChange = (parameter) => {
|
|
159
|
+
const parameters = dataRef.current.parameters;
|
|
160
|
+
const newParameters = { ...parameters };
|
|
161
|
+
const fieldName = parameter.getName();
|
|
162
|
+
const value = parameter.getCurrentValue()?.value;
|
|
163
|
+
if (isUndefined(value) && !isUndefined(newParameters[fieldName])) {
|
|
164
|
+
delete newParameters[fieldName];
|
|
165
|
+
} else {
|
|
166
|
+
newParameters[fieldName] = [value];
|
|
167
|
+
}
|
|
168
|
+
if (!isEqual(newParameters, parameters)) {
|
|
169
|
+
onChangeBlock(block, {
|
|
170
|
+
...dataRef.current,
|
|
171
|
+
parameters: {
|
|
172
|
+
...newParameters,
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const onVizStateUpdate = useCallback((loaded, loading, error) => {
|
|
179
|
+
vizState.current = { ...vizState.current, loaded, loading, error };
|
|
180
|
+
setLoaded(loaded);
|
|
181
|
+
setLoading(loading);
|
|
182
|
+
setError(error);
|
|
183
|
+
if (setVizState) {
|
|
184
|
+
setVizState({ loaded, loading, error });
|
|
185
|
+
}
|
|
186
|
+
/* eslint-disable-next-line */
|
|
187
|
+
}, []);
|
|
188
|
+
|
|
189
|
+
const activateDefaultSheet = useCallback(() => {
|
|
190
|
+
if (!vizState.current.loaded || !viz.current) return;
|
|
191
|
+
const workbook = viz.current.getWorkbook();
|
|
192
|
+
const sheetnames = getSheetnames(viz.current);
|
|
193
|
+
const activeSheetName = getActiveSheetname(viz.current);
|
|
194
|
+
if (sheetnames.includes(sheetname) && sheetname !== activeSheetName) {
|
|
195
|
+
workbook.activateSheetAsync(sheetname).then(() => {
|
|
196
|
+
onVizStateUpdate(true, false, null);
|
|
197
|
+
});
|
|
198
|
+
} else {
|
|
199
|
+
onVizStateUpdate(true, false, null);
|
|
200
|
+
}
|
|
201
|
+
}, [onVizStateUpdate, sheetname]);
|
|
202
|
+
|
|
203
|
+
const clearData = () => {
|
|
204
|
+
onChangeBlock(block, {
|
|
205
|
+
...data,
|
|
206
|
+
filters: {},
|
|
207
|
+
parameters: {},
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const disposeViz = () => {
|
|
212
|
+
if (viz.current) {
|
|
213
|
+
viz.current.dispose();
|
|
214
|
+
viz.current = null;
|
|
215
|
+
}
|
|
216
|
+
onVizStateUpdate(false, false, null);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const initViz = () => {
|
|
220
|
+
try {
|
|
221
|
+
onVizStateUpdate(false, true, vizState.current.error);
|
|
222
|
+
viz.current = new tableau.Viz(vizEl.current, url, {
|
|
223
|
+
hideTabs,
|
|
224
|
+
hideToolbar,
|
|
225
|
+
toolbarPosition,
|
|
226
|
+
device: !!breakpointUrl ? device : 'desktop',
|
|
227
|
+
...data.filters,
|
|
228
|
+
...data.parameters,
|
|
229
|
+
...extraFilters,
|
|
230
|
+
...extraOptions,
|
|
231
|
+
onFirstInteractive: () => {
|
|
232
|
+
onVizStateUpdate(true, true, null);
|
|
233
|
+
setInitiateViz(false);
|
|
234
|
+
activateDefaultSheet();
|
|
235
|
+
if (viz.current && mode === 'edit' && !breakpointUrl) {
|
|
236
|
+
const sheetnames = getSheetnames(viz.current);
|
|
237
|
+
const activeSheetname = getActiveSheetname(viz.current);
|
|
238
|
+
const vizUrl = viz.current.getUrl();
|
|
239
|
+
const newData = {
|
|
240
|
+
...data,
|
|
241
|
+
};
|
|
242
|
+
newData.url = vizUrl;
|
|
243
|
+
if (!sheetname || !sheetnames.includes(sheetname)) {
|
|
244
|
+
newData.sheetname = activeSheetname;
|
|
245
|
+
}
|
|
246
|
+
if (newData.url !== url || newData.sheetname !== sheetname) {
|
|
247
|
+
onChangeBlock(block, {
|
|
248
|
+
...data,
|
|
249
|
+
...newData,
|
|
250
|
+
});
|
|
251
|
+
toast.success(
|
|
252
|
+
<Toast success title={'Tableau data updated'} content={null} />,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (viz.current && mode === 'edit') {
|
|
257
|
+
// Filter change event
|
|
258
|
+
viz.current.addEventListener(
|
|
259
|
+
tableau.TableauEventName.FILTER_CHANGE,
|
|
260
|
+
(event) => {
|
|
261
|
+
event.getFilterAsync().then((filter) => {
|
|
262
|
+
onFilterChange(filter);
|
|
263
|
+
});
|
|
264
|
+
},
|
|
265
|
+
);
|
|
266
|
+
// Parameter change event
|
|
267
|
+
viz.current.addEventListener(
|
|
268
|
+
tableau.TableauEventName.PARAMETER_VALUE_CHANGE,
|
|
269
|
+
(event) => {
|
|
270
|
+
event.getParameterAsync().then((parameter) => {
|
|
271
|
+
onParameterChange(parameter);
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
} catch (e) {
|
|
279
|
+
onVizStateUpdate(false, false, e.get_message());
|
|
280
|
+
setInitiateViz(false);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const addExtraFilters = (extraFilters) => {
|
|
285
|
+
const worksheets =
|
|
286
|
+
viz.current.getWorkbook().getActiveSheet().getWorksheets() || [];
|
|
287
|
+
|
|
288
|
+
worksheets.forEach((worksheet) => {
|
|
289
|
+
if (worksheet.getSheetType() === tableau.DashboardObjectType.WORKSHEET) {
|
|
290
|
+
Object.keys(extraFilters).forEach((filter) => {
|
|
291
|
+
if (!extraFilters[filter]) {
|
|
292
|
+
worksheet.clearFilterAsync(filter);
|
|
293
|
+
} else {
|
|
294
|
+
worksheet.applyFilterAsync(
|
|
295
|
+
filter,
|
|
296
|
+
extraFilters[filter],
|
|
297
|
+
tableau.FilterUpdateType.REPLACE,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const updateScale = () => {
|
|
306
|
+
const tableauEl = vizEl.current.querySelector('iframe');
|
|
307
|
+
const { sheetSize = {} } = viz.current.getVizSize() || {};
|
|
308
|
+
const vizWidth = sheetSize?.minSize?.width || 1;
|
|
309
|
+
const scale = Math.min(vizEl.current.clientWidth / vizWidth, 1);
|
|
310
|
+
tableauEl.style.transform = `scale(${scale})`;
|
|
311
|
+
window.requestAnimationFrame(() => {
|
|
312
|
+
if (vizEl.current) {
|
|
313
|
+
vizEl.current.style.height = `${scale * tableauEl.clientHeight}px`;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// Update refs
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
dataRef.current = data;
|
|
321
|
+
}, [data]);
|
|
322
|
+
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
if (!tableau) return;
|
|
325
|
+
if (url) {
|
|
326
|
+
disposeViz();
|
|
327
|
+
setInitiateViz(true);
|
|
328
|
+
} else {
|
|
329
|
+
disposeViz();
|
|
330
|
+
}
|
|
331
|
+
/* eslint-disable-next-line */
|
|
332
|
+
}, [tableau, url, hideTabs, hideToolbar, toolbarPosition]);
|
|
333
|
+
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
if (initiateViz && !loaded && !loading) {
|
|
336
|
+
initViz();
|
|
337
|
+
}
|
|
338
|
+
/* eslint-disable-next-line */
|
|
339
|
+
}, [loaded, loading, initiateViz]);
|
|
340
|
+
|
|
341
|
+
useEffect(() => {
|
|
342
|
+
if (vizState.current.loaded && viz.current) {
|
|
343
|
+
addExtraFilters(extraFilters);
|
|
344
|
+
}
|
|
345
|
+
/* eslint-disable-next-line */
|
|
346
|
+
}, [JSON.stringify(extraFilters)]);
|
|
347
|
+
|
|
348
|
+
useEffect(() => {
|
|
349
|
+
if (vizState.current.loaded && viz.current && autoScale) {
|
|
350
|
+
updateScale();
|
|
351
|
+
}
|
|
352
|
+
/* eslint-disable-next-line */
|
|
353
|
+
}, [loaded, screen?.page?.width]);
|
|
354
|
+
|
|
355
|
+
useEffect(() => {
|
|
356
|
+
if (vizState.current.loaded && viz.current) {
|
|
357
|
+
onVizStateUpdate(true, true, null);
|
|
358
|
+
activateDefaultSheet();
|
|
359
|
+
}
|
|
360
|
+
/* eslint-disable-next-line */
|
|
361
|
+
}, [sheetname]);
|
|
362
|
+
|
|
363
|
+
useImperativeHandle(
|
|
364
|
+
ref,
|
|
365
|
+
() => {
|
|
366
|
+
if (loaded) {
|
|
367
|
+
return viz.current;
|
|
368
|
+
}
|
|
369
|
+
if (loading) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
return;
|
|
373
|
+
},
|
|
374
|
+
[loaded, loading],
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
<div className="tableau-wrapper">
|
|
379
|
+
{loading && (
|
|
380
|
+
<div className="tableau-loader">
|
|
381
|
+
<span>Loading...</span>
|
|
382
|
+
</div>
|
|
383
|
+
)}
|
|
384
|
+
{!loading && mode === 'edit' && (
|
|
385
|
+
<Button
|
|
386
|
+
className="reload-tableau"
|
|
387
|
+
icon
|
|
388
|
+
secondary
|
|
389
|
+
compact
|
|
390
|
+
onClick={() => {
|
|
391
|
+
if (tableau && url) {
|
|
392
|
+
disposeViz();
|
|
393
|
+
setInitiateViz(true);
|
|
394
|
+
}
|
|
395
|
+
}}
|
|
396
|
+
>
|
|
397
|
+
<Icon name={resetSVG} size="24px" />
|
|
398
|
+
</Button>
|
|
399
|
+
)}
|
|
400
|
+
<TableauDebug
|
|
401
|
+
mode={props.mode}
|
|
402
|
+
data={data}
|
|
403
|
+
vizState={{ loaded, loading, error }}
|
|
404
|
+
url={url}
|
|
405
|
+
version={version}
|
|
406
|
+
clearData={clearData}
|
|
407
|
+
/>
|
|
408
|
+
<div
|
|
409
|
+
className={cx('tableau', `tableau-${version}`, {
|
|
410
|
+
'tableau-autoscale': autoScale,
|
|
411
|
+
})}
|
|
412
|
+
ref={vizEl}
|
|
413
|
+
/>
|
|
414
|
+
<div className="tableau-info">
|
|
415
|
+
{with_sources && loaded && <Sources sources={sources} />}
|
|
416
|
+
{with_download && loaded && <Download viz={viz.current} />}
|
|
417
|
+
{with_share && loaded && <Share viz={viz.current} />}
|
|
418
|
+
</div>
|
|
419
|
+
</div>
|
|
420
|
+
);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
export default connect(
|
|
424
|
+
(state) => ({
|
|
425
|
+
screen: state.screen,
|
|
426
|
+
}),
|
|
427
|
+
null,
|
|
428
|
+
null,
|
|
429
|
+
{ forwardRef: true },
|
|
430
|
+
)(Tableau);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import isUndefined from 'lodash/isUndefined';
|
|
2
|
+
|
|
3
|
+
export const getSheetnames = (viz) => {
|
|
4
|
+
if (!viz) return [];
|
|
5
|
+
let sheetnames = [];
|
|
6
|
+
const workbook = viz.getWorkbook();
|
|
7
|
+
workbook.getPublishedSheetsInfo().forEach((sheet) => {
|
|
8
|
+
const sheetName = sheet.getName();
|
|
9
|
+
sheetnames.push(sheetName);
|
|
10
|
+
});
|
|
11
|
+
return sheetnames;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const getSheetnamesChoices = (viz) => {
|
|
15
|
+
return getSheetnames(viz).map((sheet) => [sheet, sheet]);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const getActiveSheetname = (viz) => {
|
|
19
|
+
const workbook = viz.getWorkbook();
|
|
20
|
+
return workbook.getActiveSheet().getName();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const canChangeVizData = (viz, vizState) => {
|
|
24
|
+
// If viz is null it means that the viz is loading
|
|
25
|
+
// If viz is undefined it means that there is no viz nor it is loading
|
|
26
|
+
if (vizState?.loading) return false;
|
|
27
|
+
return !!viz || isUndefined(viz);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const getDevice = (breakpoints, width) => {
|
|
31
|
+
let device = 'desktop';
|
|
32
|
+
Object.keys(breakpoints).forEach((breakpoint) => {
|
|
33
|
+
if (
|
|
34
|
+
width <= breakpoints[breakpoint][0] &&
|
|
35
|
+
width >= breakpoints[breakpoint][1]
|
|
36
|
+
) {
|
|
37
|
+
device = breakpoint;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return device;
|
|
41
|
+
};
|