diff --git a/.changeset/sixty-queens-invent.md b/.changeset/sixty-queens-invent.md new file mode 100644 index 000000000..b5ec0bd1c --- /dev/null +++ b/.changeset/sixty-queens-invent.md @@ -0,0 +1,5 @@ +--- +"@devgateway/dvz-wp-commons": patch +--- + +Implement dataset search input and selector to search from a list of datasets diff --git a/packages/commons/src/DatasetSelector.jsx b/packages/commons/src/DatasetSelector.jsx new file mode 100644 index 000000000..92213ab42 --- /dev/null +++ b/packages/commons/src/DatasetSelector.jsx @@ -0,0 +1,64 @@ +import React, { useState } from "react"; +import { ComboboxControl } from "@wordpress/components"; +import { __ } from "@wordpress/i18n"; + +export const DatasetSelector = (props) => { + const { + setAttributes, + setState, + loadMetadata, + dvzProxyDatasetId, + app, + datasets + } = props; + const [ filterValue, setFilterValue ] = useState(""); + const selectedDataset = (datasets || []).find(dataset => + dataset.value.toString() === dvzProxyDatasetId?.toString() + ); + const filteredOptions = (datasets || []).filter(dataset => { + const searchValue = filterValue.toLowerCase(); + return dataset.label.toLowerCase().includes(searchValue) || + dataset.value.toString().toLowerCase().includes(searchValue); + }); + + return ( +
+ { + setAttributes({ + dvzProxyDatasetId: newDatasetId, + dimension1: "none", + dimension2: "none", + dimension3: "none", + measures: [], + }); + setState({ + dimensions: [], + measures: [], + filters: [], + categories: [], + }); + loadMetadata(app, newDatasetId); + }} + options={filteredOptions} + onFilterValueChange={inputValue => { + setFilterValue(inputValue || ""); + }} + help={__('Select the dataset from the API.')} + /> +
+ ) +} \ No newline at end of file diff --git a/packages/commons/src/index.js b/packages/commons/src/index.js index ccca21455..2ad5a3db1 100644 --- a/packages/commons/src/index.js +++ b/packages/commons/src/index.js @@ -41,4 +41,5 @@ export { export { ChartLegends } from './ChartLegends'; export { GenericIcon, ChartIcon } from './icons/index'; -export { CSVConfig as CSVSourceConfig } from './CSVSourceConfig'; \ No newline at end of file +export { CSVConfig as CSVSourceConfig } from './CSVSourceConfig'; +export { DatasetSelector } from './DatasetSelector'; \ No newline at end of file diff --git a/plugins/wp-react-blocks-plugin/blocks/big-filter/BlockEdit.js b/plugins/wp-react-blocks-plugin/blocks/big-filter/BlockEdit.js index 5e2a73c93..6a1171e45 100644 --- a/plugins/wp-react-blocks-plugin/blocks/big-filter/BlockEdit.js +++ b/plugins/wp-react-blocks-plugin/blocks/big-filter/BlockEdit.js @@ -6,6 +6,7 @@ import { PanelRow, ResizableBox, SelectControl, + ComboboxControl, TextControl, FontSizePicker, __experimentalText as Text, @@ -25,12 +26,20 @@ import { useSelect } from '@wordpress/data'; class BlockEdit extends BlockEditWithAPIMetadata { constructor(props) { super(props); + this.state = { ...(this.state || {}), filteredDatasets: null }; } componentDidMount() { super.componentDidMount() } + componentDidUpdate(prevProps, prevState, snapshot) { + if (this.state.datasets !== prevState.datasets && this.state.filteredDatasets !== null) { + this.setState({ filteredDatasets: null }) + } + super.componentDidUpdate(prevProps, prevState, snapshot) + } + render() { const { className, isSelected, @@ -144,18 +153,30 @@ class BlockEdit extends BlockEditWithAPIMetadata { {isSupersetAPI(app, this.state.apps) && - { - setAttributes({ - dvzProxyDatasetId: newDatasetId - }) - - this.loadMetadata(app, newDatasetId) - }} - options={datasets} - /> +
+ { + setAttributes({ + dvzProxyDatasetId: newDatasetId + }) + + this.loadMetadata(app, newDatasetId) + }} + options={this.state.filteredDatasets || datasets} + isLoading={datasets.length === 0} + onFilterValueChange={(inputValue) => { + const searchValue = (inputValue || '').toLowerCase() + const filteredDatasets = datasets.filter((option) => + option.label.toLowerCase().includes(searchValue) || + option.value.toString().toLowerCase().includes(searchValue) + ) + this.setState({ filteredDatasets }) + }} + /> +
} diff --git a/plugins/wp-react-blocks-plugin/blocks/big-number-trend/BlockEdit.js b/plugins/wp-react-blocks-plugin/blocks/big-number-trend/BlockEdit.js index b284cd8bd..4558b6366 100644 --- a/plugins/wp-react-blocks-plugin/blocks/big-number-trend/BlockEdit.js +++ b/plugins/wp-react-blocks-plugin/blocks/big-number-trend/BlockEdit.js @@ -5,6 +5,7 @@ import { PanelRow, ResizableBox, SelectControl, + ComboboxControl, TextControl, FontSizePicker, __experimentalText as Text, @@ -24,6 +25,7 @@ import { Format } from '@devgateway/dvz-wp-commons'; class BlockEdit extends BlockEditWithAPIMetadata { constructor(props) { super(props); + this.state = { ...(this.state || {}), filteredDatasets: null }; } componentDidMount() { @@ -32,6 +34,9 @@ class BlockEdit extends BlockEditWithAPIMetadata { componentDidUpdate(prevProps, prevState, snapshot) { super.componentDidUpdate(prevProps, prevState, snapshot); + if (this.state.datasets !== prevState.datasets && this.state.filteredDatasets !== null) { + this.setState({ filteredDatasets: null }) + } // The base class sends raw WP attributes, but measures/format/filters/percentChangeFormat // are Array/Object types that the embeddable expects as JSON strings. Send a correction. if (this.iframe && this.iframe.current) { @@ -141,18 +146,30 @@ class BlockEdit extends BlockEditWithAPIMetadata { {isSupersetAPI(app, this.state.apps) && - { - setAttributes({ - dvzProxyDatasetId: newDatasetId - }) - - this.loadMetadata(app, newDatasetId) - }} - options={datasets} - /> +
+ { + setAttributes({ + dvzProxyDatasetId: newDatasetId + }) + + this.loadMetadata(app, newDatasetId) + }} + options={this.state.filteredDatasets || datasets} + isLoading={datasets.length === 0} + onFilterValueChange={(inputValue) => { + const searchValue = (inputValue || '').toLowerCase() + const filteredDatasets = datasets.filter((option) => + option.label.toLowerCase().includes(searchValue) || + option.value.toString().toLowerCase().includes(searchValue) + ) + this.setState({ filteredDatasets }) + }} + /> +
} diff --git a/plugins/wp-react-blocks-plugin/blocks/big-number/BlockEdit.js b/plugins/wp-react-blocks-plugin/blocks/big-number/BlockEdit.js index 14e11a509..952e367c8 100644 --- a/plugins/wp-react-blocks-plugin/blocks/big-number/BlockEdit.js +++ b/plugins/wp-react-blocks-plugin/blocks/big-number/BlockEdit.js @@ -5,6 +5,7 @@ import { PanelRow, ResizableBox, SelectControl, + ComboboxControl, TextControl, FontSizePicker, __experimentalText as Text, @@ -24,12 +25,20 @@ import { Format } from '@devgateway/dvz-wp-commons'; class BlockEdit extends BlockEditWithAPIMetadata { constructor(props) { super(props); + this.state = { ...(this.state || {}), filteredDatasets: null }; } componentDidMount() { super.componentDidMount() } + componentDidUpdate(prevProps, prevState, snapshot) { + if (this.state.datasets !== prevState.datasets && this.state.filteredDatasets !== null) { + this.setState({ filteredDatasets: null }) + } + super.componentDidUpdate(prevProps, prevState, snapshot) + } + render() { const { className, isSelected, @@ -119,18 +128,30 @@ class BlockEdit extends BlockEditWithAPIMetadata { {isSupersetAPI(app, this.state.apps) && - { - setAttributes({ - dvzProxyDatasetId: newDatasetId - }) +
+ { + setAttributes({ + dvzProxyDatasetId: newDatasetId + }) - this.loadMetadata(app, newDatasetId) - }} - options={datasets} - /> + this.loadMetadata(app, newDatasetId) + }} + options={this.state.filteredDatasets || datasets} + isLoading={datasets.length === 0} + onFilterValueChange={(inputValue) => { + const searchValue = (inputValue || '').toLowerCase() + const filteredDatasets = datasets.filter((option) => + option.label.toLowerCase().includes(searchValue) || + option.value.toString().toLowerCase().includes(searchValue) + ) + this.setState({ filteredDatasets }) + }} + /> +
} diff --git a/plugins/wp-react-blocks-plugin/blocks/charts/BlockEdit.js b/plugins/wp-react-blocks-plugin/blocks/charts/BlockEdit.js index ca726249b..bcc71891f 100644 --- a/plugins/wp-react-blocks-plugin/blocks/charts/BlockEdit.js +++ b/plugins/wp-react-blocks-plugin/blocks/charts/BlockEdit.js @@ -9,7 +9,8 @@ import { SelectControl, TextareaControl, TextControl, - ToggleControl + ToggleControl, + ComboboxControl } from '@wordpress/components'; import {InnerBlocks} from '@wordpress/editor'; // or wp.editor @@ -33,6 +34,48 @@ class BlockEdit extends BlockEditWithAPIMetadata { constructor(props) { super(props); this.ignoreAttributes = ['tooltip']; + this.state = { + ...this.state, + datasetFilterValue: '', + filteredDatasets: null + } + } + + componentDidMount() { + super.componentDidMount(); + this.migrateLegacyAttributes(); + } + + migrateLegacyAttributes() { + const {setAttributes, attributes: {measures, app, tooltip}} = this.props; + + if (Object.keys(measures).indexOf('global') > -1) { + const appMeasures = { + [app]: {}, + csv: measures.csv + }; + const count = Object.keys(measures).filter(key => measures[key].selected).length; + + Object.keys(measures) + .filter(key => !['global', 'csv'].includes(key)) + .forEach(key => { + if (measures[key].selected) { + appMeasures[app][key] = measures[key]; + } + if (count === 1) { + appMeasures[app].format = measures[key].format; + } + if (count > 1) { + appMeasures[app].format = measures.global.format; + } + }); + + setAttributes({measures: appMeasures}); + } + + if (typeof tooltip === 'string' && tooltip !== '') { + setAttributes({tooltipHTML: tooltip, tooltip: ''}); + } } componentDidUpdate(prevProps, prevState, snapshot) { @@ -44,6 +87,10 @@ class BlockEdit extends BlockEditWithAPIMetadata { setAttributes({previewMode: newPreviewMode}); } + if (this.state.datasets !== prevState.datasets && this.state.filteredDatasets !== null) { + this.setState({filteredDatasets: null}); + } + if (type !== prevType) { if (type === 'radar') { if (colorBy !== 'id') { @@ -180,38 +227,6 @@ class BlockEdit extends BlockEditWithAPIMetadata { } = this.props; - if (Object.keys(measures).indexOf("global") > -1) { - - //migrating measures - const appMeasures = {} - appMeasures[app] = {} - appMeasures['csv'] = measures['csv'] - const count = Object.keys(measures).filter(k => measures[k].selected).length - Object.keys(measures) - .filter(k => ['global', 'csv'] - .indexOf(k) == -1).forEach(k => { - - if (measures[k].selected) { - appMeasures[app][k] = measures[k] - } - if (count == 1) { - appMeasures[app]['format'] = measures[k].format - } - if (count > 1) { - appMeasures[app]['format'] = measures['global']['format'] - } - - } - ) - setAttributes({measures: appMeasures}) - return null; - } - //migration code - if (tooltip != '') { - setAttributes({tooltipHTML: tooltip, tooltip: ''}) - return null; - } - const levels = [dimension1, dimension2, dimension3] const source = levels.filter(l => l != 'none' && l != null).join('/') @@ -366,9 +381,21 @@ class BlockEdit extends BlockEditWithAPIMetadata { {isSupersetAPI(app, this.state.apps) && - + { this.setState({ @@ -390,8 +417,19 @@ class BlockEdit extends BlockEditWithAPIMetadata { //look at component did update of BlockEditWithAPIMetadata //this.loadMetadata(app, newDatasetId) }} - options={datasets} + options={this.state.filteredDatasets ? this.state.filteredDatasets : datasets} + isLoading={datasets.length === 0} + onFilterValueChange={(inputValue) => { + const searchValue = (inputValue || '').toLowerCase(); + const opts = datasets.filter((option) => + option.label.toLowerCase().includes(searchValue) || + option.value.toString().toLowerCase().includes(searchValue) + ) + this.setState({ filteredDatasets: opts }) + }} /> + + @@ -464,15 +502,15 @@ class BlockEdit extends BlockEditWithAPIMetadata { Value -> {'{value}'} + style={{"font-size": "11px"}}>Value {'->'} {'{value}'} Value Percent -> {'{valuePercent}'} + style={{"font-size": "11px"}}>Value Percent {'->'} {'{valuePercent}'} Category -> {'{category}'} + style={{"font-size": "11px"}}>Category {'->'} {'{category}'} } diff --git a/plugins/wp-react-blocks-plugin/blocks/d3Map/BlockSave.js b/plugins/wp-react-blocks-plugin/blocks/d3Map/BlockSave.js index 9100179e1..5a02f39a8 100644 --- a/plugins/wp-react-blocks-plugin/blocks/d3Map/BlockSave.js +++ b/plugins/wp-react-blocks-plugin/blocks/d3Map/BlockSave.js @@ -17,6 +17,12 @@ const SaveComponent = (props) => { } } = props; + const normalizedLayers = (layers || []).map(layer => ({ + ...layer, + labelSettings: Array.isArray(layer.labelSettings) ? {} : (layer.labelSettings || {}), + customMeasuresLabels: Array.isArray(layer.customMeasuresLabels) ? {} : (layer.customMeasuresLabels || {}) + })); + const blockProps = useBlockProps.save({ className: 'viz component map' }); @@ -34,7 +40,7 @@ const SaveComponent = (props) => { data-component={"newMap"} data-zoom-enabled={zoomEnabled} data-rotation-enabled={rotationEnabled} - data-layers={encodeURIComponent(JSON.stringify(layers))} + data-layers={encodeURIComponent(JSON.stringify(normalizedLayers))} data-wait-for-filters={waitForFilters} > diff --git a/plugins/wp-react-blocks-plugin/blocks/d3Map/layers/Data.jsx b/plugins/wp-react-blocks-plugin/blocks/d3Map/layers/Data.jsx index f4bd8580c..8d8a69c80 100644 --- a/plugins/wp-react-blocks-plugin/blocks/d3Map/layers/Data.jsx +++ b/plugins/wp-react-blocks-plugin/blocks/d3Map/layers/Data.jsx @@ -7,6 +7,7 @@ import { PanelRow, RangeControl, SelectControl, + ComboboxControl, TextareaControl, TextControl, ToggleControl @@ -65,7 +66,7 @@ export class DataLayerSetting extends Component { this.getCSValue = this.getCSValue.bind(this) this.onFormatChange = this.onFormatChange.bind(this) this.state = { - measures: [], dimensions: [], filters: [], categories: [] + measures: [], dimensions: [], filters: [], categories: [], filteredDatasets: null } } @@ -301,14 +302,26 @@ export class DataLayerSetting extends Component { {isSupersetAPI(app, apps) && - { - onChangeProperty("dvzProxyDatasetId", newDatasetId) - }} - options={allDatasets} - /> +
+ { + onChangeProperty("dvzProxyDatasetId", newDatasetId) + }} + options={this.state.filteredDatasets || allDatasets} + isLoading={allDatasets.length === 0} + onFilterValueChange={(inputValue) => { + const searchValue = (inputValue || '').toLowerCase() + const filteredDatasets = allDatasets.filter((option) => + option.label.toLowerCase().includes(searchValue) || + option.value.toString().toLowerCase().includes(searchValue) + ) + this.setState({ filteredDatasets }) + }} + /> +
} {type != 'dataPoints' && {isSupersetAPI(app, apps) && - { - onChangeProperty("dvzProxyDatasetId", newDatasetId) - }} - options={allDatasets} - /> +
+ { + onChangeProperty("dvzProxyDatasetId", newDatasetId) + }} + options={this.state.filteredDatasets || allDatasets} + isLoading={allDatasets.length === 0} + onFilterValueChange={(inputValue) => { + const searchValue = (inputValue || '').toLowerCase() + const filteredDatasets = allDatasets.filter((option) => + option.label.toLowerCase().includes(searchValue) || + option.value.toString().toLowerCase().includes(searchValue) + ) + this.setState({ filteredDatasets }) + }} + /> +
} {type != 'dataPoints' && {isSupersetAPI(app, apps) && - { - onChangeProperty("dvzProxyDatasetId", newDatasetId) - }} - options={allDatasets} - /> +
+ { + onChangeProperty("dvzProxyDatasetId", newDatasetId) + }} + options={this.state.filteredDatasets || allDatasets} + isLoading={allDatasets.length === 0} + onFilterValueChange={(inputValue) => { + const searchValue = (inputValue || '').toLowerCase() + const filteredDatasets = allDatasets.filter((option) => + option.label.toLowerCase().includes(searchValue) || + option.value.toString().toLowerCase().includes(searchValue) + ) + this.setState({ filteredDatasets }) + }} + /> +
} diff --git a/plugins/wp-react-blocks-plugin/blocks/filter/BlockEdit.js b/plugins/wp-react-blocks-plugin/blocks/filter/BlockEdit.js index fd19d98bb..444623eee 100644 --- a/plugins/wp-react-blocks-plugin/blocks/filter/BlockEdit.js +++ b/plugins/wp-react-blocks-plugin/blocks/filter/BlockEdit.js @@ -1,5 +1,5 @@ import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; -import { Panel, PanelBody, PanelRow, SelectControl, TextControl, ToggleControl, Button } from '@wordpress/components'; +import { Panel, PanelBody, PanelRow, SelectControl, TextControl, ToggleControl, Button, ComboboxControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { BlockEditWithAPIMetadata } from '@devgateway/dvz-wp-commons' import { isSupersetAPI } from '@devgateway/dvz-wp-commons'; @@ -95,7 +95,11 @@ class BlockEdit extends BlockEditWithAPIMetadata { this.selectDefaultValue = this.selectDefaultValue.bind(this) // Add a key to force iframe reload on attribute changes - this.state = { ...(this.state || {}), iframeReloadKey: 0 } + this.state = { + ...(this.state || {}), + iframeReloadKey: 0, + filteredDatasets: null + } } updateHiddenFilters(value, idx) { @@ -139,6 +143,10 @@ class BlockEdit extends BlockEditWithAPIMetadata { this.setState({ iframeReloadKey: (this.state.iframeReloadKey || 0) + 1 }) } + if (this.state.datasets !== prevState.datasets && this.state.filteredDatasets !== null) { + this.setState({ filteredDatasets: null }) + } + if (attributes && attributes.filterType === 'single-select' && attributes.defaultTopNEnabled && attributes.defaultTopNCount > 1) { setAttributes({ defaultTopNCount: 1 }) } @@ -233,23 +241,34 @@ class BlockEdit extends BlockEditWithAPIMetadata { {isSupersetAPI(app, this.state.apps) && - - { - setAttributes({ - dvzProxyDatasetId: newDatasetId, - dimension1: 'none', - dimension2: 'none' - - }) - this.setState({ dimensions: [], measures: [], filters: [], categories: [] }) - // this.loadMetadataForSuperset(app, newDatasetId) - }} - options={datasets} - help={__('Select the dataset from the API.')} - /> +
+ { + setAttributes({ + dvzProxyDatasetId: newDatasetId, + dimension1: 'none', + dimension2: 'none' + + }) + this.setState({ dimensions: [], measures: [], filters: [], categories: [] }) + // this.loadMetadataForSuperset(app, newDatasetId) + }} + options={this.state.filteredDatasets || datasets} + isLoading={datasets.length === 0} + onFilterValueChange={(inputValue) => { + const searchValue = (inputValue || '').toLowerCase() + const filteredDatasets = datasets.filter((option) => + option.label.toLowerCase().includes(searchValue) || + option.value.toString().toLowerCase().includes(searchValue) + ) + this.setState({ filteredDatasets }) + }} + help={__('Select the dataset from the API.')} + /> +
} diff --git a/plugins/wp-react-blocks-plugin/blocks/map/BlockEdit.js b/plugins/wp-react-blocks-plugin/blocks/map/BlockEdit.js index 7fb70556e..f06fabb94 100644 --- a/plugins/wp-react-blocks-plugin/blocks/map/BlockEdit.js +++ b/plugins/wp-react-blocks-plugin/blocks/map/BlockEdit.js @@ -9,6 +9,7 @@ import { ToggleControl, TextControl, Button, + ComboboxControl, } from "@wordpress/components"; import { PanelColorSettings } from "@wordpress/block-editor"; import { __ } from "@wordpress/i18n"; @@ -16,6 +17,7 @@ import { BlockEditWithAPIMetadata, isSupersetAPI, MapCSVSourceConfig, + DatasetSelector } from "@devgateway/dvz-wp-commons"; import APIConfig from "./APIConfig"; import LegendBreaks from "./LegendBreaks"; @@ -31,6 +33,7 @@ class BlockEdit extends BlockEditWithAPIMetadata { types: "media", taxonomies: null, loading: true, + datasetFilter: null, }; this.iframe = React.createRef(); @@ -544,6 +547,7 @@ class BlockEdit extends BlockEditWithAPIMetadata { const divStyles = { height: height + "px", width: "100%" }; + return [ isSelected && ( @@ -583,26 +587,13 @@ class BlockEdit extends BlockEditWithAPIMetadata { {isSupersetAPI(app, this.state.apps) && ( - { - setAttributes({ - dvzProxyDatasetId: newDatasetId, - dimension1: "none", - dimension2: "none", - dimension3: "none", - measures: [], - }); - this.setState({ - dimensions: [], - measures: [], - filters: [], - categories: [], - }); - this.loadMetadata(app, newDatasetId); - }} - options={datasets} + )} diff --git a/plugins/wp-react-blocks-plugin/blocks/package.json b/plugins/wp-react-blocks-plugin/blocks/package.json index 800d964ce..db2838689 100644 --- a/plugins/wp-react-blocks-plugin/blocks/package.json +++ b/plugins/wp-react-blocks-plugin/blocks/package.json @@ -39,7 +39,7 @@ "start": "wp-scripts start" }, "dependencies": { - "@devgateway/dvz-wp-commons": "workspace:@devgateway/dvz-wp-commons@*", + "@devgateway/dvz-wp-commons": "workspace:*", "@types/react": "18", "@wordpress/a11y": "^4.32.0", "@wordpress/api-fetch": "^7.32.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c7b170e2..2a557cb6a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -154,7 +154,7 @@ importers: plugins/wp-react-blocks-plugin/blocks: dependencies: '@devgateway/dvz-wp-commons': - specifier: workspace:@devgateway/dvz-wp-commons@* + specifier: workspace:* version: link:../../../packages/commons '@types/react': specifier: '18'