@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.
Files changed (48) hide show
  1. package/CHANGELOG.md +20 -7
  2. package/Jenkinsfile +2 -0
  3. package/jest-addon.config.js +2 -2
  4. package/package.json +1 -1
  5. package/src/{TableauBlock → Blocks/EmbedTableauVisualization}/Edit.jsx +7 -7
  6. package/src/Blocks/EmbedTableauVisualization/View.jsx +66 -0
  7. package/src/Blocks/EmbedTableauVisualization/index.js +30 -0
  8. package/src/Blocks/{EmbedEEATableauBlock → EmbedTableauVisualization}/schema.js +32 -13
  9. package/src/Blocks/TableauBlock/Edit.jsx +41 -0
  10. package/src/Blocks/TableauBlock/View.jsx +101 -0
  11. package/src/Blocks/TableauBlock/index.js +30 -0
  12. package/src/Blocks/TableauBlock/schema.js +224 -0
  13. package/src/Blocks/index.js +9 -0
  14. package/src/Tableau/Tableau.jsx +430 -0
  15. package/src/Tableau/helpers.js +41 -0
  16. package/src/Utils/Download/Download.jsx +72 -0
  17. package/src/Utils/JsonCodeSnippet/JsonCodeSnippet.jsx +48 -0
  18. package/src/Utils/Share/Share.jsx +21 -0
  19. package/src/Utils/Sources/Sources.jsx +66 -0
  20. package/src/Views/VisualizationView.jsx +18 -32
  21. package/src/Widgets/VisualizationWidget.jsx +92 -122
  22. package/src/Widgets/schema.js +88 -115
  23. package/src/helpers.js +15 -34
  24. package/src/hooks.js +18 -0
  25. package/src/icons/download.svg +5 -0
  26. package/src/index.js +4 -66
  27. package/src/less/tableau.less +172 -70
  28. package/src/less/tableau.variables +7 -13
  29. package/src/Blocks/EmbedEEATableauBlock/Edit.jsx +0 -56
  30. package/src/Blocks/EmbedEEATableauBlock/View.jsx +0 -74
  31. package/src/ConnectedTableau/ConnectedTableau.jsx +0 -29
  32. package/src/CustomWidgets/UrlParamsWidget.jsx +0 -29
  33. package/src/DownloadExtras/TableauDownload.jsx +0 -124
  34. package/src/DownloadExtras/TableauFullscreen.jsx +0 -78
  35. package/src/DownloadExtras/TableauShare.jsx +0 -81
  36. package/src/DownloadExtras/style.less +0 -152
  37. package/src/Sources/Sources.jsx +0 -50
  38. package/src/Sources/index.js +0 -3
  39. package/src/Sources/style.css +0 -7
  40. package/src/Tableau/View.jsx +0 -254
  41. package/src/TableauBlock/View.jsx +0 -109
  42. package/src/TableauBlock/schema.js +0 -124
  43. package/src/Widgets/style.less +0 -8
  44. package/src/actions.js +0 -9
  45. package/src/constants.js +0 -1
  46. package/src/downloadHelpers/downloadHelpers.js +0 -25
  47. package/src/middleware.js +0 -39
  48. package/src/store.js +0 -72
@@ -0,0 +1,72 @@
1
+ import React from 'react';
2
+ import { Button, Popup } from 'semantic-ui-react';
3
+ import cx from 'classnames';
4
+ import { Icon } from '@plone/volto/components';
5
+ import downloadSVG from '@eeacms/volto-tableau/icons/download.svg';
6
+
7
+ const Download = ({ viz }) => {
8
+ const [expanded, setExpanded] = React.useState(false);
9
+ const popupRef = React.useRef();
10
+
11
+ return (
12
+ <Popup
13
+ popper={{ id: 'tableau-download-popup' }}
14
+ trigger={
15
+ <div className="tableau-download-container">
16
+ <button className={cx('tableau-download-button', { expanded })}>
17
+ <span>Download data</span>
18
+ <Icon name={downloadSVG} size="24px" />
19
+ </button>
20
+ </div>
21
+ }
22
+ position="bottom left"
23
+ on="click"
24
+ onClose={() => {
25
+ setExpanded(false);
26
+ }}
27
+ onOpen={() => {
28
+ setExpanded(true);
29
+ }}
30
+ ref={popupRef}
31
+ >
32
+ <Button
33
+ className="primary"
34
+ onClick={() => {
35
+ viz.showExportImageDialog();
36
+ popupRef.current.triggerRef.current.click();
37
+ }}
38
+ >
39
+ Image
40
+ </Button>
41
+ <Button
42
+ className="primary"
43
+ onClick={() => {
44
+ viz.showExportPDFDialog();
45
+ popupRef.current.triggerRef.current.click();
46
+ }}
47
+ >
48
+ PDF
49
+ </Button>
50
+ <Button
51
+ className="primary"
52
+ onClick={() => {
53
+ viz.showExportCrossTabDialog();
54
+ popupRef.current.triggerRef.current.click();
55
+ }}
56
+ >
57
+ CSV
58
+ </Button>
59
+ <Button
60
+ className="primary"
61
+ onClick={() => {
62
+ viz.exportCrossTabToExcel();
63
+ popupRef.current.triggerRef.current.click();
64
+ }}
65
+ >
66
+ Excel
67
+ </Button>
68
+ </Popup>
69
+ );
70
+ };
71
+
72
+ export default Download;
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import isObject from 'lodash/isObject';
3
+ import isArray from 'lodash/isArray';
4
+ import isString from 'lodash/isString';
5
+ import isBoolean from 'lodash/isBoolean';
6
+ import isNull from 'lodash/isNull';
7
+ import isUndefined from 'lodash/isUndefined';
8
+
9
+ const JsonCodeSnippet = ({ obj, depth = 1 }) => {
10
+ const keys = Object.keys(obj);
11
+ return (
12
+ <>
13
+ <span className="left-curly-brace">&#123;</span>
14
+ {!!keys.length && <br />}
15
+ {keys.map((key) => {
16
+ const value = obj[key];
17
+
18
+ return (
19
+ <React.Fragment key={`${key}-depth`}>
20
+ <span className="key" style={{ marginLeft: `${depth}rem` }}>
21
+ "{key}":
22
+ </span>
23
+ <span className="value">
24
+ &nbsp;
25
+ {isObject(value) && !isArray(value) && (
26
+ <JsonCodeSnippet obj={value} depth={depth + 1} />
27
+ )}
28
+ {isArray(value) && JSON.stringify(value)}
29
+ {isString(value) && <>"{value}"</>}
30
+ {isBoolean(value) && <>{value}</>}
31
+ {isNull(value) && <>null</>}
32
+ {isUndefined(value) && <>undefined</>}
33
+ </span>
34
+ <br />
35
+ </React.Fragment>
36
+ );
37
+ })}
38
+ <span
39
+ className="right-curly-brace"
40
+ style={{ marginLeft: !!keys.length ? `${depth - 1}rem` : 0 }}
41
+ >
42
+ &#125;
43
+ </span>
44
+ </>
45
+ );
46
+ };
47
+
48
+ export default JsonCodeSnippet;
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { Icon } from '@plone/volto/components';
3
+ import shareSVG from '@plone/volto/icons/share.svg';
4
+
5
+ const Share = ({ viz }) => {
6
+ return (
7
+ <div className="tableau-share-container">
8
+ <button
9
+ className="tableau-share-button"
10
+ onClick={() => {
11
+ viz.showShareDialog();
12
+ }}
13
+ >
14
+ <span>Share</span>
15
+ <Icon name={shareSVG} size="24px" />
16
+ </button>
17
+ </div>
18
+ );
19
+ };
20
+
21
+ export default Share;
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import cx from 'classnames';
3
+ import { Popup } from 'semantic-ui-react';
4
+ import { UniversalLink } from '@plone/volto/components';
5
+
6
+ const Link = ({ children, ...props }) => {
7
+ if (props.href) {
8
+ return <UniversalLink {...props}>{children}</UniversalLink>;
9
+ }
10
+ return <span {...props}>{children}</span>;
11
+ };
12
+
13
+ const Source = ({ source }) => {
14
+ return (
15
+ <>
16
+ <Link className="embed-sources-param-title" href={source.link}>
17
+ {source.title},{' '}
18
+ <span className="embed-sources-param-description">
19
+ {source.organisation}
20
+ </span>
21
+ </Link>
22
+ </>
23
+ );
24
+ };
25
+
26
+ const SourcesWidget = ({ sources }) => {
27
+ const [expanded, setExpanded] = React.useState(false);
28
+
29
+ return (
30
+ <div className="tableau-sources-container">
31
+ <Popup
32
+ content={
33
+ sources?.length ? (
34
+ <ol className="sources-list">
35
+ {sources?.map((source, index) => {
36
+ return (
37
+ <li key={index}>
38
+ <Source source={source} />
39
+ </li>
40
+ );
41
+ })}
42
+ </ol>
43
+ ) : (
44
+ <p>Data provenance is not set for this visualization.</p>
45
+ )
46
+ }
47
+ position="bottom left"
48
+ popper={{ id: 'tableau-sources-popup' }}
49
+ trigger={
50
+ <button className={cx('tableau-sources-button', { expanded })}>
51
+ Sources
52
+ </button>
53
+ }
54
+ on="click"
55
+ onClose={() => {
56
+ setExpanded(false);
57
+ }}
58
+ onOpen={() => {
59
+ setExpanded(true);
60
+ }}
61
+ />
62
+ </div>
63
+ );
64
+ };
65
+
66
+ export default SourcesWidget;
@@ -1,41 +1,27 @@
1
1
  import React from 'react';
2
- import TableauView from '../TableauBlock/View';
2
+ import { Container } from 'semantic-ui-react';
3
+ import config from '@plone/volto/registry';
4
+ import Tableau from '@eeacms/volto-tableau/Tableau/Tableau';
3
5
 
4
6
  const VisualizationView = (props) => {
5
- const [tableauError, setTableauError] = React.useState('');
6
7
  const { content = {} } = props;
7
- const { tableau_visualization_data = {} } = content;
8
+ const { tableau_visualization = {}, data_provenance = {} } = content;
8
9
 
9
- const TableauNotDisplayed = () => {
10
- return (
11
- <div className="tableau-block not_displayed_tableau">
12
- <div className="tableau-info">
13
- {!tableau_visualization_data.general?.url ? (
14
- <p className="tableau-error">URL required</p>
15
- ) : tableauError ? (
16
- <p className="tableau-error">{tableauError}</p>
17
- ) : (
18
- ''
19
- )}
20
- </div>
21
- </div>
22
- );
23
- };
24
10
  return (
25
- <div>
26
- {!tableau_visualization_data?.general?.url || tableauError ? (
27
- <TableauNotDisplayed />
28
- ) : (
29
- <TableauView
30
- setTableauError={setTableauError}
31
- data={{
32
- ...tableau_visualization_data.general,
33
- ...tableau_visualization_data.options,
34
- ...tableau_visualization_data.extraOptions,
35
- }}
36
- />
37
- )}
38
- </div>
11
+ <Container id="page-document">
12
+ <Tableau
13
+ data={{
14
+ ...tableau_visualization,
15
+ with_sources: true,
16
+ with_download: true,
17
+ with_share: true,
18
+ }}
19
+ sources={data_provenance.data || []}
20
+ breakpoints={
21
+ config.blocks.blocksConfig.embed_tableau_visualization.breakpoints
22
+ }
23
+ />
24
+ </Container>
39
25
  );
40
26
  };
41
27
 
@@ -1,77 +1,107 @@
1
1
  import React from 'react';
2
2
  import { Modal, Button, Grid } from 'semantic-ui-react';
3
- import '@eeacms/volto-tableau/less/tableau.less';
4
3
  import config from '@plone/volto/registry';
5
-
6
4
  import { FormFieldWrapper, InlineForm } from '@plone/volto/components';
5
+ import Tableau from '@eeacms/volto-tableau/Tableau/Tableau';
6
+ import getSchema from './schema';
7
7
 
8
- import TableauView from '../TableauBlock/View';
9
- import Schema from './schema';
8
+ import '@eeacms/volto-tableau/less/tableau.less';
10
9
 
11
10
  const VisualizationWidget = (props) => {
11
+ const viz = React.useRef();
12
+ const [vizState, setVizState] = React.useState({
13
+ loaded: false,
14
+ loading: false,
15
+ error: null,
16
+ });
12
17
  const [open, setOpen] = React.useState(false);
13
- const { onChange = {}, id } = props;
14
-
15
- const block = React.useMemo(() => props.block, [props.block]);
16
- const value = React.useMemo(() => props.value, [props.value]);
18
+ const [value, setValue] = React.useState(props.value);
17
19
 
18
- const [intValue, setIntValue] = React.useState(value);
19
- const [tableauError, setTableauError] = React.useState('');
20
+ const schema = React.useMemo(() => getSchema(config, viz.current, vizState), [
21
+ vizState,
22
+ ]);
20
23
 
21
- const dataForm = { tableau_data: intValue };
24
+ const extraOptions = React.useMemo(() => {
25
+ const options = {};
26
+ (value.staticParameters || []).forEach((parameter) => {
27
+ if (parameter.field) {
28
+ options[parameter.field] = parameter.value;
29
+ }
30
+ });
31
+ return options;
32
+ }, [value]);
22
33
 
23
34
  const handleApplyChanges = () => {
24
- onChange(id, intValue);
35
+ props.onChange(props.id, value);
25
36
  setOpen(false);
26
37
  };
27
38
 
28
39
  const handleClose = () => {
29
- setIntValue(value);
40
+ setValue(props.value);
30
41
  setOpen(false);
31
42
  };
32
43
 
33
- const handleChangeField = (val) => {
34
- setIntValue(val);
35
- };
36
-
37
- const TableauNotDisplayed = () => {
38
- return (
39
- <div className="tableau-block not_displayed_tableau">
40
- <div className="tableau-info">
41
- {intValue && intValue.general && !intValue.general.url ? (
42
- <p className="tableau-error">URL required</p>
43
- ) : tableauError ? (
44
- <p className="tableau-error">{tableauError}</p>
45
- ) : (
46
- ''
47
- )}
48
- </div>
49
- </div>
50
- );
51
- };
52
-
53
- let schema = Schema(config);
54
-
55
- React.useEffect(() => {
56
- if (!intValue?.options) {
57
- setIntValue({
58
- ...intValue,
59
- options: {
60
- autoScale: false,
61
- hideTabs: false,
62
- hideToolbar: false,
63
- toolbarPosition: 'Top',
64
- },
65
- });
66
- }
67
- // eslint-disable-next-line react-hooks/exhaustive-deps
68
- }, []);
69
-
70
44
  return (
71
45
  <FormFieldWrapper {...props}>
72
- <div className="wrapper">
46
+ <Modal id="tableau-editor-modal" open={open}>
47
+ <Modal.Content scrolling>
48
+ <Grid>
49
+ <Grid.Column
50
+ mobile={4}
51
+ tablet={4}
52
+ computer={4}
53
+ className="tableau-editor-column"
54
+ >
55
+ <InlineForm
56
+ block={props.block}
57
+ schema={schema}
58
+ onChangeField={(id, fieldValue) => {
59
+ setValue((value) => ({
60
+ ...value,
61
+ [id]: fieldValue,
62
+ }));
63
+ }}
64
+ formData={value}
65
+ />
66
+ </Grid.Column>
67
+ <Grid.Column
68
+ mobile={8}
69
+ tablet={8}
70
+ computer={8}
71
+ className="tableau-visualization-column"
72
+ >
73
+ <Tableau
74
+ ref={viz}
75
+ data={value}
76
+ mode="edit"
77
+ breakpoints={
78
+ config.blocks.blocksConfig.embed_tableau_visualization
79
+ .breakpoints
80
+ }
81
+ extraOptions={extraOptions}
82
+ setVizState={setVizState}
83
+ onChangeBlock={(_, newValue) => {
84
+ setValue(newValue);
85
+ }}
86
+ />
87
+ </Grid.Column>
88
+ </Grid>
89
+ </Modal.Content>
90
+ <Modal.Actions>
91
+ <Grid>
92
+ <Grid.Row>
93
+ <div className="map-edit-actions-container">
94
+ <Button onClick={handleClose}>Close</Button>
95
+ <Button color="green" onClick={handleApplyChanges}>
96
+ Apply changes
97
+ </Button>
98
+ </div>
99
+ </Grid.Row>
100
+ </Grid>
101
+ </Modal.Actions>
102
+ </Modal>
103
+ <div>
73
104
  <Button
74
- floated="right"
75
105
  size="tiny"
76
106
  onClick={(e) => {
77
107
  e.preventDefault();
@@ -82,76 +112,16 @@ const VisualizationWidget = (props) => {
82
112
  Open Tableau Editor
83
113
  </Button>
84
114
  </div>
85
-
86
- {open && (
87
- <Modal
88
- id="tableau-editor-modal"
89
- style={{ width: '95% !important' }}
90
- open={true}
91
- >
92
- <Modal.Content scrolling>
93
- <Grid stackable reversed="mobile vertically tablet vertically">
94
- <Grid.Column
95
- mobile={12}
96
- tablet={12}
97
- computer={5}
98
- className="tableau-editor-column"
99
- >
100
- <InlineForm
101
- block={block}
102
- schema={schema}
103
- onChangeField={(id, value) => {
104
- handleChangeField(value);
105
- }}
106
- formData={dataForm}
107
- />
108
- </Grid.Column>
109
- <Grid.Column mobile={12} tablet={12} computer={7}>
110
- {(intValue && intValue.general && !intValue.general.url) ||
111
- tableauError ? (
112
- <TableauNotDisplayed />
113
- ) : (
114
- <div className="tableau-container">
115
- <TableauView
116
- setTableauError={setTableauError}
117
- data={{
118
- ...intValue?.general,
119
- ...intValue?.options,
120
- ...intValue?.extraOptions,
121
- }}
122
- />
123
- </div>
124
- )}
125
- </Grid.Column>
126
- </Grid>
127
- </Modal.Content>
128
- <Modal.Actions>
129
- <Grid>
130
- <Grid.Row>
131
- <div className="map-edit-actions-container">
132
- <Button onClick={handleClose}>Close</Button>
133
- <Button color="green" onClick={handleApplyChanges}>
134
- Apply changes
135
- </Button>
136
- </div>
137
- </Grid.Row>
138
- </Grid>
139
- </Modal.Actions>
140
- </Modal>
141
- )}
142
- {(intValue && intValue.general && !intValue.general.url) ||
143
- tableauError ? (
144
- <TableauNotDisplayed />
145
- ) : (
146
- <TableauView
147
- setTableauError={setTableauError}
148
- data={{
149
- ...value?.general,
150
- ...value?.options,
151
- ...value?.extraOptions,
152
- }}
153
- />
154
- )}
115
+ <Tableau
116
+ data={{
117
+ ...props.value,
118
+ autoScale: true,
119
+ }}
120
+ breakpoints={
121
+ config.blocks.blocksConfig.embed_tableau_visualization.breakpoints
122
+ }
123
+ extraOptions={extraOptions}
124
+ />
155
125
  </FormFieldWrapper>
156
126
  );
157
127
  };