Skip to content

Commit

Permalink
Merge pull request #80 from eea/develop
Browse files Browse the repository at this point in the history
Extract parameters from tableau url, ref #279989
  • Loading branch information
avoinea authored Nov 14, 2024
2 parents e5161d6 + 8a20994 commit 5fc1c78
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 92 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. Dates are d

Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).

### [8.1.2](https://github.com/eea/volto-tableau/compare/8.1.1...8.1.2) - 14 November 2024

#### :hammer_and_wrench: Others

- add test [Miu Razvan - [`862b32c`](https://github.com/eea/volto-tableau/commit/862b32c6d2760cc2169eef9f900491e6afb9a00a)]
- add VisualizationView test [Miu Razvan - [`b4bea0a`](https://github.com/eea/volto-tableau/commit/b4bea0a6fc9814927b0b471ceebf4a9d4bfc50f2)]
- Disable tableau editor actions while loading the tableau [Miu Razvan - [`9f33c3c`](https://github.com/eea/volto-tableau/commit/9f33c3c2c3bcb0a3e36ba21e40cbb86f2dfc6150)]
- Extract parameters from tableau url, ref #279989 [Miu Razvan - [`f3c00d1`](https://github.com/eea/volto-tableau/commit/f3c00d124bd9417f44d9e9a6b054687de0859bbd)]
### [8.1.1](https://github.com/eea/volto-tableau/compare/8.1.0...8.1.1) - 11 October 2024

#### :bug: Bug Fixes
Expand Down
11 changes: 11 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ global.store = mockStore({
const mockReactRouter = jest.requireActual('react-router');
const mockSemanticComponents = jest.requireActual('semantic-ui-react');
const mockComponents = jest.requireActual('@plone/volto/components');
const config = jest.requireActual('@plone/volto/registry').default;

config.blocks.blocksConfig = {
embed_tableau_visualization: {
breakpoints: {
desktop: [Infinity, 992],
tablet: [991, 768],
phone: [767, 0],
},
},
};

jest.mock('react-router', () => {
return {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@eeacms/volto-tableau",
"version": "8.1.1",
"version": "8.1.2",
"description": "@eeacms/volto-tableau: Volto add-on",
"main": "src/index.js",
"author": "European Environment Agency: IDM2 A-Team",
Expand Down
4 changes: 2 additions & 2 deletions src/Blocks/EmbedTableauVisualization/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const staticParameters = {
required: [],
};

const schema = (props) => {
const getSchema = (props) => {
return {
title: 'Embed Dashboard (Tableau)',
fieldsets: [
Expand Down Expand Up @@ -182,4 +182,4 @@ const schema = (props) => {
};
};

export default schema;
export default getSchema;
120 changes: 79 additions & 41 deletions src/Tableau/Tableau.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
toInteger,
toNumber,
} from 'lodash';
import qs from 'qs';
import cx from 'classnames';
import { Button } from 'semantic-ui-react';
import { Toast, Icon } from '@plone/volto/components';
Expand All @@ -44,6 +45,12 @@ import resetSVG from '@plone/volto/icons/reset.svg';

import '@eeacms/volto-embed/Toolbar/styles.less';

function decodeString(str) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = str;
return tempDiv.textContent || tempDiv.innerText || '';
}

function getHeight(height) {
const asNumber = isNumber(Number(height)) && !isNaN(Number(height));
if (asNumber) {
Expand All @@ -52,6 +59,22 @@ function getHeight(height) {
return height;
}

function getTableauValue(value, dataType) {
if (dataType === 'string' && !isString(value)) {
return toString(value);
}
if (dataType === 'integer' && !isInteger(value)) {
return toInteger(value);
}
if (dataType === 'float' && !isNumber(value)) {
return toNumber(value);
}
if (dataType === 'boolean' && !isBoolean(value)) {
return !!value;
}
return value;
}

const TableauDebug = ({ mode, data, vizState, url, version, clearData }) => {
const { loaded, error } = vizState;
const { filters = {}, parameters = {} } = data;
Expand Down Expand Up @@ -136,6 +159,7 @@ const Tableau = forwardRef((props, ref) => {
with_enlarge = true,
tableau_height,
} = data;

const device = useMemo(
() => getDevice(breakpoints, screen.page?.width || Infinity),
[breakpoints, screen],
Expand Down Expand Up @@ -214,46 +238,45 @@ const Tableau = forwardRef((props, ref) => {
}
};

const onVizStateUpdate = useCallback((loaded, loading, error) => {
vizState.current = { ...vizState.current, loaded, loading, error };
setLoaded(loaded);
setLoading(loading);
setError(error);
if (setVizState) {
setVizState({ loaded, loading, error });
}
/* eslint-disable-next-line */
}, []);
const onVizStateUpdate = useCallback(
(loaded, loading, error) => {
vizState.current = { ...vizState.current, loaded, loading, error };
setLoaded(loaded);
setLoading(loading);
setError(error);
if (setVizState) {
setVizState({ loaded, loading, error });
}
},
[setVizState],
);

const activateDefaultSheet = useCallback(() => {
if (!vizState.current.loaded || !viz.current) return;
const activateDefaultSheet = useCallback(async () => {
if (!vizState.current.loaded || !viz.current) return Promise.resolve();
const workbook = viz.current.getWorkbook();
const sheetnames = getSheetnames(viz.current);
const activeSheetName = getActiveSheetname(viz.current);
if (sheetnames.includes(sheetname) && sheetname !== activeSheetName) {
workbook.activateSheetAsync(sheetname).then(() => {
onVizStateUpdate(true, false, null);
});
} else {
onVizStateUpdate(true, false, null);
return workbook.activateSheetAsync(sheetname);
}
}, [onVizStateUpdate, sheetname]);
return Promise.resolve();
}, [sheetname]);

const clearData = () => {
const clearData = useCallback(() => {
onChangeBlock(block, {
...data,
filters: {},
parameters: {},
});
};
}, [onChangeBlock, block, data]);

const disposeViz = () => {
const disposeViz = useCallback(() => {
if (viz.current) {
viz.current.dispose();
viz.current = null;
}
onVizStateUpdate(false, false, null);
};
}, [onVizStateUpdate]);

const initViz = () => {
try {
Expand All @@ -265,12 +288,12 @@ const Tableau = forwardRef((props, ref) => {
device: !!breakpointUrl ? device : 'desktop',
...extraOptions,
...data.filters,
...data.parameters,
...extraFilters,
...extraParameters,
onFirstInteractive: () => {
onFirstInteractive: async () => {
onVizStateUpdate(true, true, null);
setInitiateViz(false);
activateDefaultSheet();
await activateDefaultSheet();
if (viz.current && mode === 'edit' && !breakpointUrl) {
const sheetnames = getSheetnames(viz.current);
const activeSheetname = getActiveSheetname(viz.current);
Expand All @@ -282,6 +305,24 @@ const Tableau = forwardRef((props, ref) => {
if (!sheetname || !sheetnames.includes(sheetname)) {
newData.sheetname = activeSheetname;
}
if (newData.url !== url) {
// Get parameters from url
const workbook = viz.current.getWorkbook();
const tableauParameters = await workbook.getParametersAsync();
const searchParams = qs.parse(decodeString(new URL(url).search));
tableauParameters.forEach((param) => {
const name = param.getName();
const dataType = param.getDataType();
if (!searchParams[name]) return;
if (!newData.parameters) {
newData.parameters = {};
}
newData.parameters[name] = getTableauValue(
searchParams[name],
dataType,
);
});
}
if (newData.url !== url || newData.sheetname !== sheetname) {
onChangeBlock(block, {
...data,
Expand Down Expand Up @@ -312,6 +353,8 @@ const Tableau = forwardRef((props, ref) => {
},
);
}
onVizStateUpdate(true, false, null);
setInitiateViz(false);
},
});
} catch (e) {
Expand Down Expand Up @@ -346,9 +389,9 @@ const Tableau = forwardRef((props, ref) => {
} else {
disposeViz();
}
/* eslint-disable-next-line */
}, [tableau, url, hideTabs, hideToolbar, toolbarPosition]);
}, [tableau, url, disposeViz, hideTabs, hideToolbar, toolbarPosition]);

// Initialize viz
useEffect(() => {
if (initiateViz && !loaded && !loading) {
initViz();
Expand Down Expand Up @@ -396,6 +439,7 @@ const Tableau = forwardRef((props, ref) => {
// /* eslint-disable-next-line */
// }, [loaded, JSON.stringify(extraFilters)]);

// Add extra parameters
useEffect(() => {
async function addExtraParameters() {
if (vizState.current.loaded && viz.current) {
Expand All @@ -419,19 +463,7 @@ const Tableau = forwardRef((props, ref) => {
value = value
.filter((v) => includes(values, v))
.map((v) => {
if (dataType === 'string' && !isString(v)) {
return toString(v);
}
if (dataType === 'integer' && !isInteger(v)) {
return toInteger(v);
}
if (dataType === 'float' && !isNumber(v)) {
return toNumber(v);
}
if (dataType === 'boolean' && !isBoolean(v)) {
return !!v;
}
return v;
return getTableauValue(v, dataType);
});
if (value?.length) {
workbook.changeParameterValueAsync(fieldName, value);
Expand All @@ -450,21 +482,26 @@ const Tableau = forwardRef((props, ref) => {
/* eslint-disable-next-line */
}, [loaded, JSON.stringify(extraParameters)]);

// Update viz scale on window resize
useEffect(() => {
if (vizState.current.loaded && viz.current && autoScale) {
updateScale();
}
/* eslint-disable-next-line */
}, [loaded, screen?.page?.width]);

// Activate default sheet
useEffect(() => {
if (vizState.current.loaded && viz.current) {
onVizStateUpdate(true, true, null);
activateDefaultSheet();
activateDefaultSheet().then(() => {
onVizStateUpdate(true, false, null);
});
}
/* eslint-disable-next-line */
}, [sheetname]);

// Set mobile mode
useEffect(() => {
if (!loading && tableauEl.current) {
const visWidth = tableauEl.current.offsetWidth;
Expand All @@ -477,6 +514,7 @@ const Tableau = forwardRef((props, ref) => {
}
}, [screen, mobile, loading]);

// Keep viz reference
useImperativeHandle(
ref,
() => {
Expand Down
6 changes: 4 additions & 2 deletions src/Tableau/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,10 @@ export const getActiveSheetname = (viz) => {
export const canChangeVizData = (viz, vizState) => {
// If viz is null it means that the viz is loading
// If viz is undefined it means that there is no viz nor it is loading
if (vizState?.loading) return false;
return !!viz || isUndefined(viz);
if (vizState?.loading || !viz || isUndefined(viz)) {
return false;
}
return true;
};

export const getDevice = (breakpoints, width) => {
Expand Down
37 changes: 37 additions & 0 deletions src/Views/VisualizationView.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { Provider } from 'react-redux';

import VisualizationView from './VisualizationView';

jest.doMock('@plone/volto/registry', {
blocks: {
blocksConfig: {
embed_tableau_visualization: {
breakpoints: {
desktop: [Infinity, 992],
tablet: [991, 768],
phone: [767, 0],
},
},
},
},
});

describe('VisualizationViewWidget', () => {
it('should render the component', () => {
const { container } = render(
<Provider store={{ ...global.store }}>
<VisualizationView
content={{
tableau_visualization: {
url: 'http://localhost:3000/tableau-ct',
},
}}
/>
</Provider>,
);
expect(container.querySelector('.tableau-wrapper')).toBeInTheDocument();
});
});
Loading

0 comments on commit 5fc1c78

Please sign in to comment.