@eeacms/volto-tableau 3.0.7 → 4.0.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +23 -6
  2. package/Jenkinsfile +2 -0
  3. package/jest-addon.config.js +2 -2
  4. package/package.json +9 -4
  5. package/src/Blocks/EmbedTableauVisualization/Edit.jsx +31 -0
  6. package/src/Blocks/EmbedTableauVisualization/View.jsx +63 -0
  7. package/src/Blocks/EmbedTableauVisualization/index.js +30 -0
  8. package/src/Blocks/{EmbedEEATableauBlock → EmbedTableauVisualization}/schema.js +29 -13
  9. package/src/{TableauBlock → Blocks/TableauBlock}/Edit.jsx +3 -2
  10. package/src/{TableauBlock → Blocks/TableauBlock}/View.jsx +29 -40
  11. package/src/Blocks/TableauBlock/index.js +30 -0
  12. package/src/{TableauBlock → Blocks/TableauBlock}/schema.js +50 -2
  13. package/src/Blocks/index.js +9 -0
  14. package/src/Tableau/Tableau.jsx +263 -0
  15. package/src/Utils/Download/Download.jsx +72 -0
  16. package/src/Utils/Share/Share.jsx +21 -0
  17. package/src/Utils/Sources/Sources.jsx +66 -0
  18. package/src/Views/VisualizationView.jsx +12 -32
  19. package/src/Widgets/VisualizationWidget.jsx +60 -124
  20. package/src/Widgets/schema.js +62 -131
  21. package/src/helpers.js +15 -34
  22. package/src/hooks.js +18 -0
  23. package/src/icons/download.svg +5 -0
  24. package/src/index.js +4 -66
  25. package/src/less/tableau.less +131 -72
  26. package/src/less/tableau.variables +6 -13
  27. package/src/Blocks/EmbedEEATableauBlock/Edit.jsx +0 -56
  28. package/src/Blocks/EmbedEEATableauBlock/View.jsx +0 -71
  29. package/src/ConnectedTableau/ConnectedTableau.jsx +0 -29
  30. package/src/CustomWidgets/UrlParamsWidget.jsx +0 -29
  31. package/src/DownloadExtras/TableauDownload.jsx +0 -124
  32. package/src/DownloadExtras/TableauFullscreen.jsx +0 -78
  33. package/src/DownloadExtras/TableauShare.jsx +0 -81
  34. package/src/DownloadExtras/style.less +0 -152
  35. package/src/Sources/Sources.jsx +0 -50
  36. package/src/Sources/index.js +0 -3
  37. package/src/Sources/style.css +0 -7
  38. package/src/Tableau/View.jsx +0 -254
  39. package/src/Widgets/style.less +0 -8
  40. package/src/actions.js +0 -9
  41. package/src/constants.js +0 -1
  42. package/src/downloadHelpers/downloadHelpers.js +0 -25
  43. package/src/middleware.js +0 -39
  44. package/src/store.js +0 -72
@@ -1,56 +0,0 @@
1
- import React from 'react';
2
- import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm';
3
- import { SidebarPortal } from '@plone/volto/components';
4
- import { getContent } from '@plone/volto/actions';
5
- import View from './View';
6
- import Schema from './schema';
7
-
8
- import { connect } from 'react-redux';
9
- import { compose } from 'redux';
10
-
11
- const Edit = (props) => {
12
- const { block, onChangeBlock, id } = props;
13
- const data = React.useMemo(() => props.data || {}, [props.data]);
14
- const schema = React.useMemo(() => Schema(props), [props]);
15
-
16
- React.useEffect(() => {
17
- if (!Object.hasOwn(data, 'show_sources')) {
18
- onChangeBlock(block, {
19
- ...data,
20
- show_sources: true,
21
- });
22
- }
23
- }, [block, data, onChangeBlock]);
24
-
25
- return (
26
- <React.Fragment>
27
- <View {...props} data={data} id={id} mode="edit" />
28
- <SidebarPortal selected={props.selected}>
29
- <BlockDataForm
30
- block={block}
31
- title={schema.title}
32
- schema={schema}
33
- onChangeField={(id, value) => {
34
- props.onChangeBlock(block, {
35
- ...data,
36
- [id]: value,
37
- });
38
- }}
39
- formData={data}
40
- />
41
- </SidebarPortal>
42
- </React.Fragment>
43
- );
44
- };
45
-
46
- export default compose(
47
- connect(
48
- (state, props) => ({
49
- block_data: state.content.data,
50
- data_provenance: state.content.subrequests?.[props.id]?.data_provenance,
51
- }),
52
- {
53
- getContent,
54
- },
55
- ),
56
- )(React.memo(Edit));
@@ -1,71 +0,0 @@
1
- import React from 'react';
2
- import ConnectedTableau from '../../ConnectedTableau/ConnectedTableau';
3
- import { Sources } from '../../Sources';
4
- import { PrivacyProtection } from '@eeacms/volto-embed';
5
-
6
- import { getContent } from '@plone/volto/actions';
7
-
8
- import { connect } from 'react-redux';
9
- import { compose } from 'redux';
10
-
11
- const View = (props) => {
12
- const { data_provenance, tableau_visualization } = props || {};
13
- const data = React.useMemo(() => props.data || {}, [props.data]);
14
- const { vis_url = '' } = data;
15
- const show_sources = data?.show_sources ?? false;
16
-
17
- React.useEffect(() => {
18
- if (vis_url) {
19
- props.getContent(vis_url, null, props.id);
20
- }
21
- // eslint-disable-next-line react-hooks/exhaustive-deps
22
- }, [vis_url]);
23
-
24
- return (
25
- <div className="embed-container">
26
- <PrivacyProtection data={data} {...props}>
27
- {data?.vis_url ? (
28
- <>
29
- {tableau_visualization?.general?.url ? (
30
- <>
31
- <ConnectedTableau
32
- {...props.tableau_visualization}
33
- id={props.id}
34
- {...props}
35
- />
36
-
37
- {show_sources &&
38
- data_provenance &&
39
- data_provenance?.data?.data_provenance &&
40
- tableau_visualization ? (
41
- <Sources sources={data_provenance.data.data_provenance} />
42
- ) : show_sources ? (
43
- <div>Data provenance is not set in the visualization</div>
44
- ) : (
45
- ''
46
- )}
47
- </>
48
- ) : !tableau_visualization?.general?.url ? (
49
- <div>Url is not set in the visualization</div>
50
- ) : null}
51
- </>
52
- ) : (
53
- <div>Please select a visualization from block editor.</div>
54
- )}
55
- </PrivacyProtection>
56
- </div>
57
- );
58
- };
59
-
60
- export default compose(
61
- connect(
62
- (state, props) => ({
63
- data_provenance: state.content.subrequests?.[props.id],
64
- tableau_visualization:
65
- state.content.subrequests?.[props.id]?.data?.tableau_visualization_data,
66
- }),
67
- {
68
- getContent,
69
- },
70
- ),
71
- )(React.memo(View));
@@ -1,29 +0,0 @@
1
- import React from 'react';
2
- import Tableau from '@eeacms/volto-tableau/Tableau/View';
3
-
4
- const ConnectedTableau = (props) => {
5
- const [error, setError] = React.useState(null);
6
- const [loaded, setLoaded] = React.useState(null);
7
- return (
8
- <div className="tableau-block">
9
- {loaded && props.mode === 'edit' ? (
10
- <div className="tableau-info">
11
- <h3 className="tableau-version">== Tableau ==</h3>
12
- </div>
13
- ) : (
14
- ''
15
- )}
16
- <Tableau
17
- error={error}
18
- loaded={loaded}
19
- setError={setError}
20
- setLoaded={setLoaded}
21
- data={{ ...props?.general, ...props?.options, ...props?.extraOptions }}
22
- url={props?.general?.url}
23
- {...props}
24
- />
25
- </div>
26
- );
27
- };
28
-
29
- export default ConnectedTableau;
@@ -1,29 +0,0 @@
1
- import React from 'react';
2
- import '@eeacms/volto-tableau/less/tableau.less';
3
-
4
- const UrlParamsWidget = () => {
5
- const fields = [
6
- 'embed',
7
- 'isGuestRedirectFromVizportal',
8
- 'showShareOptions',
9
- 'jsdebug',
10
- 'sheetname',
11
- 'display_count',
12
- 'showVizHome',
13
- 'origin',
14
- 'device',
15
- ];
16
-
17
- return (
18
- <div className="availableFieldsContainer">
19
- <p className="availableFieldsTitle">Available Fields:</p>
20
- {fields.map((field) => (
21
- <p className="availableFields">
22
- <strong>{field}</strong>
23
- </p>
24
- ))}
25
- </div>
26
- );
27
- };
28
-
29
- export default UrlParamsWidget;
@@ -1,124 +0,0 @@
1
- import React from 'react';
2
- import { Popup, Button, Modal, Dropdown } from 'semantic-ui-react';
3
- import { Icon } from '@plone/volto/components';
4
- import downloadSVG from '@plone/volto/icons/download.svg';
5
-
6
- const TableauDownload = (props) => {
7
- const viz = props.viz || {};
8
-
9
- const [open, setOpen] = React.useState(false);
10
- const [
11
- isSheetSelectorCsvVisible,
12
- setIsSheetSelectorCsvVisible,
13
- ] = React.useState(false);
14
- const [
15
- isSheetSelectorExcelVisible,
16
- setIsSheetSelectorExcelVisible,
17
- ] = React.useState(false);
18
- const [availableSheets, setAvailableSheets] = React.useState([]);
19
-
20
- const initializeDropdown = () => {
21
- const sheets = viz
22
- ?.getWorkbook()
23
- ?.getActiveSheet()
24
- ?.getWorksheets()
25
- .map((sheet, index) => ({
26
- key: index,
27
- text: sheet.getName(),
28
- value: sheet.getName(),
29
- }));
30
- setAvailableSheets(sheets);
31
- };
32
-
33
- const exportImage = () => {
34
- setIsSheetSelectorCsvVisible(false);
35
- setIsSheetSelectorExcelVisible(false);
36
- viz.showExportImageDialog();
37
- };
38
-
39
- const exportToCSV = () => {
40
- setIsSheetSelectorExcelVisible(false);
41
- initializeDropdown();
42
- setIsSheetSelectorCsvVisible(true);
43
- };
44
-
45
- const exportToExcel = () => {
46
- setIsSheetSelectorCsvVisible(false);
47
- initializeDropdown();
48
- setIsSheetSelectorExcelVisible(true);
49
- };
50
-
51
- const handleSheetSelectionCsvChange = (e, data) => {
52
- viz.showExportCrossTabDialog(data.value);
53
- };
54
-
55
- const handleSheetSelectionExcelChange = (e, data) => {
56
- viz.exportCrossTabToExcel(data.value);
57
- };
58
-
59
- const hideDropdowns = () => {
60
- setIsSheetSelectorCsvVisible(false);
61
- setIsSheetSelectorExcelVisible(false);
62
- };
63
-
64
- return (
65
- <>
66
- <Popup
67
- basic
68
- className="tableau-download-dialog"
69
- position="top center"
70
- on="click"
71
- trigger={
72
- <div className="toolbar-button-wrapper">
73
- <Button
74
- className="toolbar-button"
75
- title="Download"
76
- onClick={hideDropdowns}
77
- >
78
- <Icon name={downloadSVG} size="26px" />
79
- </Button>
80
- <span className="btn-text">Save</span>
81
- </div>
82
- }
83
- >
84
- <Popup.Header>Download</Popup.Header>
85
- <Popup.Content>
86
- <p>Select your file format.</p>
87
- <Button onClick={exportImage}>Image</Button>
88
- <Button onClick={exportToCSV}>CSV</Button>
89
- {isSheetSelectorCsvVisible ? (
90
- <Dropdown
91
- placeholder="Sheet"
92
- selection
93
- options={availableSheets}
94
- onChange={handleSheetSelectionCsvChange}
95
- />
96
- ) : null}
97
- <Button onClick={exportToExcel}>Excel</Button>
98
- {isSheetSelectorExcelVisible ? (
99
- <Dropdown
100
- placeholder="Sheet"
101
- selection
102
- options={availableSheets}
103
- onChange={handleSheetSelectionExcelChange}
104
- />
105
- ) : null}
106
- </Popup.Content>
107
- </Popup>
108
-
109
- <Modal onClose={() => setOpen(false)} open={open}>
110
- <Modal.Content>
111
- Permissions are required to download the workbook.
112
- </Modal.Content>
113
-
114
- <Modal.Actions>
115
- <Button primary onClick={() => setOpen(false)}>
116
- OK
117
- </Button>
118
- </Modal.Actions>
119
- </Modal>
120
- </>
121
- );
122
- };
123
-
124
- export default TableauDownload;
@@ -1,78 +0,0 @@
1
- import React from 'react';
2
- import { Modal, Button } from 'semantic-ui-react';
3
- import { Icon } from '@plone/volto/components';
4
- import { useHistory, useLocation } from 'react-router-dom';
5
- import fullscreenSVG from '@plone/volto/icons/fullscreen.svg';
6
-
7
- import config from '@plone/volto/registry';
8
-
9
- const TableauFullscreen = (props) => {
10
- const tableau_url = props.data.url;
11
- const modalHash = props?.item.getId + '_preview';
12
- const [open, setOpen] = React.useState(false);
13
- const history = useHistory();
14
- const location = useLocation();
15
- const {
16
- blocks: { blocksConfig },
17
- } = config;
18
- const TableauBlockView = blocksConfig.tableau_block.view;
19
-
20
- React.useEffect(() => {
21
- if (location.hash.includes(modalHash)) {
22
- setOpen(true);
23
- } else {
24
- setOpen(false);
25
- }
26
- }, [location, modalHash]);
27
-
28
- const closeModal = () => {
29
- history.push({
30
- hash: '',
31
- });
32
- setOpen(false);
33
- };
34
-
35
- return (
36
- <>
37
- <div className="toolbar-button-wrapper">
38
- <Button
39
- className="toolbar-button"
40
- title="Full Screen"
41
- onClick={() => {
42
- setOpen(true);
43
- if (props.item) {
44
- history.push({
45
- hash: props.item.getId + '_preview',
46
- });
47
- }
48
- }}
49
- >
50
- <Icon name={fullscreenSVG} size="23px" />
51
- </Button>
52
- <span className="btn-text">Enlarge</span>
53
- </div>
54
-
55
- <Modal
56
- className="tableau-fullscreen"
57
- onClose={closeModal}
58
- onOpen={() => setOpen(true)}
59
- open={open}
60
- >
61
- <Modal.Content>
62
- <TableauBlockView
63
- {...props}
64
- data={{ url: tableau_url, hideToolbar: true }}
65
- ></TableauBlockView>
66
- </Modal.Content>
67
-
68
- <Modal.Actions>
69
- <Button primary onClick={closeModal}>
70
- Close
71
- </Button>
72
- </Modal.Actions>
73
- </Modal>
74
- </>
75
- );
76
- };
77
-
78
- export default TableauFullscreen;
@@ -1,81 +0,0 @@
1
- import React from 'react';
2
- import { Popup, Tab, Button, Menu, Input } from 'semantic-ui-react';
3
- import { Icon } from '@plone/volto/components';
4
- import useCopyToClipboard from '../downloadHelpers/downloadHelpers';
5
-
6
- import shareSVG from '@plone/volto/icons/share.svg';
7
- import linkSVG from '@plone/volto/icons/link.svg';
8
-
9
- import cx from 'classnames';
10
-
11
- const TableauShare = (props) => {
12
- const tableau_url = props.data.url;
13
-
14
- const CopyUrlButton = ({ url, buttonText }) => {
15
- const [copyUrlStatus, copyUrl] = useCopyToClipboard(url);
16
-
17
- if (copyUrlStatus === 'copied') {
18
- buttonText = 'Copied!';
19
- } else if (copyUrlStatus === 'failed') {
20
- buttonText = 'Copy failed. Please try again.';
21
- }
22
-
23
- return (
24
- <Button
25
- primary
26
- onClick={copyUrl}
27
- className={cx('copy-button', {
28
- 'green-button': copyUrlStatus === 'copied',
29
- })}
30
- >
31
- {buttonText}
32
- </Button>
33
- );
34
- };
35
-
36
- const panes = [
37
- {
38
- menuItem: (
39
- <Menu.Item key="location">
40
- <span className="nav-dot">
41
- <Icon name={linkSVG} size="24px" />
42
- </span>
43
- <span className="nav-dot-title">URL</span>
44
- </Menu.Item>
45
- ),
46
- render: () => (
47
- <Tab.Pane>
48
- <Input defaultValue={tableau_url} />
49
- <CopyUrlButton url={tableau_url} buttonText="Copy sharing URL" />
50
- </Tab.Pane>
51
- ),
52
- },
53
- ];
54
-
55
- return (
56
- <Popup
57
- basic
58
- className="tableau-share-dialog"
59
- position="top center"
60
- on="click"
61
- trigger={
62
- <div className="toolbar-button-wrapper">
63
- <Button className="toolbar-button" title="Share">
64
- <Icon name={shareSVG} size="26px" />
65
- </Button>
66
- <span className="btn-text">Share</span>
67
- </div>
68
- }
69
- >
70
- <Popup.Header>Share Visualization</Popup.Header>
71
- <Popup.Content>
72
- <Tab
73
- menu={{ secondary: true, pointing: true, fluid: true }}
74
- panes={panes}
75
- />
76
- </Popup.Content>
77
- </Popup>
78
- );
79
- };
80
-
81
- export default TableauShare;
@@ -1,152 +0,0 @@
1
- .dashboard-wrapper {
2
- display: flex;
3
- flex-direction: row;
4
- flex-wrap: wrap;
5
- background-color: #f5f5f5;
6
-
7
- .tableau-block {
8
- position: relative;
9
- display: flex;
10
- flex: 1 1;
11
- flex-direction: column;
12
- padding: 1em 0.5em;
13
- }
14
-
15
- .toolbar-button {
16
- position: relative;
17
- width: 32px;
18
- height: 32px;
19
- padding: 2px !important;
20
- background-color: #ff421b !important;
21
- border-radius: 3px !important;
22
- color: white !important;
23
-
24
- &:hover {
25
- opacity: 0.6;
26
- }
27
-
28
- svg {
29
- position: absolute;
30
- top: 50%;
31
- left: 50%;
32
- margin: 0 !important;
33
- fill: #fff !important;
34
- -webkit-transform: translate(-50%, -50%);
35
- transform: translate(-50%, -50%);
36
- }
37
- }
38
- }
39
-
40
- .tableau-download-dialog {
41
- max-width: 282px !important;
42
-
43
- .header {
44
- padding: 0.5em 0 !important;
45
- }
46
-
47
- button {
48
- display: block;
49
- width: 100%;
50
- margin: 5px 0 !important;
51
- }
52
- }
53
-
54
- .tableau-share-dialog {
55
- min-width: 400px !important;
56
-
57
- .ui.secondary.pointing.menu .item {
58
- display: flex;
59
- flex-direction: column;
60
- padding: 10px 25px;
61
- }
62
-
63
- .nav-dot-title {
64
- margin-top: 5px;
65
- font-size: 14px;
66
- }
67
-
68
- .ui.secondary.pointing.menu .active.item {
69
- border-color: #09b;
70
-
71
- .nav-dot-title {
72
- font-weight: 500;
73
- }
74
-
75
- .nav-dot {
76
- opacity: 1;
77
- }
78
- }
79
-
80
- .nav-dot {
81
- display: flex;
82
- width: 37px;
83
- height: 37px;
84
- align-items: center;
85
- justify-content: center;
86
- margin: 8px auto;
87
- background-color: #09b;
88
- border-radius: 50%;
89
- opacity: 0.6;
90
-
91
- svg {
92
- fill: #fff !important;
93
- }
94
- }
95
-
96
- .ui.secondary.pointing.menu .active.item:hover {
97
- border-color: #09b;
98
- }
99
-
100
- .ui.tab {
101
- .ui.input {
102
- display: block;
103
- margin-bottom: 1em;
104
-
105
- input {
106
- width: 100%;
107
- }
108
- }
109
-
110
- textarea {
111
- display: block;
112
- width: 100%;
113
- height: 100px;
114
- padding: 6px 12px;
115
- border: 1px solid grey;
116
- margin-bottom: 1.5em;
117
- color: #696969;
118
- font-family: 'Lucida Console', Monaco, monospace;
119
- font-size: 13px;
120
- }
121
- }
122
- }
123
-
124
- .copy-button {
125
- margin-top: 1rem !important;
126
- }
127
-
128
- .copy-button.green-button {
129
- background-color: #269b65 !important;
130
- }
131
-
132
- .toolbar-button-wrapper {
133
- display: flex;
134
- flex-direction: column;
135
- text-align: center;
136
-
137
- .btn-text {
138
- font-family: Roboto, Helvetica Neue, Arial, Helvetica, sans-serif;
139
- font-size: 12px;
140
- font-weight: 400;
141
- line-height: 20px;
142
- }
143
-
144
- .ui.button.toolbar-button {
145
- margin: 0 7px !important;
146
- }
147
- }
148
-
149
- .tableau-icons {
150
- display: flex;
151
- flex-direction: row;
152
- }
@@ -1,50 +0,0 @@
1
- /* eslint-disable jsx-a11y/no-static-element-interactions */
2
- /* eslint-disable jsx-a11y/click-events-have-key-events */
3
- /* eslint-disable jsx-a11y/anchor-is-valid */
4
-
5
- import React from 'react';
6
- import { UniversalLink, Icon } from '@plone/volto/components';
7
-
8
- import rightKeySVG from '@plone/volto/icons/right-key.svg';
9
- import downKeySVG from '@plone/volto/icons/down-key.svg';
10
-
11
- import './style.css';
12
-
13
- const SourcesWidget = ({ sources }) => {
14
- const [expand, setExpand] = React.useState(true);
15
- return (
16
- <div>
17
- <a className="embed-sources-header" onClick={() => setExpand(!expand)}>
18
- <h3>
19
- <Icon
20
- name={expand ? downKeySVG : rightKeySVG}
21
- title={expand ? 'Collapse' : 'Expand'}
22
- size="17px"
23
- />
24
- Sources:
25
- </h3>
26
- </a>
27
- {expand && (
28
- <ul>
29
- {sources &&
30
- sources.data &&
31
- sources.data.map((param, i) =>
32
- param.link ? (
33
- <li key={i} className="embed-source-param">
34
- <UniversalLink
35
- className="embed-sources-param-title"
36
- href={param.link}
37
- >
38
- {param.title}
39
- </UniversalLink>
40
- , {param.organisation}
41
- </li>
42
- ) : null,
43
- )}
44
- </ul>
45
- )}
46
- </div>
47
- );
48
- };
49
-
50
- export default SourcesWidget;
@@ -1,3 +0,0 @@
1
- import Sources from './Sources';
2
-
3
- export { Sources };
@@ -1,7 +0,0 @@
1
- .embed-sources-header {
2
- cursor: pointer;
3
- }
4
-
5
- .embed-sources-param-description {
6
- margin-left: 5px;
7
- }